From c4a482eac82eb6f2f8f1645987b156359693c0a2 Mon Sep 17 00:00:00 2001 From: Phillip Thelen Date: Wed, 16 Nov 2022 14:02:58 +0100 Subject: [PATCH] Remove RxJava from all repositories --- Habitica/AndroidManifest.xml | 3 - .../items/ItemRecyclerFragmentTest.kt | 8 +- .../inventory/stable/PetDetailFragmentTest.kt | 8 +- .../android/habitica/api/ApiService.kt | 165 +++--- .../habitica/api/MaintenanceApiService.java | 16 - .../habitica/api/MaintenanceApiService.kt | 12 + .../habitica/components/UserComponent.java | 3 - .../android/habitica/data/ApiClient.kt | 165 +++--- .../habitica/data/ChallengeRepository.kt | 34 +- .../habitica/data/ContentRepository.kt | 4 +- .../habitica/data/CustomizationRepository.kt | 4 +- .../android/habitica/data/FAQRepository.kt | 6 +- .../habitica/data/InventoryRepository.kt | 44 +- .../android/habitica/data/SocialRepository.kt | 57 +- .../android/habitica/data/TagRepository.kt | 19 +- .../android/habitica/data/TaskRepository.kt | 28 +- .../habitica/data/TutorialRepository.kt | 6 +- .../android/habitica/data/UserRepository.kt | 36 +- .../data/implementation/ApiClientImpl.kt | 359 ++++++------ .../implementation/ChallengeRepositoryImpl.kt | 100 ++-- .../implementation/ContentRepositoryImpl.kt | 4 +- .../CustomizationRepositoryImpl.kt | 4 +- .../data/implementation/FAQRepositoryImpl.kt | 6 +- .../implementation/InventoryRepositoryImpl.kt | 137 +++-- .../implementation/SocialRepositoryImpl.kt | 145 +++-- .../data/implementation/TagRepositoryImpl.kt | 63 +-- .../data/implementation/TaskRepositoryImpl.kt | 141 +++-- .../implementation/TutorialRepositoryImpl.kt | 6 +- .../data/implementation/UserRepositoryImpl.kt | 205 +++---- .../data/local/ChallengeLocalRepository.kt | 16 +- .../data/local/ContentLocalRepository.kt | 3 +- .../local/CustomizationLocalRepository.kt | 4 +- .../habitica/data/local/FAQLocalRepository.kt | 6 +- .../data/local/InventoryLocalRepository.kt | 29 +- .../data/local/SocialLocalRepository.kt | 9 +- .../habitica/data/local/TagLocalRepository.kt | 3 +- .../data/local/TaskLocalRepository.kt | 9 +- .../data/local/TutorialLocalRepository.kt | 6 +- .../data/local/UserLocalRepository.kt | 11 +- .../RealmChallengeLocalRepository.kt | 69 ++- .../RealmContentLocalRepository.kt | 16 +- .../RealmCustomizationLocalRepository.kt | 14 +- .../implementation/RealmFAQLocalRepository.kt | 26 +- .../RealmInventoryLocalRepository.kt | 361 ++++++------ .../RealmSocialLocalRepository.kt | 30 +- .../implementation/RealmTagLocalRepository.kt | 10 +- .../RealmTaskLocalRepository.kt | 47 +- .../RealmTutorialLocalRepository.kt | 29 +- .../RealmUserLocalRepository.kt | 64 +-- .../habitica/helpers/AppConfigManager.kt | 12 +- .../habitica/helpers/ExceptionHandler.kt | 6 +- .../habitica/helpers/NotificationsManager.kt | 41 +- .../habitica/helpers/PurchaseHandler.kt | 97 ++-- .../habitica/helpers/UserStatComputer.kt | 4 +- .../notifications/PushNotificationManager.kt | 11 +- .../interactors/CheckClassSelectionUseCase.kt | 30 +- .../interactors/DisplayItemDropUseCase.kt | 53 +- .../habitica/interactors/FeedPetUseCase.kt | 110 ++-- .../habitica/interactors/HatchPetUseCase.kt | 20 +- .../habitica/interactors/LevelUpUseCase.kt | 135 +++-- .../habitica/interactors/NotifyUserUseCase.kt | 42 +- .../android/habitica/interactors/UseCase.kt | 4 +- .../LocalNotificationActionReceiver.kt | 33 +- .../receivers/NotificationPublisher.kt | 39 +- .../habitica/ui/activities/ArmoireActivity.kt | 6 +- .../habitica/ui/activities/BaseActivity.kt | 24 +- .../ui/activities/ChallengeFormActivity.kt | 160 +++--- .../ui/activities/FullProfileActivity.kt | 184 ++++-- .../activities/HabitButtonWidgetActivity.kt | 7 +- .../habitica/ui/activities/LoginActivity.kt | 38 +- .../habitica/ui/activities/MainActivity.kt | 49 +- .../ui/activities/MaintenanceActivity.kt | 24 +- .../ui/activities/NotificationsActivity.kt | 16 +- .../ui/activities/ReportMessageActivity.kt | 15 +- .../habitica/ui/activities/SetupActivity.kt | 10 +- .../ui/activities/SkillMemberActivity.kt | 12 +- .../ui/activities/SkillTasksActivity.kt | 7 +- .../ui/activities/TaskFormActivity.kt | 274 +++++---- .../ui/activities/VerifyUsernameActivity.kt | 135 ----- .../adapter/SkillTasksRecyclerViewAdapter.kt | 11 +- .../inventory/EquipmentRecyclerViewAdapter.kt | 7 +- .../adapter/inventory/ItemRecyclerAdapter.kt | 56 +- .../inventory/MountDetailRecyclerAdapter.kt | 9 +- .../inventory/PetDetailRecyclerAdapter.kt | 16 +- .../inventory/StableRecyclerAdapter.kt | 14 +- .../setup/CustomizationSetupAdapter.kt | 8 +- .../social/PartyMemberRecyclerViewAdapter.kt | 11 +- .../ChallengeTasksRecyclerViewAdapter.kt | 29 +- .../ui/fragments/AchievementsFragment.kt | 11 +- .../ui/fragments/BaseDialogFragment.kt | 36 +- .../habitica/ui/fragments/BaseFragment.kt | 34 +- .../ui/fragments/NavigationDrawerFragment.kt | 314 ++++++++--- .../habitica/ui/fragments/StatsFragment.kt | 126 ++--- .../AvatarCustomizationFragment.kt | 56 +- .../equipment/EquipmentDetailFragment.kt | 20 +- .../inventory/items/ItemDialogFragment.kt | 94 ++-- .../inventory/items/ItemRecyclerFragment.kt | 83 ++- .../fragments/inventory/shops/ShopFragment.kt | 120 ++-- .../stable/MountDetailRecyclerFragment.kt | 14 +- .../stable/PetDetailRecyclerFragment.kt | 40 +- .../stable/StableRecyclerFragment.kt | 11 +- .../preferences/AccountPreferenceFragment.kt | 98 ++-- .../preferences/PreferencesFragment.kt | 8 +- .../purchases/GiftBalanceGemsFragment.kt | 21 +- .../purchases/SubscriptionFragment.kt | 25 +- .../ui/fragments/setup/AvatarSetupFragment.kt | 7 +- .../ui/fragments/setup/WelcomeFragment.kt | 94 +++- .../skills/SkillTasksRecyclerViewFragment.kt | 19 +- .../ui/fragments/skills/SkillsFragment.kt | 28 +- .../social/InboxMessageListFragment.kt | 7 +- .../fragments/social/InboxOverviewFragment.kt | 18 +- .../fragments/social/QuestDetailFragment.kt | 90 +-- .../challenges/ChallengeDetailFragment.kt | 108 ++-- .../challenges/ChallengeListFragment.kt | 87 +-- .../social/guilds/GuildDetailFragment.kt | 13 +- .../social/guilds/GuildListFragment.kt | 40 +- .../social/party/NoPartyFragmentFragment.kt | 5 +- .../social/party/PartyDetailFragment.kt | 22 +- .../ui/fragments/support/FAQDetailFragment.kt | 18 +- .../fragments/support/FAQOverviewFragment.kt | 41 +- .../tasks/RewardsRecyclerviewFragment.kt | 13 +- .../tasks/TaskRecyclerViewFragment.kt | 529 ++++++++++-------- .../ui/fragments/tasks/TasksFragment.kt | 18 +- .../ui/helpers/AutocompleteAdapter.kt | 4 +- .../ui/viewHolders/MountViewHolder.kt | 7 +- .../habitica/ui/viewHolders/PetViewHolder.kt | 13 +- .../ui/viewmodels/AuthenticationViewModel.kt | 63 +-- .../habitica/ui/viewmodels/BaseViewModel.kt | 4 - .../habitica/ui/viewmodels/GroupViewModel.kt | 112 ++-- .../ui/viewmodels/MainActivityViewModel.kt | 56 +- .../ui/viewmodels/NotificationsViewModel.kt | 96 ++-- .../habitica/ui/viewmodels/PartyViewModel.kt | 29 +- .../habitica/ui/viewmodels/StableViewModel.kt | 7 +- .../habitica/ui/viewmodels/TasksViewModel.kt | 7 +- .../equipment/EquipmentOverviewViewModel.kt | 11 +- .../ui/views/dialogs/PetSuggestHatchDialog.kt | 13 +- .../habitica/ui/views/shops/PurchaseDialog.kt | 11 +- .../ui/views/stats/BulkAllocateStatsDialog.kt | 24 +- .../ui/views/tasks/TaskFilterDialog.kt | 25 +- .../views/yesterdailies/YesterdailyDialog.kt | 119 ++-- .../widget/AvatarStatsWidgetProvider.kt | 22 +- 141 files changed, 3548 insertions(+), 3652 deletions(-) delete mode 100644 Habitica/src/main/java/com/habitrpg/android/habitica/api/MaintenanceApiService.java create mode 100644 Habitica/src/main/java/com/habitrpg/android/habitica/api/MaintenanceApiService.kt delete mode 100644 Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/VerifyUsernameActivity.kt diff --git a/Habitica/AndroidManifest.xml b/Habitica/AndroidManifest.xml index 39e9edf19..ed0a0b018 100644 --- a/Habitica/AndroidManifest.xml +++ b/Habitica/AndroidManifest.xml @@ -204,9 +204,6 @@ android:theme="@style/Theme.AppCompat.Light.NoActionBar" /> - diff --git a/Habitica/src/androidTest/java/com/habitrpg/android/habitica/ui/fragments/inventory/items/ItemRecyclerFragmentTest.kt b/Habitica/src/androidTest/java/com/habitrpg/android/habitica/ui/fragments/inventory/items/ItemRecyclerFragmentTest.kt index d4806812c..ca01795b6 100644 --- a/Habitica/src/androidTest/java/com/habitrpg/android/habitica/ui/fragments/inventory/items/ItemRecyclerFragmentTest.kt +++ b/Habitica/src/androidTest/java/com/habitrpg/android/habitica/ui/fragments/inventory/items/ItemRecyclerFragmentTest.kt @@ -134,7 +134,7 @@ internal class ItemRecyclerFragmentTest : FragmentTestCase() - every { hatchPetUseCase.observable(capture(slot)) } returns mockk(relaxed = true) + every { hatchPetUseCase.callInteractor(capture(slot)) } returns mockk(relaxed = true) fragment.itemType = "eggs" launchFragment() screen { @@ -142,7 +142,7 @@ internal class ItemRecyclerFragmentTest : FragmentTestCase { withDescendant { withText("Wolf") } }.click() KView { withText(R.string.hatch_with_potion) }.click() KView { withText("Shade") }.click() - verify { hatchPetUseCase.observable(any()) } + verify { hatchPetUseCase.callInteractor(any()) } slot.captured.egg.key shouldBe "Wolf" slot.captured.potion.key shouldBe "Shade" } @@ -152,7 +152,7 @@ internal class ItemRecyclerFragmentTest : FragmentTestCase() - every { hatchPetUseCase.observable(capture(slot)) } returns mockk(relaxed = true) + every { hatchPetUseCase.callInteractor(capture(slot)) } returns mockk(relaxed = true) fragment.itemType = "hatchingPotions" launchFragment() screen { @@ -160,7 +160,7 @@ internal class ItemRecyclerFragmentTest : FragmentTestCase { withDescendant { withText("Shade") } }.click() KView { withText(R.string.hatch_egg) }.click() KView { withText("Wolf") }.click() - verify { hatchPetUseCase.observable(any()) } + verify { hatchPetUseCase.callInteractor(any()) } slot.captured.egg.key shouldBe "Wolf" slot.captured.potion.key shouldBe "Shade" } diff --git a/Habitica/src/androidTest/java/com/habitrpg/android/habitica/ui/fragments/inventory/stable/PetDetailFragmentTest.kt b/Habitica/src/androidTest/java/com/habitrpg/android/habitica/ui/fragments/inventory/stable/PetDetailFragmentTest.kt index 78509ba7e..b44295720 100644 --- a/Habitica/src/androidTest/java/com/habitrpg/android/habitica/ui/fragments/inventory/stable/PetDetailFragmentTest.kt +++ b/Habitica/src/androidTest/java/com/habitrpg/android/habitica/ui/fragments/inventory/stable/PetDetailFragmentTest.kt @@ -69,7 +69,7 @@ internal class PetDetailRecyclerFragmentTest : @Test fun canFeedPet() { val slot = CapturingSlot() - every { feedPetUseCase.observable(capture(slot)) } returns mockk(relaxed = true) + every { feedPetUseCase.callInteractor(capture(slot)) } returns mockk(relaxed = true) every { inventoryRepository.getPets( any(), @@ -92,7 +92,7 @@ internal class PetDetailRecyclerFragmentTest : childWith { withContentDescription("Skeleton Cactus") }.click() KView { withText(R.string.feed) }.click() KView { withText("Meat") }.click() - verify { feedPetUseCase.observable(any()) } + verify { feedPetUseCase.callInteractor(any()) } slot.captured.pet.key shouldBe "Cactus-Skeleton" slot.captured.food.key shouldBe "Meat" } @@ -102,7 +102,7 @@ internal class PetDetailRecyclerFragmentTest : @Test fun canUseSaddle() { val slot = CapturingSlot() - every { feedPetUseCase.observable(capture(slot)) } returns mockk(relaxed = true) + every { feedPetUseCase.callInteractor(capture(slot)) } returns mockk(relaxed = true) every { inventoryRepository.getPets( any(), @@ -122,7 +122,7 @@ internal class PetDetailRecyclerFragmentTest : recycler { childWith { withContentDescription("Shade Fox") }.click() KView { withText(R.string.use_saddle) }.click() - verify { feedPetUseCase.observable(any()) } + verify { feedPetUseCase.callInteractor(any()) } slot.captured.pet.key shouldBe "Fox-Shade" slot.captured.food.key shouldBe "Saddle" } 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 8fb909988..e56a215b0 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 @@ -36,7 +36,6 @@ import com.habitrpg.shared.habitica.models.responses.FeedResponse import com.habitrpg.shared.habitica.models.responses.Status import com.habitrpg.shared.habitica.models.responses.TaskDirectionData import com.habitrpg.shared.habitica.models.responses.VerifyUsernameResponse -import io.reactivex.rxjava3.core.Flowable import retrofit2.http.Body import retrofit2.http.DELETE import retrofit2.http.GET @@ -60,7 +59,7 @@ interface ApiService { @GET("inbox/messages") suspend fun getInboxMessages(@Query("conversation") uuid: String, @Query("page") page: Int): HabitResponse> @GET("inbox/conversations") - fun getInboxConversations(): Flowable>> + suspend fun getInboxConversations(): HabitResponse> @GET("tasks/user") suspend fun getTasks(): HabitResponse @@ -75,13 +74,13 @@ interface ApiService { suspend fun updateUser(@Body updateDictionary: Map): HabitResponse @PUT("user/") - fun registrationLanguage(@Header("Accept-Language") registrationLanguage: String): Flowable> + suspend fun registrationLanguage(@Header("Accept-Language") registrationLanguage: String): HabitResponse @GET("user/in-app-rewards") suspend fun retrieveInAppRewards(): HabitResponse> @POST("user/equip/{type}/{key}") - fun equipItem(@Path("type") type: String, @Path("key") itemKey: String): Flowable> + suspend fun equipItem(@Path("type") type: String, @Path("key") itemKey: String): HabitResponse @POST("user/buy/{key}") suspend fun buyItem(@Path("key") itemKey: String, @Body quantity: Map): HabitResponse @@ -106,72 +105,72 @@ interface ApiService { suspend fun purchaseSpecialSpell(@Path("key") key: String): HabitResponse @POST("user/sell/{type}/{key}") - fun sellItem(@Path("type") itemType: String, @Path("key") itemKey: String): Flowable> + suspend fun sellItem(@Path("type") itemType: String, @Path("key") itemKey: String): HabitResponse @POST("user/feed/{pet}/{food}") - fun feedPet(@Path("pet") petKey: String, @Path("food") foodKey: String): Flowable> + suspend fun feedPet(@Path("pet") petKey: String, @Path("food") foodKey: String): HabitResponse @POST("user/hatch/{egg}/{hatchingPotion}") - fun hatchPet(@Path("egg") eggKey: String, @Path("hatchingPotion") hatchingPotionKey: String): Flowable> + suspend fun hatchPet(@Path("egg") eggKey: String, @Path("hatchingPotion") hatchingPotionKey: String): HabitResponse @GET("tasks/user") - fun getTasks(@Query("type") type: String): Flowable> + suspend fun getTasks(@Query("type") type: String): HabitResponse @GET("tasks/user") - fun getTasks(@Query("type") type: String, @Query("dueDate") dueDate: String): Flowable> + suspend fun getTasks(@Query("type") type: String, @Query("dueDate") dueDate: String): HabitResponse @POST("user/unlock") suspend fun unlockPath(@Query("path") path: String): HabitResponse @GET("tasks/{id}") - fun getTask(@Path("id") id: String): Flowable> + suspend fun getTask(@Path("id") id: String): HabitResponse @POST("tasks/{id}/score/{direction}") suspend fun postTaskDirection(@Path("id") id: String, @Path("direction") direction: String): HabitResponse @POST("tasks/bulk-score") - fun bulkScoreTasks(@Body data: List>): Flowable> + suspend fun bulkScoreTasks(@Body data: List>): HabitResponse @POST("tasks/{id}/move/to/{position}") - fun postTaskNewPosition(@Path("id") id: String, @Path("position") position: Int): Flowable>> + suspend fun postTaskNewPosition(@Path("id") id: String, @Path("position") position: Int): HabitResponse> @POST("tasks/{taskId}/checklist/{itemId}/score") suspend fun scoreChecklistItem(@Path("taskId") taskId: String, @Path("itemId") itemId: String): HabitResponse @POST("tasks/user") - fun createTask(@Body item: Task): Flowable> + suspend fun createTask(@Body item: Task): HabitResponse @POST("tasks/user") - fun createTasks(@Body tasks: List): Flowable>> + suspend fun createTasks(@Body tasks: List): HabitResponse> @PUT("tasks/{id}") - fun updateTask(@Path("id") id: String, @Body item: Task): Flowable> + suspend fun updateTask(@Path("id") id: String, @Body item: Task): HabitResponse @DELETE("tasks/{id}") - fun deleteTask(@Path("id") id: String): Flowable> + suspend fun deleteTask(@Path("id") id: String): HabitResponse @POST("tags") - fun createTag(@Body tag: Tag): Flowable> + suspend fun createTag(@Body tag: Tag): HabitResponse @PUT("tags/{id}") - fun updateTag(@Path("id") id: String, @Body tag: Tag): Flowable> + suspend fun updateTag(@Path("id") id: String, @Body tag: Tag): HabitResponse @DELETE("tags/{id}") - fun deleteTag(@Path("id") id: String): Flowable> + suspend fun deleteTag(@Path("id") id: String): HabitResponse @POST("user/auth/local/register") - fun registerUser(@Body auth: UserAuth): Flowable> + suspend fun registerUser(@Body auth: UserAuth): HabitResponse @POST("user/auth/local/login") - fun connectLocal(@Body auth: UserAuth): Flowable> + suspend fun connectLocal(@Body auth: UserAuth): HabitResponse @POST("user/auth/social") - fun connectSocial(@Body auth: UserAuthSocial): Flowable> + suspend fun connectSocial(@Body auth: UserAuthSocial): HabitResponse @DELETE("user/auth/social/{network}") - fun disconnectSocial(@Path("network") network: String): Flowable> + suspend fun disconnectSocial(@Path("network") network: String): HabitResponse @POST("user/auth/apple") - fun loginApple(@Body auth: Map): Flowable> + suspend fun loginApple(@Body auth: Map): HabitResponse @POST("user/sleep") suspend fun sleep(): HabitResponse @@ -199,12 +198,12 @@ interface ApiService { suspend fun disableClasses(): HabitResponse @POST("user/mark-pms-read") - fun markPrivateMessagesRead(): Flowable + suspend fun markPrivateMessagesRead(): Void /* Group API */ @GET("groups") - fun listGroups(@Query("type") type: String): Flowable>> + suspend fun listGroups(@Query("type") type: String): HabitResponse> @GET("groups/{gid}") suspend fun getGroup(@Path("gid") groupId: String): HabitResponse @@ -228,13 +227,13 @@ interface ApiService { suspend fun leaveGroup(@Path("gid") groupId: String, @Query("keepChallenges") keepChallenges: String): HabitResponse @POST("groups/{gid}/chat") - fun postGroupChat(@Path("gid") groupId: String, @Body message: Map): Flowable> + suspend fun postGroupChat(@Path("gid") groupId: String, @Body message: Map): HabitResponse @DELETE("groups/{gid}/chat/{messageId}") - fun deleteMessage(@Path("gid") groupId: String, @Path("messageId") messageId: String): Flowable> + suspend fun deleteMessage(@Path("gid") groupId: String, @Path("messageId") messageId: String): HabitResponse @DELETE("inbox/messages/{messageId}") - fun deleteInboxMessage(@Path("messageId") messageId: String): Flowable> + suspend fun deleteInboxMessage(@Path("messageId") messageId: String): HabitResponse @GET("groups/{gid}/members") suspend fun getGroupMembers( @@ -251,59 +250,59 @@ interface ApiService { // Like returns the full chat list @POST("groups/{gid}/chat/{mid}/like") - fun likeMessage(@Path("gid") groupId: String, @Path("mid") mid: String): Flowable> + suspend fun likeMessage(@Path("gid") groupId: String, @Path("mid") mid: String): HabitResponse @POST("groups/{gid}/chat/{mid}/flag") - fun flagMessage( + suspend fun flagMessage( @Path("gid") groupId: String, @Path("mid") mid: String, @Body data: Map - ): Flowable> + ): HabitResponse @POST("groups/{gid}/chat/seen") - fun seenMessages(@Path("gid") groupId: String): Flowable> + suspend fun seenMessages(@Path("gid") groupId: String): HabitResponse @POST("groups/{gid}/invite") - fun inviteToGroup(@Path("gid") groupId: String, @Body inviteData: Map): Flowable>> + suspend fun inviteToGroup(@Path("gid") groupId: String, @Body inviteData: Map): HabitResponse> @POST("groups/{gid}/reject-invite") - fun rejectGroupInvite(@Path("gid") groupId: String): Flowable> + suspend fun rejectGroupInvite(@Path("gid") groupId: String): HabitResponse @POST("groups/{gid}/quests/accept") - fun acceptQuest(@Path("gid") groupId: String): Flowable> + suspend fun acceptQuest(@Path("gid") groupId: String): HabitResponse @POST("groups/{gid}/quests/reject") - fun rejectQuest(@Path("gid") groupId: String): Flowable> + suspend fun rejectQuest(@Path("gid") groupId: String): HabitResponse @POST("groups/{gid}/quests/cancel") - fun cancelQuest(@Path("gid") groupId: String): Flowable> + suspend fun cancelQuest(@Path("gid") groupId: String): HabitResponse @POST("groups/{gid}/quests/force-start") - fun forceStartQuest(@Path("gid") groupId: String, @Body group: Group): Flowable> + suspend fun forceStartQuest(@Path("gid") groupId: String, @Body group: Group): HabitResponse @POST("groups/{gid}/quests/invite/{questKey}") - fun inviteToQuest(@Path("gid") groupId: String, @Path("questKey") questKey: String): Flowable> + suspend fun inviteToQuest(@Path("gid") groupId: String, @Path("questKey") questKey: String): HabitResponse @POST("groups/{gid}/quests/abort") - fun abortQuest(@Path("gid") groupId: String): Flowable> + suspend fun abortQuest(@Path("gid") groupId: String): HabitResponse @POST("groups/{gid}/quests/leave") - fun leaveQuest(@Path("gid") groupId: String): Flowable> + suspend fun leaveQuest(@Path("gid") groupId: String): HabitResponse @POST("/iap/android/verify") - fun validatePurchase(@Body request: PurchaseValidationRequest): Flowable> + suspend fun validatePurchase(@Body request: PurchaseValidationRequest): HabitResponse @POST("/iap/android/subscribe") - fun validateSubscription(@Body request: PurchaseValidationRequest): Flowable> + suspend fun validateSubscription(@Body request: PurchaseValidationRequest): HabitResponse @GET("/iap/android/subscribe/cancel") suspend fun cancelSubscription(): HabitResponse @POST("/iap/android/norenew-subscribe") - fun validateNoRenewSubscription(@Body request: PurchaseValidationRequest): Flowable> + suspend fun validateNoRenewSubscription(@Body request: PurchaseValidationRequest): HabitResponse @POST("user/custom-day-start") - fun changeCustomDayStart(@Body updateObject: Map): Flowable> + suspend fun changeCustomDayStart(@Body updateObject: Map): HabitResponse // Members URL @GET("members/{mid}") @@ -313,128 +312,128 @@ interface ApiService { suspend fun getMemberWithUsername(@Path("username") username: String): HabitResponse @GET("members/{mid}/achievements") - fun getMemberAchievements(@Path("mid") memberId: String, @Query("lang") language: String?): Flowable>> + suspend fun getMemberAchievements(@Path("mid") memberId: String, @Query("lang") language: String?): HabitResponse> @POST("members/send-private-message") suspend fun postPrivateMessage(@Body messageDetails: Map): HabitResponse @GET("members/find/{username}") - fun findUsernames( + suspend fun findUsernames( @Path("username") username: String, @Query("context") context: String?, @Query("id") id: String? - ): Flowable>> + ): HabitResponse> @POST("members/flag-private-message/{mid}") - fun flagInboxMessage(@Path("mid") mid: String, @Body data: Map): Flowable> + suspend fun flagInboxMessage(@Path("mid") mid: String, @Body data: Map): HabitResponse @GET("shops/{identifier}") - fun retrieveShopInventory(@Path("identifier") identifier: String, @Query("lang") language: String?): Flowable> + suspend fun retrieveShopInventory(@Path("identifier") identifier: String, @Query("lang") language: String?): HabitResponse @GET("shops/market-gear") - fun retrieveMarketGear(@Query("lang") language: String?): Flowable> + suspend fun retrieveMarketGear(@Query("lang") language: String?): HabitResponse // Push notifications @POST("user/push-devices") - fun addPushDevice(@Body pushDeviceData: Map): Flowable>> + suspend fun addPushDevice(@Body pushDeviceData: Map): HabitResponse> @DELETE("user/push-devices/{regId}") - fun deletePushDevice(@Path("regId") regId: String): Flowable>> + suspend fun deletePushDevice(@Path("regId") regId: String): HabitResponse> /* challenges api */ @GET("challenges/user") - fun getUserChallenges(@Query("page") page: Int?, @Query("member") memberOnly: Boolean): Flowable>> + suspend fun getUserChallenges(@Query("page") page: Int?, @Query("member") memberOnly: Boolean): HabitResponse> @GET("challenges/user") - fun getUserChallenges(@Query("page") page: Int?): Flowable>> + suspend fun getUserChallenges(@Query("page") page: Int?): HabitResponse> @GET("tasks/challenge/{challengeId}") - fun getChallengeTasks(@Path("challengeId") challengeId: String): Flowable> + suspend fun getChallengeTasks(@Path("challengeId") challengeId: String): HabitResponse @GET("challenges/{challengeId}") - fun getChallenge(@Path("challengeId") challengeId: String): Flowable> + suspend fun getChallenge(@Path("challengeId") challengeId: String): HabitResponse @POST("challenges/{challengeId}/join") - fun joinChallenge(@Path("challengeId") challengeId: String): Flowable> + suspend fun joinChallenge(@Path("challengeId") challengeId: String): HabitResponse @POST("challenges/{challengeId}/leave") - fun leaveChallenge(@Path("challengeId") challengeId: String, @Body body: LeaveChallengeBody): Flowable> + suspend fun leaveChallenge(@Path("challengeId") challengeId: String, @Body body: LeaveChallengeBody): HabitResponse @POST("challenges") - fun createChallenge(@Body challenge: Challenge): Flowable> + suspend fun createChallenge(@Body challenge: Challenge): HabitResponse @POST("tasks/challenge/{challengeId}") - fun createChallengeTasks(@Path("challengeId") challengeId: String, @Body tasks: List): Flowable>> + suspend fun createChallengeTasks(@Path("challengeId") challengeId: String, @Body tasks: List): HabitResponse> @POST("tasks/challenge/{challengeId}") - fun createChallengeTask(@Path("challengeId") challengeId: String, @Body task: Task): Flowable> + suspend fun createChallengeTask(@Path("challengeId") challengeId: String, @Body task: Task): HabitResponse @PUT("challenges/{challengeId}") - fun updateChallenge(@Path("challengeId") challengeId: String, @Body challenge: Challenge): Flowable> + suspend fun updateChallenge(@Path("challengeId") challengeId: String, @Body challenge: Challenge): HabitResponse @DELETE("challenges/{challengeId}") - fun deleteChallenge(@Path("challengeId") challengeId: String): Flowable> + suspend fun deleteChallenge(@Path("challengeId") challengeId: String): HabitResponse // DEBUG: These calls only work on a local development server @POST("debug/add-ten-gems") - fun debugAddTenGems(): Flowable> + suspend fun debugAddTenGems(): HabitResponse // Notifications @POST("notifications/{notificationId}/read") - fun readNotification(@Path("notificationId") notificationId: String): Flowable>> + suspend fun readNotification(@Path("notificationId") notificationId: String): HabitResponse> @POST("notifications/read") - fun readNotifications(@Body notificationIds: Map>): Flowable>> + suspend fun readNotifications(@Body notificationIds: Map>): HabitResponse> @POST("notifications/see") - fun seeNotifications(@Body notificationIds: Map>): Flowable>> + suspend fun seeNotifications(@Body notificationIds: Map>): HabitResponse> @POST("user/open-mystery-item") - fun openMysteryItem(): Flowable> + suspend fun openMysteryItem(): HabitResponse @POST("cron") - fun runCron(): Flowable> + suspend fun runCron(): HabitResponse @POST("user/reset") - fun resetAccount(): Flowable> + suspend fun resetAccount(): HabitResponse @HTTP(method = "DELETE", path = "user", hasBody = true) - fun deleteAccount(@Body body: Map): Flowable> + suspend fun deleteAccount(@Body body: Map): HabitResponse @GET("user/toggle-pinned-item/{pinType}/{path}") suspend fun togglePinnedItem(@Path("pinType") pinType: String, @Path("path") path: String): HabitResponse @POST("user/reset-password") - fun sendPasswordResetEmail(@Body data: Map): Flowable> + suspend fun sendPasswordResetEmail(@Body data: Map): HabitResponse @PUT("user/auth/update-username") suspend fun updateLoginName(@Body data: Map): HabitResponse @POST("user/auth/verify-username") - fun verifyUsername(@Body data: Map): Flowable> + suspend fun verifyUsername(@Body data: Map): HabitResponse @PUT("user/auth/update-email") - fun updateEmail(@Body data: Map): Flowable> + suspend fun updateEmail(@Body data: Map): HabitResponse @PUT("user/auth/update-password") - fun updatePassword(@Body data: Map): Flowable> + suspend fun updatePassword(@Body data: Map): HabitResponse @POST("user/allocate") - fun allocatePoint(@Query("stat") stat: String): Flowable> + suspend fun allocatePoint(@Query("stat") stat: String): HabitResponse @POST("user/allocate-bulk") - fun bulkAllocatePoints(@Body stats: Map>): Flowable> + suspend fun bulkAllocatePoints(@Body stats: Map>): HabitResponse @POST("members/transfer-gems") - fun transferGems(@Body data: Map): Flowable> + suspend fun transferGems(@Body data: Map): HabitResponse @POST("tasks/unlink-all/{challengeID}") - fun unlinkAllTasks(@Path("challengeID") challengeID: String?, @Query("keep") keepOption: String): Flowable> + suspend fun unlinkAllTasks(@Path("challengeID") challengeID: String?, @Query("keep") keepOption: String): HabitResponse @POST("user/block/{userID}") - fun blockMember(@Path("userID") userID: String): Flowable>> + suspend fun blockMember(@Path("userID") userID: String): HabitResponse> @POST("user/reroll") suspend fun reroll(): HabitResponse @@ -442,7 +441,7 @@ interface ApiService { // Team Plans @GET("group-plans") - fun getTeamPlans(): Flowable>> + suspend fun getTeamPlans(): HabitResponse> @GET("tasks/group/{groupID}") suspend fun getTeamPlanTasks(@Path("groupID") groupId: String): HabitResponse diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/api/MaintenanceApiService.java b/Habitica/src/main/java/com/habitrpg/android/habitica/api/MaintenanceApiService.java deleted file mode 100644 index d90a4cbfe..000000000 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/api/MaintenanceApiService.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.habitrpg.android.habitica.api; - -import com.habitrpg.shared.habitica.models.responses.MaintenanceResponse; - -import io.reactivex.rxjava3.core.Flowable; -import retrofit2.http.GET; - -public interface MaintenanceApiService { - - @GET("maintenance-android.json") - Flowable getMaintenanceStatus(); - - @GET("deprecation-android.json") - Flowable getDepricationStatus(); - -} diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/api/MaintenanceApiService.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/api/MaintenanceApiService.kt new file mode 100644 index 000000000..25e6e5208 --- /dev/null +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/api/MaintenanceApiService.kt @@ -0,0 +1,12 @@ +package com.habitrpg.android.habitica.api + +import com.habitrpg.shared.habitica.models.responses.MaintenanceResponse +import retrofit2.http.GET + +interface MaintenanceApiService { + @GET("maintenance-android.json") + suspend fun getMaintenanceStatus(): MaintenanceResponse? + + @GET("deprecation-android.json") + suspend fun getDepricationStatus(): MaintenanceResponse? +} \ No newline at end of file diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/components/UserComponent.java b/Habitica/src/main/java/com/habitrpg/android/habitica/components/UserComponent.java index 6cbab653f..9da7461ba 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/components/UserComponent.java +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/components/UserComponent.java @@ -40,7 +40,6 @@ import com.habitrpg.android.habitica.ui.activities.SkillTasksActivity; import com.habitrpg.android.habitica.ui.activities.TaskFormActivity; import com.habitrpg.android.habitica.ui.activities.TaskSummaryActivity; import com.habitrpg.android.habitica.ui.activities.TaskSummaryViewModel; -import com.habitrpg.android.habitica.ui.activities.VerifyUsernameActivity; import com.habitrpg.android.habitica.ui.adapter.social.challenges.ChallengeTasksRecyclerViewAdapter; import com.habitrpg.android.habitica.ui.adapter.tasks.DailiesRecyclerViewHolder; import com.habitrpg.android.habitica.ui.adapter.tasks.HabitsRecyclerViewAdapter; @@ -293,8 +292,6 @@ public interface UserComponent { void inject(ChallengeDetailFragment challengeDetailFragment); - void inject(VerifyUsernameActivity verifyUsernameActivity); - void inject(GroupViewModel viewModel); void inject(NotificationsViewModel viewModel); 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 907486b5e..bd41d9e15 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 @@ -27,7 +27,6 @@ import com.habitrpg.android.habitica.models.user.Items import com.habitrpg.android.habitica.models.user.Stats import com.habitrpg.android.habitica.models.user.User import com.habitrpg.common.habitica.api.HostConfig -import com.habitrpg.common.habitica.models.HabitResponse import com.habitrpg.common.habitica.models.PurchaseValidationRequest import com.habitrpg.common.habitica.models.PurchaseValidationResult import com.habitrpg.common.habitica.models.auth.UserAuthResponse @@ -36,8 +35,6 @@ import com.habitrpg.shared.habitica.models.responses.FeedResponse import com.habitrpg.shared.habitica.models.responses.Status import com.habitrpg.shared.habitica.models.responses.TaskDirectionData import com.habitrpg.shared.habitica.models.responses.VerifyUsernameResponse -import io.reactivex.rxjava3.core.Flowable -import io.reactivex.rxjava3.core.FlowableTransformer import retrofit2.HttpException interface ApiClient { @@ -52,19 +49,19 @@ interface ApiClient { /* challenges api */ - fun getUserChallenges(page: Int, memberOnly: Boolean): Flowable> + suspend fun getUserChallenges(page: Int, memberOnly: Boolean): List? suspend fun getWorldState(): WorldState? - fun setLanguageCode(languageCode: String) + var languageCode: String? suspend fun getContent(language: String? = null): ContentResult? suspend fun updateUser(updateDictionary: Map): User? - fun registrationLanguage(registrationLanguage: String): Flowable + suspend fun registrationLanguage(registrationLanguage: String): User? suspend fun retrieveInAppRewards(): List? - fun equipItem(type: String, itemKey: String): Flowable + suspend fun equipItem(type: String, itemKey: String): Items? suspend fun buyItem(itemKey: String, purchaseQuantity: Int): BuyResponse? @@ -76,51 +73,51 @@ interface ApiClient { suspend fun purchaseQuest(key: String): Void? suspend fun purchaseSpecialSpell(key: String): Void? - fun validateSubscription(request: PurchaseValidationRequest): Flowable - fun validateNoRenewSubscription(request: PurchaseValidationRequest): Flowable + suspend fun validateSubscription(request: PurchaseValidationRequest): Any? + suspend fun validateNoRenewSubscription(request: PurchaseValidationRequest): Any? suspend fun cancelSubscription(): Void? - fun sellItem(itemType: String, itemKey: String): Flowable + suspend fun sellItem(itemType: String, itemKey: String): User? - fun feedPet(petKey: String, foodKey: String): Flowable + suspend fun feedPet(petKey: String, foodKey: String): FeedResponse? - fun hatchPet(eggKey: String, hatchingPotionKey: String): Flowable - fun getTasks(type: String): Flowable - fun getTasks(type: String, dueDate: String): Flowable + suspend fun hatchPet(eggKey: String, hatchingPotionKey: String): Items? + suspend fun getTasks(type: String): TaskList? + suspend fun getTasks(type: String, dueDate: String): TaskList? suspend fun unlockPath(path: String): UnlockResponse? - fun getTask(id: String): Flowable + suspend fun getTask(id: String): Task? suspend fun postTaskDirection(id: String, direction: String): TaskDirectionData? - fun bulkScoreTasks(data: List>): Flowable + suspend fun bulkScoreTasks(data: List>): BulkTaskScoringData? - fun postTaskNewPosition(id: String, position: Int): Flowable> + suspend fun postTaskNewPosition(id: String, position: Int): List? suspend fun scoreChecklistItem(taskId: String, itemId: String): Task? - fun createTask(item: Task): Flowable + suspend fun createTask(item: Task): Task? - fun createTasks(tasks: List): Flowable> + suspend fun createTasks(tasks: List): List? - fun updateTask(id: String, item: Task): Flowable + suspend fun updateTask(id: String, item: Task): Task? - fun deleteTask(id: String): Flowable + suspend fun deleteTask(id: String): Void? - fun createTag(tag: Tag): Flowable + suspend fun createTag(tag: Tag): Tag? - fun updateTag(id: String, tag: Tag): Flowable + suspend fun updateTag(id: String, tag: Tag): Tag? - fun deleteTag(id: String): Flowable + suspend fun deleteTag(id: String): Void? - fun registerUser(username: String, email: String, password: String, confirmPassword: String): Flowable + suspend fun registerUser(username: String, email: String, password: String, confirmPassword: String): UserAuthResponse? - fun connectUser(username: String, password: String): Flowable + suspend fun connectUser(username: String, password: String): UserAuthResponse? - fun connectSocial(network: String, userId: String, accessToken: String): Flowable - fun disconnectSocial(network: String): Flowable + suspend fun connectSocial(network: String, userId: String, accessToken: String): UserAuthResponse? + suspend fun disconnectSocial(network: String): Void? - fun loginApple(authToken: String): Flowable + suspend fun loginApple(authToken: String): UserAuthResponse? suspend fun sleep(): Boolean? suspend fun revive(): User? @@ -133,11 +130,11 @@ interface ApiClient { suspend fun disableClasses(): User? - fun markPrivateMessagesRead(): Flowable + suspend fun markPrivateMessagesRead(): Void? /* Group API */ - fun listGroups(type: String): Flowable> + suspend fun listGroups(type: String): List? suspend fun getGroup(groupId: String): Group? @@ -151,83 +148,83 @@ interface ApiClient { suspend fun leaveGroup(groupId: String, keepChallenges: String): Void? - fun postGroupChat(groupId: String, message: Map): Flowable + suspend fun postGroupChat(groupId: String, message: Map): PostChatMessageResult? - fun deleteMessage(groupId: String, messageId: String): Flowable - fun deleteInboxMessage(id: String): Flowable + suspend fun deleteMessage(groupId: String, messageId: String): Void? + suspend fun deleteInboxMessage(id: String): Void? suspend fun getGroupMembers(groupId: String, includeAllPublicFields: Boolean?): List? suspend fun getGroupMembers(groupId: String, includeAllPublicFields: Boolean?, lastId: String): List? // Like returns the full chat list - fun likeMessage(groupId: String, mid: String): Flowable + suspend fun likeMessage(groupId: String, mid: String): ChatMessage? - fun flagMessage(groupId: String, mid: String, data: MutableMap): Flowable - fun flagInboxMessage(mid: String, data: MutableMap): Flowable + suspend fun flagMessage(groupId: String, mid: String, data: MutableMap): Void? + suspend fun flagInboxMessage(mid: String, data: MutableMap): Void? - fun seenMessages(groupId: String): Flowable + suspend fun seenMessages(groupId: String): Void? - fun inviteToGroup(groupId: String, inviteData: Map): Flowable> + suspend fun inviteToGroup(groupId: String, inviteData: Map): List? - fun rejectGroupInvite(groupId: String): Flowable + suspend fun rejectGroupInvite(groupId: String): Void? - fun acceptQuest(groupId: String): Flowable + suspend fun acceptQuest(groupId: String): Void? - fun rejectQuest(groupId: String): Flowable + suspend fun rejectQuest(groupId: String): Void? - fun cancelQuest(groupId: String): Flowable + suspend fun cancelQuest(groupId: String): Void? - fun forceStartQuest(groupId: String, group: Group): Flowable + suspend fun forceStartQuest(groupId: String, group: Group): Quest? - fun inviteToQuest(groupId: String, questKey: String): Flowable + suspend fun inviteToQuest(groupId: String, questKey: String): Quest? - fun abortQuest(groupId: String): Flowable + suspend fun abortQuest(groupId: String): Quest? - fun leaveQuest(groupId: String): Flowable + suspend fun leaveQuest(groupId: String): Void? - fun validatePurchase(request: PurchaseValidationRequest): Flowable + suspend fun validatePurchase(request: PurchaseValidationRequest): PurchaseValidationResult? - fun changeCustomDayStart(updateObject: Map): Flowable + suspend fun changeCustomDayStart(updateObject: Map): User? // Members URL suspend fun getMember(memberId: String): Member? suspend fun getMemberWithUsername(username: String): Member? - fun getMemberAchievements(memberId: String): Flowable> + suspend fun getMemberAchievements(memberId: String): List? suspend fun postPrivateMessage(messageDetails: Map): PostChatMessageResult? - fun retrieveShopIventory(identifier: String): Flowable + suspend fun retrieveShopIventory(identifier: String): Shop? // Push notifications - fun addPushDevice(pushDeviceData: Map): Flowable> + suspend fun addPushDevice(pushDeviceData: Map): List? - fun deletePushDevice(regId: String): Flowable> + suspend fun deletePushDevice(regId: String): List? - fun getChallengeTasks(challengeId: String): Flowable + suspend fun getChallengeTasks(challengeId: String): TaskList? - fun getChallenge(challengeId: String): Flowable + suspend fun getChallenge(challengeId: String): Challenge? - fun joinChallenge(challengeId: String): Flowable + suspend fun joinChallenge(challengeId: String): Challenge? - fun leaveChallenge(challengeId: String, body: LeaveChallengeBody): Flowable + suspend fun leaveChallenge(challengeId: String, body: LeaveChallengeBody): Void? - fun createChallenge(challenge: Challenge): Flowable + suspend fun createChallenge(challenge: Challenge): Challenge? - fun createChallengeTasks(challengeId: String, tasks: List): Flowable> - fun createChallengeTask(challengeId: String, task: Task): Flowable - fun updateChallenge(challenge: Challenge): Flowable - fun deleteChallenge(challengeId: String): Flowable + suspend fun createChallengeTasks(challengeId: String, tasks: List): List? + suspend fun createChallengeTask(challengeId: String, task: Task): Task? + suspend fun updateChallenge(challenge: Challenge): Challenge? + suspend fun deleteChallenge(challengeId: String): Void? // DEBUG: These calls only work on a local development server - fun debugAddTenGems(): Flowable + suspend fun debugAddTenGems(): Void? // Notifications - fun readNotification(notificationId: String): Flowable> - fun readNotifications(notificationIds: Map>): Flowable> - fun seeNotifications(notificationIds: Map>): Flowable> + suspend fun readNotification(notificationId: String): List? + suspend fun readNotifications(notificationIds: Map>): List? + suspend fun seeNotifications(notificationIds: Map>): List? fun getErrorResponse(throwable: HttpException): ErrorResponse @@ -237,42 +234,40 @@ interface ApiClient { suspend fun retrieveUser(withTasks: Boolean = false): User? suspend fun retrieveInboxMessages(uuid: String, page: Int): List? - fun retrieveInboxConversations(): Flowable> + suspend fun retrieveInboxConversations(): List? - fun configureApiCallObserver(): FlowableTransformer, T> + suspend fun openMysteryItem(): Equipment? - fun openMysteryItem(): Flowable - - fun runCron(): Flowable + suspend fun runCron(): Void? suspend fun reroll(): User? - fun resetAccount(): Flowable - fun deleteAccount(password: String): Flowable + suspend fun resetAccount(): Void? + suspend fun deleteAccount(password: String): Void? suspend fun togglePinnedItem(pinType: String, path: String): Void? - fun sendPasswordResetEmail(email: String): Flowable + suspend fun sendPasswordResetEmail(email: String): Void? suspend fun updateLoginName(newLoginName: String, password: String): Void? suspend fun updateUsername(newLoginName: String): Void? - fun updateEmail(newEmail: String, password: String): Flowable + suspend fun updateEmail(newEmail: String, password: String): Void? - fun updatePassword(oldPassword: String, newPassword: String, newPasswordConfirmation: String): Flowable + suspend fun updatePassword(oldPassword: String, newPassword: String, newPasswordConfirmation: String): Void? - fun allocatePoint(stat: String): Flowable + suspend fun allocatePoint(stat: String): Stats? - fun bulkAllocatePoints(strength: Int, intelligence: Int, constitution: Int, perception: Int): Flowable + suspend fun bulkAllocatePoints(strength: Int, intelligence: Int, constitution: Int, perception: Int): Stats? - fun retrieveMarketGear(): Flowable - fun verifyUsername(username: String): Flowable + suspend fun retrieveMarketGear(): Shop? + suspend fun verifyUsername(username: String): VerifyUsernameResponse? fun updateServerUrl(newAddress: String?) - fun findUsernames(username: String, context: String?, id: String?): Flowable> + suspend fun findUsernames(username: String, context: String?, id: String?): List? - fun transferGems(giftedID: String, amount: Int): Flowable - fun unlinkAllTasks(challengeID: String?, keepOption: String): Flowable - fun blockMember(userID: String): Flowable> - fun getTeamPlans(): Flowable> + suspend fun transferGems(giftedID: String, amount: Int): Void? + suspend fun unlinkAllTasks(challengeID: String?, keepOption: String): Void? + suspend fun blockMember(userID: String): List? + suspend fun getTeamPlans(): List? suspend fun getTeamPlanTasks(teamID: String): TaskList? } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/ChallengeRepository.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/ChallengeRepository.kt index 6f5758b2e..626216fb5 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/ChallengeRepository.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/ChallengeRepository.kt @@ -4,18 +4,18 @@ import com.habitrpg.android.habitica.models.social.Challenge import com.habitrpg.android.habitica.models.social.ChallengeMembership import com.habitrpg.android.habitica.models.tasks.Task import com.habitrpg.android.habitica.models.tasks.TaskList -import io.reactivex.rxjava3.core.Flowable +import kotlinx.coroutines.flow.Flow interface ChallengeRepository : BaseRepository { - fun retrieveChallenges(page: Int = 0, memberOnly: Boolean): Flowable> - fun getChallenges(): Flowable> - fun getChallenge(challengeId: String): Flowable - fun getChallengeTasks(challengeId: String): Flowable> + suspend fun retrieveChallenges(page: Int = 0, memberOnly: Boolean): List? + fun getChallenges(): Flow> + fun getChallenge(challengeId: String): Flow + fun getChallengeTasks(challengeId: String): Flow> - fun retrieveChallenge(challengeID: String): Flowable - fun retrieveChallengeTasks(challengeID: String): Flowable - fun createChallenge(challenge: Challenge, taskList: List): Flowable + suspend fun retrieveChallenge(challengeID: String): Challenge? + suspend fun retrieveChallengeTasks(challengeID: String): TaskList? + suspend fun createChallenge(challenge: Challenge, taskList: List): Challenge? /** * @@ -26,22 +26,22 @@ interface ChallengeRepository : BaseRepository { * @param removedTaskList tasks that has be to be removed * @return Observable with the updated challenge */ - fun updateChallenge( + suspend fun updateChallenge( challenge: Challenge, fullTaskList: List, addedTaskList: List, updatedTaskList: List, removedTaskList: List - ): Flowable + ): Challenge? - fun deleteChallenge(challengeId: String): Flowable - fun getUserChallenges(userId: String? = null): Flowable> + suspend fun deleteChallenge(challengeId: String): Void? + fun getUserChallenges(userId: String? = null): Flow> - fun leaveChallenge(challenge: Challenge, keepTasks: String): Flowable + suspend fun leaveChallenge(challenge: Challenge, keepTasks: String): Void? - fun joinChallenge(challenge: Challenge): Flowable + suspend fun joinChallenge(challenge: Challenge): Challenge? - fun getChallengepMembership(id: String): Flowable - fun getChallengeMemberships(): Flowable> - fun isChallengeMember(challengeID: String): Flowable + fun getChallengepMembership(id: String): Flow + fun getChallengeMemberships(): Flow> + fun isChallengeMember(challengeID: String): Flow } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/ContentRepository.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/ContentRepository.kt index 1a71ef514..141c5cacc 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/ContentRepository.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/ContentRepository.kt @@ -2,11 +2,11 @@ package com.habitrpg.android.habitica.data import com.habitrpg.android.habitica.models.ContentResult import com.habitrpg.android.habitica.models.WorldState -import io.reactivex.rxjava3.core.Flowable +import kotlinx.coroutines.flow.Flow interface ContentRepository: BaseRepository { suspend fun retrieveContent(forced: Boolean = false): ContentResult? suspend fun retrieveWorldState(): WorldState? - fun getWorldState(): Flowable + fun getWorldState(): Flow } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/CustomizationRepository.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/CustomizationRepository.kt index f65af96df..8c657e98a 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/CustomizationRepository.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/CustomizationRepository.kt @@ -1,8 +1,8 @@ package com.habitrpg.android.habitica.data import com.habitrpg.android.habitica.models.inventory.Customization -import io.reactivex.rxjava3.core.Flowable +import kotlinx.coroutines.flow.Flow interface CustomizationRepository : BaseRepository { - fun getCustomizations(type: String, category: String?, onlyAvailable: Boolean): Flowable> + fun getCustomizations(type: String, category: String?, onlyAvailable: Boolean): Flow> } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/FAQRepository.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/FAQRepository.kt index 9ead63c61..d4b61d443 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/FAQRepository.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/FAQRepository.kt @@ -1,9 +1,9 @@ package com.habitrpg.android.habitica.data import com.habitrpg.android.habitica.models.FAQArticle -import io.reactivex.rxjava3.core.Flowable +import kotlinx.coroutines.flow.Flow interface FAQRepository : BaseRepository { - fun getArticles(): Flowable> - fun getArticle(position: Int): Flowable + fun getArticles(): Flow> + fun getArticle(position: Int): Flow } 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 7cacbe430..16263bfb4 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 @@ -18,15 +18,14 @@ import com.habitrpg.android.habitica.models.user.OwnedMount import com.habitrpg.android.habitica.models.user.OwnedPet import com.habitrpg.android.habitica.models.user.User import com.habitrpg.shared.habitica.models.responses.FeedResponse -import io.reactivex.rxjava3.core.Flowable import kotlinx.coroutines.flow.Flow interface InventoryRepository : BaseRepository { fun getArmoireRemainingCount(): Long - fun getInAppRewards(): Flowable> - fun getOwnedEquipment(): Flowable> + fun getInAppRewards(): Flow> + fun getOwnedEquipment(): Flow> fun getMounts(): Flow> @@ -38,18 +37,18 @@ interface InventoryRepository : BaseRepository { fun getQuestContent(key: String): Flow fun getQuestContent(keys: List): Flow> - fun getEquipment(searchedKeys: List): Flowable> + fun getEquipment(searchedKeys: List): Flow> suspend fun retrieveInAppRewards(): List? - fun getOwnedEquipment(type: String): Flowable> - fun getEquipmentType(type: String, set: String): Flowable> + fun getOwnedEquipment(type: String): Flow> + fun getEquipmentType(type: String, set: String): Flow> fun getOwnedItems(itemType: String, includeZero: Boolean = false): Flow> - fun getOwnedItems(includeZero: Boolean = false): Flowable> + fun getOwnedItems(includeZero: Boolean = false): Flow> - fun getEquipment(key: String): Flowable + fun getEquipment(key: String): Flow - fun openMysteryItem(user: User?): Flowable + suspend fun openMysteryItem(user: User?): Equipment? fun saveEquipment(equipment: Equipment) fun getMounts(type: String?, group: String?, color: String?): Flow> @@ -57,24 +56,24 @@ interface InventoryRepository : BaseRepository { fun updateOwnedEquipment(user: User) - fun changeOwnedCount(type: String, key: String, amountToAdd: Int) + suspend fun changeOwnedCount(type: String, key: String, amountToAdd: Int) - fun sellItem(type: String, key: String): Flowable - fun sellItem(item: OwnedItem): Flowable + suspend fun sellItem(type: String, key: String): User? + suspend fun sellItem(item: OwnedItem): User? - fun equipGear(equipment: String, asCostume: Boolean): Flowable - fun equip(type: String, key: String): Flowable + suspend fun equipGear(equipment: String, asCostume: Boolean): Items? + suspend fun equip(type: String, key: String): Items? - fun feedPet(pet: Pet, food: Food): Flowable + suspend fun feedPet(pet: Pet, food: Food): FeedResponse? - fun hatchPet(egg: Egg, hatchingPotion: HatchingPotion, successFunction: () -> Unit): Flowable + suspend fun hatchPet(egg: Egg, hatchingPotion: HatchingPotion, successFunction: () -> Unit): Items? - fun inviteToQuest(quest: QuestContent): Flowable + suspend fun inviteToQuest(quest: QuestContent): Quest? suspend fun buyItem(user: User?, id: String, value: Double, purchaseQuantity: Int): BuyResponse? - fun retrieveShopInventory(identifier: String): Flowable - fun retrieveMarketGear(): Flowable + suspend fun retrieveShopInventory(identifier: String): Shop? + suspend fun retrieveMarketGear(): Shop? suspend fun purchaseMysterySet(categoryIdentifier: String): Void? @@ -87,9 +86,8 @@ interface InventoryRepository : BaseRepository { suspend fun togglePinnedItem(item: ShopItem): List? fun getItems(itemClass: Class, keys: Array): Flow> - fun getItemsFlowable(itemClass: Class): Flowable> fun getItems(itemClass: Class): Flow> - fun getLatestMysteryItem(): Flowable - fun getItem(type: String, key: String): Flowable - fun getAvailableLimitedItems(): Flowable> + fun getLatestMysteryItem(): Flow + fun getItem(type: String, key: String): Flow + fun getAvailableLimitedItems(): Flow> } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/SocialRepository.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/SocialRepository.kt index dfb6713f7..5ccb1674d 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/SocialRepository.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/SocialRepository.kt @@ -10,35 +10,34 @@ import com.habitrpg.android.habitica.models.social.Group import com.habitrpg.android.habitica.models.social.GroupMembership import com.habitrpg.android.habitica.models.social.InboxConversation import com.habitrpg.android.habitica.models.user.User -import io.reactivex.rxjava3.core.Flowable import io.realm.RealmResults import kotlinx.coroutines.flow.Flow interface SocialRepository : BaseRepository { - fun getPublicGuilds(): Flowable> + fun getPublicGuilds(): Flow> fun getUserGroups(type: String?): Flow> suspend fun retrieveGroupChat(groupId: String): List? - fun getGroupChat(groupId: String): Flowable> + fun getGroupChat(groupId: String): Flow> - fun markMessagesSeen(seenGroupId: String) + suspend fun markMessagesSeen(seenGroupId: String) - fun flagMessage( + suspend fun flagMessage( chatMessageID: String, additionalInfo: String, groupID: String? = null - ): Flowable + ): Void? - fun likeMessage(chatMessage: ChatMessage): Flowable + suspend fun likeMessage(chatMessage: ChatMessage): ChatMessage? - fun deleteMessage(chatMessage: ChatMessage): Flowable + suspend fun deleteMessage(chatMessage: ChatMessage): Void? - fun postGroupChat( + suspend fun postGroupChat( groupId: String, messageObject: HashMap - ): Flowable + ): PostChatMessageResult? - fun postGroupChat(groupId: String, message: String): Flowable + suspend fun postGroupChat(groupId: String, message: String): PostChatMessageResult? suspend fun retrieveGroup(id: String): Group? fun getGroup(id: String?): Flow @@ -64,12 +63,12 @@ interface SocialRepository : BaseRepository { leaderCreateChallenge: Boolean? ): Group? - fun retrieveGroups(type: String): Flowable> - fun getGroups(type: String): Flowable> + suspend fun retrieveGroups(type: String): List? + fun getGroups(type: String): Flow> fun getInboxMessages(replyToUserID: String?): Flow> suspend fun retrieveInboxMessages(uuid: String, page: Int): List? - fun retrieveInboxConversations(): Flowable> + suspend fun retrieveInboxConversations(): List? fun getInboxConversations(): Flow> suspend fun postPrivateMessage( recipientId: String, @@ -82,42 +81,42 @@ interface SocialRepository : BaseRepository { suspend fun getGroupMembers(id: String): Flow> suspend fun retrievePartyMembers(id: String, includeAllPublicFields: Boolean): List? - fun inviteToGroup(id: String, inviteData: Map): Flowable> + suspend fun inviteToGroup(id: String, inviteData: Map): List? suspend fun retrieveMember(userId: String?): Member? suspend fun retrieveMemberWithUsername(username: String?): Member? - fun findUsernames( + suspend fun findUsernames( username: String, context: String? = null, id: String? = null - ): Flowable> + ): List? - fun markPrivateMessagesRead(user: User?): Flowable + suspend fun markPrivateMessagesRead(user: User?): Void? fun markSomePrivateMessagesAsRead(user: User?, messages: List) suspend fun transferGroupOwnership(groupID: String, userID: String): Group? suspend fun removeMemberFromGroup(groupID: String, userID: String): List? - fun acceptQuest(user: User?, partyId: String = "party"): Flowable - fun rejectQuest(user: User?, partyId: String = "party"): Flowable + suspend fun acceptQuest(user: User?, partyId: String = "party"): Void? + suspend fun rejectQuest(user: User?, partyId: String = "party"): Void? - fun leaveQuest(partyId: String): Flowable + suspend fun leaveQuest(partyId: String): Void? - fun cancelQuest(partyId: String): Flowable + suspend fun cancelQuest(partyId: String): Void? - fun abortQuest(partyId: String): Flowable + suspend fun abortQuest(partyId: String): Quest? - fun rejectGroupInvite(groupId: String): Flowable + suspend fun rejectGroupInvite(groupId: String): Void? - fun forceStartQuest(party: Group): Flowable + suspend fun forceStartQuest(party: Group): Quest? - fun getMemberAchievements(userId: String?): Flowable> + suspend fun getMemberAchievements(userId: String?): List? - fun transferGems(giftedID: String, amount: Int): Flowable + suspend fun transferGems(giftedID: String, amount: Int): Void? fun getGroupMembership(id: String): Flow - fun getGroupMemberships(): Flowable> - fun blockMember(userID: String): Flowable> + fun getGroupMemberships(): Flow> + suspend fun blockMember(userID: String): List? } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/TagRepository.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/TagRepository.kt index 8369c33cd..b6a04dcc2 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/TagRepository.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/TagRepository.kt @@ -1,19 +1,18 @@ package com.habitrpg.android.habitica.data import com.habitrpg.android.habitica.models.Tag -import io.reactivex.rxjava3.core.Flowable -import io.reactivex.rxjava3.core.Single +import kotlinx.coroutines.flow.Flow interface TagRepository : BaseRepository { - fun getTags(): Flowable> - fun getTags(userId: String): Flowable> + fun getTags(): Flow> + fun getTags(userId: String): Flow> - fun createTag(tag: Tag): Flowable - fun updateTag(tag: Tag): Flowable - fun deleteTag(id: String): Flowable + suspend fun createTag(tag: Tag): Tag? + suspend fun updateTag(tag: Tag): Tag? + suspend fun deleteTag(id: String): Void? - fun createTags(tags: Collection): Single> - fun updateTags(tags: Collection): Single> - fun deleteTags(tagIds: Collection): Single> + suspend fun createTags(tags: Collection): List + suspend fun updateTags(tags: Collection): List + suspend fun deleteTags(tagIds: Collection): List } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/TaskRepository.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/TaskRepository.kt index 9548b54b3..efa4f3507 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/TaskRepository.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/TaskRepository.kt @@ -8,19 +8,15 @@ import com.habitrpg.android.habitica.models.user.User import com.habitrpg.shared.habitica.models.responses.TaskScoringResult import com.habitrpg.shared.habitica.models.tasks.TaskType import com.habitrpg.shared.habitica.models.tasks.TasksOrder -import io.reactivex.rxjava3.core.Flowable -import io.reactivex.rxjava3.core.Maybe -import io.reactivex.rxjava3.core.Single import kotlinx.coroutines.flow.Flow import java.util.Date interface TaskRepository : BaseRepository { fun getTasks(taskType: TaskType, userID: String? = null, includedGroupIDs: Array): Flow> - fun getTasksFlowable(taskType: TaskType, userID: String? = null, includedGroupIDs: Array): Flowable> fun saveTasks(userId: String, order: TasksOrder, tasks: TaskList) suspend fun retrieveTasks(userId: String, tasksOrder: TasksOrder): TaskList? - fun retrieveTasks(userId: String, tasksOrder: TasksOrder, dueDate: Date): Flowable + suspend fun retrieveTasks(userId: String, tasksOrder: TasksOrder, dueDate: Date): TaskList? suspend fun taskChecked( user: User?, @@ -40,12 +36,12 @@ interface TaskRepository : BaseRepository { fun getTask(taskId: String): Flow fun getTaskCopy(taskId: String): Flow - fun createTask(task: Task, force: Boolean = false): Flowable - fun updateTask(task: Task, force: Boolean = false): Maybe? - fun deleteTask(taskId: String): Flowable + suspend fun createTask(task: Task, force: Boolean = false): Task? + suspend fun updateTask(task: Task, force: Boolean = false): Task? + suspend fun deleteTask(taskId: String): Void? fun saveTask(task: Task) - fun createTasks(newTasks: List): Flowable> + suspend fun createTasks(newTasks: List): List? fun markTaskCompleted(taskId: String, isCompleted: Boolean) @@ -53,7 +49,7 @@ interface TaskRepository : BaseRepository { fun swapTaskPosition(firstPosition: Int, secondPosition: Int) - fun updateTaskPosition(taskType: TaskType, taskID: String, newPosition: Int): Maybe> + suspend fun updateTaskPosition(taskType: TaskType, taskID: String, newPosition: Int): List? fun getUnmanagedTask(taskid: String): Flow @@ -65,10 +61,10 @@ interface TaskRepository : BaseRepository { fun getTaskCopies(tasks: List): List - fun retrieveDailiesFromDate(date: Date): Flowable - fun retrieveCompletedTodos(userId: String? = null): Flowable - fun syncErroredTasks(): Single> - fun unlinkAllTasks(challengeID: String?, keepOption: String): Flowable - fun getTasksForChallenge(challengeID: String?): Flowable> - fun bulkScoreTasks(data: List>): Flowable + suspend fun retrieveDailiesFromDate(date: Date): TaskList? + suspend fun retrieveCompletedTodos(userId: String? = null): TaskList? + suspend fun syncErroredTasks(): List? + suspend fun unlinkAllTasks(challengeID: String?, keepOption: String): Void? + fun getTasksForChallenge(challengeID: String?): Flow> + suspend fun bulkScoreTasks(data: List>): BulkTaskScoringData? } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/TutorialRepository.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/TutorialRepository.kt index f982b7f3e..5940aa690 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/TutorialRepository.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/TutorialRepository.kt @@ -1,10 +1,10 @@ package com.habitrpg.android.habitica.data import com.habitrpg.android.habitica.models.TutorialStep -import io.reactivex.rxjava3.core.Flowable +import kotlinx.coroutines.flow.Flow interface TutorialRepository : BaseRepository { - fun getTutorialStep(key: String): Flowable - fun getTutorialSteps(keys: List): Flowable> + fun getTutorialStep(key: String): Flow + fun getTutorialSteps(keys: List): 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 220c675a0..5390821a9 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 @@ -14,12 +14,10 @@ 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 kotlinx.coroutines.flow.Flow interface UserRepository : BaseRepository { fun getUser(): Flow - fun getUserFlowable(): Flowable fun getUser(userID: String): Flow suspend fun updateUser(updateData: Map): User? @@ -33,9 +31,9 @@ interface UserRepository : BaseRepository { suspend fun sleep(user: User): User? - fun getSkills(user: User): Flowable> + fun getSkills(user: User): Flow> - fun getSpecialItems(user: User): Flowable> + fun getSpecialItems(user: User): Flow> suspend fun useSkill(key: String, target: String?, taskId: String): SkillResponse? suspend fun useSkill(key: String, target: String?): SkillResponse? @@ -49,37 +47,37 @@ interface UserRepository : BaseRepository { suspend fun runCron(tasks: MutableList) suspend fun runCron() - fun readNotification(id: String): Flowable> - fun readNotifications(notificationIds: Map>): Flowable> - fun seeNotifications(notificationIds: Map>): Flowable> + suspend fun readNotification(id: String): List? + suspend fun readNotifications(notificationIds: Map>): List? + suspend fun seeNotifications(notificationIds: Map>): List? - fun changeCustomDayStart(dayStartTime: Int): Flowable + suspend fun changeCustomDayStart(dayStartTime: Int): User? suspend fun updateLanguage(languageCode: String): User? suspend fun resetAccount(): User? - fun deleteAccount(password: String): Flowable + suspend fun deleteAccount(password: String): Void? - fun sendPasswordResetEmail(email: String): Flowable + suspend fun sendPasswordResetEmail(email: String): Void? 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 + suspend fun updateEmail(newEmail: String, password: String): Void? + suspend fun updatePassword(oldPassword: String, newPassword: String, newPasswordConfirmation: String): Void? + suspend fun verifyUsername(username: String): VerifyUsernameResponse? - fun allocatePoint(stat: Attribute): Flowable - fun bulkAllocatePoints(strength: Int, intelligence: Int, constitution: Int, perception: Int): Flowable + suspend fun allocatePoint(stat: Attribute): Stats? + suspend fun bulkAllocatePoints(strength: Int, intelligence: Int, constitution: Int, perception: Int): Stats? suspend fun useCustomization(type: String, category: String?, identifier: String): User? - fun retrieveAchievements(): Flowable> + suspend fun retrieveAchievements(): List? fun getAchievements(): Flow> fun getQuestAchievements(): Flow> - fun getUserQuestStatus(): Flowable + fun getUserQuestStatus(): Flow suspend fun reroll(): User? - fun retrieveTeamPlans(): Flowable> + suspend fun retrieveTeamPlans(): List? fun getTeamPlans(): Flow> suspend fun retrieveTeamPlan(teamID: String): Group? - fun getTeamPlan(teamID: String): Flowable + fun getTeamPlan(teamID: String): 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 c42556b06..3b56c2116 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 @@ -51,11 +51,7 @@ import com.habitrpg.shared.habitica.models.responses.FeedResponse import com.habitrpg.shared.habitica.models.responses.Status import com.habitrpg.shared.habitica.models.responses.TaskDirectionData import com.habitrpg.shared.habitica.models.responses.VerifyUsernameResponse -import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers -import io.reactivex.rxjava3.core.Flowable -import io.reactivex.rxjava3.core.FlowableTransformer import io.reactivex.rxjava3.functions.Consumer -import io.reactivex.rxjava3.schedulers.Schedulers import okhttp3.Cache import okhttp3.OkHttpClient import okhttp3.Request @@ -87,17 +83,6 @@ class ApiClientImpl( // I think we don't need the ApiClientImpl anymore we could just use ApiService private lateinit var apiService: ApiService - private val apiCallTransformer = FlowableTransformer, Any> { observable -> - observable - .filter { it.data != null } - .map { habitResponse -> - processResponse(habitResponse) - } - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .doOnError(this) - } - private fun processResponse(habitResponse: HabitResponse): T? { habitResponse.notifications?.let { notificationsManager.setNotifications(it) @@ -114,7 +99,7 @@ class ApiClientImpl( return null } - private var languageCode: String? = null + override var languageCode: String? = null private var lastAPICallURL: String? = null private var hadError = false @@ -186,28 +171,28 @@ class ApiClientImpl( } } - override fun registerUser( + override suspend fun registerUser( username: String, email: String, password: String, confirmPassword: String - ): Flowable { + ): UserAuthResponse? { val auth = UserAuth() auth.username = username auth.password = password auth.confirmPassword = confirmPassword auth.email = email - return this.apiService.registerUser(auth).compose(configureApiCallObserver()) + return process { this.apiService.registerUser(auth) } } - override fun connectUser(username: String, password: String): Flowable { + override suspend fun connectUser(username: String, password: String): UserAuthResponse? { val auth = UserAuth() auth.username = username auth.password = password - return this.apiService.connectLocal(auth).compose(configureApiCallObserver()) + return process { this.apiService.connectLocal(auth) } } - override fun connectSocial(network: String, userId: String, accessToken: String): Flowable { + override suspend fun connectSocial(network: String, userId: String, accessToken: String): UserAuthResponse? { val auth = UserAuthSocial() auth.network = network val authResponse = UserAuthSocialTokens() @@ -215,15 +200,15 @@ class ApiClientImpl( authResponse.access_token = accessToken auth.authResponse = authResponse - return this.apiService.connectSocial(auth).compose(configureApiCallObserver()) + return process { this.apiService.connectSocial(auth) } } - override fun disconnectSocial(network: String): Flowable { - return this.apiService.disconnectSocial(network).compose(configureApiCallObserver()) + override suspend fun disconnectSocial(network: String): Void? { + return process { this.apiService.disconnectSocial(network) } } - override fun loginApple(authToken: String): Flowable { - return apiService.loginApple(mapOf(Pair("code", authToken))).compose(configureApiCallObserver()) + override suspend fun loginApple(authToken: String): UserAuthResponse? { + return process { apiService.loginApple(mapOf(Pair("code", authToken))) } } override fun accept(throwable: Throwable) { @@ -290,8 +275,8 @@ class ApiClientImpl( return process { apiService.getInboxMessages(uuid, page) } } - override fun retrieveInboxConversations(): Flowable> { - return apiService.getInboxConversations().compose(configureApiCallObserver()) + override suspend fun retrieveInboxConversations(): List? { + return process { apiService.getInboxConversations() } } override fun hasAuthenticationKeys(): Boolean { @@ -330,11 +315,6 @@ class ApiClientImpl( See here for more info: http://blog.danlew.net/2015/03/02/dont-break-the-chain/ */ - override fun configureApiCallObserver(): FlowableTransformer, T> { - @Suppress("UNCHECKED_CAST") - return apiCallTransformer as FlowableTransformer, T> - } - override fun updateAuthenticationCredentials(userID: String?, apiToken: String?) { this.hostConfig.userID = userID ?: "" this.hostConfig.apiKey = apiToken ?: "" @@ -342,10 +322,6 @@ class ApiClientImpl( Amplitude.getInstance().userId = this.hostConfig.userID } - override fun setLanguageCode(languageCode: String) { - this.languageCode = languageCode - } - override suspend fun getStatus(): Status? = process { apiService.getStatus() } override suspend fun getContent(language: String?): ContentResult? { @@ -356,40 +332,40 @@ class ApiClientImpl( return process { apiService.updateUser(updateDictionary) } } - override fun registrationLanguage(registrationLanguage: String): Flowable { - return apiService.registrationLanguage(registrationLanguage).compose(configureApiCallObserver()) + override suspend fun registrationLanguage(registrationLanguage: String): User? { + return process { apiService.registrationLanguage(registrationLanguage) } } 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 suspend fun equipItem(type: String, itemKey: String): Items? { + return process { apiService.equipItem(type, itemKey) } } 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 { - return apiService.unlinkAllTasks(challengeID, keepOption).compose(configureApiCallObserver()) + override suspend fun unlinkAllTasks(challengeID: String?, keepOption: String): Void? { + return process { apiService.unlinkAllTasks(challengeID, keepOption) } } - override fun blockMember(userID: String): Flowable> { - return apiService.blockMember(userID).compose(configureApiCallObserver()) + override suspend fun blockMember(userID: String): List? { + return process { apiService.blockMember(userID) } } 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 { - return apiService.validateSubscription(request).compose(configureApiCallObserver()) + override suspend fun validateSubscription(request: PurchaseValidationRequest): Any? { + return process { apiService.validateSubscription(request) } } - override fun validateNoRenewSubscription(request: PurchaseValidationRequest): Flowable { - return apiService.validateNoRenewSubscription(request).compose(configureApiCallObserver()) + override suspend fun validateNoRenewSubscription(request: PurchaseValidationRequest): Any? { + return process { apiService.validateNoRenewSubscription(request) } } override suspend fun cancelSubscription(): Void? { @@ -412,83 +388,80 @@ class ApiClientImpl( return process { apiService.purchaseSpecialSpell(key) } } - override fun sellItem(itemType: String, itemKey: String): Flowable { - return apiService.sellItem(itemType, itemKey).compose(configureApiCallObserver()) + override suspend fun sellItem(itemType: String, itemKey: String): User? { + return process { apiService.sellItem(itemType, itemKey) } } - override fun feedPet(petKey: String, foodKey: String): Flowable { - return apiService.feedPet(petKey, foodKey) - .map { - it.data?.message = it.message - it - } - .compose(configureApiCallObserver()) + override suspend fun feedPet(petKey: String, foodKey: String): FeedResponse? { + val response = apiService.feedPet(petKey, foodKey) + response.data?.message = response.message + return process { response } } - override fun hatchPet(eggKey: String, hatchingPotionKey: String): Flowable { - return apiService.hatchPet(eggKey, hatchingPotionKey).compose(configureApiCallObserver()) + override suspend fun hatchPet(eggKey: String, hatchingPotionKey: String): Items? { + return process { apiService.hatchPet(eggKey, hatchingPotionKey) } } override suspend fun getTasks(): TaskList? = process { apiService.getTasks() } - override fun getTasks(type: String): Flowable { - return apiService.getTasks(type).compose(configureApiCallObserver()) + override suspend fun getTasks(type: String): TaskList? { + return process { apiService.getTasks(type) } } - override fun getTasks(type: String, dueDate: String): Flowable { - return apiService.getTasks(type, dueDate).compose(configureApiCallObserver()) + override suspend fun getTasks(type: String, dueDate: String): TaskList? { + return process { apiService.getTasks(type, dueDate) } } override suspend fun unlockPath(path: String): UnlockResponse? { return process { apiService.unlockPath(path) } } - override fun getTask(id: String): Flowable { - return apiService.getTask(id).compose(configureApiCallObserver()) + override suspend fun getTask(id: String): Task? { + return process { apiService.getTask(id) } } override suspend fun postTaskDirection(id: String, direction: String): TaskDirectionData? { return process { apiService.postTaskDirection(id, direction) } } - override fun bulkScoreTasks(data: List>): Flowable { - return apiService.bulkScoreTasks(data).compose(configureApiCallObserver()) + override suspend fun bulkScoreTasks(data: List>): BulkTaskScoringData? { + return process { apiService.bulkScoreTasks(data) } } - override fun postTaskNewPosition(id: String, position: Int): Flowable> { - return apiService.postTaskNewPosition(id, position).compose(configureApiCallObserver()) + override suspend fun postTaskNewPosition(id: String, position: Int): List? { + return process { apiService.postTaskNewPosition(id, position) } } override suspend fun scoreChecklistItem(taskId: String, itemId: String): Task? { return process { apiService.scoreChecklistItem(taskId, itemId) } } - override fun createTask(item: Task): Flowable { - return apiService.createTask(item).compose(configureApiCallObserver()) + override suspend fun createTask(item: Task): Task? { + return process { apiService.createTask(item) } } - override fun createTasks(tasks: List): Flowable> { - return apiService.createTasks(tasks).compose(configureApiCallObserver()) + override suspend fun createTasks(tasks: List): List? { + return process { apiService.createTasks(tasks) } } - override fun updateTask(id: String, item: Task): Flowable { - return apiService.updateTask(id, item).compose(configureApiCallObserver()) + override suspend fun updateTask(id: String, item: Task): Task? { + return process { apiService.updateTask(id, item) } } - override fun deleteTask(id: String): Flowable { - return apiService.deleteTask(id).compose(configureApiCallObserver()) + override suspend fun deleteTask(id: String): Void? { + return process { apiService.deleteTask(id) } } - override fun createTag(tag: Tag): Flowable { - return apiService.createTag(tag).compose(configureApiCallObserver()) + override suspend fun createTag(tag: Tag): Tag? { + return process { apiService.createTag(tag) } } - override fun updateTag(id: String, tag: Tag): Flowable { - return apiService.updateTag(id, tag).compose(configureApiCallObserver()) + override suspend fun updateTag(id: String, tag: Tag): Tag? { + return process { apiService.updateTag(id, tag) } } - override fun deleteTag(id: String): Flowable { - return apiService.deleteTag(id).compose(configureApiCallObserver()) + override suspend fun deleteTag(id: String): Void? { + return process { apiService.deleteTag(id) } } override suspend fun sleep(): Boolean? = process { apiService.sleep() } @@ -515,16 +488,12 @@ class ApiClientImpl( override suspend fun disableClasses(): User? = process { apiService.disableClasses() } - override fun markPrivateMessagesRead(): Flowable { - // This is necessary, because the API call returns weird data. + override suspend fun markPrivateMessagesRead(): Void { return apiService.markPrivateMessagesRead() - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .doOnError(this) } - override fun listGroups(type: String): Flowable> { - return apiService.listGroups(type).compose(configureApiCallObserver()) + override suspend fun listGroups(type: String): List? { + return process { apiService.listGroups(type) } } override suspend fun getGroup(groupId: String): Group? { @@ -555,15 +524,15 @@ class ApiClientImpl( return processResponse(apiService.leaveGroup(groupId, keepChallenges)) } - override fun postGroupChat(groupId: String, message: Map): Flowable { - return apiService.postGroupChat(groupId, message).compose(configureApiCallObserver()) + override suspend fun postGroupChat(groupId: String, message: Map): PostChatMessageResult? { + return process { apiService.postGroupChat(groupId, message) } } - override fun deleteMessage(groupId: String, messageId: String): Flowable { - return apiService.deleteMessage(groupId, messageId).compose(configureApiCallObserver()) + override suspend fun deleteMessage(groupId: String, messageId: String): Void? { + return process { apiService.deleteMessage(groupId, messageId) } } - override fun deleteInboxMessage(id: String): Flowable { - return apiService.deleteInboxMessage(id).compose(configureApiCallObserver()) + override suspend fun deleteInboxMessage(id: String): Void? { + return process { apiService.deleteInboxMessage(id) } } override suspend fun getGroupMembers(groupId: String, includeAllPublicFields: Boolean?): List? { @@ -574,181 +543,181 @@ class ApiClientImpl( return processResponse(apiService.getGroupMembers(groupId, includeAllPublicFields, lastId)) } - override fun likeMessage(groupId: String, mid: String): Flowable { - return apiService.likeMessage(groupId, mid).compose(configureApiCallObserver()) + override suspend fun likeMessage(groupId: String, mid: String): ChatMessage? { + return process { apiService.likeMessage(groupId, mid) } } - override fun flagMessage(groupId: String, mid: String, data: MutableMap): Flowable { - return apiService.flagMessage(groupId, mid, data).compose(configureApiCallObserver()) + override suspend fun flagMessage(groupId: String, mid: String, data: MutableMap): Void? { + return process { apiService.flagMessage(groupId, mid, data) } } - override fun flagInboxMessage(mid: String, data: MutableMap): Flowable { - return apiService.flagInboxMessage(mid, data).compose(configureApiCallObserver()) + override suspend fun flagInboxMessage(mid: String, data: MutableMap): Void? { + return process { apiService.flagInboxMessage(mid, data) } } - override fun seenMessages(groupId: String): Flowable { - return apiService.seenMessages(groupId).compose(configureApiCallObserver()) + override suspend fun seenMessages(groupId: String): Void? { + return process { apiService.seenMessages(groupId) } } - override fun inviteToGroup(groupId: String, inviteData: Map): Flowable> { - return apiService.inviteToGroup(groupId, inviteData).compose(configureApiCallObserver()) + override suspend fun inviteToGroup(groupId: String, inviteData: Map): List? { + return process { apiService.inviteToGroup(groupId, inviteData) } } - override fun rejectGroupInvite(groupId: String): Flowable { - return apiService.rejectGroupInvite(groupId).compose(configureApiCallObserver()) + override suspend fun rejectGroupInvite(groupId: String): Void? { + return process { apiService.rejectGroupInvite(groupId) } } - override fun acceptQuest(groupId: String): Flowable { - return apiService.acceptQuest(groupId).compose(configureApiCallObserver()) + override suspend fun acceptQuest(groupId: String): Void? { + return process { apiService.acceptQuest(groupId) } } - override fun rejectQuest(groupId: String): Flowable { - return apiService.rejectQuest(groupId).compose(configureApiCallObserver()) + override suspend fun rejectQuest(groupId: String): Void? { + return process { apiService.rejectQuest(groupId) } } - override fun cancelQuest(groupId: String): Flowable { - return apiService.cancelQuest(groupId).compose(configureApiCallObserver()) + override suspend fun cancelQuest(groupId: String): Void? { + return process { apiService.cancelQuest(groupId) } } - override fun forceStartQuest(groupId: String, group: Group): Flowable { - return apiService.forceStartQuest(groupId, group).compose(configureApiCallObserver()) + override suspend fun forceStartQuest(groupId: String, group: Group): Quest? { + return process { apiService.forceStartQuest(groupId, group) } } - override fun inviteToQuest(groupId: String, questKey: String): Flowable { - return apiService.inviteToQuest(groupId, questKey).compose(configureApiCallObserver()) + override suspend fun inviteToQuest(groupId: String, questKey: String): Quest? { + return process { apiService.inviteToQuest(groupId, questKey) } } - override fun abortQuest(groupId: String): Flowable { - return apiService.abortQuest(groupId).compose(configureApiCallObserver()) + override suspend fun abortQuest(groupId: String): Quest? { + return process { apiService.abortQuest(groupId) } } - override fun leaveQuest(groupId: String): Flowable { - return apiService.leaveQuest(groupId).compose(configureApiCallObserver()) + override suspend fun leaveQuest(groupId: String): Void? { + return process { apiService.leaveQuest(groupId) } } - override fun validatePurchase(request: PurchaseValidationRequest): Flowable { - return apiService.validatePurchase(request).compose(configureApiCallObserver()) + override suspend fun validatePurchase(request: PurchaseValidationRequest): PurchaseValidationResult? { + return process { apiService.validatePurchase(request) } } - override fun changeCustomDayStart(updateObject: Map): Flowable { - return apiService.changeCustomDayStart(updateObject).compose(configureApiCallObserver()) + override suspend fun changeCustomDayStart(updateObject: Map): User? { + return process { apiService.changeCustomDayStart(updateObject) } } override suspend fun getMember(memberId: String) = processResponse(apiService.getMember(memberId)) override suspend fun getMemberWithUsername(username: String) = processResponse(apiService.getMemberWithUsername(username)) - override fun getMemberAchievements(memberId: String): Flowable> { - return apiService.getMemberAchievements(memberId, languageCode).compose(configureApiCallObserver()) + override suspend fun getMemberAchievements(memberId: String): List? { + return process { apiService.getMemberAchievements(memberId, languageCode) } } - override fun findUsernames(username: String, context: String?, id: String?): Flowable> { - return apiService.findUsernames(username, context, id).compose(configureApiCallObserver()) + override suspend fun findUsernames(username: String, context: String?, id: String?): List? { + return process { apiService.findUsernames(username, context, id) } } override suspend fun postPrivateMessage(messageDetails: Map): PostChatMessageResult? { return process { apiService.postPrivateMessage(messageDetails) } } - override fun retrieveShopIventory(identifier: String): Flowable { - return apiService.retrieveShopInventory(identifier, languageCode).compose(configureApiCallObserver()) + override suspend fun retrieveShopIventory(identifier: String): Shop? { + return process { apiService.retrieveShopInventory(identifier, languageCode) } } - override fun addPushDevice(pushDeviceData: Map): Flowable> { - return apiService.addPushDevice(pushDeviceData).compose(configureApiCallObserver()) + override suspend fun addPushDevice(pushDeviceData: Map): List? { + return process { apiService.addPushDevice(pushDeviceData) } } - override fun deletePushDevice(regId: String): Flowable> { - return apiService.deletePushDevice(regId).compose(configureApiCallObserver()) + override suspend fun deletePushDevice(regId: String): List? { + return process { apiService.deletePushDevice(regId) } } - override fun getUserChallenges(page: Int, memberOnly: Boolean): Flowable> { + override suspend fun getUserChallenges(page: Int, memberOnly: Boolean): List? { return if (memberOnly) { - apiService.getUserChallenges(page, memberOnly).compose(configureApiCallObserver()) + process { apiService.getUserChallenges(page, memberOnly) } } else { - apiService.getUserChallenges(page).compose(configureApiCallObserver()) + process { apiService.getUserChallenges(page) } } } - override fun getChallengeTasks(challengeId: String): Flowable { - return apiService.getChallengeTasks(challengeId).compose(configureApiCallObserver()) + override suspend fun getChallengeTasks(challengeId: String): TaskList? { + return process { apiService.getChallengeTasks(challengeId) } } - override fun getChallenge(challengeId: String): Flowable { - return apiService.getChallenge(challengeId).compose(configureApiCallObserver()) + override suspend fun getChallenge(challengeId: String): Challenge? { + return process { apiService.getChallenge(challengeId) } } - override fun joinChallenge(challengeId: String): Flowable { - return apiService.joinChallenge(challengeId).compose(configureApiCallObserver()) + override suspend fun joinChallenge(challengeId: String): Challenge? { + return process { apiService.joinChallenge(challengeId) } } - override fun leaveChallenge(challengeId: String, body: LeaveChallengeBody): Flowable { - return apiService.leaveChallenge(challengeId, body).compose(configureApiCallObserver()) + override suspend fun leaveChallenge(challengeId: String, body: LeaveChallengeBody): Void? { + return process { apiService.leaveChallenge(challengeId, body) } } - override fun createChallenge(challenge: Challenge): Flowable { - return apiService.createChallenge(challenge).compose(configureApiCallObserver()) + override suspend fun createChallenge(challenge: Challenge): Challenge? { + return process { apiService.createChallenge(challenge) } } - override fun createChallengeTasks(challengeId: String, tasks: List): Flowable> { - return apiService.createChallengeTasks(challengeId, tasks).compose(configureApiCallObserver()) + override suspend fun createChallengeTasks(challengeId: String, tasks: List): List? { + return process { apiService.createChallengeTasks(challengeId, tasks) } } - override fun createChallengeTask(challengeId: String, task: Task): Flowable { - return apiService.createChallengeTask(challengeId, task).compose(configureApiCallObserver()) + override suspend fun createChallengeTask(challengeId: String, task: Task): Task? { + return process { apiService.createChallengeTask(challengeId, task) } } - override fun updateChallenge(challenge: Challenge): Flowable { - return apiService.updateChallenge(challenge.id ?: "", challenge).compose(configureApiCallObserver()) + override suspend fun updateChallenge(challenge: Challenge): Challenge? { + return process { apiService.updateChallenge(challenge.id ?: "", challenge) } } - override fun deleteChallenge(challengeId: String): Flowable { - return apiService.deleteChallenge(challengeId).compose(configureApiCallObserver()) + override suspend fun deleteChallenge(challengeId: String): Void? { + return process { apiService.deleteChallenge(challengeId) } } - override fun debugAddTenGems(): Flowable { - return apiService.debugAddTenGems().compose(configureApiCallObserver()) + override suspend fun debugAddTenGems(): Void? { + return process { apiService.debugAddTenGems() } } - override fun readNotification(notificationId: String): Flowable> { - return apiService.readNotification(notificationId).compose(configureApiCallObserver()) + override suspend fun readNotification(notificationId: String): List? { + return process { apiService.readNotification(notificationId) } } - override fun readNotifications(notificationIds: Map>): Flowable> { - return apiService.readNotifications(notificationIds).compose(configureApiCallObserver()) + override suspend fun readNotifications(notificationIds: Map>): List? { + return process { apiService.readNotifications(notificationIds) } } - override fun seeNotifications(notificationIds: Map>): Flowable> { - return apiService.seeNotifications(notificationIds).compose(configureApiCallObserver()) + override suspend fun seeNotifications(notificationIds: Map>): List? { + return process { apiService.seeNotifications(notificationIds) } } - override fun openMysteryItem(): Flowable { - return apiService.openMysteryItem().compose(configureApiCallObserver()) + override suspend fun openMysteryItem(): Equipment? { + return process { apiService.openMysteryItem() } } - override fun runCron(): Flowable { - return apiService.runCron().compose(configureApiCallObserver()) + override suspend fun runCron(): Void? { + return process { apiService.runCron() } } override suspend fun reroll(): User? = process { apiService.reroll() } - override fun resetAccount(): Flowable { - return apiService.resetAccount().compose(configureApiCallObserver()) + override suspend fun resetAccount(): Void? { + return process { apiService.resetAccount() } } - override fun deleteAccount(password: String): Flowable { + override suspend fun deleteAccount(password: String): Void? { val updateObject = HashMap() updateObject["password"] = password - return apiService.deleteAccount(updateObject).compose(configureApiCallObserver()) + return process { apiService.deleteAccount(updateObject) } } override suspend fun togglePinnedItem(pinType: String, path: String): Void? { return process { apiService.togglePinnedItem(pinType, path) } } - override fun sendPasswordResetEmail(email: String): Flowable { + override suspend fun sendPasswordResetEmail(email: String): Void? { val data = HashMap() data["email"] = email - return apiService.sendPasswordResetEmail(data).compose(configureApiCallObserver()) + return process { apiService.sendPasswordResetEmail(data) } } override suspend fun updateLoginName(newLoginName: String, password: String): Void? { @@ -764,55 +733,55 @@ class ApiClientImpl( return process { apiService.updateLoginName(updateObject) } } - override fun verifyUsername(username: String): Flowable { + override suspend fun verifyUsername(username: String): VerifyUsernameResponse? { val updateObject = HashMap() updateObject["username"] = username - return this.apiService.verifyUsername(updateObject).compose(configureApiCallObserver()) + return process { this.apiService.verifyUsername(updateObject) } } - override fun updateEmail(newEmail: String, password: String): Flowable { + override suspend fun updateEmail(newEmail: String, password: String): Void? { val updateObject = HashMap() updateObject["newEmail"] = newEmail if (password.isNotBlank()) { updateObject["password"] = password } - return apiService.updateEmail(updateObject).compose(configureApiCallObserver()) + return process { apiService.updateEmail(updateObject) } } - override fun updatePassword( + override suspend fun updatePassword( oldPassword: String, newPassword: String, newPasswordConfirmation: String - ): Flowable { + ): Void? { val updateObject = HashMap() updateObject["password"] = oldPassword updateObject["newPassword"] = newPassword updateObject["confirmPassword"] = newPasswordConfirmation - return apiService.updatePassword(updateObject).compose(configureApiCallObserver()) + return process { apiService.updatePassword(updateObject) } } - override fun allocatePoint(stat: String): Flowable { - return apiService.allocatePoint(stat).compose(configureApiCallObserver()) + override suspend fun allocatePoint(stat: String): Stats? { + return process { apiService.allocatePoint(stat) } } - override fun transferGems(giftedID: String, amount: Int): Flowable { - return apiService.transferGems(mapOf(Pair("toUserId", giftedID), Pair("gemAmount", amount))).compose(configureApiCallObserver()) + override suspend fun transferGems(giftedID: String, amount: Int): Void? { + return process { apiService.transferGems(mapOf(Pair("toUserId", giftedID), Pair("gemAmount", amount))) } } - override fun getTeamPlans(): Flowable> { - return apiService.getTeamPlans().compose(configureApiCallObserver()) + override suspend fun getTeamPlans(): List? { + return process { apiService.getTeamPlans() } } override suspend fun getTeamPlanTasks(teamID: String): TaskList? { return processResponse(apiService.getTeamPlanTasks(teamID)) } - override fun bulkAllocatePoints( + override suspend fun bulkAllocatePoints( strength: Int, intelligence: Int, constitution: Int, perception: Int - ): Flowable { + ): Stats? { val body = HashMap>() val stats = HashMap() stats["str"] = strength @@ -820,11 +789,11 @@ class ApiClientImpl( stats["con"] = constitution stats["per"] = perception body["stats"] = stats - return apiService.bulkAllocatePoints(body).compose(configureApiCallObserver()) + return process { apiService.bulkAllocatePoints(body) } } - override fun retrieveMarketGear(): Flowable { - return apiService.retrieveMarketGear(languageCode).compose(configureApiCallObserver()) + override suspend fun retrieveMarketGear(): Shop? { + return process { apiService.retrieveMarketGear(languageCode) } } override suspend fun getWorldState(): WorldState? = process { apiService.worldState() } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/ChallengeRepositoryImpl.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/ChallengeRepositoryImpl.kt index 747992885..8621aa273 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/ChallengeRepositoryImpl.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/ChallengeRepositoryImpl.kt @@ -10,8 +10,7 @@ import com.habitrpg.android.habitica.models.tasks.Task import com.habitrpg.android.habitica.models.tasks.TaskList import com.habitrpg.shared.habitica.models.tasks.TaskType import com.habitrpg.shared.habitica.models.tasks.TasksOrder -import io.reactivex.rxjava3.core.Flowable -import retrofit2.HttpException +import kotlinx.coroutines.flow.Flow class ChallengeRepositoryImpl( localRepository: ChallengeLocalRepository, @@ -19,47 +18,42 @@ class ChallengeRepositoryImpl( userID: String ) : BaseRepositoryImpl(localRepository, apiClient, userID), ChallengeRepository { - override fun isChallengeMember(challengeID: String): Flowable { + override fun isChallengeMember(challengeID: String): Flow { return localRepository.isChallengeMember(userID, challengeID) } - override fun getChallengepMembership(id: String): Flowable { + override fun getChallengepMembership(id: String): Flow { return localRepository.getChallengeMembership(userID, id) } - override fun getChallengeMemberships(): Flowable> { + override fun getChallengeMemberships(): Flow> { return localRepository.getChallengeMemberships(userID) } - override fun getChallenge(challengeId: String): Flowable { + override fun getChallenge(challengeId: String): Flow { return localRepository.getChallenge(challengeId) } - override fun getChallengeTasks(challengeId: String): Flowable> { + override fun getChallengeTasks(challengeId: String): Flow> { return localRepository.getTasks(challengeId) } - override fun retrieveChallenge(challengeID: String): Flowable { - return apiClient.getChallenge(challengeID).doOnNext { - localRepository.save(it) - } - .doOnError { - if (it is HttpException && it.code() == 404) { - localRepository.getChallenge(challengeID).firstElement().subscribe { challenge -> - localRepository.delete(challenge) - } - } - } + override suspend fun retrieveChallenge(challengeID: String): Challenge? { + val challenge = apiClient.getChallenge(challengeID) ?: return null + localRepository.save(challenge) + return challenge } - override fun retrieveChallengeTasks(challengeID: String): Flowable { - return apiClient.getChallengeTasks(challengeID).doOnNext { tasks -> + override suspend fun retrieveChallengeTasks(challengeID: String): TaskList? { + val tasks = apiClient.getChallengeTasks(challengeID) + if (tasks != null) { val taskList = tasks.tasks.values.toList() taskList.forEach { it.userId = challengeID } localRepository.save(taskList) } + return tasks } private fun getTaskOrders(taskList: List): TasksOrder { @@ -81,75 +75,81 @@ class ChallengeRepositoryImpl( return tasksOrder } - private fun addChallengeTasks(challenge: Challenge, addedTaskList: List): Flowable { - return when { - addedTaskList.count() == 1 -> apiClient.createChallengeTask(challenge.id ?: "", addedTaskList[0]).map { challenge } - addedTaskList.count() > 1 -> apiClient.createChallengeTasks(challenge.id ?: "", addedTaskList).map { challenge } - else -> Flowable.just(challenge) + private suspend fun addChallengeTasks(challenge: Challenge, addedTaskList: List) { + when { + addedTaskList.count() == 1 -> apiClient.createChallengeTask(challenge.id ?: "", addedTaskList[0]) + addedTaskList.count() > 1 -> apiClient.createChallengeTasks(challenge.id ?: "", addedTaskList) } } - override fun createChallenge(challenge: Challenge, taskList: List): Flowable { + override suspend fun createChallenge(challenge: Challenge, taskList: List): Challenge? { challenge.tasksOrder = getTaskOrders(taskList) - return apiClient.createChallenge(challenge).flatMap { - addChallengeTasks(it, taskList) + val createdChallenge = apiClient.createChallenge(challenge) + if (createdChallenge != null) { + addChallengeTasks(createdChallenge, taskList) } + return createdChallenge } - override fun updateChallenge( + override suspend fun updateChallenge( challenge: Challenge, fullTaskList: List, addedTaskList: List, updatedTaskList: List, removedTaskList: List - ): Flowable { - - var flowable: Flowable<*> = Flowable.just("") - + ): Challenge? { updatedTaskList .map { localRepository.getUnmanagedCopy(it) } .forEach { task -> - flowable = flowable.flatMap { apiClient.updateTask(task.id ?: "", task) } + apiClient.updateTask(task.id ?: "", task) } removedTaskList.forEach { task -> - flowable = flowable.flatMap { apiClient.deleteTask(task) } + apiClient.deleteTask(task) } if (addedTaskList.isNotEmpty()) { - flowable = flowable.flatMap { addChallengeTasks(challenge, addedTaskList) } + addChallengeTasks(challenge, addedTaskList) } challenge.tasksOrder = getTaskOrders(fullTaskList) - return flowable.flatMap { apiClient.updateChallenge(challenge) } - .doOnNext { localRepository.save(challenge) } + val updatedChallenges = apiClient.updateChallenge(challenge) + if (updatedChallenges != null) { + localRepository.save(updatedChallenges) + } + return updatedChallenges } - override fun deleteChallenge(challengeId: String): Flowable { + override suspend fun deleteChallenge(challengeId: String): Void? { return apiClient.deleteChallenge(challengeId) } - override fun getChallenges(): Flowable> { + override fun getChallenges(): Flow> { return localRepository.challenges } - override fun getUserChallenges(userId: String?): Flowable> { + override fun getUserChallenges(userId: String?): Flow> { return localRepository.getUserChallenges(userId ?: userID) } - override fun retrieveChallenges(page: Int, memberOnly: Boolean): Flowable> { - return apiClient.getUserChallenges(page, memberOnly) - .doOnNext { localRepository.saveChallenges(it, page == 0, memberOnly, userID) } + override suspend fun retrieveChallenges(page: Int, memberOnly: Boolean): List? { + val challenges = apiClient.getUserChallenges(page, memberOnly) + if (challenges != null) { + localRepository.saveChallenges(challenges, page == 0, memberOnly, userID) + } + return challenges } - override fun leaveChallenge(challenge: Challenge, keepTasks: String): Flowable { - return apiClient.leaveChallenge(challenge.id ?: "", LeaveChallengeBody(keepTasks)) - .doOnNext { localRepository.setParticipating(userID, challenge.id ?: "", false) } + override suspend fun leaveChallenge(challenge: Challenge, keepTasks: String): Void? { + apiClient.leaveChallenge(challenge.id ?: "", LeaveChallengeBody(keepTasks)) + localRepository.setParticipating(userID, challenge.id ?: "", false) + return null } - override fun joinChallenge(challenge: Challenge): Flowable { - return apiClient.joinChallenge(challenge.id ?: "") - .doOnNext { localRepository.setParticipating(userID, challenge.id ?: "", true) } + override suspend fun joinChallenge(challenge: Challenge): Challenge? { + val returnedChallenge = apiClient.joinChallenge(challenge.id ?: "") ?: return null + localRepository.setParticipating(userID, returnedChallenge.id ?: "", true) + return returnedChallenge } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/ContentRepositoryImpl.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/ContentRepositoryImpl.kt index 5028049df..ba6ed961b 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/ContentRepositoryImpl.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/ContentRepositoryImpl.kt @@ -8,8 +8,8 @@ import com.habitrpg.android.habitica.helpers.AprilFoolsHandler import com.habitrpg.android.habitica.models.ContentResult import com.habitrpg.android.habitica.models.WorldState import com.habitrpg.android.habitica.models.inventory.SpecialItem -import io.reactivex.rxjava3.core.Flowable import io.realm.RealmList +import kotlinx.coroutines.flow.Flow import java.util.Date class ContentRepositoryImpl( @@ -52,7 +52,7 @@ class ContentRepositoryImpl( return null } - override fun getWorldState(): Flowable { + override fun getWorldState(): Flow { return localRepository.getWorldState() } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/CustomizationRepositoryImpl.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/CustomizationRepositoryImpl.kt index 502e9cd64..ee17bff76 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/CustomizationRepositoryImpl.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/CustomizationRepositoryImpl.kt @@ -4,7 +4,7 @@ import com.habitrpg.android.habitica.data.ApiClient import com.habitrpg.android.habitica.data.CustomizationRepository import com.habitrpg.android.habitica.data.local.CustomizationLocalRepository import com.habitrpg.android.habitica.models.inventory.Customization -import io.reactivex.rxjava3.core.Flowable +import kotlinx.coroutines.flow.Flow class CustomizationRepositoryImpl( localRepository: CustomizationLocalRepository, @@ -12,7 +12,7 @@ class CustomizationRepositoryImpl( userID: String ) : BaseRepositoryImpl(localRepository, apiClient, userID), CustomizationRepository { - override fun getCustomizations(type: String, category: String?, onlyAvailable: Boolean): Flowable> { + override fun getCustomizations(type: String, category: String?, onlyAvailable: Boolean): Flow> { return localRepository.getCustomizations(type, category, onlyAvailable) } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/FAQRepositoryImpl.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/FAQRepositoryImpl.kt index eb7ef1dc9..8c74b2722 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/FAQRepositoryImpl.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/FAQRepositoryImpl.kt @@ -4,14 +4,14 @@ import com.habitrpg.android.habitica.data.ApiClient import com.habitrpg.android.habitica.data.FAQRepository import com.habitrpg.android.habitica.data.local.FAQLocalRepository import com.habitrpg.android.habitica.models.FAQArticle -import io.reactivex.rxjava3.core.Flowable +import kotlinx.coroutines.flow.Flow class FAQRepositoryImpl(localRepository: FAQLocalRepository, apiClient: ApiClient, userID: String) : BaseRepositoryImpl(localRepository, apiClient, userID), FAQRepository { - override fun getArticle(position: Int): Flowable { + override fun getArticle(position: Int): Flow { return localRepository.getArticle(position) } - override fun getArticles(): Flowable> { + override fun getArticles(): Flow> { return localRepository.articles } } 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 e8a165d56..712c970ae 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 @@ -22,8 +22,8 @@ import com.habitrpg.android.habitica.models.user.OwnedMount import com.habitrpg.android.habitica.models.user.OwnedPet import com.habitrpg.android.habitica.models.user.User import com.habitrpg.shared.habitica.models.responses.FeedResponse -import io.reactivex.rxjava3.core.Flowable import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.firstOrNull class InventoryRepositoryImpl( localRepository: InventoryLocalRepository, @@ -35,7 +35,7 @@ class InventoryRepositoryImpl( override fun getQuestContent(key: String) = localRepository.getQuestContent(key) - override fun getEquipment(searchedKeys: List): Flowable> { + override fun getEquipment(searchedKeys: List): Flow> { return localRepository.getEquipment(searchedKeys) } @@ -43,7 +43,7 @@ class InventoryRepositoryImpl( return localRepository.getArmoireRemainingCount() } - override fun getInAppRewards(): Flowable> { + override fun getInAppRewards(): Flow> { return localRepository.getInAppRewards() } @@ -55,15 +55,15 @@ class InventoryRepositoryImpl( return rewards } - override fun getOwnedEquipment(type: String): Flowable> { + override fun getOwnedEquipment(type: String): Flow> { return localRepository.getOwnedEquipment(type) } - override fun getOwnedEquipment(): Flowable> { + override fun getOwnedEquipment(): Flow> { return localRepository.getOwnedEquipment() } - override fun getEquipmentType(type: String, set: String): Flowable> { + override fun getEquipmentType(type: String, set: String): Flow> { return localRepository.getEquipmentType(type, set) } @@ -71,36 +71,31 @@ class InventoryRepositoryImpl( return localRepository.getOwnedItems(itemType, userID, includeZero) } - override fun getOwnedItems(includeZero: Boolean): Flowable> { + override fun getOwnedItems(includeZero: Boolean): Flow> { return localRepository.getOwnedItems(userID, includeZero) } override fun getItems(itemClass: Class, keys: Array): Flow> { - return localRepository.getItemsFlowable(itemClass, keys) - } - - override fun getItemsFlowable(itemClass: Class): Flowable> { - return localRepository.getItemsFlowable(itemClass) + return localRepository.getItems(itemClass, keys) } override fun getItems(itemClass: Class): Flow> { return localRepository.getItems(itemClass) } - override fun getEquipment(key: String): Flowable { + override fun getEquipment(key: String): Flow { return localRepository.getEquipment(key) } - override fun openMysteryItem(user: User?): Flowable { - return apiClient.openMysteryItem() - .flatMap { localRepository.getEquipment(it.key ?: "").firstElement().toFlowable() } - .doOnNext { itemData -> - val liveEquipment = localRepository.getLiveObject(itemData) - localRepository.executeTransaction { - liveEquipment?.owned = true - } - localRepository.decrementMysteryItemCount(user) - } + override suspend fun openMysteryItem(user: User?): Equipment? { + val item = apiClient.openMysteryItem() + val equipment = localRepository.getEquipment(item?.key ?: "").firstOrNull() ?: return null + val liveEquipment = localRepository.getLiveObject(equipment) + localRepository.executeTransaction { + liveEquipment?.owned = true + } + localRepository.decrementMysteryItemCount(user) + return equipment } override fun saveEquipment(equipment: Equipment) { @@ -135,44 +130,42 @@ class InventoryRepositoryImpl( localRepository.updateOwnedEquipment(user) } - override fun changeOwnedCount(type: String, key: String, amountToAdd: Int) { + override suspend fun changeOwnedCount(type: String, key: String, amountToAdd: Int) { localRepository.changeOwnedCount(type, key, userID, amountToAdd) } - override fun sellItem(type: String, key: String): Flowable { - return localRepository.getOwnedItem(userID, type, key, true) - .flatMap { item -> sellItem(item) } + override suspend fun sellItem(type: String, key: String): User? { + val item = localRepository.getOwnedItem(userID, type, key, true).firstOrNull() ?: return null + return sellItem(item) } - override fun sellItem(item: OwnedItem): Flowable { - return localRepository.getItem(item.itemType ?: "", item.key ?: "") - .flatMap { newItem -> sellItem(newItem, item) } + override suspend fun sellItem(ownedItem: OwnedItem): User? { + val item = localRepository.getItem(ownedItem.itemType ?: "", ownedItem.key ?: "").firstOrNull() ?: return null + return sellItem(item, ownedItem) } - override fun getLatestMysteryItem(): Flowable { + override fun getLatestMysteryItem(): Flow { return localRepository.getLatestMysteryItem() } - override fun getItem(type: String, key: String): Flowable { + override fun getItem(type: String, key: String): Flow { return localRepository.getItem(type, key) } - private fun sellItem(item: Item, ownedItem: OwnedItem): Flowable { + private suspend fun sellItem(item: Item, ownedItem: OwnedItem): User? { localRepository.executeTransaction { val liveItem = localRepository.getLiveObject(ownedItem) liveItem?.numberOwned = (liveItem?.numberOwned ?: 0) - 1 } - return apiClient.sellItem(item.type, item.key) - .map { user -> - localRepository.soldItem(userID, user) - } + val user = apiClient.sellItem(item.type, item.key) ?: return null + return localRepository.soldItem(userID, user) } - override fun equipGear(equipment: String, asCostume: Boolean): Flowable { + override suspend fun equipGear(equipment: String, asCostume: Boolean): Items? { return equip(if (asCostume) "costume" else "equipped", equipment) } - override fun equip(type: String, key: String): Flowable { + override suspend fun equip(type: String, key: String): Items? { val liveUser = localRepository.getLiveUser(userID) if (liveUser != null) { @@ -199,47 +192,45 @@ class InventoryRepositoryImpl( } } } - return apiClient.equipItem(type, key) - .doOnNext { items -> - if (liveUser == null) return@doOnNext - localRepository.modify(liveUser) { liveUser -> - val newEquipped = items.gear?.equipped - val oldEquipped = liveUser.items?.gear?.equipped - val newCostume = items.gear?.costume - val oldCostume = liveUser.items?.gear?.costume - newEquipped?.let { equipped -> oldEquipped?.updateWith(equipped) } - newCostume?.let { costume -> oldCostume?.updateWith(costume) } - liveUser.items?.currentMount = items.currentMount - liveUser.items?.currentPet = items.currentPet - liveUser.balance = liveUser.balance - } - } + val items = apiClient.equipItem(type, key) ?: return null + if (liveUser == null) return null + localRepository.modify(liveUser) { liveUser -> + val newEquipped = items.gear?.equipped + val oldEquipped = liveUser.items?.gear?.equipped + val newCostume = items.gear?.costume + val oldCostume = liveUser.items?.gear?.costume + newEquipped?.let { equipped -> oldEquipped?.updateWith(equipped) } + newCostume?.let { costume -> oldCostume?.updateWith(costume) } + liveUser.items?.currentMount = items.currentMount + liveUser.items?.currentPet = items.currentPet + liveUser.balance = liveUser.balance + } + return items } - override fun feedPet(pet: Pet, food: Food): Flowable { - return apiClient.feedPet(pet.key ?: "", food.key) - .doOnNext { feedResponse -> - localRepository.feedPet(food.key, pet.key ?: "", feedResponse.value ?: 0, userID) - } + override suspend fun feedPet(pet: Pet, food: Food): FeedResponse? { + val feedResponse = apiClient.feedPet(pet.key ?: "", food.key) ?: return null + localRepository.feedPet(food.key, pet.key ?: "", feedResponse.value ?: 0, userID) + return feedResponse } - override fun hatchPet(egg: Egg, hatchingPotion: HatchingPotion, successFunction: () -> Unit): Flowable { + override suspend fun hatchPet(egg: Egg, hatchingPotion: HatchingPotion, successFunction: () -> Unit): Items? { if (appConfigManager.enableLocalChanges()) { localRepository.hatchPet(egg.key, hatchingPotion.key, userID) successFunction() } - return apiClient.hatchPet(egg.key, hatchingPotion.key) - .doOnNext { - localRepository.save(it, userID) - if (!appConfigManager.enableLocalChanges()) { - successFunction() - } - } + val items = apiClient.hatchPet(egg.key, hatchingPotion.key) ?: return null + localRepository.save(items, userID) + if (!appConfigManager.enableLocalChanges()) { + successFunction() + } + return items } - override fun inviteToQuest(quest: QuestContent): Flowable { - return apiClient.inviteToQuest("party", quest.key) - .doOnNext { localRepository.changeOwnedCount("quests", quest.key, userID, -1) } + override suspend fun inviteToQuest(quest: QuestContent): Quest? { + val newQuest = apiClient.inviteToQuest("party", quest.key) + localRepository.changeOwnedCount("quests", quest.key, userID, -1) + return newQuest } override suspend fun buyItem(user: User?, id: String, value: Double, purchaseQuantity: Int): BuyResponse? { @@ -270,15 +261,15 @@ class InventoryRepositoryImpl( return buyResponse } - override fun getAvailableLimitedItems(): Flowable> { + override fun getAvailableLimitedItems(): Flow> { return localRepository.getAvailableLimitedItems() } - override fun retrieveShopInventory(identifier: String): Flowable { + override suspend fun retrieveShopInventory(identifier: String): Shop? { return apiClient.retrieveShopIventory(identifier) } - override fun retrieveMarketGear(): Flowable { + override suspend fun retrieveMarketGear(): Shop? { return apiClient.retrieveMarketGear() } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/SocialRepositoryImpl.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/SocialRepositoryImpl.kt index 20de3196e..a99a5fcae 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/SocialRepositoryImpl.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/SocialRepositoryImpl.kt @@ -4,7 +4,6 @@ import com.habitrpg.android.habitica.BuildConfig import com.habitrpg.android.habitica.data.ApiClient import com.habitrpg.android.habitica.data.SocialRepository import com.habitrpg.android.habitica.data.local.SocialLocalRepository -import com.habitrpg.android.habitica.helpers.ExceptionHandler import com.habitrpg.android.habitica.models.Achievement import com.habitrpg.android.habitica.models.inventory.Quest import com.habitrpg.android.habitica.models.members.Member @@ -15,7 +14,6 @@ import com.habitrpg.android.habitica.models.social.Group import com.habitrpg.android.habitica.models.social.GroupMembership import com.habitrpg.android.habitica.models.social.InboxConversation import com.habitrpg.android.habitica.models.user.User -import io.reactivex.rxjava3.core.Flowable import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.flow.first @@ -38,13 +36,13 @@ class SocialRepositoryImpl( return retrievePartyMembers(groupID, true) } - override fun blockMember(userID: String): Flowable> { + override suspend fun blockMember(userID: String): List? { return apiClient.blockMember(userID) } override fun getGroupMembership(id: String) = localRepository.getGroupMembership(userID, id) - override fun getGroupMemberships(): Flowable> { + override fun getGroupMemberships(): Flow> { return localRepository.getGroupMemberships(userID) } @@ -54,18 +52,18 @@ class SocialRepositoryImpl( return messages } - override fun getGroupChat(groupId: String): Flowable> { + override fun getGroupChat(groupId: String): Flow> { return localRepository.getGroupChat(groupId) } - override fun markMessagesSeen(seenGroupId: String) { - apiClient.seenMessages(seenGroupId).subscribe({ }, ExceptionHandler.rx()) + override suspend fun markMessagesSeen(seenGroupId: String) { + apiClient.seenMessages(seenGroupId) } - override fun flagMessage(chatMessageID: String, additionalInfo: String, groupID: String?): Flowable { + override suspend fun flagMessage(chatMessageID: String, additionalInfo: String, groupID: String?): Void? { return when { - chatMessageID.isBlank() -> Flowable.empty() - userID == BuildConfig.ANDROID_TESTING_UUID -> Flowable.empty() + chatMessageID.isBlank() -> return null + userID == BuildConfig.ANDROID_TESTING_UUID -> return null else -> { val data = mutableMapOf() data["comment"] = additionalInfo @@ -78,38 +76,36 @@ class SocialRepositoryImpl( } } - override fun likeMessage(chatMessage: ChatMessage): Flowable { + override suspend fun likeMessage(chatMessage: ChatMessage): ChatMessage? { if (chatMessage.id.isBlank()) { - return Flowable.empty() + return null } val liked = chatMessage.userLikesMessage(userID) if (chatMessage.isManaged) { localRepository.likeMessage(chatMessage, userID, !liked) } - return apiClient.likeMessage(chatMessage.groupId ?: "", chatMessage.id) - .map { - it.groupId = chatMessage.groupId - it - } + val message = apiClient.likeMessage(chatMessage.groupId ?: "", chatMessage.id) + message?.groupId = chatMessage.groupId + return null } - override fun deleteMessage(chatMessage: ChatMessage): Flowable { - return if (chatMessage.isInboxMessage) { + override suspend fun deleteMessage(chatMessage: ChatMessage): Void? { + if (chatMessage.isInboxMessage) { apiClient.deleteInboxMessage(chatMessage.id) } else { apiClient.deleteMessage(chatMessage.groupId ?: "", chatMessage.id) - }.doOnNext { localRepository.deleteMessage(chatMessage.id) } + } + localRepository.deleteMessage(chatMessage.id) + return null } - override fun postGroupChat(groupId: String, messageObject: HashMap): Flowable { - return apiClient.postGroupChat(groupId, messageObject) - .map { postChatMessageResult -> - postChatMessageResult.message.groupId = groupId - postChatMessageResult - } + override suspend fun postGroupChat(groupId: String, messageObject: HashMap): PostChatMessageResult? { + val result = apiClient.postGroupChat(groupId, messageObject) + result?.message?.groupId = groupId + return result } - override fun postGroupChat(groupId: String, message: String): Flowable { + override suspend fun postGroupChat(groupId: String, message: String): PostChatMessageResult? { val messageObject = HashMap() messageObject["message"] = message return postGroupChat(groupId, messageObject) @@ -189,17 +185,16 @@ class SocialRepositoryImpl( return apiClient.updateGroup(copiedGroup.id, copiedGroup) } - override fun retrieveGroups(type: String): Flowable> { - return apiClient.listGroups(type) - .doOnNext { groups -> - if ("guilds" == type) { - val memberships = groups.map { - GroupMembership(userID, it.id) - } - localRepository.saveGroupMemberships(userID, memberships) - } - localRepository.save(groups) + override suspend fun retrieveGroups(type: String): List? { + val groups = apiClient.listGroups(type) ?: return null + if ("guilds" == type) { + val memberships = groups.map { + GroupMembership(userID, it.id) } + localRepository.saveGroupMemberships(userID, memberships) + } + localRepository.save(groups) + return groups } override fun getGroups(type: String) = localRepository.getGroups(type) @@ -219,14 +214,14 @@ class SocialRepositoryImpl( return messages } - override fun retrieveInboxConversations(): Flowable> { - return apiClient.retrieveInboxConversations().doOnNext { conversations -> - localRepository.saveInboxConversations(userID, conversations) - } + override suspend fun retrieveInboxConversations(): List? { + val conversations = apiClient.retrieveInboxConversations() ?: return null + localRepository.saveInboxConversations(userID, conversations) + return conversations } override suspend fun postPrivateMessage(recipientId: String, messageObject: HashMap): List? { - val message = apiClient.postPrivateMessage(messageObject) + apiClient.postPrivateMessage(messageObject) return retrieveInboxMessages(recipientId, 0) } @@ -246,7 +241,7 @@ class SocialRepositoryImpl( return members } - override fun inviteToGroup(id: String, inviteData: Map): Flowable> = apiClient.inviteToGroup(id, inviteData) + override suspend fun inviteToGroup(id: String, inviteData: Map) = apiClient.inviteToGroup(id, inviteData) override suspend fun retrieveMember(userId: String?): Member? { return if (userId == null) { @@ -264,11 +259,11 @@ class SocialRepositoryImpl( return retrieveMember(username) } - override fun findUsernames(username: String, context: String?, id: String?): Flowable> { + override suspend fun findUsernames(username: String, context: String?, id: String?): List? { return apiClient.findUsernames(username, context, id) } - override fun markPrivateMessagesRead(user: User?): Flowable { + override suspend fun markPrivateMessagesRead(user: User?): Void? { if (user?.isManaged == true) { localRepository.modify(user) { it.inbox?.hasUserSeenInbox = true @@ -298,57 +293,57 @@ class SocialRepositoryImpl( override fun getUserGroups(type: String?) = localRepository.getUserGroups(userID, type) - override fun acceptQuest(user: User?, partyId: String): Flowable { - return apiClient.acceptQuest(partyId) - .doOnNext { - user?.let { - localRepository.updateRSVPNeeded(it, false) - } + override suspend fun acceptQuest(user: User?, partyId: String): Void? { + apiClient.acceptQuest(partyId) + user?.let { + localRepository.updateRSVPNeeded(it, false) } + return null } - override fun rejectQuest(user: User?, partyId: String): Flowable { - return apiClient.rejectQuest(partyId) - .doOnNext { _ -> - user?.let { - localRepository.updateRSVPNeeded(it, false) - } + override suspend fun rejectQuest(user: User?, partyId: String): Void? { + apiClient.rejectQuest(partyId) + user?.let { + localRepository.updateRSVPNeeded(it, false) } + return null } - override fun leaveQuest(partyId: String): Flowable { + override suspend fun leaveQuest(partyId: String): Void? { return apiClient.leaveQuest(partyId) } - override fun cancelQuest(partyId: String): Flowable { - return apiClient.cancelQuest(partyId) - .doOnNext { localRepository.removeQuest(partyId) } + override suspend fun cancelQuest(partyId: String): Void? { + apiClient.cancelQuest(partyId) + localRepository.removeQuest(partyId) + return null } - override fun abortQuest(partyId: String): Flowable { - return apiClient.abortQuest(partyId) - .doOnNext { localRepository.removeQuest(partyId) } + override suspend fun abortQuest(partyId: String): Quest? { + val quest = apiClient.abortQuest(partyId) + localRepository.removeQuest(partyId) + return quest } - override fun rejectGroupInvite(groupId: String): Flowable { - return apiClient.rejectGroupInvite(groupId) - .doOnNext { - localRepository.rejectGroupInvitation(userID, groupId) - } + override suspend fun rejectGroupInvite(groupId: String): Void? { + apiClient.rejectGroupInvite(groupId) + localRepository.rejectGroupInvitation(userID, groupId) + return null } - override fun forceStartQuest(party: Group): Flowable { - return apiClient.forceStartQuest(party.id, localRepository.getUnmanagedCopy(party)) - .doOnNext { localRepository.setQuestActivity(party, true) } + override suspend fun forceStartQuest(party: Group): Quest? { + val quest = apiClient.forceStartQuest(party.id, localRepository.getUnmanagedCopy(party)) + localRepository.setQuestActivity(party, true) + return quest } - override fun getMemberAchievements(userId: String?): Flowable> { + override suspend fun getMemberAchievements(userId: String?): List? { return if (userId == null) { - Flowable.empty() + null } else apiClient.getMemberAchievements(userId) } - override fun transferGems(giftedID: String, amount: Int): Flowable { + override suspend fun transferGems(giftedID: String, amount: Int): Void? { return apiClient.transferGems(giftedID, amount) } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/TagRepositoryImpl.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/TagRepositoryImpl.kt index e70a7f83c..ff56233b0 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/TagRepositoryImpl.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/TagRepositoryImpl.kt @@ -4,58 +4,53 @@ import com.habitrpg.android.habitica.data.ApiClient import com.habitrpg.android.habitica.data.TagRepository import com.habitrpg.android.habitica.data.local.TagLocalRepository import com.habitrpg.android.habitica.models.Tag -import io.reactivex.rxjava3.core.Flowable -import io.reactivex.rxjava3.core.Single +import kotlinx.coroutines.flow.Flow class TagRepositoryImpl(localRepository: TagLocalRepository, apiClient: ApiClient, userID: String) : BaseRepositoryImpl(localRepository, apiClient, userID), TagRepository { - override fun getTags(): Flowable> { + override fun getTags(): Flow> { return getTags(userID) } - override fun getTags(userId: String): Flowable> { + override fun getTags(userId: String): Flow> { return localRepository.getTags(userId) } - override fun createTag(tag: Tag): Flowable { - return apiClient.createTag(tag) - .doOnNext { - it.userId = userID - localRepository.save(it) - } + override suspend fun createTag(tag: Tag): Tag? { + val savedTag = apiClient.createTag(tag) ?: return null + savedTag.userId = userID + localRepository.save(savedTag) + return savedTag } - override fun updateTag(tag: Tag): Flowable { - return apiClient.updateTag(tag.id, tag) - .doOnNext { - it.userId = userID - localRepository.save(it) - } + override suspend fun updateTag(tag: Tag): Tag? { + val savedTag = apiClient.updateTag(tag.id, tag) ?: return null + savedTag.userId = userID + localRepository.save(savedTag) + return savedTag } - override fun deleteTag(id: String): Flowable { - return apiClient.deleteTag(id) - .doOnNext { - localRepository.deleteTag(id) - } + override suspend fun deleteTag(id: String): Void? { + apiClient.deleteTag(id) + localRepository.deleteTag(id) + return null } - override fun createTags(tags: Collection): Single> { - return Flowable.defer { Flowable.fromIterable(tags) } - .filter { tag -> tag.name.isNotEmpty() } - .flatMap { this.createTag(it) } - .toList() + override suspend fun createTags(tags: Collection): List { + return tags.mapNotNull { + createTag(it) + } } - override fun updateTags(tags: Collection): Single> { - return Flowable.defer { Flowable.fromIterable(tags) } - .flatMap { this.updateTag(it) } - .toList() + override suspend fun updateTags(tags: Collection): List { + return tags.mapNotNull { + updateTag(it) + } } - override fun deleteTags(tagIds: Collection): Single> { - return Flowable.defer { Flowable.fromIterable(tagIds) } - .flatMap { this.deleteTag(it) } - .toList() + override suspend fun deleteTags(tagIds: Collection): List { + return tagIds.mapNotNull { + deleteTag(it) + } } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/TaskRepositoryImpl.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/TaskRepositoryImpl.kt index a6e0a8196..3a89070db 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/TaskRepositoryImpl.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/TaskRepositoryImpl.kt @@ -5,7 +5,7 @@ import com.habitrpg.android.habitica.data.ApiClient import com.habitrpg.android.habitica.data.TaskRepository import com.habitrpg.android.habitica.data.local.TaskLocalRepository import com.habitrpg.android.habitica.helpers.AppConfigManager -import com.habitrpg.android.habitica.helpers.ExceptionHandler +import com.habitrpg.android.habitica.helpers.launchCatching import com.habitrpg.android.habitica.interactors.ScoreTaskLocallyInteractor import com.habitrpg.android.habitica.models.BaseMainObject import com.habitrpg.android.habitica.models.responses.BulkTaskScoringData @@ -20,9 +20,7 @@ import com.habitrpg.shared.habitica.models.responses.TaskDirectionData import com.habitrpg.shared.habitica.models.responses.TaskScoringResult import com.habitrpg.shared.habitica.models.tasks.TaskType import com.habitrpg.shared.habitica.models.tasks.TasksOrder -import io.reactivex.rxjava3.core.Flowable -import io.reactivex.rxjava3.core.Maybe -import io.reactivex.rxjava3.core.Single +import kotlinx.coroutines.MainScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.map @@ -43,9 +41,6 @@ class TaskRepositoryImpl( override fun getTasks(taskType: TaskType, userID: String?, includedGroupIDs: Array): Flow> = this.localRepository.getTasks(taskType, userID ?: this.userID, includedGroupIDs) - override fun getTasksFlowable(taskType: TaskType, userID: String?, includedGroupIDs: Array): Flowable> = - this.localRepository.getTasksFlowable(taskType, userID ?: this.userID, includedGroupIDs) - override fun saveTasks(userId: String, order: TasksOrder, tasks: TaskList) { localRepository.saveTasks(userId, order, tasks) } @@ -56,18 +51,18 @@ class TaskRepositoryImpl( return tasks } - override fun retrieveCompletedTodos(userId: String?): Flowable { - return this.apiClient.getTasks("completedTodos") - .doOnNext { taskList -> - val tasks = taskList.tasks - this.localRepository.saveCompletedTodos(userId ?: this.userID, tasks.values) - } + override suspend fun retrieveCompletedTodos(userId: String?): TaskList? { + val taskList = this.apiClient.getTasks("completedTodos") ?: return null + val tasks = taskList.tasks + this.localRepository.saveCompletedTodos(userId ?: this.userID, tasks.values) + return taskList } - override fun retrieveTasks(userId: String, tasksOrder: TasksOrder, dueDate: Date): Flowable { + override suspend fun retrieveTasks(userId: String, tasksOrder: TasksOrder, dueDate: Date): TaskList? { val formatter = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZZZZZ", Locale.US) - return this.apiClient.getTasks("dailys", formatter.format(dueDate)) - .doOnNext { res -> this.localRepository.saveTasks(userId, tasksOrder, res) } + val taskList = this.apiClient.getTasks("dailys", formatter.format(dueDate)) ?: return null + this.localRepository.saveTasks(userId, tasksOrder, taskList) + return taskList } @Suppress("ReturnCount") @@ -120,7 +115,7 @@ class TaskRepositoryImpl( return result } - override fun bulkScoreTasks(data: List>): Flowable { + override suspend fun bulkScoreTasks(data: List>): BulkTaskScoringData? { return apiClient.bulkScoreTasks(data) } @@ -212,10 +207,10 @@ class TaskRepositoryImpl( override fun getTaskCopy(taskId: String) = localRepository.getTaskCopy(taskId) - override fun createTask(task: Task, force: Boolean): Flowable { + override suspend fun createTask(task: Task, force: Boolean): Task? { val now = Date().time if (lastTaskAction > now - 500 && !force) { - return Flowable.empty() + return null } lastTaskAction = now @@ -228,61 +223,56 @@ class TaskRepositoryImpl( } localRepository.saveSyncronous(task) - return apiClient.createTask(task) - .map { task1 -> - task1.dateCreated = Date() - task1 - } - .doOnNext { - it.tags = task.tags - localRepository.save(it) - } - .doOnError { - task.hasErrored = true - task.isSaving = false - localRepository.saveSyncronous(task) - } + val savedTask = apiClient.createTask(task) + savedTask?.dateCreated = Date() + if (savedTask != null) { + savedTask.tags = task.tags + localRepository.save(savedTask) + } else { + task.hasErrored = true + task.isSaving = false + localRepository.saveSyncronous(task) + } + return savedTask } @Suppress("ReturnCount") - override fun updateTask(task: Task, force: Boolean): Maybe { + override suspend fun updateTask(task: Task, force: Boolean): Task? { val now = Date().time if ((lastTaskAction > now - 500 && !force) || !task.isValid) { - return Maybe.just(task) + return task } lastTaskAction = now - val id = task.id ?: return Maybe.just(task) + val id = task.id ?: return task val unmanagedTask = localRepository.getUnmanagedCopy(task) unmanagedTask.isSaving = true unmanagedTask.hasErrored = false localRepository.saveSyncronous(unmanagedTask) - return apiClient.updateTask(id, unmanagedTask).singleElement() - .map { task1 -> - task1.position = task.position - task1.id = task.id - task1 - } - .doOnSuccess { - it.tags = task.tags - localRepository.save(it) - } - .doOnError { - unmanagedTask.hasErrored = true - unmanagedTask.isSaving = false - localRepository.saveSyncronous(unmanagedTask) - } + val savedTask = apiClient.updateTask(id, unmanagedTask) + savedTask?.position = task.position + savedTask?.id = task.id + if (savedTask != null) { + savedTask.tags = task.tags + localRepository.save(savedTask) + } else { + unmanagedTask.hasErrored = true + unmanagedTask.isSaving = false + localRepository.saveSyncronous(unmanagedTask) + } + return savedTask } - override fun deleteTask(taskId: String): Flowable { - return apiClient.deleteTask(taskId) - .doOnNext { localRepository.deleteTask(taskId) } + override suspend fun deleteTask(taskId: String): Void? { + apiClient.deleteTask(taskId) ?: return null + localRepository.deleteTask(taskId) + return null } override fun saveTask(task: Task) { localRepository.save(task) } - override fun createTasks(newTasks: List): Flowable> = apiClient.createTasks(newTasks) + override suspend fun createTasks(newTasks: List) = apiClient.createTasks(newTasks) override fun markTaskCompleted(taskId: String, isCompleted: Boolean) { localRepository.markTaskCompleted(taskId, isCompleted) @@ -296,19 +286,24 @@ class TaskRepositoryImpl( localRepository.swapTaskPosition(firstPosition, secondPosition) } - override fun updateTaskPosition(taskType: TaskType, taskID: String, newPosition: Int): Maybe> { - return apiClient.postTaskNewPosition(taskID, newPosition).firstElement() - .doOnSuccess { localRepository.updateTaskPositions(it) } + override suspend fun updateTaskPosition(taskType: TaskType, taskID: String, newPosition: Int): List? { + val positions = apiClient.postTaskNewPosition(taskID, newPosition) ?: return null + localRepository.updateTaskPositions(positions) + return positions } override fun getUnmanagedTask(taskid: String) = getTask(taskid).map { localRepository.getUnmanagedCopy(it) } override fun updateTaskInBackground(task: Task) { - updateTask(task).subscribe({ }, ExceptionHandler.rx()) + MainScope().launchCatching { + updateTask(task) + } } override fun createTaskInBackground(task: Task) { - createTask(task).subscribe({ }, ExceptionHandler.rx()) + MainScope().launchCatching { + createTask(task) + } } override fun getTaskCopies(userId: String): Flow> = @@ -316,29 +311,27 @@ class TaskRepositoryImpl( override fun getTaskCopies(tasks: List): List = localRepository.getUnmanagedCopy(tasks) - override fun retrieveDailiesFromDate(date: Date): Flowable { + override suspend fun retrieveDailiesFromDate(date: Date): TaskList? { val formatter = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZZZZZ", Locale.US) return apiClient.getTasks("dailys", formatter.format(date)) } - override fun syncErroredTasks(): Single> { - return localRepository.getErroredTasks(userID).firstElement() - .flatMapPublisher { Flowable.fromIterable(it) } - .map { localRepository.getUnmanagedCopy(it) } - .flatMap { - return@flatMap if (it.isCreating) { - createTask(it, true) - } else { - updateTask(it, true).toFlowable() - } - }.toList() + override suspend fun syncErroredTasks(): List? { + val tasks = localRepository.getErroredTasks(userID).firstOrNull() + return tasks?.map { localRepository.getUnmanagedCopy(it) }?.mapNotNull { + if (it.isCreating) { + createTask(it, true) + } else { + updateTask(it, true) + } + } } - override fun unlinkAllTasks(challengeID: String?, keepOption: String): Flowable { + override suspend fun unlinkAllTasks(challengeID: String?, keepOption: String): Void? { return apiClient.unlinkAllTasks(challengeID, keepOption) } - override fun getTasksForChallenge(challengeID: String?): Flowable> { + override fun getTasksForChallenge(challengeID: String?): Flow> { return localRepository.getTasksForChallenge(challengeID, userID) } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/TutorialRepositoryImpl.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/TutorialRepositoryImpl.kt index faa1a0455..b6bd7e63f 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/TutorialRepositoryImpl.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/TutorialRepositoryImpl.kt @@ -4,7 +4,7 @@ import com.habitrpg.android.habitica.data.ApiClient import com.habitrpg.android.habitica.data.TutorialRepository import com.habitrpg.android.habitica.data.local.TutorialLocalRepository import com.habitrpg.android.habitica.models.TutorialStep -import io.reactivex.rxjava3.core.Flowable +import kotlinx.coroutines.flow.Flow class TutorialRepositoryImpl( localRepository: TutorialLocalRepository, @@ -12,9 +12,9 @@ class TutorialRepositoryImpl( userID: String ) : BaseRepositoryImpl(localRepository, apiClient, userID), TutorialRepository { - override fun getTutorialStep(key: String): Flowable = + override fun getTutorialStep(key: String): Flow = localRepository.getTutorialStep(key) - override fun getTutorialSteps(keys: List): Flowable> = + override fun getTutorialSteps(keys: List): Flow> = localRepository.getTutorialSteps(keys) } 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 44a081976..dbc733a99 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 @@ -4,12 +4,9 @@ import com.habitrpg.android.habitica.data.ApiClient import com.habitrpg.android.habitica.data.TaskRepository import com.habitrpg.android.habitica.data.UserRepository import com.habitrpg.android.habitica.data.local.UserLocalRepository -import com.habitrpg.android.habitica.extensions.filterMapEmpty import com.habitrpg.android.habitica.helpers.AppConfigManager -import com.habitrpg.android.habitica.helpers.ExceptionHandler import com.habitrpg.android.habitica.models.Achievement import com.habitrpg.android.habitica.models.QuestAchievement -import com.habitrpg.android.habitica.models.Skill import com.habitrpg.android.habitica.models.TeamPlan import com.habitrpg.android.habitica.models.inventory.Customization import com.habitrpg.android.habitica.models.responses.SkillResponse @@ -21,17 +18,10 @@ 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.android.habitica.proxy.AnalyticsManager -import com.habitrpg.common.habitica.extensions.Optional import com.habitrpg.shared.habitica.models.responses.TaskDirection -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 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 import java.util.concurrent.TimeUnit @@ -48,8 +38,6 @@ class UserRepositoryImpl( private var lastSync: Date? = null override fun getUser(): Flow = getUser(userID) - override fun getUserFlowable(): Flowable = localRepository.getUserFlowable(userID) - override fun getUser(userID: String): Flow = localRepository.getUser(userID) private suspend fun updateUser(userID: String, updateData: Map): User? { @@ -123,11 +111,9 @@ class UserRepositoryImpl( return user } - override fun getSkills(user: User): Flowable> = - localRepository.getSkills(user) + override fun getSkills(user: User) = localRepository.getSkills(user) - override fun getSpecialItems(user: User): Flowable> = - localRepository.getSpecialItems(user) + override fun getSpecialItems(user: User) = localRepository.getSpecialItems(user) override suspend fun useSkill(key: String, target: String?, taskId: String): SkillResponse? { val response = apiClient.useSkill(key, target ?: "", taskId) ?: return null @@ -177,8 +163,8 @@ class UserRepositoryImpl( runCron(ArrayList()) } - override fun readNotification(id: String): Flowable> = apiClient.readNotification(id) - override fun getUserQuestStatus(): Flowable { + override suspend fun readNotification(id: String) = apiClient.readNotification(id) + override fun getUserQuestStatus(): Flow { return localRepository.getUserQuestStatus(userID) } @@ -186,13 +172,11 @@ class UserRepositoryImpl( return apiClient.reroll() } - override fun readNotifications(notificationIds: Map>): Flowable> = - apiClient.readNotifications(notificationIds) + override suspend fun readNotifications(notificationIds: Map>) = apiClient.readNotifications(notificationIds) - override fun seeNotifications(notificationIds: Map>): Flowable> = - apiClient.seeNotifications(notificationIds) + override suspend fun seeNotifications(notificationIds: Map>) = apiClient.seeNotifications(notificationIds) - override fun changeCustomDayStart(dayStartTime: Int): Flowable { + override suspend fun changeCustomDayStart(dayStartTime: Int): User? { val updateObject = HashMap() updateObject["dayStart"] = dayStartTime return apiClient.changeCustomDayStart(updateObject) @@ -200,7 +184,7 @@ class UserRepositoryImpl( override suspend fun updateLanguage(languageCode: String): User? { val user = updateUser("preferences.language", languageCode) - apiClient.setLanguageCode(languageCode) + apiClient.languageCode = languageCode return user } @@ -209,11 +193,9 @@ class UserRepositoryImpl( return retrieveUser(withTasks = true, forced = true) } - override fun deleteAccount(password: String): Flowable = - apiClient.deleteAccount(password) + override suspend fun deleteAccount(password: String) = apiClient.deleteAccount(password) - override fun sendPasswordResetEmail(email: String): Flowable = - apiClient.sendPasswordResetEmail(email) + override suspend fun sendPasswordResetEmail(email: String) = apiClient.sendPasswordResetEmail(email) override suspend fun updateLoginName(newLoginName: String, password: String?): User? { if (password != null && password.isNotEmpty()) { @@ -229,35 +211,32 @@ class UserRepositoryImpl( return user } - override fun verifyUsername(username: String): Flowable = apiClient.verifyUsername(username.trim()) + override suspend fun verifyUsername(username: String) = apiClient.verifyUsername(username.trim()) - override fun updateEmail(newEmail: String, password: String): Flowable = - apiClient.updateEmail(newEmail.trim(), password) + override suspend fun updateEmail(newEmail: String, password: String) = apiClient.updateEmail(newEmail.trim(), password) - override fun updatePassword( + override suspend fun updatePassword( oldPassword: String, newPassword: String, newPasswordConfirmation: String - ): Flowable = - apiClient.updatePassword(oldPassword.trim(), newPassword.trim(), newPasswordConfirmation.trim()) + ) = apiClient.updatePassword(oldPassword.trim(), newPassword.trim(), newPasswordConfirmation.trim()) - override fun allocatePoint(stat: Attribute): Flowable { - getLiveUserFlowable().firstElement().subscribe( - { liveUser -> - localRepository.executeTransaction { - when (stat) { - Attribute.STRENGTH -> liveUser.stats?.strength = liveUser.stats?.strength?.inc() - Attribute.INTELLIGENCE -> liveUser.stats?.intelligence = liveUser.stats?.intelligence?.inc() - Attribute.CONSTITUTION -> liveUser.stats?.constitution = liveUser.stats?.constitution?.inc() - Attribute.PERCEPTION -> liveUser.stats?.per = liveUser.stats?.per?.inc() - } - liveUser.stats?.points = liveUser.stats?.points?.dec() + override suspend fun allocatePoint(stat: Attribute): Stats? { + val liveUser = getLiveUser() + if (liveUser != null) { + localRepository.executeTransaction { + when (stat) { + Attribute.STRENGTH -> liveUser.stats?.strength = liveUser.stats?.strength?.inc() + Attribute.INTELLIGENCE -> liveUser.stats?.intelligence = liveUser.stats?.intelligence?.inc() + Attribute.CONSTITUTION -> liveUser.stats?.constitution = liveUser.stats?.constitution?.inc() + Attribute.PERCEPTION -> liveUser.stats?.per = liveUser.stats?.per?.inc() } - }, - ExceptionHandler.rx() - ) - return zipWithLiveUser(apiClient.allocatePoint(stat.value)) { stats, user -> - localRepository.modify(user) { liveUser -> + liveUser.stats?.points = liveUser.stats?.points?.dec() + } + } + val stats = apiClient.allocatePoint(stat.value) ?: return null + if (liveUser != null) { + localRepository.executeTransaction { liveUser.stats?.strength = stats.strength liveUser.stats?.constitution = stats.constitution liveUser.stats?.per = stats.per @@ -265,17 +244,24 @@ class UserRepositoryImpl( liveUser.stats?.points = stats.points liveUser.stats?.mp = stats.mp } - stats } + return stats } - override fun bulkAllocatePoints( + override suspend fun bulkAllocatePoints( strength: Int, intelligence: Int, constitution: Int, perception: Int - ): Flowable = - zipWithLiveUser(apiClient.bulkAllocatePoints(strength, intelligence, constitution, perception)) { stats, user -> + ): Stats? { + val stats = apiClient.bulkAllocatePoints( + strength, + intelligence, + constitution, + perception + ) ?: return null + val user = getLiveUser() + if (user != null) { localRepository.modify(user) { liveUser -> liveUser.stats?.strength = stats.strength liveUser.stats?.constitution = stats.constitution @@ -284,63 +270,54 @@ class UserRepositoryImpl( liveUser.stats?.points = stats.points liveUser.stats?.mp = stats.mp } - stats } + return stats + } override suspend fun runCron(tasks: MutableList) { - withContext(Dispatchers.Main) { - var observable: Maybe = localRepository.getUserFlowable(userID).firstElement() - .filter { it.needsCron } - .map { user -> - localRepository.modify(user) { liveUser -> - liveUser.needsCron = false - liveUser.lastCron = Date() - } - user - } - if (tasks.isNotEmpty()) { - val scoringList = mutableListOf>() - for (task in tasks) { - val map = mutableMapOf() - map["id"] = task.id ?: "" - map["direction"] = TaskDirection.UP.text - scoringList.add(map) - } - observable = observable.flatMap { taskRepository.bulkScoreTasks(scoringList).firstElement() } + val user = getLiveUser() + if (user != null) { + localRepository.modify(user) { liveUser -> + liveUser.needsCron = false + liveUser.lastCron = Date() } - observable.flatMap { apiClient.runCron().firstElement() } - // .flatMap { - // this.retrieveUser(withTasks = true, forced = true) - // } - .subscribe({ }, ExceptionHandler.rx()) } + if (tasks.isNotEmpty()) { + val scoringList = mutableListOf>() + for (task in tasks) { + val map = mutableMapOf() + map["id"] = task.id ?: "" + map["direction"] = TaskDirection.UP.text + scoringList.add(map) + } + taskRepository.bulkScoreTasks(scoringList) + } + apiClient.runCron() } override suspend fun useCustomization(type: String, category: String?, identifier: String): User? { if (appConfigManager.enableLocalChanges()) { - localRepository.getUserFlowable(userID).firstElement().subscribe( - { liveUser -> - localRepository.modify(liveUser) { user -> - when (type) { - "skin" -> user.preferences?.skin = identifier - "shirt" -> user.preferences?.shirt = identifier - "hair" -> { - when (category) { - "color" -> user.preferences?.hair?.color = identifier - "flower" -> user.preferences?.hair?.flower = identifier.toInt() - "mustache" -> user.preferences?.hair?.mustache = identifier.toInt() - "beard" -> user.preferences?.hair?.beard = identifier.toInt() - "bangs" -> user.preferences?.hair?.bangs = identifier.toInt() - "base" -> user.preferences?.hair?.base = identifier.toInt() - } + val liveUser = getLiveUser() + if (liveUser != null) { + localRepository.modify(liveUser) { user -> + when (type) { + "skin" -> user.preferences?.skin = identifier + "shirt" -> user.preferences?.shirt = identifier + "hair" -> { + when (category) { + "color" -> user.preferences?.hair?.color = identifier + "flower" -> user.preferences?.hair?.flower = identifier.toInt() + "mustache" -> user.preferences?.hair?.mustache = identifier.toInt() + "beard" -> user.preferences?.hair?.beard = identifier.toInt() + "bangs" -> user.preferences?.hair?.bangs = identifier.toInt() + "base" -> user.preferences?.hair?.base = identifier.toInt() } - "background" -> user.preferences?.background = identifier - "chair" -> user.preferences?.chair = identifier } + "background" -> user.preferences?.background = identifier + "chair" -> user.preferences?.chair = identifier } - }, - ExceptionHandler.rx() - ) + } + } } var updatePath = "preferences.$type" if (category != null) { @@ -349,10 +326,10 @@ class UserRepositoryImpl( return updateUser(updatePath, identifier) } - override fun retrieveAchievements(): Flowable> { - return apiClient.getMemberAchievements(userID).doOnNext { - localRepository.save(it) - } + override suspend fun retrieveAchievements(): List? { + val achievements = apiClient.getMemberAchievements(userID) ?: return null + localRepository.save(achievements) + return achievements } override fun getAchievements(): Flow> { @@ -363,11 +340,11 @@ class UserRepositoryImpl( return localRepository.getQuestAchievements(userID) } - override fun retrieveTeamPlans(): Flowable> { - return apiClient.getTeamPlans().doOnNext { teams -> - teams.forEach { it.userID = userID } - localRepository.save(teams) - } + override suspend fun retrieveTeamPlans(): List? { + val teams = apiClient.getTeamPlans() ?: return null + teams.forEach { it.userID = userID } + localRepository.save(teams) + return teams } override fun getTeamPlans(): Flow> { @@ -389,25 +366,15 @@ class UserRepositoryImpl( return team } - override fun getTeamPlan(teamID: String): Flowable { + override fun getTeamPlan(teamID: String): Flow { return localRepository.getTeamPlan(teamID) } - 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, getLiveUserFlowable().firstElement().toFlowable(), mergeFunc) - } - private fun mergeUser(oldUser: User?, newUser: User): User { if (oldUser == null || !oldUser.isValid) { return oldUser ?: newUser diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/ChallengeLocalRepository.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/ChallengeLocalRepository.kt index a549cec18..c72ce4084 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/ChallengeLocalRepository.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/ChallengeLocalRepository.kt @@ -3,15 +3,15 @@ package com.habitrpg.android.habitica.data.local import com.habitrpg.android.habitica.models.social.Challenge import com.habitrpg.android.habitica.models.social.ChallengeMembership import com.habitrpg.android.habitica.models.tasks.Task -import io.reactivex.rxjava3.core.Flowable +import kotlinx.coroutines.flow.Flow interface ChallengeLocalRepository : BaseLocalRepository { - val challenges: Flowable> - fun getChallenge(id: String): Flowable - fun getTasks(challengeID: String): Flowable> + val challenges: Flow> + fun getChallenge(id: String): Flow + fun getTasks(challengeID: String): Flow> - fun getUserChallenges(userId: String): Flowable> + fun getUserChallenges(userId: String): Flow> fun setParticipating(userID: String, challengeID: String, isParticipating: Boolean) @@ -21,7 +21,7 @@ interface ChallengeLocalRepository : BaseLocalRepository { memberOnly: Boolean, userID: String ) - fun getChallengeMembership(userId: String, id: String): Flowable - fun getChallengeMemberships(userId: String): Flowable> - fun isChallengeMember(userID: String, challengeID: String): Flowable + fun getChallengeMembership(userId: String, id: String): Flow + fun getChallengeMemberships(userId: String): Flow> + fun isChallengeMember(userID: String, challengeID: String): Flow } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/ContentLocalRepository.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/ContentLocalRepository.kt index a3f601ab9..670a0dbce 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/ContentLocalRepository.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/ContentLocalRepository.kt @@ -3,9 +3,10 @@ package com.habitrpg.android.habitica.data.local import com.habitrpg.android.habitica.models.ContentResult import com.habitrpg.android.habitica.models.WorldState import io.reactivex.rxjava3.core.Flowable +import kotlinx.coroutines.flow.Flow interface ContentLocalRepository : BaseLocalRepository { fun saveContent(contentResult: ContentResult) fun saveWorldState(worldState: WorldState) - fun getWorldState(): Flowable + fun getWorldState(): Flow } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/CustomizationLocalRepository.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/CustomizationLocalRepository.kt index 2b21bae96..122aa7399 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/CustomizationLocalRepository.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/CustomizationLocalRepository.kt @@ -1,8 +1,8 @@ package com.habitrpg.android.habitica.data.local import com.habitrpg.android.habitica.models.inventory.Customization -import io.reactivex.rxjava3.core.Flowable +import kotlinx.coroutines.flow.Flow interface CustomizationLocalRepository : ContentLocalRepository { - fun getCustomizations(type: String, category: String?, onlyAvailable: Boolean): Flowable> + fun getCustomizations(type: String, category: String?, onlyAvailable: Boolean): Flow> } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/FAQLocalRepository.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/FAQLocalRepository.kt index 43bb04245..f7c2b04fb 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/FAQLocalRepository.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/FAQLocalRepository.kt @@ -1,10 +1,10 @@ package com.habitrpg.android.habitica.data.local import com.habitrpg.android.habitica.models.FAQArticle -import io.reactivex.rxjava3.core.Flowable +import kotlinx.coroutines.flow.Flow interface FAQLocalRepository : ContentLocalRepository { - fun getArticle(position: Int): Flowable + fun getArticle(position: Int): Flow - val articles: Flowable> + val articles: Flow> } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/InventoryLocalRepository.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/InventoryLocalRepository.kt index 895c57fe3..77cecd4d6 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/InventoryLocalRepository.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/InventoryLocalRepository.kt @@ -11,13 +11,12 @@ import com.habitrpg.android.habitica.models.user.OwnedItem import com.habitrpg.android.habitica.models.user.OwnedMount import com.habitrpg.android.habitica.models.user.OwnedPet import com.habitrpg.android.habitica.models.user.User -import io.reactivex.rxjava3.core.Flowable import kotlinx.coroutines.flow.Flow interface InventoryLocalRepository : ContentLocalRepository { fun getArmoireRemainingCount(): Long - fun getOwnedEquipment(): Flowable> + fun getOwnedEquipment(): Flow> fun getMounts(): Flow> @@ -27,31 +26,29 @@ interface InventoryLocalRepository : ContentLocalRepository { fun getOwnedPets(userID: String): Flow> - fun getInAppRewards(): Flowable> + fun getInAppRewards(): Flow> fun getQuestContent(key: String): Flow fun getQuestContent(keys: List): Flow> - fun getEquipment(searchedKeys: List): Flowable> + fun getEquipment(searchedKeys: List): Flow> - fun getOwnedEquipment(type: String): Flowable> + fun getOwnedEquipment(type: String): Flow> - fun getItemsFlowable(itemClass: Class, keys: Array): Flow> - fun getItemsFlowable(itemClass: Class): Flowable> fun getOwnedItems(itemType: String, userID: String, includeZero: Boolean): Flow> - fun getOwnedItems(userID: String, includeZero: Boolean): Flowable> - fun getEquipmentType(type: String, set: String): Flowable> + fun getOwnedItems(userID: String, includeZero: Boolean): Flow> + fun getEquipmentType(type: String, set: String): Flow> - fun getEquipment(key: String): Flowable + fun getEquipment(key: String): Flow fun getMounts(type: String?, group: String?, color: String?): Flow> fun getPets(type: String?, group: String?, color: String?): Flow> fun updateOwnedEquipment(user: User) - fun changeOwnedCount(type: String, key: String, userID: String, amountToAdd: Int) + suspend fun changeOwnedCount(type: String, key: String, userID: String, amountToAdd: Int) fun changeOwnedCount(item: OwnedItem, amountToAdd: Int?) - fun getItem(type: String, key: String): Flowable - fun getOwnedItem(userID: String, type: String, key: String, includeZero: Boolean): Flowable + fun getItem(type: String, key: String): Flow + fun getOwnedItem(userID: String, type: String, key: String, includeZero: Boolean): Flow fun decrementMysteryItemCount(user: User?) fun saveInAppRewards(onlineItems: List) @@ -59,12 +56,14 @@ interface InventoryLocalRepository : ContentLocalRepository { fun hatchPet(eggKey: String, potionKey: String, userID: String) fun unhatchPet(eggKey: String, potionKey: String, userID: String) fun feedPet(foodKey: String, petKey: String, feedValue: Int, userID: String) - fun getLatestMysteryItem(): Flowable + fun getLatestMysteryItem(): Flow fun soldItem(userID: String, updatedUser: User): User - fun getAvailableLimitedItems(): Flowable> + fun getAvailableLimitedItems(): Flow> fun save(items: Items, userID: String) fun getLiveObject(obj: OwnedItem): OwnedItem? fun getItems(itemClass: Class): Flow> + fun getItems(itemClass: Class, keys: Array): Flow> + } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/SocialLocalRepository.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/SocialLocalRepository.kt index b1f8dbdd5..c40981364 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/SocialLocalRepository.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/SocialLocalRepository.kt @@ -6,20 +6,19 @@ import com.habitrpg.android.habitica.models.social.Group import com.habitrpg.android.habitica.models.social.GroupMembership import com.habitrpg.android.habitica.models.social.InboxConversation import com.habitrpg.android.habitica.models.user.User -import io.reactivex.rxjava3.core.Flowable import io.realm.RealmResults import kotlinx.coroutines.flow.Flow interface SocialLocalRepository : BaseLocalRepository { - fun getPublicGuilds(): Flowable> + fun getPublicGuilds(): Flow> fun getUserGroups(userID: String, type: String?): Flow> - fun getGroups(type: String): Flowable> + fun getGroups(type: String): Flow> fun getGroup(id: String): Flow fun saveGroup(group: Group) - fun getGroupChat(groupId: String): Flowable> + fun getGroupChat(groupId: String): Flow> fun deleteMessage(id: String) @@ -41,7 +40,7 @@ interface SocialLocalRepository : BaseLocalRepository { fun doesGroupExist(id: String): Boolean fun updateMembership(userId: String, id: String, isMember: Boolean) fun getGroupMembership(userId: String, id: String): Flow - fun getGroupMemberships(userId: String): Flowable> + fun getGroupMemberships(userId: String): Flow> fun rejectGroupInvitation(userID: String, groupID: String) fun getInboxMessages(userId: String, replyToUserID: String?): Flow> diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/TagLocalRepository.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/TagLocalRepository.kt index fff81f926..b4a2cf921 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/TagLocalRepository.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/TagLocalRepository.kt @@ -2,9 +2,10 @@ package com.habitrpg.android.habitica.data.local import com.habitrpg.android.habitica.models.Tag import io.reactivex.rxjava3.core.Flowable +import kotlinx.coroutines.flow.Flow interface TagLocalRepository : BaseLocalRepository { - fun getTags(userId: String): Flowable> + fun getTags(userId: String): Flow> fun deleteTag(tagID: String) } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/TaskLocalRepository.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/TaskLocalRepository.kt index 6f8b80a60..e8a3a0f24 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/TaskLocalRepository.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/TaskLocalRepository.kt @@ -5,14 +5,12 @@ import com.habitrpg.android.habitica.models.tasks.TaskList import com.habitrpg.android.habitica.models.user.User import com.habitrpg.shared.habitica.models.tasks.TaskType import com.habitrpg.shared.habitica.models.tasks.TasksOrder -import io.reactivex.rxjava3.core.Flowable import io.reactivex.rxjava3.core.Maybe import kotlinx.coroutines.flow.Flow interface TaskLocalRepository : BaseLocalRepository { fun getTasks(taskType: TaskType, userID: String, includedGroupIDs: Array): Flow> - fun getTasksFlowable(taskType: TaskType, userID: String, includedGroupIDs: Array): Flowable> fun getTasks(userId: String): Flow> fun saveTasks(ownerID: String, tasksOrder: TasksOrder, tasks: TaskList) @@ -26,14 +24,13 @@ interface TaskLocalRepository : BaseLocalRepository { fun swapTaskPosition(firstPosition: Int, secondPosition: Int) - fun getTaskAtPosition(taskType: String, position: Int): Flowable + fun getTaskAtPosition(taskType: String, position: Int): Flow fun updateIsdue(daily: TaskList): Maybe fun updateTaskPositions(taskOrder: List) fun saveCompletedTodos(userId: String, tasks: MutableCollection) - fun getErroredTasks(userID: String): Flowable> - fun getUserFlowable(userID: String): Flowable + fun getErroredTasks(userID: String): Flow> fun getUser(userID: String): Flow - fun getTasksForChallenge(challengeID: String?, userID: String?): Flowable> + fun getTasksForChallenge(challengeID: String?, userID: String?): Flow> } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/TutorialLocalRepository.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/TutorialLocalRepository.kt index 455dc5d75..951986edc 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/TutorialLocalRepository.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/TutorialLocalRepository.kt @@ -1,10 +1,10 @@ package com.habitrpg.android.habitica.data.local import com.habitrpg.android.habitica.models.TutorialStep -import io.reactivex.rxjava3.core.Flowable +import kotlinx.coroutines.flow.Flow interface TutorialLocalRepository : BaseLocalRepository { - fun getTutorialStep(key: String): Flowable - fun getTutorialSteps(keys: List): Flowable> + fun getTutorialStep(key: String): Flow + fun getTutorialSteps(keys: List): Flow> } 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 04dbb8e90..49ef8bf35 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 @@ -9,7 +9,6 @@ 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 io.reactivex.rxjava3.core.Flowable import io.realm.RealmResults import kotlinx.coroutines.flow.Flow @@ -18,18 +17,16 @@ interface UserLocalRepository : BaseLocalRepository { suspend fun getTutorialSteps(): Flow> fun getUser(userID: String): Flow - fun getUserFlowable(userID: String): Flowable - fun saveUser(user: User, overrideExisting: Boolean = true) fun saveMessages(messages: List) - fun getSkills(user: User): Flowable> + fun getSkills(user: User): Flow> - fun getSpecialItems(user: User): Flowable> + fun getSpecialItems(user: User): Flow> fun getAchievements(): Flow> fun getQuestAchievements(userID: String): Flow> - fun getUserQuestStatus(userID: String): Flowable + fun getUserQuestStatus(userID: String): Flow fun getTeamPlans(userID: String): Flow> - fun getTeamPlan(teamID: String): Flowable + fun getTeamPlan(teamID: String): Flow } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/implementation/RealmChallengeLocalRepository.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/implementation/RealmChallengeLocalRepository.kt index 9dfb48d1b..393ab5d4b 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/implementation/RealmChallengeLocalRepository.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/implementation/RealmChallengeLocalRepository.kt @@ -1,84 +1,79 @@ package com.habitrpg.android.habitica.data.local.implementation import com.habitrpg.android.habitica.data.local.ChallengeLocalRepository -import com.habitrpg.android.habitica.extensions.filterMap import com.habitrpg.android.habitica.models.social.Challenge import com.habitrpg.android.habitica.models.social.ChallengeMembership import com.habitrpg.android.habitica.models.tasks.Task import com.habitrpg.android.habitica.models.user.User -import hu.akarnokd.rxjava3.bridge.RxJavaBridge -import io.reactivex.rxjava3.core.Flowable import io.realm.Realm import io.realm.Sort +import io.realm.kotlin.toFlow +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.filterNotNull +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.map class RealmChallengeLocalRepository(realm: Realm) : RealmBaseLocalRepository(realm), ChallengeLocalRepository { - override fun isChallengeMember(userID: String, challengeID: String): Flowable = RxJavaBridge.toV3Flowable( - realm.where(ChallengeMembership::class.java) + override fun isChallengeMember(userID: String, challengeID: String): Flow = realm.where(ChallengeMembership::class.java) .equalTo("userID", userID) .equalTo("challengeID", challengeID) .findAll() - .asFlowable() + .toFlow() .filter { it.isLoaded } - ).map { it.count() > 0 } + .map { it.count() > 0 } - override fun getChallengeMembership(userId: String, id: String): Flowable = RxJavaBridge.toV3Flowable( - realm.where(ChallengeMembership::class.java) + override fun getChallengeMembership(userId: String, id: String) = realm.where(ChallengeMembership::class.java) .equalTo("userID", userId) .equalTo("challengeID", id) .findAll() - .asFlowable() + .toFlow() .filter { it.isLoaded } - ).filterMap { it.first() } + .map { it.first() } + .filterNotNull() - override fun getChallengeMemberships(userId: String): Flowable> = RxJavaBridge.toV3Flowable( - realm.where(ChallengeMembership::class.java) + override fun getChallengeMemberships(userId: String) = realm.where(ChallengeMembership::class.java) .equalTo("userID", userId) .findAll() - .asFlowable() + .toFlow() .filter { it.isLoaded } - ) - override fun getChallenge(id: String): Flowable { - return RxJavaBridge.toV3Flowable( - realm.where(Challenge::class.java) + override fun getChallenge(id: String): Flow { + return realm.where(Challenge::class.java) .equalTo("id", id) .findAll() - .asFlowable() + .toFlow() .filter { realmObject -> realmObject.isLoaded && realmObject.isNotEmpty() } .map { it.first() } - ) + .filterNotNull() } - override fun getTasks(challengeID: String): Flowable> { - return RxJavaBridge.toV3Flowable( - realm.where(Task::class.java) + override fun getTasks(challengeID: String): Flow> { + return realm.where(Task::class.java) .equalTo("userId", challengeID) .findAll() - .asFlowable() + .toFlow() .filter { realmObject -> realmObject.isLoaded } - ) } - override val challenges: Flowable> - get() = RxJavaBridge.toV3Flowable( - realm.where(Challenge::class.java) + override val challenges: Flow> + get() = realm.where(Challenge::class.java) .isNotNull("name") .sort("official", Sort.DESCENDING, "createdAt", Sort.DESCENDING) .findAll() - .asFlowable() + .toFlow() .filter { it.isLoaded } - ) - override fun getUserChallenges(userId: String): Flowable> { - return RxJavaBridge.toV3Flowable( - realm.where(ChallengeMembership::class.java) + @OptIn(ExperimentalCoroutinesApi::class) + override fun getUserChallenges(userId: String): Flow> { + return realm.where(ChallengeMembership::class.java) .equalTo("userID", userId) .findAll() - .asFlowable() + .toFlow() .filter { it.isLoaded } - ) - .flatMap { it -> + .flatMapLatest { it -> val ids = it.map { return@map it.challengeID }.toTypedArray() @@ -91,7 +86,7 @@ class RealmChallengeLocalRepository(realm: Realm) : RealmBaseLocalRepository(rea .endGroup() .sort("official", Sort.DESCENDING, "createdAt", Sort.DESCENDING) .findAll() - .asFlowable() + .toFlow() .filter { it.isLoaded } } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/implementation/RealmContentLocalRepository.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/implementation/RealmContentLocalRepository.kt index 9445ba11f..eddc37ca9 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/implementation/RealmContentLocalRepository.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/implementation/RealmContentLocalRepository.kt @@ -5,9 +5,12 @@ import com.habitrpg.android.habitica.models.ContentResult import com.habitrpg.android.habitica.models.WorldState import com.habitrpg.android.habitica.models.inventory.Quest import com.habitrpg.android.habitica.models.social.Group -import hu.akarnokd.rxjava3.bridge.RxJavaBridge -import io.reactivex.rxjava3.core.Flowable import io.realm.Realm +import io.realm.kotlin.toFlow +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.filterNotNull +import kotlinx.coroutines.flow.map open class RealmContentLocalRepository(realm: Realm) : RealmBaseLocalRepository(realm), ContentLocalRepository { @@ -34,14 +37,13 @@ open class RealmContentLocalRepository(realm: Realm) : RealmBaseLocalRepository( } } - override fun getWorldState(): Flowable { - return RxJavaBridge.toV3Flowable( - realm.where(WorldState::class.java) + override fun getWorldState(): Flow { + return realm.where(WorldState::class.java) .findAll() - .asFlowable() + .toFlow() .filter { it.isLoaded && it.size > 0 } .map { it.first() } - ) + .filterNotNull() } override fun saveWorldState(worldState: WorldState) { diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/implementation/RealmCustomizationLocalRepository.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/implementation/RealmCustomizationLocalRepository.kt index c67d3d5a7..f7e564370 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/implementation/RealmCustomizationLocalRepository.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/implementation/RealmCustomizationLocalRepository.kt @@ -2,14 +2,15 @@ package com.habitrpg.android.habitica.data.local.implementation import com.habitrpg.android.habitica.data.local.CustomizationLocalRepository import com.habitrpg.android.habitica.models.inventory.Customization -import hu.akarnokd.rxjava3.bridge.RxJavaBridge -import io.reactivex.rxjava3.core.Flowable import io.realm.Realm +import io.realm.kotlin.toFlow +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.filter import java.util.Date class RealmCustomizationLocalRepository(realm: Realm) : RealmContentLocalRepository(realm), CustomizationLocalRepository { - override fun getCustomizations(type: String, category: String?, onlyAvailable: Boolean): Flowable> { + override fun getCustomizations(type: String, category: String?, onlyAvailable: Boolean): Flow> { var query = realm.where(Customization::class.java) .equalTo("type", type) .equalTo("category", category) @@ -28,13 +29,10 @@ class RealmCustomizationLocalRepository(realm: Realm) : RealmContentLocalReposit .endGroup() .endGroup() } - return RxJavaBridge.toV3Flowable( - query + return query .sort("customizationSet") .findAll() - .asFlowable() + .toFlow() .filter { it.isLoaded } - .map { it } - ) } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/implementation/RealmFAQLocalRepository.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/implementation/RealmFAQLocalRepository.kt index 5a704138f..84be05ed2 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/implementation/RealmFAQLocalRepository.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/implementation/RealmFAQLocalRepository.kt @@ -2,27 +2,27 @@ package com.habitrpg.android.habitica.data.local.implementation import com.habitrpg.android.habitica.data.local.FAQLocalRepository import com.habitrpg.android.habitica.models.FAQArticle -import hu.akarnokd.rxjava3.bridge.RxJavaBridge -import io.reactivex.rxjava3.core.Flowable import io.realm.Realm +import io.realm.kotlin.toFlow +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.filterNotNull +import kotlinx.coroutines.flow.map class RealmFAQLocalRepository(realm: Realm) : RealmContentLocalRepository(realm), FAQLocalRepository { - override fun getArticle(position: Int): Flowable { - return RxJavaBridge.toV3Flowable( - realm.where(FAQArticle::class.java) + override fun getArticle(position: Int): Flow { + return realm.where(FAQArticle::class.java) .equalTo("position", position) .findAll() - .asFlowable() + .toFlow() .filter { it.isLoaded && it.count() > 0 } - .map { it.first() } - ) + .map { it.firstOrNull() } + .filterNotNull() } - override val articles: Flowable> - get() = RxJavaBridge.toV3Flowable( - realm.where(FAQArticle::class.java) + override val articles: Flow> + get() = realm.where(FAQArticle::class.java) .findAll() - .asFlowable() + .toFlow() .filter { it.isLoaded } - ) } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/implementation/RealmInventoryLocalRepository.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/implementation/RealmInventoryLocalRepository.kt index 7d1159c6f..c3ebc3af8 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/implementation/RealmInventoryLocalRepository.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/implementation/RealmInventoryLocalRepository.kt @@ -1,7 +1,6 @@ package com.habitrpg.android.habitica.data.local.implementation import com.habitrpg.android.habitica.data.local.InventoryLocalRepository -import com.habitrpg.android.habitica.helpers.ExceptionHandler import com.habitrpg.android.habitica.models.inventory.Egg import com.habitrpg.android.habitica.models.inventory.Equipment import com.habitrpg.android.habitica.models.inventory.Food @@ -17,44 +16,44 @@ import com.habitrpg.android.habitica.models.user.OwnedItem import com.habitrpg.android.habitica.models.user.OwnedMount import com.habitrpg.android.habitica.models.user.OwnedPet import com.habitrpg.android.habitica.models.user.User -import hu.akarnokd.rxjava3.bridge.RxJavaBridge -import io.reactivex.rxjava3.core.Flowable import io.realm.Realm import io.realm.RealmObject import io.realm.Sort import io.realm.kotlin.toFlow import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.filterNotNull +import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.map import java.text.SimpleDateFormat import java.util.Date import java.util.Locale -class RealmInventoryLocalRepository(realm: Realm) : RealmContentLocalRepository(realm), InventoryLocalRepository { +class RealmInventoryLocalRepository(realm: Realm) : RealmContentLocalRepository(realm), + InventoryLocalRepository { override fun getQuestContent(keys: List): Flow> { return realm.where(QuestContent::class.java) - .`in`("key", keys.toTypedArray()) - .findAll() - .toFlow() - .filter { it.isLoaded } + .`in`("key", keys.toTypedArray()) + .findAll() + .toFlow() + .filter { it.isLoaded } } override fun getQuestContent(key: String): Flow { return realm.where(QuestContent::class.java).equalTo("key", key) - .findAll() - .toFlow() - .filter { content -> content.isLoaded && content.isValid && !content.isEmpty() } - .map { content -> content.first() } + .findAll() + .toFlow() + .filter { content -> content.isLoaded && content.isValid && !content.isEmpty() } + .map { content -> content.first() } } - override fun getEquipment(searchedKeys: List): Flowable> { - return RxJavaBridge.toV3Flowable( - realm.where(Equipment::class.java) - .`in`("key", searchedKeys.toTypedArray()) - .findAll() - .asFlowable() - .filter { it.isLoaded } - ) + override fun getEquipment(searchedKeys: List): Flow> { + return realm.where(Equipment::class.java) + .`in`("key", searchedKeys.toTypedArray()) + .findAll() + .toFlow() + .filter { it.isLoaded } } override fun getArmoireRemainingCount(): Long { @@ -68,39 +67,37 @@ class RealmInventoryLocalRepository(realm: Realm) : RealmContentLocalRepository( .count() } - override fun getOwnedEquipment(type: String): Flowable> { - return RxJavaBridge.toV3Flowable( - realm.where(Equipment::class.java) - .equalTo("type", type) - .equalTo("owned", true) - .findAll() - .asFlowable() - .filter { it.isLoaded } - ) + override fun getOwnedEquipment(type: String): Flow> { + return realm.where(Equipment::class.java) + .equalTo("type", type) + .equalTo("owned", true) + .findAll() + .toFlow() + .filter { it.isLoaded } } - override fun getOwnedEquipment(): Flowable> { - return RxJavaBridge.toV3Flowable( - realm.where(Equipment::class.java) - .equalTo("owned", true) - .findAll() - .asFlowable() - .filter { it.isLoaded } - ) + override fun getOwnedEquipment(): Flow> { + return realm.where(Equipment::class.java) + .equalTo("owned", true) + .findAll() + .toFlow() + .filter { it.isLoaded } } - override fun getEquipmentType(type: String, set: String): Flowable> { - return RxJavaBridge.toV3Flowable( - realm.where(Equipment::class.java) - .equalTo("type", type) - .equalTo("gearSet", set) - .findAll() - .asFlowable() - .filter { it.isLoaded } - ) + override fun getEquipmentType(type: String, set: String): Flow> { + return realm.where(Equipment::class.java) + .equalTo("type", type) + .equalTo("gearSet", set) + .findAll() + .toFlow() + .filter { it.isLoaded } } - override fun getOwnedItems(itemType: String, userID: String, includeZero: Boolean): Flow> { + override fun getOwnedItems( + itemType: String, + userID: String, + includeZero: Boolean + ): Flow> { return queryUser(userID).map { val items = when (itemType) { "eggs" -> it?.items?.eggs @@ -118,56 +115,49 @@ class RealmInventoryLocalRepository(realm: Realm) : RealmContentLocalRepository( } } - override fun getItemsFlowable(itemClass: Class, keys: Array): Flow> { - return realm.where(itemClass).`in`("key", keys).findAll().toFlow() - .filter { it.isLoaded } - } - - override fun getItemsFlowable(itemClass: Class): Flowable> { - return RxJavaBridge.toV3Flowable( - realm.where(itemClass).findAll().asFlowable() - .filter { it.isLoaded } - ) - } - override fun getItems(itemClass: Class): Flow> { return realm.where(itemClass).findAll().toFlow() .filter { it.isLoaded } } - override fun getOwnedItems(userID: String, includeZero: Boolean): Flowable> { - return queryUserFlowable(userID).map { - val items = HashMap() - it.items?.eggs?.forEach { items[it.key + "-" + it.itemType] = it } - it.items?.food?.forEach { items[it.key + "-" + it.itemType] = it } - it.items?.hatchingPotions?.forEach { items[it.key + "-" + it.itemType] = it } - it.items?.quests?.forEach { items[it.key + "-" + it.itemType] = it } - if (includeZero) { - items - } else { - items.filter { it.value.numberOwned > 0 } - } - } + override fun getItems(itemClass: Class, keys: Array): Flow> { + return realm.where(itemClass).`in`("key", keys).findAll().toFlow() + .filter { it.isLoaded } } - override fun getEquipment(key: String): Flowable { - return RxJavaBridge.toV3Flowable( - realm.where(Equipment::class.java) - .equalTo("key", key) - .findAll() - .asFlowable() - .filter { realmObject -> realmObject.isLoaded && realmObject.isNotEmpty() } - .map { it.first() } - .cast(Equipment::class.java) - ) + override fun getOwnedItems(userID: String, includeZero: Boolean): Flow> { + return queryUser(userID) + .filterNotNull() + .map { + val items = HashMap() + it.items?.eggs?.forEach { items[it.key + "-" + it.itemType] = it } + it.items?.food?.forEach { items[it.key + "-" + it.itemType] = it } + it.items?.hatchingPotions?.forEach { items[it.key + "-" + it.itemType] = it } + it.items?.quests?.forEach { items[it.key + "-" + it.itemType] = it } + if (includeZero) { + items + } else { + items.filter { it.value.numberOwned > 0 } + } + } + } + + override fun getEquipment(key: String): Flow { + return realm.where(Equipment::class.java) + .equalTo("key", key) + .findAll() + .toFlow() + .filter { realmObject -> realmObject.isLoaded && realmObject.isNotEmpty() } + .map { it.first() } + .filterNotNull() } override fun getMounts(): Flow> { return realm.where(Mount::class.java) - .sort("type", Sort.ASCENDING, "animal", Sort.ASCENDING) - .findAll() - .toFlow() - .filter { it.isLoaded } + .sort("type", Sort.ASCENDING, "animal", Sort.ASCENDING) + .findAll() + .toFlow() + .filter { it.isLoaded } } override fun getMounts(type: String?, group: String?, color: String?): Flow> { @@ -183,8 +173,8 @@ class RealmInventoryLocalRepository(realm: Realm) : RealmContentLocalRepository( query = query.equalTo("color", color) } return query.findAll() - .toFlow() - .filter { it.isLoaded } + .toFlow() + .filter { it.isLoaded } } override fun getOwnedMounts(userID: String): Flow> { @@ -198,10 +188,10 @@ class RealmInventoryLocalRepository(realm: Realm) : RealmContentLocalRepository( override fun getPets(): Flow> { return realm.where(Pet::class.java) - .sort("type", Sort.ASCENDING, "animal", Sort.ASCENDING) - .findAll() - .toFlow() - .filter { it.isLoaded } + .sort("type", Sort.ASCENDING, "animal", Sort.ASCENDING) + .findAll() + .toFlow() + .filter { it.isLoaded } } override fun getPets(type: String?, group: String?, color: String?): Flow> { @@ -217,15 +207,15 @@ class RealmInventoryLocalRepository(realm: Realm) : RealmContentLocalRepository( query = query.equalTo("color", color) } return query.findAll() - .toFlow() - .filter { it.isLoaded } + .toFlow() + .filter { it.isLoaded } } override fun getOwnedPets(userID: String): Flow> { return realm.where(User::class.java) - .equalTo("id", userID) - .findAll() - .toFlow() + .equalTo("id", userID) + .findAll() + .toFlow() .filter { it.isLoaded && it.isValid && !it.isEmpty() } .map { it.first()?.items?.pets?.filter { @@ -237,8 +227,16 @@ class RealmInventoryLocalRepository(realm: Realm) : RealmContentLocalRepository( override fun updateOwnedEquipment(user: User) { } - override fun changeOwnedCount(type: String, key: String, userID: String, amountToAdd: Int) { - getOwnedItem(userID, type, key, true).firstElement().subscribe({ changeOwnedCount(it, amountToAdd) }, ExceptionHandler.rx()) + override suspend fun changeOwnedCount( + type: String, + key: String, + userID: String, + amountToAdd: Int + ) { + val item = getOwnedItem(userID, type, key, true).firstOrNull() + if (item != null) { + changeOwnedCount(item, amountToAdd) + } } override fun changeOwnedCount(item: OwnedItem, amountToAdd: Int?) { @@ -248,29 +246,36 @@ class RealmInventoryLocalRepository(realm: Realm) : RealmContentLocalRepository( } } - override fun getOwnedItem(userID: String, type: String, key: String, includeZero: Boolean): Flowable { - return queryUserFlowable(userID).map { - var items = ( - when (type) { - "eggs" -> it.items?.eggs - "hatchingPotions" -> it.items?.hatchingPotions - "food" -> it.items?.food - "quests" -> it.items?.quests - else -> emptyList() - } ?: emptyList() - ) - items = items.filter { it.key == key } - if (includeZero) { - items - } else { - items.filter { it.numberOwned > 0 } + override fun getOwnedItem( + userID: String, + type: String, + key: String, + includeZero: Boolean + ): Flow { + return queryUser(userID) + .filterNotNull() + .map { + var items = ( + when (type) { + "eggs" -> it.items?.eggs + "hatchingPotions" -> it.items?.hatchingPotions + "food" -> it.items?.food + "quests" -> it.items?.quests + else -> emptyList() + } ?: emptyList() + ) + items = items.filter { it.key == key } + if (includeZero) { + items + } else { + items.filter { it.numberOwned > 0 } + } } - } .filter { it.isNotEmpty() } .map { it.first() } } - override fun getItem(type: String, key: String): Flowable { + override fun getItem(type: String, key: String): Flow { val itemClass: Class = when (type) { "eggs" -> Egg::class.java "hatchingPotions" -> HatchingPotion::class.java @@ -279,14 +284,12 @@ class RealmInventoryLocalRepository(realm: Realm) : RealmContentLocalRepository( "special" -> SpecialItem::class.java else -> Egg::class.java } - return RxJavaBridge.toV3Flowable( - realm.where(itemClass).equalTo("key", key) - .findAll() - .asFlowable() - .filter { realmObject -> realmObject.isLoaded && realmObject.isNotEmpty() } - .map { it.first() } - .cast(Item::class.java) - ) + return realm.where(itemClass).equalTo("key", key) + .findAll() + .toFlow() + .filter { realmObject -> realmObject.isLoaded && realmObject.isNotEmpty() } + .map { it.firstOrNull() as? Item } + .filterNotNull() } override fun decrementMysteryItemCount(user: User?) { @@ -301,18 +304,17 @@ class RealmInventoryLocalRepository(realm: Realm) : RealmContentLocalRepository( item.numberOwned = item.numberOwned - 1 } if (liveUser?.isValid == true) { - liveUser.purchased?.plan?.mysteryItemCount = (user.purchased?.plan?.mysteryItemCount ?: 0) - 1 + liveUser.purchased?.plan?.mysteryItemCount = + (user.purchased?.plan?.mysteryItemCount ?: 0) - 1 } } } - override fun getInAppRewards(): Flowable> { - return RxJavaBridge.toV3Flowable( - realm.where(ShopItem::class.java) - .findAll() - .asFlowable() - .filter { it.isLoaded } - ) + override fun getInAppRewards(): Flow> { + return realm.where(ShopItem::class.java) + .findAll() + .toFlow() + .filter { it.isLoaded } } override fun saveInAppRewards(onlineItems: List) { @@ -333,7 +335,8 @@ class RealmInventoryLocalRepository(realm: Realm) : RealmContentLocalRepository( newPet.trained = 5 val user = realm.where(User::class.java).equalTo("id", userID).findFirst() ?: return val egg = user.items?.eggs?.firstOrNull { it.key == eggKey } ?: return - val hatchingPotion = user.items?.hatchingPotions?.firstOrNull { it.key == potionKey } ?: return + val hatchingPotion = + user.items?.hatchingPotions?.firstOrNull { it.key == potionKey } ?: return executeTransaction { egg.numberOwned -= 1 hatchingPotion.numberOwned -= 1 @@ -344,7 +347,8 @@ class RealmInventoryLocalRepository(realm: Realm) : RealmContentLocalRepository( override fun getLiveObject(obj: OwnedItem): OwnedItem? { if (isClosed) return null if (!obj.isManaged) return obj - return realm.where(OwnedItem::class.java).equalTo("key", obj.key).equalTo("itemType", obj.itemType).findFirst() + return realm.where(OwnedItem::class.java).equalTo("key", obj.key) + .equalTo("itemType", obj.itemType).findFirst() } override fun save(items: Items, userID: String) { @@ -359,7 +363,8 @@ class RealmInventoryLocalRepository(realm: Realm) : RealmContentLocalRepository( val pet = realm.where(OwnedPet::class.java).equalTo("key", "$eggKey-$potionKey").findFirst() val user = realm.where(User::class.java).equalTo("id", userID).findFirst() ?: return val egg = user.items?.eggs?.firstOrNull { it.key == eggKey } ?: return - val hatchingPotion = user.items?.hatchingPotions?.firstOrNull { it.key == potionKey } ?: return + val hatchingPotion = + user.items?.hatchingPotions?.firstOrNull { it.key == potionKey } ?: return executeTransaction { egg.numberOwned += 1 hatchingPotion.numberOwned += 1 @@ -384,21 +389,19 @@ class RealmInventoryLocalRepository(realm: Realm) : RealmContentLocalRepository( } } - override fun getLatestMysteryItem(): Flowable { - return RxJavaBridge.toV3Flowable( - realm.where(Equipment::class.java) - .contains("key", "mystery_2") - .sort("mystery", Sort.DESCENDING) - .findAll() - .asFlowable() - .filter { it.isLoaded && it.size > 0 } - .map { - val format = SimpleDateFormat("yyyyMM", Locale.US) - it.first { - it.key?.contains(format.format(Date())) == true - } + override fun getLatestMysteryItem(): Flow { + return realm.where(Equipment::class.java) + .contains("key", "mystery_2") + .sort("mystery", Sort.DESCENDING) + .findAll() + .toFlow() + .filter { it.isLoaded && it.size > 0 } + .map { + val format = SimpleDateFormat("yyyyMM", Locale.US) + it.first { + it.key?.contains(format.format(Date())) == true } - ) + } } override fun soldItem(userID: String, updatedUser: User): User { @@ -418,32 +421,42 @@ class RealmInventoryLocalRepository(realm: Realm) : RealmContentLocalRepository( return user } - override fun getAvailableLimitedItems(): Flowable> { - return Flowable.combineLatest( - realm.where(Egg::class.java) - .lessThan("event.start", Date()) - .greaterThan("event.end", Date()) - .findAll().asFlowable(), - realm.where(Food::class.java) - .lessThan("event.start", Date()) - .greaterThan("event.end", Date()) - .findAll().asFlowable(), - realm.where(HatchingPotion::class.java) - .lessThan("event.start", Date()) - .greaterThan("event.end", Date()) - .findAll().asFlowable(), - realm.where(QuestContent::class.java) - .lessThan("event.start", Date()) - .greaterThan("event.end", Date()) - .findAll().asFlowable(), - { eggs, food, potions, quests -> + override fun getAvailableLimitedItems(): Flow> { + return realm.where(Egg::class.java) + .lessThan("event.start", Date()) + .greaterThan("event.end", Date()) + .findAll().toFlow() + .map { val items = mutableListOf() - items.addAll(eggs) - items.addAll(food) - items.addAll(potions) - items.addAll(quests) + items.addAll(it) + items + } + .combine( + realm.where(Food::class.java) + .lessThan("event.start", Date()) + .greaterThan("event.end", Date()) + .findAll().toFlow() + ) { items, food -> + items.addAll(food) + items + } + .combine( + realm.where(HatchingPotion::class.java) + .lessThan("event.start", Date()) + .greaterThan("event.end", Date()) + .findAll().toFlow() + ) { items, food -> + items.addAll(food) + items + } + .combine( + realm.where(QuestContent::class.java) + .lessThan("event.start", Date()) + .greaterThan("event.end", Date()) + .findAll().toFlow() + ) { items, food -> + items.addAll(food) items } - ) } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/implementation/RealmSocialLocalRepository.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/implementation/RealmSocialLocalRepository.kt index eb1f5dec9..c63e9094a 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/implementation/RealmSocialLocalRepository.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/implementation/RealmSocialLocalRepository.kt @@ -9,8 +9,6 @@ import com.habitrpg.android.habitica.models.social.Group import com.habitrpg.android.habitica.models.social.GroupMembership import com.habitrpg.android.habitica.models.social.InboxConversation import com.habitrpg.android.habitica.models.user.User -import hu.akarnokd.rxjava3.bridge.RxJavaBridge -import io.reactivex.rxjava3.core.Flowable import io.realm.Realm import io.realm.Sort import io.realm.kotlin.toFlow @@ -30,13 +28,11 @@ class RealmSocialLocalRepository(realm: Realm) : RealmBaseLocalRepository(realm) .filter { it.isLoaded && it.isNotEmpty() } .map { it.first() } - override fun getGroupMemberships(userId: String): Flowable> = RxJavaBridge.toV3Flowable( - realm.where(GroupMembership::class.java) + override fun getGroupMemberships(userId: String): Flow> = realm.where(GroupMembership::class.java) .equalTo("userID", userId) .findAll() - .asFlowable() + .toFlow() .filter { it.isLoaded } - ) override fun updateMembership(userId: String, id: String, isMember: Boolean) { if (isMember) { @@ -123,16 +119,14 @@ class RealmSocialLocalRepository(realm: Realm) : RealmBaseLocalRepository(realm) } } - override fun getPublicGuilds(): Flowable> = RxJavaBridge.toV3Flowable( - realm.where(Group::class.java) + override fun getPublicGuilds() = realm.where(Group::class.java) .equalTo("type", "guild") .equalTo("privacy", "public") .notEqualTo("id", Group.TAVERN_ID) .sort("memberCount", Sort.DESCENDING) .findAll() - .asFlowable() + .toFlow() .filter { it.isLoaded } - ) @OptIn(ExperimentalCoroutinesApi::class) override fun getUserGroups(userID: String, type: String?) = realm.where(GroupMembership::class.java) @@ -155,14 +149,12 @@ class RealmSocialLocalRepository(realm: Realm) : RealmBaseLocalRepository(realm) .toFlow() } - override fun getGroups(type: String): Flowable> { - return RxJavaBridge.toV3Flowable( - realm.where(Group::class.java) + override fun getGroups(type: String): Flow> { + return realm.where(Group::class.java) .equalTo("type", type) .findAll() - .asFlowable() + .toFlow() .filter { it.isLoaded } - ) } override fun getGroup(id: String): Flow { @@ -174,15 +166,13 @@ class RealmSocialLocalRepository(realm: Realm) : RealmBaseLocalRepository(realm) .map { groups -> groups.first() } } - override fun getGroupChat(groupId: String): Flowable> { - return RxJavaBridge.toV3Flowable( - realm.where(ChatMessage::class.java) + override fun getGroupChat(groupId: String): Flow> { + return realm.where(ChatMessage::class.java) .equalTo("groupId", groupId) .sort("timestamp", Sort.DESCENDING) .findAll() - .asFlowable() + .toFlow() .filter { it.isLoaded } - ) } override fun deleteMessage(id: String) { diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/implementation/RealmTagLocalRepository.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/implementation/RealmTagLocalRepository.kt index 8f1e93c8c..99d26a19c 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/implementation/RealmTagLocalRepository.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/implementation/RealmTagLocalRepository.kt @@ -2,9 +2,9 @@ package com.habitrpg.android.habitica.data.local.implementation import com.habitrpg.android.habitica.data.local.TagLocalRepository import com.habitrpg.android.habitica.models.Tag -import hu.akarnokd.rxjava3.bridge.RxJavaBridge -import io.reactivex.rxjava3.core.Flowable import io.realm.Realm +import io.realm.kotlin.toFlow +import kotlinx.coroutines.flow.Flow class RealmTagLocalRepository(realm: Realm) : RealmBaseLocalRepository(realm), TagLocalRepository { override fun deleteTag(tagID: String) { @@ -12,9 +12,7 @@ class RealmTagLocalRepository(realm: Realm) : RealmBaseLocalRepository(realm), T executeTransaction { tags.deleteAllFromRealm() } } - override fun getTags(userId: String): Flowable> { - return RxJavaBridge.toV3Flowable( - realm.where(Tag::class.java).equalTo("userId", userId).findAll().asFlowable() - ) + override fun getTags(userId: String): Flow> { + return realm.where(Tag::class.java).equalTo("userId", userId).findAll().toFlow() } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/implementation/RealmTaskLocalRepository.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/implementation/RealmTaskLocalRepository.kt index 6d97a4e61..51ff44e96 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/implementation/RealmTaskLocalRepository.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/implementation/RealmTaskLocalRepository.kt @@ -8,7 +8,6 @@ import com.habitrpg.android.habitica.models.tasks.TaskList import com.habitrpg.android.habitica.models.user.User import com.habitrpg.shared.habitica.models.tasks.TaskType import com.habitrpg.shared.habitica.models.tasks.TasksOrder -import hu.akarnokd.rxjava3.bridge.RxJavaBridge import io.reactivex.rxjava3.core.Flowable import io.reactivex.rxjava3.core.Maybe import io.realm.Realm @@ -30,13 +29,6 @@ class RealmTaskLocalRepository(realm: Realm) : RealmBaseLocalRepository(realm), .filter { it.isLoaded } } - override fun getTasksFlowable(taskType: TaskType, userID: String, includedGroupIDs: Array): Flowable> { - if (realm.isClosed) return Flowable.empty() - return RxJavaBridge.toV3Flowable(findTasks(taskType, userID, includedGroupIDs) - .asFlowable() - .filter { it.isLoaded }) - } - private fun findTasks( taskType: TaskType, ownerID: String, @@ -226,16 +218,13 @@ class RealmTaskLocalRepository(realm: Realm) : RealmBaseLocalRepository(realm), } } - override fun getTaskAtPosition(taskType: String, position: Int): Flowable { - return RxJavaBridge.toV3Flowable( - realm.where(Task::class.java).equalTo("typeValue", taskType).equalTo("position", position) + override fun getTaskAtPosition(taskType: String, position: Int): Flow { + return realm.where(Task::class.java).equalTo("typeValue", taskType).equalTo("position", position) .findAll() - .asFlowable() + .toFlow() .filter { realmObject -> realmObject.isLoaded && realmObject.isNotEmpty() } .map { it.first() } - .filter { realmObject -> realmObject.isLoaded } - .cast(Task::class.java) - ) + .filterNotNull() } override fun updateIsdue(daily: TaskList): Maybe { @@ -258,27 +247,14 @@ class RealmTaskLocalRepository(realm: Realm) : RealmBaseLocalRepository(realm), } } - override fun getErroredTasks(userID: String): Flowable> { - return RxJavaBridge.toV3Flowable( - realm.where(Task::class.java) + override fun getErroredTasks(userID: String): Flow> { + return realm.where(Task::class.java) .equalTo("userId", userID) .equalTo("hasErrored", true) .sort("position") .findAll() - .asFlowable() + .toFlow() .filter { it.isLoaded } - ).retry(1) - } - - override fun getUserFlowable(userID: String): Flowable { - 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() } - ) } override fun getUser(userID: String): Flow { @@ -291,15 +267,12 @@ class RealmTaskLocalRepository(realm: Realm) : RealmBaseLocalRepository(realm), .filterNotNull() } - override fun getTasksForChallenge(challengeID: String?, userID: String?): Flowable> { - return RxJavaBridge.toV3Flowable( - realm.where(Task::class.java) + override fun getTasksForChallenge(challengeID: String?, userID: String?): Flow> { + return realm.where(Task::class.java) .equalTo("challengeID", challengeID) .equalTo("userId", userID) .findAll() - .asFlowable() + .toFlow() .filter { it.isLoaded } - ) - .retry(1) } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/implementation/RealmTutorialLocalRepository.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/implementation/RealmTutorialLocalRepository.kt index 8f7577680..0e7c81185 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/implementation/RealmTutorialLocalRepository.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/implementation/RealmTutorialLocalRepository.kt @@ -2,31 +2,32 @@ package com.habitrpg.android.habitica.data.local.implementation import com.habitrpg.android.habitica.data.local.TutorialLocalRepository import com.habitrpg.android.habitica.models.TutorialStep -import hu.akarnokd.rxjava3.bridge.RxJavaBridge -import io.reactivex.rxjava3.core.Flowable import io.realm.Realm +import io.realm.kotlin.toFlow +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.emptyFlow +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.filterNotNull +import kotlinx.coroutines.flow.map class RealmTutorialLocalRepository(realm: Realm) : RealmBaseLocalRepository(realm), TutorialLocalRepository { - override fun getTutorialStep(key: String): Flowable { - if (realm.isClosed) return Flowable.empty() - return RxJavaBridge.toV3Flowable( - realm.where(TutorialStep::class.java).equalTo("identifier", key) + override fun getTutorialStep(key: String): Flow { + if (realm.isClosed) return emptyFlow() + return realm.where(TutorialStep::class.java).equalTo("identifier", key) .findAll() - .asFlowable() + .toFlow() .filter { realmObject -> realmObject.isLoaded && realmObject.isValid && realmObject.isNotEmpty() } .map { steps -> steps.first() } - ) + .filterNotNull() } - override fun getTutorialSteps(keys: List): Flowable> { - if (realm.isClosed) return Flowable.empty() - return RxJavaBridge.toV3Flowable( - realm.where(TutorialStep::class.java) + override fun getTutorialSteps(keys: List): Flow> { + if (realm.isClosed) return emptyFlow() + return realm.where(TutorialStep::class.java) .`in`("identifier", keys.toTypedArray()) .findAll() - .asFlowable() + .toFlow() .filter { it.isLoaded } - ) } } 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 501efe7b8..f835f4017 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 @@ -13,36 +13,40 @@ 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 import io.realm.kotlin.toFlow +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.filterNotNull +import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.map class RealmUserLocalRepository(realm: Realm) : RealmBaseLocalRepository(realm), UserLocalRepository { - override fun getUserQuestStatus(userID: String): Flowable { - return getUserFlowable(userID) + @OptIn(ExperimentalCoroutinesApi::class) + override fun getUserQuestStatus(userID: String): Flow { + return getUser(userID) + .filterNotNull() .map { it.party?.id ?: "" } - .distinctUntilChanged() .filter { it.isNotBlank() } - .flatMap { - RxJavaBridge.toV3Flowable( + .flatMapLatest { realm.where(Group::class.java) .equalTo("id", it) .findAll() - .asFlowable() + .toFlow() .filter { groups -> groups.size > 0 } - ).filterMap { it.first() } + .map { it.firstOrNull() } + .filterNotNull() } .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?.hp ?: 0.0 > 0.0 -> UserQuestStatus.QUEST_BOSS + (it.quest?.progress?.hp ?: 0.0) > 0.0 -> UserQuestStatus.QUEST_BOSS else -> UserQuestStatus.QUEST_UNKNOWN } } @@ -79,17 +83,6 @@ class RealmUserLocalRepository(realm: Realm) : RealmBaseLocalRepository(realm), .map { users -> users.first() } } - override fun getUserFlowable(userID: String): Flowable { - 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() }) - } - override fun saveUser(user: User, overrideExisting: Boolean) { if (realm.isClosed) return val oldUser = realm.where(User::class.java) @@ -134,45 +127,40 @@ class RealmUserLocalRepository(realm: Realm) : RealmBaseLocalRepository(realm), .filter { it.isLoaded } } - override fun getTeamPlan(teamID: String): Flowable { - if (realm.isClosed) return Flowable.empty() - return RxJavaBridge.toV3Flowable( - realm.where(Group::class.java) + override fun getTeamPlan(teamID: String): Flow { + if (realm.isClosed) return emptyFlow() + return realm.where(Group::class.java) .equalTo("id", teamID) .findAll() - .asFlowable() + .toFlow() .filter { realmObject -> realmObject.isLoaded && realmObject.isValid && !realmObject.isEmpty() } - .map { teams -> teams.first() } - ) + .map { teams -> teams.firstOrNull() } + .filterNotNull() } - override fun getSkills(user: User): Flowable> { + override fun getSkills(user: User): Flow> { val habitClass = if (user.preferences?.disableClasses == true) "none" else user.stats?.habitClass - return RxJavaBridge.toV3Flowable( - realm.where(Skill::class.java) + return realm.where(Skill::class.java) .equalTo("habitClass", habitClass) .sort("lvl") .findAll() - .asFlowable() + .toFlow() .filter { it.isLoaded } - ) } - override fun getSpecialItems(user: User): Flowable> { + override fun getSpecialItems(user: User): Flow> { val specialItems = user.items?.special val ownedItems = ArrayList() for (key in listOf("snowball", "shinySeed", "seafoam", "spookySparkles")) { - if (specialItems?.firstOrNull() { it.key == key }?.numberOwned ?: 0 > 0) { + if ((specialItems?.firstOrNull { it.key == key }?.numberOwned ?: 0) > 0) { ownedItems.add(key) } } - return RxJavaBridge.toV3Flowable( - realm.where(Skill::class.java) + return realm.where(Skill::class.java) .`in`("key", ownedItems.toTypedArray()) .findAll() - .asFlowable() + .toFlow() .filter { it.isLoaded } - ) } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/AppConfigManager.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/AppConfigManager.kt index ff3e5cc5b..95828ea89 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/AppConfigManager.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/AppConfigManager.kt @@ -12,6 +12,7 @@ import com.habitrpg.android.habitica.models.promotions.HabiticaPromotion import com.habitrpg.android.habitica.models.promotions.HabiticaWebPromotion import com.habitrpg.android.habitica.models.promotions.getHabiticaPromotionFromKey import com.habitrpg.common.habitica.helpers.AppTestingLevel +import kotlinx.coroutines.MainScope class AppConfigManager(contentRepository: ContentRepository?): com.habitrpg.common.habitica.helpers.AppConfigManager() { @@ -19,12 +20,11 @@ class AppConfigManager(contentRepository: ContentRepository?): com.habitrpg.comm init { try { - contentRepository?.getWorldState()?.subscribe( - { - worldState = it - }, - ExceptionHandler.rx() - ) + MainScope().launchCatching { + contentRepository?.getWorldState()?.collect { + worldState = it + } + } } catch (_: java.lang.IllegalStateException) { // pass } 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 91f3821f4..2e52bb4ec 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 @@ -56,6 +56,8 @@ class ExceptionHandler { } } -fun CoroutineScope.launchCatching(function: suspend CoroutineScope.() -> Unit) { - launch((ExceptionHandler.coroutine()), block = function) +fun CoroutineScope.launchCatching(errorHandler: ((Throwable) -> Unit)? = null, function: suspend CoroutineScope.() -> Unit) { + launch((ExceptionHandler.coroutine { + errorHandler?.invoke(it) + }), block = function) } \ No newline at end of file diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/NotificationsManager.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/NotificationsManager.kt index 96eaeb26b..803829294 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/NotificationsManager.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/NotificationsManager.kt @@ -3,56 +3,50 @@ package com.habitrpg.android.habitica.helpers import android.content.Context import androidx.core.app.NotificationManagerCompat import com.habitrpg.android.habitica.data.ApiClient -import com.habitrpg.common.habitica.models.Notification import com.habitrpg.android.habitica.models.tasks.Task -import io.reactivex.rxjava3.core.BackpressureStrategy -import io.reactivex.rxjava3.core.Flowable -import io.reactivex.rxjava3.subjects.BehaviorSubject -import io.reactivex.rxjava3.subjects.PublishSubject +import com.habitrpg.common.habitica.models.Notification +import kotlinx.coroutines.MainScope +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.filterNotNull import java.lang.ref.WeakReference import java.util.Date interface NotificationsManager { - val displayNotificationEvents: Flowable + val displayNotificationEvents: Flow var apiClient: WeakReference? fun setNotifications(current: List) - fun getNotifications(): Flowable> + fun getNotifications(): Flow> fun getNotification(id: String): Notification? fun dismissTaskNotification(context: Context, task: Task) } class MainNotificationsManager: NotificationsManager { - private val displayNotificationSubject = PublishSubject.create() private val seenNotifications: MutableMap override var apiClient: WeakReference? = null - private val notifications: BehaviorSubject> private var lastNotificationHandling: Date? = null - - override val displayNotificationEvents: Flowable - get() { - return displayNotificationSubject.toFlowable(BackpressureStrategy.DROP) - } + val _notifications = MutableStateFlow?>(null) + val _displaynotificationEvents = MutableStateFlow(null) + override val displayNotificationEvents: Flow = _displaynotificationEvents.filterNotNull() init { this.seenNotifications = HashMap() - this.notifications = BehaviorSubject.create() } override fun setNotifications(current: List) { - this.notifications.onNext(current) + _notifications.value = current this.handlePopupNotifications(current) } - override fun getNotifications(): Flowable> { - return this.notifications.startWithArray(emptyList()) - .toFlowable(BackpressureStrategy.LATEST) + override fun getNotifications(): Flow> { + return _notifications.filterNotNull() } override fun getNotification(id: String): Notification? { - return this.notifications.value?.find { it.id == id } + return _notifications.value?.find { it.id == id } } override fun dismissTaskNotification(context: Context, task: Task) { @@ -109,7 +103,7 @@ class MainNotificationsManager: NotificationsManager { } if (notificationDisplayed) { - displayNotificationSubject.onNext(it) + _displaynotificationEvents.value = it this.seenNotifications[it.id] = true readNotification(it) } @@ -119,7 +113,8 @@ class MainNotificationsManager: NotificationsManager { } private fun readNotification(notification: Notification) { - apiClient?.get()?.readNotification(notification.id) - ?.subscribe({ }, ExceptionHandler.rx()) + MainScope().launchCatching { + apiClient?.get()?.readNotification(notification.id) + } } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/PurchaseHandler.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/PurchaseHandler.kt index 6acb1c644..7d497f91a 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/PurchaseHandler.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/PurchaseHandler.kt @@ -72,10 +72,13 @@ class PurchaseHandler( val mostRecentSub = findMostRecentSubscription(purchases) val plan = userViewModel.user.value?.purchased?.plan for (purchase in purchases) { - if (plan?.isActive == true && PurchaseTypes.allSubscriptionTypes.contains(purchase.skus.firstOrNull())) { + if (plan?.isActive == true && PurchaseTypes.allSubscriptionTypes.contains( + purchase.skus.firstOrNull() + ) + ) { if ((plan.additionalData?.data?.orderId == purchase.orderId && - ((plan.dateTerminated != null) == purchase.isAutoRenewing)) || - mostRecentSub?.orderId != purchase.orderId + ((plan.dateTerminated != null) == purchase.isAutoRenewing)) || + mostRecentSub?.orderId != purchase.orderId ) { return } @@ -138,8 +141,8 @@ class PurchaseHandler( billingClient.endConnection() } - fun queryPurchases(){ - if (billingClientState == BillingClientState.READY){ + fun queryPurchases() { + if (billingClientState == BillingClientState.READY) { billingClient.queryPurchasesAsync( BillingClient.SkuType.SUBS, this@PurchaseHandler @@ -185,7 +188,12 @@ class PurchaseHandler( return skuDetailsResult.skuDetailsList } - fun purchase(activity: Activity, skuDetails: SkuDetails, recipient: String? = null, isSaleGemPurchase: Boolean = false) { + fun purchase( + activity: Activity, + skuDetails: SkuDetails, + recipient: String? = null, + isSaleGemPurchase: Boolean = false + ) { this.isSaleGemPurchase = isSaleGemPurchase recipient?.let { addGift(skuDetails.sku, it) @@ -216,41 +224,50 @@ class PurchaseHandler( when { PurchaseTypes.allGemTypes.contains(sku) -> { val validationRequest = buildValidationRequest(purchase) - apiClient.validatePurchase(validationRequest).subscribe({ - processedPurchase(purchase) - val gift = removeGift(sku) - CoroutineScope(Dispatchers.IO).launch(ExceptionHandler.coroutine()) { - consume(purchase) + MainScope().launchCatching { + try { + apiClient.validatePurchase(validationRequest) + processedPurchase(purchase) + val gift = removeGift(sku) + CoroutineScope(Dispatchers.IO).launch(ExceptionHandler.coroutine()) { + consume(purchase) + } + displayConfirmationDialog(purchase, gift?.second) + } catch (throwable: Throwable) { + handleError(throwable, purchase) } - displayConfirmationDialog(purchase, gift?.second) - }) { throwable: Throwable -> - handleError(throwable, purchase) } } PurchaseTypes.allSubscriptionNoRenewTypes.contains(sku) -> { val validationRequest = buildValidationRequest(purchase) - apiClient.validateNoRenewSubscription(validationRequest).subscribe({ - processedPurchase(purchase) - val gift = removeGift(sku) - CoroutineScope(Dispatchers.IO).launch(ExceptionHandler.coroutine()) { - consume(purchase) + MainScope().launchCatching { + try { + apiClient.validateNoRenewSubscription(validationRequest) + processedPurchase(purchase) + val gift = removeGift(sku) + CoroutineScope(Dispatchers.IO).launch(ExceptionHandler.coroutine()) { + consume(purchase) + } + displayConfirmationDialog(purchase, gift?.second) + } catch (throwable: Throwable) { + handleError(throwable, purchase) } - displayConfirmationDialog(purchase, gift?.second) - }) { throwable: Throwable -> - handleError(throwable, purchase) } } PurchaseTypes.allSubscriptionTypes.contains(sku) -> { val validationRequest = buildValidationRequest(purchase) - apiClient.validateSubscription(validationRequest).subscribe({ - processedPurchase(purchase) - analyticsManager.logEvent("user_subscribed", bundleOf(Pair("sku", sku))) - CoroutineScope(Dispatchers.IO).launch(ExceptionHandler.coroutine()) { - acknowledgePurchase(purchase) + MainScope().launchCatching { + try { + apiClient.validateSubscription(validationRequest) + processedPurchase(purchase) + analyticsManager.logEvent("user_subscribed", bundleOf(Pair("sku", sku))) + CoroutineScope(Dispatchers.IO).launch(ExceptionHandler.coroutine()) { + acknowledgePurchase(purchase) + } + displayConfirmationDialog(purchase) + } catch (throwable: Throwable) { + handleError(throwable, purchase) } - displayConfirmationDialog(purchase) - }) { throwable: Throwable -> - handleError(throwable, purchase) } } } @@ -320,7 +337,8 @@ class PurchaseHandler( } private fun findMostRecentSubscription(purchasesList: List): Purchase? { - val purchases = purchasesList.filter { it.isAcknowledged }.sortedByDescending { it.purchaseTime } + val purchases = + purchasesList.filter { it.isAcknowledged }.sortedByDescending { it.purchaseTime } var fallback: Purchase? = null // If there is a subscription that is still active, prioritise that. Otherwise return the most recent one. for (purchase in purchases) { @@ -380,18 +398,29 @@ class PurchaseHandler( val message = when { PurchaseTypes.allSubscriptionNoRenewTypes.contains(sku) -> { title = context.getString(R.string.gift_confirmation_title) - context.getString(R.string.gift_confirmation_text_sub, giftedTo, durationString(sku)) + context.getString( + R.string.gift_confirmation_text_sub, + giftedTo, + durationString(sku) + ) } PurchaseTypes.allSubscriptionTypes.contains(sku) -> { if (sku == PurchaseTypes.Subscription1Month) { context.getString(R.string.subscription_confirmation) } else { - context.getString(R.string.subscription_confirmation_multiple, durationString(sku)) + context.getString( + R.string.subscription_confirmation_multiple, + durationString(sku) + ) } } PurchaseTypes.allGemTypes.contains(sku) && giftedTo != null -> { title = context.getString(R.string.gift_confirmation_title) - context.getString(R.string.gift_confirmation_text_gems_new, giftedTo, gemAmountString(sku)) + context.getString( + R.string.gift_confirmation_text_gems_new, + giftedTo, + gemAmountString(sku) + ) } PurchaseTypes.allGemTypes.contains(sku) && giftedTo == null -> { context.getString(R.string.gem_purchase_confirmation, gemAmountString(sku)) diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/UserStatComputer.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/UserStatComputer.kt index de8b61a14..f75d18a68 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/UserStatComputer.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/UserStatComputer.kt @@ -25,7 +25,7 @@ class UserStatComputer { var stats: String? = null } - fun computeClassBonus(equipmentList: List, user: Avatar): List { + fun computeClassBonus(equipmentList: List?, user: Avatar): List { val skillRows = ArrayList() var strAttributes = 0f @@ -39,7 +39,7 @@ class UserStatComputer { var perClassBonus = 0f // Summarize stats and fill equipment table - for (i in equipmentList) { + for (i in equipmentList ?: emptyList()) { val strength = i.str val intelligence = i._int val constitution = i.con diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/notifications/PushNotificationManager.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/notifications/PushNotificationManager.kt index 8fac75a74..89f2a7ea1 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/notifications/PushNotificationManager.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/notifications/PushNotificationManager.kt @@ -7,8 +7,9 @@ import com.google.firebase.messaging.FirebaseMessaging import com.google.firebase.messaging.RemoteMessage import com.habitrpg.android.habitica.data.ApiClient import com.habitrpg.android.habitica.helpers.AmplitudeManager -import com.habitrpg.android.habitica.helpers.ExceptionHandler +import com.habitrpg.android.habitica.helpers.launchCatching import com.habitrpg.android.habitica.models.user.User +import kotlinx.coroutines.MainScope class PushNotificationManager( var apiClient: ApiClient, @@ -51,14 +52,18 @@ class PushNotificationManager( val pushDeviceData = HashMap() pushDeviceData["regId"] = this.refreshedToken pushDeviceData["type"] = "android" - apiClient.addPushDevice(pushDeviceData).subscribe({ }, ExceptionHandler.rx()) + MainScope().launchCatching { + apiClient.addPushDevice(pushDeviceData) + } } fun removePushDeviceUsingStoredToken() { if (this.refreshedToken.isEmpty() || !userHasPushDevice()) { return } - apiClient.deletePushDevice(this.refreshedToken).subscribe({ }, ExceptionHandler.rx()) + MainScope().launchCatching { + apiClient.deletePushDevice(refreshedToken) + } } private fun userHasPushDevice(): Boolean { diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/interactors/CheckClassSelectionUseCase.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/interactors/CheckClassSelectionUseCase.kt index d0cd898ff..ecbace013 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/interactors/CheckClassSelectionUseCase.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/interactors/CheckClassSelectionUseCase.kt @@ -6,27 +6,21 @@ import android.os.Bundle import com.habitrpg.android.habitica.executors.PostExecutionThread import com.habitrpg.android.habitica.models.user.User import com.habitrpg.android.habitica.ui.activities.ClassSelectionActivity -import io.reactivex.rxjava3.core.Flowable import javax.inject.Inject -class CheckClassSelectionUseCase @Inject constructor(postExecutionThread: PostExecutionThread) : UseCase(postExecutionThread) { +class CheckClassSelectionUseCase @Inject constructor(postExecutionThread: PostExecutionThread) : FlowUseCase() { - override fun buildUseCaseObservable(requestValues: RequestValues): Flowable { - return Flowable.defer { - val user = requestValues.user - - if (requestValues.currentClass == null) { - if (user?.stats?.lvl ?: 0 >= 9 && - user?.preferences?.disableClasses != true && - user?.flags?.classSelected != true - ) { - displayClassSelectionActivity(true, null, requestValues.activity) - } - } else { - displayClassSelectionActivity(requestValues.isInitialSelection, requestValues.currentClass, requestValues.activity) + override suspend fun run(requestValues: RequestValues) { + val user = requestValues.user + if (requestValues.currentClass == null) { + if ((user?.stats?.lvl ?: 0) >= 9 && + user?.preferences?.disableClasses != true && + user?.flags?.classSelected != true + ) { + displayClassSelectionActivity(true, null, requestValues.activity) } - - Flowable.empty() + } else { + displayClassSelectionActivity(requestValues.isInitialSelection, requestValues.currentClass, requestValues.activity) } } @@ -49,5 +43,5 @@ class CheckClassSelectionUseCase @Inject constructor(postExecutionThread: PostEx val isInitialSelection: Boolean, val currentClass: String?, val activity: Activity - ) : UseCase.RequestValues + ) : FlowUseCase.RequestValues } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/interactors/DisplayItemDropUseCase.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/interactors/DisplayItemDropUseCase.kt index c5e841032..f108a63bc 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/interactors/DisplayItemDropUseCase.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/interactors/DisplayItemDropUseCase.kt @@ -3,11 +3,9 @@ package com.habitrpg.android.habitica.interactors import android.view.ViewGroup import androidx.appcompat.app.AppCompatActivity import com.habitrpg.android.habitica.R -import com.habitrpg.android.habitica.executors.PostExecutionThread import com.habitrpg.android.habitica.helpers.SoundManager import com.habitrpg.android.habitica.ui.views.HabiticaSnackbar import com.habitrpg.shared.habitica.models.responses.TaskScoringResult -import io.reactivex.rxjava3.core.Flowable import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.MainScope import kotlinx.coroutines.delay @@ -15,32 +13,35 @@ import kotlinx.coroutines.launch import javax.inject.Inject class DisplayItemDropUseCase @Inject -constructor(private val soundManager: SoundManager, postExecutionThread: PostExecutionThread) : UseCase(postExecutionThread) { +constructor(private val soundManager: SoundManager): + FlowUseCase() { - override fun buildUseCaseObservable(requestValues: RequestValues): Flowable { - return Flowable.defer { - val data = requestValues.data - val snackbarText = StringBuilder(data?.drop?.dialog ?: "") + override suspend fun run(requestValues: RequestValues) { + val data = requestValues.data + val snackbarText = StringBuilder(data?.drop?.dialog ?: "") - if ((data?.questItemsFound ?: 0) > 0 && requestValues.showQuestItems) { - if (snackbarText.isNotEmpty()) - snackbarText.append('\n') - snackbarText.append(requestValues.context.getString(R.string.quest_items_found, data!!.questItemsFound)) - } - - if (snackbarText.isNotEmpty()) { - MainScope().launch(context = Dispatchers.Main) { - delay(3000L) - HabiticaSnackbar.showSnackbar( - requestValues.snackbarTargetView, - snackbarText, HabiticaSnackbar.SnackbarDisplayType.DROP, true - ) - soundManager.loadAndPlayAudio(SoundManager.SoundItemDrop) - } - } - - Flowable.empty() + if ((data?.questItemsFound ?: 0) > 0 && requestValues.showQuestItems) { + if (snackbarText.isNotEmpty()) + snackbarText.append('\n') + snackbarText.append( + requestValues.context.getString( + R.string.quest_items_found, + data!!.questItemsFound + ) + ) } + + if (snackbarText.isNotEmpty()) { + MainScope().launch(context = Dispatchers.Main) { + delay(3000L) + HabiticaSnackbar.showSnackbar( + requestValues.snackbarTargetView, + snackbarText, HabiticaSnackbar.SnackbarDisplayType.DROP, true + ) + soundManager.loadAndPlayAudio(SoundManager.SoundItemDrop) + } + } + return } class RequestValues( @@ -48,5 +49,5 @@ constructor(private val soundManager: SoundManager, postExecutionThread: PostExe val context: AppCompatActivity, val snackbarTargetView: ViewGroup, val showQuestItems: Boolean - ) : UseCase.RequestValues + ) : FlowUseCase.RequestValues } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/interactors/FeedPetUseCase.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/interactors/FeedPetUseCase.kt index eba34e677..5ae6bf77b 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/interactors/FeedPetUseCase.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/interactors/FeedPetUseCase.kt @@ -7,75 +7,75 @@ import android.view.View import android.widget.FrameLayout import com.habitrpg.android.habitica.R import com.habitrpg.android.habitica.data.InventoryRepository -import com.habitrpg.android.habitica.executors.PostExecutionThread import com.habitrpg.android.habitica.models.inventory.Food import com.habitrpg.android.habitica.models.inventory.Pet -import com.habitrpg.shared.habitica.models.responses.FeedResponse import com.habitrpg.android.habitica.ui.activities.BaseActivity -import com.habitrpg.common.habitica.extensions.loadImage -import com.habitrpg.common.habitica.views.PixelArtView import com.habitrpg.android.habitica.ui.views.SnackbarActivity import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog -import io.reactivex.rxjava3.core.Flowable +import com.habitrpg.common.habitica.extensions.loadImage +import com.habitrpg.common.habitica.views.PixelArtView +import com.habitrpg.shared.habitica.models.responses.FeedResponse import javax.inject.Inject class FeedPetUseCase @Inject constructor( private val inventoryRepository: InventoryRepository, - postExecutionThread: PostExecutionThread -) : UseCase(postExecutionThread) { - override fun buildUseCaseObservable(requestValues: FeedPetUseCase.RequestValues): Flowable { - return inventoryRepository.feedPet(requestValues.pet, requestValues.food) - .doOnNext { feedResponse -> - (requestValues.context as? SnackbarActivity)?.showSnackbar(content = feedResponse.message) - if (feedResponse.value == -1) { - val mountWrapper = - View.inflate( - requestValues.context, - R.layout.pet_imageview, - null - ) as? FrameLayout - val mountImageView = - mountWrapper?.findViewById(R.id.pet_imageview) as? PixelArtView +) : FlowUseCase() { + override suspend fun run(requestValues: FeedPetUseCase.RequestValues): FeedResponse? { + val feedResponse = inventoryRepository.feedPet(requestValues.pet, requestValues.food) + (requestValues.context as? SnackbarActivity)?.showSnackbar(content = feedResponse?.message) + if (feedResponse?.value == -1) { + val mountWrapper = + View.inflate( + requestValues.context, + R.layout.pet_imageview, + null + ) as? FrameLayout + val mountImageView = + mountWrapper?.findViewById(R.id.pet_imageview) as? PixelArtView - mountImageView?.loadImage("Mount_Icon_" + requestValues.pet.key) - val dialog = HabiticaAlertDialog(requestValues.context) - dialog.setTitle( - requestValues.context.getString( - R.string.evolved_pet_title, - requestValues.pet.text - ) + mountImageView?.loadImage("Mount_Icon_" + requestValues.pet.key) + val dialog = HabiticaAlertDialog(requestValues.context) + dialog.setTitle( + requestValues.context.getString( + R.string.evolved_pet_title, + requestValues.pet.text + ) + ) + dialog.setAdditionalContentView(mountWrapper) + dialog.addButton(R.string.onwards, true) + dialog.addButton(R.string.share, false) { hatchingDialog, _ -> + val message = + requestValues.context.getString( + R.string.share_raised, + requestValues.pet.text ) - dialog.setAdditionalContentView(mountWrapper) - dialog.addButton(R.string.onwards, true) - dialog.addButton(R.string.share, false) { hatchingDialog, _ -> - val message = - requestValues.context.getString( - R.string.share_raised, - requestValues.pet.text - ) - val mountImageSideLength = 99 - val sharedImage = Bitmap.createBitmap( - mountImageSideLength, - mountImageSideLength, - Bitmap.Config.ARGB_8888 - ) - val canvas = Canvas(sharedImage) - mountImageView?.drawable?.setBounds( - 0, - 0, - mountImageSideLength, - mountImageSideLength - ) - mountImageView?.drawable?.draw(canvas) - (requestValues.context as? BaseActivity)?.shareContent("raisedPet", message, sharedImage) - hatchingDialog.dismiss() - } - dialog.enqueue() - } + val mountImageSideLength = 99 + val sharedImage = Bitmap.createBitmap( + mountImageSideLength, + mountImageSideLength, + Bitmap.Config.ARGB_8888 + ) + val canvas = Canvas(sharedImage) + mountImageView?.drawable?.setBounds( + 0, + 0, + mountImageSideLength, + mountImageSideLength + ) + mountImageView?.drawable?.draw(canvas) + (requestValues.context as? BaseActivity)?.shareContent( + "raisedPet", + message, + sharedImage + ) + hatchingDialog.dismiss() } + dialog.enqueue() + } + return feedResponse } class RequestValues(val pet: Pet, val food: Food, val context: Context) : - UseCase.RequestValues + FlowUseCase.RequestValues } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/interactors/HatchPetUseCase.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/interactors/HatchPetUseCase.kt index 4125c9194..b2cbdbb50 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/interactors/HatchPetUseCase.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/interactors/HatchPetUseCase.kt @@ -7,24 +7,21 @@ import android.view.View import android.widget.FrameLayout import com.habitrpg.android.habitica.R import com.habitrpg.android.habitica.data.InventoryRepository -import com.habitrpg.android.habitica.executors.PostExecutionThread -import com.habitrpg.android.habitica.helpers.ExceptionHandler +import com.habitrpg.android.habitica.helpers.launchCatching import com.habitrpg.android.habitica.models.inventory.Egg import com.habitrpg.android.habitica.models.inventory.HatchingPotion import com.habitrpg.android.habitica.models.user.Items import com.habitrpg.android.habitica.ui.activities.BaseActivity +import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog import com.habitrpg.common.habitica.extensions.loadImage import com.habitrpg.common.habitica.views.PixelArtView -import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog -import io.reactivex.rxjava3.core.Flowable +import kotlinx.coroutines.MainScope import javax.inject.Inject class HatchPetUseCase @Inject constructor( - private val inventoryRepository: InventoryRepository, - postExecutionThread: PostExecutionThread -) : UseCase(postExecutionThread) { - override fun buildUseCaseObservable(requestValues: RequestValues): Flowable { + private val inventoryRepository: InventoryRepository) : FlowUseCase() { + override suspend fun run(requestValues: RequestValues): Items? { return inventoryRepository.hatchPet(requestValues.egg, requestValues.potion) { val petWrapper = View.inflate(requestValues.context, R.layout.pet_imageview, null) as? FrameLayout val petImageView = petWrapper?.findViewById(R.id.pet_imageview) as? PixelArtView @@ -36,8 +33,9 @@ constructor( dialog.setTitle(requestValues.context.getString(R.string.hatched_pet_title, potionName, eggName)) dialog.setAdditionalContentView(petWrapper) dialog.addButton(R.string.equip, true) { _, _ -> - inventoryRepository.equip("pet", requestValues.egg.key + "-" + requestValues.potion.key) - .subscribe({}, ExceptionHandler.rx()) + MainScope().launchCatching { + inventoryRepository.equip("pet", requestValues.egg.key + "-" + requestValues.potion.key) + } } dialog.addButton(R.string.share, false) { hatchingDialog, _ -> val message = requestValues.context.getString(R.string.share_hatched, potionName, eggName) @@ -54,5 +52,5 @@ constructor( } } - class RequestValues(val potion: HatchingPotion, val egg: Egg, val context: Context) : UseCase.RequestValues + class RequestValues(val potion: HatchingPotion, val egg: Egg, val context: Context) : FlowUseCase.RequestValues } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/interactors/LevelUpUseCase.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/interactors/LevelUpUseCase.kt index 669a660ab..2f0e7b2c6 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/interactors/LevelUpUseCase.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/interactors/LevelUpUseCase.kt @@ -4,97 +4,94 @@ import android.graphics.Bitmap import android.view.ViewGroup import com.habitrpg.android.habitica.R import com.habitrpg.android.habitica.databinding.DialogLevelup10Binding -import com.habitrpg.android.habitica.executors.PostExecutionThread -import com.habitrpg.android.habitica.helpers.ExceptionHandler import com.habitrpg.android.habitica.helpers.SoundManager +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.common.habitica.views.AvatarView import com.habitrpg.android.habitica.ui.activities.BaseActivity import com.habitrpg.android.habitica.ui.views.HabiticaIconsHelper import com.habitrpg.android.habitica.ui.views.HabiticaSnackbar import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog -import io.reactivex.rxjava3.core.Flowable +import com.habitrpg.common.habitica.views.AvatarView +import kotlinx.coroutines.MainScope import javax.inject.Inject class LevelUpUseCase @Inject constructor( private val soundManager: SoundManager, - postExecutionThread: PostExecutionThread, private val checkClassSelectionUseCase: CheckClassSelectionUseCase -) : UseCase(postExecutionThread) { +) : FlowUseCase() { - override fun buildUseCaseObservable(requestValues: RequestValues): Flowable { - return Flowable.defer { - soundManager.loadAndPlayAudio(SoundManager.SoundLevelUp) + override suspend fun run(requestValues: RequestValues): Stats? { + soundManager.loadAndPlayAudio(SoundManager.SoundLevelUp) + val suppressedModals = requestValues.user.preferences?.suppressModals - val suppressedModals = requestValues.user.preferences?.suppressModals + if (requestValues.newLevel == 10) { + val binding = DialogLevelup10Binding.inflate(requestValues.activity.layoutInflater) + binding.healerIconView.setImageBitmap(HabiticaIconsHelper.imageOfHealerLightBg()) + binding.mageIconView.setImageBitmap(HabiticaIconsHelper.imageOfMageLightBg()) + binding.rogueIconView.setImageBitmap(HabiticaIconsHelper.imageOfRogueLightBg()) + binding.warriorIconView.setImageBitmap(HabiticaIconsHelper.imageOfWarriorLightBg()) - if (requestValues.newLevel == 10) { - val binding = DialogLevelup10Binding.inflate(requestValues.activity.layoutInflater) - binding.healerIconView.setImageBitmap(HabiticaIconsHelper.imageOfHealerLightBg()) - binding.mageIconView.setImageBitmap(HabiticaIconsHelper.imageOfMageLightBg()) - binding.rogueIconView.setImageBitmap(HabiticaIconsHelper.imageOfRogueLightBg()) - binding.warriorIconView.setImageBitmap(HabiticaIconsHelper.imageOfWarriorLightBg()) - - val alert = HabiticaAlertDialog(requestValues.activity) - alert.setTitle(requestValues.activity.getString(R.string.levelup_header, requestValues.newLevel)) - alert.setAdditionalContentView(binding.root) - alert.addButton(R.string.select_class, true) { _, _ -> + val alert = HabiticaAlertDialog(requestValues.activity) + alert.setTitle(requestValues.activity.getString(R.string.levelup_header, requestValues.newLevel)) + alert.setAdditionalContentView(binding.root) + alert.addButton(R.string.select_class, true) { _, _ -> + MainScope().launchCatching { showClassSelection(requestValues) } - alert.addButton(R.string.not_now, false) - alert.isCelebratory = true - - if (!requestValues.activity.isFinishing) { - alert.enqueue() - } - } else { - if (suppressedModals?.levelUp == true) { - HabiticaSnackbar.showSnackbar( - requestValues.snackbarTargetView, - requestValues.activity.getString(R.string.levelup_header, requestValues.newLevel), - HabiticaSnackbar.SnackbarDisplayType.SUCCESS, true - ) - return@defer Flowable.just(requestValues.user.stats) - } - val customView = requestValues.activity.layoutInflater.inflate(R.layout.dialog_levelup, null) - if (customView != null) { - val dialogAvatarView = customView.findViewById(R.id.avatarView) - dialogAvatarView.setAvatar(requestValues.user) - } - - val message = requestValues.activity.getString(R.string.share_levelup, requestValues.newLevel) - val avatarView = AvatarView(requestValues.activity, showBackground = true, showMount = true, showPet = true) - avatarView.setAvatar(requestValues.user) - var sharedImage: Bitmap? = null - avatarView.onAvatarImageReady { image -> - sharedImage = image - } - - val alert = HabiticaAlertDialog(requestValues.activity) - alert.setTitle(requestValues.activity.getString(R.string.levelup_header, requestValues.newLevel)) - alert.setAdditionalContentView(customView) - alert.addButton(R.string.onwards, true) { _, _ -> - showClassSelection(requestValues) - } - alert.addButton(R.string.share, false) { _, _ -> - requestValues.activity.shareContent("levelup", message, sharedImage) - } - alert.isCelebratory = true - - if (!requestValues.activity.isFinishing) { - alert.enqueue() - } } + alert.addButton(R.string.not_now, false) + alert.isCelebratory = true - Flowable.just(requestValues.user.stats!!) + if (!requestValues.activity.isFinishing) { + alert.enqueue() + } + } else { + if (suppressedModals?.levelUp == true) { + HabiticaSnackbar.showSnackbar( + requestValues.snackbarTargetView, + requestValues.activity.getString(R.string.levelup_header, requestValues.newLevel), + HabiticaSnackbar.SnackbarDisplayType.SUCCESS, true + ) + return requestValues.user.stats + } + val customView = requestValues.activity.layoutInflater.inflate(R.layout.dialog_levelup, null) + if (customView != null) { + val dialogAvatarView = customView.findViewById(R.id.avatarView) + dialogAvatarView.setAvatar(requestValues.user) + } + + val message = requestValues.activity.getString(R.string.share_levelup, requestValues.newLevel) + val avatarView = AvatarView(requestValues.activity, showBackground = true, showMount = true, showPet = true) + avatarView.setAvatar(requestValues.user) + var sharedImage: Bitmap? = null + avatarView.onAvatarImageReady { image -> + sharedImage = image + } + + val alert = HabiticaAlertDialog(requestValues.activity) + alert.setTitle(requestValues.activity.getString(R.string.levelup_header, requestValues.newLevel)) + alert.setAdditionalContentView(customView) + alert.addButton(R.string.onwards, true) { _, _ -> + MainScope().launchCatching { + showClassSelection(requestValues) + } + } + alert.addButton(R.string.share, false) { _, _ -> + requestValues.activity.shareContent("levelup", message, sharedImage) + } + alert.isCelebratory = true + + if (!requestValues.activity.isFinishing) { + alert.enqueue() + } } + return requestValues.user.stats } - private fun showClassSelection(requestValues: RequestValues) { - checkClassSelectionUseCase.observable(CheckClassSelectionUseCase.RequestValues(requestValues.user, true, null, requestValues.activity)) - .subscribe({ }, ExceptionHandler.rx()) + private suspend fun showClassSelection(requestValues: RequestValues) { + checkClassSelectionUseCase.callInteractor(CheckClassSelectionUseCase.RequestValues(requestValues.user, true, null, requestValues.activity)) } class RequestValues( @@ -102,7 +99,7 @@ constructor( val level: Int?, val activity: BaseActivity, val snackbarTargetView: ViewGroup - ) : UseCase.RequestValues { + ) : FlowUseCase.RequestValues { val newLevel: Int = level ?: 0 } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/interactors/NotifyUserUseCase.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/interactors/NotifyUserUseCase.kt index 19b3f55b2..a70070cd0 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/interactors/NotifyUserUseCase.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/interactors/NotifyUserUseCase.kt @@ -13,7 +13,6 @@ import androidx.core.content.ContextCompat import androidx.core.util.Pair import com.habitrpg.android.habitica.R import com.habitrpg.android.habitica.data.UserRepository -import com.habitrpg.android.habitica.executors.PostExecutionThread import com.habitrpg.android.habitica.models.user.Stats import com.habitrpg.android.habitica.models.user.User import com.habitrpg.android.habitica.ui.activities.BaseActivity @@ -21,39 +20,30 @@ import com.habitrpg.android.habitica.ui.views.HabiticaIconsHelper import com.habitrpg.android.habitica.ui.views.HabiticaSnackbar import com.habitrpg.android.habitica.ui.views.HabiticaSnackbar.SnackbarDisplayType import com.habitrpg.shared.habitica.extensions.round -import io.reactivex.rxjava3.core.Flowable import javax.inject.Inject import kotlin.math.abs class NotifyUserUseCase @Inject constructor( - postExecutionThread: PostExecutionThread, private val levelUpUseCase: LevelUpUseCase, private val userRepository: UserRepository -) : UseCase(postExecutionThread) { +) : FlowUseCase() { - override fun buildUseCaseObservable(requestValues: RequestValues): Flowable { - return Flowable.defer { - if (requestValues.user == null) { - return@defer Flowable.empty() - } - val stats = requestValues.user.stats - - val pair = getNotificationAndAddStatsToUser(requestValues.context, requestValues.xp, requestValues.hp, requestValues.gold, requestValues.mp, requestValues.questDamage, requestValues.user) - val view = pair.first - val type = pair.second - if (view != null && type != null) { - HabiticaSnackbar.showSnackbar(requestValues.snackbarTargetView, null, null, view, type) - } - if (requestValues.hasLeveledUp == true) { - return@defer levelUpUseCase.observable(LevelUpUseCase.RequestValues(requestValues.user, requestValues.level, requestValues.context, requestValues.snackbarTargetView)) - // TODO: .flatMap { userRepository.retrieveUser(true) } - .flatMap { userRepository.getUserFlowable().firstElement().toFlowable() } - .map { it.stats } - } else { - return@defer Flowable.just(stats) - } + override suspend fun run(requestValues: RequestValues): Stats? { + if (requestValues.user == null) { + return null } + val pair = getNotificationAndAddStatsToUser(requestValues.context, requestValues.xp, requestValues.hp, requestValues.gold, requestValues.mp, requestValues.questDamage, requestValues.user) + val view = pair.first + val type = pair.second + if (view != null && type != null) { + HabiticaSnackbar.showSnackbar(requestValues.snackbarTargetView, null, null, view, type) + } + if (requestValues.hasLeveledUp == true) { + levelUpUseCase.callInteractor(LevelUpUseCase.RequestValues(requestValues.user, requestValues.level, requestValues.context, requestValues.snackbarTargetView)) + userRepository.retrieveUser(true) + } + return requestValues.user.stats } class RequestValues( @@ -67,7 +57,7 @@ constructor( val questDamage: Double?, val hasLeveledUp: Boolean?, val level: Int? - ) : UseCase.RequestValues + ) : FlowUseCase.RequestValues companion object { diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/interactors/UseCase.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/interactors/UseCase.kt index d60e1ed32..45e028064 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/interactors/UseCase.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/interactors/UseCase.kt @@ -18,8 +18,8 @@ abstract class UseCase protected constructor abstract class FlowUseCase { protected abstract suspend fun run(requestValues: Q): T - suspend fun observable(requestValues: Q): T { - return withContext(Dispatchers.IO) { + suspend fun callInteractor(requestValues: Q): T { + return withContext(Dispatchers.Main) { run(requestValues) } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/receivers/LocalNotificationActionReceiver.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/receivers/LocalNotificationActionReceiver.kt index 4ecc891e7..4e8662a2d 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/receivers/LocalNotificationActionReceiver.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/receivers/LocalNotificationActionReceiver.kt @@ -15,6 +15,7 @@ import com.habitrpg.android.habitica.data.SocialRepository import com.habitrpg.android.habitica.data.TaskRepository import com.habitrpg.android.habitica.data.UserRepository import com.habitrpg.android.habitica.helpers.ExceptionHandler +import com.habitrpg.android.habitica.helpers.launchCatching import com.habitrpg.android.habitica.interactors.NotifyUserUseCase import com.habitrpg.android.habitica.models.user.User import kotlinx.coroutines.MainScope @@ -65,15 +66,20 @@ class LocalNotificationActionReceiver : BroadcastReceiver() { } context?.getString(R.string.reject_party_invite) -> { groupID?.let { - socialRepository.rejectGroupInvite(it) - .subscribe({ }, ExceptionHandler.rx()) + MainScope().launchCatching { + socialRepository.rejectGroupInvite(it) + } } } context?.getString(R.string.accept_quest_invite) -> { - socialRepository.acceptQuest(user).subscribe({ }, ExceptionHandler.rx()) + MainScope().launchCatching { + socialRepository.acceptQuest(user) + } } context?.getString(R.string.reject_quest_invite) -> { - socialRepository.rejectQuest(user).subscribe({ }, ExceptionHandler.rx()) + MainScope().launchCatching { + socialRepository.rejectQuest(user) + } } context?.getString(R.string.accept_guild_invite) -> { groupID?.let { @@ -84,21 +90,20 @@ class LocalNotificationActionReceiver : BroadcastReceiver() { } context?.getString(R.string.reject_guild_invite) -> { groupID?.let { - socialRepository.rejectGroupInvite(it) - .subscribe({ }, ExceptionHandler.rx()) + MainScope().launchCatching { + socialRepository.rejectGroupInvite(it) + } } } context?.getString(R.string.group_message_reply) -> { groupID?.let { getMessageText(context?.getString(R.string.group_message_reply))?.let { message -> - socialRepository.postGroupChat(it, message).subscribe( - { - context?.let { c -> - NotificationManagerCompat.from(c).cancel(it.hashCode()) - } - }, - ExceptionHandler.rx() - ) + MainScope().launchCatching { + socialRepository.postGroupChat(it, message) + context?.let { c -> + NotificationManagerCompat.from(c).cancel(it.hashCode()) + } + } } } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/receivers/NotificationPublisher.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/receivers/NotificationPublisher.kt index dd0dac59a..979a5323e 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/receivers/NotificationPublisher.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/receivers/NotificationPublisher.kt @@ -15,13 +15,12 @@ import com.habitrpg.android.habitica.R import com.habitrpg.android.habitica.data.TaskRepository import com.habitrpg.android.habitica.data.UserRepository import com.habitrpg.android.habitica.extensions.withImmutableFlag -import com.habitrpg.android.habitica.helpers.ExceptionHandler import com.habitrpg.android.habitica.helpers.TaskAlarmManager -import com.habitrpg.android.habitica.models.tasks.Task -import com.habitrpg.android.habitica.models.user.User +import com.habitrpg.android.habitica.helpers.launchCatching import com.habitrpg.android.habitica.ui.activities.MainActivity import com.habitrpg.shared.habitica.models.tasks.TaskType -import io.reactivex.rxjava3.functions.BiFunction +import kotlinx.coroutines.MainScope +import kotlinx.coroutines.flow.firstOrNull import java.util.Calendar import java.util.Date import java.util.Random @@ -57,26 +56,20 @@ class NotificationPublisher : BroadcastReceiver() { } val checkDailies = intent.getBooleanExtra(CHECK_DAILIES, false) if (checkDailies) { - taskRepository.getTasksFlowable(TaskType.DAILY, null, emptyArray()).firstElement().zipWith( - userRepository.getUserFlowable().firstElement(), - BiFunction, User, Pair, User>> { tasks, user -> - return@BiFunction Pair(tasks, user) + MainScope().launchCatching { + val tasks = taskRepository.getTasks(TaskType.DAILY, null, emptyArray()).firstOrNull() + val user = userRepository.getUser().firstOrNull() + var showNotifications = false + for (task in tasks ?: emptyList()) { + if (task.checkIfDue()) { + showNotifications = true + break + } } - ).subscribe( - { pair -> - var showNotifications = false - for (task in pair.first) { - if (task.checkIfDue()) { - showNotifications = true - break - } - } - if (showNotifications) { - notify(intent, buildNotification(wasInactive, pair.second.authentication?.timestamps?.createdAt)) - } - }, - ExceptionHandler.rx() - ) + if (showNotifications) { + notify(intent, buildNotification(wasInactive, user?.authentication?.timestamps?.createdAt)) + } + } } else { notify(intent, buildNotification(wasInactive)) } 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 f18b327f0..b1a973642 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,6 +18,7 @@ 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.android.habitica.helpers.launchCatching 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 @@ -25,6 +26,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.MainScope import kotlinx.coroutines.launch import java.util.Locale import javax.inject.Inject @@ -115,7 +117,9 @@ class ArmoireActivity : BaseActivity() { finish() } binding.equipButton.setOnClickListener { - equipmentKey?.let { it1 -> inventoryRepository.equip("equipped", it1).subscribe({}, ExceptionHandler.rx()) } + equipmentKey?.let { it1 -> + MainScope().launchCatching { inventoryRepository.equip("equipped", it1) } + } finish() } binding.dropRateButton.setOnClickListener { diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/BaseActivity.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/BaseActivity.kt index 0e63b948f..8f112448c 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/BaseActivity.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/BaseActivity.kt @@ -29,6 +29,7 @@ import com.habitrpg.android.habitica.data.UserRepository import com.habitrpg.android.habitica.extensions.updateStatusBarColor import com.habitrpg.android.habitica.helpers.ExceptionHandler import com.habitrpg.android.habitica.helpers.NotificationsManager +import com.habitrpg.android.habitica.helpers.launchCatching import com.habitrpg.android.habitica.interactors.ShowNotificationInteractor import com.habitrpg.android.habitica.proxy.AnalyticsManager import com.habitrpg.android.habitica.ui.helpers.ToolbarColorHelper @@ -36,7 +37,6 @@ import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog import com.habitrpg.common.habitica.extensions.getThemeColor import com.habitrpg.common.habitica.extensions.isUsingNightModeResources import com.habitrpg.common.habitica.helpers.LanguageHelper -import io.reactivex.rxjava3.disposables.CompositeDisposable import kotlinx.coroutines.launch import java.util.Date import java.util.Locale @@ -66,8 +66,6 @@ abstract class BaseActivity : AppCompatActivity() { return (getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater).inflate(layoutResId ?: 0, null) } - var compositeSubscription = CompositeDisposable() - private val habiticaApplication: HabiticaApplication get() = application as HabiticaApplication @@ -95,17 +93,15 @@ abstract class BaseActivity : AppCompatActivity() { getLayoutResId()?.let { setContentView(getContentView(it)) } - compositeSubscription = CompositeDisposable() - compositeSubscription.add(notificationsManager.displayNotificationEvents.subscribe( - { - if (ShowNotificationInteractor(this, lifecycleScope).handleNotification(it)) { - lifecycleScope.launch(ExceptionHandler.coroutine()) { - userRepository.retrieveUser(false, true) + lifecycleScope.launchCatching { + notificationsManager.displayNotificationEvents.collect { + if (ShowNotificationInteractor(this@BaseActivity, lifecycleScope).handleNotification(it)) { + lifecycleScope.launch(ExceptionHandler.coroutine()) { + userRepository.retrieveUser(false, true) + } } } - }, - ExceptionHandler.rx() - )) + } } override fun onRestart() { @@ -212,10 +208,6 @@ abstract class BaseActivity : AppCompatActivity() { override fun onDestroy() { destroyed = true - - if (!compositeSubscription.isDisposed) { - compositeSubscription.dispose() - } super.onDestroy() } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/ChallengeFormActivity.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/ChallengeFormActivity.kt index 9e56e29c7..64e6fecf1 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/ChallengeFormActivity.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/ChallengeFormActivity.kt @@ -25,6 +25,7 @@ import com.habitrpg.android.habitica.databinding.ActivityCreateChallengeBinding import com.habitrpg.android.habitica.extensions.addCloseButton 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.social.Challenge import com.habitrpg.android.habitica.models.social.Group import com.habitrpg.android.habitica.models.tasks.Task @@ -38,7 +39,6 @@ import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaProgressDialog import com.habitrpg.common.habitica.extensions.getThemeColor import com.habitrpg.shared.habitica.models.tasks.TaskType -import io.reactivex.rxjava3.core.Flowable import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.flow.firstOrNull @@ -52,8 +52,10 @@ class ChallengeFormActivity : BaseActivity() { @Inject internal lateinit var challengeRepository: ChallengeRepository + @Inject internal lateinit var socialRepository: SocialRepository + @Inject internal lateinit var userViewModel: MainUserViewModel @@ -69,6 +71,7 @@ class ChallengeFormActivity : BaseActivity() { private val removedTasks = HashMap() override var overrideModernHeader: Boolean? = true + // Add {*} Items private lateinit var addHabit: Task private lateinit var addDaily: Task @@ -130,37 +133,34 @@ class ChallengeFormActivity : BaseActivity() { savingInProgress = true val dialog = HabiticaProgressDialog.show(this, R.string.saving) - val observable: Flowable = if (editMode) { - updateChallenge() - } else { - createChallenge() - } + lifecycleScope.launchCatching({ + dialog?.dismiss() + savingInProgress = false + ExceptionHandler.reportError(it) + }) { + val challenge = if (editMode) { + updateChallenge() + } else { + createChallenge() + } - compositeSubscription.add( - observable - .flatMap { - challengeId = it.id - challengeRepository.retrieveChallenges(0, true) + challengeId = challenge?.id + challengeRepository.retrieveChallenges(0, true) + + dialog?.dismiss() + savingInProgress = false + finish() + if (!editMode) { + lifecycleScope.launch(context = Dispatchers.Main) { + delay(500L) + MainNavigationController.navigate( + ChallengesOverviewFragmentDirections.openChallengeDetail( + challengeId ?: "" + ) + ) } - .subscribe( - { - dialog?.dismiss() - savingInProgress = false - finish() - if (!editMode) { - lifecycleScope.launch(context = Dispatchers.Main) { - delay(500L) - MainNavigationController.navigate(ChallengesOverviewFragmentDirections.openChallengeDetail(challengeId ?: "")) - } - } - }, - { throwable -> - dialog?.dismiss() - savingInProgress = false - ExceptionHandler.reportError(throwable) - } - ) - ) + } + } } else if (item.itemId == android.R.id.home) { finish() return true @@ -229,13 +229,11 @@ class ChallengeFormActivity : BaseActivity() { openTaskDisabled = false, taskActionsDisabled = true ).also { challengeTasks = it } - compositeSubscription.add( - challengeTasks.taskOpenEvents.subscribe { - if (it.isValid) { - openNewTaskActivity(it.type, it) - } + challengeTasks.onTaskOpen = { + if (it.isValid) { + openNewTaskActivity(it.type, it) } - ) + } locationAdapter = GroupArrayAdapter(this) if (bundle != null) { @@ -339,7 +337,8 @@ class ChallengeFormActivity : BaseActivity() { locationAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item) lifecycleScope.launch(ExceptionHandler.coroutine()) { - val groups = socialRepository.getUserGroups("guild").firstOrNull()?.toMutableList() ?: return@launch + val groups = socialRepository.getUserGroups("guild").firstOrNull()?.toMutableList() + ?: return@launch val partyID = userRepository.getUser().firstOrNull()?.party?.id val party = if (partyID?.isNotBlank() == true) { socialRepository.retrieveGroup(partyID) @@ -360,13 +359,20 @@ class ChallengeFormActivity : BaseActivity() { } binding.challengeLocationSpinner.adapter = locationAdapter - binding.challengeLocationSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener { - override fun onItemSelected(adapterView: AdapterView<*>, view: View?, i: Int, l: Long) { - checkPrizeAndMinimumForTavern() - } + binding.challengeLocationSpinner.onItemSelectedListener = + object : AdapterView.OnItemSelectedListener { + override fun onItemSelected( + adapterView: AdapterView<*>, + view: View?, + i: Int, + l: Long + ) { + checkPrizeAndMinimumForTavern() + } - override fun onNothingSelected(adapterView: AdapterView<*>) { /* no-on */ } - } + override fun onNothingSelected(adapterView: AdapterView<*>) { /* no-on */ + } + } binding.createChallengePrize.setOnKeyListener { _, _, _ -> checkPrizeAndMinimumForTavern() @@ -380,21 +386,17 @@ class ChallengeFormActivity : BaseActivity() { taskList.add(addReward) challengeTasks.setTasks(taskList) - compositeSubscription.add( - challengeTasks.addItemObservable().subscribe( - { t -> - when (t.text) { - addHabit.text -> openNewTaskActivity(TaskType.HABIT, null) - addDaily.text -> openNewTaskActivity(TaskType.DAILY, null) - addTodo.text -> openNewTaskActivity(TaskType.TODO, null) - addReward.text -> openNewTaskActivity(TaskType.REWARD, null) - } - }, - ExceptionHandler.rx() - ) - ) + challengeTasks.onTaskOpen = { t -> + when (t.text) { + addHabit.text -> openNewTaskActivity(TaskType.HABIT, null) + addDaily.text -> openNewTaskActivity(TaskType.DAILY, null) + addTodo.text -> openNewTaskActivity(TaskType.TODO, null) + addReward.text -> openNewTaskActivity(TaskType.REWARD, null) + } + } - binding.createChallengeTaskList.addOnItemTouchListener(object : androidx.recyclerview.widget.RecyclerView.SimpleOnItemTouchListener() { + binding.createChallengeTaskList.addOnItemTouchListener(object : + androidx.recyclerview.widget.RecyclerView.SimpleOnItemTouchListener() { override fun onInterceptTouchEvent( rv: androidx.recyclerview.widget.RecyclerView, e: MotionEvent @@ -404,13 +406,14 @@ class ChallengeFormActivity : BaseActivity() { } }) binding.createChallengeTaskList.adapter = challengeTasks - binding.createChallengeTaskList.layoutManager = androidx.recyclerview.widget.LinearLayoutManager(this) + binding.createChallengeTaskList.layoutManager = + androidx.recyclerview.widget.LinearLayoutManager(this) } private fun fillControlsByChallenge() { challengeId?.let { - challengeRepository.getChallenge(it).subscribe( - { challenge -> + lifecycleScope.launchCatching { + challengeRepository.getChallenge(it).collect { challenge -> groupID = challenge.groupId editMode = true binding.createChallengeTitle.setText(challenge.name) @@ -428,17 +431,15 @@ class ChallengeFormActivity : BaseActivity() { } } checkPrizeAndMinimumForTavern() - }, - ExceptionHandler.rx() - ) - challengeRepository.getChallengeTasks(it).subscribe( - { tasks -> + } + } + lifecycleScope.launchCatching { + challengeRepository.getChallengeTasks(it).collect { tasks -> tasks.forEach { task -> addOrUpdateTaskInList(task, true) } - }, - ExceptionHandler.rx() - ) + } + } } } @@ -460,16 +461,17 @@ class ChallengeFormActivity : BaseActivity() { newTaskResult.launch(intent) } - private val newTaskResult = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { - if (it.resultCode == Activity.RESULT_OK) { - val task = it.data?.getParcelableExtra(TaskFormActivity.PARCELABLE_TASK) - if (task != null) { - addOrUpdateTaskInList(task) + private val newTaskResult = + registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { + if (it.resultCode == Activity.RESULT_OK) { + val task = it.data?.getParcelableExtra(TaskFormActivity.PARCELABLE_TASK) + if (task != null) { + addOrUpdateTaskInList(task) + } } } - } - private fun createChallenge(): Flowable { + private suspend fun createChallenge(): Challenge? { val c = challengeData val taskList = challengeTasks.taskList @@ -481,7 +483,7 @@ class ChallengeFormActivity : BaseActivity() { return challengeRepository.createChallenge(c, taskList) } - private fun updateChallenge(): Flowable { + private suspend fun updateChallenge(): Challenge? { val c = challengeData val taskList = challengeTasks.taskList @@ -528,7 +530,8 @@ class ChallengeFormActivity : BaseActivity() { return editText.text.toString() } - private class GroupArrayAdapter(context: Context) : ArrayAdapter(context, android.R.layout.simple_spinner_item) { + private class GroupArrayAdapter(context: Context) : + ArrayAdapter(context, android.R.layout.simple_spinner_item) { override fun getView(position: Int, convertView: View?, parent: ViewGroup): View { val checkedTextView = super.getView(position, convertView, parent) as? TextView @@ -537,7 +540,8 @@ class ChallengeFormActivity : BaseActivity() { } override fun getDropDownView(position: Int, convertView: View?, parent: ViewGroup): View { - val checkedTextView = super.getDropDownView(position, convertView, parent) as? AppCompatCheckedTextView + val checkedTextView = + super.getDropDownView(position, convertView, parent) as? AppCompatCheckedTextView checkedTextView?.text = getItem(position)?.name return checkedTextView ?: View(context) } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/FullProfileActivity.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/FullProfileActivity.kt index 4b598252a..7b1adc430 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/FullProfileActivity.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/FullProfileActivity.kt @@ -24,6 +24,7 @@ import com.habitrpg.android.habitica.extensions.addCancelButton import com.habitrpg.android.habitica.helpers.ExceptionHandler import com.habitrpg.android.habitica.helpers.MainNavigationController import com.habitrpg.android.habitica.helpers.UserStatComputer +import com.habitrpg.android.habitica.helpers.launchCatching import com.habitrpg.android.habitica.models.Achievement import com.habitrpg.android.habitica.models.inventory.Equipment import com.habitrpg.android.habitica.models.members.Member @@ -39,10 +40,10 @@ import com.habitrpg.common.habitica.extensions.loadImage import com.habitrpg.common.habitica.helpers.RecyclerViewState import com.habitrpg.common.habitica.helpers.setMarkdown import com.habitrpg.common.habitica.views.PixelArtView -import io.reactivex.rxjava3.core.Flowable import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.MainScope import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.launch import java.text.SimpleDateFormat import javax.inject.Inject @@ -54,8 +55,10 @@ class FullProfileActivity : BaseActivity() { @Inject lateinit var inventoryRepository: InventoryRepository + @Inject lateinit var apiClient: ApiClient + @Inject lateinit var socialRepository: SocialRepository @@ -91,7 +94,12 @@ class FullProfileActivity : BaseActivity() { } avatarWithBars = AvatarWithBarsViewModel(this, binding.avatarWithBars) - binding.avatarWithBars.root.setBackgroundColor(ContextCompat.getColor(this, R.color.transparent)) + binding.avatarWithBars.root.setBackgroundColor( + ContextCompat.getColor( + this, + R.color.transparent + ) + ) binding.avatarWithBars.hpBar.barBackgroundColor = getThemeColor(R.color.window_background) binding.avatarWithBars.xpBar.barBackgroundColor = getThemeColor(R.color.window_background) binding.avatarWithBars.mpBar.barBackgroundColor = getThemeColor(R.color.window_background) @@ -100,13 +108,24 @@ class FullProfileActivity : BaseActivity() { binding.attributesCardView.setOnClickListener { toggleAttributeDetails() } binding.sendMessageButton.setOnClickListener { showSendMessageToUserDialog() } - binding.giftGemsButton.setOnClickListener { MainNavigationController.navigate(R.id.giftGemsActivity, bundleOf(Pair("userID", userID), Pair("username", null))) } - binding.giftSubscriptionButton.setOnClickListener { MainNavigationController.navigate(R.id.giftSubscriptionActivity, bundleOf(Pair("userID", userID), Pair("username", null))) } + binding.giftGemsButton.setOnClickListener { + MainNavigationController.navigate( + R.id.giftGemsActivity, + bundleOf(Pair("userID", userID), Pair("username", null)) + ) + } + binding.giftSubscriptionButton.setOnClickListener { + MainNavigationController.navigate( + R.id.giftSubscriptionActivity, + bundleOf(Pair("userID", userID), Pair("username", null)) + ) + } lifecycleScope.launch(ExceptionHandler.coroutine()) { userRepository.getUser() .collect { blocks = it?.inbox?.blocks ?: listOf() - binding.blockedDisclaimerView.visibility = if (isUserBlocked()) View.VISIBLE else View.GONE + binding.blockedDisclaimerView.visibility = + if (isUserBlocked()) View.VISIBLE else View.GONE } } } @@ -139,22 +158,26 @@ class FullProfileActivity : BaseActivity() { true } R.id.copy_username -> { - val clipboard = this.getSystemService(Context.CLIPBOARD_SERVICE) as? android.content.ClipboardManager + val clipboard = + this.getSystemService(Context.CLIPBOARD_SERVICE) as? android.content.ClipboardManager val clip = android.content.ClipData.newPlainText(username, username) clipboard?.setPrimaryClip(clip) HabiticaSnackbar.showSnackbar( this@FullProfileActivity.binding.scrollView.getChildAt(0) as ViewGroup, - String.format(getString(R.string.username_copied), userDisplayName), SnackbarDisplayType.NORMAL + String.format(getString(R.string.username_copied), userDisplayName), + SnackbarDisplayType.NORMAL ) true } R.id.copy_userid -> { - val clipboard = this.getSystemService(Context.CLIPBOARD_SERVICE) as? android.content.ClipboardManager + val clipboard = + this.getSystemService(Context.CLIPBOARD_SERVICE) as? android.content.ClipboardManager val clip = android.content.ClipData.newPlainText(userID, userID) clipboard?.setPrimaryClip(clip) HabiticaSnackbar.showSnackbar( this@FullProfileActivity.binding.scrollView.getChildAt(0) as ViewGroup, - String.format(getString(R.string.id_copied), userDisplayName), SnackbarDisplayType.NORMAL + String.format(getString(R.string.id_copied), userDisplayName), + SnackbarDisplayType.NORMAL ) true } @@ -171,17 +194,11 @@ class FullProfileActivity : BaseActivity() { } private fun useBlock() { - compositeSubscription.add( - socialRepository.blockMember(userID).subscribe( - { - lifecycleScope.launch(ExceptionHandler.coroutine()) { - userRepository.retrieveUser() - invalidateOptionsMenu() - } - }, - ExceptionHandler.rx() - ) - ) + lifecycleScope.launchCatching { + socialRepository.blockMember(userID) + userRepository.retrieveUser() + invalidateOptionsMenu() + } } private fun showBlockDialog() { @@ -199,7 +216,10 @@ class FullProfileActivity : BaseActivity() { finish() MainScope().launch(context = Dispatchers.Main) { delay(500L) - MainNavigationController.navigate(R.id.inboxMessageListFragment, bundleOf(Pair("username", username), Pair("userID", userID))) + MainNavigationController.navigate( + R.id.inboxMessageListFragment, + bundleOf(Pair("username", username), Pair("userID", userID)) + ) } } @@ -226,22 +246,33 @@ class FullProfileActivity : BaseActivity() { binding.blurbTextView.movementMethod = LinkMovementMethod.getInstance() } - user.authentication?.timestamps?.createdAt?.let { binding.joinedView.text = dateFormatter.format(it) } - user.authentication?.timestamps?.lastLoggedIn?.let { binding.lastLoginView.text = dateFormatter.format(it) } + user.authentication?.timestamps?.createdAt?.let { + binding.joinedView.text = dateFormatter.format(it) + } + user.authentication?.timestamps?.lastLoggedIn?.let { + binding.lastLoginView.text = dateFormatter.format(it) + } binding.totalCheckinsView.text = user.loginIncentives.toString() avatarWithBars?.updateData(user) - compositeSubscription.add(loadItemDataByOutfit(user.equipped).subscribe({ gear -> this.gotGear(gear, user) }, ExceptionHandler.rx())) + lifecycleScope.launchCatching { + loadItemDataByOutfit(user.equipped).collect { gear -> gotGear(gear, user) } + } if (user.preferences?.costume == true) { - compositeSubscription.add(loadItemDataByOutfit(user.costume).subscribe({ this.gotCostume(it) }, ExceptionHandler.rx())) + lifecycleScope.launchCatching { + loadItemDataByOutfit(user.costume).collect { gotCostume(it) } + } } else { binding.costumeCard.visibility = View.GONE } // Load the members achievements now - compositeSubscription.add(socialRepository.getMemberAchievements(this.userID).subscribe({ this.fillAchievements(it) }, ExceptionHandler.rx())) + lifecycleScope.launchCatching { + val achievements = socialRepository.getMemberAchievements(userID) + fillAchievements(achievements) + } } private fun updatePetsMountsView(user: Member) { @@ -252,9 +283,9 @@ class FullProfileActivity : BaseActivity() { if (user.currentMount?.isNotBlank() == true) binding.currentMountDrawee.loadImage("Mount_Icon_" + user.currentMount) } - // endregion +// endregion - // region Attributes +// region Attributes private fun fillAchievements(achievements: List?) { if (achievements == null) { @@ -262,23 +293,36 @@ class FullProfileActivity : BaseActivity() { } val items = ArrayList() - fillAchievements(R.string.basic_achievements, achievements.filter { it.category == "basic" }, items) - fillAchievements(R.string.seasonal_achievements, achievements.filter { it.category == "seasonal" }, items) - fillAchievements(R.string.special_achievements, achievements.filter { it.category == "special" }, items) + fillAchievements( + R.string.basic_achievements, + achievements.filter { it.category == "basic" }, + items + ) + fillAchievements( + R.string.seasonal_achievements, + achievements.filter { it.category == "seasonal" }, + items + ) + fillAchievements( + R.string.special_achievements, + achievements.filter { it.category == "special" }, + items + ) val adapter = AchievementProfileAdapter() adapter.setItemList(items) val layoutManager = androidx.recyclerview.widget.GridLayoutManager(this, 3) - layoutManager.spanSizeLookup = object : androidx.recyclerview.widget.GridLayoutManager.SpanSizeLookup() { - override fun getSpanSize(position: Int): Int { - return if (adapter.getItemViewType(position) == 0) { - layoutManager.spanCount - } else { - 1 + layoutManager.spanSizeLookup = + object : androidx.recyclerview.widget.GridLayoutManager.SpanSizeLookup() { + override fun getSpanSize(position: Int): Int { + return if (adapter.getItemViewType(position) == 0) { + layoutManager.spanCount + } else { + 1 + } } } - } binding.achievementGroupList.layoutManager = layoutManager binding.achievementGroupList.adapter = adapter @@ -292,7 +336,9 @@ class FullProfileActivity : BaseActivity() { ) { // Order by ID first val achievementList = ArrayList(achievements) - achievementList.sortWith { achievement, t1 -> achievement.index.toDouble().compareTo(t1.index.toDouble()) } + achievementList.sortWith { achievement, t1 -> + achievement.index.toDouble().compareTo(t1.index.toDouble()) + } targetList.add(getString(labelID)) targetList.addAll(achievementList) @@ -318,8 +364,14 @@ class FullProfileActivity : BaseActivity() { } } - private fun addEquipmentRow(table: TableLayout, gearKey: String?, text: String?, stats: String?) { - val gearRow = layoutInflater.inflate(R.layout.profile_gear_tablerow, table, false) as? TableRow + private fun addEquipmentRow( + table: TableLayout, + gearKey: String?, + text: String?, + stats: String? + ) { + val gearRow = + layoutInflater.inflate(R.layout.profile_gear_tablerow, table, false) as? TableRow val draweeView = gearRow?.findViewById(R.id.gear_drawee) @@ -348,7 +400,7 @@ class FullProfileActivity : BaseActivity() { ) } - private fun loadItemDataByOutfit(outfit: Outfit?): Flowable> { + private fun loadItemDataByOutfit(outfit: Outfit?): Flow> { val outfitList = ArrayList() if (outfit != null) { outfitList.add(outfit.armor) @@ -381,7 +433,15 @@ class FullProfileActivity : BaseActivity() { if (row is UserStatComputer.EquipmentRow) { addEquipmentRow(binding.equipmentTableLayout, row.gearKey, row.text, row.stats) } else if (row is UserStatComputer.AttributeRow) { - addAttributeRow(getString(row.labelId), row.strVal, row.intVal, row.conVal, row.perVal, row.roundDown, row.summary) + addAttributeRow( + getString(row.labelId), + row.strVal, + row.intVal, + row.conVal, + row.perVal, + row.roundDown, + row.summary + ) } } @@ -400,7 +460,11 @@ class FullProfileActivity : BaseActivity() { val buffs = stats.buffs addAttributeRow( - getString(R.string.profile_allocated), stats.strength?.toFloat() ?: 0f, stats.intelligence?.toFloat() ?: 0f, stats.constitution?.toFloat() ?: 0f, stats.per?.toFloat() ?: 0f, + getString(R.string.profile_allocated), + stats.strength?.toFloat() ?: 0f, + stats.intelligence?.toFloat() ?: 0f, + stats.constitution?.toFloat() ?: 0f, + stats.per?.toFloat() ?: 0f, roundDown = true, isSummary = false ) @@ -408,11 +472,23 @@ class FullProfileActivity : BaseActivity() { getString(R.string.buffs), buffs?.str ?: 0f, - buffs?._int ?: 0f, buffs?.con ?: 0f, buffs?.per ?: 0f, roundDown = true, isSummary = false + buffs?._int ?: 0f, + buffs?.con ?: 0f, + buffs?.per ?: 0f, + roundDown = true, + isSummary = false ) // Summary row - addAttributeRow("", attributeStrSum, attributeIntSum, attributeConSum, attributePerSum, roundDown = false, isSummary = true) + addAttributeRow( + "", + attributeStrSum, + attributeIntSum, + attributeConSum, + attributePerSum, + roundDown = false, + isSummary = true + ) } private fun addAttributeRow( @@ -424,7 +500,11 @@ class FullProfileActivity : BaseActivity() { roundDown: Boolean, isSummary: Boolean ) { - val tableRow = layoutInflater.inflate(R.layout.profile_attributetablerow, binding.attributesTableLayout, false) as? TableRow ?: return + val tableRow = layoutInflater.inflate( + R.layout.profile_attributetablerow, + binding.attributesTableLayout, + false + ) as? TableRow ?: return val keyTextView = tableRow.findViewById(R.id.tv_attribute_type) keyTextView?.text = label @@ -475,9 +555,9 @@ class FullProfileActivity : BaseActivity() { } } - // endregion +// endregion - // region Navigation +// region Navigation override fun onSupportNavigateUp(): Boolean { finish() @@ -488,9 +568,9 @@ class FullProfileActivity : BaseActivity() { finish() } - // endregion +// endregion - // region BaseActivity-Overrides +// region BaseActivity-Overrides override fun getLayoutResId(): Int { return R.layout.activity_full_profile @@ -517,5 +597,5 @@ class FullProfileActivity : BaseActivity() { } } - // endregion +// endregion } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/HabitButtonWidgetActivity.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/HabitButtonWidgetActivity.kt index 1b7c805a8..10f111e5f 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/HabitButtonWidgetActivity.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/HabitButtonWidgetActivity.kt @@ -75,10 +75,9 @@ class HabitButtonWidgetActivity : BaseActivity() { } adapter = SkillTasksRecyclerViewAdapter() - adapter?.getTaskSelectionEvents()?.subscribe( - { task -> taskSelected(task.id) }, - ExceptionHandler.rx() - )?.let { compositeSubscription.add(it) } + adapter?.onTaskSelection = { + taskSelected(it.id) + } binding.recyclerView.adapter = adapter CoroutineScope(Dispatchers.Main + job).launch(ExceptionHandler.coroutine()) { diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/LoginActivity.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/LoginActivity.kt index e2ec696a9..5d4e59bd3 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/LoginActivity.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/LoginActivity.kt @@ -42,6 +42,7 @@ import com.habitrpg.android.habitica.extensions.updateStatusBarColor import com.habitrpg.android.habitica.helpers.AmplitudeManager import com.habitrpg.android.habitica.helpers.AppConfigManager import com.habitrpg.android.habitica.helpers.ExceptionHandler +import com.habitrpg.android.habitica.helpers.launchCatching import com.habitrpg.android.habitica.models.user.User import com.habitrpg.android.habitica.ui.helpers.dismissKeyboard import com.habitrpg.android.habitica.ui.viewmodels.AuthenticationViewModel @@ -81,14 +82,15 @@ class LoginActivity : BaseActivity() { showValidationError(getString(R.string.password_too_short, configManager.minimumPasswordLength())) return@OnClickListener } - apiClient.registerUser(username, email, password, confirmPassword) - .subscribe( - { handleAuthResponse(it) }, - { - hideProgress() - ExceptionHandler.reportError(it) - } - ) + lifecycleScope.launch(ExceptionHandler.coroutine { + hideProgress() + ExceptionHandler.reportError(it) + }) { + val response = apiClient.registerUser(username, email, password, confirmPassword) + if (response != null) { + handleAuthResponse(response) + } + } } else { val username: String = binding.username.text.toString().trim { it <= ' ' } val password: String = binding.password.text.toString() @@ -97,14 +99,15 @@ class LoginActivity : BaseActivity() { return@OnClickListener } Log.d("LoginActivity", ": $username, $password") - apiClient.connectUser(username, password).subscribe( - { handleAuthResponse(it) }, - { - hideProgress() - ExceptionHandler.reportError(it) - Log.d("LoginActivity", ": ${it.message}", it) + lifecycleScope.launch(ExceptionHandler.coroutine { + hideProgress() + ExceptionHandler.reportError(it) + }) { + val response = apiClient.connectUser(username, password) + if (response != null) { + handleAuthResponse(response) } - ) + } } } @@ -450,7 +453,10 @@ class LoginActivity : BaseActivity() { alertDialog.setMessage(R.string.forgot_password_description) alertDialog.setAdditionalContentView(input) alertDialog.addButton(R.string.send, true) { _, _ -> - userRepository.sendPasswordResetEmail(input.text.toString()).subscribe({ showPasswordEmailConfirmation() }, ExceptionHandler.rx()) + lifecycleScope.launchCatching { + userRepository.sendPasswordResetEmail(input.text.toString()) + showPasswordEmailConfirmation() + } } alertDialog.addCancelButton() alertDialog.show() diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/MainActivity.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/MainActivity.kt index 64dcf69f2..96c564248 100755 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/MainActivity.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/MainActivity.kt @@ -22,7 +22,6 @@ import androidx.navigation.NavDestination import androidx.navigation.findNavController import androidx.navigation.fragment.NavHostFragment import com.google.android.gms.wearable.Wearable -import com.google.android.material.composethemeadapter.MdcTheme import com.google.firebase.perf.FirebasePerformance import com.habitrpg.android.habitica.BuildConfig import com.habitrpg.android.habitica.R @@ -33,7 +32,6 @@ import com.habitrpg.android.habitica.data.TaskRepository import com.habitrpg.android.habitica.databinding.ActivityMainBinding import com.habitrpg.android.habitica.extensions.hideKeyboard import com.habitrpg.android.habitica.extensions.observeOnce -import com.habitrpg.android.habitica.extensions.subscribeWithErrorHandler import com.habitrpg.android.habitica.extensions.updateStatusBarColor import com.habitrpg.android.habitica.helpers.AmplitudeManager import com.habitrpg.android.habitica.helpers.AppConfigManager @@ -41,6 +39,7 @@ import com.habitrpg.android.habitica.helpers.ExceptionHandler import com.habitrpg.android.habitica.helpers.MainNavigationController import com.habitrpg.android.habitica.helpers.NotificationOpenHandler import com.habitrpg.android.habitica.helpers.SoundManager +import com.habitrpg.android.habitica.helpers.launchCatching import com.habitrpg.android.habitica.interactors.CheckClassSelectionUseCase import com.habitrpg.android.habitica.interactors.DisplayItemDropUseCase import com.habitrpg.android.habitica.interactors.NotifyUserUseCase @@ -154,11 +153,11 @@ open class MainActivity : BaseActivity(), SnackbarActivity { viewModel.user.observe(this) { setUserData(it) } - compositeSubscription.add( - userRepository.getUserQuestStatus().subscribeWithErrorHandler { + lifecycleScope.launchCatching { + userRepository.getUserQuestStatus().collect { userQuestStatus = it } - ) + } val drawerLayout = findViewById(R.id.drawer_layout) drawerFragment = supportFragmentManager.findFragmentById(R.id.navigation_drawer) as? NavigationDrawerFragment @@ -383,7 +382,7 @@ open class MainActivity : BaseActivity(), SnackbarActivity { if (user != null) { val preferences = user.preferences - preferences?.language?.let { apiClient.setLanguageCode(it) } + preferences?.language?.let { apiClient.languageCode = it } if (preferences?.language != viewModel.preferenceLanguage) { viewModel.preferenceLanguage = preferences?.language } @@ -392,11 +391,6 @@ open class MainActivity : BaseActivity(), SnackbarActivity { displayDeathDialogIfNeeded() YesterdailyDialog.showDialogIfNeeded(this, user.id, userRepository, taskRepository) - if (user.flags?.verifiedUsername == false && isActivityVisible) { - val intent = Intent(this, VerifyUsernameActivity::class.java) - startActivity(intent) - } - val quest = user.party?.quest if (quest?.completed?.isNotBlank() == true) { lifecycleScope.launch(ExceptionHandler.coroutine()) { @@ -447,22 +441,35 @@ open class MainActivity : BaseActivity(), SnackbarActivity { UserQuestStatus.QUEST_BOSS -> data.questDamage else -> 0.0 } - compositeSubscription.add( - notifyUserUseCase.observable( + lifecycleScope.launchCatching { + notifyUserUseCase.callInteractor( NotifyUserUseCase.RequestValues( - this, snackbarContainer, - viewModel.user.value, data.experienceDelta, data.healthDelta, data.goldDelta, data.manaDelta, damageValue, data.hasLeveledUp, data.level + this@MainActivity, + snackbarContainer, + viewModel.user.value, + data.experienceDelta, + data.healthDelta, + data.goldDelta, + data.manaDelta, + damageValue, + data.hasLeveledUp, + data.level ) ) - .subscribe({ }, ExceptionHandler.rx()) - ) + } } val showItemsFound = userQuestStatus == UserQuestStatus.QUEST_COLLECT - compositeSubscription.add( - displayItemDropUseCase.observable(DisplayItemDropUseCase.RequestValues(data, this, snackbarContainer, showItemsFound)) - .subscribe({ }, ExceptionHandler.rx()) - ) + lifecycleScope.launchCatching { + displayItemDropUseCase.callInteractor( + DisplayItemDropUseCase.RequestValues( + data, + this@MainActivity, + snackbarContainer, + showItemsFound + ) + ) + } } private var lastDeathDialogDisplay = 0L diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/MaintenanceActivity.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/MaintenanceActivity.kt index 386d8cd5f..4dea75377 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/MaintenanceActivity.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/MaintenanceActivity.kt @@ -5,15 +5,14 @@ import android.os.Bundle import android.text.method.LinkMovementMethod import android.view.View import androidx.core.net.toUri +import androidx.lifecycle.lifecycleScope import com.habitrpg.android.habitica.R import com.habitrpg.android.habitica.api.MaintenanceApiService import com.habitrpg.android.habitica.components.UserComponent import com.habitrpg.android.habitica.data.ApiClient import com.habitrpg.android.habitica.databinding.ActivityMaintenanceBinding -import com.habitrpg.android.habitica.helpers.ExceptionHandler +import com.habitrpg.android.habitica.helpers.launchCatching import com.habitrpg.common.habitica.helpers.setMarkdown -import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers -import io.reactivex.rxjava3.schedulers.Schedulers import javax.inject.Inject class MaintenanceActivity : BaseActivity() { @@ -66,19 +65,12 @@ class MaintenanceActivity : BaseActivity() { override fun onResume() { super.onResume() if (!isDeprecationNotice) { - compositeSubscription.add( - this.maintenanceService.maintenanceStatus - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe( - { maintenanceResponse -> - if (maintenanceResponse.activeMaintenance == false) { - finish() - } - }, - ExceptionHandler.rx() - ) - ) + lifecycleScope.launchCatching { + val maintenanceResponse = maintenanceService.getMaintenanceStatus() + if (maintenanceResponse?.activeMaintenance == false) { + finish() + } + } } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/NotificationsActivity.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/NotificationsActivity.kt index 6ccfc37ea..a8fbfa936 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/NotificationsActivity.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/NotificationsActivity.kt @@ -21,6 +21,7 @@ import com.habitrpg.android.habitica.data.SocialRepository import com.habitrpg.android.habitica.databinding.ActivityNotificationsBinding import com.habitrpg.android.habitica.extensions.fromHtml import com.habitrpg.android.habitica.helpers.ExceptionHandler +import com.habitrpg.android.habitica.helpers.launchCatching import com.habitrpg.android.habitica.models.inventory.QuestContent import com.habitrpg.android.habitica.ui.viewmodels.NotificationsViewModel import com.habitrpg.common.habitica.models.Notification @@ -65,15 +66,12 @@ class NotificationsActivity : BaseActivity(), androidx.swiperefreshlayout.widget inflater = getSystemService(Context.LAYOUT_INFLATER_SERVICE) as? LayoutInflater - compositeSubscription.add( - viewModel.getNotifications().subscribe( - { - this.setNotifications(it) - viewModel.markNotificationsAsSeen(it) - }, - ExceptionHandler.rx() - ) - ) + lifecycleScope.launchCatching { + viewModel.getNotifications().collect { + setNotifications(it) + viewModel.markNotificationsAsSeen(it) + } + } binding.notificationsRefreshLayout.setOnRefreshListener(this) } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/ReportMessageActivity.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/ReportMessageActivity.kt index b08d214da..fbebe5d71 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/ReportMessageActivity.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/ReportMessageActivity.kt @@ -8,16 +8,18 @@ import android.os.Bundle import android.view.View import androidx.annotation.RequiresApi import androidx.core.content.ContextCompat +import androidx.lifecycle.lifecycleScope import androidx.navigation.navArgs import com.google.android.material.bottomsheet.BottomSheetBehavior import com.habitrpg.android.habitica.R import com.habitrpg.android.habitica.components.UserComponent import com.habitrpg.android.habitica.data.SocialRepository import com.habitrpg.android.habitica.databinding.ActivityReportMessageBinding -import com.habitrpg.common.habitica.extensions.getThemeColor import com.habitrpg.android.habitica.helpers.ExceptionHandler import com.habitrpg.android.habitica.ui.helpers.dismissKeyboard +import com.habitrpg.common.habitica.extensions.getThemeColor import com.habitrpg.common.habitica.helpers.setMarkdown +import kotlinx.coroutines.launch import javax.inject.Inject class ReportMessageActivity : BaseActivity() { @@ -94,12 +96,11 @@ class ReportMessageActivity : BaseActivity() { } isReporting = true messageID?.let { - socialRepository.flagMessage(it, binding.additionalInfoEdittext.text.toString(), groupID) - .doOnError { isReporting = false } - .subscribe( - { finish() }, - ExceptionHandler.rx() - ) + lifecycleScope.launch(ExceptionHandler.coroutine { + isReporting = false + }) { + finish() + } } } 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 ec94f2fc8..54e51ff75 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 @@ -25,6 +25,7 @@ import com.habitrpg.android.habitica.data.TaskRepository import com.habitrpg.android.habitica.databinding.ActivitySetupBinding import com.habitrpg.android.habitica.helpers.AmplitudeManager import com.habitrpg.android.habitica.helpers.ExceptionHandler +import com.habitrpg.android.habitica.helpers.launchCatching import com.habitrpg.android.habitica.models.user.User import com.habitrpg.android.habitica.ui.fragments.setup.AvatarSetupFragment import com.habitrpg.android.habitica.ui.fragments.setup.TaskSetupFragment @@ -84,10 +85,9 @@ class SetupActivity : BaseActivity(), ViewPager.OnPageChangeListener { val currentDeviceLanguage = Locale.getDefault().language for (language in resources.getStringArray(R.array.LanguageValues)) { if (language == currentDeviceLanguage) { - compositeSubscription.add( + lifecycleScope.launchCatching { apiClient.registrationLanguage(currentDeviceLanguage) - .subscribe({ }, ExceptionHandler.rx()) - ) + } } } @@ -141,7 +141,9 @@ class SetupActivity : BaseActivity(), ViewPager.OnPageChangeListener { this.completedSetup = true createdTasks = true newTasks?.let { - this.taskRepository.createTasks(it).subscribe({ onUserReceived(user) }, ExceptionHandler.rx()) + lifecycleScope.launchCatching { + taskRepository.createTasks(it) + } } } else if (binding.viewPager.currentItem == 0) { diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/SkillMemberActivity.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/SkillMemberActivity.kt index dc4a75519..1fe0effb4 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/SkillMemberActivity.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/SkillMemberActivity.kt @@ -11,6 +11,7 @@ import com.habitrpg.android.habitica.components.UserComponent import com.habitrpg.android.habitica.data.SocialRepository import com.habitrpg.android.habitica.databinding.ActivitySkillMembersBinding import com.habitrpg.android.habitica.helpers.ExceptionHandler +import com.habitrpg.android.habitica.helpers.launchCatching import com.habitrpg.android.habitica.ui.adapter.social.PartyMemberRecyclerViewAdapter import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel import kotlinx.coroutines.flow.filterNotNull @@ -50,15 +51,14 @@ class SkillMemberActivity : BaseActivity() { private fun loadMemberList() { binding.recyclerView.layoutManager = LinearLayoutManager(this) viewAdapter = PartyMemberRecyclerViewAdapter() - viewAdapter?.getUserClickedEvents()?.subscribe( - { userId -> + viewAdapter?.onUserClicked = { + lifecycleScope.launchCatching { val resultIntent = Intent() - resultIntent.putExtra("member_id", userId) + resultIntent.putExtra("member_id", it) setResult(Activity.RESULT_OK, resultIntent) finish() - }, - ExceptionHandler.rx() - )?.let { compositeSubscription.add(it) } + } + } binding.recyclerView.adapter = viewAdapter lifecycleScope.launch(ExceptionHandler.coroutine()) { diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/SkillTasksActivity.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/SkillTasksActivity.kt index 10c8a9062..caa7afae3 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/SkillTasksActivity.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/SkillTasksActivity.kt @@ -13,11 +13,10 @@ import com.habitrpg.android.habitica.R import com.habitrpg.android.habitica.components.UserComponent import com.habitrpg.android.habitica.data.TaskRepository import com.habitrpg.android.habitica.databinding.ActivitySkillTasksBinding -import com.habitrpg.android.habitica.helpers.ExceptionHandler import com.habitrpg.android.habitica.models.tasks.Task -import com.habitrpg.shared.habitica.models.tasks.TaskType import com.habitrpg.android.habitica.modules.AppModule import com.habitrpg.android.habitica.ui.fragments.skills.SkillTasksRecyclerViewFragment +import com.habitrpg.shared.habitica.models.tasks.TaskType import javax.inject.Inject import javax.inject.Named @@ -60,7 +59,9 @@ class SkillTasksActivity : BaseActivity() { 1 -> TaskType.DAILY else -> TaskType.TODO } - compositeSubscription.add(fragment.getTaskSelectionEvents().subscribe({ task -> taskSelected(task) }, ExceptionHandler.rx())) + fragment.onTaskSelection = { + taskSelected(it) + } viewFragmentsDictionary.put(position, fragment) return fragment } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/TaskFormActivity.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/TaskFormActivity.kt index a00a30eeb..44f672935 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/TaskFormActivity.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/TaskFormActivity.kt @@ -6,6 +6,7 @@ import android.content.SharedPreferences import android.content.res.ColorStateList import android.graphics.Typeface import android.graphics.drawable.ColorDrawable +import android.os.Build import android.os.Bundle import android.os.Handler import android.view.Menu @@ -33,6 +34,7 @@ import com.habitrpg.android.habitica.extensions.OnChangeTextWatcher import com.habitrpg.android.habitica.extensions.addCancelButton import com.habitrpg.android.habitica.helpers.ExceptionHandler import com.habitrpg.android.habitica.helpers.TaskAlarmManager +import com.habitrpg.android.habitica.helpers.launchCatching import com.habitrpg.android.habitica.models.Tag import com.habitrpg.android.habitica.models.social.Challenge import com.habitrpg.android.habitica.models.tasks.Task @@ -47,6 +49,7 @@ import com.habitrpg.shared.habitica.models.tasks.HabitResetOption import com.habitrpg.shared.habitica.models.tasks.TaskType import io.realm.RealmList import kotlinx.coroutines.flow.firstOrNull +import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch import java.util.Date import javax.inject.Inject @@ -56,16 +59,22 @@ class TaskFormActivity : BaseActivity() { private lateinit var binding: ActivityTaskFormBinding private var userScrolled: Boolean = false private var isSaving: Boolean = false + @Inject lateinit var taskRepository: TaskRepository + @Inject lateinit var tagRepository: TagRepository + @Inject lateinit var taskAlarmManager: TaskAlarmManager + @Inject lateinit var challengeRepository: ChallengeRepository + @Inject lateinit var sharedPreferences: SharedPreferences + @Inject lateinit var userViewModel: MainUserViewModel @@ -135,18 +144,23 @@ class TaskFormActivity : BaseActivity() { super.onCreate(savedInstanceState) if (forcedTheme == "yellow") { - binding.taskDifficultyButtons.textTintColor = ContextCompat.getColor(this, R.color.text_yellow) - binding.habitScoringButtons.textTintColor = ContextCompat.getColor(this, R.color.text_yellow) + binding.taskDifficultyButtons.textTintColor = + ContextCompat.getColor(this, R.color.text_yellow) + binding.habitScoringButtons.textTintColor = + ContextCompat.getColor(this, R.color.text_yellow) } else if (forcedTheme == "taskform") { - binding.taskDifficultyButtons.textTintColor = ContextCompat.getColor(this, R.color.text_brand_neon) - binding.habitScoringButtons.textTintColor = ContextCompat.getColor(this, R.color.text_brand_neon) + binding.taskDifficultyButtons.textTintColor = + ContextCompat.getColor(this, R.color.text_brand_neon) + binding.habitScoringButtons.textTintColor = + ContextCompat.getColor(this, R.color.text_brand_neon) } setSupportActionBar(binding.toolbar) supportActionBar?.setDisplayHomeAsUpEnabled(true) supportActionBar?.setDisplayShowHomeEnabled(true) tintColor = getThemeColor(R.attr.taskFormTint) - val upperTintColor = if (forcedTheme == "taskform") getThemeColor(R.attr.taskFormTint) else getThemeColor(R.attr.colorAccent) + val upperTintColor = + if (forcedTheme == "taskform") getThemeColor(R.attr.taskFormTint) else getThemeColor(R.attr.colorAccent) supportActionBar?.setBackgroundDrawable(ColorDrawable(upperTintColor)) binding.upperTextWrapper.setBackgroundColor(upperTintColor) @@ -155,19 +169,17 @@ class TaskFormActivity : BaseActivity() { taskType = TaskType.from(bundle.getString(TASK_TYPE_KEY)) ?: TaskType.HABIT preselectedTags = bundle.getStringArrayList(SELECTED_TAGS_KEY) - compositeSubscription.add( + lifecycleScope.launchCatching { tagRepository.getTags() .map { tagRepository.getUnmanagedCopy(it) } - .subscribe( - { - tags = it - setTagViews() - }, - ExceptionHandler.rx() - ) - ) + .collect { + tags = it + setTagViews() + } + } userViewModel.user.observe(this) { - usesTaskAttributeStats = it?.preferences?.allocationMode == "taskbased" && it.preferences?.automaticAllocation == true + usesTaskAttributeStats = + it?.preferences?.allocationMode == "taskbased" && it.preferences?.automaticAllocation == true configureForm() } @@ -188,7 +200,8 @@ class TaskFormActivity : BaseActivity() { binding.statConstitutionButton.setOnClickListener { selectedStat = Attribute.CONSTITUTION } binding.statPerceptionButton.setOnClickListener { selectedStat = Attribute.PERCEPTION } binding.scrollView.setOnTouchListener { view, event -> - userScrolled = view == binding.scrollView && (event.action == MotionEvent.ACTION_SCROLL || event.action == MotionEvent.ACTION_MOVE) + userScrolled = + view == binding.scrollView && (event.action == MotionEvent.ACTION_SCROLL || event.action == MotionEvent.ACTION_MOVE) return@setOnTouchListener false } binding.scrollView.setOnScrollChangeListener { _: NestedScrollView?, _: Int, _: Int, _: Int, _: Int -> @@ -202,31 +215,33 @@ class TaskFormActivity : BaseActivity() { taskId != null -> { isCreating = false lifecycleScope.launch(ExceptionHandler.coroutine()) { - val task = taskRepository.getUnmanagedTask(taskId).firstOrNull() ?: return@launch + val task = + taskRepository.getUnmanagedTask(taskId).firstOrNull() ?: return@launch if (!task.isValid) return@launch this@TaskFormActivity.task = task initialTaskInstance = task // tintColor = ContextCompat.getColor(this, it.mediumTaskColor) fillForm(task) task.challengeID?.let { challengeID -> - compositeSubscription.add( - challengeRepository.retrieveChallenge(challengeID) - .subscribe( - { challenge -> - this@TaskFormActivity.challenge = challenge - binding.challengeNameView.text = getString(R.string.challenge_task_name, challenge.name) - binding.challengeNameView.visibility = View.VISIBLE - disableEditingForUneditableFieldsInChallengeTask() - }, - ExceptionHandler.rx() - ) - ) + lifecycleScope.launchCatching { + val challenge = challengeRepository.retrieveChallenge(challengeID) + ?: return@launchCatching + this@TaskFormActivity.challenge = challenge + binding.challengeNameView.text = + getString(R.string.challenge_task_name, challenge.name) + binding.challengeNameView.visibility = View.VISIBLE + disableEditingForUneditableFieldsInChallengeTask() + } } } } bundle.containsKey(PARCELABLE_TASK) -> { isCreating = false - task = bundle.getParcelable(PARCELABLE_TASK, Task::class.java) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + task = bundle.getParcelable(PARCELABLE_TASK, Task::class.java) + } else { + task = bundle.getParcelable(PARCELABLE_TASK) + } task?.let { fillForm(it) } } else -> { @@ -249,7 +264,8 @@ class TaskFormActivity : BaseActivity() { override fun loadTheme(sharedPreferences: SharedPreferences, forced: Boolean) { super.loadTheme(sharedPreferences, forced) - val upperTintColor = if (forcedTheme == "taskform") getThemeColor(R.attr.taskFormTint) else getThemeColor(R.attr.colorAccent) + val upperTintColor = + if (forcedTheme == "taskform") getThemeColor(R.attr.taskFormTint) else getThemeColor(R.attr.colorAccent) window.statusBarColor = upperTintColor } @@ -275,8 +291,8 @@ class TaskFormActivity : BaseActivity() { alert.dismiss() } alert.setOnDismissListener { - isDiscardCancelled = true - } + isDiscardCancelled = true + } alert.show() } else { super.onBackPressed() @@ -315,13 +331,15 @@ class TaskFormActivity : BaseActivity() { binding.habitScoringButtons.visibility = habitViewsVisibility binding.habitResetStreakTitleView.visibility = habitViewsVisibility binding.habitResetStreakButtons.visibility = habitViewsVisibility - (binding.habitAdjustNegativeStreakView.parent as ViewGroup).visibility = habitViewsVisibility + (binding.habitAdjustNegativeStreakView.parent as ViewGroup).visibility = + habitViewsVisibility if (taskType == TaskType.HABIT) { binding.habitScoringButtons.isPositive = true binding.habitScoringButtons.isNegative = false } - val habitDailyVisibility = if (taskType == TaskType.DAILY || taskType == TaskType.HABIT) View.VISIBLE else View.GONE + val habitDailyVisibility = + if (taskType == TaskType.DAILY || taskType == TaskType.HABIT) View.VISIBLE else View.GONE binding.adjustStreakTitleView.visibility = habitDailyVisibility binding.adjustStreakWrapper.visibility = habitDailyVisibility if (taskType == TaskType.HABIT) { @@ -332,13 +350,18 @@ class TaskFormActivity : BaseActivity() { binding.adjustStreakTitleView.text = getString(R.string.adjust_streak) } - val todoDailyViewsVisibility = if (taskType == TaskType.DAILY || taskType == TaskType.TODO) View.VISIBLE else View.GONE + val todoDailyViewsVisibility = + if (taskType == TaskType.DAILY || taskType == TaskType.TODO) View.VISIBLE else View.GONE - binding.checklistTitleView.visibility = if (isChallengeTask) View.GONE else todoDailyViewsVisibility - binding.checklistContainer.visibility = if (isChallengeTask) View.GONE else todoDailyViewsVisibility + binding.checklistTitleView.visibility = + if (isChallengeTask) View.GONE else todoDailyViewsVisibility + binding.checklistContainer.visibility = + if (isChallengeTask) View.GONE else todoDailyViewsVisibility - binding.remindersTitleView.visibility = if (isChallengeTask) View.GONE else todoDailyViewsVisibility - binding.remindersContainer.visibility = if (isChallengeTask) View.GONE else todoDailyViewsVisibility + binding.remindersTitleView.visibility = + if (isChallengeTask) View.GONE else todoDailyViewsVisibility + binding.remindersContainer.visibility = + if (isChallengeTask) View.GONE else todoDailyViewsVisibility binding.remindersContainer.taskType = taskType binding.remindersContainer.firstDayOfWeek = firstDayOfWeek @@ -405,12 +428,15 @@ class TaskFormActivity : BaseActivity() { binding.habitScoringButtons.isPositive = task.up ?: false binding.habitScoringButtons.isNegative = task.down ?: false task.frequency?.let { - binding.habitResetStreakButtons.selectedResetOption = HabitResetOption.from(it) ?: HabitResetOption.DAILY + binding.habitResetStreakButtons.selectedResetOption = + HabitResetOption.from(it) ?: HabitResetOption.DAILY } binding.habitAdjustPositiveStreakView.setText((task.counterUp ?: 0).toString()) binding.habitAdjustNegativeStreakView.setText((task.counterDown ?: 0).toString()) - (binding.habitAdjustPositiveStreakView.parent as ViewGroup).visibility = if (task.up == true) View.VISIBLE else View.GONE - (binding.habitAdjustNegativeStreakView.parent as ViewGroup).visibility = if (task.down == true) View.VISIBLE else View.GONE + (binding.habitAdjustPositiveStreakView.parent as ViewGroup).visibility = + if (task.up == true) View.VISIBLE else View.GONE + (binding.habitAdjustNegativeStreakView.parent as ViewGroup).visibility = + if (task.down == true) View.VISIBLE else View.GONE if (task.up != true && task.down != true) { binding.adjustStreakTitleView.visibility = View.GONE binding.adjustStreakWrapper.visibility = View.GONE @@ -440,13 +466,24 @@ class TaskFormActivity : BaseActivity() { private fun setSelectedAttribute(attributeName: Attribute) { if (!usesTaskAttributeStats) return configureStatsButton(binding.statStrengthButton, attributeName == Attribute.STRENGTH) - configureStatsButton(binding.statIntelligenceButton, attributeName == Attribute.INTELLIGENCE) - configureStatsButton(binding.statConstitutionButton, attributeName == Attribute.CONSTITUTION) + configureStatsButton( + binding.statIntelligenceButton, + attributeName == Attribute.INTELLIGENCE + ) + configureStatsButton( + binding.statConstitutionButton, + attributeName == Attribute.CONSTITUTION + ) configureStatsButton(binding.statPerceptionButton, attributeName == Attribute.PERCEPTION) } private fun configureStatsButton(button: TextView, isSelected: Boolean) { - button.background.setTint(if (isSelected) tintColor else ContextCompat.getColor(this, R.color.taskform_gray)) + button.background.setTint( + if (isSelected) tintColor else ContextCompat.getColor( + this, + R.color.taskform_gray + ) + ) val textColorID = if (isSelected) R.color.window_background else R.color.text_secondary button.setTextColor(ContextCompat.getColor(this, textColorID)) if (isSelected) { @@ -578,7 +615,11 @@ class TaskFormActivity : BaseActivity() { alert.setTitle(R.string.are_you_sure) alert.addButton(R.string.delete_task, true) { _, _ -> if (task?.isValid != true) return@addButton - task?.id?.let { taskRepository.deleteTask(it).subscribe({ }, ExceptionHandler.rx()) } + task?.id?.let { + lifecycleScope.launchCatching { + taskRepository.deleteTask(it) + } + } finish() } alert.addCancelButton() @@ -586,52 +627,46 @@ class TaskFormActivity : BaseActivity() { } private fun showChallengeDeleteTask() { - compositeSubscription.add( - taskRepository.getTasksForChallenge(task?.challengeID).firstElement().subscribe( - { tasks -> - val taskCount = tasks.size - val alert = HabiticaAlertDialog(this) - alert.setTitle(getString(R.string.delete_challenge_task_title)) - alert.setMessage(getString(R.string.delete_challenge_task_description, taskCount, challenge?.name ?: "")) - alert.addButton(R.string.leave_delete_task, isPrimary = true, isDestructive = true) { _, _ -> - challenge?.let { - compositeSubscription.add( - challengeRepository.leaveChallenge(it, "keep-all") - .flatMap { taskRepository.deleteTask(task?.id ?: "") } - .subscribe( - { - lifecycleScope.launch(ExceptionHandler.coroutine()) { - userRepository.retrieveUser(true, true) - } - finish() - }, - ExceptionHandler.rx() - ) - ) - } - } - alert.addButton(getString(R.string.leave_delete_x_tasks, taskCount), isPrimary = false, isDestructive = true) { _, _ -> - challenge?.let { - compositeSubscription.add( - challengeRepository.leaveChallenge(it, "remove-all") - .subscribe( - { - lifecycleScope.launch(ExceptionHandler.coroutine()) { - userRepository.retrieveUser(true, true) - } - finish() - }, - ExceptionHandler.rx() - ) - ) - } - } - alert.setExtraCloseButtonVisibility(View.VISIBLE) - alert.show() - }, - ExceptionHandler.rx() + lifecycleScope.launchCatching { + val tasks = taskRepository.getTasksForChallenge(task?.challengeID).firstOrNull() ?: return@launchCatching + val taskCount = tasks.size + val alert = HabiticaAlertDialog(this@TaskFormActivity) + alert.setTitle(getString(R.string.delete_challenge_task_title)) + alert.setMessage( + getString( + R.string.delete_challenge_task_description, + taskCount, + challenge?.name ?: "" + ) ) - ) + alert.addButton( + R.string.leave_delete_task, + isPrimary = true, + isDestructive = true + ) { _, _ -> + challenge?.let { + lifecycleScope.launchCatching { + challengeRepository.leaveChallenge(it, "keep-all") + taskRepository.deleteTask(task?.id ?: "") + userRepository.retrieveUser(true, true) + } + } + } + alert.addButton( + getString(R.string.leave_delete_x_tasks, taskCount), + isPrimary = false, + isDestructive = true + ) { _, _ -> + challenge?.let { + lifecycleScope.launchCatching { + challengeRepository.leaveChallenge(it, "remove-all") + userRepository.retrieveUser(true, true) + } + } + } + alert.setExtraCloseButtonVisibility(View.VISIBLE) + alert.show() + } } private fun showBrokenChallengeDialog() { @@ -639,43 +674,40 @@ class TaskFormActivity : BaseActivity() { if (!task.isValid) { return } - compositeSubscription.add( - taskRepository.getTasksForChallenge(task.challengeID).subscribe( - { tasks -> + lifecycleScope.launchCatching { + val tasks = taskRepository.getTasksForChallenge(task.challengeID).firstOrNull() ?: return@launchCatching val taskCount = tasks.size - val dialog = HabiticaAlertDialog(this) + val dialog = HabiticaAlertDialog(this@TaskFormActivity) dialog.setTitle(R.string.broken_challenge) - dialog.setMessage(this.getString(R.string.broken_challenge_description, taskCount)) - dialog.addButton(this.getString(R.string.keep_x_tasks, taskCount), true) { _, _ -> - taskRepository.unlinkAllTasks(task.challengeID, "keep-all") - .subscribe( - { - lifecycleScope.launch(ExceptionHandler.coroutine()) { - userRepository.retrieveUser(true, true) - } - finish() - }, - ExceptionHandler.rx() + dialog.setMessage( + getString( + R.string.broken_challenge_description, + taskCount ) + ) + dialog.addButton( + getString(R.string.keep_x_tasks, taskCount), + true + ) { _, _ -> + lifecycleScope.launchCatching { + taskRepository.unlinkAllTasks(task.challengeID, "keep-all") + userRepository.retrieveUser(true, true) + } } - dialog.addButton(this.getString(R.string.delete_x_tasks, taskCount), false, true) { _, _ -> + dialog.addButton( + getString(R.string.delete_x_tasks, taskCount), + false, + true + ) { _, _ -> + lifecycleScope.launchCatching { + taskRepository.unlinkAllTasks(task.challengeID, "remove-all") - .subscribe( - { - lifecycleScope.launch(ExceptionHandler.coroutine()) { - userRepository.retrieveUser(true, true) - } - finish() - }, - ExceptionHandler.rx() - ) + userRepository.retrieveUser(true, true) + } } dialog.setExtraCloseButtonVisibility(View.VISIBLE) dialog.show() - }, - ExceptionHandler.rx() - ) - ) + } } private fun disableEditingForUneditableFieldsInChallengeTask() { 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 deleted file mode 100644 index 31ac9393d..000000000 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/VerifyUsernameActivity.kt +++ /dev/null @@ -1,135 +0,0 @@ -package com.habitrpg.android.habitica.ui.activities - -import android.graphics.drawable.BitmapDrawable -import android.graphics.drawable.Drawable -import android.os.Bundle -import android.text.method.LinkMovementMethod -import android.view.View -import androidx.core.content.ContextCompat -import androidx.lifecycle.lifecycleScope -import com.habitrpg.android.habitica.R -import com.habitrpg.android.habitica.components.UserComponent -import com.habitrpg.android.habitica.databinding.ActivityVerifyUsernameBinding -import com.habitrpg.android.habitica.extensions.OnChangeTextWatcher -import com.habitrpg.android.habitica.extensions.runDelayed -import com.habitrpg.android.habitica.helpers.ExceptionHandler -import com.habitrpg.android.habitica.ui.views.HabiticaIconsHelper -import com.habitrpg.android.habitica.ui.views.HabiticaSnackbar -import io.reactivex.rxjava3.core.BackpressureStrategy -import io.reactivex.rxjava3.core.Flowable -import io.reactivex.rxjava3.subjects.PublishSubject -import kotlinx.coroutines.flow.firstOrNull -import kotlinx.coroutines.launch -import java.util.concurrent.TimeUnit - -class VerifyUsernameActivity : BaseActivity() { - private lateinit var binding: ActivityVerifyUsernameBinding - - private val displayNameVerificationEvents = PublishSubject.create() - private val usernameVerificationEvents = PublishSubject.create() - - private val checkmarkIcon: Drawable by lazy { - BitmapDrawable(resources, HabiticaIconsHelper.imageOfCheckmark(ContextCompat.getColor(this, R.color.text_green), 1f)) - } - private val alertIcon: Drawable by lazy { - BitmapDrawable(resources, HabiticaIconsHelper.imageOfAlertIcon()) - } - - override fun getLayoutResId(): Int { - return R.layout.activity_verify_username - } - - override fun getContentView(layoutResId: Int?): View { - binding = ActivityVerifyUsernameBinding.inflate(layoutInflater) - return binding.root - } - - override fun injectActivity(component: UserComponent?) { - component?.inject(this) - } - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - - binding.wikiTextView.movementMethod = LinkMovementMethod.getInstance() - binding.footerTextView.movementMethod = LinkMovementMethod.getInstance() - - binding.confirmUsernameButton.setOnClickListener { confirmNames() } - - binding.displayNameEditText.addTextChangedListener( - OnChangeTextWatcher { p0, _, _, _ -> - displayNameVerificationEvents.onNext(p0.toString()) - } - ) - binding.usernameEditText.addTextChangedListener( - OnChangeTextWatcher { p0, _, _, _ -> - usernameVerificationEvents.onNext(p0.toString()) - } - ) - - compositeSubscription.add( - Flowable.combineLatest( - displayNameVerificationEvents.toFlowable(BackpressureStrategy.DROP) - .map { it.length in 1..30 } - .doOnNext { - if (it) { - binding.displayNameEditText.setCompoundDrawablesWithIntrinsicBounds(null, null, checkmarkIcon, null) - binding.issuesTextView.visibility = View.GONE - } else { - binding.displayNameEditText.setCompoundDrawablesWithIntrinsicBounds(null, null, alertIcon, null) - binding.issuesTextView.visibility = View.VISIBLE - binding.issuesTextView.text = getString(R.string.display_name_length_error) - } - }, - usernameVerificationEvents.toFlowable(BackpressureStrategy.DROP) - .throttleLast(1, TimeUnit.SECONDS) - .flatMap { userRepository.verifyUsername(binding.usernameEditText.text.toString()) } - .doOnNext { - if (it.isUsable) { - binding.usernameEditText.setCompoundDrawablesWithIntrinsicBounds(null, null, checkmarkIcon, null) - binding.issuesTextView.visibility = View.GONE - } else { - binding.usernameEditText.setCompoundDrawablesWithIntrinsicBounds(null, null, alertIcon, null) - binding.issuesTextView.visibility = View.VISIBLE - binding.issuesTextView.text = it.issues.joinToString("\n") - } - } - ) { displayNameUsable, usernameUsable -> displayNameUsable && usernameUsable.isUsable } - .subscribe( - { - binding.confirmUsernameButton.isEnabled = it - }, - ExceptionHandler.rx() - ) - ) - - lifecycleScope.launch(ExceptionHandler.coroutine()) { - val user = userRepository.getUser().firstOrNull() - binding.displayNameEditText.setText(user?.profile?.name) - displayNameVerificationEvents.onNext(user?.profile?.name ?: "") - binding.usernameEditText.setText(user?.authentication?.localAuthentication?.username) - usernameVerificationEvents.onNext(user?.username ?: "") - } - } - - private fun confirmNames() { - binding.confirmUsernameButton.isClickable = false - lifecycleScope.launch(ExceptionHandler.coroutine()) { - userRepository.updateUser("profile.name", binding.displayNameEditText.text.toString()) - userRepository.updateLoginName(binding.usernameEditText.text.toString()) - showConfirmationAndFinish() - binding.confirmUsernameButton.isClickable = true - } - } - - private fun showConfirmationAndFinish() { - HabiticaSnackbar.showSnackbar(binding.snackbarView, getString(R.string.username_confirmed), HabiticaSnackbar.SnackbarDisplayType.SUCCESS) - runDelayed(3, TimeUnit.SECONDS) { - finish() - } - } - - override fun onBackPressed() { - moveTaskToBack(true) - } -} diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/SkillTasksRecyclerViewAdapter.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/SkillTasksRecyclerViewAdapter.kt index e519a18fd..2496b7fdf 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/SkillTasksRecyclerViewAdapter.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/SkillTasksRecyclerViewAdapter.kt @@ -8,14 +8,11 @@ import androidx.recyclerview.widget.RecyclerView import com.habitrpg.android.habitica.R import com.habitrpg.android.habitica.databinding.SkillTaskItemCardBinding import com.habitrpg.android.habitica.models.tasks.Task -import io.reactivex.rxjava3.core.BackpressureStrategy -import io.reactivex.rxjava3.core.Flowable -import io.reactivex.rxjava3.subjects.PublishSubject import java.util.UUID class SkillTasksRecyclerViewAdapter : BaseRecyclerViewAdapter() { - private val taskSelectionEvents = PublishSubject.create() + var onTaskSelection: ((Task) -> Unit)? = null override fun getItemId(position: Int): Long { val task = getItem(position) @@ -35,10 +32,6 @@ class SkillTasksRecyclerViewAdapter : BaseRecyclerViewAdapter { - return taskSelectionEvents.toFlowable(BackpressureStrategy.DROP) - } - inner class TaskViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView), View.OnClickListener { private val binding = SkillTaskItemCardBinding.bind(itemView) var task: Task? = null @@ -63,7 +56,7 @@ class SkillTasksRecyclerViewAdapter : BaseRecyclerViewAdapter() { @@ -22,7 +21,7 @@ class EquipmentRecyclerViewAdapter : BaseRecyclerViewAdapter = PublishSubject.create() + var onEquip: ((String) -> Unit)? = null override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): GearViewHolder { val view = LayoutInflater.from(parent.context).inflate(R.layout.gear_list_item, parent, false) @@ -45,7 +44,7 @@ class EquipmentRecyclerViewAdapter : BaseRecyclerViewAdapter() - private val questInvitationEvents = PublishSubject.create() - private val openMysteryItemEvents = PublishSubject.create() - private val startHatchingSubject = PublishSubject.create() - private val hatchPetSubject = PublishSubject.create>() - private val feedPetSubject = PublishSubject.create() - private val createNewPartySubject = PublishSubject.create() - private val useSpecialSubject = PublishSubject.create() - - fun getSellItemFlowable(): Flowable { - return sellItemEvents.toFlowable(BackpressureStrategy.DROP) - } - - fun getQuestInvitationFlowable(): Flowable { - return questInvitationEvents.toFlowable(BackpressureStrategy.DROP) - } - fun getOpenMysteryItemFlowable(): Flowable { - return openMysteryItemEvents.toFlowable(BackpressureStrategy.DROP) - } - - val startHatchingEvents: Flowable = startHatchingSubject.toFlowable(BackpressureStrategy.DROP) - val hatchPetEvents: Flowable> = hatchPetSubject.toFlowable(BackpressureStrategy.DROP) - val feedPetEvents: Flowable = feedPetSubject.toFlowable(BackpressureStrategy.DROP) - val startNewPartyEvents: Flowable = createNewPartySubject.toFlowable(BackpressureStrategy.DROP) - val useSpecialEvents: Flowable = useSpecialSubject.toFlowable(BackpressureStrategy.DROP) + var onSellItem: ((OwnedItem) -> Unit)? = null + var onQuestInvitation: ((QuestContent) -> Unit)? = null + var onOpenMysteryItem: ((Item) -> Unit)? = null + var onStartHatching: ((Item) -> Unit)? = null + var onHatchPet: ((HatchingPotion, Egg) -> Unit)? = null + var onFeedPet: ((Food) -> Unit)? = null + var onCreateNewParty: (() -> Unit)? = null + var onUseSpecialItem: ((SpecialItem) -> Unit)? = null override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemViewHolder { return ItemViewHolder(ItemItemBinding.inflate(context.layoutInflater, parent, false)) @@ -188,12 +168,12 @@ class ItemRecyclerAdapter(val context: Context) : BaseRecyclerViewAdapter item?.let { selectedItem -> if (!(selectedItem is QuestContent || selectedItem is SpecialItem) && index == 0) { - ownedItem?.let { selectedOwnedItem -> sellItemEvents.onNext(selectedOwnedItem) } + ownedItem?.let { selectedOwnedItem -> onSellItem?.invoke(selectedOwnedItem) } return@let } when (selectedItem) { - is Egg -> item?.let { startHatchingSubject.onNext(it) } - is HatchingPotion -> startHatchingSubject.onNext(selectedItem) + is Egg -> item?.let { onStartHatching?.invoke(it) } + is HatchingPotion -> onStartHatching?.invoke(selectedItem) is QuestContent -> { if (index == 0) { val dialog = DetailDialog(context) @@ -201,17 +181,17 @@ class ItemRecyclerAdapter(val context: Context) : BaseRecyclerViewAdapter if (item?.key != "inventory_present") { - useSpecialSubject.onNext(selectedItem) + onUseSpecialItem?.invoke(selectedItem) } else { - openMysteryItemEvents.onNext(selectedItem) + onOpenMysteryItem?.invoke(selectedItem) } } } @@ -224,17 +204,17 @@ class ItemRecyclerAdapter(val context: Context) : BaseRecyclerViewAdapter if (firstItem is Egg) { (hatchingItem as? HatchingPotion)?.let { potion -> - hatchPetSubject.onNext(Pair(potion, firstItem)) + onHatchPet?.invoke(potion, firstItem) } } else if (firstItem is HatchingPotion) { (hatchingItem as? Egg)?.let { egg -> - hatchPetSubject.onNext(Pair(firstItem, egg)) + onHatchPet?.invoke(firstItem, egg) } } return@let } } else if (isFeeding) { - feedPetSubject.onNext(item as Food?) + (item as Food?)?.let { onFeedPet?.invoke(it) } fragment?.dismiss() } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/inventory/MountDetailRecyclerAdapter.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/inventory/MountDetailRecyclerAdapter.kt index 865550a3c..1fc7f5a3e 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/inventory/MountDetailRecyclerAdapter.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/inventory/MountDetailRecyclerAdapter.kt @@ -6,11 +6,10 @@ import com.habitrpg.android.habitica.models.inventory.StableSection import com.habitrpg.android.habitica.models.user.OwnedMount import com.habitrpg.android.habitica.ui.viewHolders.MountViewHolder import com.habitrpg.android.habitica.ui.viewHolders.SectionViewHolder -import io.reactivex.rxjava3.core.BackpressureStrategy -import io.reactivex.rxjava3.core.Flowable import io.reactivex.rxjava3.subjects.PublishSubject class MountDetailRecyclerAdapter : androidx.recyclerview.widget.RecyclerView.Adapter() { + var onEquip: ((String) -> Unit)? = null private var ownedMounts: Map? = null private val equipEvents = PublishSubject.create() @@ -27,14 +26,10 @@ class MountDetailRecyclerAdapter : androidx.recyclerview.widget.RecyclerView.Ada this.notifyDataSetChanged() } - fun getEquipFlowable(): Flowable { - return equipEvents.toFlowable(BackpressureStrategy.DROP) - } - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): androidx.recyclerview.widget.RecyclerView.ViewHolder = when (viewType) { 1 -> SectionViewHolder(parent) - else -> MountViewHolder(parent, equipEvents) + else -> MountViewHolder(parent, onEquip) } override fun onBindViewHolder( diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/inventory/PetDetailRecyclerAdapter.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/inventory/PetDetailRecyclerAdapter.kt index f06af2b9f..f8452d3d0 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/inventory/PetDetailRecyclerAdapter.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/inventory/PetDetailRecyclerAdapter.kt @@ -5,7 +5,6 @@ import android.view.ViewGroup import com.habitrpg.android.habitica.R import com.habitrpg.android.habitica.databinding.CanHatchItemBinding import com.habitrpg.android.habitica.extensions.inflate -import com.habitrpg.common.habitica.helpers.Animations import com.habitrpg.android.habitica.models.inventory.Animal import com.habitrpg.android.habitica.models.inventory.Egg import com.habitrpg.android.habitica.models.inventory.Food @@ -16,15 +15,16 @@ import com.habitrpg.android.habitica.models.inventory.StableSection import com.habitrpg.android.habitica.models.user.OwnedItem import com.habitrpg.android.habitica.models.user.OwnedMount import com.habitrpg.android.habitica.models.user.OwnedPet -import com.habitrpg.common.habitica.extensions.loadImage import com.habitrpg.android.habitica.ui.viewHolders.PetViewHolder import com.habitrpg.android.habitica.ui.viewHolders.SectionViewHolder import com.habitrpg.android.habitica.ui.views.dialogs.PetSuggestHatchDialog -import io.reactivex.rxjava3.core.BackpressureStrategy -import io.reactivex.rxjava3.core.Flowable +import com.habitrpg.common.habitica.extensions.loadImage +import com.habitrpg.common.habitica.helpers.Animations import io.reactivex.rxjava3.subjects.PublishSubject class PetDetailRecyclerAdapter : androidx.recyclerview.widget.RecyclerView.Adapter() { + var onFeed: ((Pet, Food?) -> Unit)? = null + var onEquip: ((String) -> Unit)? = null private var existingMounts: List? = null private var ownedPets: Map? = null private var ownedMounts: Map? = null @@ -45,12 +45,6 @@ class PetDetailRecyclerAdapter : androidx.recyclerview.widget.RecyclerView.Adapt this.notifyDataSetChanged() } - fun getEquipFlowable(): Flowable { - return equipEvents.toFlowable(BackpressureStrategy.DROP) - } - - var feedFlowable: Flowable> = feedEvents.toFlowable(BackpressureStrategy.DROP) - var animalIngredientsRetriever: ((Animal, ((Pair) -> Unit)) -> Unit)? = null private fun canRaiseToMount(pet: Pet): Boolean { @@ -74,7 +68,7 @@ class PetDetailRecyclerAdapter : androidx.recyclerview.widget.RecyclerView.Adapt when (viewType) { 1 -> SectionViewHolder(parent) 2 -> CanHatchViewHolder(parent, animalIngredientsRetriever) - else -> PetViewHolder(parent, equipEvents, feedEvents, animalIngredientsRetriever) + else -> PetViewHolder(parent, onEquip, onFeed, animalIngredientsRetriever) } override fun onBindViewHolder( diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/inventory/StableRecyclerAdapter.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/inventory/StableRecyclerAdapter.kt index c9c4c06e0..d16b06f27 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/inventory/StableRecyclerAdapter.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/inventory/StableRecyclerAdapter.kt @@ -25,16 +25,13 @@ import com.habitrpg.android.habitica.ui.viewHolders.PetViewHolder import com.habitrpg.android.habitica.ui.viewHolders.SectionViewHolder import com.habitrpg.common.habitica.extensions.loadImage import com.habitrpg.common.habitica.views.PixelArtView -import io.reactivex.rxjava3.core.BackpressureStrategy -import io.reactivex.rxjava3.core.Flowable -import io.reactivex.rxjava3.subjects.PublishSubject class StableRecyclerAdapter : RecyclerView.Adapter() { var shopSpriteSuffix: String? = null private var eggs: Map = mapOf() var animalIngredientsRetriever: ((Animal, ((Pair) -> Unit)) -> Unit)? = null - private val feedEvents = PublishSubject.create>() + var onFeed: ((Pet, Food?) -> Unit)? = null var itemType: String? = null var currentPet: String? = null set(value) { @@ -46,16 +43,13 @@ class StableRecyclerAdapter : RecyclerView.Adapter() { field = value notifyDataSetChanged() } - private val equipEvents = PublishSubject.create() + var onEquip: ((String) -> Unit)? = null private var existingMounts: List? = null private var ownedMounts: Map? = null private var ownedItems: Map? = null private var ownsSaddles: Boolean = false private var itemList: List = ArrayList() - fun getEquipFlowable(): Flowable { - return equipEvents.toFlowable(BackpressureStrategy.DROP) - } private fun canRaiseToMount(pet: Pet): Boolean { if (pet.type == "special") return false @@ -101,8 +95,8 @@ class StableRecyclerAdapter : RecyclerView.Adapter() { 1 -> SectionViewHolder(parent) 4 -> StableViewHolder(parent.inflate(R.layout.pet_overview_item)) 5 -> StableViewHolder(parent.inflate(R.layout.mount_overview_item)) - 2 -> PetViewHolder(parent, equipEvents, feedEvents, animalIngredientsRetriever) - 3 -> MountViewHolder(parent, equipEvents) + 2 -> PetViewHolder(parent, onEquip, onFeed, animalIngredientsRetriever) + 3 -> MountViewHolder(parent, onEquip) else -> StableHeaderViewHolder(parent) } 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 2eea727e3..dbe59c6da 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 @@ -12,9 +12,6 @@ import com.habitrpg.android.habitica.extensions.inflate import com.habitrpg.android.habitica.extensions.setTintWith import com.habitrpg.android.habitica.models.SetupCustomization import com.habitrpg.android.habitica.models.user.User -import io.reactivex.rxjava3.core.BackpressureStrategy -import io.reactivex.rxjava3.core.Flowable -import io.reactivex.rxjava3.subjects.PublishSubject internal class CustomizationSetupAdapter : RecyclerView.Adapter() { @@ -22,8 +19,7 @@ internal class CustomizationSetupAdapter : RecyclerView.Adapter = emptyList() - private val equipGearEventSubject = PublishSubject.create() - val equipGearEvents: Flowable = equipGearEventSubject.toFlowable(BackpressureStrategy.DROP) + var onEquipGear: ((String) -> Unit)? = null var onUpdateUser: ((Map) -> Unit)? = null fun setCustomizationList(newCustomizationList: List) { @@ -122,7 +118,7 @@ internal class CustomizationSetupAdapter : RecyclerView.Adapter() val updatePath = "preferences." + selectedCustomization.path diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/social/PartyMemberRecyclerViewAdapter.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/social/PartyMemberRecyclerViewAdapter.kt index b1127ecc7..7e8b4bf36 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/social/PartyMemberRecyclerViewAdapter.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/social/PartyMemberRecyclerViewAdapter.kt @@ -6,15 +6,12 @@ import com.habitrpg.android.habitica.extensions.inflate import com.habitrpg.android.habitica.models.members.Member import com.habitrpg.android.habitica.ui.adapter.BaseRecyclerViewAdapter import com.habitrpg.android.habitica.ui.viewHolders.GroupMemberViewHolder -import io.reactivex.rxjava3.core.BackpressureStrategy -import io.reactivex.rxjava3.core.Flowable -import io.reactivex.rxjava3.subjects.PublishSubject class PartyMemberRecyclerViewAdapter : BaseRecyclerViewAdapter() { var leaderID: String? = null - private val userClickedEvents = PublishSubject.create() + var onUserClicked: ((String) -> Unit)? = null override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): GroupMemberViewHolder { return GroupMemberViewHolder(parent.inflate(R.layout.party_member)) @@ -23,11 +20,7 @@ class PartyMemberRecyclerViewAdapter : BaseRecyclerViewAdapter { - return userClickedEvents.toFlowable(BackpressureStrategy.DROP) - } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/social/challenges/ChallengeTasksRecyclerViewAdapter.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/social/challenges/ChallengeTasksRecyclerViewAdapter.kt index b399fd74e..d700317cb 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/social/challenges/ChallengeTasksRecyclerViewAdapter.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/social/challenges/ChallengeTasksRecyclerViewAdapter.kt @@ -17,9 +17,6 @@ import com.habitrpg.android.habitica.ui.viewHolders.tasks.RewardViewHolder import com.habitrpg.android.habitica.ui.viewHolders.tasks.TodoViewHolder import com.habitrpg.android.habitica.ui.viewmodels.TasksViewModel import com.habitrpg.shared.habitica.models.tasks.TaskType -import io.reactivex.rxjava3.core.BackpressureStrategy -import io.reactivex.rxjava3.core.Flowable -import io.reactivex.rxjava3.subjects.PublishSubject class ChallengeTasksRecyclerViewAdapter( viewModel: TasksViewModel, @@ -30,13 +27,11 @@ class ChallengeTasksRecyclerViewAdapter( private val taskActionsDisabled: Boolean ) : BaseTasksRecyclerViewAdapter>(TaskType.HABIT, viewModel, layoutResource, newContext, userID) { - private val addItemSubject = PublishSubject.create() - val taskList: MutableList get() = content?.map { t -> t }?.toMutableList() ?: mutableListOf() - private var taskOpenEventsSubject = PublishSubject.create() - val taskOpenEvents: Flowable = taskOpenEventsSubject.toFlowable(BackpressureStrategy.LATEST) + var onAddItem: ((Task) -> Unit)? = null + var onTaskOpen: ((Task) -> Unit)? = null override fun injectThis(component: UserComponent) { component.inject(this) @@ -50,14 +45,10 @@ class ChallengeTasksRecyclerViewAdapter( TaskType.DAILY -> TYPE_DAILY TaskType.TODO -> TYPE_TODO TaskType.REWARD -> TYPE_REWARD - else -> if (addItemSubject.hasObservers() && task?.id == "addtask") TYPE_ADD_ITEM else TYPE_HEADER + else -> if (task?.id == "addtask") TYPE_ADD_ITEM else TYPE_HEADER } } - fun addItemObservable(): Flowable { - return addItemSubject.toFlowable(BackpressureStrategy.BUFFER) - } - fun addTaskUnder(taskToAdd: Task, taskAbove: Task?): Int { val position = content?.indexOfFirst { t -> t.id == taskAbove?.id } ?: 0 @@ -70,18 +61,18 @@ class ChallengeTasksRecyclerViewAdapter( override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BindableViewHolder { val viewHolder: BindableViewHolder = when (viewType) { TYPE_HABIT -> HabitViewHolder(getContentView(parent, R.layout.habit_item_card), { _, _ -> }, { }, { task -> - taskOpenEventsSubject.onNext(task) + onTaskOpen?.invoke(task) }, null) TYPE_DAILY -> DailyViewHolder(getContentView(parent, R.layout.daily_item_card), { _, _ -> }, { _, _ -> }, { }, { task -> - taskOpenEventsSubject.onNext(task) + onTaskOpen?.invoke(task) }, null) TYPE_TODO -> TodoViewHolder(getContentView(parent, R.layout.todo_item_card), { _, _ -> }, { _, _ -> }, { }, { task -> - taskOpenEventsSubject.onNext(task) + onTaskOpen?.invoke(task) }, null) TYPE_REWARD -> RewardViewHolder(getContentView(parent, R.layout.reward_item_card), { _, _ -> }, { }, { task -> - taskOpenEventsSubject.onNext(task) + onTaskOpen?.invoke(task) }, null) - TYPE_ADD_ITEM -> AddItemViewHolder(getContentView(parent, R.layout.challenge_add_task_item), addItemSubject) + TYPE_ADD_ITEM -> AddItemViewHolder(getContentView(parent, R.layout.challenge_add_task_item), onAddItem) else -> DividerViewHolder(getContentView(parent, R.layout.challenge_task_divider)) } @@ -113,7 +104,7 @@ class ChallengeTasksRecyclerViewAdapter( inner class AddItemViewHolder internal constructor( itemView: View, - private val callback: PublishSubject + private val callback: ((Task) -> Unit)? ) : BindableViewHolder(itemView) { private val addBtn: Button = itemView.findViewById(R.id.btn_add_task) @@ -121,7 +112,7 @@ class ChallengeTasksRecyclerViewAdapter( init { addBtn.isClickable = true - addBtn.setOnClickListener { newTask?.let { callback.onNext(it) } } + addBtn.setOnClickListener { newTask?.let { callback?.invoke(it) } } } override fun bind( diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/AchievementsFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/AchievementsFragment.kt index 215f9ebfe..e4c8b93d6 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/AchievementsFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/AchievementsFragment.kt @@ -17,6 +17,7 @@ import com.habitrpg.android.habitica.components.UserComponent import com.habitrpg.android.habitica.data.InventoryRepository import com.habitrpg.android.habitica.databinding.FragmentRefreshRecyclerviewBinding import com.habitrpg.android.habitica.helpers.ExceptionHandler +import com.habitrpg.android.habitica.helpers.launchCatching import com.habitrpg.android.habitica.ui.adapter.AchievementsAdapter import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel import kotlinx.coroutines.flow.combine @@ -167,12 +168,8 @@ class AchievementsFragment : BaseMainFragment : BottomSheetDialogFragment() { @@ -68,21 +70,19 @@ abstract class BaseDialogFragment : BottomSheetDialogFragment( private fun showTutorialIfNeeded() { if (view != null) { - if (this.tutorialStepIdentifier != null) { - compositeSubscription.add( - tutorialRepository.getTutorialStep(this.tutorialStepIdentifier ?: "").firstElement() - .delay(1, TimeUnit.SECONDS) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe( - Consumer { step -> - if (step.isValid && step.isManaged && step.shouldDisplay) { - val mainActivity = activity as? MainActivity ?: return@Consumer - mainActivity.displayTutorialStep(step, tutorialTexts, tutorialCanBeDeferred) - } - }, - ExceptionHandler.rx() + tutorialStepIdentifier?.let { identifier -> + lifecycleScope.launchCatching { + val step = tutorialRepository.getTutorialStep(identifier).firstOrNull() + delay(1.toDuration(DurationUnit.SECONDS)) + if (step?.isValid == true && step.isManaged && step.shouldDisplay) { + val mainActivity = activity as? MainActivity ?: return@launchCatching + mainActivity.displayTutorialStep( + step, + tutorialTexts, + tutorialCanBeDeferred ) - ) + } + } } } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/BaseFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/BaseFragment.kt index 5a39ddbb4..9a9653b41 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/BaseFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/BaseFragment.kt @@ -5,17 +5,20 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.fragment.app.Fragment +import androidx.lifecycle.lifecycleScope import androidx.viewbinding.ViewBinding import com.habitrpg.android.habitica.HabiticaBaseApplication import com.habitrpg.android.habitica.components.UserComponent import com.habitrpg.android.habitica.data.TutorialRepository -import com.habitrpg.android.habitica.helpers.ExceptionHandler +import com.habitrpg.android.habitica.helpers.launchCatching import com.habitrpg.android.habitica.proxy.AnalyticsManager import com.habitrpg.android.habitica.ui.activities.MainActivity -import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers import io.reactivex.rxjava3.disposables.CompositeDisposable -import java.util.concurrent.TimeUnit +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.firstOrNull import javax.inject.Inject +import kotlin.time.DurationUnit +import kotlin.time.toDuration abstract class BaseFragment : Fragment() { @@ -72,21 +75,18 @@ abstract class BaseFragment : Fragment() { private fun showTutorialIfNeeded() { tutorialStepIdentifier?.let { identifier -> - compositeSubscription.add( - tutorialRepository.getTutorialStep(identifier) - .firstElement() - .delay(1, TimeUnit.SECONDS) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe( - { step -> - if (step.isValid && step.isManaged && step.shouldDisplay) { - val mainActivity = activity as? MainActivity ?: return@subscribe - mainActivity.displayTutorialStep(step, tutorialTexts, tutorialCanBeDeferred) - } - }, - ExceptionHandler.rx() + lifecycleScope.launchCatching { + val step = tutorialRepository.getTutorialStep(identifier).firstOrNull() + delay(1.toDuration(DurationUnit.SECONDS)) + if (step?.isValid == true && step.isManaged && step.shouldDisplay) { + val mainActivity = activity as? MainActivity ?: return@launchCatching + mainActivity.displayTutorialStep( + step, + tutorialTexts, + tutorialCanBeDeferred ) - ) + } + } } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/NavigationDrawerFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/NavigationDrawerFragment.kt index 8759207e8..ea8444cfe 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/NavigationDrawerFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/NavigationDrawerFragment.kt @@ -29,10 +29,10 @@ import com.habitrpg.android.habitica.databinding.DrawerMainBinding import com.habitrpg.android.habitica.extensions.getMinuteOrSeconds import com.habitrpg.android.habitica.extensions.getRemainingString import com.habitrpg.android.habitica.extensions.getShortRemainingString -import com.habitrpg.android.habitica.extensions.subscribeWithErrorHandler import com.habitrpg.android.habitica.helpers.AppConfigManager 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.WorldStateEvent import com.habitrpg.android.habitica.models.inventory.Item import com.habitrpg.android.habitica.models.promotions.HabiticaPromotion @@ -47,11 +47,11 @@ import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel import com.habitrpg.android.habitica.ui.viewmodels.NotificationsViewModel import com.habitrpg.android.habitica.ui.views.HabiticaSnackbar import com.habitrpg.common.habitica.extensions.getThemeColor -import io.reactivex.rxjava3.core.Flowable import io.reactivex.rxjava3.disposables.CompositeDisposable import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.combine import kotlinx.coroutines.launch import java.util.Calendar import java.util.Date @@ -68,16 +68,22 @@ class NavigationDrawerFragment : DialogFragment() { @Inject lateinit var socialRepository: SocialRepository + @Inject lateinit var inventoryRepository: InventoryRepository + @Inject lateinit var userRepository: UserRepository + @Inject lateinit var configManager: AppConfigManager + @Inject lateinit var contentRepository: ContentRepository + @Inject lateinit var sharedPreferences: SharedPreferences + @Inject lateinit var userViewModel: MainUserViewModel @@ -101,7 +107,10 @@ class NavigationDrawerFragment : DialogFragment() { override fun onCreate(savedInstanceState: Bundle?) { val context = context adapter = if (context != null) { - NavigationDrawerAdapter(context.getThemeColor(R.attr.colorPrimary), context.getThemeColor(R.attr.colorPrimaryOffset)) + NavigationDrawerAdapter( + context.getThemeColor(R.attr.colorPrimary), + context.getThemeColor(R.attr.colorPrimaryOffset) + ) } else { NavigationDrawerAdapter(0, 0) } @@ -130,8 +139,10 @@ class NavigationDrawerFragment : DialogFragment() { binding = DrawerMainBinding.bind(view) binding?.recyclerView?.adapter = adapter - binding?.recyclerView?.layoutManager = androidx.recyclerview.widget.LinearLayoutManager(context) - (binding?.recyclerView?.itemAnimator as? SimpleItemAnimator)?.supportsChangeAnimations = false + binding?.recyclerView?.layoutManager = + androidx.recyclerview.widget.LinearLayoutManager(context) + (binding?.recyclerView?.itemAnimator as? SimpleItemAnimator)?.supportsChangeAnimations = + false initializeMenuItems() subscriptions?.add( @@ -154,26 +165,25 @@ class NavigationDrawerFragment : DialogFragment() { ) ) - subscriptions?.add( - Flowable.combineLatest( - contentRepository.getWorldState(), inventoryRepository.getAvailableLimitedItems() - ) { state, items -> - return@combineLatest Pair(state, items) - }.subscribe( - { pair -> + lifecycleScope.launchCatching { + contentRepository.getWorldState() + .combine( + inventoryRepository.getAvailableLimitedItems() + ) { state, items -> Pair(state, items) } + .collect { pair -> val gearEvent = pair.first.events.firstOrNull { it.gear } createUpdatingJob("seasonal", { gearEvent?.isCurrentlyActive == true || pair.second.isNotEmpty() }, { val diff = (gearEvent?.end?.time ?: 0) - Date().time - if (diff < (1.toDuration(DurationUnit.HOURS).inWholeMilliseconds)) 1.toDuration(DurationUnit.SECONDS) else 1.toDuration(DurationUnit.MINUTES) + if (diff < (1.toDuration(DurationUnit.HOURS).inWholeMilliseconds)) 1.toDuration( + DurationUnit.SECONDS + ) else 1.toDuration(DurationUnit.MINUTES) }) { updateSeasonalMenuEntries(gearEvent, pair.second) } - }, - ExceptionHandler.rx() - ) - ) + } + } userViewModel.user.observe(viewLifecycleOwner) { if (it != null) { @@ -181,8 +191,22 @@ class NavigationDrawerFragment : DialogFragment() { } } - binding?.messagesButtonWrapper?.setOnClickListener { setSelection(R.id.inboxFragment, null, true, preventReselection = false) } - binding?.settingsButtonWrapper?.setOnClickListener { setSelection(R.id.prefsActivity, null, true, preventReselection = false) } + binding?.messagesButtonWrapper?.setOnClickListener { + setSelection( + R.id.inboxFragment, + null, + true, + preventReselection = false + ) + } + binding?.settingsButtonWrapper?.setOnClickListener { + setSelection( + R.id.prefsActivity, + null, + true, + preventReselection = false + ) + } binding?.notificationsButtonWrapper?.setOnClickListener { startNotificationsActivity() } } @@ -219,7 +243,8 @@ class NavigationDrawerFragment : DialogFragment() { shop.pillText = context?.getString(R.string.open) if (gearEvent?.isCurrentlyActive == true) { shop.isVisible = true - shop.subtitle = context?.getString(R.string.open_for, gearEvent.end?.getShortRemainingString()) + shop.subtitle = + context?.getString(R.string.open_for, gearEvent.end?.getShortRemainingString()) } else { shop.isVisible = false } @@ -228,7 +253,10 @@ class NavigationDrawerFragment : DialogFragment() { private fun updateUser(user: User) { binding?.avatarView?.setOnClickListener { - MainNavigationController.navigate(R.id.openProfileActivity, bundleOf(Pair("userID", user.id))) + MainNavigationController.navigate( + R.id.openProfileActivity, + bundleOf(Pair("userID", user.id)) + ) } setMessagesCount(user.inbox) @@ -289,7 +317,8 @@ class NavigationDrawerFragment : DialogFragment() { val daysDiff = TimeUnit.MILLISECONDS.toDays(msDiff) if (daysDiff <= 30) { context?.let { - subscriptionItem?.subtitle = user.purchased?.plan?.dateTerminated?.getRemainingString(it.resources) + subscriptionItem?.subtitle = + user.purchased?.plan?.dateTerminated?.getRemainingString(it.resources) subscriptionItem?.subtitleTextColor = when { daysDiff <= 2 -> ContextCompat.getColor(it, R.color.red_100) daysDiff <= 7 -> ContextCompat.getColor(it, R.color.brand_400) @@ -337,36 +366,182 @@ class NavigationDrawerFragment : DialogFragment() { private fun initializeMenuItems() { val items = ArrayList() context?.let { context -> - items.add(HabiticaDrawerItem(R.id.tasksFragment, SIDEBAR_TASKS, context.getString(R.string.sidebar_tasks))) - items.add(HabiticaDrawerItem(R.id.skillsFragment, SIDEBAR_SKILLS, context.getString(R.string.sidebar_skills))) - items.add(HabiticaDrawerItem(R.id.statsFragment, SIDEBAR_STATS, context.getString(R.string.sidebar_stats))) - items.add(HabiticaDrawerItem(R.id.achievementsFragment, SIDEBAR_ACHIEVEMENTS, context.getString(R.string.sidebar_achievements))) + items.add( + HabiticaDrawerItem( + R.id.tasksFragment, + SIDEBAR_TASKS, + context.getString(R.string.sidebar_tasks) + ) + ) + items.add( + HabiticaDrawerItem( + R.id.skillsFragment, + SIDEBAR_SKILLS, + context.getString(R.string.sidebar_skills) + ) + ) + items.add( + HabiticaDrawerItem( + R.id.statsFragment, + SIDEBAR_STATS, + context.getString(R.string.sidebar_stats) + ) + ) + items.add( + HabiticaDrawerItem( + R.id.achievementsFragment, + SIDEBAR_ACHIEVEMENTS, + context.getString(R.string.sidebar_achievements) + ) + ) - items.add(HabiticaDrawerItem(0, SIDEBAR_INVENTORY, context.getString(R.string.sidebar_shops), isHeader = true)) - items.add(HabiticaDrawerItem(R.id.marketFragment, SIDEBAR_SHOPS_MARKET, context.getString(R.string.market))) - items.add(HabiticaDrawerItem(R.id.questShopFragment, SIDEBAR_SHOPS_QUEST, context.getString(R.string.questShop))) - val seasonalShopEntry = HabiticaDrawerItem(R.id.seasonalShopFragment, SIDEBAR_SHOPS_SEASONAL, context.getString(R.string.seasonalShop)) + items.add( + HabiticaDrawerItem( + 0, + SIDEBAR_INVENTORY, + context.getString(R.string.sidebar_shops), + isHeader = true + ) + ) + items.add( + HabiticaDrawerItem( + R.id.marketFragment, + SIDEBAR_SHOPS_MARKET, + context.getString(R.string.market) + ) + ) + items.add( + HabiticaDrawerItem( + R.id.questShopFragment, + SIDEBAR_SHOPS_QUEST, + context.getString(R.string.questShop) + ) + ) + val seasonalShopEntry = HabiticaDrawerItem( + R.id.seasonalShopFragment, + SIDEBAR_SHOPS_SEASONAL, + context.getString(R.string.seasonalShop) + ) seasonalShopEntry.isVisible = false items.add(seasonalShopEntry) - items.add(HabiticaDrawerItem(R.id.timeTravelersShopFragment, SIDEBAR_SHOPS_TIMETRAVEL, context.getString(R.string.timeTravelers))) + items.add( + HabiticaDrawerItem( + R.id.timeTravelersShopFragment, + SIDEBAR_SHOPS_TIMETRAVEL, + context.getString(R.string.timeTravelers) + ) + ) - items.add(HabiticaDrawerItem(0, SIDEBAR_INVENTORY, context.getString(R.string.sidebar_section_inventory), isHeader = true)) - items.add(HabiticaDrawerItem(R.id.avatarOverviewFragment, SIDEBAR_AVATAR, context.getString(R.string.sidebar_avatar_equipment))) - items.add(HabiticaDrawerItem(R.id.itemsFragment, SIDEBAR_ITEMS, context.getString(R.string.sidebar_items))) - items.add(HabiticaDrawerItem(R.id.stableFragment, SIDEBAR_STABLE, context.getString(R.string.sidebar_stable))) - items.add(HabiticaDrawerItem(R.id.gemPurchaseActivity, SIDEBAR_GEMS, context.getString(R.string.sidebar_gems))) - items.add(HabiticaDrawerItem(R.id.subscriptionPurchaseActivity, SIDEBAR_SUBSCRIPTION, context.getString(R.string.sidebar_subscription))) + items.add( + HabiticaDrawerItem( + 0, + SIDEBAR_INVENTORY, + context.getString(R.string.sidebar_section_inventory), + isHeader = true + ) + ) + items.add( + HabiticaDrawerItem( + R.id.avatarOverviewFragment, + SIDEBAR_AVATAR, + context.getString(R.string.sidebar_avatar_equipment) + ) + ) + items.add( + HabiticaDrawerItem( + R.id.itemsFragment, + SIDEBAR_ITEMS, + context.getString(R.string.sidebar_items) + ) + ) + items.add( + HabiticaDrawerItem( + R.id.stableFragment, + SIDEBAR_STABLE, + context.getString(R.string.sidebar_stable) + ) + ) + items.add( + HabiticaDrawerItem( + R.id.gemPurchaseActivity, + SIDEBAR_GEMS, + context.getString(R.string.sidebar_gems) + ) + ) + items.add( + HabiticaDrawerItem( + R.id.subscriptionPurchaseActivity, + SIDEBAR_SUBSCRIPTION, + context.getString(R.string.sidebar_subscription) + ) + ) - items.add(HabiticaDrawerItem(0, SIDEBAR_SOCIAL, context.getString(R.string.sidebar_section_social), isHeader = true)) - items.add(HabiticaDrawerItem(R.id.partyFragment, SIDEBAR_PARTY, context.getString(R.string.sidebar_party))) - items.add(HabiticaDrawerItem(R.id.tavernFragment, SIDEBAR_TAVERN, context.getString(R.string.sidebar_tavern))) - items.add(HabiticaDrawerItem(R.id.guildOverviewFragment, SIDEBAR_GUILDS, context.getString(R.string.sidebar_guilds))) - items.add(HabiticaDrawerItem(R.id.challengesOverviewFragment, SIDEBAR_CHALLENGES, context.getString(R.string.sidebar_challenges))) + items.add( + HabiticaDrawerItem( + 0, + SIDEBAR_SOCIAL, + context.getString(R.string.sidebar_section_social), + isHeader = true + ) + ) + items.add( + HabiticaDrawerItem( + R.id.partyFragment, + SIDEBAR_PARTY, + context.getString(R.string.sidebar_party) + ) + ) + items.add( + HabiticaDrawerItem( + R.id.tavernFragment, + SIDEBAR_TAVERN, + context.getString(R.string.sidebar_tavern) + ) + ) + items.add( + HabiticaDrawerItem( + R.id.guildOverviewFragment, + SIDEBAR_GUILDS, + context.getString(R.string.sidebar_guilds) + ) + ) + items.add( + HabiticaDrawerItem( + R.id.challengesOverviewFragment, + SIDEBAR_CHALLENGES, + context.getString(R.string.sidebar_challenges) + ) + ) - items.add(HabiticaDrawerItem(0, SIDEBAR_ABOUT_HEADER, context.getString(R.string.sidebar_about), isHeader = true)) - items.add(HabiticaDrawerItem(R.id.newsFragment, SIDEBAR_NEWS, context.getString(R.string.sidebar_news))) - items.add(HabiticaDrawerItem(R.id.supportMainFragment, SIDEBAR_HELP, context.getString(R.string.sidebar_help))) - items.add(HabiticaDrawerItem(R.id.aboutFragment, SIDEBAR_ABOUT, context.getString(R.string.sidebar_about))) + items.add( + HabiticaDrawerItem( + 0, + SIDEBAR_ABOUT_HEADER, + context.getString(R.string.sidebar_about), + isHeader = true + ) + ) + items.add( + HabiticaDrawerItem( + R.id.newsFragment, + SIDEBAR_NEWS, + context.getString(R.string.sidebar_news) + ) + ) + items.add( + HabiticaDrawerItem( + R.id.supportMainFragment, + SIDEBAR_HELP, + context.getString(R.string.sidebar_help) + ) + ) + items.add( + HabiticaDrawerItem( + R.id.aboutFragment, + SIDEBAR_ABOUT, + context.getString(R.string.sidebar_about) + ) + ) } val promoItem = HabiticaDrawerItem(R.id.subscriptionPurchaseActivity, SIDEBAR_PROMO) @@ -375,7 +550,8 @@ class NavigationDrawerFragment : DialogFragment() { items.add(promoItem) if (configManager.showSubscriptionBanner()) { - val item = HabiticaDrawerItem(R.id.subscriptionPurchaseActivity, SIDEBAR_SUBSCRIPTION_PROMO) + val item = + HabiticaDrawerItem(R.id.subscriptionPurchaseActivity, SIDEBAR_SUBSCRIPTION_PROMO) item.itemViewType = 2 items.add(item) } @@ -421,14 +597,15 @@ class NavigationDrawerFragment : DialogFragment() { } } - private val notificationClickResult = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { - if (it.resultCode == Activity.RESULT_OK) { - (activity as? MainActivity)?.notificationsViewModel?.click( - it.data?.getStringExtra("notificationId") ?: "", - MainNavigationController - ) + private val notificationClickResult = + registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { + if (it.resultCode == Activity.RESULT_OK) { + (activity as? MainActivity)?.notificationsViewModel?.click( + it.data?.getStringExtra("notificationId") ?: "", + MainNavigationController + ) + } } - } /** * Users of this fragment must call this method to set UP the navigation drawer interactions. @@ -448,22 +625,22 @@ class NavigationDrawerFragment : DialogFragment() { this.drawerLayout?.setDrawerShadow(R.drawable.drawer_shadow, GravityCompat.START) // set UP the drawer's list view with items and click listener - subscriptions?.add( - viewModel.getNotificationCount().subscribeWithErrorHandler { + lifecycleScope.launchCatching { + viewModel.getNotificationCount().collect { setNotificationsCount(it) } - ) - subscriptions?.add( - viewModel.allNotificationsSeen().subscribeWithErrorHandler { + } + lifecycleScope.launchCatching { + viewModel.allNotificationsSeen().collect { setNotificationsSeen(it) } - ) - subscriptions?.add( - viewModel.getHasPartyNotification().subscribeWithErrorHandler { + } + lifecycleScope.launchCatching { + viewModel.getHasPartyNotification().collect { val partyMenuItem = getItemWithIdentifier(SIDEBAR_PARTY) partyMenuItem?.showBubble = it } - ) + } } fun openDrawer() { @@ -590,15 +767,20 @@ class NavigationDrawerFragment : DialogFragment() { } if (promotedItem == null) return@let promotedItem.pillText = context?.getString(R.string.sale) - promotedItem.pillBackground = context?.let { activePromo.pillBackgroundDrawable(it) } + promotedItem.pillBackground = + context?.let { activePromo.pillBackgroundDrawable(it) } createUpdatingJob(activePromo.promoType.name, { activePromo.isActive }, { - val diff = (activePromo.endDate.time - Date().time).toDuration(DurationUnit.SECONDS) + val diff = + (activePromo.endDate.time - Date().time).toDuration(DurationUnit.SECONDS) 1.toDuration(diff.getMinuteOrSeconds()) }) { if (activePromo.isActive) { - promotedItem.subtitle = context?.getString(R.string.sale_ends_in, activePromo.endDate.getShortRemainingString()) + promotedItem.subtitle = context?.getString( + R.string.sale_ends_in, + activePromo.endDate.getShortRemainingString() + ) updateItem(promotedItem) } else { promotedItem.subtitle = null 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 dc955b258..6476bd335 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 @@ -13,7 +13,6 @@ import com.habitrpg.android.habitica.data.InventoryRepository import com.habitrpg.android.habitica.databinding.FragmentStatsBinding import com.habitrpg.android.habitica.extensions.addOkButton import com.habitrpg.android.habitica.extensions.setScaledPadding -import com.habitrpg.android.habitica.helpers.ExceptionHandler import com.habitrpg.android.habitica.helpers.UserStatComputer import com.habitrpg.android.habitica.helpers.launchCatching import com.habitrpg.android.habitica.models.user.Stats @@ -24,6 +23,7 @@ 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 kotlinx.coroutines.flow.firstOrNull import javax.inject.Inject import kotlin.math.min @@ -181,9 +181,9 @@ class StatsFragment : BaseMainFragment() { } private fun allocatePoint(stat: Attribute) { - compositeSubscription.add( - userRepository.allocatePoint(stat).subscribe({ }, ExceptionHandler.rx()) - ) + lifecycleScope.launchCatching { + userRepository.allocatePoint(stat) + } } private fun updateAttributePoints(user: User) { @@ -263,74 +263,68 @@ class StatsFragment : BaseMainFragment() { outfitList.add(thisOutfit.weapon) } - compositeSubscription.add( - inventoryRepository.getEquipment(outfitList).firstElement() - .retry(1) - .subscribe( - { - val levelStat = min((user.stats?.lvl ?: 0) / 2.0f, 50f).toInt() + lifecycleScope.launchCatching { + val equipment = inventoryRepository.getEquipment(outfitList).firstOrNull() + val levelStat = min((user.stats?.lvl ?: 0) / 2.0f, 50f).toInt() - totalStrength = levelStat - totalIntelligence = levelStat - totalConstitution = levelStat - totalPerception = levelStat + totalStrength = levelStat + totalIntelligence = levelStat + totalConstitution = levelStat + totalPerception = levelStat - binding?.strengthStatsView?.levelValue = levelStat - binding?.intelligenceStatsView?.levelValue = levelStat - binding?.constitutionStatsView?.levelValue = levelStat - binding?.perceptionStatsView?.levelValue = levelStat + binding?.strengthStatsView?.levelValue = levelStat + binding?.intelligenceStatsView?.levelValue = levelStat + binding?.constitutionStatsView?.levelValue = levelStat + binding?.perceptionStatsView?.levelValue = levelStat - totalStrength += user.stats?.buffs?.str?.toInt() ?: 0 - totalIntelligence += user.stats?.buffs?._int?.toInt() ?: 0 - totalConstitution += user.stats?.buffs?.con?.toInt() ?: 0 - totalPerception += user.stats?.buffs?.per?.toInt() ?: 0 - binding?.strengthStatsView?.buffValue = user.stats?.buffs?.str?.toInt() ?: 0 - binding?.intelligenceStatsView?.buffValue = - user.stats?.buffs?._int?.toInt() ?: 0 - binding?.constitutionStatsView?.buffValue = - user.stats?.buffs?.con?.toInt() ?: 0 - binding?.perceptionStatsView?.buffValue = - user.stats?.buffs?.per?.toInt() ?: 0 + totalStrength += user.stats?.buffs?.str?.toInt() ?: 0 + totalIntelligence += user.stats?.buffs?._int?.toInt() ?: 0 + totalConstitution += user.stats?.buffs?.con?.toInt() ?: 0 + totalPerception += user.stats?.buffs?.per?.toInt() ?: 0 + binding?.strengthStatsView?.buffValue = user.stats?.buffs?.str?.toInt() ?: 0 + binding?.intelligenceStatsView?.buffValue = + user.stats?.buffs?._int?.toInt() ?: 0 + binding?.constitutionStatsView?.buffValue = + user.stats?.buffs?.con?.toInt() ?: 0 + binding?.perceptionStatsView?.buffValue = + user.stats?.buffs?.per?.toInt() ?: 0 - totalStrength += user.stats?.strength ?: 0 - totalIntelligence += user.stats?.intelligence ?: 0 - totalConstitution += user.stats?.constitution ?: 0 - totalPerception += user.stats?.per ?: 0 - binding?.strengthStatsView?.allocatedValue = user.stats?.strength ?: 0 - binding?.intelligenceStatsView?.allocatedValue = - user.stats?.intelligence ?: 0 - binding?.constitutionStatsView?.allocatedValue = - user.stats?.constitution ?: 0 - binding?.perceptionStatsView?.allocatedValue = user.stats?.per ?: 0 - val userStatComputer = UserStatComputer() - val statsRows = userStatComputer.computeClassBonus(it, user) + totalStrength += user.stats?.strength ?: 0 + totalIntelligence += user.stats?.intelligence ?: 0 + totalConstitution += user.stats?.constitution ?: 0 + totalPerception += user.stats?.per ?: 0 + binding?.strengthStatsView?.allocatedValue = user.stats?.strength ?: 0 + binding?.intelligenceStatsView?.allocatedValue = + user.stats?.intelligence ?: 0 + binding?.constitutionStatsView?.allocatedValue = + user.stats?.constitution ?: 0 + binding?.perceptionStatsView?.allocatedValue = user.stats?.per ?: 0 + val userStatComputer = UserStatComputer() + val statsRows = userStatComputer.computeClassBonus(equipment, user) - var strength = 0 - var intelligence = 0 - var constitution = 0 - var perception = 0 + var strength = 0 + var intelligence = 0 + var constitution = 0 + var perception = 0 - for (row in statsRows) { - if (row.javaClass == UserStatComputer.AttributeRow::class.java) { - val attributeRow = row as UserStatComputer.AttributeRow - strength += attributeRow.strVal.toInt() - intelligence += attributeRow.intVal.toInt() - constitution += attributeRow.conVal.toInt() - perception += attributeRow.perVal.toInt() - } - } + for (row in statsRows) { + if (row.javaClass == UserStatComputer.AttributeRow::class.java) { + val attributeRow = row as UserStatComputer.AttributeRow + strength += attributeRow.strVal.toInt() + intelligence += attributeRow.intVal.toInt() + constitution += attributeRow.conVal.toInt() + perception += attributeRow.perVal.toInt() + } + } - totalStrength += strength - totalIntelligence += intelligence - totalConstitution += constitution - totalPerception += perception - binding?.strengthStatsView?.equipmentValue = strength - binding?.intelligenceStatsView?.equipmentValue = intelligence - binding?.constitutionStatsView?.equipmentValue = constitution - binding?.perceptionStatsView?.equipmentValue = perception - }, - ExceptionHandler.rx() - ) - ) + totalStrength += strength + totalIntelligence += intelligence + totalConstitution += constitution + totalPerception += perception + binding?.strengthStatsView?.equipmentValue = strength + binding?.intelligenceStatsView?.equipmentValue = intelligence + binding?.constitutionStatsView?.equipmentValue = constitution + binding?.perceptionStatsView?.equipmentValue = perception + } } } 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 f09793524..919977db5 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 @@ -39,10 +39,9 @@ import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaBottomSheetDialog import com.habitrpg.common.habitica.extensions.dpToPx import com.habitrpg.common.habitica.extensions.getThemeColor -import io.reactivex.rxjava3.core.BackpressureStrategy -import io.reactivex.rxjava3.kotlin.combineLatest -import io.reactivex.rxjava3.subjects.BehaviorSubject -import io.reactivex.rxjava3.subjects.PublishSubject +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch import javax.inject.Inject @@ -71,8 +70,8 @@ class AvatarCustomizationFragment : internal var adapter: CustomizationRecyclerViewAdapter = CustomizationRecyclerViewAdapter() internal var layoutManager: FlexboxLayoutManager = FlexboxLayoutManager(activity, ROW) - private val currentFilter = BehaviorSubject.create() - private val ownedCustomizations = PublishSubject.create>() + private val currentFilter = MutableStateFlow(CustomizationFilter(false, type != "background")) + private val ownedCustomizations = MutableStateFlow>(emptyList()) override fun onCreateView( inflater: LayoutInflater, @@ -95,11 +94,11 @@ class AvatarCustomizationFragment : } } - compositeSubscription.add( - this.inventoryRepository.getInAppRewards() + lifecycleScope.launchCatching { + inventoryRepository.getInAppRewards() .map { rewards -> rewards.map { it.key } } - .subscribe({ adapter.setPinnedItemKeys(it) }, ExceptionHandler.rx()) - ) + .collect { adapter.setPinnedItemKeys(it) } + } return super.onCreateView(inflater, container, savedInstanceState) } @@ -127,7 +126,6 @@ class AvatarCustomizationFragment : this.loadCustomizations() userViewModel.user.observe(viewLifecycleOwner) { updateUser(it) } - currentFilter.onNext(CustomizationFilter(false, type != "background")) binding?.recyclerView?.doOnLayout { adapter.columnCount = it.width / (80.dpToPx(context)) @@ -182,15 +180,13 @@ class AvatarCustomizationFragment : private fun loadCustomizations() { val type = this.type ?: return - compositeSubscription.add( + lifecycleScope.launchCatching { customizationRepository.getCustomizations(type, category, false) - .combineLatest( - currentFilter.toFlowable(BackpressureStrategy.DROP), - ownedCustomizations.toFlowable(BackpressureStrategy.DROP) - ) - .subscribe( - { (customizations, filter, ownedCustomizations) -> - adapter.ownedCustomizations = ownedCustomizations.map { it.key + "_" + it.type + "_" + it.category } + .combine(currentFilter) { customizations, filter -> Pair(customizations, filter) } + .combine(ownedCustomizations) { pair, ownedCustomizations -> Triple(pair.first, pair.second, ownedCustomizations) } + .collect { (customizations, filter, ownedCustomizations) -> + adapter.ownedCustomizations = + ownedCustomizations.map { it.key + "_" + it.type + "_" + it.category } if (filter.isFiltering) { val displayedCustomizations = mutableListOf() for (customization in customizations) { @@ -213,13 +209,15 @@ class AvatarCustomizationFragment : } ) } - }, - ExceptionHandler.rx() - ) - ) + } + } if (type == "hair" && (category == "beard" || category == "mustache")) { val otherCategory = if (category == "mustache") "beard" else "mustache" - compositeSubscription.add(customizationRepository.getCustomizations(type, otherCategory, true).subscribe({ adapter.additionalSetItems = it }, ExceptionHandler.rx())) + lifecycleScope.launchCatching { + customizationRepository.getCustomizations(type, otherCategory, true).collect { + adapter.additionalSetItems = it + } + } } } @@ -236,7 +234,7 @@ class AvatarCustomizationFragment : fun updateUser(user: User?) { if (user == null) return this.updateActiveCustomization(user) - ownedCustomizations.onNext(user.purchased?.customizations?.filter { it.type == this.type && it.purchased }) + ownedCustomizations.value = user.purchased?.customizations?.filter { it.type == this.type && it.purchased } ?: emptyList() this.adapter.userSize = user.preferences?.size this.adapter.hairColor = user.preferences?.hair?.color this.adapter.gemBalance = user.gemCount @@ -286,17 +284,17 @@ class AvatarCustomizationFragment : binding.showMeWrapper.check(if (filter.onlyPurchased) R.id.show_purchased_button else R.id.show_all_button) binding.showMeWrapper.setOnCheckedChangeListener { _, checkedId -> filter.onlyPurchased = checkedId == R.id.show_purchased_button - currentFilter.onNext(filter) + currentFilter.value = filter } binding.clearButton.setOnClickListener { - currentFilter.onNext(CustomizationFilter(false, type != "background")) + currentFilter.value = CustomizationFilter(false, type != "background") dialog.dismiss() } if (type == "background") { binding.sortByWrapper.check(if (filter.ascending) R.id.oldest_button else R.id.newest_button) binding.sortByWrapper.setOnCheckedChangeListener { _, checkedId -> filter.ascending = checkedId == R.id.oldest_button - currentFilter.onNext(filter) + currentFilter.value = filter } configureMonthFilterButton(binding.januaryButton, 1, filter) configureMonthFilterButton(binding.febuaryButton, 2, filter) @@ -333,7 +331,7 @@ class AvatarCustomizationFragment : button.typeface = Typeface.create("sans-serif-medium", Typeface.NORMAL) filter.months.add(identifier) } - currentFilter.onNext(filter) + currentFilter.value = filter } } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/equipment/EquipmentDetailFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/equipment/EquipmentDetailFragment.kt index 6dc48579f..0f6e84cfc 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/equipment/EquipmentDetailFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/equipment/EquipmentDetailFragment.kt @@ -12,12 +12,13 @@ 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.FragmentRefreshRecyclerviewBinding -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.ui.adapter.inventory.EquipmentRecyclerViewAdapter import com.habitrpg.android.habitica.ui.fragments.BaseMainFragment -import com.habitrpg.common.habitica.helpers.EmptyItem import com.habitrpg.android.habitica.ui.helpers.SafeDefaultItemAnimator +import com.habitrpg.common.habitica.helpers.EmptyItem import kotlinx.coroutines.launch import javax.inject.Inject @@ -45,10 +46,11 @@ class EquipmentDetailFragment : container: ViewGroup?, savedInstanceState: Bundle? ): View? { - compositeSubscription.add( - this.adapter.equipEvents.flatMapMaybe { key -> inventoryRepository.equipGear(key, isCostume ?: false).firstElement() } - .subscribe({ }, ExceptionHandler.rx()) - ) + adapter.onEquip = { + lifecycleScope.launchCatching { + inventoryRepository.equipGear(it, isCostume ?: false) + } + } return super.onCreateView(inflater, container, savedInstanceState) } @@ -82,7 +84,11 @@ class EquipmentDetailFragment : binding?.recyclerView?.addItemDecoration(DividerItemDecoration(getActivity(), DividerItemDecoration.VERTICAL)) binding?.recyclerView?.itemAnimator = SafeDefaultItemAnimator() - type?.let { type -> inventoryRepository.getOwnedEquipment(type).subscribe({ this.adapter.data = it }, ExceptionHandler.rx()) } + type?.let { type -> + lifecycleScope.launchCatching { + inventoryRepository.getOwnedEquipment(type).collect { adapter.data = it } + } + } } override fun onDestroy() { diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/items/ItemDialogFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/items/ItemDialogFragment.kt index b8a24cb9d..d89dce1ee 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/items/ItemDialogFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/items/ItemDialogFragment.kt @@ -14,9 +14,9 @@ import com.habitrpg.android.habitica.data.UserRepository import com.habitrpg.android.habitica.databinding.FragmentItemsDialogBinding 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.ExceptionHandler import com.habitrpg.android.habitica.helpers.MainNavigationController +import com.habitrpg.android.habitica.helpers.launchCatching import com.habitrpg.android.habitica.interactors.FeedPetUseCase import com.habitrpg.android.habitica.interactors.HatchPetUseCase import com.habitrpg.android.habitica.models.inventory.Egg @@ -183,69 +183,53 @@ class ItemDialogFragment : BaseDialogFragment() { adapter?.feedingPet = this.feedingPet } binding?.recyclerView?.adapter = adapter - - adapter?.let { adapter -> - compositeSubscription.add( - adapter.getSellItemFlowable() - .flatMap { item -> inventoryRepository.sellItem(item) } - .subscribe({ }, ExceptionHandler.rx()) - ) - - compositeSubscription.add( - adapter.getQuestInvitationFlowable() - .flatMap { quest -> inventoryRepository.inviteToQuest(quest) } - .subscribe( - { - lifecycleScope.launch(ExceptionHandler.coroutine()) { - socialRepository.retrieveGroup("party") - if (isModal) { - dismiss() - } else { - MainNavigationController.navigate(R.id.partyFragment) - } - } - }, - ExceptionHandler.rx() - ) - ) - compositeSubscription.add( - adapter.getOpenMysteryItemFlowable() - .flatMap { inventoryRepository.openMysteryItem(user) } - .doOnNext { - val activity = activity as? MainActivity - if (activity != null) { - val dialog = OpenedMysteryitemDialog(activity) - dialog.isCelebratory = true - dialog.setTitle(R.string.mystery_item_title) - dialog.binding.iconView.loadImage("shop_${it.key}") - dialog.binding.titleView.text = it.text - dialog.binding.descriptionView.text = it.notes - dialog.addButton(R.string.equip, true) { _, _ -> - inventoryRepository.equip("equipped", it.key ?: "").subscribe({}, ExceptionHandler.rx()) - } - dialog.addCloseButton() - dialog.enqueue() + adapter?.onSellItem = { + lifecycleScope.launchCatching { + inventoryRepository.sellItem(it) + } + } + adapter?.onQuestInvitation = { + lifecycleScope.launchCatching { + inventoryRepository.inviteToQuest(it) + MainNavigationController.navigate(R.id.partyFragment) + } + } + adapter?.onOpenMysteryItem = { + lifecycleScope.launchCatching { + val item = inventoryRepository.openMysteryItem(user) ?: return@launchCatching + val activity = activity as? MainActivity + if (activity != null) { + val dialog = OpenedMysteryitemDialog(activity) + dialog.isCelebratory = true + dialog.setTitle(R.string.mystery_item_title) + dialog.binding.iconView.loadImage("shop_${it.key}") + dialog.binding.titleView.text = item.text + dialog.binding.descriptionView.text = item.notes + dialog.addButton(R.string.equip, true) { _, _ -> + lifecycleScope.launchCatching { + inventoryRepository.equip("equipped", it.key) } } - .subscribe({ }, ExceptionHandler.rx()) - ) - compositeSubscription.add(adapter.hatchPetEvents.subscribeWithErrorHandler { hatchPet(it.first, it.second) }) - compositeSubscription.add(adapter.feedPetEvents.subscribeWithErrorHandler { feedPet(it) }) + dialog.addCloseButton() + dialog.enqueue() + } + } } + adapter?.onHatchPet = { pet, egg -> hatchPet(pet, egg) } } } private fun feedPet(food: Food) { val pet = feedingPet ?: return val activity = activity ?: return - parentSubscription?.add( - feedPetUseCase.observable( + lifecycleScope.launchCatching { + feedPetUseCase.callInteractor( FeedPetUseCase.RequestValues( pet, food, activity ) - ).subscribeWithErrorHandler {} - ) + ) + } } override fun onResume() { @@ -267,14 +251,14 @@ class ItemDialogFragment : BaseDialogFragment() { private fun hatchPet(potion: HatchingPotion, egg: Egg) { dismiss() val activity = activity ?: return - parentSubscription?.add( - hatchPetUseCase.observable( + activity.lifecycleScope.launchCatching { + hatchPetUseCase.callInteractor( HatchPetUseCase.RequestValues( potion, egg, activity ) - ).subscribeWithErrorHandler {} - ) + ) + } } private fun loadItems() { 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 345df8dda..92dbffea9 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 @@ -18,7 +18,6 @@ import com.habitrpg.android.habitica.data.UserRepository 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.ExceptionHandler import com.habitrpg.android.habitica.helpers.MainNavigationController import com.habitrpg.android.habitica.helpers.launchCatching @@ -141,50 +140,42 @@ class ItemRecyclerFragment : BaseFragment(), SwipeRefreshL adapter = ItemRecyclerAdapter(context) } binding?.recyclerView?.adapter = adapter - adapter?.useSpecialEvents?.subscribeWithErrorHandler { onSpecialItemSelected(it) }?.let { compositeSubscription.add(it) } - adapter?.let { adapter -> - compositeSubscription.add( - adapter.getSellItemFlowable() - .flatMap { item -> inventoryRepository.sellItem(item) } - .subscribe({ }, ExceptionHandler.rx()) - ) - - compositeSubscription.add( - adapter.getQuestInvitationFlowable() - .flatMap { quest -> inventoryRepository.inviteToQuest(quest) } - //.flatMap { socialRepository.retrieveGroup("party") } - .subscribe( - { - MainNavigationController.navigate(R.id.partyFragment) - }, - ExceptionHandler.rx() - ) - ) - compositeSubscription.add( - adapter.getOpenMysteryItemFlowable() - .flatMap { inventoryRepository.openMysteryItem(user) } - .doOnNext { - val activity = activity as? MainActivity - if (activity != null) { - val dialog = OpenedMysteryitemDialog(activity) - dialog.isCelebratory = true - dialog.setTitle(R.string.mystery_item_title) - dialog.binding.iconView.loadImage("shop_${it.key}") - dialog.binding.titleView.text = it.text - dialog.binding.descriptionView.text = it.notes - dialog.addButton(R.string.equip, true) { _, _ -> - inventoryRepository.equip("equipped", it.key ?: "").subscribe({}, ExceptionHandler.rx()) - } - dialog.addCloseButton() - dialog.enqueue() + adapter?.onUseSpecialItem = { onSpecialItemSelected(it) } + adapter?.onSellItem = { + lifecycleScope.launchCatching { + inventoryRepository.sellItem(it) + } + } + adapter?.onQuestInvitation = { + lifecycleScope.launchCatching { + inventoryRepository.inviteToQuest(it) + MainNavigationController.navigate(R.id.partyFragment) + } + } + adapter?.onOpenMysteryItem = { + lifecycleScope.launchCatching { + val item = inventoryRepository.openMysteryItem(user) ?: return@launchCatching + val activity = activity as? MainActivity + if (activity != null) { + val dialog = OpenedMysteryitemDialog(activity) + dialog.isCelebratory = true + dialog.setTitle(R.string.mystery_item_title) + dialog.binding.iconView.loadImage("shop_${it.key}") + dialog.binding.titleView.text = item.text + dialog.binding.descriptionView.text = item.notes + dialog.addButton(R.string.equip, true) { _, _ -> + lifecycleScope.launchCatching { + inventoryRepository.equip("equipped", it.key) } } - .subscribe({ }, ExceptionHandler.rx()) - ) - compositeSubscription.add(adapter.startHatchingEvents.subscribeWithErrorHandler { showHatchingDialog(it) }) - compositeSubscription.add(adapter.hatchPetEvents.subscribeWithErrorHandler { hatchPet(it.first, it.second) }) - compositeSubscription.addAll(adapter.startNewPartyEvents.subscribeWithErrorHandler { createNewParty() }) + dialog.addCloseButton() + dialog.enqueue() + } + } } + adapter?.onStartHatching = { showHatchingDialog(it) } + adapter?.onHatchPet = { pet, egg -> hatchPet(pet, egg) } + adapter?.onCreateNewParty = { createNewParty() } } } @@ -218,14 +209,14 @@ class ItemRecyclerFragment : BaseFragment(), SwipeRefreshL private fun hatchPet(potion: HatchingPotion, egg: Egg) { (activity as? BaseActivity)?.let { - compositeSubscription.add( - hatchPetUseCase.observable( + lifecycleScope.launchCatching { + hatchPetUseCase.callInteractor( HatchPetUseCase.RequestValues( potion, egg, it ) - ).subscribeWithErrorHandler {} - ) + ) + } } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/shops/ShopFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/shops/ShopFragment.kt index 26adcc2f4..f54215d23 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/shops/ShopFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/shops/ShopFragment.kt @@ -14,6 +14,7 @@ import com.habitrpg.android.habitica.data.SocialRepository import com.habitrpg.android.habitica.databinding.FragmentRefreshRecyclerviewBinding import com.habitrpg.android.habitica.helpers.AppConfigManager import com.habitrpg.android.habitica.helpers.ExceptionHandler +import com.habitrpg.android.habitica.helpers.launchCatching import com.habitrpg.android.habitica.models.shops.Shop import com.habitrpg.android.habitica.models.shops.ShopCategory import com.habitrpg.android.habitica.models.shops.ShopItem @@ -26,8 +27,8 @@ import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel import com.habitrpg.android.habitica.ui.views.CurrencyViews import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog import com.habitrpg.common.habitica.helpers.RecyclerViewState -import io.reactivex.rxjava3.kotlin.combineLatest import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch import javax.inject.Inject @@ -180,62 +181,48 @@ open class ShopFragment : BaseMainFragment() Shop.SEASONAL_SHOP -> "seasonal" else -> "" } - compositeSubscription.add( - this.inventoryRepository.retrieveShopInventory(shopUrl) - .map { shop1 -> - if (shop1.identifier == Shop.MARKET) { - val user = userViewModel.user.value - val specialCategory = ShopCategory() - specialCategory.text = getString(R.string.special) - if (user?.isValid == true && user.purchased?.plan?.isActive == true) { - val item = ShopItem.makeGemItem(context?.resources) - item.limitedNumberLeft = user.purchased?.plan?.numberOfGemsLeft - specialCategory.items.add(item) - } - specialCategory.items.add(ShopItem.makeFortifyItem(context?.resources)) - shop1.categories.add(specialCategory) - } - when (shop1.identifier) { - Shop.TIME_TRAVELERS_SHOP -> { - formatTimeTravelersShop(shop1) - } - Shop.SEASONAL_SHOP -> { - shop1.categories.sortWith( - compareBy { it.items.size != 1 } - .thenBy { it.items.firstOrNull()?.currency != "gold" } - .thenByDescending { it.items.firstOrNull()?.event?.end } - ) - shop1 - } - else -> { - shop1 - } - } + lifecycleScope.launchCatching({ + binding?.recyclerView?.state = RecyclerViewState.FAILED + }) { + val shop1 = inventoryRepository.retrieveShopInventory(shopUrl) ?: return@launchCatching + if (shop1.identifier == Shop.MARKET) { + val user = userViewModel.user.value + val specialCategory = ShopCategory() + specialCategory.text = getString(R.string.special) + if (user?.isValid == true && user.purchased?.plan?.isActive == true) { + val item = ShopItem.makeGemItem(context?.resources) + item.limitedNumberLeft = user.purchased?.plan?.numberOfGemsLeft + specialCategory.items.add(item) } - .subscribe( - { - this.shop = it - this.adapter?.setShop(it) - }, - { - binding?.recyclerView?.state = RecyclerViewState.FAILED - ExceptionHandler.reportError(it) - }, - { - binding?.refreshLayout?.isRefreshing = false - } - ) - ) + specialCategory.items.add(ShopItem.makeFortifyItem(context?.resources)) + shop1.categories.add(specialCategory) + } + when (shop1.identifier) { + Shop.TIME_TRAVELERS_SHOP -> { + formatTimeTravelersShop(shop1) + } + Shop.SEASONAL_SHOP -> { + shop1.categories.sortWith( + compareBy { it.items.size != 1 } + .thenBy { it.items.firstOrNull()?.currency != "gold" } + .thenByDescending { it.items.firstOrNull()?.event?.end } + ) + } + } + shop = shop1 + adapter?.setShop(shop1) + binding?.refreshLayout?.isRefreshing = false + } - compositeSubscription.add( - this.inventoryRepository.getOwnedItems() - .subscribe({ adapter?.setOwnedItems(it) }, ExceptionHandler.rx()) - ) - compositeSubscription.add( - this.inventoryRepository.getInAppRewards() + lifecycleScope.launchCatching { + inventoryRepository.getOwnedItems() + .collect { adapter?.setOwnedItems(it) } + } + lifecycleScope.launchCatching { + inventoryRepository.getInAppRewards() .map { rewards -> rewards.map { it.key } } - .subscribe({ adapter?.setPinnedItemKeys(it) }, ExceptionHandler.rx()) - ) + .collect { adapter?.setPinnedItemKeys(it) } + } } private fun formatTimeTravelersShop(shop: Shop): Shop { @@ -264,29 +251,22 @@ open class ShopFragment : BaseMainFragment() } private fun loadMarketGear() { - compositeSubscription.add( - inventoryRepository.retrieveMarketGear() - .combineLatest( - inventoryRepository.getOwnedEquipment().map { equipment -> equipment.map { it.key } } - ) - .map { (shop, equipment) -> - for (category in shop.categories) { + lifecycleScope.launchCatching { + val shop = inventoryRepository.retrieveMarketGear() + inventoryRepository.getOwnedEquipment() + .map { equipment -> equipment.map { it.key } } + .collect { equipment -> + for (category in shop?.categories ?: emptyList()) { val items = category.items.asSequence().filter { !equipment.contains(it.key) }.sortedBy { it.locked }.toList() category.items.clear() category.items.addAll(items) } - shop + gearCategories = shop?.categories + adapter?.gearCategories = shop?.categories ?: mutableListOf() } - .subscribe( - { - this.gearCategories = it.categories - adapter?.gearCategories = it.categories - }, - ExceptionHandler.rx() - ) - ) + } } override fun injectFragment(component: UserComponent) { diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/stable/MountDetailRecyclerFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/stable/MountDetailRecyclerFragment.kt index 2d108d89a..3ad7102b4 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/stable/MountDetailRecyclerFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/stable/MountDetailRecyclerFragment.kt @@ -12,6 +12,7 @@ import com.habitrpg.android.habitica.data.InventoryRepository import com.habitrpg.android.habitica.databinding.FragmentRefreshRecyclerviewBinding import com.habitrpg.android.habitica.extensions.getTranslatedType import com.habitrpg.android.habitica.helpers.ExceptionHandler +import com.habitrpg.android.habitica.helpers.launchCatching import com.habitrpg.android.habitica.models.inventory.Mount import com.habitrpg.android.habitica.models.inventory.StableSection import com.habitrpg.android.habitica.models.user.OwnedMount @@ -97,13 +98,12 @@ class MountDetailRecyclerFragment : binding?.recyclerView?.itemAnimator = SafeDefaultItemAnimator() this.loadItems() - adapter?.getEquipFlowable()?.flatMap { key -> inventoryRepository.equip("mount", key) } - ?.subscribe( - { - adapter?.currentMount = it.currentMount - }, - ExceptionHandler.rx() - )?.let { compositeSubscription.add(it) } + adapter?.onEquip = { + lifecycleScope.launchCatching { + val items = inventoryRepository.equip("mount", it) + adapter?.currentMount = items?.currentMount + } + } } userViewModel.user.observe(viewLifecycleOwner) { adapter?.currentMount = it?.currentMount } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/stable/PetDetailRecyclerFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/stable/PetDetailRecyclerFragment.kt index 3ec178450..abfc35864 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/stable/PetDetailRecyclerFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/stable/PetDetailRecyclerFragment.kt @@ -13,6 +13,7 @@ import com.habitrpg.android.habitica.databinding.FragmentRefreshRecyclerviewBind import com.habitrpg.android.habitica.extensions.getTranslatedType 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.FeedPetUseCase import com.habitrpg.android.habitica.models.inventory.Egg import com.habitrpg.android.habitica.models.inventory.Food @@ -124,23 +125,19 @@ class PetDetailRecyclerFragment : binding?.recyclerView?.itemAnimator = SafeDefaultItemAnimator() this.loadItems() - compositeSubscription.add( - adapter.getEquipFlowable() - .flatMap { key -> inventoryRepository.equip("pet", key) } - .subscribe( - { - adapter.currentPet = it.currentPet - }, - ExceptionHandler.rx() - ) - ) + adapter.onEquip = { + lifecycleScope.launchCatching { + val items = inventoryRepository.equip("pet", it) + adapter.currentPet = items?.currentPet + } + } userViewModel.user.observe(viewLifecycleOwner) { adapter.currentPet = it?.currentPet } - compositeSubscription.add(adapter.feedFlowable.subscribe({ + adapter.onFeed = { pet, food -> showFeedingDialog( - it.first, - it.second + pet, + food ) - }, ExceptionHandler.rx())) + } view.post { setGridSpanCount(view.width) } } @@ -179,10 +176,9 @@ class PetDetailRecyclerFragment : return@map mountMap }.collect { adapter.setOwnedMounts(it) } } - compositeSubscription.add( - inventoryRepository.getOwnedItems(true) - .subscribe({ adapter.setOwnedItems(it) }, ExceptionHandler.rx()) - ) + lifecycleScope.launchCatching { + inventoryRepository.getOwnedItems(true).collect { adapter.setOwnedItems(it) } + } lifecycleScope.launch(ExceptionHandler.coroutine()) { val mounts = inventoryRepository.getMounts( animalType, @@ -231,14 +227,14 @@ class PetDetailRecyclerFragment : private fun showFeedingDialog(pet: Pet, food: Food?) { if (food != null) { val context = activity ?: context ?: return - compositeSubscription.add( - feedPetUseCase.observable( + lifecycleScope.launchCatching { + feedPetUseCase.callInteractor( FeedPetUseCase.RequestValues( pet, food, context ) - ).subscribeWithErrorHandler {} - ) + ) + } return } val fragment = ItemDialogFragment() diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/stable/StableRecyclerFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/stable/StableRecyclerFragment.kt index ac4ae7502..615c21d74 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/stable/StableRecyclerFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/stable/StableRecyclerFragment.kt @@ -15,6 +15,7 @@ import com.habitrpg.android.habitica.data.UserRepository import com.habitrpg.android.habitica.databinding.FragmentRefreshRecyclerviewBinding import com.habitrpg.android.habitica.helpers.AppConfigManager import com.habitrpg.android.habitica.helpers.ExceptionHandler +import com.habitrpg.android.habitica.helpers.launchCatching import com.habitrpg.android.habitica.models.inventory.Egg import com.habitrpg.android.habitica.models.inventory.HatchingPotion import com.habitrpg.android.habitica.ui.adapter.inventory.StableRecyclerAdapter @@ -118,11 +119,11 @@ class StableRecyclerFragment : binding?.recyclerView?.itemAnimator = SafeDefaultItemAnimator() adapter?.let { - compositeSubscription.add( - it.getEquipFlowable() - .flatMap { key -> inventoryRepository.equip(if (itemType == "pets") "pet" else "mount", key) } - .subscribe({ }, ExceptionHandler.rx()) - ) + it.onEquip = { + lifecycleScope.launchCatching { + inventoryRepository.equip(if (itemType == "pets") "pet" else "mount", it) + } + } } } userViewModel.user.observe(viewLifecycleOwner) { 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 09e002eae..95453313e 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 @@ -205,12 +205,11 @@ class AccountPreferenceFragment : val dialog = HabiticaAlertDialog(context) dialog.setTitle(R.string.are_you_sure) dialog.addButton(R.string.disconnect, true) { _, _ -> - apiClient.disconnectSocial(network) - .subscribe({ - lifecycleScope.launch(ExceptionHandler.coroutine()) { - userRepository.retrieveUser(true, true) - } - displayDisconnectSuccess(networkName) }, ExceptionHandler.rx()) + lifecycleScope.launch { + apiClient.disconnectSocial(network) + userRepository.retrieveUser(true, true) + displayDisconnectSuccess(networkName) + } } dialog.addCancelButton() dialog.show() @@ -282,19 +281,17 @@ class AccountPreferenceFragment : passwordEditText?.showErrorIfNecessary() passwordRepeatEditText?.showErrorIfNecessary() if (passwordEditText?.isValid != true || passwordRepeatEditText?.isValid != true) return@addButton - userRepository.updatePassword( - oldPasswordEditText?.text ?: "", - passwordEditText.text ?: "", - passwordRepeatEditText.text ?: "") - .subscribe( - { - (activity as? SnackbarActivity)?.showSnackbar( - content = context.getString(R.string.password_changed), - displayType = HabiticaSnackbar.SnackbarDisplayType.SUCCESS - ) - }, - ExceptionHandler.rx() + lifecycleScope.launchCatching { + userRepository.updatePassword( + oldPasswordEditText?.text ?: "", + passwordEditText.text ?: "", + passwordRepeatEditText.text ?: "" ) + (activity as? SnackbarActivity)?.showSnackbar( + content = context.getString(R.string.password_changed), + displayType = HabiticaSnackbar.SnackbarDisplayType.SUCCESS + ) + } dialog.dismiss() } dialog.addCancelButton() @@ -331,16 +328,18 @@ class AccountPreferenceFragment : passwordRepeatEditText?.showErrorIfNecessary() if ((showEmail && emailEditText?.isValid != true) || passwordEditText?.isValid != true || passwordRepeatEditText?.isValid != true) return@addButton val email = if (showEmail) emailEditText?.text else user?.authentication?.findFirstSocialEmail() - apiClient.registerUser(user?.username ?: "", email ?: "", passwordEditText.text ?: "", passwordRepeatEditText?.text ?: "") - .subscribe( - { - (activity as? SnackbarActivity)?.showSnackbar( - content = context.getString(R.string.password_added), - displayType = HabiticaSnackbar.SnackbarDisplayType.SUCCESS - ) - }, - ExceptionHandler.rx() + lifecycleScope.launchCatching { + apiClient.registerUser( + user?.username ?: "", + email ?: "", + passwordEditText.text ?: "", + passwordRepeatEditText?.text ?: "" ) + (activity as? SnackbarActivity)?.showSnackbar( + content = context.getString(R.string.password_added), + displayType = HabiticaSnackbar.SnackbarDisplayType.SUCCESS + ) + } dialog.dismiss() } dialog.addCancelButton() @@ -369,16 +368,16 @@ class AccountPreferenceFragment : KeyboardUtil.dismissKeyboard(activity) emailEditText?.showErrorIfNecessary() if (emailEditText?.isValid != true) return@addButton - userRepository.updateEmail(emailEditText.text.toString(), passwordEditText?.text.toString()) - .subscribe( - { - lifecycleScope.launch(ExceptionHandler.coroutine()) { - userRepository.retrieveUser(true, true) - } - configurePreference(findPreference("email"), emailEditText.text.toString()) - }, - ExceptionHandler.rx() + lifecycleScope.launchCatching { + userRepository.updateEmail( + emailEditText.text.toString(), + passwordEditText?.text.toString() ) + lifecycleScope.launch(ExceptionHandler.coroutine()) { + userRepository.retrieveUser(true, true) + } + configurePreference(findPreference("email"), emailEditText.text.toString()) + } dialog.dismiss() } dialog.addCancelButton() @@ -442,24 +441,23 @@ class AccountPreferenceFragment : private fun deleteAccount(password: String) { val dialog = HabiticaProgressDialog.show(context, R.string.deleting_account) - compositeSubscription.add( - userRepository.deleteAccount(password).subscribe({ _ -> + lifecycleScope.launchCatching({ throwable -> + dialog?.dismiss() + if (throwable is HttpException && throwable.code() == 401) { + val errorDialog = context?.let { HabiticaAlertDialog(it) } + errorDialog?.setTitle(R.string.authentication_error_title) + errorDialog?.setMessage(R.string.incorrect_password) + errorDialog?.addCloseButton() + errorDialog?.show() + } + ExceptionHandler.reportError(throwable) + }) { + userRepository.deleteAccount(password) dialog?.dismiss() accountDialog.dismiss() context?.let { HabiticaBaseApplication.logout(it) } activity?.finish() - }) { throwable -> - dialog?.dismiss() - if (throwable is HttpException && throwable.code() == 401) { - val errorDialog = context?.let { HabiticaAlertDialog(it) } - errorDialog?.setTitle(R.string.authentication_error_title) - errorDialog?.setMessage(R.string.incorrect_password) - errorDialog?.addCloseButton() - errorDialog?.show() - } - ExceptionHandler.reportError(throwable) - } - ) + } } private fun showAccountResetConfirmation(user: User?) { 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 8191c4983..5ff94f39d 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 @@ -70,7 +70,9 @@ class PreferencesFragment : BasePreferencesFragment(), SharedPreferences.OnShare super.onViewCreated(view, savedInstanceState) listView.itemAnimator = null - userRepository.retrieveTeamPlans().subscribe({}, ExceptionHandler.rx()) + lifecycleScope.launchCatching { + userRepository.retrieveTeamPlans() + } } override fun setupPreferences() { @@ -227,7 +229,9 @@ class PreferencesFragment : BasePreferencesFragment(), SharedPreferences.OnShare "cds_time" -> { val timeval = sharedPreferences.getString("cds_time", "0") ?: "0" val hour = Integer.parseInt(timeval) - userRepository.changeCustomDayStart(hour).subscribe({ }, ExceptionHandler.rx()) + lifecycleScope.launchCatching { + userRepository.changeCustomDayStart(hour) + } val preference = findPreference(key) preference?.summary = preference?.entry } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/purchases/GiftBalanceGemsFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/purchases/GiftBalanceGemsFragment.kt index 6e6d6afd0..4dde1b73f 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/purchases/GiftBalanceGemsFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/purchases/GiftBalanceGemsFragment.kt @@ -10,10 +10,9 @@ import com.habitrpg.android.habitica.components.UserComponent import com.habitrpg.android.habitica.data.SocialRepository import com.habitrpg.android.habitica.data.UserRepository import com.habitrpg.android.habitica.databinding.FragmentGiftGemBalanceBinding -import com.habitrpg.android.habitica.helpers.ExceptionHandler +import com.habitrpg.android.habitica.helpers.launchCatching import com.habitrpg.android.habitica.models.members.Member import com.habitrpg.android.habitica.ui.fragments.BaseFragment -import kotlinx.coroutines.launch import javax.inject.Inject class GiftBalanceGemsFragment : BaseFragment() { @@ -64,20 +63,12 @@ class GiftBalanceGemsFragment : BaseFragment() { try { val amount = binding?.giftEditText?.text.toString().toInt() giftedMember?.id?.let { - compositeSubscription.add( + lifecycleScope.launchCatching({ + isGifting = false + }) { socialRepository.transferGems(it, amount) - .doOnError { - isGifting = false - } - .subscribe( - { - lifecycleScope.launch(ExceptionHandler.coroutine()) { - userRepository.retrieveUser(false) - } - activity?.finish() - }, ExceptionHandler.rx() - ) - ) + userRepository.retrieveUser(false) + } } } catch (ignored: NumberFormatException) {} } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/purchases/SubscriptionFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/purchases/SubscriptionFragment.kt index 330503222..022da9244 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/purchases/SubscriptionFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/purchases/SubscriptionFragment.kt @@ -18,9 +18,10 @@ import com.habitrpg.android.habitica.databinding.FragmentSubscriptionBinding import com.habitrpg.android.habitica.extensions.addCancelButton import com.habitrpg.android.habitica.helpers.AmplitudeManager import com.habitrpg.android.habitica.helpers.AppConfigManager +import com.habitrpg.android.habitica.helpers.ExceptionHandler import com.habitrpg.android.habitica.helpers.PurchaseHandler import com.habitrpg.android.habitica.helpers.PurchaseTypes -import com.habitrpg.android.habitica.helpers.ExceptionHandler +import com.habitrpg.android.habitica.helpers.launchCatching import com.habitrpg.android.habitica.models.user.User import com.habitrpg.android.habitica.ui.activities.GiftSubscriptionActivity import com.habitrpg.android.habitica.ui.fragments.BaseFragment @@ -88,15 +89,19 @@ class SubscriptionFragment : BaseFragment() { binding?.refreshLayout?.setOnRefreshListener { refresh() } - compositeSubscription.add( - inventoryRepository.getLatestMysteryItem().subscribe( - { - binding?.subBenefitsMysteryItemIcon?.loadImage("shop_set_mystery_${it.key?.split("_")?.last()}") - binding?.subBenefitsMysteryItemText?.text = context?.getString(R.string.subscribe_listitem3_description_new, it.text) - }, - ExceptionHandler.rx() - ) - ) + lifecycleScope.launchCatching { + inventoryRepository.getLatestMysteryItem().collect { + binding?.subBenefitsMysteryItemIcon?.loadImage( + "shop_set_mystery_${ + it.key?.split( + "_" + )?.last() + }" + ) + binding?.subBenefitsMysteryItemText?.text = + context?.getString(R.string.subscribe_listitem3_description_new, it.text) + } + } AmplitudeManager.sendNavigationEvent("subscription screen") } 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 31dc320b3..36cf20998 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 @@ -15,7 +15,6 @@ import com.habitrpg.android.habitica.data.InventoryRepository 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 @@ -63,7 +62,11 @@ class AvatarSetupFragment : BaseFragment() { userRepository.updateUser(it) } } - adapter?.equipGearEvents?.flatMap { inventoryRepository.equip("equipped", it) }?.subscribeWithErrorHandler {}?.let { compositeSubscription.add(it) } + adapter?.onEquipGear = { + lifecycleScope.launchCatching { + inventoryRepository.equip("equipped", it) + } + } this.adapter?.user = this.user val layoutManager = LinearLayoutManager(activity) diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/setup/WelcomeFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/setup/WelcomeFragment.kt index 8746f1ca1..d4721ab31 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/setup/WelcomeFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/setup/WelcomeFragment.kt @@ -14,15 +14,17 @@ import com.habitrpg.android.habitica.components.UserComponent import com.habitrpg.android.habitica.data.UserRepository import com.habitrpg.android.habitica.databinding.FragmentWelcomeBinding import com.habitrpg.android.habitica.extensions.OnChangeTextWatcher -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.ui.fragments.BaseFragment import com.habitrpg.android.habitica.ui.views.HabiticaIconsHelper -import io.reactivex.rxjava3.core.BackpressureStrategy import io.reactivex.rxjava3.subjects.PublishSubject +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.firstOrNull +import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch -import java.util.concurrent.TimeUnit import javax.inject.Inject class WelcomeFragment : BaseFragment() { @@ -34,16 +36,25 @@ class WelcomeFragment : BaseFragment() { override var binding: FragmentWelcomeBinding? = null - override fun createBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentWelcomeBinding { + override fun createBinding( + inflater: LayoutInflater, + container: ViewGroup? + ): FragmentWelcomeBinding { return FragmentWelcomeBinding.inflate(inflater, container, false) } - private val displayNameVerificationEvents = PublishSubject.create() - private val usernameVerificationEvents = PublishSubject.create() + private val displayNameVerificationEvents = MutableStateFlow(null) + private val usernameVerificationEvents = MutableStateFlow(null) private val checkmarkIcon: Drawable by lazy { context?.let { - BitmapDrawable(resources, HabiticaIconsHelper.imageOfCheckmark(ContextCompat.getColor(it, R.color.green_50), 1f)) + BitmapDrawable( + resources, + HabiticaIconsHelper.imageOfCheckmark( + ContextCompat.getColor(it, R.color.green_50), + 1f + ) + ) } ?: VectorDrawable() } private val alertIcon: Drawable by lazy { @@ -63,53 +74,74 @@ class WelcomeFragment : BaseFragment() { binding?.displayNameEditText?.addTextChangedListener( OnChangeTextWatcher { p0, _, _, _ -> - displayNameVerificationEvents.onNext(p0.toString()) + displayNameVerificationEvents.value = p0.toString() } ) binding?.usernameEditText?.addTextChangedListener( OnChangeTextWatcher { p0, _, _, _ -> - usernameVerificationEvents.onNext(p0.toString()) + usernameVerificationEvents.value = p0.toString() } ) - compositeSubscription.add( - displayNameVerificationEvents.toFlowable(BackpressureStrategy.DROP) - .map { it.length in 1..30 } - .subscribeWithErrorHandler { + lifecycleScope.launchCatching { + displayNameVerificationEvents + .map { it?.length in 1..30 } + .collect { if (it) { - binding?.displayNameEditText?.setCompoundDrawablesWithIntrinsicBounds(null, null, checkmarkIcon, null) + binding?.displayNameEditText?.setCompoundDrawablesWithIntrinsicBounds( + null, + null, + checkmarkIcon, + null + ) binding?.issuesTextView?.visibility = View.GONE } else { - binding?.displayNameEditText?.setCompoundDrawablesWithIntrinsicBounds(null, null, alertIcon, null) + binding?.displayNameEditText?.setCompoundDrawablesWithIntrinsicBounds( + null, + null, + alertIcon, + null + ) binding?.issuesTextView?.visibility = View.VISIBLE - binding?.issuesTextView?.text = context?.getString(R.string.display_name_length_error) + binding?.issuesTextView?.text = + context?.getString(R.string.display_name_length_error) } } - ) - compositeSubscription.add( - usernameVerificationEvents.toFlowable(BackpressureStrategy.DROP) - .filter { it.length in 1..30 } - .throttleLast(1, TimeUnit.SECONDS) - .flatMap { userRepository.verifyUsername(it) } - .subscribeWithErrorHandler { - if (it.isUsable) { - binding?.usernameEditText?.setCompoundDrawablesWithIntrinsicBounds(null, null, checkmarkIcon, null) + } + lifecycleScope.launchCatching { + usernameVerificationEvents + .filter { it?.length in 1..30 } + .filterNotNull() + .map { userRepository.verifyUsername(it) } + .collect { + if (it?.isUsable == true) { + binding?.usernameEditText?.setCompoundDrawablesWithIntrinsicBounds( + null, + null, + checkmarkIcon, + null + ) binding?.issuesTextView?.visibility = View.GONE } else { - binding?.usernameEditText?.setCompoundDrawablesWithIntrinsicBounds(null, null, alertIcon, null) + binding?.usernameEditText?.setCompoundDrawablesWithIntrinsicBounds( + null, + null, + alertIcon, + null + ) binding?.issuesTextView?.visibility = View.VISIBLE - binding?.issuesTextView?.text = it.issues.joinToString("\n") + binding?.issuesTextView?.text = it?.issues?.joinToString("\n") } - nameValidEvents.onNext(it.isUsable) + nameValidEvents.onNext(it?.isUsable) } - ) + } lifecycleScope.launch(ExceptionHandler.coroutine()) { val user = userRepository.getUser().firstOrNull() binding?.displayNameEditText?.setText(user?.profile?.name) - displayNameVerificationEvents.onNext(user?.profile?.name ?: "") + displayNameVerificationEvents.value = user?.profile?.name ?: "" binding?.usernameEditText?.setText(user?.authentication?.localAuthentication?.username) - usernameVerificationEvents.onNext(user?.username ?: "") + usernameVerificationEvents.value = user?.username ?: "" } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/skills/SkillTasksRecyclerViewFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/skills/SkillTasksRecyclerViewFragment.kt index e677357d5..116dd322c 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/skills/SkillTasksRecyclerViewFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/skills/SkillTasksRecyclerViewFragment.kt @@ -9,15 +9,11 @@ import androidx.recyclerview.widget.LinearLayoutManager import com.habitrpg.android.habitica.components.UserComponent import com.habitrpg.android.habitica.data.TaskRepository import com.habitrpg.android.habitica.databinding.FragmentRecyclerviewBinding -import com.habitrpg.android.habitica.helpers.ExceptionHandler import com.habitrpg.android.habitica.models.tasks.Task import com.habitrpg.android.habitica.ui.adapter.SkillTasksRecyclerViewAdapter import com.habitrpg.android.habitica.ui.fragments.BaseFragment import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel import com.habitrpg.shared.habitica.models.tasks.TaskType -import io.reactivex.rxjava3.core.BackpressureStrategy -import io.reactivex.rxjava3.core.Flowable -import io.reactivex.rxjava3.subjects.PublishSubject import kotlinx.coroutines.flow.map import javax.inject.Inject @@ -37,11 +33,7 @@ class SkillTasksRecyclerViewFragment : BaseFragment var adapter: SkillTasksRecyclerViewAdapter = SkillTasksRecyclerViewAdapter() internal var layoutManager: LinearLayoutManager? = null - private val taskSelectionEvents = PublishSubject.create() - - fun getTaskSelectionEvents(): Flowable { - return taskSelectionEvents.toFlowable(BackpressureStrategy.DROP) - } + var onTaskSelection: ((Task) -> Unit)? = null override fun injectFragment(component: UserComponent) { component.inject(this) @@ -54,14 +46,7 @@ class SkillTasksRecyclerViewFragment : BaseFragment binding?.recyclerView?.layoutManager = layoutManager adapter = SkillTasksRecyclerViewAdapter() - compositeSubscription.add( - adapter.getTaskSelectionEvents().subscribe( - { - taskSelectionEvents.onNext(it) - }, - ExceptionHandler.rx() - ) - ) + adapter.onTaskSelection = { onTaskSelection?.invoke(it) } binding?.recyclerView?.adapter = adapter val additionalGroupIDs = userViewModel.mirrorGroupTasks.toTypedArray() 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 8b6322401..ba043322a 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 @@ -28,8 +28,8 @@ import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel import com.habitrpg.android.habitica.ui.views.HabiticaIconsHelper import com.habitrpg.android.habitica.ui.views.HabiticaSnackbar import com.habitrpg.android.habitica.ui.views.HabiticaSnackbar.Companion.showSnackbar -import io.reactivex.rxjava3.core.Flowable import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.combine import kotlinx.coroutines.launch import javax.inject.Inject @@ -80,19 +80,19 @@ class SkillsFragment : BaseMainFragment() { adapter?.mana = user.stats?.mp ?: 0.0 adapter?.level = user.stats?.lvl ?: 0 adapter?.specialItems = user.items?.special - Flowable.combineLatest( - userRepository.getSkills(user), - userRepository.getSpecialItems(user) - ) { skills, items -> - val allEntries = mutableListOf() - for (skill in skills) { - allEntries.add(skill) - } - for (item in items) { - allEntries.add(item) - } - return@combineLatest allEntries - }.subscribe({ skills -> adapter?.setSkillList(skills) }, ExceptionHandler.rx()) + lifecycleScope.launchCatching { + userRepository.getSkills(user) + .combine(userRepository.getSpecialItems(user)) { skills, items -> + val allEntries = mutableListOf() + for (skill in skills) { + allEntries.add(skill) + } + for (item in items) { + allEntries.add(item) + } + return@combine allEntries + }.collect { skills -> adapter?.setSkillList(skills) } + } } private fun onSkillSelected(skill: Skill) { diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/InboxMessageListFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/InboxMessageListFragment.kt index 9c87e9eaf..dd37586d8 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/InboxMessageListFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/InboxMessageListFragment.kt @@ -23,6 +23,7 @@ import com.habitrpg.android.habitica.extensions.addOkButton import com.habitrpg.android.habitica.helpers.AppConfigManager 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.social.ChatMessage import com.habitrpg.android.habitica.ui.activities.FullProfileActivity import com.habitrpg.android.habitica.ui.activities.MainActivity @@ -264,7 +265,11 @@ class InboxMessageListFragment : BaseMainFragment socialRepository.deleteMessage(chatMessage).subscribe({ }, ExceptionHandler.rx()) } + .setPositiveButton(R.string.yes) { _, _ -> + lifecycleScope.launchCatching { + socialRepository.deleteMessage(chatMessage) + } + } .setNegativeButton(R.string.no, null).show() } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/InboxOverviewFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/InboxOverviewFragment.kt index d0dd57c43..be5499b91 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/InboxOverviewFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/InboxOverviewFragment.kt @@ -20,6 +20,7 @@ import com.habitrpg.android.habitica.extensions.getAgoString import com.habitrpg.android.habitica.helpers.AppConfigManager 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.social.InboxConversation import com.habitrpg.android.habitica.ui.fragments.BaseMainFragment import com.habitrpg.android.habitica.ui.helpers.dismissKeyboard @@ -51,7 +52,9 @@ class InboxOverviewFragment : BaseMainFragment(), androidx savedInstanceState: Bundle? ): View? { this.hidesToolbar = true - compositeSubscription.add(this.socialRepository.markPrivateMessagesRead(null).subscribe({ }, ExceptionHandler.rx())) + lifecycleScope.launchCatching { + socialRepository.markPrivateMessagesRead(null) + } return super.onCreateView(inflater, container, savedInstanceState) } @@ -141,15 +144,10 @@ class InboxOverviewFragment : BaseMainFragment(), androidx } private fun retrieveMessages() { - compositeSubscription.add( - this.socialRepository.retrieveInboxConversations() - .subscribe( - { - binding?.inboxRefreshLayout?.isRefreshing = false - }, - ExceptionHandler.rx() - ) - ) + lifecycleScope.launchCatching { + socialRepository.retrieveInboxConversations() + binding?.inboxRefreshLayout?.isRefreshing = false + } } override fun onRefresh() { diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/QuestDetailFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/QuestDetailFragment.kt index 165f63010..403c3fee5 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/QuestDetailFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/QuestDetailFragment.kt @@ -19,6 +19,7 @@ import com.habitrpg.android.habitica.databinding.FragmentQuestDetailBinding import com.habitrpg.android.habitica.extensions.fromHtml import com.habitrpg.android.habitica.helpers.ExceptionHandler import com.habitrpg.android.habitica.helpers.HapticFeedbackManager +import com.habitrpg.android.habitica.helpers.launchCatching import com.habitrpg.android.habitica.models.inventory.Quest import com.habitrpg.android.habitica.models.inventory.QuestContent import com.habitrpg.android.habitica.models.members.Member @@ -42,16 +43,22 @@ class QuestDetailFragment : BaseMainFragment() { @Inject lateinit var socialRepository: SocialRepository + @Inject lateinit var inventoryRepository: InventoryRepository + @field:[Inject Named(AppModule.NAMED_USER_ID)] lateinit var userId: String + @Inject lateinit var userViewModel: MainUserViewModel override var binding: FragmentQuestDetailBinding? = null - override fun createBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentQuestDetailBinding { + override fun createBinding( + inflater: LayoutInflater, + container: ViewGroup? + ): FragmentQuestDetailBinding { return FragmentQuestDetailBinding.inflate(inflater, container, false) } @@ -107,9 +114,10 @@ class QuestDetailFragment : BaseMainFragment() { lifecycleScope.launch(ExceptionHandler.coroutine()) { val member = socialRepository.retrieveMember(quest?.leader) if (context != null && binding?.questLeaderView != null) { - binding?.questLeaderView?.text = context?.getString(R.string.quest_leader_header, member?.displayName) - } + binding?.questLeaderView?.text = + context?.getString(R.string.quest_leader_header, member?.displayName) } + } val user = userViewModel.user.value if (binding?.questResponseWrapper != null) { @@ -142,7 +150,10 @@ class QuestDetailFragment : BaseMainFragment() { } binding?.titleView?.text = questContent.text // We need to do this, because the quest description can contain markdown AND HTML. - binding?.descriptionView?.setText(MarkdownParser.parseMarkdown(questContent.notes).toHtml().fromHtml(), TextView.BufferType.SPANNABLE) + binding?.descriptionView?.setText( + MarkdownParser.parseMarkdown(questContent.notes).toHtml().fromHtml(), + TextView.BufferType.SPANNABLE + ) binding?.questScrollImageView?.loadImage("inventory_quest_scroll_" + questContent.key) } @@ -158,7 +169,8 @@ class QuestDetailFragment : BaseMainFragment() { if (quest?.active == true && participant.participatesInQuest == false) { continue } - val participantView = inflater?.inflate(R.layout.quest_participant, binding?.questParticipantList, false) + val participantView = + inflater?.inflate(R.layout.quest_participant, binding?.questParticipantList, false) val textView = participantView?.findViewById(R.id.participant_name) as? TextView textView?.text = participant.displayName val statusTextView = participantView?.findViewById(R.id.status_view) as? TextView @@ -167,15 +179,30 @@ class QuestDetailFragment : BaseMainFragment() { when (participant.participatesInQuest) { null -> { statusTextView?.setText(R.string.pending) - statusTextView?.setTextColor(ContextCompat.getColor(it, R.color.text_ternary)) + statusTextView?.setTextColor( + ContextCompat.getColor( + it, + R.color.text_ternary + ) + ) } true -> { statusTextView?.setText(R.string.accepted) - statusTextView?.setTextColor(ContextCompat.getColor(it, R.color.text_green)) + statusTextView?.setTextColor( + ContextCompat.getColor( + it, + R.color.text_green + ) + ) } else -> { statusTextView?.setText(R.string.declined) - statusTextView?.setTextColor(ContextCompat.getColor(it, R.color.text_red)) + statusTextView?.setTextColor( + ContextCompat.getColor( + it, + R.color.text_red + ) + ) } } } @@ -196,8 +223,10 @@ class QuestDetailFragment : BaseMainFragment() { } else { binding?.participantsHeader?.setText(R.string.invitations) @SuppressLint("SetTextI18n") - binding?.participantsHeaderCount?.text = participantCount.toString() + "/" + participants?.size - beginQuestMessage = getString(R.string.quest_begin_message, participantCount, participants?.size) + binding?.participantsHeaderCount?.text = + participantCount.toString() + "/" + participants?.size + beginQuestMessage = + getString(R.string.quest_begin_message, participantCount, participants?.size) } } @@ -217,8 +246,9 @@ class QuestDetailFragment : BaseMainFragment() { alert.addButton(R.string.yes, true) { _, _ -> val party = party if (party != null) { - socialRepository.forceStartQuest(party) - .subscribe({ }, ExceptionHandler.rx()) + lifecycleScope.launchCatching { + socialRepository.forceStartQuest(party) + } } } alert.addButton(R.string.no, false) @@ -234,12 +264,11 @@ class QuestDetailFragment : BaseMainFragment() { .setMessage(R.string.quest_abort_message) .setPositiveButton(R.string.yes) { _, _ -> party?.id?.let { partyID -> - socialRepository.abortQuest(partyID) - .subscribe({ - lifecycleScope.launch(ExceptionHandler.coroutine()) { - userRepository.retrieveUser(true) - } - getActivity()?.supportFragmentManager?.popBackStack() }, ExceptionHandler.rx()) + lifecycleScope.launchCatching { + socialRepository.abortQuest(partyID) + userRepository.retrieveUser(true) + getActivity()?.supportFragmentManager?.popBackStack() + } } }.setNegativeButton(R.string.no) { _, _ -> } builder.show() @@ -248,13 +277,11 @@ class QuestDetailFragment : BaseMainFragment() { alert.setMessage(R.string.quest_cancel_message) alert.addButton(R.string.yes, true) { _, _ -> party?.id?.let { partyID -> - socialRepository.cancelQuest(partyID) - .subscribe({ - lifecycleScope.launch(ExceptionHandler.coroutine()) { - userRepository.retrieveUser(true) - } - getActivity()?.supportFragmentManager?.popBackStack() - }, ExceptionHandler.rx()) + lifecycleScope.launchCatching { + socialRepository.cancelQuest(partyID) + userRepository.retrieveUser(true) + getActivity()?.supportFragmentManager?.popBackStack() + } } } alert.addButton(R.string.no, false) @@ -269,13 +296,12 @@ class QuestDetailFragment : BaseMainFragment() { .setMessage(if (quest?.active == true) R.string.quest_leave_message else R.string.quest_leave_message_nostart) .setPositiveButton(R.string.yes) { _, _ -> party?.id?.let { partyID -> - socialRepository.leaveQuest(partyID) - .subscribe({ - lifecycleScope.launch(ExceptionHandler.coroutine()) { - socialRepository.retrieveGroup(partyID) - userRepository.retrieveUser(true) - } - getActivity()?.supportFragmentManager?.popBackStack() }, ExceptionHandler.rx()) + lifecycleScope.launchCatching { + socialRepository.leaveQuest(partyID) + socialRepository.retrieveGroup(partyID) + userRepository.retrieveUser(true) + getActivity()?.supportFragmentManager?.popBackStack() + } } }.setNegativeButton(R.string.no) { _, _ -> } builder.show() diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/challenges/ChallengeDetailFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/challenges/ChallengeDetailFragment.kt index 6f0e54624..16780f1b6 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/challenges/ChallengeDetailFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/challenges/ChallengeDetailFragment.kt @@ -11,7 +11,6 @@ import android.view.MenuItem import android.view.View import android.view.ViewGroup import androidx.core.net.toUri -import androidx.core.view.isVisible import androidx.lifecycle.lifecycleScope import com.habitrpg.android.habitica.R import com.habitrpg.android.habitica.components.UserComponent @@ -23,6 +22,7 @@ import com.habitrpg.android.habitica.extensions.addCloseButton import com.habitrpg.android.habitica.extensions.inflate 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.members.Member import com.habitrpg.android.habitica.models.social.Challenge import com.habitrpg.android.habitica.models.tasks.Task @@ -39,7 +39,8 @@ import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog import com.habitrpg.common.habitica.helpers.EmojiParser import com.habitrpg.common.habitica.helpers.setMarkdown import com.habitrpg.shared.habitica.models.tasks.TaskType -import kotlinx.coroutines.launch +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.map import retrofit2.HttpException import javax.inject.Inject @@ -94,43 +95,20 @@ class ChallengeDetailFragment : BaseMainFragment FullProfileActivity.open(leaderID) } - loadTasks() - - binding?.joinButton?.setOnClickListener { - challenge?.let { challenge -> - challengeRepository.joinChallenge(challenge) - .subscribe({ - lifecycleScope.launch(ExceptionHandler.coroutine()) { - userRepository.retrieveUser(true) - } - }, ExceptionHandler.rx()) - } - } - binding?.leaveButton?.setOnClickListener { showChallengeLeaveDialog() } - - refresh() - } - - private fun loadTasks() { challengeID?.let { id -> - compositeSubscription.add( + lifecycleScope.launchCatching { challengeRepository.getChallenge(id) - .doOnNext { - set(it) - } .map { - return@map (it.leaderId ?: "") + set(it) + (it.leaderId ?: "") } - .filter { it.isNotEmpty() } - .subscribe({ - lifecycleScope.launch(ExceptionHandler.coroutine()) { - set(socialRepository.retrieveMember(it)) - } - }, ExceptionHandler.rx()) - ) - compositeSubscription.add( - challengeRepository.getChallengeTasks(id).subscribe( - { taskList -> + .distinctUntilChanged() + .collect { + set(socialRepository.retrieveMember(it)) + } + } + lifecycleScope.launchCatching { + challengeRepository.getChallengeTasks(id).collect { taskList -> binding?.taskGroupLayout?.removeAllViewsInLayout() val todos = ArrayList() @@ -163,20 +141,27 @@ class ChallengeDetailFragment : BaseMainFragment if (rewards.size > 0) { addRewards(rewards) } - }, - ExceptionHandler.rx() - ) - ) + } + } - compositeSubscription.add( - challengeRepository.isChallengeMember(id).subscribe( - { isMember -> - setJoined(isMember) - }, - ExceptionHandler.rx() - ) - ) + lifecycleScope.launchCatching { + challengeRepository.isChallengeMember(id).collect { isMember -> + setJoined(isMember) + } + } } + + binding?.joinButton?.setOnClickListener { + challenge?.let { challenge -> + lifecycleScope.launchCatching { + challengeRepository.joinChallenge(challenge) + userRepository.retrieveUser(true) + } + } + } + binding?.leaveButton?.setOnClickListener { showChallengeLeaveDialog() } + + refresh() } override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { @@ -235,18 +220,15 @@ class ChallengeDetailFragment : BaseMainFragment private fun refresh() { challengeID?.let { id -> - challengeRepository.retrieveChallenge(id) - .flatMap { challengeRepository.retrieveChallengeTasks(id) } - .subscribe({ taskList -> - if (binding?.taskGroupLayout?.childCount == 0 && taskList.tasks.isNotEmpty()) { - loadTasks() - } - }, { - if (it is HttpException && it.code() == 404) { - MainNavigationController.navigateBack() - } - ExceptionHandler.reportError(it) - }) + lifecycleScope.launchCatching({ + if (it is HttpException && it.code() == 404) { + MainNavigationController.navigateBack() + } + ExceptionHandler.reportError(it) + }) { + challengeRepository.retrieveChallenge(id) + challengeRepository.retrieveChallengeTasks(id) + } } } @@ -351,11 +333,15 @@ class ChallengeDetailFragment : BaseMainFragment alert.setMessage(this.getString(R.string.challenge_leave_description)) alert.addButton(R.string.leave_keep_tasks, true) { _, _ -> val challenge = challenge ?: return@addButton - challengeRepository.leaveChallenge(challenge, "keep-all").subscribe({}, ExceptionHandler.rx()) + lifecycleScope.launchCatching { + challengeRepository.leaveChallenge(challenge, "keep-all") + } } alert.addButton(R.string.leave_delete_tasks, isPrimary = false, isDestructive = true) { _, _ -> val challenge = challenge ?: return@addButton - challengeRepository.leaveChallenge(challenge, "remove-all").subscribe({}, ExceptionHandler.rx()) + lifecycleScope.launchCatching { + challengeRepository.leaveChallenge(challenge, "remove-all") + } } alert.setExtraCloseButtonVisibility(View.VISIBLE) alert.show() diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/challenges/ChallengeListFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/challenges/ChallengeListFragment.kt index 5587f488d..10ef6d705 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/challenges/ChallengeListFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/challenges/ChallengeListFragment.kt @@ -14,6 +14,7 @@ import com.habitrpg.android.habitica.data.UserRepository import com.habitrpg.android.habitica.databinding.FragmentRefreshRecyclerviewBinding 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.social.Challenge import com.habitrpg.android.habitica.models.social.Group import com.habitrpg.android.habitica.modules.AppModule @@ -21,26 +22,32 @@ import com.habitrpg.android.habitica.ui.adapter.social.ChallengesListViewAdapter import com.habitrpg.android.habitica.ui.fragments.BaseFragment import com.habitrpg.android.habitica.ui.helpers.SafeDefaultItemAnimator import com.habitrpg.common.habitica.helpers.EmptyItem -import io.reactivex.rxjava3.core.Flowable import kotlinx.coroutines.flow.combine import kotlinx.coroutines.launch import javax.inject.Inject import javax.inject.Named -class ChallengeListFragment : BaseFragment(), androidx.swiperefreshlayout.widget.SwipeRefreshLayout.OnRefreshListener { +class ChallengeListFragment : BaseFragment(), + androidx.swiperefreshlayout.widget.SwipeRefreshLayout.OnRefreshListener { @Inject lateinit var challengeRepository: ChallengeRepository + @Inject lateinit var socialRepository: SocialRepository + @Inject lateinit var userRepository: UserRepository + @field:[Inject Named(AppModule.NAMED_USER_ID)] lateinit var userId: String override var binding: FragmentRefreshRecyclerviewBinding? = null - override fun createBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentRefreshRecyclerviewBinding { + override fun createBinding( + inflater: LayoutInflater, + container: ViewGroup? + ): FragmentRefreshRecyclerviewBinding { return FragmentRefreshRecyclerviewBinding.inflate(inflater, container, false) } @@ -68,7 +75,8 @@ class ChallengeListFragment : BaseFragment() super.onViewCreated(view, savedInstanceState) challengeAdapter = ChallengesListViewAdapter(viewUserChallengesOnly, userId) - challengeAdapter?.getOpenDetailFragmentFlowable()?.subscribe({ openDetailFragment(it) }, ExceptionHandler.rx()) + challengeAdapter?.getOpenDetailFragmentFlowable() + ?.subscribe({ openDetailFragment(it) }, ExceptionHandler.rx()) ?.let { compositeSubscription.add(it) } binding?.refreshLayout?.setOnRefreshListener(this) @@ -79,16 +87,18 @@ class ChallengeListFragment : BaseFragment() getString(R.string.empty_discover_description) ) } - binding?.recyclerView?.layoutManager = androidx.recyclerview.widget.LinearLayoutManager(this.activity) + binding?.recyclerView?.layoutManager = + androidx.recyclerview.widget.LinearLayoutManager(this.activity) binding?.recyclerView?.adapter = challengeAdapter if (!viewUserChallengesOnly) { binding?.recyclerView?.setBackgroundResource(R.color.content_background) } lifecycleScope.launch(ExceptionHandler.coroutine()) { - socialRepository.getGroup(Group.TAVERN_ID).combine(socialRepository.getUserGroups("guild")) { tavern, guilds -> - return@combine Pair(tavern, guilds) - }.collect { + socialRepository.getGroup(Group.TAVERN_ID) + .combine(socialRepository.getUserGroups("guild")) { tavern, guilds -> + return@combine Pair(tavern, guilds) + }.collect { this@ChallengeListFragment.filterGroups = mutableListOf() it.first?.let { tavern -> filterGroups?.add(tavern) } filterGroups?.addAll(it.second) @@ -112,7 +122,11 @@ class ChallengeListFragment : BaseFragment() } private fun openDetailFragment(challengeID: String) { - MainNavigationController.navigate(ChallengesOverviewFragmentDirections.openChallengeDetail(challengeID)) + MainNavigationController.navigate( + ChallengesOverviewFragmentDirections.openChallengeDetail( + challengeID + ) + ) } override fun injectFragment(component: UserComponent) { @@ -130,24 +144,20 @@ class ChallengeListFragment : BaseFragment() } private fun loadLocalChallenges() { - val observable: Flowable> = if (viewUserChallengesOnly) { - challengeRepository.getUserChallenges() - } else { - challengeRepository.getChallenges() + lifecycleScope.launchCatching { + val flow = if (viewUserChallengesOnly) { + challengeRepository.getUserChallenges() + } else { + challengeRepository.getChallenges() + } + flow.collect { challenges -> + if (challenges.isEmpty()) { + retrieveChallengesPage() + } + this@ChallengeListFragment.challenges = challenges + challengeAdapter?.updateUnfilteredData(challenges) + } } - - compositeSubscription.add( - observable.subscribe( - { challenges -> - if (challenges.isEmpty()) { - retrieveChallengesPage() - } - this.challenges = challenges - challengeAdapter?.updateUnfilteredData(challenges) - }, - ExceptionHandler.rx() - ) - ) } internal fun retrieveChallengesPage(forced: Boolean = false) { @@ -155,19 +165,15 @@ class ChallengeListFragment : BaseFragment() return } setRefreshing(true) - compositeSubscription.add( - challengeRepository.retrieveChallenges(nextPageToLoad, viewUserChallengesOnly).doOnComplete { - setRefreshing(false) - }.subscribe( - { - if (it.size < 10) { - loadedAllData = true - } - nextPageToLoad += 1 - }, - ExceptionHandler.rx() - ) - ) + lifecycleScope.launchCatching { + val challenges = + challengeRepository.retrieveChallenges(nextPageToLoad, viewUserChallengesOnly) + setRefreshing(false) + if ((challenges?.size ?: 0) < 10) { + loadedAllData = true + } + nextPageToLoad += 1 + } } internal fun showFilterDialog() { @@ -175,7 +181,8 @@ class ChallengeListFragment : BaseFragment() ChallengeFilterDialogHolder.showDialog( it, filterGroups ?: emptyList(), - filterOptions) { + filterOptions + ) { changeFilter(it) } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/guilds/GuildDetailFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/guilds/GuildDetailFragment.kt index 0439cf78e..3645a9ec5 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/guilds/GuildDetailFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/guilds/GuildDetailFragment.kt @@ -17,6 +17,7 @@ import com.habitrpg.android.habitica.data.UserRepository import com.habitrpg.android.habitica.databinding.FragmentGuildDetailBinding import com.habitrpg.android.habitica.helpers.AppConfigManager import com.habitrpg.android.habitica.helpers.MainNavigationController +import com.habitrpg.android.habitica.helpers.launchCatching import com.habitrpg.android.habitica.models.members.Member import com.habitrpg.android.habitica.models.social.Challenge import com.habitrpg.android.habitica.models.social.Group @@ -31,6 +32,7 @@ import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog import com.habitrpg.common.habitica.helpers.setMarkdown import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.launch import javax.inject.Inject @@ -133,11 +135,12 @@ class GuildDetailFragment : BaseFragment() { private fun getGroupChallenges(): List { val groupChallenges = mutableListOf() - userRepository.getUserFlowable().forEach { - it.challenges?.forEach { - challengeRepository.getChallenge(it.challengeID).forEach { - if (it.groupId.equals(viewModel?.groupID)) { - groupChallenges.add(it) + lifecycleScope.launchCatching { + userRepository.getUser().collect { + it?.challenges?.forEach { membership -> + val challenge = challengeRepository.getChallenge(membership.challengeID).firstOrNull() + if (challenge != null && challenge.groupId == viewModel?.groupID) { + groupChallenges.add(challenge) } } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/guilds/GuildListFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/guilds/GuildListFragment.kt index 848024aa1..72e4d972a 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/guilds/GuildListFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/guilds/GuildListFragment.kt @@ -12,6 +12,7 @@ import com.habitrpg.android.habitica.components.UserComponent import com.habitrpg.android.habitica.data.SocialRepository import com.habitrpg.android.habitica.databinding.FragmentRefreshRecyclerviewBinding import com.habitrpg.android.habitica.helpers.ExceptionHandler +import com.habitrpg.android.habitica.helpers.launchCatching import com.habitrpg.android.habitica.ui.adapter.social.GuildListAdapter import com.habitrpg.android.habitica.ui.fragments.BaseFragment import com.habitrpg.android.habitica.ui.helpers.KeyboardUtil @@ -20,7 +21,9 @@ import com.habitrpg.common.habitica.helpers.EmptyItem import kotlinx.coroutines.launch import javax.inject.Inject -class GuildListFragment : BaseFragment(), SearchView.OnQueryTextListener, SearchView.OnCloseListener, SwipeRefreshLayout.OnRefreshListener { +class GuildListFragment : BaseFragment(), + SearchView.OnQueryTextListener, SearchView.OnCloseListener, + SwipeRefreshLayout.OnRefreshListener { @Inject lateinit var socialRepository: SocialRepository @@ -28,7 +31,10 @@ class GuildListFragment : BaseFragment(), Se override var binding: FragmentRefreshRecyclerviewBinding? = null var onlyShowUsersGuilds: Boolean = false - override fun createBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentRefreshRecyclerviewBinding { + override fun createBinding( + inflater: LayoutInflater, + container: ViewGroup? + ): FragmentRefreshRecyclerviewBinding { return FragmentRefreshRecyclerviewBinding.inflate(inflater, container, false) } @@ -41,7 +47,8 @@ class GuildListFragment : BaseFragment(), Se override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - binding?.recyclerView?.layoutManager = androidx.recyclerview.widget.LinearLayoutManager(activity) + binding?.recyclerView?.layoutManager = + androidx.recyclerview.widget.LinearLayoutManager(activity) viewAdapter.socialRepository = socialRepository binding?.recyclerView?.adapter = viewAdapter binding?.recyclerView?.itemAnimator = SafeDefaultItemAnimator() @@ -61,15 +68,11 @@ class GuildListFragment : BaseFragment(), Se } } } else { - compositeSubscription.add( - this.socialRepository.getPublicGuilds() - .subscribe( - { groups -> - this@GuildListFragment.viewAdapter.setUnfilteredData(groups) - }, - ExceptionHandler.rx() - ) - ) + lifecycleScope.launchCatching { + socialRepository.getPublicGuilds().collect { groups -> + this@GuildListFragment.viewAdapter.setUnfilteredData(groups) + } + } } this.fetchGuilds() } @@ -80,15 +83,10 @@ class GuildListFragment : BaseFragment(), Se } internal fun fetchGuilds() { - compositeSubscription.add( - this.socialRepository.retrieveGroups(if (onlyShowUsersGuilds) "guilds" else "publicGuilds") - .subscribe( - { - binding?.refreshLayout?.isRefreshing = false - }, - ExceptionHandler.rx() - ) - ) + lifecycleScope.launchCatching { + socialRepository.retrieveGroups(if (onlyShowUsersGuilds) "guilds" else "publicGuilds") + binding?.refreshLayout?.isRefreshing = false + } } override fun onQueryTextSubmit(s: String?): Boolean { diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/party/NoPartyFragmentFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/party/NoPartyFragmentFragment.kt index 1843af8bd..f4f29c64a 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/party/NoPartyFragmentFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/party/NoPartyFragmentFragment.kt @@ -25,6 +25,7 @@ import com.habitrpg.android.habitica.databinding.FragmentNoPartyBinding import com.habitrpg.android.habitica.helpers.AppConfigManager 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.activities.GroupFormActivity import com.habitrpg.android.habitica.ui.fragments.BaseMainFragment import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel @@ -78,7 +79,9 @@ class NoPartyFragmentFragment : BaseMainFragment() { } binding?.invitationsView?.rejectCall = { - socialRepository.rejectGroupInvite(it).subscribe({ }, ExceptionHandler.rx()) + lifecycleScope.launchCatching { + socialRepository.rejectGroupInvite(it) + } binding?.invitationWrapper?.visibility = View.GONE } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/party/PartyDetailFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/party/PartyDetailFragment.kt index a24a53493..281b1df0c 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/party/PartyDetailFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/party/PartyDetailFragment.kt @@ -21,6 +21,7 @@ import com.habitrpg.android.habitica.extensions.inflate 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.inventory.QuestContent import com.habitrpg.android.habitica.models.members.Member import com.habitrpg.android.habitica.models.social.Challenge @@ -108,12 +109,10 @@ class PartyDetailFragment : BaseFragment() { } binding?.invitationsView?.rejectCall = { - socialRepository.rejectGroupInvite(it) - .subscribe({ - lifecycleScope.launch(ExceptionHandler.coroutine()) { - userRepository.retrieveUser(false, true) - } - }, ExceptionHandler.rx()) + lifecycleScope.launchCatching { + socialRepository.rejectGroupInvite(it) + userRepository.retrieveUser(false, true) + } } viewModel?.getGroupData()?.observe(viewLifecycleOwner) { updateParty(it) } @@ -370,11 +369,12 @@ class PartyDetailFragment : BaseFragment() { private fun getGroupChallenges(): List { val groupChallenges = mutableListOf() - userRepository.getUserFlowable().forEach { - it.challenges?.forEach { - challengeRepository.getChallenge(it.challengeID).forEach { - if (it.groupId.equals(viewModel?.groupID)) { - groupChallenges.add(it) + lifecycleScope.launchCatching { + userRepository.getUser().collect { + it?.challenges?.forEach { membership -> + val challenge = challengeRepository.getChallenge(membership.challengeID).firstOrNull() + if (challenge != null && challenge.groupId == viewModel?.groupID) { + groupChallenges.add(challenge) } } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/support/FAQDetailFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/support/FAQDetailFragment.kt index 9c9e8c51f..50140e9c0 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/support/FAQDetailFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/support/FAQDetailFragment.kt @@ -5,10 +5,11 @@ import android.text.method.LinkMovementMethod import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.lifecycle.lifecycleScope import com.habitrpg.android.habitica.components.UserComponent import com.habitrpg.android.habitica.data.FAQRepository import com.habitrpg.android.habitica.databinding.FragmentFaqDetailBinding -import com.habitrpg.android.habitica.helpers.ExceptionHandler +import com.habitrpg.android.habitica.helpers.launchCatching import com.habitrpg.android.habitica.ui.fragments.BaseMainFragment import com.habitrpg.common.habitica.helpers.MarkdownParser import javax.inject.Inject @@ -42,15 +43,12 @@ class FAQDetailFragment : BaseMainFragment() { binding?.questionTextView?.text = args.question binding?.answerTextView?.text = MarkdownParser.parseMarkdown(args.answer) } else { - compositeSubscription.add( - faqRepository.getArticle(args.position).subscribe( - { faq -> - binding?.questionTextView?.text = faq.question - binding?.answerTextView?.text = MarkdownParser.parseMarkdown(faq.answer) - }, - ExceptionHandler.rx() - ) - ) + lifecycleScope.launchCatching { + faqRepository.getArticle(args.position).collect { faq -> + binding?.questionTextView?.text = faq.question + binding?.answerTextView?.text = MarkdownParser.parseMarkdown(faq.answer) + } + } } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/support/FAQOverviewFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/support/FAQOverviewFragment.kt index e80d41327..9fecc4330 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/support/FAQOverviewFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/support/FAQOverviewFragment.kt @@ -7,18 +7,18 @@ import android.view.View import android.view.ViewGroup import android.widget.ImageView import androidx.core.os.bundleOf +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.FragmentFaqOverviewBinding import com.habitrpg.android.habitica.databinding.SupportFaqItemBinding -import com.habitrpg.common.habitica.extensions.layoutInflater import com.habitrpg.android.habitica.helpers.MainNavigationController -import com.habitrpg.android.habitica.helpers.ExceptionHandler +import com.habitrpg.android.habitica.helpers.launchCatching import com.habitrpg.android.habitica.ui.fragments.BaseMainFragment -import com.habitrpg.common.habitica.helpers.setMarkdown import com.habitrpg.android.habitica.ui.views.HabiticaIconsHelper -import io.reactivex.rxjava3.functions.Consumer +import com.habitrpg.common.habitica.extensions.layoutInflater +import com.habitrpg.common.habitica.helpers.setMarkdown import javax.inject.Inject class FAQOverviewFragment : BaseMainFragment() { @@ -77,22 +77,23 @@ class FAQOverviewFragment : BaseMainFragment() { } private fun loadArticles() { - compositeSubscription.add( - faqRepository.getArticles().subscribe( - Consumer { - val context = context ?: return@Consumer - for (article in it) { - val binding = SupportFaqItemBinding.inflate(context.layoutInflater, binding?.faqLinearLayout, true) - binding.textView.text = article.question - binding.root.setOnClickListener { - val direction = FAQOverviewFragmentDirections.openFAQDetail(null, null) - direction.position = article.position ?: 0 - MainNavigationController.navigate(direction) - } + lifecycleScope.launchCatching { + faqRepository.getArticles().collect { + val context = context ?: return@collect + for (article in it) { + val binding = SupportFaqItemBinding.inflate( + context.layoutInflater, + binding?.faqLinearLayout, + true + ) + binding.textView.text = article.question + binding.root.setOnClickListener { + val direction = FAQOverviewFragmentDirections.openFAQDetail(null, null) + direction.position = article.position ?: 0 + MainNavigationController.navigate(direction) } - }, - 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 d646de22e..45f7fcd56 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 @@ -61,14 +61,11 @@ class RewardsRecyclerviewFragment : TaskRecyclerViewFragment() { binding?.recyclerView?.itemAnimator = SafeDefaultItemAnimator() if (showCustomRewards) { - compositeSubscription.add( - inventoryRepository.getInAppRewards().subscribe( - { - (recyclerAdapter as? RewardsRecyclerViewAdapter)?.updateItemRewards(it) - }, - ExceptionHandler.rx() - ) - ) + lifecycleScope.launchCatching { + inventoryRepository.getInAppRewards().collect { + (recyclerAdapter as? RewardsRecyclerViewAdapter)?.updateItemRewards(it) + } + } } (recyclerAdapter as? RewardsRecyclerViewAdapter)?.purchaseCardEvents?.subscribe( diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/tasks/TaskRecyclerViewFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/tasks/TaskRecyclerViewFragment.kt index 7184362d0..f964224de 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/tasks/TaskRecyclerViewFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/tasks/TaskRecyclerViewFragment.kt @@ -29,6 +29,7 @@ import com.habitrpg.android.habitica.helpers.HapticFeedbackManager import com.habitrpg.android.habitica.helpers.MainNavigationController import com.habitrpg.android.habitica.helpers.NotificationsManager import com.habitrpg.android.habitica.helpers.SoundManager +import com.habitrpg.android.habitica.helpers.launchCatching import com.habitrpg.android.habitica.models.tasks.ChecklistItem import com.habitrpg.android.habitica.models.tasks.Task import com.habitrpg.android.habitica.ui.activities.MainActivity @@ -50,46 +51,56 @@ import com.habitrpg.common.habitica.helpers.EmptyItem import com.habitrpg.shared.habitica.models.responses.TaskDirection import com.habitrpg.shared.habitica.models.responses.TaskScoringResult import com.habitrpg.shared.habitica.models.tasks.TaskType -import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers import io.reactivex.rxjava3.disposables.CompositeDisposable import kotlinx.coroutines.Job import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.distinctUntilChangedBy +import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.takeWhile import kotlinx.coroutines.launch import java.util.Date -import java.util.concurrent.TimeUnit import javax.inject.Inject -open class TaskRecyclerViewFragment : BaseFragment(), androidx.swiperefreshlayout.widget.SwipeRefreshLayout.OnRefreshListener { +open class TaskRecyclerViewFragment : BaseFragment(), + androidx.swiperefreshlayout.widget.SwipeRefreshLayout.OnRefreshListener { private var taskFlowJob: Job? = null - val viewModel: TasksViewModel by viewModels({requireParentFragment()}) + val viewModel: TasksViewModel by viewModels({ requireParentFragment() }) internal var canEditTasks: Boolean = true internal var canScoreTaks: Boolean = true override var binding: FragmentRefreshRecyclerviewBinding? = null - override fun createBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentRefreshRecyclerviewBinding { + override fun createBinding( + inflater: LayoutInflater, + container: ViewGroup? + ): FragmentRefreshRecyclerviewBinding { return FragmentRefreshRecyclerviewBinding.inflate(inflater, container, false) } private var recyclerSubscription: CompositeDisposable = CompositeDisposable() var recyclerAdapter: TaskRecyclerViewAdapter? = null var itemAnimator = SafeDefaultItemAnimator() + @Inject lateinit var userRepository: UserRepository + @Inject lateinit var inventoryRepository: InventoryRepository + @Inject lateinit var taskRepository: TaskRepository + @Inject lateinit var soundManager: SoundManager + @Inject lateinit var configManager: AppConfigManager + @Inject lateinit var sharedPreferences: SharedPreferences + @Inject lateinit var notificationsManager: NotificationsManager @@ -114,7 +125,11 @@ open class TaskRecyclerViewFragment : BaseFragment HabitsRecyclerViewAdapter(R.layout.habit_item_card, viewModel) TaskType.DAILY -> DailiesRecyclerViewHolder(R.layout.daily_item_card, viewModel) TaskType.TODO -> TodosRecyclerViewAdapter(R.layout.todo_item_card, viewModel) - TaskType.REWARD -> RewardsRecyclerViewAdapter(null, R.layout.reward_item_card, viewModel) + TaskType.REWARD -> RewardsRecyclerViewAdapter( + null, + R.layout.reward_item_card, + viewModel + ) else -> null } @@ -129,7 +144,9 @@ open class TaskRecyclerViewFragment : BaseFragment notificationsManager.dismissTaskNotification(it1, it.first) } - }?.subscribeWithErrorHandler { scoreTask(it.first, it.second) }?.let { recyclerSubscription.add(it) } + }?.subscribeWithErrorHandler { scoreTask(it.first, it.second) } + ?.let { recyclerSubscription.add(it) } recyclerAdapter?.checklistItemScoreEvents?.subscribeWithErrorHandler { scoreChecklistItem(it.first, it.second) }?.let { recyclerSubscription.add(it) } - recyclerAdapter?.brokenTaskEvents?.subscribeWithErrorHandler { showBrokenChallengeDialog(it) }?.let { recyclerSubscription.add(it) } - recyclerAdapter?.adventureGuideOpenEvents?.subscribeWithErrorHandler { MainNavigationController.navigate(R.id.adventureGuideActivity) }?.let { recyclerSubscription.add(it) } + recyclerAdapter?.brokenTaskEvents?.subscribeWithErrorHandler { showBrokenChallengeDialog(it) } + ?.let { recyclerSubscription.add(it) } + recyclerAdapter?.adventureGuideOpenEvents?.subscribeWithErrorHandler { + MainNavigationController.navigate( + R.id.adventureGuideActivity + ) + }?.let { recyclerSubscription.add(it) } viewModel.ownerID.observe(viewLifecycleOwner) { canEditTasks = viewModel.isPersonalBoard @@ -172,7 +195,9 @@ open class TaskRecyclerViewFragment : BaseFragment HabiticaSnackbar.showSnackbar( - activity.snackbarContainer, null, getString(R.string.notification_purchase_reward), + activity.snackbarContainer, + null, + getString(R.string.notification_purchase_reward), BitmapDrawable(resources, HabiticaIconsHelper.imageOfGold()), ContextCompat.getColor(activity, R.color.yellow_10), "-$value", @@ -221,7 +246,10 @@ open class TaskRecyclerViewFragment : BaseFragment= (recyclerAdapter?.data?.size ?: 0)) { recyclerAdapter?.data?.get(newPosition - 1)?.position ?: newPosition } else { - (recyclerAdapter?.data?.get(newPosition + 1)?.position ?: newPosition) - 1 + (recyclerAdapter?.data?.get(newPosition + 1)?.position + ?: newPosition) - 1 } } //Factor in if adventure guide is shown. if (recyclerAdapter?.showAdventureGuide == true) { newPosition = newPosition - 1 } - compositeSubscription.add( + lifecycleScope.launchCatching { taskRepository.updateTaskPosition( taskType, validTaskId, newPosition ) - .delay(1, TimeUnit.SECONDS) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe( - { - }, - ExceptionHandler.rx() - ) - ) + } } } } @@ -332,7 +361,8 @@ open class TaskRecyclerViewFragment : BaseFragment - val taskCount = tasks.size - val dialog = HabiticaAlertDialog(it) - dialog.setTitle(R.string.broken_challenge) - dialog.setMessage(it.getString(R.string.broken_challenge_description, taskCount)) - dialog.addButton(it.getString(R.string.keep_x_tasks, taskCount), true) { _, _ -> - if (!task.isValid) return@addButton + lifecycleScope.launchCatching { + val tasks = taskRepository.getTasksForChallenge(task.challengeID).firstOrNull() + ?: return@launchCatching + val taskCount = tasks.size + val dialog = HabiticaAlertDialog(it) + dialog.setTitle(R.string.broken_challenge) + dialog.setMessage( + it.getString( + R.string.broken_challenge_description, + taskCount + ) + ) + dialog.addButton( + it.getString(R.string.keep_x_tasks, taskCount), + true + ) { _, _ -> + if (!task.isValid) return@addButton + lifecycleScope.launch { taskRepository.unlinkAllTasks(task.challengeID, "keep-all") - .subscribe({ - lifecycleScope.launch(ExceptionHandler.coroutine()) { - userRepository.retrieveUser(true, forced = true) - } - }, ExceptionHandler.rx()) + userRepository.retrieveUser(true, forced = true) } - dialog.addButton( - it.getString(R.string.delete_x_tasks, taskCount), - isPrimary = false, - isDestructive = true - ) { _, _ -> - if (!task.isValid) return@addButton + } + dialog.addButton( + it.getString(R.string.delete_x_tasks, taskCount), + isPrimary = false, + isDestructive = true + ) { _, _ -> + if (!task.isValid) return@addButton + lifecycleScope.launch { taskRepository.unlinkAllTasks(task.challengeID, "remove-all") - .subscribe({ - lifecycleScope.launch(ExceptionHandler.coroutine()) { - userRepository.retrieveUser(true, forced = true) - } - }, ExceptionHandler.rx()) - } - dialog.setExtraCloseButtonVisibility(View.VISIBLE) - dialog.show() - }, - ExceptionHandler.rx() - ) - } - } - - private fun setEmptyLabels() { - binding?.recyclerView?.emptyItem = if (viewModel.filterCount(taskType) > 0) { - when (this.taskType) { - TaskType.HABIT -> { - EmptyItem( - getString(R.string.empty_title_habits_filtered), - getString(R.string.empty_description_habits_filtered), - R.drawable.icon_habits - ) - } - TaskType.DAILY -> { - EmptyItem( - getString(R.string.empty_title_dailies_filtered), - getString(R.string.empty_description_dailies_filtered), - R.drawable.icon_dailies - ) - } - TaskType.TODO -> { - EmptyItem( - getString(R.string.empty_title_todos_filtered), - getString(R.string.empty_description_todos_filtered), - R.drawable.icon_todos - ) - } - TaskType.REWARD -> { - EmptyItem( - getString(R.string.empty_title_rewards_filtered), - null, - R.drawable.icon_rewards - ) - } - else -> EmptyItem("") - } - } else { - when (this.taskType) { - TaskType.HABIT -> { - EmptyItem( - getString(R.string.empty_title_habits), - getString(R.string.empty_description_habits), - R.drawable.icon_habits - ) - } - TaskType.DAILY -> { - EmptyItem( - getString(R.string.empty_title_dailies), - getString(R.string.empty_description_dailies), - R.drawable.icon_dailies - ) - } - TaskType.TODO -> { - EmptyItem( - getString(R.string.empty_title_todos), - getString(R.string.empty_description_todos), - R.drawable.icon_todos - ) - } - TaskType.REWARD -> { - EmptyItem( - getString(R.string.empty_title_rewards), - null, - R.drawable.icon_rewards - ) - } - else -> EmptyItem("") - } - } - } - - private fun scoreTask(task: Task, direction: TaskDirection) { - viewModel.scoreTask(task, direction) { result, value -> - handleTaskResult(result, value) - } - } - - override fun onSaveInstanceState(outState: Bundle) { - super.onSaveInstanceState(outState) - outState.putString(CLASS_TYPE_KEY, this.taskType.value) - } - - override val displayedClassName: String? - get() = this.taskType.value + super.displayedClassName - - override fun onRefresh() { - binding?.refreshLayout?.isRefreshing = true - viewModel.refreshData { - binding?.refreshLayout?.isRefreshing = false - } - } - - override fun onResume() { - super.onResume() - context?.let { recyclerAdapter?.taskDisplayMode = configManager.taskDisplayMode(it) } - setInnerAdapter() - } - - fun setActiveFilter(activeFilter: String) { - viewModel.setActiveFilter(taskType, activeFilter) - recyclerAdapter?.filter() - - setEmptyLabels() - - if (activeFilter == Task.FILTER_COMPLETED) { - compositeSubscription.add(taskRepository.retrieveCompletedTodos().subscribe({}, ExceptionHandler.rx())) - } - } - - private fun setPreferenceTaskFilters() { - (activity as? MainActivity)?.viewModel?.user?.observeOnce(this) { - if (it != null) { - when (taskType) { - TaskType.TODO -> viewModel.setActiveFilter( - TaskType.TODO, - Task.FILTER_ACTIVE - ) - TaskType.DAILY -> { - if (!viewModel.initialPreferenceFilterSet) { - viewModel.initialPreferenceFilterSet = true - if (it.isValid && it.preferences?.dailyDueDefaultView == true) { - viewModel.setActiveFilter(TaskType.DAILY, Task.FILTER_ACTIVE) - } - } - } - else -> {} - } - } - } - } - - private fun openTaskForm(task: Task) { - if (Date().time - (TasksFragment.lastTaskFormOpen?.time ?: 0) < 2000 || !task.isValid || !canEditTasks) { - return - } - - val bundle = Bundle() - bundle.putString(TaskFormActivity.TASK_TYPE_KEY, task.type?.value) - bundle.putString(TaskFormActivity.TASK_ID_KEY, task.id) - bundle.putDouble(TaskFormActivity.TASK_VALUE_KEY, task.value) - - if (task.canEdit(viewModel.userViewModel.userID)) { - MainNavigationController.navigate(R.id.taskFormActivity, bundle) - } else { - MainNavigationController.navigate(R.id.taskSummaryActivity, bundle) - } - TasksFragment.lastTaskFormOpen = Date() - } - - companion object { - private const val CLASS_TYPE_KEY = "CLASS_TYPE_KEY" - - fun newInstance(context: Context?, classType: TaskType): TaskRecyclerViewFragment { - val fragment = TaskRecyclerViewFragment() - fragment.taskType = classType - var tutorialTexts: List? = null - if (context != null) { - when (fragment.taskType) { - TaskType.HABIT -> { - fragment.tutorialStepIdentifier = "habits" - tutorialTexts = listOf(context.getString(R.string.tutorial_overview), context.getString(R.string.tutorial_habits_1), context.getString(R.string.tutorial_habits_2), context.getString(R.string.tutorial_habits_3), context.getString(R.string.tutorial_habits_4)) - } - TaskType.DAILY -> { - fragment.tutorialStepIdentifier = "dailies" - tutorialTexts = listOf(context.getString(R.string.tutorial_dailies_1), context.getString(R.string.tutorial_dailies_2)) - } - TaskType.TODO -> { - fragment.tutorialStepIdentifier = "todos" - tutorialTexts = listOf(context.getString(R.string.tutorial_todos_1), context.getString(R.string.tutorial_todos_2)) - } - TaskType.REWARD -> { - fragment.tutorialStepIdentifier = "rewards" - tutorialTexts = listOf(context.getString(R.string.tutorial_rewards_1), context.getString(R.string.tutorial_rewards_2)) + userRepository.retrieveUser(true, forced = true) } } - } - - if (tutorialTexts != null) { - fragment.tutorialTexts = ArrayList(tutorialTexts) - } - fragment.tutorialCanBeDeferred = false - - return fragment + dialog.setExtraCloseButtonVisibility(View.VISIBLE) + dialog.show() } } } + +private fun setEmptyLabels() { + binding?.recyclerView?.emptyItem = if (viewModel.filterCount(taskType) > 0) { + when (this.taskType) { + TaskType.HABIT -> { + EmptyItem( + getString(R.string.empty_title_habits_filtered), + getString(R.string.empty_description_habits_filtered), + R.drawable.icon_habits + ) + } + TaskType.DAILY -> { + EmptyItem( + getString(R.string.empty_title_dailies_filtered), + getString(R.string.empty_description_dailies_filtered), + R.drawable.icon_dailies + ) + } + TaskType.TODO -> { + EmptyItem( + getString(R.string.empty_title_todos_filtered), + getString(R.string.empty_description_todos_filtered), + R.drawable.icon_todos + ) + } + TaskType.REWARD -> { + EmptyItem( + getString(R.string.empty_title_rewards_filtered), + null, + R.drawable.icon_rewards + ) + } + else -> EmptyItem("") + } + } else { + when (this.taskType) { + TaskType.HABIT -> { + EmptyItem( + getString(R.string.empty_title_habits), + getString(R.string.empty_description_habits), + R.drawable.icon_habits + ) + } + TaskType.DAILY -> { + EmptyItem( + getString(R.string.empty_title_dailies), + getString(R.string.empty_description_dailies), + R.drawable.icon_dailies + ) + } + TaskType.TODO -> { + EmptyItem( + getString(R.string.empty_title_todos), + getString(R.string.empty_description_todos), + R.drawable.icon_todos + ) + } + TaskType.REWARD -> { + EmptyItem( + getString(R.string.empty_title_rewards), + null, + R.drawable.icon_rewards + ) + } + else -> EmptyItem("") + } + } +} + +private fun scoreTask(task: Task, direction: TaskDirection) { + viewModel.scoreTask(task, direction) { result, value -> + handleTaskResult(result, value) + } +} + +override fun onSaveInstanceState(outState: Bundle) { + super.onSaveInstanceState(outState) + outState.putString(CLASS_TYPE_KEY, this.taskType.value) +} + +override val displayedClassName: String? + get() = this.taskType.value + super.displayedClassName + +override fun onRefresh() { + binding?.refreshLayout?.isRefreshing = true + viewModel.refreshData { + binding?.refreshLayout?.isRefreshing = false + } +} + +override fun onResume() { + super.onResume() + context?.let { recyclerAdapter?.taskDisplayMode = configManager.taskDisplayMode(it) } + setInnerAdapter() +} + +fun setActiveFilter(activeFilter: String) { + viewModel.setActiveFilter(taskType, activeFilter) + recyclerAdapter?.filter() + + setEmptyLabels() + + if (activeFilter == Task.FILTER_COMPLETED) { + lifecycleScope.launchCatching { + taskRepository.retrieveCompletedTodos() + } + } +} + +private fun setPreferenceTaskFilters() { + (activity as? MainActivity)?.viewModel?.user?.observeOnce(this) { + if (it != null) { + when (taskType) { + TaskType.TODO -> viewModel.setActiveFilter( + TaskType.TODO, + Task.FILTER_ACTIVE + ) + TaskType.DAILY -> { + if (!viewModel.initialPreferenceFilterSet) { + viewModel.initialPreferenceFilterSet = true + if (it.isValid && it.preferences?.dailyDueDefaultView == true) { + viewModel.setActiveFilter(TaskType.DAILY, Task.FILTER_ACTIVE) + } + } + } + else -> {} + } + } + } +} + +private fun openTaskForm(task: Task) { + if (Date().time - (TasksFragment.lastTaskFormOpen?.time + ?: 0) < 2000 || !task.isValid || !canEditTasks + ) { + return + } + + val bundle = Bundle() + bundle.putString(TaskFormActivity.TASK_TYPE_KEY, task.type?.value) + bundle.putString(TaskFormActivity.TASK_ID_KEY, task.id) + bundle.putDouble(TaskFormActivity.TASK_VALUE_KEY, task.value) + + if (task.canEdit(viewModel.userViewModel.userID)) { + MainNavigationController.navigate(R.id.taskFormActivity, bundle) + } else { + MainNavigationController.navigate(R.id.taskSummaryActivity, bundle) + } + TasksFragment.lastTaskFormOpen = Date() +} + +companion object { + private const val CLASS_TYPE_KEY = "CLASS_TYPE_KEY" + + fun newInstance(context: Context?, classType: TaskType): TaskRecyclerViewFragment { + val fragment = TaskRecyclerViewFragment() + fragment.taskType = classType + var tutorialTexts: List? = null + if (context != null) { + when (fragment.taskType) { + TaskType.HABIT -> { + fragment.tutorialStepIdentifier = "habits" + tutorialTexts = listOf( + context.getString(R.string.tutorial_overview), + context.getString(R.string.tutorial_habits_1), + context.getString(R.string.tutorial_habits_2), + context.getString(R.string.tutorial_habits_3), + context.getString(R.string.tutorial_habits_4) + ) + } + TaskType.DAILY -> { + fragment.tutorialStepIdentifier = "dailies" + tutorialTexts = listOf( + context.getString(R.string.tutorial_dailies_1), + context.getString(R.string.tutorial_dailies_2) + ) + } + TaskType.TODO -> { + fragment.tutorialStepIdentifier = "todos" + tutorialTexts = listOf( + context.getString(R.string.tutorial_todos_1), + context.getString(R.string.tutorial_todos_2) + ) + } + TaskType.REWARD -> { + fragment.tutorialStepIdentifier = "rewards" + tutorialTexts = listOf( + context.getString(R.string.tutorial_rewards_1), + context.getString(R.string.tutorial_rewards_2) + ) + } + } + } + + if (tutorialTexts != null) { + fragment.tutorialTexts = ArrayList(tutorialTexts) + } + fragment.tutorialCanBeDeferred = false + + return fragment + } +} +} diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/tasks/TasksFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/tasks/TasksFragment.kt index 51ef3782b..fc188fbcc 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/tasks/TasksFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/tasks/TasksFragment.kt @@ -17,6 +17,7 @@ import androidx.core.content.ContextCompat import androidx.core.content.edit import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels +import androidx.lifecycle.lifecycleScope import androidx.viewpager2.adapter.FragmentStateAdapter import androidx.viewpager2.widget.ViewPager2 import com.habitrpg.android.habitica.HabiticaBaseApplication @@ -25,7 +26,7 @@ import com.habitrpg.android.habitica.components.UserComponent import com.habitrpg.android.habitica.databinding.FragmentViewpagerBinding import com.habitrpg.android.habitica.extensions.setTintWith import com.habitrpg.android.habitica.helpers.AmplitudeManager -import com.habitrpg.android.habitica.helpers.ExceptionHandler +import com.habitrpg.android.habitica.helpers.launchCatching import com.habitrpg.android.habitica.ui.activities.TaskFormActivity import com.habitrpg.android.habitica.ui.fragments.BaseMainFragment import com.habitrpg.android.habitica.ui.viewmodels.TasksViewModel @@ -257,9 +258,9 @@ class TasksFragment : BaseMainFragment(), SearchView.O if (bottomNavigation == null) { return } - compositeSubscription.add( - tutorialRepository.getTutorialSteps(listOf("habits", "dailies", "todos", "rewards")).subscribe( - { tutorialSteps -> + lifecycleScope.launchCatching { + tutorialRepository.getTutorialSteps(listOf("habits", "dailies", "todos", "rewards")) + .collect { tutorialSteps -> val activeTutorialFragments = ArrayList() for (step in tutorialSteps) { var id = -1 @@ -291,7 +292,8 @@ class TasksFragment : BaseMainFragment(), SearchView.O } } if (activeTutorialFragments.size == 1) { - val fragment = viewFragmentsDictionary?.get(indexForTaskType(activeTutorialFragments[0])) + val fragment = + viewFragmentsDictionary?.get(indexForTaskType(activeTutorialFragments[0])) if (fragment?.tutorialTexts != null && context != null) { val finalText = context?.getString(R.string.tutorial_tasks_complete) if (!fragment.tutorialTexts.contains(finalText) && finalText != null) { @@ -299,10 +301,8 @@ class TasksFragment : BaseMainFragment(), SearchView.O } } } - }, - ExceptionHandler.rx() - ) - ) + } + } } // endregion diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/helpers/AutocompleteAdapter.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/helpers/AutocompleteAdapter.kt index d624d7787..0812b69c6 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/helpers/AutocompleteAdapter.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/helpers/AutocompleteAdapter.kt @@ -43,11 +43,11 @@ class AutocompleteAdapter( lastAutocomplete = Date().time userResults = arrayListOf() isAutocompletingUsers = true - socialRepository.findUsernames(constraint.toString().drop(1), autocompleteContext, groupID).blockingSubscribe { + /*socialRepository.findUsernames(constraint.toString().drop(1), autocompleteContext, groupID).blockingSubscribe { userResults = it filterResults.values = userResults filterResults.count = userResults.size - } + }*/ } else { filterResults.values = userResults filterResults.count = userResults.size diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/MountViewHolder.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/MountViewHolder.kt index 77cb28d8b..0bc6bae05 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/MountViewHolder.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/MountViewHolder.kt @@ -9,12 +9,11 @@ import com.habitrpg.android.habitica.R import com.habitrpg.android.habitica.databinding.MountOverviewItemBinding import com.habitrpg.android.habitica.extensions.inflate import com.habitrpg.android.habitica.models.inventory.Mount -import com.habitrpg.common.habitica.extensions.DataBindingUtils import com.habitrpg.android.habitica.ui.menu.BottomSheetMenu import com.habitrpg.android.habitica.ui.menu.BottomSheetMenuItem -import io.reactivex.rxjava3.subjects.PublishSubject +import com.habitrpg.common.habitica.extensions.DataBindingUtils -class MountViewHolder(parent: ViewGroup, private val equipEvents: PublishSubject) : androidx.recyclerview.widget.RecyclerView.ViewHolder(parent.inflate(R.layout.mount_overview_item)), View.OnClickListener { +class MountViewHolder(parent: ViewGroup, private val onEquip: ((String) -> Unit)?) : androidx.recyclerview.widget.RecyclerView.ViewHolder(parent.inflate(R.layout.mount_overview_item)), View.OnClickListener { private var binding: MountOverviewItemBinding = MountOverviewItemBinding.bind(itemView) private var owned: Boolean = false var animal: Mount? = null @@ -60,7 +59,7 @@ class MountViewHolder(parent: ViewGroup, private val equipEvents: PublishSubject val labelId = if (hasCurrentMount) R.string.unequip else R.string.equip menu.addMenuItem(BottomSheetMenuItem(resources.getString(labelId))) menu.setSelectionRunnable { - animal?.let { equipEvents.onNext(it.key ?: "") } + animal?.let { onEquip?.invoke(it.key ?: "") } } menu.show() } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/PetViewHolder.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/PetViewHolder.kt index f17959cfe..be791b6f7 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/PetViewHolder.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/PetViewHolder.kt @@ -13,16 +13,15 @@ import com.habitrpg.android.habitica.models.inventory.Egg import com.habitrpg.android.habitica.models.inventory.Food import com.habitrpg.android.habitica.models.inventory.HatchingPotion import com.habitrpg.android.habitica.models.inventory.Pet -import com.habitrpg.common.habitica.extensions.DataBindingUtils import com.habitrpg.android.habitica.ui.menu.BottomSheetMenu import com.habitrpg.android.habitica.ui.menu.BottomSheetMenuItem import com.habitrpg.android.habitica.ui.views.dialogs.PetSuggestHatchDialog -import io.reactivex.rxjava3.subjects.PublishSubject +import com.habitrpg.common.habitica.extensions.DataBindingUtils class PetViewHolder( parent: ViewGroup, - private val equipEvents: PublishSubject, - private val feedEvents: PublishSubject>, + private val onEquip: ((String) -> Unit)?, + private val onFeed: ((Pet, Food?) -> Unit)?, private val ingredientsReceiver: ((Animal, ((Pair) -> Unit)) -> Unit)? ) : androidx.recyclerview.widget.RecyclerView.ViewHolder(parent.inflate(R.layout.pet_detail_item)), View.OnClickListener { @@ -130,16 +129,16 @@ class PetViewHolder( when (index) { 0 -> { animal?.let { - equipEvents.onNext(it.key ?: "") + onEquip?.invoke(it.key ?: "") } } 1 -> { - feedEvents.onNext(Pair(pet, null)) + onFeed?.invoke(pet, null) } 2 -> { val saddle = Food() saddle.key = "Saddle" - feedEvents.onNext(Pair(pet, saddle)) + onFeed?.invoke(pet, saddle) } } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/AuthenticationViewModel.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/AuthenticationViewModel.kt index 8c5b6958c..dce3fc33d 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/AuthenticationViewModel.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/AuthenticationViewModel.kt @@ -17,16 +17,15 @@ import com.google.android.gms.common.ConnectionResult import com.google.android.gms.common.GoogleApiAvailability import com.google.android.gms.common.GooglePlayServicesUtil import com.google.android.gms.common.Scopes -import com.google.android.gms.common.UserRecoverableException import com.habitrpg.android.habitica.BuildConfig import com.habitrpg.android.habitica.HabiticaBaseApplication import com.habitrpg.android.habitica.R import com.habitrpg.android.habitica.data.ApiClient import com.habitrpg.android.habitica.data.UserRepository import com.habitrpg.android.habitica.extensions.addCloseButton -import com.habitrpg.android.habitica.helpers.ExceptionHandler import com.habitrpg.android.habitica.helpers.SignInWithAppleResult import com.habitrpg.android.habitica.helpers.SignInWithAppleService +import com.habitrpg.android.habitica.helpers.launchCatching import com.habitrpg.android.habitica.models.user.User import com.habitrpg.android.habitica.proxy.AnalyticsManager import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog @@ -34,13 +33,8 @@ import com.habitrpg.common.habitica.api.HostConfig import com.habitrpg.common.habitica.helpers.KeyHelper import com.habitrpg.common.habitica.models.auth.UserAuthResponse import com.willowtreeapps.signinwithapplebutton.SignInWithAppleConfiguration -import io.reactivex.rxjava3.core.Flowable import io.reactivex.rxjava3.disposables.CompositeDisposable -import io.reactivex.rxjava3.exceptions.Exceptions -import io.reactivex.rxjava3.schedulers.Schedulers import kotlinx.coroutines.MainScope -import kotlinx.coroutines.launch -import java.io.IOException import javax.inject.Inject class AuthenticationViewModel() { @@ -120,46 +114,25 @@ class AuthenticationViewModel() { val scopesString = Scopes.PROFILE + " " + Scopes.EMAIL val scopes = "oauth2:$scopesString" var newUser = false - compositeSubscription.add( - Flowable.defer { - try { - @Suppress("Deprecation") - return@defer Flowable.just(GoogleAuthUtil.getToken(activity, googleEmail ?: "", scopes)) - } catch (e: IOException) { - throw Exceptions.propagate(e) - } catch (e: GoogleAuthException) { - throw Exceptions.propagate(e) - } catch (e: UserRecoverableException) { - return@defer Flowable.empty() + MainScope().launchCatching({ throwable -> + if (recoverFromPlayServicesErrorResult == null) return@launchCatching + throwable.cause?.let { + if (GoogleAuthException::class.java.isAssignableFrom(it.javaClass)) { + handleGoogleAuthException( + throwable.cause as GoogleAuthException, + activity, + recoverFromPlayServicesErrorResult + ) } } - .subscribeOn(Schedulers.io()) - .flatMap { token -> apiClient.connectSocial("google", googleEmail ?: "", token) } - .doOnNext { - newUser = it.newUser - handleAuthResponse(it) - } - .subscribe( - { - MainScope().launch(ExceptionHandler.coroutine()) { - val user = userRepository.retrieveUser(true, true) ?: return@launch - onSuccess(user, newUser) - } - }, - { throwable -> - if (recoverFromPlayServicesErrorResult == null) return@subscribe - throwable.cause?.let { - if (GoogleAuthException::class.java.isAssignableFrom(it.javaClass)) { - handleGoogleAuthException( - throwable.cause as GoogleAuthException, - activity, - recoverFromPlayServicesErrorResult - ) - } - } - } - ) - ) + }) { + val token = GoogleAuthUtil.getToken(activity, googleEmail ?: "", scopes) + val response = apiClient.connectSocial("google", googleEmail ?: "", token) ?: return@launchCatching + newUser = response.newUser + handleAuthResponse(response) + val user = userRepository.retrieveUser(true, true) ?: return@launchCatching + onSuccess(user, newUser) + } } private fun handleGoogleAuthException( 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 e4656357d..f54e5b2dc 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 @@ -8,7 +8,6 @@ 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 @@ -33,12 +32,9 @@ abstract class BaseViewModel(initializeComponent: Boolean = true) : ViewModel() override fun onCleared() { userRepository.close() - disposable.clear() super.onCleared() } - internal val disposable = CompositeDisposable() - fun updateUser(path: String, value: Any) { viewModelScope.launch(ExceptionHandler.coroutine()) { userRepository.updateUser(path, value) diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/GroupViewModel.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/GroupViewModel.kt index 0b07975b4..06f351c1b 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/GroupViewModel.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/GroupViewModel.kt @@ -11,6 +11,7 @@ import com.habitrpg.android.habitica.data.SocialRepository import com.habitrpg.android.habitica.helpers.ExceptionHandler import com.habitrpg.android.habitica.helpers.MainNavigationController import com.habitrpg.android.habitica.helpers.NotificationsManager +import com.habitrpg.android.habitica.helpers.launchCatching import com.habitrpg.android.habitica.models.members.Member import com.habitrpg.android.habitica.models.social.Challenge import com.habitrpg.android.habitica.models.social.ChatMessage @@ -20,6 +21,7 @@ import io.realm.kotlin.toFlow import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.filterNotNull +import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch @@ -37,8 +39,10 @@ open class GroupViewModel(initializeComponent: Boolean) : BaseViewModel(initiali @Inject lateinit var challengeRepository: ChallengeRepository + @Inject lateinit var socialRepository: SocialRepository + @Inject lateinit var notificationsManager: NotificationsManager @@ -52,13 +56,11 @@ open class GroupViewModel(initializeComponent: Boolean) : BaseViewModel(initiali .flatMapLatest { socialRepository.getGroup(it) } private val group = groupFlow.asLiveData() - private val leaderFlow = groupFlow.map { it?.leaderID } .filterNotNull() .flatMapLatest { socialRepository.retrieveMember(it).toFlow() } private val leader = leaderFlow.asLiveData() - private val isMemberFlow = groupIDFlow .filterNotNull() .flatMapLatest { socialRepository.getGroupMembership(it) } @@ -87,21 +89,14 @@ open class GroupViewModel(initializeComponent: Boolean) : BaseViewModel(initiali if (groupID == groupIDState.value) return groupIDState.value = groupID - disposable.add( - notificationsManager.getNotifications().firstElement().map { - it.filter { notification -> + viewModelScope.launchCatching { + val notifications = + notificationsManager.getNotifications().firstOrNull()?.filter { notification -> val data = notification.data as? NewChatMessageData data?.group?.id == groupID - } - } - .filter { it.isNotEmpty() } - .flatMapPublisher { userRepository.readNotification(it.first().id) } - .subscribe( - { - }, - ExceptionHandler.rx() - ) - ) + } ?: return@launchCatching + notifications.forEach { userRepository.readNotification(it.id) } + } } val groupID: String? @@ -136,10 +131,9 @@ open class GroupViewModel(initializeComponent: Boolean) : BaseViewModel(initiali } fun inviteToGroup(inviteData: HashMap) { - disposable.add( + viewModelScope.launchCatching { socialRepository.inviteToGroup(group.value?.id ?: "", inviteData) - .subscribe({ }, ExceptionHandler.rx()) - ) + } } fun updateOrCreateGroup(bundle: Bundle?) { @@ -154,12 +148,12 @@ open class GroupViewModel(initializeComponent: Boolean) : BaseViewModel(initiali bundle?.getBoolean("leaderCreateChallenge") ) } else { - socialRepository.updateGroup( - group.value, bundle?.getString("name"), - bundle?.getString("description"), - bundle?.getString("leader"), - bundle?.getBoolean("leaderCreateChallenge") - ) + socialRepository.updateGroup( + group.value, bundle?.getString("name"), + bundle?.getString("description"), + bundle?.getString("leader"), + bundle?.getBoolean("leaderCreateChallenge") + ) } } } @@ -170,8 +164,10 @@ open class GroupViewModel(initializeComponent: Boolean) : BaseViewModel(initiali function: (() -> Unit)? = null ) { if (!keepChallenges) { - for (challenge in groupChallenges) { - challengeRepository.leaveChallenge(challenge, "remove-all").subscribe({}, ExceptionHandler.rx()) + viewModelScope.launchCatching { + for (challenge in groupChallenges) { + challengeRepository.leaveChallenge(challenge, "remove-all") + } } } viewModelScope.launch(ExceptionHandler.coroutine()) { @@ -181,7 +177,7 @@ open class GroupViewModel(initializeComponent: Boolean) : BaseViewModel(initiali } fun joinGroup(id: String? = null, function: (() -> Unit)? = null) { - viewModelScope.launch(ExceptionHandler.coroutine()) { + viewModelScope.launchCatching { socialRepository.joinGroup(id ?: groupID) function?.invoke() } @@ -189,14 +185,18 @@ open class GroupViewModel(initializeComponent: Boolean) : BaseViewModel(initiali fun rejectGroupInvite(id: String? = null) { groupID?.let { - disposable.add(socialRepository.rejectGroupInvite(id ?: it).subscribe({ }, ExceptionHandler.rx())) + viewModelScope.launchCatching { + socialRepository.rejectGroupInvite(id ?: it) + } } } fun markMessagesSeen() { groupID?.let { if (groupViewType != GroupViewType.TAVERN && it.isNotEmpty() && gotNewMessages) { - socialRepository.markMessagesSeen(it) + viewModelScope.launchCatching { + socialRepository.markMessagesSeen(it) + } } } } @@ -204,15 +204,14 @@ open class GroupViewModel(initializeComponent: Boolean) : BaseViewModel(initiali fun likeMessage(message: ChatMessage) { val index = _chatMessages.value?.indexOf(message) if (index == null || index < 0) return - disposable.add( - socialRepository.likeMessage(message).subscribe( - { - val list = _chatMessages.value?.toMutableList() - list?.set(index, it) - _chatMessages.postValue(list) - }, ExceptionHandler.rx() - ) - ) + viewModelScope.launchCatching { + val message = socialRepository.likeMessage(message) + val list = _chatMessages.value?.toMutableList() + if (message != null) { + list?.set(index, message) + } + _chatMessages.postValue(list) + } } fun deleteMessage(chatMessage: ChatMessage) { @@ -220,30 +219,29 @@ open class GroupViewModel(initializeComponent: Boolean) : BaseViewModel(initiali val list = _chatMessages.value?.toMutableList() list?.remove(chatMessage) _chatMessages.postValue(list) - disposable.add( - socialRepository.deleteMessage(chatMessage).subscribe({ - }, { - list?.add(oldIndex, chatMessage) - _chatMessages.postValue(list) - ExceptionHandler.reportError(it) - }) - ) + viewModelScope.launch(ExceptionHandler.coroutine { + list?.add(oldIndex, chatMessage) + _chatMessages.postValue(list) + ExceptionHandler.reportError(it) + }) { + socialRepository.deleteMessage(chatMessage) + } } fun postGroupChat(chatText: String, onComplete: () -> Unit, onError: () -> Unit) { groupID?.let { groupID -> - socialRepository.postGroupChat(groupID, chatText).subscribe( - { - val list = _chatMessages.value?.toMutableList() - list?.add(0, it.message) - _chatMessages.postValue(list) - onComplete() - }, - { error -> - ExceptionHandler.reportError(error) - onError() + viewModelScope.launch(ExceptionHandler.coroutine { + ExceptionHandler.reportError(it) + onError() + }) { + val response = socialRepository.postGroupChat(groupID, chatText) + val list = _chatMessages.value?.toMutableList() + if (response != null) { + list?.add(0, response.message) } - ) + _chatMessages.postValue(list) + onComplete() + } } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/MainActivityViewModel.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/MainActivityViewModel.kt index d1110e573..9c6c215d4 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/MainActivityViewModel.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/MainActivityViewModel.kt @@ -12,6 +12,7 @@ import com.habitrpg.android.habitica.data.TaskRepository import com.habitrpg.android.habitica.helpers.AmplitudeManager import com.habitrpg.android.habitica.helpers.ExceptionHandler 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.TutorialStep import com.habitrpg.android.habitica.models.inventory.Egg @@ -19,11 +20,10 @@ import com.habitrpg.android.habitica.proxy.AnalyticsManager import com.habitrpg.android.habitica.ui.TutorialView import com.habitrpg.common.habitica.api.HostConfig import com.habitrpg.shared.habitica.models.responses.MaintenanceResponse -import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers -import io.reactivex.rxjava3.schedulers.Schedulers import io.realm.kotlin.isValid +import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.launch -import java.util.* +import java.util.Date import javax.inject.Inject class MainActivityViewModel : BaseViewModel(), TutorialView.OnTutorialReaction { @@ -101,10 +101,9 @@ class MainActivityViewModel : BaseViewModel(), TutorialView.OnTutorialReaction { } contentRepository.retrieveContent() } - disposable.add( + viewModelScope.launchCatching { userRepository.retrieveTeamPlans() - .subscribe({ }, ExceptionHandler.rx()) - ) + } } } @@ -131,20 +130,13 @@ class MainActivityViewModel : BaseViewModel(), TutorialView.OnTutorialReaction { } fun ifNeedsMaintenance(onResult: ((MaintenanceResponse) -> Unit)) { - disposable.add( - this.maintenanceService.maintenanceStatus - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe( - { maintenanceResponse -> - if (maintenanceResponse.activeMaintenance == null) { - return@subscribe - } - onResult(maintenanceResponse) - }, - ExceptionHandler.rx() - ) - ) + viewModelScope.launchCatching { + val maintenanceResponse = maintenanceService.getMaintenanceStatus() + if (maintenanceResponse?.activeMaintenance == null) { + return@launchCatching + } + onResult(maintenanceResponse) + } } fun getToolbarTitle( @@ -154,21 +146,17 @@ class MainActivityViewModel : BaseViewModel(), TutorialView.OnTutorialReaction { onSuccess: ((CharSequence?) -> Unit) ) { if (id == R.id.petDetailRecyclerFragment || id == R.id.mountDetailRecyclerFragment) { - disposable.add( - inventoryRepository.getItem("egg", eggType ?: "").firstElement().subscribe( - { - if (!it.isValid()) return@subscribe - onSuccess( - if (id == R.id.petDetailRecyclerFragment) { - (it as? Egg)?.text - } else { - (it as? Egg)?.mountText - } - ) - }, - ExceptionHandler.rx() + viewModelScope.launchCatching { + val item = inventoryRepository.getItem("egg", eggType ?: "").firstOrNull() + if (item?.isValid() != true) return@launchCatching + onSuccess( + if (id == R.id.petDetailRecyclerFragment) { + (item as? Egg)?.text + } else { + (item as? Egg)?.mountText + } ) - ) + } } else { onSuccess( if (id == R.id.promoInfoFragment) { diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/NotificationsViewModel.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/NotificationsViewModel.kt index 42f00777f..292a1465c 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/NotificationsViewModel.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/NotificationsViewModel.kt @@ -6,9 +6,10 @@ import androidx.lifecycle.viewModelScope import com.habitrpg.android.habitica.R import com.habitrpg.android.habitica.components.UserComponent import com.habitrpg.android.habitica.data.SocialRepository +import com.habitrpg.android.habitica.helpers.ExceptionHandler import com.habitrpg.android.habitica.helpers.MainNavigationController import com.habitrpg.android.habitica.helpers.NotificationsManager -import com.habitrpg.android.habitica.helpers.ExceptionHandler +import com.habitrpg.android.habitica.helpers.launchCatching import com.habitrpg.android.habitica.models.social.UserParty import com.habitrpg.android.habitica.models.user.User import com.habitrpg.common.habitica.models.Notification @@ -20,11 +21,11 @@ import com.habitrpg.common.habitica.models.notifications.NewStuffData import com.habitrpg.common.habitica.models.notifications.PartyInvitationData import com.habitrpg.common.habitica.models.notifications.PartyInvite import com.habitrpg.common.habitica.models.notifications.QuestInvitationData -import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers -import io.reactivex.rxjava3.core.BackpressureStrategy -import io.reactivex.rxjava3.core.Flowable -import io.reactivex.rxjava3.functions.BiFunction -import io.reactivex.rxjava3.subjects.BehaviorSubject +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch import javax.inject.Inject @@ -51,15 +52,13 @@ open class NotificationsViewModel : BaseViewModel() { private var party: UserParty? = null - private val customNotifications: BehaviorSubject> = BehaviorSubject.create() + private val customNotifications = MutableStateFlow>(emptyList()) override fun inject(component: UserComponent) { component.inject(this) } init { - customNotifications.onNext(emptyList()) - userViewModel.user.observeForever { if (it == null) return@observeForever party = it.party @@ -72,42 +71,35 @@ open class NotificationsViewModel : BaseViewModel() { notification.data = data notifications.add(notification) } - customNotifications.onNext(notifications) + customNotifications.value = notifications } } - fun getNotifications(): Flowable> { + fun getNotifications(): Flow> { val serverNotifications = notificationsManager.getNotifications() .map { filterSupportedTypes(it) } - return Flowable.combineLatest( - serverNotifications, - customNotifications.toFlowable(BackpressureStrategy.LATEST), - BiFunction { - serverNotificationsList, customNotificationsList -> - if (serverNotificationsList.firstOrNull { notification -> notification.type == Notification.Type.NEW_STUFF.type } != null) { - return@BiFunction serverNotificationsList + customNotificationsList.filter { notification -> notification.type != Notification.Type.NEW_STUFF.type } - } - return@BiFunction serverNotificationsList + customNotificationsList + return serverNotifications.combine(customNotifications) { serverNotificationsList, customNotificationsList -> + if (serverNotificationsList.firstOrNull { notification -> notification.type == Notification.Type.NEW_STUFF.type } != null) { + return@combine serverNotificationsList + customNotificationsList.filter { notification -> notification.type != Notification.Type.NEW_STUFF.type } } - ) - .map { it.sortedBy { notification -> notification.priority } } - .observeOn(AndroidSchedulers.mainThread()) + return@combine serverNotificationsList + customNotificationsList + }.map { it.sortedBy { notification -> notification.priority } } } - fun getNotificationCount(): Flowable { + fun getNotificationCount(): Flow { return getNotifications() .map { it.count() } .distinctUntilChanged() } - fun allNotificationsSeen(): Flowable { + fun allNotificationsSeen(): Flow { return getNotifications() .map { it.all { notification -> notification.seen != false } } .distinctUntilChanged() } - fun getHasPartyNotification(): Flowable { + fun getHasPartyNotification(): Flow { return getNotifications() .map { it.find { notification -> @@ -197,17 +189,14 @@ open class NotificationsViewModel : BaseViewModel() { fun dismissNotification(notification: Notification) { if (isCustomNotification(notification)) { if (isCustomNewStuffNotification(notification)) { - customNotifications.onNext( - customNotifications.value?.filterNot { it.id == notification.id } ?: listOf() - ) + customNotifications.value = customNotifications.value.filterNot { it.id == notification.id } } return } - disposable.add( + viewModelScope.launchCatching { userRepository.readNotification(notification.id) - .subscribe({}, ExceptionHandler.rx()) - ) + } } fun dismissAllNotifications(notifications: List) { @@ -230,10 +219,9 @@ open class NotificationsViewModel : BaseViewModel() { val notificationIds = HashMap>() notificationIds["notificationIds"] = dismissableIds - disposable.add( + viewModelScope.launchCatching { userRepository.readNotifications(notificationIds) - .subscribe({}, ExceptionHandler.rx()) - ) + } } fun markNotificationsAsSeen(notifications: List) { @@ -249,10 +237,9 @@ open class NotificationsViewModel : BaseViewModel() { val notificationIds = HashMap>() notificationIds["notificationIds"] = unseenIds - disposable.add( + viewModelScope.launchCatching { userRepository.seeNotifications(notificationIds) - .subscribe({}, ExceptionHandler.rx()) - ) + } } private fun findNotification(id: String): Notification? { @@ -367,43 +354,28 @@ open class NotificationsViewModel : BaseViewModel() { fun rejectGroupInvite(groupId: String?) { groupId?.let { - disposable.add( + viewModelScope.launchCatching { socialRepository.rejectGroupInvite(it) - .subscribe( - { - refreshUser() - }, - ExceptionHandler.rx() - ) - ) + refreshUser() + } } } private fun acceptQuestInvitation() { party?.id?.let { - disposable.add( + viewModelScope.launchCatching { socialRepository.acceptQuest(null, it) - .subscribe( - { - refreshUser() - }, - ExceptionHandler.rx() - ) - ) + refreshUser() + } } } private fun rejectQuestInvitation() { party?.id?.let { - disposable.add( + viewModelScope.launchCatching { socialRepository.rejectQuest(null, it) - .subscribe( - { - refreshUser() - }, - ExceptionHandler.rx() - ) - ) + refreshUser() + } } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/PartyViewModel.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/PartyViewModel.kt index 344def085..e00498adc 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/PartyViewModel.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/PartyViewModel.kt @@ -4,6 +4,7 @@ import androidx.lifecycle.asLiveData import androidx.lifecycle.viewModelScope import com.habitrpg.android.habitica.components.UserComponent import com.habitrpg.android.habitica.helpers.ExceptionHandler +import com.habitrpg.android.habitica.helpers.launchCatching import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.flatMapLatest @@ -39,33 +40,21 @@ class PartyViewModel(initializeComponent: Boolean) : GroupViewModel(initializeCo fun acceptQuest() { groupID?.let { groupID -> - disposable.add( + viewModelScope.launchCatching { socialRepository.acceptQuest(null, groupID) - .subscribe({ - viewModelScope.launch(ExceptionHandler.coroutine()) { - socialRepository.retrieveGroup(groupID) - userRepository.retrieveUser() - } - }, - ExceptionHandler.rx() - ) - ) + socialRepository.retrieveGroup(groupID) + userRepository.retrieveUser() + } } } fun rejectQuest() { groupID?.let { groupID -> - disposable.add( + viewModelScope.launchCatching { socialRepository.rejectQuest(null, groupID) - .subscribe({ - viewModelScope.launch(ExceptionHandler.coroutine()) { - socialRepository.retrieveGroup(groupID) - userRepository.retrieveUser() - } - }, - ExceptionHandler.rx() - ) - ) + socialRepository.retrieveGroup(groupID) + userRepository.retrieveUser() + } } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/StableViewModel.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/StableViewModel.kt index 588ce4585..6ed5036b6 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/StableViewModel.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/StableViewModel.kt @@ -12,6 +12,7 @@ import com.habitrpg.android.habitica.components.UserComponent import com.habitrpg.android.habitica.data.InventoryRepository import com.habitrpg.android.habitica.extensions.getTranslatedType import com.habitrpg.android.habitica.helpers.ExceptionHandler +import com.habitrpg.android.habitica.helpers.launchCatching import com.habitrpg.android.habitica.models.inventory.Animal import com.habitrpg.android.habitica.models.inventory.Egg import com.habitrpg.android.habitica.models.inventory.Mount @@ -78,7 +79,9 @@ class StableViewModel(private val application: Application?, private val itemTyp }.collect { _items.value = mapAnimals(animals, it) } - disposable.add(inventoryRepository.getOwnedItems(true).subscribe({ _ownedItems.value = it }, ExceptionHandler.rx())) + viewModelScope.launchCatching { + _ownedItems.value = inventoryRepository.getOwnedItems(true).firstOrNull() + } _mounts.value = if ("pets" == itemType) { inventoryRepository.getMounts().firstOrNull() ?: emptyList() } else { @@ -129,7 +132,7 @@ class StableViewModel(private val application: Application?, private val itemTyp val isOwned = when (itemType) { "pets" -> { val ownedPet = ownedAnimals[animal.key] as? OwnedPet - ownedPet?.trained ?: 0 > 0 + (ownedPet?.trained ?: 0) > 0 } "mounts" -> { val ownedMount = ownedAnimals[animal.key] as? OwnedMount diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/TasksViewModel.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/TasksViewModel.kt index abaf697de..2a63d6f42 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/TasksViewModel.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/TasksViewModel.kt @@ -14,6 +14,7 @@ import com.habitrpg.android.habitica.helpers.AmplitudeManager import com.habitrpg.android.habitica.helpers.AppConfigManager import com.habitrpg.android.habitica.helpers.ExceptionHandler import com.habitrpg.android.habitica.helpers.GroupPlanInfoProvider +import com.habitrpg.android.habitica.helpers.launchCatching import com.habitrpg.android.habitica.models.TeamPlan import com.habitrpg.android.habitica.models.tasks.Task import com.habitrpg.shared.habitica.models.responses.TaskDirection @@ -81,9 +82,9 @@ class TasksViewModel : BaseViewModel(), GroupPlanInfoProvider { } } } - compositeSubscription.add( - userRepository.retrieveTeamPlans().subscribe({}, ExceptionHandler.rx()) - ) + viewModelScope.launchCatching { + userRepository.retrieveTeamPlans() + } } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/inventory/equipment/EquipmentOverviewViewModel.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/inventory/equipment/EquipmentOverviewViewModel.kt index befd2219f..31133d63f 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/inventory/equipment/EquipmentOverviewViewModel.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/inventory/equipment/EquipmentOverviewViewModel.kt @@ -1,8 +1,9 @@ package com.habitrpg.android.habitica.ui.viewmodels.inventory.equipment +import androidx.lifecycle.viewModelScope import com.habitrpg.android.habitica.components.UserComponent import com.habitrpg.android.habitica.data.InventoryRepository -import com.habitrpg.android.habitica.helpers.ExceptionHandler +import com.habitrpg.android.habitica.helpers.launchCatching import com.habitrpg.android.habitica.models.inventory.Equipment import com.habitrpg.android.habitica.ui.viewmodels.BaseViewModel import javax.inject.Inject @@ -21,10 +22,10 @@ class EquipmentOverviewViewModel : BaseViewModel() { } fun getGear(key: String, onSuccess: (Equipment) -> Unit) { - disposable.add( - inventoryRepository.getEquipment(key).subscribe({ + viewModelScope.launchCatching { + inventoryRepository.getEquipment(key).collect { onSuccess(it) - }, 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 0cd43b1da..566af2cb9 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 @@ -11,7 +11,6 @@ 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 @@ -176,12 +175,14 @@ class PetSuggestHatchDialog(context: Context) : HabiticaAlertDialog(context) { private fun hatchPet(potion: HatchingPotion, egg: Egg) { (getActivity() as? BaseActivity)?.let { - hatchPetUseCase.observable( - HatchPetUseCase.RequestValues( - potion, egg, - it + it.lifecycleScope.launchCatching { + hatchPetUseCase.callInteractor( + HatchPetUseCase.RequestValues( + potion, egg, + it + ) ) - ).subscribeWithErrorHandler {} + } } } 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 b17cbb98d..fde907d89 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 @@ -46,6 +46,7 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.MainScope import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.launch import java.lang.Integer.max @@ -108,7 +109,10 @@ class PurchaseDialog(context: Context, component: UserComponent?, val item: Shop } shopItem.isTypeGear -> { contentView = PurchaseDialogGearContent(context) - inventoryRepository.getEquipment(shopItem.key).firstElement().subscribe({ contentView.setEquipment(it) }, ExceptionHandler.rx()) + lifecycleScope.launchCatching { + inventoryRepository.getEquipment(shopItem.key).firstOrNull() + ?.let { contentView.setEquipment(it) } + } checkGearClass() } "gems" == shopItem.purchaseType -> contentView = PurchaseDialogGemsContent(context) @@ -263,8 +267,9 @@ class PurchaseDialog(context: Context, component: UserComponent?, val item: Shop } shopItem = item - - compositeSubscription.add(userRepository.getUserFlowable().subscribe({ this.setUser(it) }, ExceptionHandler.rx())) + lifecycleScope.launchCatching { + userRepository.getUser().filterNotNull().collect { setUser(it) } + } } private fun setUser(user: User) { diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/stats/BulkAllocateStatsDialog.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/stats/BulkAllocateStatsDialog.kt index ab6c2836a..dbc0415fe 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/stats/BulkAllocateStatsDialog.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/stats/BulkAllocateStatsDialog.kt @@ -5,11 +5,13 @@ import android.content.Context import android.os.Bundle import androidx.appcompat.app.AlertDialog import androidx.core.content.ContextCompat +import androidx.lifecycle.lifecycleScope import com.habitrpg.android.habitica.R import com.habitrpg.android.habitica.components.UserComponent import com.habitrpg.android.habitica.data.UserRepository import com.habitrpg.android.habitica.databinding.DialogBulkAllocateBinding import com.habitrpg.android.habitica.helpers.ExceptionHandler +import com.habitrpg.android.habitica.helpers.launchCatching import com.habitrpg.common.habitica.extensions.getThemeColor import com.habitrpg.common.habitica.extensions.layoutInflater import io.reactivex.rxjava3.disposables.Disposable @@ -59,21 +61,15 @@ class BulkAllocateStatsDialog(context: Context, component: UserComponent?) : Ale private fun saveChanges() { getButton(BUTTON_POSITIVE).isEnabled = false - userRepository.bulkAllocatePoints( - binding.strengthSliderView.currentValue, - binding.intelligenceSliderView.currentValue, - binding.constitutionSliderView.currentValue, - binding.perceptionSliderView.currentValue - ) - .subscribe( - { - this.dismiss() - }, - { - ExceptionHandler.reportError(it) - this.dismiss() - } + lifecycleScope.launchCatching { + userRepository.bulkAllocatePoints( + binding.strengthSliderView.currentValue, + binding.intelligenceSliderView.currentValue, + binding.constitutionSliderView.currentValue, + binding.perceptionSliderView.currentValue ) + dismiss() + } } override fun onCreate(savedInstanceState: Bundle?) { diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/tasks/TaskFilterDialog.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/tasks/TaskFilterDialog.kt index eac11188b..eab805918 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/tasks/TaskFilterDialog.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/tasks/TaskFilterDialog.kt @@ -14,21 +14,20 @@ import androidx.appcompat.widget.AppCompatCheckBox import androidx.core.content.ContextCompat import androidx.core.widget.CompoundButtonCompat import androidx.core.widget.TextViewCompat +import androidx.lifecycle.lifecycleScope import com.habitrpg.android.habitica.R import com.habitrpg.android.habitica.components.UserComponent import com.habitrpg.android.habitica.data.TagRepository import com.habitrpg.android.habitica.databinding.DialogTaskFilterBinding import com.habitrpg.android.habitica.databinding.EditTagItemBinding import com.habitrpg.android.habitica.extensions.OnChangeTextWatcher -import com.habitrpg.android.habitica.helpers.ExceptionHandler +import com.habitrpg.android.habitica.helpers.launchCatching import com.habitrpg.android.habitica.models.Tag import com.habitrpg.android.habitica.models.tasks.Task import com.habitrpg.android.habitica.ui.viewmodels.TasksViewModel import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaBottomSheetDialog import com.habitrpg.common.habitica.extensions.getThemeColor import com.habitrpg.shared.habitica.models.tasks.TaskType -import io.reactivex.rxjava3.core.Observable -import io.reactivex.rxjava3.disposables.Disposable import java.util.UUID import javax.inject.Inject @@ -36,8 +35,6 @@ class TaskFilterDialog(context: Context, component: UserComponent?) : HabiticaBo lateinit var viewModel: TasksViewModel private val binding = DialogTaskFilterBinding.inflate(layoutInflater) - var tagDisposale: Disposable? = null - @Inject lateinit var repository: TagRepository @@ -99,14 +96,15 @@ class TaskFilterDialog(context: Context, component: UserComponent?) : HabiticaBo } override fun dismiss() { - tagDisposale?.dispose() super.dismiss() } override fun show() { - tagDisposale = viewModel.tagRepository.getTags().subscribe({ - setTags(it) - }, ExceptionHandler.rx()) + lifecycleScope.launchCatching { + viewModel.tagRepository.getTags().collect { + setTags(it) + } + } super.show() this.window?.clearFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM) } @@ -193,9 +191,12 @@ class TaskFilterDialog(context: Context, component: UserComponent?) : HabiticaBo createTagViews() binding.tagEditButton.setText(R.string.edit_tag_btn_edit) this.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN) - repository.updateTags(editedTags.values).toObservable().flatMap { tags -> Observable.fromIterable(tags) }.subscribe({ tag -> editedTags.remove(tag.id) }, ExceptionHandler.rx()) - repository.createTags(createdTags.values).toObservable().flatMap { tags -> Observable.fromIterable(tags) }.subscribe({ tag -> createdTags.remove(tag.id) }, ExceptionHandler.rx()) - repository.deleteTags(deletedTags).subscribe({ deletedTags.clear() }, ExceptionHandler.rx()) + lifecycleScope.launchCatching { + + repository.updateTags(editedTags.values).forEach { editedTags.remove(it.id) } + repository.createTags(createdTags.values).forEach { tag -> createdTags.remove(tag.id) } + repeat(repository.deleteTags(deletedTags).size) { deletedTags.clear() } + } } private fun createTagEditViews() { diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/yesterdailies/YesterdailyDialog.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/yesterdailies/YesterdailyDialog.kt index 155833d03..1f0bb6a6f 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/yesterdailies/YesterdailyDialog.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/yesterdailies/YesterdailyDialog.kt @@ -15,21 +15,24 @@ import com.habitrpg.android.habitica.data.TaskRepository import com.habitrpg.android.habitica.data.UserRepository import com.habitrpg.android.habitica.helpers.AmplitudeManager import com.habitrpg.android.habitica.helpers.ExceptionHandler +import com.habitrpg.android.habitica.helpers.launchCatching import com.habitrpg.android.habitica.models.tasks.ChecklistItem import com.habitrpg.android.habitica.models.tasks.Task import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog import com.habitrpg.common.habitica.extensions.dpToPx import com.habitrpg.common.habitica.extensions.isUsingNightModeResources import com.habitrpg.shared.habitica.models.tasks.TaskType -import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers -import io.reactivex.rxjava3.core.Observable import kotlinx.coroutines.MainScope +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.firstOrNull +import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch import java.lang.ref.WeakReference import java.util.Calendar import java.util.Date -import java.util.concurrent.TimeUnit import kotlin.math.abs +import kotlin.time.DurationUnit +import kotlin.time.toDuration class YesterdailyDialog private constructor( context: Context, @@ -220,77 +223,57 @@ class YesterdailyDialog private constructor( taskRepository: TaskRepository ) { if (userRepository != null && userId != null) { - Observable.just("") - .delay(500, TimeUnit.MILLISECONDS, AndroidSchedulers.mainThread()) - .filter { !userRepository.isClosed } - .flatMapMaybe { userRepository.getUserFlowable().firstElement() } - .filter { it.needsCron } - .flatMapMaybe { - val cal = Calendar.getInstance() - cal.add(Calendar.DATE, -1) - taskRepository.retrieveDailiesFromDate(cal.time).firstElement() + MainScope().launchCatching { + delay(500.toDuration(DurationUnit.MILLISECONDS)) + if (userRepository.isClosed) { + return@launchCatching } - .map { - it.tasks.values.filter { task -> - return@filter task.type == TaskType.DAILY && task.isDue == true && !task.completed && task.yesterDaily - } + val user = userRepository.getUser().firstOrNull() + if (user?.needsCron != true) { + return@launchCatching + } + val cal = Calendar.getInstance() + cal.add(Calendar.DATE, -1) + val tasks = taskRepository.retrieveDailiesFromDate(cal.time)?.tasks?.values?.filter { task -> + return@filter task.type == TaskType.DAILY && task.isDue == true && !task.completed && task.yesterDaily && !task.isGroupTask + } + if (displayedDialog?.get()?.isShowing == true) { + return@launchCatching } - .retry(1) - .throttleFirst(2, TimeUnit.SECONDS) - .filter { - if (displayedDialog?.get()?.isShowing == true) { - return@filter false - } - if (abs((lastCronRun?.time ?: 0) - Date().time) < 60 * 60 * 1000L) { - return@filter false - } - return@filter true + if (abs((lastCronRun?.time ?: 0) - Date().time) < 60 * 60 * 1000L) { + return@launchCatching } - .firstElement() - .map { - it.filter { !it.isGroupTask } - } - .zipWith( - taskRepository.getTasksFlowable(TaskType.DAILY, null, emptyArray()) - .firstElement() - .map { - val taskMap = mutableMapOf() - it.forEachIndexed { index, task -> taskMap[task.id ?: ""] = index } - taskMap - } - ) { yesterdayTasks, dailies -> - yesterdayTasks.sortedBy { dailies[it.id ?: ""] } - } - .subscribe( - { tasks -> - val additionalData = HashMap() - additionalData["task count"] = tasks.size - AmplitudeManager.sendEvent( - "show cron", - AmplitudeManager.EVENT_CATEGORY_BEHAVIOUR, - AmplitudeManager.EVENT_HITTYPE_EVENT, - additionalData - ) + val dailies = taskRepository.getTasks(TaskType.DAILY, null, emptyArray()).map { + val taskMap = mutableMapOf() + it.forEachIndexed { index, task -> taskMap[task.id ?: ""] = index } + taskMap + }.firstOrNull() + val sortedTasks = tasks?.sortedBy { dailies?.get(it.id ?: "") } - if (tasks.isNotEmpty()) { - displayedDialog = WeakReference( - showDialog( - activity, - userRepository, - taskRepository, - tasks - ) - ) - } else { - lastCronRun = Date() - MainScope().launch(ExceptionHandler.coroutine()) { - userRepository.runCron() - } - } - }, - ExceptionHandler.rx() + val additionalData = HashMap() + additionalData["task count"] = sortedTasks?.size ?: 0 + AmplitudeManager.sendEvent( + "show cron", + AmplitudeManager.EVENT_CATEGORY_BEHAVIOUR, + AmplitudeManager.EVENT_HITTYPE_EVENT, + additionalData ) + + if (sortedTasks?.isNotEmpty() == true) { + displayedDialog = WeakReference( + showDialog( + activity, + userRepository, + taskRepository, + sortedTasks + ) + ) + } else { + lastCronRun = Date() + userRepository.runCron() + } + } } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/widget/AvatarStatsWidgetProvider.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/widget/AvatarStatsWidgetProvider.kt index 74e5cf493..62f511e6f 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/widget/AvatarStatsWidgetProvider.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/widget/AvatarStatsWidgetProvider.kt @@ -11,7 +11,7 @@ import android.widget.RemoteViews import com.habitrpg.android.habitica.HabiticaBaseApplication import com.habitrpg.android.habitica.R import com.habitrpg.android.habitica.extensions.withImmutableFlag -import com.habitrpg.android.habitica.helpers.ExceptionHandler +import com.habitrpg.android.habitica.helpers.launchCatching import com.habitrpg.android.habitica.models.user.User import com.habitrpg.android.habitica.ui.activities.MainActivity import com.habitrpg.android.habitica.ui.views.HabiticaIconsHelper @@ -19,6 +19,9 @@ import com.habitrpg.common.habitica.extensions.dpToPx import com.habitrpg.common.habitica.helpers.HealthFormatter import com.habitrpg.common.habitica.helpers.NumberAbbreviator import com.habitrpg.common.habitica.views.AvatarView +import kotlinx.coroutines.MainScope +import kotlinx.coroutines.flow.firstOrNull +import kotlinx.coroutines.launch class AvatarStatsWidgetProvider : BaseWidgetProvider() { @@ -52,10 +55,12 @@ class AvatarStatsWidgetProvider : BaseWidgetProvider() { avatarView.layoutParams = layoutParams this.setUp() - userRepository.getUserFlowable().subscribe({ - user = it - updateData() - }, ExceptionHandler.rx()) + MainScope().launchCatching { + userRepository.getUser().collect { + user = it + updateData() + } + } } override fun onUpdate( @@ -79,10 +84,11 @@ class AvatarStatsWidgetProvider : BaseWidgetProvider() { this.context = context if (user == null) { - userRepository.getUserFlowable().firstElement().subscribe({ - user = it + MainScope().launch { + val user = userRepository.getUser().firstOrNull() ?: return@launch + this@AvatarStatsWidgetProvider.user = user updateData(appWidgetIds) - }, ExceptionHandler.rx()) + } } else { updateData() }