diff --git a/Habitica/src/androidTest/java/com/habitrpg/android/habitica/HabiticaTestCase.kt b/Habitica/src/androidTest/java/com/habitrpg/android/habitica/HabiticaTestCase.kt index 6d04fe669..5320b66d7 100644 --- a/Habitica/src/androidTest/java/com/habitrpg/android/habitica/HabiticaTestCase.kt +++ b/Habitica/src/androidTest/java/com/habitrpg/android/habitica/HabiticaTestCase.kt @@ -3,7 +3,6 @@ package com.habitrpg.android.habitica import android.content.SharedPreferences import androidx.lifecycle.MutableLiveData import com.habitrpg.android.habitica.api.GSonFactoryCreator -import com.habitrpg.common.habitica.api.HostConfig import com.habitrpg.android.habitica.api.MaintenanceApiService import com.habitrpg.android.habitica.data.ApiClient import com.habitrpg.android.habitica.data.ContentRepository @@ -14,9 +13,9 @@ import com.habitrpg.android.habitica.data.TaskRepository import com.habitrpg.android.habitica.data.TutorialRepository import com.habitrpg.android.habitica.data.UserRepository 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.NotificationsManager -import com.habitrpg.android.habitica.helpers.RxErrorHandler import com.habitrpg.android.habitica.helpers.SoundManager import com.habitrpg.android.habitica.interactors.FeedPetUseCase import com.habitrpg.android.habitica.interactors.HatchPetUseCase @@ -29,22 +28,23 @@ import com.habitrpg.android.habitica.models.inventory.QuestContent import com.habitrpg.android.habitica.models.user.User import com.habitrpg.android.habitica.proxy.AnalyticsManager import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel +import com.habitrpg.common.habitica.api.HostConfig import com.kaspersky.kaspresso.testcases.api.testcase.TestCase import io.mockk.clearAllMocks import io.mockk.every import io.mockk.mockk import io.mockk.mockkObject import io.mockk.slot -import io.reactivex.rxjava3.core.BackpressureStrategy import io.reactivex.rxjava3.core.Flowable -import io.reactivex.rxjava3.subjects.PublishSubject +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.flowOf +import org.junit.Before import java.io.InputStreamReader import java.lang.reflect.Type import kotlin.reflect.KCallable import kotlin.reflect.KMutableProperty1 import kotlin.reflect.full.starProjectedType import kotlin.reflect.jvm.javaField -import org.junit.Before open class HabiticaTestCase : TestCase() { val gson = GSonFactoryCreator.createGson() @@ -68,8 +68,7 @@ open class HabiticaTestCase : TestCase() { val hatchPetUseCase: HatchPetUseCase = mockk(relaxed = true) val feedPetUseCase: FeedPetUseCase = mockk(relaxed = true) - val userSubject = PublishSubject.create() - val userEvents: Flowable = userSubject.toFlowable(BackpressureStrategy.DROP) + val userState = MutableStateFlow(null) var user = User() lateinit var content: ContentResult @@ -85,27 +84,27 @@ open class HabiticaTestCase : TestCase() { user = loadJsonFile("user", User::class.java) user.stats?.lvl = 20 user.stats?.points = 30 - every { userRepository.getUser() } returns userEvents + every { userRepository.getUser() } returns userState every { userViewModel.user } returns MutableLiveData(user) - mockkObject(RxErrorHandler) - every { RxErrorHandler.reportError(capture(errorSlot)) } answers { + mockkObject(ExceptionHandler) + every { ExceptionHandler.reportError(capture(errorSlot)) } answers { throw errorSlot.captured } every { socialRepository.getUnmanagedCopy(capture(unmanagedSlot)) } answers { unmanagedSlot.captured } content = loadJsonFile("content", ContentResult::class.java) - every { inventoryRepository.getPets() } returns Flowable.just(content.pets) - every { inventoryRepository.getMounts() } returns Flowable.just(content.mounts) + every { inventoryRepository.getPets() } returns flowOf(content.pets) + every { inventoryRepository.getMounts() } returns flowOf(content.mounts) every { inventoryRepository.getItemsFlowable(Food::class.java) } returns Flowable.just(content.food) every { inventoryRepository.getItemsFlowable(Egg::class.java) } returns Flowable.just(content.eggs) every { inventoryRepository.getItemsFlowable(HatchingPotion::class.java) } returns Flowable.just(content.hatchingPotions) every { inventoryRepository.getItemsFlowable(QuestContent::class.java) } returns Flowable.just(content.quests) - every { inventoryRepository.getItemsFlowable(Food::class.java, any()) } returns Flowable.just(content.food) - every { inventoryRepository.getItemsFlowable(Egg::class.java, any()) } answers { - Flowable.just(content.eggs) + every { inventoryRepository.getItems(Food::class.java, any()) } returns flowOf(content.food) + every { inventoryRepository.getItems(Egg::class.java, any()) } answers { + flowOf(content.eggs) } - every { inventoryRepository.getItemsFlowable(HatchingPotion::class.java, any()) } returns Flowable.just(content.hatchingPotions) - every { inventoryRepository.getItemsFlowable(QuestContent::class.java, any()) } returns Flowable.just(content.quests) + every { inventoryRepository.getItems(HatchingPotion::class.java, any()) } returns flowOf(content.hatchingPotions) + every { inventoryRepository.getItems(QuestContent::class.java, any()) } returns flowOf(content.quests) } internal fun loadJsonFile(s: String, type: Type): T { diff --git a/Habitica/src/androidTest/java/com/habitrpg/android/habitica/ui/fragments/StatsFragmentTest.kt b/Habitica/src/androidTest/java/com/habitrpg/android/habitica/ui/fragments/StatsFragmentTest.kt index 2ee2ba7cc..a69fb6889 100644 --- a/Habitica/src/androidTest/java/com/habitrpg/android/habitica/ui/fragments/StatsFragmentTest.kt +++ b/Habitica/src/androidTest/java/com/habitrpg/android/habitica/ui/fragments/StatsFragmentTest.kt @@ -63,7 +63,7 @@ class StatsFragmentTest : FragmentTestCase - // Compact if the file is over 100MB in size and less than 50% 'used' val oneHundredMB = 50 * 1024 * 1024 (totalBytes > oneHundredMB) && (usedBytes / totalBytes) < 0.5 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 b94f5b2a5..59160edf4 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 @@ -26,13 +26,13 @@ import com.habitrpg.android.habitica.models.tasks.TaskList 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.models.HabitResponse import com.habitrpg.common.habitica.models.PurchaseValidationRequest import com.habitrpg.common.habitica.models.PurchaseValidationResult import com.habitrpg.common.habitica.models.auth.UserAuth import com.habitrpg.common.habitica.models.auth.UserAuthResponse import com.habitrpg.common.habitica.models.auth.UserAuthSocial import com.habitrpg.shared.habitica.models.responses.FeedResponse -import com.habitrpg.common.habitica.models.HabitResponse import com.habitrpg.shared.habitica.models.responses.Status import com.habitrpg.shared.habitica.models.responses.TaskDirectionData import com.habitrpg.shared.habitica.models.responses.VerifyUsernameResponse @@ -49,27 +49,27 @@ import retrofit2.http.Query @JvmSuppressWildcards interface ApiService { - @get:GET("status") - val status: Flowable> + @GET("status") + suspend fun getStatus(): HabitResponse /* user API */ - @get:GET("user/") - val user: Flowable> + @GET("user/") + suspend fun getUser(): HabitResponse @GET("inbox/messages") - fun getInboxMessages(@Query("conversation") uuid: String, @Query("page") page: Int): Flowable>> + suspend fun getInboxMessages(@Query("conversation") uuid: String, @Query("page") page: Int): HabitResponse> @GET("inbox/conversations") fun getInboxConversations(): Flowable>> - @get:GET("tasks/user") - val tasks: Flowable> + @GET("tasks/user") + suspend fun getTasks(): HabitResponse - @get:GET("world-state") - val worldState: Flowable> + @GET("world-state") + suspend fun worldState(): HabitResponse @GET("content") - fun getContent(@Query("language") language: String?): Flowable> + suspend fun getContent(@Query("language") language: String?): HabitResponse @PUT("user/") fun updateUser(@Body updateDictionary: Map): Flowable> @@ -177,10 +177,10 @@ interface ApiService { fun loginApple(@Body auth: Map): Flowable> @POST("user/sleep") - fun sleep(): Flowable> + suspend fun sleep(): HabitResponse @POST("user/revive") - fun revive(): Flowable> + suspend fun revive(): HabitResponse @POST("user/class/cast/{skill}") fun useSkill( @@ -193,13 +193,13 @@ interface ApiService { fun useSkill(@Path("skill") skillName: String, @Query("targetType") targetType: String): Flowable> @POST("user/change-class") - fun changeClass(): Flowable> + suspend fun changeClass(): HabitResponse @POST("user/change-class") - fun changeClass(@Query("class") className: String): Flowable> + suspend fun changeClass(@Query("class") className: String): HabitResponse @POST("user/disable-classes") - fun disableClasses(): Flowable> + suspend fun disableClasses(): HabitResponse @POST("user/mark-pms-read") fun markPrivateMessagesRead(): Flowable @@ -210,25 +210,25 @@ interface ApiService { fun listGroups(@Query("type") type: String): Flowable>> @GET("groups/{gid}") - fun getGroup(@Path("gid") groupId: String): Flowable> + suspend fun getGroup(@Path("gid") groupId: String): HabitResponse @POST("groups") - fun createGroup(@Body item: Group): Flowable> + suspend fun createGroup(@Body item: Group): HabitResponse @PUT("groups/{id}") - fun updateGroup(@Path("id") id: String, @Body item: Group): Flowable> + suspend fun updateGroup(@Path("id") id: String, @Body item: Group): HabitResponse @POST("groups/{groupID}/removeMember/{userID}") - fun removeMemberFromGroup(@Path("groupID") groupID: String, @Path("userID") userID: String): Flowable> + suspend fun removeMemberFromGroup(@Path("groupID") groupID: String, @Path("userID") userID: String): HabitResponse @GET("groups/{gid}/chat") - fun listGroupChat(@Path("gid") groupId: String): Flowable>> + suspend fun listGroupChat(@Path("gid") groupId: String): HabitResponse> @POST("groups/{gid}/join") - fun joinGroup(@Path("gid") groupId: String): Flowable> + suspend fun joinGroup(@Path("gid") groupId: String): HabitResponse @POST("groups/{gid}/leave") - fun leaveGroup(@Path("gid") groupId: String, @Query("keepChallenges") keepChallenges: String): Flowable> + 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> @@ -240,17 +240,17 @@ interface ApiService { fun deleteInboxMessage(@Path("messageId") messageId: String): Flowable> @GET("groups/{gid}/members") - fun getGroupMembers( + suspend fun getGroupMembers( @Path("gid") groupId: String, @Query("includeAllPublicFields") includeAllPublicFields: Boolean? - ): Flowable>> + ): HabitResponse> @GET("groups/{gid}/members") - fun getGroupMembers( + suspend fun getGroupMembers( @Path("gid") groupId: String, @Query("includeAllPublicFields") includeAllPublicFields: Boolean?, @Query("lastId") lastId: String - ): Flowable>> + ): HabitResponse> // Like returns the full chat list @POST("groups/{gid}/chat/{mid}/like") @@ -300,7 +300,7 @@ interface ApiService { fun validateSubscription(@Body request: PurchaseValidationRequest): Flowable> @GET("/iap/android/subscribe/cancel") - fun cancelSubscription(): Flowable> + suspend fun cancelSubscription(): HabitResponse @POST("/iap/android/norenew-subscribe") fun validateNoRenewSubscription(@Body request: PurchaseValidationRequest): Flowable> @@ -310,16 +310,16 @@ interface ApiService { // Members URL @GET("members/{mid}") - fun getMember(@Path("mid") memberId: String): Flowable> + suspend fun getMember(@Path("mid") memberId: String): HabitResponse @GET("members/username/{username}") - fun getMemberWithUsername(@Path("username") username: String): Flowable> + suspend fun getMemberWithUsername(@Path("username") username: String): HabitResponse @GET("members/{mid}/achievements") fun getMemberAchievements(@Path("mid") memberId: String, @Query("lang") language: String?): Flowable>> @POST("members/send-private-message") - fun postPrivateMessage(@Body messageDetails: Map): Flowable> + suspend fun postPrivateMessage(@Body messageDetails: Map): HabitResponse @GET("members/find/{username}") fun findUsernames( @@ -440,7 +440,7 @@ interface ApiService { fun blockMember(@Path("userID") userID: String): Flowable>> @POST("user/reroll") - fun reroll(): Flowable> + suspend fun reroll(): HabitResponse // Team Plans @@ -448,5 +448,5 @@ interface ApiService { fun getTeamPlans(): Flowable>> @GET("tasks/group/{groupID}") - fun getTeamPlanTasks(@Path("groupID") groupId: String): Flowable> + suspend fun getTeamPlanTasks(@Path("groupID") groupId: String): HabitResponse } 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 e972141e9..a4184fc9e 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,12 +27,12 @@ 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 import com.habitrpg.shared.habitica.models.responses.ErrorResponse import com.habitrpg.shared.habitica.models.responses.FeedResponse -import com.habitrpg.common.habitica.models.HabitResponse import com.habitrpg.shared.habitica.models.responses.Status import com.habitrpg.shared.habitica.models.responses.TaskDirectionData import com.habitrpg.shared.habitica.models.responses.VerifyUsernameResponse @@ -44,23 +44,19 @@ interface ApiClient { val hostConfig: HostConfig - val status: Flowable - - val content: Flowable + suspend fun getStatus(): Status? /* user API */ - val user: Flowable - - val tasks: Flowable + suspend fun getTasks(): TaskList? /* challenges api */ fun getUserChallenges(page: Int, memberOnly: Boolean): Flowable> - val worldState: Flowable + suspend fun getWorldState(): WorldState? fun setLanguageCode(languageCode: String) - fun getContent(language: String): Flowable + suspend fun getContent(language: String? = null): ContentResult? fun updateUser(updateDictionary: Map): Flowable @@ -83,7 +79,7 @@ interface ApiClient { fun purchaseSpecialSpell(key: String): Flowable fun validateSubscription(request: PurchaseValidationRequest): Flowable fun validateNoRenewSubscription(request: PurchaseValidationRequest): Flowable - fun cancelSubscription(): Flowable + suspend fun cancelSubscription(): Void? fun sellItem(itemType: String, itemKey: String): Flowable @@ -127,19 +123,16 @@ interface ApiClient { fun loginApple(authToken: String): Flowable - fun sleep(): Flowable - - fun revive(): Flowable + suspend fun sleep(): Boolean? + suspend fun revive(): User? fun useSkill(skillName: String, targetType: String, targetId: String): Flowable fun useSkill(skillName: String, targetType: String): Flowable - fun changeClass(): Flowable + suspend fun changeClass(className: String?): User? - fun changeClass(className: String): Flowable - - fun disableClasses(): Flowable + suspend fun disableClasses(): User? fun markPrivateMessagesRead(): Flowable @@ -147,26 +140,26 @@ interface ApiClient { fun listGroups(type: String): Flowable> - fun getGroup(groupId: String): Flowable + suspend fun getGroup(groupId: String): Group? - fun createGroup(group: Group): Flowable - fun updateGroup(id: String, item: Group): Flowable - fun removeMemberFromGroup(groupID: String, userID: String): Flowable + suspend fun createGroup(group: Group): Group? + suspend fun updateGroup(id: String, item: Group): Group? + suspend fun removeMemberFromGroup(groupID: String, userID: String): Void? - fun listGroupChat(groupId: String): Flowable> + suspend fun listGroupChat(groupId: String): List? - fun joinGroup(groupId: String): Flowable + suspend fun joinGroup(groupId: String): Group? - fun leaveGroup(groupId: String, keepChallenges: String): Flowable + suspend fun leaveGroup(groupId: String, keepChallenges: String): Void? fun postGroupChat(groupId: String, message: Map): Flowable fun deleteMessage(groupId: String, messageId: String): Flowable fun deleteInboxMessage(id: String): Flowable - fun getGroupMembers(groupId: String, includeAllPublicFields: Boolean?): Flowable> + suspend fun getGroupMembers(groupId: String, includeAllPublicFields: Boolean?): List? - fun getGroupMembers(groupId: String, includeAllPublicFields: Boolean?, lastId: String): Flowable> + suspend fun getGroupMembers(groupId: String, includeAllPublicFields: Boolean?, lastId: String): List? // Like returns the full chat list fun likeMessage(groupId: String, mid: String): Flowable @@ -199,12 +192,12 @@ interface ApiClient { fun changeCustomDayStart(updateObject: Map): Flowable // Members URL - fun getMember(memberId: String): Flowable - fun getMemberWithUsername(username: String): Flowable + suspend fun getMember(memberId: String): Member? + suspend fun getMemberWithUsername(username: String): Member? fun getMemberAchievements(memberId: String): Flowable> - fun postPrivateMessage(messageDetails: Map): Flowable + suspend fun postPrivateMessage(messageDetails: Map): PostChatMessageResult? fun retrieveShopIventory(identifier: String): Flowable @@ -243,8 +236,8 @@ interface ApiClient { fun hasAuthenticationKeys(): Boolean - fun retrieveUser(withTasks: Boolean): Flowable - fun retrieveInboxMessages(uuid: String, page: Int): Flowable> + suspend fun retrieveUser(withTasks: Boolean = false): User? + suspend fun retrieveInboxMessages(uuid: String, page: Int): List? fun retrieveInboxConversations(): Flowable> fun configureApiCallObserver(): FlowableTransformer, T> @@ -253,7 +246,7 @@ interface ApiClient { fun runCron(): Flowable - fun reroll(): Flowable + suspend fun reroll(): User? fun resetAccount(): Flowable fun deleteAccount(password: String): Flowable @@ -282,5 +275,5 @@ interface ApiClient { fun unlinkAllTasks(challengeID: String?, keepOption: String): Flowable fun blockMember(userID: String): Flowable> fun getTeamPlans(): Flowable> - fun getTeamPlanTasks(teamID: String): Flowable + suspend fun getTeamPlanTasks(teamID: String): TaskList? } 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 ff0f53e2d..1a71ef514 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 @@ -4,9 +4,9 @@ import com.habitrpg.android.habitica.models.ContentResult import com.habitrpg.android.habitica.models.WorldState import io.reactivex.rxjava3.core.Flowable -interface ContentRepository : BaseRepository { - fun retrieveContent(forced: Boolean = false): Flowable +interface ContentRepository: BaseRepository { + suspend fun retrieveContent(forced: Boolean = false): ContentResult? - fun retrieveWorldState(): Flowable + suspend fun retrieveWorldState(): WorldState? fun getWorldState(): Flowable } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/InventoryRepository.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/InventoryRepository.kt index da3677bcd..8fa5eea2c 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 @@ -35,7 +35,7 @@ interface InventoryRepository : BaseRepository { fun getPets(): Flow> fun getOwnedPets(): Flow> - fun getQuestContent(key: String): Flowable + fun getQuestContent(key: String): Flow fun getQuestContent(keys: List): Flow> fun getEquipment(searchedKeys: List): Flowable> @@ -86,7 +86,7 @@ interface InventoryRepository : BaseRepository { fun purchaseItem(purchaseType: String, key: String, purchaseQuantity: Int): Flowable fun togglePinnedItem(item: ShopItem): Flowable> - fun getItemsFlowable(itemClass: Class, keys: Array): Flow> + fun getItems(itemClass: Class, keys: Array): Flow> fun getItemsFlowable(itemClass: Class): Flowable> fun getItems(itemClass: Class): Flow> fun getLatestMysteryItem(): Flowable diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/SocialRepository.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/SocialRepository.kt index 4d0732594..dfd31c660 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 @@ -11,14 +11,14 @@ 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.reactivex.rxjava3.core.Single +import io.realm.RealmResults import kotlinx.coroutines.flow.Flow interface SocialRepository : BaseRepository { fun getPublicGuilds(): Flowable> - fun getUserGroups(type: String?): Flowable> - fun retrieveGroupChat(groupId: String): Single> + fun getUserGroups(type: String?): Flow> + suspend fun retrieveGroupChat(groupId: String): List? fun getGroupChat(groupId: String): Flowable> fun markMessagesSeen(seenGroupId: String) @@ -40,52 +40,51 @@ interface SocialRepository : BaseRepository { fun postGroupChat(groupId: String, message: String): Flowable - fun retrieveGroup(id: String): Flowable + suspend fun retrieveGroup(id: String): Group? fun getGroup(id: String?): Flow - fun getGroupFlowable(id: String?): Flowable - fun leaveGroup(id: String?, keepChallenges: Boolean): Flowable + suspend fun leaveGroup(id: String?, keepChallenges: Boolean): Group? - fun joinGroup(id: String?): Flowable + suspend fun joinGroup(id: String?): Group? - fun createGroup( + suspend fun createGroup( name: String?, description: String?, leader: String?, type: String?, privacy: String?, leaderCreateChallenge: Boolean? - ): Flowable + ): Group? - fun updateGroup( + suspend fun updateGroup( group: Group?, name: String?, description: String?, leader: String?, leaderCreateChallenge: Boolean? - ): Flowable + ): Group? fun retrieveGroups(type: String): Flowable> fun getGroups(type: String): Flowable> - fun getInboxMessages(replyToUserID: String?): Flowable> - fun retrieveInboxMessages(uuid: String, page: Int): Flowable> + fun getInboxMessages(replyToUserID: String?): Flow> + suspend fun retrieveInboxMessages(uuid: String, page: Int): List? fun retrieveInboxConversations(): Flowable> - fun getInboxConversations(): Flowable> - fun postPrivateMessage( + fun getInboxConversations(): Flow> + suspend fun postPrivateMessage( recipientId: String, messageObject: HashMap - ): Flowable> + ): List? - fun postPrivateMessage(recipientId: String, message: String): Flowable> + suspend fun postPrivateMessage(recipientId: String, message: String): List? - fun getGroupMembers(id: String): Flow> - fun retrieveGroupMembers(id: String, includeAllPublicFields: Boolean): Flowable> + suspend fun getGroupMembers(id: String): Flow> + suspend fun retrieveGroupMembers(id: String, includeAllPublicFields: Boolean): List? fun inviteToGroup(id: String, inviteData: Map): Flowable> - fun getMember(userId: String?): Flowable - fun getMemberWithUsername(username: String?): Flowable + suspend fun retrieveMember(userId: String?): Member? + suspend fun retrieveMemberWithUsername(username: String?): Member? fun findUsernames( username: String, @@ -97,8 +96,8 @@ interface SocialRepository : BaseRepository { fun markSomePrivateMessagesAsRead(user: User?, messages: List) - fun transferGroupOwnership(groupID: String, userID: String): Flowable - fun removeMemberFromGroup(groupID: String, userID: String): Flowable> + 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 @@ -117,7 +116,7 @@ interface SocialRepository : BaseRepository { fun transferGems(giftedID: String, amount: Int): Flowable - fun getGroupMembership(id: String): Flowable + fun getGroupMembership(id: String): Flow fun getGroupMemberships(): Flowable> fun blockMember(userID: String): Flowable> } 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 a6ba14b09..a8e011813 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 @@ -19,7 +19,7 @@ interface TaskRepository : BaseRepository { fun getTasksFlowable(taskType: TaskType, userID: String? = null, includedGroupIDs: Array): Flowable> fun saveTasks(userId: String, order: TasksOrder, tasks: TaskList) - fun retrieveTasks(userId: String, tasksOrder: TasksOrder): Flowable + suspend fun retrieveTasks(userId: String, tasksOrder: TasksOrder): TaskList? fun retrieveTasks(userId: String, tasksOrder: TasksOrder, dueDate: Date): Flowable fun taskChecked( 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 458f21fb0..2c957f717 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 @@ -26,18 +26,13 @@ interface UserRepository : BaseRepository { fun updateUser(updateData: Map): Flowable fun updateUser(key: String, value: Any): Flowable - fun retrieveUser(withTasks: Boolean): Flowable - fun retrieveUser( - withTasks: Boolean = false, - forced: Boolean = false, - overrideExisting: Boolean = false - ): Flowable + suspend fun retrieveUser(withTasks: Boolean = false, forced: Boolean = false, overrideExisting: Boolean = false): User? - fun revive(): Flowable + suspend fun revive(): User? fun resetTutorial(): Maybe - fun sleep(user: User): Flowable + suspend fun sleep(user: User): User? fun getSkills(user: User): Flowable> @@ -46,17 +41,14 @@ interface UserRepository : BaseRepository { fun useSkill(key: String, target: String?, taskId: String): Flowable fun useSkill(key: String, target: String?): Flowable - fun changeClass(): Flowable - - fun disableClasses(): Flowable - - fun changeClass(selectedClass: String): Flowable + suspend fun disableClasses(): User? + suspend fun changeClass(selectedClass: String? = null): User? fun unlockPath(path: String, price: Int): Flowable fun unlockPath(customization: Customization): Flowable - fun runCron(tasks: MutableList) - fun runCron() + suspend fun runCron(tasks: MutableList) + suspend fun runCron() fun readNotification(id: String): Flowable> fun readNotifications(notificationIds: Map>): Flowable> @@ -66,7 +58,7 @@ interface UserRepository : BaseRepository { fun updateLanguage(languageCode: String): Flowable - fun resetAccount(): Flowable + suspend fun resetAccount(): User? fun deleteAccount(password: String): Flowable fun sendPasswordResetEmail(email: String): Flowable @@ -86,9 +78,9 @@ interface UserRepository : BaseRepository { fun getUserQuestStatus(): Flowable - fun reroll(): Flowable + suspend fun reroll(): User? fun retrieveTeamPlans(): Flowable> fun getTeamPlans(): Flow> - fun retrieveTeamPlan(teamID: String): Flowable + suspend fun retrieveTeamPlan(teamID: String): Group? fun getTeamPlan(teamID: String): Flowable } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/ApiClientImpl.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/ApiClientImpl.kt index 32d792593..fb8226076 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 @@ -9,7 +9,6 @@ import com.habitrpg.android.habitica.R import com.habitrpg.android.habitica.api.ApiService import com.habitrpg.android.habitica.api.GSonFactoryCreator import com.habitrpg.android.habitica.data.ApiClient -import com.habitrpg.android.habitica.extensions.filterMap import com.habitrpg.android.habitica.helpers.NotificationsManager import com.habitrpg.android.habitica.models.Achievement import com.habitrpg.android.habitica.models.ContentResult @@ -40,6 +39,7 @@ import com.habitrpg.android.habitica.models.user.User import com.habitrpg.android.habitica.proxy.AnalyticsManager import com.habitrpg.common.habitica.api.HostConfig import com.habitrpg.common.habitica.api.Server +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.UserAuth @@ -48,7 +48,6 @@ import com.habitrpg.common.habitica.models.auth.UserAuthSocial import com.habitrpg.common.habitica.models.auth.UserAuthSocialTokens import com.habitrpg.shared.habitica.models.responses.ErrorResponse import com.habitrpg.shared.habitica.models.responses.FeedResponse -import com.habitrpg.common.habitica.models.HabitResponse import com.habitrpg.shared.habitica.models.responses.Status import com.habitrpg.shared.habitica.models.responses.TaskDirectionData import com.habitrpg.shared.habitica.models.responses.VerifyUsernameResponse @@ -90,19 +89,31 @@ class ApiClientImpl( private val apiCallTransformer = FlowableTransformer, Any> { observable -> observable - .filterMap { habitResponse -> - habitResponse.notifications?.let { - notificationsManager.setNotifications(it) - } - if (hadError) { - hideConnectionProblemDialog() - } - habitResponse.data + .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) + } + return habitResponse.data + } + + suspend fun handleSuspendCall(apiCall: suspend () -> HabitResponse): T? { + try { + return processResponse(apiCall()) + } catch (throwable: Throwable) { + accept(throwable) + } + return null + } + private var languageCode: String? = null private var lastAPICallURL: String? = null private var hadError = false @@ -268,25 +279,15 @@ class ApiClientImpl( } } - override fun retrieveUser(withTasks: Boolean): Flowable { - - var userObservable = this.user - - if (withTasks) { - val tasksObservable = this.tasks - - userObservable = Flowable.zip( - userObservable, tasksObservable - ) { habitRPGUser, tasks -> - habitRPGUser.tasks = tasks - habitRPGUser - } - } - return userObservable + override suspend fun retrieveUser(withTasks: Boolean): User? { + val user = handleSuspendCall { apiService.getUser() } + val tasks = getTasks() + user?.tasks = tasks + return user } - override fun retrieveInboxMessages(uuid: String, page: Int): Flowable> { - return apiService.getInboxMessages(uuid, page).compose(configureApiCallObserver()) + override suspend fun retrieveInboxMessages(uuid: String, page: Int): List? { + return handleSuspendCall { apiService.getInboxMessages(uuid, page) } } override fun retrieveInboxConversations(): Flowable> { @@ -345,16 +346,12 @@ class ApiClientImpl( this.languageCode = languageCode } - override val status: Flowable - get() = apiService.status.compose(configureApiCallObserver()) + override suspend fun getStatus(): Status? = handleSuspendCall { apiService.getStatus() } - override fun getContent(language: String): Flowable { - return apiService.getContent(language).compose(configureApiCallObserver()) + override suspend fun getContent(language: String?): ContentResult? { + return handleSuspendCall { apiService.getContent(language) } } - override val user: Flowable - get() = apiService.user.compose(configureApiCallObserver()) - override fun updateUser(updateDictionary: Map): Flowable { return apiService.updateUser(updateDictionary).compose(configureApiCallObserver()) } @@ -399,8 +396,8 @@ class ApiClientImpl( return apiService.validateNoRenewSubscription(request).compose(configureApiCallObserver()) } - override fun cancelSubscription(): Flowable { - return apiService.cancelSubscription().compose(configureApiCallObserver()) + override suspend fun cancelSubscription(): Void? { + return processResponse(apiService.cancelSubscription()) } override fun purchaseHourglassItem(type: String, itemKey: String): Flowable { @@ -436,8 +433,7 @@ class ApiClientImpl( return apiService.hatchPet(eggKey, hatchingPotionKey).compose(configureApiCallObserver()) } - override val tasks: Flowable - get() = apiService.tasks.compose(configureApiCallObserver()) + override suspend fun getTasks(): TaskList? = handleSuspendCall { apiService.getTasks() } override fun getTasks(type: String): Flowable { return apiService.getTasks(type).compose(configureApiCallObserver()) @@ -499,13 +495,9 @@ class ApiClientImpl( return apiService.deleteTag(id).compose(configureApiCallObserver()) } - override fun sleep(): Flowable { - return apiService.sleep().compose(configureApiCallObserver()) - } + override suspend fun sleep(): Boolean? = handleSuspendCall { apiService.sleep() } - override fun revive(): Flowable { - return apiService.revive().compose(configureApiCallObserver()) - } + override suspend fun revive(): User? = handleSuspendCall { apiService.revive() } override fun useSkill(skillName: String, targetType: String, targetId: String): Flowable { return apiService.useSkill(skillName, targetType, targetId).compose(configureApiCallObserver()) @@ -515,17 +507,17 @@ class ApiClientImpl( return apiService.useSkill(skillName, targetType).compose(configureApiCallObserver()) } - override fun changeClass(): Flowable { - return apiService.changeClass().compose(configureApiCallObserver()) + override suspend fun changeClass(className: String?): User? { + return handleSuspendCall { + if (className != null) { + apiService.changeClass(className) + } else { + apiService.changeClass() + } + } } - override fun changeClass(className: String): Flowable { - return apiService.changeClass(className).compose(configureApiCallObserver()) - } - - override fun disableClasses(): Flowable { - return apiService.disableClasses().compose(configureApiCallObserver()) - } + override suspend fun disableClasses(): User? = handleSuspendCall { apiService.disableClasses() } override fun markPrivateMessagesRead(): Flowable { // This is necessary, because the API call returns weird data. @@ -539,32 +531,32 @@ class ApiClientImpl( return apiService.listGroups(type).compose(configureApiCallObserver()) } - override fun getGroup(groupId: String): Flowable { - return apiService.getGroup(groupId).compose(configureApiCallObserver()) + override suspend fun getGroup(groupId: String): Group? { + return processResponse(apiService.getGroup(groupId)) } - override fun createGroup(group: Group): Flowable { - return apiService.createGroup(group).compose(configureApiCallObserver()) + override suspend fun createGroup(group: Group): Group? { + return processResponse(apiService.createGroup(group)) } - override fun updateGroup(id: String, item: Group): Flowable { - return apiService.updateGroup(id, item).compose(configureApiCallObserver()) + override suspend fun updateGroup(id: String, item: Group): Group? { + return processResponse(apiService.updateGroup(id, item)) } - override fun removeMemberFromGroup(groupID: String, userID: String): Flowable { - return apiService.removeMemberFromGroup(groupID, userID).compose(configureApiCallObserver()) + override suspend fun removeMemberFromGroup(groupID: String, userID: String): Void? { + return processResponse(apiService.removeMemberFromGroup(groupID, userID)) } - override fun listGroupChat(groupId: String): Flowable> { - return apiService.listGroupChat(groupId).compose(configureApiCallObserver()) + override suspend fun listGroupChat(groupId: String): List? { + return processResponse(apiService.listGroupChat(groupId)) } - override fun joinGroup(groupId: String): Flowable { - return apiService.joinGroup(groupId).compose(configureApiCallObserver()) + override suspend fun joinGroup(groupId: String): Group? { + return processResponse(apiService.joinGroup(groupId)) } - override fun leaveGroup(groupId: String, keepChallenges: String): Flowable { - return apiService.leaveGroup(groupId, keepChallenges).compose(configureApiCallObserver()) + override suspend fun leaveGroup(groupId: String, keepChallenges: String): Void? { + return processResponse(apiService.leaveGroup(groupId, keepChallenges)) } override fun postGroupChat(groupId: String, message: Map): Flowable { @@ -578,12 +570,12 @@ class ApiClientImpl( return apiService.deleteInboxMessage(id).compose(configureApiCallObserver()) } - override fun getGroupMembers(groupId: String, includeAllPublicFields: Boolean?): Flowable> { - return apiService.getGroupMembers(groupId, includeAllPublicFields).compose(configureApiCallObserver()) + override suspend fun getGroupMembers(groupId: String, includeAllPublicFields: Boolean?): List? { + return processResponse(apiService.getGroupMembers(groupId, includeAllPublicFields)) } - override fun getGroupMembers(groupId: String, includeAllPublicFields: Boolean?, lastId: String): Flowable> { - return apiService.getGroupMembers(groupId, includeAllPublicFields, lastId).compose(configureApiCallObserver()) + override suspend fun getGroupMembers(groupId: String, includeAllPublicFields: Boolean?, lastId: String): List? { + return processResponse(apiService.getGroupMembers(groupId, includeAllPublicFields, lastId)) } override fun likeMessage(groupId: String, mid: String): Flowable { @@ -646,13 +638,8 @@ class ApiClientImpl( return apiService.changeCustomDayStart(updateObject).compose(configureApiCallObserver()) } - override fun getMember(memberId: String): Flowable { - return apiService.getMember(memberId).compose(configureApiCallObserver()) - } - - override fun getMemberWithUsername(username: String): Flowable { - return apiService.getMemberWithUsername(username).compose(configureApiCallObserver()) - } + 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()) @@ -662,8 +649,8 @@ class ApiClientImpl( return apiService.findUsernames(username, context, id).compose(configureApiCallObserver()) } - override fun postPrivateMessage(messageDetails: Map): Flowable { - return apiService.postPrivateMessage(messageDetails).compose(configureApiCallObserver()) + override suspend fun postPrivateMessage(messageDetails: Map): PostChatMessageResult? { + return handleSuspendCall { apiService.postPrivateMessage(messageDetails) } } override fun retrieveShopIventory(identifier: String): Flowable { @@ -738,9 +725,6 @@ class ApiClientImpl( return apiService.seeNotifications(notificationIds).compose(configureApiCallObserver()) } - override val content: Flowable - get() = apiService.getContent(languageCode).compose(configureApiCallObserver()) - override fun openMysteryItem(): Flowable { return apiService.openMysteryItem().compose(configureApiCallObserver()) } @@ -749,9 +733,7 @@ class ApiClientImpl( return apiService.runCron().compose(configureApiCallObserver()) } - override fun reroll(): Flowable { - return apiService.reroll().compose(configureApiCallObserver()) - } + override suspend fun reroll(): User? = handleSuspendCall { apiService.reroll() } override fun resetAccount(): Flowable { return apiService.resetAccount().compose(configureApiCallObserver()) @@ -825,8 +807,8 @@ class ApiClientImpl( return apiService.getTeamPlans().compose(configureApiCallObserver()) } - override fun getTeamPlanTasks(teamID: String): Flowable { - return apiService.getTeamPlanTasks(teamID).compose(configureApiCallObserver()) + override suspend fun getTeamPlanTasks(teamID: String): TaskList? { + return processResponse(apiService.getTeamPlanTasks(teamID)) } override fun bulkAllocatePoints( @@ -849,8 +831,7 @@ class ApiClientImpl( return apiService.retrieveMarketGear(languageCode).compose(configureApiCallObserver()) } - override val worldState: Flowable - get() = apiService.worldState.compose(configureApiCallObserver()) + override suspend fun getWorldState(): WorldState? = handleSuspendCall { apiService.worldState() } companion object { fun createGsonFactory(): GsonConverterFactory { 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 0989bc814..5028049df 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 @@ -9,6 +9,7 @@ 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 java.util.Date class ContentRepositoryImpl( @@ -22,34 +23,33 @@ class ContentRepositoryImpl( private var lastContentSync = 0L private var lastWorldStateSync = 0L - override fun retrieveContent(forced: Boolean): Flowable { + override suspend fun retrieveContent(forced: Boolean): ContentResult? { val now = Date().time - return if (forced || now - this.lastContentSync > 300000) { + if (forced || now - this.lastContentSync > 300000) { + val content = apiClient.getContent() ?: return null lastContentSync = now - apiClient.content.doOnNext { - it.special.add(mysteryItem) - localRepository.saveContent(it) - } - } else { - Flowable.just(ContentResult()) + content.special = RealmList() + content.special.add(mysteryItem) + localRepository.saveContent(content) + return content } + return null } - override fun retrieveWorldState(): Flowable { + override suspend fun retrieveWorldState(): WorldState? { val now = Date().time - return if (now - this.lastWorldStateSync > 3600000) { + if (now - this.lastWorldStateSync > 3600000) { + val state = apiClient.getWorldState() ?: return null lastWorldStateSync = now - apiClient.worldState.doOnNext { - localRepository.saveWorldState(it) - for (event in it.events) { - if (event.aprilFools != null && event.isCurrentlyActive) { - AprilFoolsHandler.handle(event.aprilFools, event.end) - } + localRepository.save(state) + for (event in state.events) { + if (event.aprilFools != null && event.isCurrentlyActive) { + AprilFoolsHandler.handle(event.aprilFools, event.end) } } - } else { - Flowable.just(WorldState()) + return state } + return null } override fun getWorldState(): Flowable { 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 422e46e9c..3f11e468a 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 @@ -31,13 +31,9 @@ class InventoryRepositoryImpl( userID: String, var appConfigManager: AppConfigManager ) : BaseRepositoryImpl(localRepository, apiClient, userID), InventoryRepository { - override fun getQuestContent(keys: List): Flow> { - return localRepository.getQuestContent(keys) - } + override fun getQuestContent(keys: List) = localRepository.getQuestContent(keys) - override fun getQuestContent(key: String): Flowable { - return localRepository.getQuestContent(key) - } + override fun getQuestContent(key: String) = localRepository.getQuestContent(key) override fun getEquipment(searchedKeys: List): Flowable> { return localRepository.getEquipment(searchedKeys) @@ -75,7 +71,7 @@ class InventoryRepositoryImpl( return localRepository.getOwnedItems(userID, includeZero) } - override fun getItemsFlowable(itemClass: Class, keys: Array): Flow> { + override fun getItems(itemClass: Class, keys: Array): Flow> { return localRepository.getItemsFlowable(itemClass, keys) } 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 30cbe18c0..dd82f6db3 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,7 @@ 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.RxErrorHandler +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 @@ -16,12 +16,10 @@ 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.reactivex.rxjava3.core.Single -import kotlinx.coroutines.MainScope +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.flow.first -import kotlinx.coroutines.launch -import retrofit2.HttpException +import kotlinx.coroutines.flow.firstOrNull import java.util.UUID class SocialRepositoryImpl( @@ -29,45 +27,31 @@ class SocialRepositoryImpl( apiClient: ApiClient, userID: String ) : BaseRepositoryImpl(localRepository, apiClient, userID), SocialRepository { - override fun transferGroupOwnership(groupID: String, userID: String): Flowable { - return localRepository.getGroupFlowable(groupID) - .map { - val group = localRepository.getUnmanagedCopy(it) - group.leaderID = userID - group - } - .flatMap { - apiClient.updateGroup(it.id, it) - } + override suspend fun transferGroupOwnership(groupID: String, userID: String): Group? { + val group = localRepository.getGroup(groupID).first()?.let { localRepository.getUnmanagedCopy(it) } + group?.leaderID = userID + return group?.let { apiClient.updateGroup(groupID, it) } } - override fun removeMemberFromGroup(groupID: String, userID: String): Flowable> { - return apiClient.removeMemberFromGroup(groupID, userID) - .flatMap { - retrieveGroupMembers(groupID, true) - } + override suspend fun removeMemberFromGroup(groupID: String, userID: String): List? { + apiClient.removeMemberFromGroup(groupID, userID) + return retrieveGroupMembers(groupID, true) } override fun blockMember(userID: String): Flowable> { return apiClient.blockMember(userID) } - override fun getGroupMembership(id: String): Flowable { - return localRepository.getGroupMembership(userID, id) - } + override fun getGroupMembership(id: String) = localRepository.getGroupMembership(userID, id) override fun getGroupMemberships(): Flowable> { return localRepository.getGroupMemberships(userID) } - override fun retrieveGroupChat(groupId: String): Single> { - return apiClient.listGroupChat(groupId) - .flatMap { Flowable.fromIterable(it) } - .map { chatMessage -> - chatMessage.groupId = groupId - chatMessage - } - .toList() + override suspend fun retrieveGroupChat(groupId: String): List? { + val messages = apiClient.listGroupChat(groupId) + messages?.forEach { it.groupId = groupId } + return messages } override fun getGroupChat(groupId: String): Flowable> { @@ -75,7 +59,7 @@ class SocialRepositoryImpl( } override fun markMessagesSeen(seenGroupId: String) { - apiClient.seenMessages(seenGroupId).subscribe({ }, RxErrorHandler.handleEmptyError()) + apiClient.seenMessages(seenGroupId).subscribe({ }, ExceptionHandler.rx()) } override fun flagMessage(chatMessageID: String, additionalInfo: String, groupID: String?): Flowable { @@ -131,76 +115,70 @@ class SocialRepositoryImpl( return postGroupChat(groupId, messageObject) } - override fun retrieveGroup(id: String): Flowable { - return Flowable.zip( - apiClient.getGroup(id).doOnNext { localRepository.saveGroup(it) }, - retrieveGroupChat(id) - .toFlowable() - ) { group, _ -> - group - }.doOnError { - if (it is HttpException && it.code() == 404) { - MainScope().launch { - val group = localRepository.getGroup(id).first() - if (group != null) { - localRepository.delete(group) - } - } - } - } + override suspend fun retrieveGroup(id: String): Group? { + val group = apiClient.getGroup(id) + group?.let { localRepository.saveGroup(it) } + retrieveGroupChat(id) + return group } - override fun getGroup(id: String?) = id?.let { localRepository.getGroup(it) } ?: emptyFlow() - override fun getGroupFlowable(id: String?): Flowable = id?.let { localRepository.getGroupFlowable(it) } ?: Flowable.empty() - - override fun leaveGroup(id: String?, keepChallenges: Boolean): Flowable { + override fun getGroup(id: String?): Flow { if (id?.isNotBlank() != true) { - return Flowable.empty() + return emptyFlow() } - return apiClient.leaveGroup(id, if (keepChallenges) "remain-in-challenges" else "leave-challenges") - .doOnNext { localRepository.updateMembership(userID, id, false) } - .flatMapMaybe { localRepository.getGroupFlowable(id).firstElement() } + return localRepository.getGroup(id) } - override fun joinGroup(id: String?): Flowable { + override suspend fun leaveGroup(id: String?, keepChallenges: Boolean): Group? { if (id?.isNotBlank() != true) { - return Flowable.empty() + return null } - return apiClient.joinGroup(id) - .doOnNext { group -> - localRepository.updateMembership(userID, id, true) - localRepository.save(group) - } + + apiClient.leaveGroup(id, if (keepChallenges) "remain-in-challenges" else "leave-challenges") + localRepository.updateMembership(userID, id, false) + return localRepository.getGroup(id).firstOrNull() } - override fun createGroup( + override suspend fun joinGroup(id: String?): Group? { + if (id?.isNotBlank() != true) { + return null + } + val group = apiClient.joinGroup(id) + group?.let { + localRepository.updateMembership(userID, id, true) + localRepository.save(group) + } + return group + } + + override suspend fun createGroup( name: String?, description: String?, leader: String?, type: String?, privacy: String?, leaderCreateChallenge: Boolean? - ): Flowable { + ): Group? { val group = Group() group.name = name group.description = description group.type = type group.leaderID = leader group.privacy = privacy - return apiClient.createGroup(group).doOnNext { - localRepository.save(it) - } + val savedGroup = apiClient.createGroup(group) + savedGroup?.let { localRepository.save(it) } + return savedGroup } - override fun updateGroup( + override suspend fun updateGroup( group: Group?, name: String?, description: String?, leader: String?, leaderCreateChallenge: Boolean? - ): Flowable { + ): Group? { if (group == null) { - return Flowable.empty() + return null } val copiedGroup = localRepository.getUnmanagedCopy(group) copiedGroup.name = name @@ -224,21 +202,21 @@ class SocialRepositoryImpl( } } - override fun getGroups(type: String): Flowable> = localRepository.getGroups(type) + override fun getGroups(type: String) = localRepository.getGroups(type) - override fun getPublicGuilds(): Flowable> = localRepository.getPublicGuilds() + override fun getPublicGuilds() = localRepository.getPublicGuilds() - override fun getInboxConversations(): Flowable> = localRepository.getInboxConversation(userID) + override fun getInboxConversations() = localRepository.getInboxConversation(userID) - override fun getInboxMessages(replyToUserID: String?): Flowable> = localRepository.getInboxMessages(userID, replyToUserID) + override fun getInboxMessages(replyToUserID: String?) = localRepository.getInboxMessages(userID, replyToUserID) - override fun retrieveInboxMessages(uuid: String, page: Int): Flowable> { - return apiClient.retrieveInboxMessages(uuid, page).doOnNext { messages -> - messages.forEach { - it.isInboxMessage = true - } - localRepository.saveInboxMessages(userID, uuid, messages, page) + override suspend fun retrieveInboxMessages(uuid: String, page: Int): List? { + val messages = apiClient.retrieveInboxMessages(uuid, page) ?: return null + messages.forEach { + it.isInboxMessage = true } + localRepository.saveInboxMessages(userID, uuid, messages, page) + return messages } override fun retrieveInboxConversations(): Flowable> { @@ -247,29 +225,31 @@ class SocialRepositoryImpl( } } - override fun postPrivateMessage(recipientId: String, messageObject: HashMap): Flowable> { - return apiClient.postPrivateMessage(messageObject).flatMap { retrieveInboxMessages(recipientId, 0) } + override suspend fun postPrivateMessage(recipientId: String, messageObject: HashMap): List? { + val message = apiClient.postPrivateMessage(messageObject) + return retrieveInboxMessages(recipientId, 0) } - override fun postPrivateMessage(recipientId: String, message: String): Flowable> { + override suspend fun postPrivateMessage(recipientId: String, message: String): List? { val messageObject = HashMap() messageObject["message"] = message messageObject["toUserId"] = recipientId return postPrivateMessage(recipientId, messageObject) } - override fun getGroupMembers(id: String) = localRepository.getGroupMembers(id) + override suspend fun getGroupMembers(id: String) = localRepository.getGroupMembers(id) - override fun retrieveGroupMembers(id: String, includeAllPublicFields: Boolean): Flowable> { - return apiClient.getGroupMembers(id, includeAllPublicFields) - .doOnNext { members -> localRepository.saveGroupMembers(id, members) } + override suspend fun retrieveGroupMembers(id: String, includeAllPublicFields: Boolean): List? { + val members = apiClient.getGroupMembers(id, includeAllPublicFields) + members?.let { localRepository.saveGroupMembers(id, it) } + return members } override fun inviteToGroup(id: String, inviteData: Map): Flowable> = apiClient.inviteToGroup(id, inviteData) - override fun getMember(userId: String?): Flowable { + override suspend fun retrieveMember(userId: String?): Member? { return if (userId == null) { - Flowable.empty() + null } else { try { apiClient.getMember(UUID.fromString(userId).toString()) @@ -279,8 +259,8 @@ class SocialRepositoryImpl( } } - override fun getMemberWithUsername(username: String?): Flowable { - return getMember(username) + override suspend fun retrieveMemberWithUsername(username: String?): Member? { + return retrieveMember(username) } override fun findUsernames(username: String, context: String?, id: String?): Flowable> { @@ -315,7 +295,7 @@ class SocialRepositoryImpl( } } - override fun getUserGroups(type: String?): Flowable> = localRepository.getUserGroups(userID, type) + override fun getUserGroups(type: String?) = localRepository.getUserGroups(userID, type) override fun acceptQuest(user: User?, partyId: String): Flowable { return apiClient.acceptQuest(partyId) 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 b01a08b23..225e183b6 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.RxErrorHandler +import com.habitrpg.android.habitica.helpers.ExceptionHandler import com.habitrpg.android.habitica.interactors.ScoreTaskLocallyInteractor import com.habitrpg.android.habitica.models.BaseMainObject import com.habitrpg.android.habitica.models.responses.BulkTaskScoringData @@ -49,9 +49,10 @@ class TaskRepositoryImpl( localRepository.saveTasks(userId, order, tasks) } - override fun retrieveTasks(userId: String, tasksOrder: TasksOrder): Flowable { - return this.apiClient.tasks - .doOnNext { res -> this.localRepository.saveTasks(userId, tasksOrder, res) } + override suspend fun retrieveTasks(userId: String, tasksOrder: TasksOrder): TaskList? { + val tasks = apiClient.getTasks() ?: return null + this.localRepository.saveTasks(userId, tasksOrder, tasks) + return tasks } override fun retrieveCompletedTodos(userId: String?): Flowable { @@ -309,11 +310,11 @@ class TaskRepositoryImpl( getTask(taskid).map { localRepository.getUnmanagedCopy(it) } override fun updateTaskInBackground(task: Task) { - updateTask(task).subscribe({ }, RxErrorHandler.handleEmptyError()) + updateTask(task).subscribe({ }, ExceptionHandler.rx()) } override fun createTaskInBackground(task: Task) { - createTask(task).subscribe({ }, RxErrorHandler.handleEmptyError()) + createTask(task).subscribe({ }, ExceptionHandler.rx()) } override fun getTaskCopies(userId: String): Flow> = 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 66c5c0500..c863f955c 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 @@ -1,6 +1,5 @@ package com.habitrpg.android.habitica.data.implementation -import androidx.core.os.bundleOf import com.habitrpg.android.habitica.data.ApiClient import com.habitrpg.android.habitica.data.TaskRepository import com.habitrpg.android.habitica.data.UserRepository @@ -8,7 +7,7 @@ import com.habitrpg.android.habitica.data.local.UserLocalRepository import com.habitrpg.android.habitica.models.user.UserQuestStatus import com.habitrpg.android.habitica.extensions.filterMapEmpty import com.habitrpg.android.habitica.helpers.AppConfigManager -import com.habitrpg.android.habitica.helpers.RxErrorHandler +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 @@ -28,7 +27,9 @@ 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.withContext import java.util.Date import java.util.GregorianCalendar import java.util.concurrent.TimeUnit @@ -70,45 +71,39 @@ class UserRepositoryImpl( return updateUser(userID, key, value) } - override fun retrieveUser(withTasks: Boolean): Flowable = - retrieveUser(withTasks, false) - @Suppress("ReturnCount") - override fun retrieveUser(withTasks: Boolean, forced: Boolean, overrideExisting: Boolean): Flowable { + override suspend fun retrieveUser(withTasks: Boolean, forced: Boolean, overrideExisting: Boolean): User? { // Only retrieve again after 3 minutes or it's forced. if (forced || this.lastSync == null || Date().time - (this.lastSync?.time ?: 0) > 180000) { + val user = apiClient.retrieveUser(withTasks) ?: return null lastSync = Date() - return apiClient.retrieveUser(withTasks) - .doOnNext { localRepository.saveUser(it, overrideExisting) } - .doOnNext { user -> - if (withTasks) { - val id = user.id - val tasksOrder = user.tasksOrder - val tasks = user.tasks - if (id != null && tasksOrder != null && tasks != null) { - taskRepository.saveTasks(id, tasksOrder, tasks) - } - } - } - .flatMap { user -> - val calendar = GregorianCalendar() - val timeZone = calendar.timeZone - val offset = -TimeUnit.MINUTES.convert(timeZone.getOffset(calendar.timeInMillis).toLong(), TimeUnit.MILLISECONDS) - if (offset.toInt() != (user.preferences?.timezoneOffset ?: 0)) { - return@flatMap updateUser(user.id ?: "", "preferences.timezoneOffset", offset.toString()) - } else { - return@flatMap Flowable.just(user) - } + localRepository.saveUser(user) + if (withTasks) { + val id = user.id + val tasksOrder = user.tasksOrder + val tasks = user.tasks + if (id != null && tasksOrder != null && tasks != null) { + taskRepository.saveTasks(id, tasksOrder, tasks) } + } + val calendar = GregorianCalendar() + val timeZone = calendar.timeZone + val offset = -TimeUnit.MINUTES.convert(timeZone.getOffset(calendar.timeInMillis).toLong(), TimeUnit.MILLISECONDS) + /*if (offset.toInt() != user.preferences?.timezoneOffset ?: 0) { + return@flatMap updateUser(user.id ?: "", "preferences.timezoneOffset", offset.toString()) + } else { + return@flatMap Flowable.just(user) + }*/ + return user } else { - return localRepository.getUserFlowable(userID).take(1) + return null } } - override fun revive(): Flowable = zipWithLiveUser(apiClient.revive()) { newUser, user -> - mergeUser(user, newUser) + override suspend fun revive(): User? { + apiClient.revive() + return retrieveUser(false, true) } - .flatMap { retrieveUser(false, true) } override fun resetTutorial(): Maybe { return localRepository.getTutorialSteps() @@ -123,9 +118,13 @@ class UserRepositoryImpl( .flatMap { updateData -> updateUser(updateData).firstElement() } } - override fun sleep(user: User): Flowable { - localRepository.modify(user) { it.preferences?.sleep = !(it.preferences?.sleep ?: false) } - return apiClient.sleep().map { user } + override suspend fun sleep(user: User): User { + val newValue = !(user.preferences?.sleep ?: false) + localRepository.modify(user) { it.preferences?.sleep = newValue } + if (apiClient.sleep() != true) { + localRepository.modify(user) { it.preferences?.sleep = !newValue } + } + return user } override fun getSkills(user: User): Flowable> = @@ -137,7 +136,7 @@ class UserRepositoryImpl( override fun useSkill(key: String, target: String?, taskId: String): Flowable { return zipWithLiveUser(apiClient.useSkill(key, target ?: "", taskId)) { response, user -> response.hpDiff = (response.user?.stats?.hp ?: 0.0) - (user.stats?.hp ?: 0.0) - response.expDiff = (response.user?.stats?.exp ?: 0.0) - (user.stats?.exp ?: 0.0) + response.expDiff =(response.user?.stats?.exp ?: 0.0) - (user.stats?.exp ?: 0.0) response.goldDiff = (response.user?.stats?.gp ?: 0.0) - (user.stats?.gp ?: 0.0) response.damage = (response.user?.party?.quest?.progress?.up ?: 0.0f) - (user.party?.quest?.progress?.up ?: 0.0f) response.user?.let { mergeUser(user, it) } @@ -148,7 +147,7 @@ class UserRepositoryImpl( override fun useSkill(key: String, target: String?): Flowable { return zipWithLiveUser(apiClient.useSkill(key, target ?: "")) { response, user -> response.hpDiff = (response.user?.stats?.hp ?: 0.0) - (user.stats?.hp ?: 0.0) - response.expDiff = (response.user?.stats?.exp ?: 0.0) - (user.stats?.exp ?: 0.0) + response.expDiff =(response.user?.stats?.exp ?: 0.0) - (user.stats?.exp ?: 0.0) response.goldDiff = (response.user?.stats?.gp ?: 0.0) - (user.stats?.gp ?: 0.0) response.damage = (response.user?.party?.quest?.progress?.up ?: 0.0f) - (user.party?.quest?.progress?.up ?: 0.0f) response.user?.let { mergeUser(user, it) } @@ -156,12 +155,15 @@ class UserRepositoryImpl( } } - override fun changeClass(): Flowable = apiClient.changeClass().flatMap { retrieveUser(withTasks = false, forced = true) } + override suspend fun disableClasses(): User? = apiClient.disableClasses() - override fun disableClasses(): Flowable = apiClient.disableClasses().flatMap { retrieveUser(withTasks = false, forced = true) } + override suspend fun changeClass(selectedClass: String?): User? { + return apiClient.changeClass(selectedClass) + } - override fun changeClass(selectedClass: String): Flowable = apiClient.changeClass(selectedClass) - .flatMap { retrieveUser(false) } + override fun unlockPath(customization: Customization): Flowable { + return unlockPath(customization.path, customization.price ?: 0) + } override fun unlockPath(path: String, price: Int): Flowable { return zipWithLiveUser(apiClient.unlockPath(path)) { unlockResponse, copiedUser -> @@ -175,11 +177,7 @@ class UserRepositoryImpl( } } - override fun unlockPath(customization: Customization): Flowable { - return unlockPath(customization.unlockPath, customization.price ?: 0) - } - - override fun runCron() { + override suspend fun runCron() { runCron(ArrayList()) } @@ -188,9 +186,8 @@ class UserRepositoryImpl( return localRepository.getUserQuestStatus(userID) } - override fun reroll(): Flowable { + override suspend fun reroll(): User? { return apiClient.reroll() - .flatMap { retrieveUser(true, true, true) } } override fun readNotifications(notificationIds: Map>): Flowable> = @@ -210,8 +207,9 @@ class UserRepositoryImpl( .doOnNext { apiClient.setLanguageCode(languageCode) } } - override fun resetAccount(): Flowable { - return apiClient.resetAccount().flatMap { retrieveUser(withTasks = true, forced = true) } + override suspend fun resetAccount(): User? { + apiClient.resetAccount() + return retrieveUser(withTasks = true, forced = true) } override fun deleteAccount(password: String): Flowable = @@ -262,7 +260,7 @@ class UserRepositoryImpl( liveUser.stats?.points = liveUser.stats?.points?.dec() } }, - RxErrorHandler.handleEmptyError() + ExceptionHandler.rx() ) return zipWithLiveUser(apiClient.allocatePoint(stat.value)) { stats, user -> localRepository.modify(user) { liveUser -> @@ -295,26 +293,33 @@ class UserRepositoryImpl( stats } - override fun runCron(tasks: MutableList) { - var observable: Maybe = localRepository.getUserFlowable(userID).firstElement() - .filter { it.needsCron } - .map { user -> - localRepository.modify(user) { liveUser -> - liveUser.needsCron = false - liveUser.lastCron = Date() + 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 } - 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() } } - if (tasks.isNotEmpty()) { - val scoringList = tasks.map { mapOf(Pair("id", it.id ?: ""), Pair("direction", TaskDirection.UP.text)) } - observable = observable.flatMap { taskRepository.bulkScoreTasks(scoringList).firstElement() } + observable.flatMap { apiClient.runCron().firstElement() } + // .flatMap { + // this.retrieveUser(withTasks = true, forced = true) + // } + .subscribe({ }, ExceptionHandler.rx()) } - observable.flatMap { apiClient.runCron().firstElement() } - .flatMap { this.retrieveUser(withTasks = true, forced = true).firstElement() } - .subscribe({ }, { - analyticsManager.logEvent("cron failed", bundleOf(Pair("error", it.localizedMessage))) - RxErrorHandler.reportError(it) - }) } override fun useCustomization(type: String, category: String?, identifier: String): Flowable { @@ -340,7 +345,7 @@ class UserRepositoryImpl( } } }, - RxErrorHandler.handleEmptyError() + ExceptionHandler.rx() ) } var updatePath = "preferences.$type" @@ -375,22 +380,17 @@ class UserRepositoryImpl( return localRepository.getTeamPlans(userID) } - override fun retrieveTeamPlan(teamID: String): Flowable { - return Flowable.zip( - apiClient.getGroup(teamID), apiClient.getTeamPlanTasks(teamID) - ) { team, tasks -> - team.tasks = tasks - team + override suspend fun retrieveTeamPlan(teamID: String): Group? { + val team = apiClient.getGroup(teamID) ?: return null + team.tasks = apiClient.getTeamPlanTasks(teamID) + localRepository.save(team) + val id = team.id + val tasksOrder = team.tasksOrder + val tasks = team.tasks + if (id.isNotBlank() && tasksOrder != null && tasks != null) { + taskRepository.saveTasks(id, tasksOrder, tasks) } - .doOnNext { localRepository.save(it) } - .doOnNext { team -> - val id = team.id - val tasksOrder = team.tasksOrder - val tasks = team.tasks - if (id.isNotBlank() && tasksOrder != null && tasks != null) { - taskRepository.saveTasks(id, tasksOrder, tasks) - } - } + return team } override fun getTeamPlan(teamID: String): Flowable { 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 252e454f5..895c57fe3 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 @@ -28,7 +28,7 @@ interface InventoryLocalRepository : ContentLocalRepository { fun getOwnedPets(userID: String): Flow> fun getInAppRewards(): Flowable> - fun getQuestContent(key: String): Flowable + fun getQuestContent(key: String): Flow fun getQuestContent(keys: List): Flow> fun getEquipment(searchedKeys: List): Flowable> 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 bd0cc9518..66a106520 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 @@ -7,16 +7,16 @@ 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 getUserGroups(userID: String, type: String?): Flowable> + fun getUserGroups(userID: String, type: String?): Flow> fun getGroups(type: String): Flowable> fun getGroup(id: String): Flow - fun getGroupFlowable(id: String): Flowable fun saveGroup(group: Group) fun getGroupChat(groupId: String): Flowable> @@ -39,13 +39,13 @@ interface SocialLocalRepository : BaseLocalRepository { fun doesGroupExist(id: String): Boolean fun updateMembership(userId: String, id: String, isMember: Boolean) - fun getGroupMembership(userId: String, id: String): Flowable + fun getGroupMembership(userId: String, id: String): Flow fun getGroupMemberships(userId: String): Flowable> fun rejectGroupInvitation(userID: String, groupID: String) - fun getInboxMessages(userId: String, replyToUserID: String?): Flowable> + fun getInboxMessages(userId: String, replyToUserID: String?): Flow> - fun getInboxConversation(userId: String): Flowable> + fun getInboxConversation(userId: String): Flow> fun saveGroupMemberships(userID: String?, memberships: List) fun saveInboxMessages( userID: String, 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 366f580af..7d1159c6f 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,7 @@ package com.habitrpg.android.habitica.data.local.implementation import com.habitrpg.android.habitica.data.local.InventoryLocalRepository -import com.habitrpg.android.habitica.helpers.RxErrorHandler +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 @@ -39,14 +39,12 @@ class RealmInventoryLocalRepository(realm: Realm) : RealmContentLocalRepository( .filter { it.isLoaded } } - override fun getQuestContent(key: String): Flowable { - return RxJavaBridge.toV3Flowable( - realm.where(QuestContent::class.java).equalTo("key", key) + override fun getQuestContent(key: String): Flow { + return realm.where(QuestContent::class.java).equalTo("key", key) .findAll() - .asFlowable() + .toFlow() .filter { content -> content.isLoaded && content.isValid && !content.isEmpty() } .map { content -> content.first() } - ) } override fun getEquipment(searchedKeys: List): Flowable> { @@ -240,7 +238,7 @@ class RealmInventoryLocalRepository(realm: Realm) : RealmContentLocalRepository( } override fun changeOwnedCount(type: String, key: String, userID: String, amountToAdd: Int) { - getOwnedItem(userID, type, key, true).firstElement().subscribe({ changeOwnedCount(it, amountToAdd) }, RxErrorHandler.handleEmptyError()) + getOwnedItem(userID, type, key, true).firstElement().subscribe({ changeOwnedCount(it, amountToAdd) }, ExceptionHandler.rx()) } override fun changeOwnedCount(item: OwnedItem, amountToAdd: Int?) { 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 5bc9eeb68..06fd7a6af 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 @@ -14,21 +14,21 @@ 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.flatMapLatest import kotlinx.coroutines.flow.map class RealmSocialLocalRepository(realm: Realm) : RealmBaseLocalRepository(realm), SocialLocalRepository { - override fun getGroupMembership(userId: String, id: String): Flowable = RxJavaBridge.toV3Flowable( - realm.where(GroupMembership::class.java) + override fun getGroupMembership(userId: String, id: String) = realm.where(GroupMembership::class.java) .equalTo("userID", userId) .equalTo("groupID", id) .findAll() - .asFlowable() + .toFlow() .filter { it.isLoaded && it.isNotEmpty() } .map { it.first() } - ) override fun getGroupMemberships(userId: String): Flowable> = RxJavaBridge.toV3Flowable( realm.where(GroupMembership::class.java) @@ -134,29 +134,25 @@ class RealmSocialLocalRepository(realm: Realm) : RealmBaseLocalRepository(realm) .filter { it.isLoaded } ) - override fun getUserGroups(userID: String, type: String?): Flowable> = RxJavaBridge.toV3Flowable( - realm.where(GroupMembership::class.java) + @OptIn(ExperimentalCoroutinesApi::class) + override fun getUserGroups(userID: String, type: String?) = realm.where(GroupMembership::class.java) .equalTo("userID", userID) .findAll() - .asFlowable() - .filter { it.isLoaded } - ) - .flatMap { memberships -> - RxJavaBridge.toV3Flowable( - realm.where(Group::class.java) - .equalTo("type", type ?: "guild") - .notEqualTo("id", Group.TAVERN_ID) - .`in`( - "id", - memberships.map { - return@map it.groupID - }.toTypedArray() - ) - .sort("memberCount", Sort.DESCENDING) - .findAll() - .asFlowable() - .filter { it.isLoaded } - ) + .toFlow() + .filter { it.isLoaded } + .flatMapLatest { memberships -> + realm.where(Group::class.java) + .equalTo("type", type ?: "guild") + .notEqualTo("id", Group.TAVERN_ID) + .`in`( + "id", + memberships.map { + return@map it.groupID + }.toTypedArray() + ) + .sort("memberCount", Sort.DESCENDING) + .findAll() + .toFlow() } override fun getGroups(type: String): Flowable> { @@ -169,17 +165,6 @@ class RealmSocialLocalRepository(realm: Realm) : RealmBaseLocalRepository(realm) ) } - override fun getGroupFlowable(id: String): Flowable { - return RxJavaBridge.toV3Flowable( - realm.where(Group::class.java) - .equalTo("id", id) - .findAll() - .asFlowable() - .filter { group -> group.isLoaded && group.isValid && !group.isEmpty() } - .map { groups -> groups.first() } - ) - } - override fun getGroup(id: String): Flow { return realm.where(Group::class.java) .equalTo("id", id) @@ -205,13 +190,10 @@ class RealmSocialLocalRepository(realm: Realm) : RealmBaseLocalRepository(realm) executeTransaction { chatMessage?.deleteFromRealm() } } - override fun getGroupMembers(partyId: String): Flow> { - return realm.where(Member::class.java) + override fun getGroupMembers(partyId: String) = realm.where(Member::class.java) .equalTo("party.id", partyId) .findAll() - .toFlow() - .filter { it.isLoaded } - } + .toFlow() override fun updateRSVPNeeded(user: User?, newValue: Boolean) { executeTransaction { user?.party?.quest?.RSVPNeeded = newValue } @@ -302,27 +284,19 @@ class RealmSocialLocalRepository(realm: Realm) : RealmBaseLocalRepository(realm) return party != null && party.isValid } - override fun getInboxMessages(userId: String, replyToUserID: String?): Flowable> { - return RxJavaBridge.toV3Flowable( - realm.where(ChatMessage::class.java) + override fun getInboxMessages(userId: String, replyToUserID: String?) = realm.where(ChatMessage::class.java) .equalTo("isInboxMessage", true) .equalTo("uuid", replyToUserID) .equalTo("userID", userId) .sort("timestamp", Sort.DESCENDING) .findAll() - .asFlowable() + .toFlow() .filter { it.isLoaded } - ) - } - override fun getInboxConversation(userId: String): Flowable> { - return RxJavaBridge.toV3Flowable( - realm.where(InboxConversation::class.java) + override fun getInboxConversation(userId: String) = realm.where(InboxConversation::class.java) .equalTo("userID", userId) .sort("timestamp", Sort.DESCENDING) .findAll() - .asFlowable() + .toFlow() .filter { it.isLoaded } - ) - } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/extensions/Flowable-Extensions.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/extensions/Flowable-Extensions.kt index 6d25b030e..c27f2de98 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/extensions/Flowable-Extensions.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/extensions/Flowable-Extensions.kt @@ -1,10 +1,10 @@ package com.habitrpg.android.habitica.extensions -import com.habitrpg.android.habitica.helpers.RxErrorHandler +import com.habitrpg.android.habitica.helpers.ExceptionHandler import io.reactivex.rxjava3.core.Flowable import io.reactivex.rxjava3.disposables.Disposable import io.reactivex.rxjava3.functions.Consumer fun Flowable.subscribeWithErrorHandler(function: Consumer): Disposable { - return subscribe(function, RxErrorHandler.handleEmptyError()) + return subscribe(function, ExceptionHandler.rx()) } 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 2550e226c..07fbb1d05 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 @@ -22,7 +22,7 @@ class AppConfigManager(contentRepository: ContentRepository?): com.habitrpg.comm { worldState = it }, - RxErrorHandler.handleEmptyError() + ExceptionHandler.rx() ) } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/RxErrorHandler.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/ExceptionHandler.kt similarity index 73% rename from Habitica/src/main/java/com/habitrpg/android/habitica/helpers/RxErrorHandler.kt rename to Habitica/src/main/java/com/habitrpg/android/habitica/helpers/ExceptionHandler.kt index dce9aebb5..2e1f573aa 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/RxErrorHandler.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/ExceptionHandler.kt @@ -4,24 +4,31 @@ import android.util.Log import com.habitrpg.android.habitica.BuildConfig import com.habitrpg.android.habitica.proxy.AnalyticsManager import io.reactivex.rxjava3.functions.Consumer -import java.io.EOFException -import java.io.IOException +import kotlinx.coroutines.CoroutineExceptionHandler import okhttp3.internal.http2.ConnectionShutdownException import retrofit2.HttpException +import java.io.EOFException +import java.io.IOException -class RxErrorHandler { +class ExceptionHandler { private var analyticsManager: AnalyticsManager? = null companion object { - private var instance: RxErrorHandler? = null + private var instance = ExceptionHandler() fun init(analyticsManager: AnalyticsManager) { - instance = RxErrorHandler() - instance?.analyticsManager = analyticsManager + instance.analyticsManager = analyticsManager } - fun handleEmptyError(): Consumer { + fun coroutine(handler: ((Throwable) -> Unit)? = null): CoroutineExceptionHandler { + return CoroutineExceptionHandler { _, throwable -> + reportError(throwable) + handler?.invoke(throwable) + } + } + + fun rx(): Consumer { // Can't be turned into a lambda, because it then doesn't work for some reason. return Consumer { reportError(it) } } @@ -40,7 +47,7 @@ class RxErrorHandler { !retrofit2.adapter.rxjava3.HttpException::class.java.isAssignableFrom(throwable.javaClass) && throwable !is ConnectionShutdownException ) { - instance?.analyticsManager?.logException(throwable) + instance.analyticsManager?.logException(throwable) } } } 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 c24f5edaf..96eaeb26b 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 @@ -120,6 +120,6 @@ class MainNotificationsManager: NotificationsManager { private fun readNotification(notification: Notification) { apiClient?.get()?.readNotification(notification.id) - ?.subscribe({ }, RxErrorHandler.handleEmptyError()) + ?.subscribe({ }, ExceptionHandler.rx()) } } 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 12193ec65..6acb1c644 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 @@ -25,7 +25,6 @@ import com.habitrpg.android.habitica.HabiticaBaseApplication import com.habitrpg.android.habitica.R import com.habitrpg.android.habitica.data.ApiClient import com.habitrpg.android.habitica.extensions.addOkButton -import com.habitrpg.android.habitica.extensions.subscribeWithErrorHandler import com.habitrpg.android.habitica.models.user.User import com.habitrpg.android.habitica.proxy.AnalyticsManager import com.habitrpg.android.habitica.ui.activities.PurchaseActivity @@ -34,9 +33,9 @@ import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog import com.habitrpg.common.habitica.models.IAPGift import com.habitrpg.common.habitica.models.PurchaseValidationRequest import com.habitrpg.common.habitica.models.Transaction -import io.reactivex.rxjava3.core.Flowable import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.MainScope import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -88,7 +87,7 @@ class PurchaseHandler( return } BillingClient.BillingResponseCode.ITEM_ALREADY_OWNED -> { - CoroutineScope(Dispatchers.IO).launch { + CoroutineScope(Dispatchers.IO).launch(ExceptionHandler.coroutine()) { for (purchase in purchases) { consume(purchase) } @@ -220,7 +219,7 @@ class PurchaseHandler( apiClient.validatePurchase(validationRequest).subscribe({ processedPurchase(purchase) val gift = removeGift(sku) - CoroutineScope(Dispatchers.IO).launch { + CoroutineScope(Dispatchers.IO).launch(ExceptionHandler.coroutine()) { consume(purchase) } displayConfirmationDialog(purchase, gift?.second) @@ -233,7 +232,7 @@ class PurchaseHandler( apiClient.validateNoRenewSubscription(validationRequest).subscribe({ processedPurchase(purchase) val gift = removeGift(sku) - CoroutineScope(Dispatchers.IO).launch { + CoroutineScope(Dispatchers.IO).launch(ExceptionHandler.coroutine()) { consume(purchase) } displayConfirmationDialog(purchase, gift?.second) @@ -246,7 +245,7 @@ class PurchaseHandler( apiClient.validateSubscription(validationRequest).subscribe({ processedPurchase(purchase) analyticsManager.logEvent("user_subscribed", bundleOf(Pair("sku", sku))) - CoroutineScope(Dispatchers.IO).launch { + CoroutineScope(Dispatchers.IO).launch(ExceptionHandler.coroutine()) { acknowledgePurchase(purchase) } displayConfirmationDialog(purchase) @@ -269,7 +268,9 @@ class PurchaseHandler( } private fun processedPurchase(purchase: Purchase) { - userViewModel.userRepository.retrieveUser(false, true).subscribeWithErrorHandler {} + MainScope().launch(ExceptionHandler.coroutine()) { + userViewModel.userRepository.retrieveUser(false, true) + } } private fun buildValidationRequest(purchase: Purchase): PurchaseValidationRequest { @@ -297,7 +298,7 @@ class PurchaseHandler( if (res.message != null && res.message == "RECEIPT_ALREADY_USED") { processedPurchase(purchase) removeGift(purchase.skus.firstOrNull()) - CoroutineScope(Dispatchers.IO).launch { + CoroutineScope(Dispatchers.IO).launch(ExceptionHandler.coroutine()) { consume(purchase) } return @@ -332,9 +333,9 @@ class PurchaseHandler( return fallback } - fun cancelSubscription(): Flowable { - return apiClient.cancelSubscription() - .flatMap { userViewModel.userRepository.retrieveUser(false, true) } + suspend fun cancelSubscription(): User? { + apiClient.cancelSubscription() + return userViewModel.userRepository.retrieveUser(false, true) } private fun durationString(sku: String): String { @@ -371,7 +372,7 @@ class PurchaseHandler( } private fun displayConfirmationDialog(purchase: Purchase, giftedTo: String? = null) { - CoroutineScope(Dispatchers.Main).launch { + CoroutineScope(Dispatchers.Main).launch(ExceptionHandler.coroutine()) { val application = (context as? HabiticaBaseApplication) ?: (context.applicationContext as? HabiticaBaseApplication) ?: return@launch val sku = purchase.skus.firstOrNull() ?: return@launch diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/SoundFile.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/SoundFile.kt index 0864cffe4..4f5cc845d 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/SoundFile.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/SoundFile.kt @@ -48,7 +48,7 @@ class SoundFile(val theme: String, private val fileName: String) { player?.start() } catch (e: IllegalStateException) { } catch (e: Exception) { - RxErrorHandler.reportError(e) + ExceptionHandler.reportError(e) } } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/SoundManager.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/SoundManager.kt index d4387f280..178e461e4 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/SoundManager.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/SoundManager.kt @@ -34,7 +34,7 @@ class SoundManager { soundFiles.add(SoundFile(soundTheme, SoundReward)) soundFiles.add(SoundFile(soundTheme, SoundTodo)) soundFileLoader.download(soundFiles) - .subscribe({}, RxErrorHandler.handleEmptyError()) + .subscribe({}, ExceptionHandler.rx()) } fun loadAndPlayAudio(type: String) { @@ -54,7 +54,7 @@ class SoundManager { loadedSoundFiles[type] = file file.play() }, - RxErrorHandler.handleEmptyError() + ExceptionHandler.rx() ) } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/TaskAlarmManager.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/TaskAlarmManager.kt index 3431f4b88..44791f639 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/TaskAlarmManager.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/TaskAlarmManager.kt @@ -58,7 +58,7 @@ class TaskAlarmManager( taskRepository.getTaskCopy(taskId) .filter { task -> task.isValid && task.isManaged && TaskType.DAILY == task.type } .firstElement() - .subscribe({ this.setAlarmsForTask(it) }, RxErrorHandler.handleEmptyError()) + .subscribe({ this.setAlarmsForTask(it) }, ExceptionHandler.rx()) } suspend fun scheduleAllSavedAlarms(preventDailyReminder: Boolean) { diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/notifications/HabiticaFirebaseMessagingService.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/notifications/HabiticaFirebaseMessagingService.kt index d4cdcd1ad..99ea9d490 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/notifications/HabiticaFirebaseMessagingService.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/notifications/HabiticaFirebaseMessagingService.kt @@ -21,7 +21,7 @@ class HabiticaFirebaseMessagingService : FirebaseMessagingService() { pushNotificationManager.displayNotification(remoteMessage) if (remoteMessage.data["identifier"]?.contains(PushNotificationManager.WON_CHALLENGE_PUSH_NOTIFICATION_KEY) == true) { - // userRepository.retrieveUser(true).subscribe({}, RxErrorHandler.handleEmptyError()) + // userRepository.retrieveUser(true).subscribe({}, ExceptionHandler.rx()) } } } 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 f6ce17841..c4b8295d6 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,7 +7,7 @@ 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.RxErrorHandler +import com.habitrpg.android.habitica.helpers.ExceptionHandler import com.habitrpg.android.habitica.models.user.User class PushNotificationManager( @@ -46,7 +46,7 @@ class PushNotificationManager( val pushDeviceData = HashMap() pushDeviceData["regId"] = this.refreshedToken pushDeviceData["type"] = "android" - apiClient.addPushDevice(pushDeviceData).subscribe({ }, RxErrorHandler.handleEmptyError()) + apiClient.addPushDevice(pushDeviceData).subscribe({ }, ExceptionHandler.rx()) } } @@ -54,7 +54,7 @@ class PushNotificationManager( if (this.refreshedToken.isEmpty()) { return } - apiClient.deletePushDevice(this.refreshedToken).subscribe({ }, RxErrorHandler.handleEmptyError()) + apiClient.deletePushDevice(this.refreshedToken).subscribe({ }, ExceptionHandler.rx()) } private fun userHasPushDevice(): Boolean { 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 a6b08896e..4125c9194 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 @@ -8,7 +8,7 @@ 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.RxErrorHandler +import com.habitrpg.android.habitica.helpers.ExceptionHandler import com.habitrpg.android.habitica.models.inventory.Egg import com.habitrpg.android.habitica.models.inventory.HatchingPotion import com.habitrpg.android.habitica.models.user.Items @@ -37,7 +37,7 @@ constructor( dialog.setAdditionalContentView(petWrapper) dialog.addButton(R.string.equip, true) { _, _ -> inventoryRepository.equip("pet", requestValues.egg.key + "-" + requestValues.potion.key) - .subscribe({}, RxErrorHandler.handleEmptyError()) + .subscribe({}, ExceptionHandler.rx()) } dialog.addButton(R.string.share, false) { hatchingDialog, _ -> val message = requestValues.context.getString(R.string.share_hatched, potionName, eggName) 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 116a782d5..669a660ab 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 @@ -5,7 +5,7 @@ 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.RxErrorHandler +import com.habitrpg.android.habitica.helpers.ExceptionHandler import com.habitrpg.android.habitica.helpers.SoundManager import com.habitrpg.android.habitica.models.user.Stats import com.habitrpg.android.habitica.models.user.User @@ -94,7 +94,7 @@ constructor( private fun showClassSelection(requestValues: RequestValues) { checkClassSelectionUseCase.observable(CheckClassSelectionUseCase.RequestValues(requestValues.user, true, null, requestValues.activity)) - .subscribe({ }, RxErrorHandler.handleEmptyError()) + .subscribe({ }, ExceptionHandler.rx()) } class RequestValues( 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 fa1ffd430..19b3f55b2 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 @@ -14,14 +14,13 @@ 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.extensions.filterMap -import com.habitrpg.shared.habitica.extensions.round import com.habitrpg.android.habitica.models.user.Stats import com.habitrpg.android.habitica.models.user.User 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.HabiticaSnackbar.SnackbarDisplayType +import com.habitrpg.shared.habitica.extensions.round import io.reactivex.rxjava3.core.Flowable import javax.inject.Inject import kotlin.math.abs @@ -48,8 +47,9 @@ constructor( } if (requestValues.hasLeveledUp == true) { return@defer levelUpUseCase.observable(LevelUpUseCase.RequestValues(requestValues.user, requestValues.level, requestValues.context, requestValues.snackbarTargetView)) - .flatMap { userRepository.retrieveUser(true) } - .filterMap { it.stats } + // TODO: .flatMap { userRepository.retrieveUser(true) } + .flatMap { userRepository.getUserFlowable().firstElement().toFlowable() } + .map { it.stats } } else { return@defer Flowable.just(stats) } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/interactors/ShowNotificationInteractor.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/interactors/ShowNotificationInteractor.kt index 9464d05ab..e81295281 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/interactors/ShowNotificationInteractor.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/interactors/ShowNotificationInteractor.kt @@ -7,6 +7,7 @@ import android.widget.TextView import androidx.lifecycle.LifecycleCoroutineScope import com.google.firebase.analytics.FirebaseAnalytics import com.habitrpg.android.habitica.R +import com.habitrpg.android.habitica.helpers.ExceptionHandler import com.habitrpg.android.habitica.models.user.User import com.habitrpg.android.habitica.ui.views.HabiticaSnackbar import com.habitrpg.android.habitica.ui.views.SnackbarActivity @@ -126,7 +127,7 @@ class ShowNotificationInteractor( } else { 200 } - lifecycleScope.launch { + lifecycleScope.launch(ExceptionHandler.coroutine()) { delay(delayTime) lifecycleScope.launch(context = Dispatchers.Main) { val dialog = AchievementDialog(activity) diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/models/tasks/Task.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/models/tasks/Task.kt index 0c4de801f..a5e393167 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/models/tasks/Task.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/models/tasks/Task.kt @@ -5,7 +5,7 @@ import android.os.Parcelable import android.text.Spanned import com.google.gson.annotations.SerializedName import com.habitrpg.android.habitica.R -import com.habitrpg.android.habitica.helpers.RxErrorHandler +import com.habitrpg.android.habitica.helpers.ExceptionHandler import com.habitrpg.android.habitica.models.BaseMainObject import com.habitrpg.android.habitica.models.Tag import com.habitrpg.common.habitica.helpers.MarkdownParser @@ -436,7 +436,7 @@ open class Task : RealmObject, BaseMainObject, Parcelable, BaseTask { i += 1 } } catch (e: JSONException) { - RxErrorHandler.reportError(e) + ExceptionHandler.reportError(e) } } this.weeksOfMonth = weeksOfMonth.toList() @@ -465,7 +465,7 @@ open class Task : RealmObject, BaseMainObject, Parcelable, BaseTask { i += 1 } } catch (e: JSONException) { - RxErrorHandler.reportError(e) + ExceptionHandler.reportError(e) } } this.daysOfMonth = daysOfMonth diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/modules/UserModule.java b/Habitica/src/main/java/com/habitrpg/android/habitica/modules/UserModule.java deleted file mode 100644 index 5b45562fa..000000000 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/modules/UserModule.java +++ /dev/null @@ -1,44 +0,0 @@ -package com.habitrpg.android.habitica.modules; - -import android.content.Context; -import android.content.SharedPreferences; - -import com.habitrpg.android.habitica.BuildConfig; -import com.habitrpg.android.habitica.data.TaskRepository; -import com.habitrpg.android.habitica.data.UserRepository; -import com.habitrpg.android.habitica.helpers.TaskAlarmManager; -import com.habitrpg.android.habitica.helpers.UserScope; -import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel; - -import javax.inject.Named; - -import dagger.Module; -import dagger.Provides; - -@Module -public class UserModule { - public static final String NAMED_USER_ID = "userId"; - - @Provides - @UserScope - TaskAlarmManager providesTaskAlarmManager(Context context, TaskRepository taskRepository, @Named(NAMED_USER_ID) String userId) { - return new TaskAlarmManager(context, taskRepository, userId); - } - - @Provides - @Named(NAMED_USER_ID) - @UserScope - public String providesUserID(SharedPreferences sharedPreferences) { - if (BuildConfig.DEBUG && !BuildConfig.TEST_USER_ID.isEmpty()) { - return BuildConfig.TEST_USER_ID; - } else { - return sharedPreferences.getString("UserID", ""); - } - } - - @Provides - @UserScope - MainUserViewModel providesUserViewModel(String userID, UserRepository userRepository) { - return new MainUserViewModel(userID, userRepository); - } -} diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/modules/UserModule.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/modules/UserModule.kt new file mode 100644 index 000000000..24acf248f --- /dev/null +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/modules/UserModule.kt @@ -0,0 +1,50 @@ +package com.habitrpg.android.habitica.modules + +import android.content.Context +import android.content.SharedPreferences +import com.habitrpg.android.habitica.BuildConfig +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.TaskAlarmManager +import com.habitrpg.android.habitica.helpers.UserScope +import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel +import dagger.Module +import dagger.Provides +import javax.inject.Named + +@Module +class UserModule { + @Provides + @UserScope + fun providesTaskAlarmManager( + context: Context, + taskRepository: TaskRepository, + @Named(NAMED_USER_ID) userId: String + ): TaskAlarmManager { + return TaskAlarmManager(context, taskRepository, userId) + } + + @Provides + @Named(NAMED_USER_ID) + @UserScope + fun providesUserID(sharedPreferences: SharedPreferences): String { + return if (BuildConfig.DEBUG && BuildConfig.TEST_USER_ID.isNotEmpty()) { + BuildConfig.TEST_USER_ID + } else { + sharedPreferences.getString("UserID", "") ?: "" + } + } + + @Provides + @UserScope + fun providesUserViewModel( + @Named(NAMED_USER_ID) userID: String, + userRepository: UserRepository, + socialRepository: SocialRepository + ) = MainUserViewModel(userID, userRepository, socialRepository) + + companion object { + const val NAMED_USER_ID = "userId" + } +} \ No newline at end of file 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 94c65e650..a20769fa4 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 @@ -14,9 +14,11 @@ import com.habitrpg.android.habitica.data.ApiClient import com.habitrpg.android.habitica.data.SocialRepository import com.habitrpg.android.habitica.data.TaskRepository import com.habitrpg.android.habitica.data.UserRepository -import com.habitrpg.android.habitica.helpers.RxErrorHandler +import com.habitrpg.android.habitica.helpers.ExceptionHandler import com.habitrpg.android.habitica.interactors.NotifyUserUseCase import com.habitrpg.android.habitica.models.user.User +import kotlinx.coroutines.MainScope +import kotlinx.coroutines.launch import javax.inject.Inject class LocalNotificationActionReceiver : BroadcastReceiver() { @@ -56,30 +58,34 @@ class LocalNotificationActionReceiver : BroadcastReceiver() { when (action) { context?.getString(R.string.accept_party_invite) -> { groupID?.let { - socialRepository.joinGroup(it).subscribe({ }, RxErrorHandler.handleEmptyError()) + MainScope().launch(ExceptionHandler.coroutine()) { + socialRepository.joinGroup(it) + } } } context?.getString(R.string.reject_party_invite) -> { groupID?.let { socialRepository.rejectGroupInvite(it) - .subscribe({ }, RxErrorHandler.handleEmptyError()) + .subscribe({ }, ExceptionHandler.rx()) } } context?.getString(R.string.accept_quest_invite) -> { - socialRepository.acceptQuest(user).subscribe({ }, RxErrorHandler.handleEmptyError()) + socialRepository.acceptQuest(user).subscribe({ }, ExceptionHandler.rx()) } context?.getString(R.string.reject_quest_invite) -> { - socialRepository.rejectQuest(user).subscribe({ }, RxErrorHandler.handleEmptyError()) + socialRepository.rejectQuest(user).subscribe({ }, ExceptionHandler.rx()) } context?.getString(R.string.accept_guild_invite) -> { groupID?.let { - socialRepository.joinGroup(it).subscribe({ }, RxErrorHandler.handleEmptyError()) + MainScope().launch(ExceptionHandler.coroutine()) { + socialRepository.joinGroup(it) + } } } context?.getString(R.string.reject_guild_invite) -> { groupID?.let { socialRepository.rejectGroupInvite(it) - .subscribe({ }, RxErrorHandler.handleEmptyError()) + .subscribe({ }, ExceptionHandler.rx()) } } context?.getString(R.string.group_message_reply) -> { @@ -91,7 +97,7 @@ class LocalNotificationActionReceiver : BroadcastReceiver() { NotificationManagerCompat.from(c).cancel(it.hashCode()) } }, - RxErrorHandler.handleEmptyError() + ExceptionHandler.rx() ) } } @@ -99,8 +105,9 @@ class LocalNotificationActionReceiver : BroadcastReceiver() { context?.getString(R.string.inbox_message_reply) -> { senderID?.let { getMessageText(context?.getString(R.string.inbox_message_reply))?.let { message -> - socialRepository.postPrivateMessage(it, message) - .subscribe({ }, RxErrorHandler.handleEmptyError()) + MainScope().launch(ExceptionHandler.coroutine()) { + socialRepository.postPrivateMessage(it, message) + } } } } @@ -109,13 +116,13 @@ class LocalNotificationActionReceiver : BroadcastReceiver() { taskRepository.taskChecked(null, it, up = true, force = false) { }.subscribe({ val pair = NotifyUserUseCase.getNotificationAndAddStatsToUserAsText( - it?.experienceDelta, - it?.healthDelta, - it?.goldDelta, - it?.manaDelta + it.experienceDelta, + it.healthDelta, + it.goldDelta, + it.manaDelta ) showToast(pair.first) - }, RxErrorHandler.handleEmptyError()) + }, ExceptionHandler.rx()) } } } 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 42bfe863f..d7071d703 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,7 +15,7 @@ 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.RxErrorHandler +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 @@ -76,7 +76,7 @@ class NotificationPublisher : BroadcastReceiver() { notify(intent, buildNotification(wasInactive, pair.second.authentication?.timestamps?.createdAt)) } }, - RxErrorHandler.handleEmptyError() + ExceptionHandler.rx() ) } else { notify(intent, buildNotification(wasInactive)) diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/receivers/TaskAlarmBootReceiver.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/receivers/TaskAlarmBootReceiver.kt index a7d527141..cb66c792e 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/receivers/TaskAlarmBootReceiver.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/receivers/TaskAlarmBootReceiver.kt @@ -5,10 +5,10 @@ import android.content.Context import android.content.Intent import android.content.SharedPreferences import com.habitrpg.android.habitica.HabiticaBaseApplication +import com.habitrpg.android.habitica.helpers.ExceptionHandler import com.habitrpg.android.habitica.helpers.TaskAlarmManager import com.habitrpg.shared.habitica.HLogger import com.habitrpg.shared.habitica.LogLevel -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.MainScope import kotlinx.coroutines.launch import javax.inject.Inject @@ -25,7 +25,7 @@ class TaskAlarmBootReceiver : BroadcastReceiver() { return } HabiticaBaseApplication.userComponent?.inject(this) - MainScope().launch(Dispatchers.Main) { + MainScope().launch(ExceptionHandler.coroutine()) { taskAlarmManager.scheduleAllSavedAlarms(sharedPreferences.getBoolean("preventDailyReminder", false)) } HLogger.log(LogLevel.INFO, this::javaClass.name, "onReceive") diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/receivers/TaskReceiver.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/receivers/TaskReceiver.kt index 470bb7ed4..1cb1c9589 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/receivers/TaskReceiver.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/receivers/TaskReceiver.kt @@ -13,7 +13,7 @@ import com.habitrpg.android.habitica.HabiticaBaseApplication import com.habitrpg.android.habitica.R import com.habitrpg.android.habitica.data.TaskRepository import com.habitrpg.android.habitica.extensions.withImmutableFlag -import com.habitrpg.android.habitica.helpers.RxErrorHandler +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.shared.habitica.models.tasks.TaskType @@ -52,7 +52,7 @@ class TaskReceiver : BroadcastReceiver() { createNotification(context, it) }, - RxErrorHandler.handleEmptyError() + ExceptionHandler.rx() ) } } 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 3430e6981..63d6d453f 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 @@ -17,7 +17,8 @@ import com.habitrpg.android.habitica.databinding.ActivityArmoireBinding 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.RxErrorHandler +import com.habitrpg.android.habitica.helpers.ExceptionHandler +import com.habitrpg.common.habitica.extensions.loadImage import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel import com.habitrpg.android.habitica.ui.views.ads.AdButton import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaBottomSheetDialog @@ -93,7 +94,7 @@ class ArmoireActivity : BaseActivity() { binding.adButton.visibility = View.INVISIBLE hasAnimatedChanges = false gold = null - }, RxErrorHandler.handleEmptyError()) + }, ExceptionHandler.rx()) ) } handler.prepare { @@ -116,7 +117,7 @@ class ArmoireActivity : BaseActivity() { finish() } binding.equipButton.setOnClickListener { - equipmentKey?.let { it1 -> inventoryRepository.equip("equipped", it1).subscribe({}, RxErrorHandler.handleEmptyError()) } + equipmentKey?.let { it1 -> inventoryRepository.equip("equipped", it1).subscribe({}, ExceptionHandler.rx()) } 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 8eac5a2fc..95bbb7b9f 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 @@ -26,10 +26,11 @@ import com.habitrpg.android.habitica.HabiticaBaseApplication import com.habitrpg.android.habitica.R import com.habitrpg.android.habitica.components.UserComponent import com.habitrpg.android.habitica.data.UserRepository -import com.habitrpg.android.habitica.extensions.subscribeWithErrorHandler +import com.habitrpg.common.habitica.extensions.getThemeColor +import com.habitrpg.common.habitica.extensions.isUsingNightModeResources import com.habitrpg.android.habitica.extensions.updateStatusBarColor import com.habitrpg.android.habitica.helpers.NotificationsManager -import com.habitrpg.android.habitica.helpers.RxErrorHandler +import com.habitrpg.android.habitica.helpers.ExceptionHandler import com.habitrpg.android.habitica.interactors.ShowNotificationInteractor import com.habitrpg.android.habitica.proxy.AnalyticsManager import com.habitrpg.android.habitica.ui.helpers.ToolbarColorHelper @@ -38,6 +39,7 @@ 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 import javax.inject.Inject @@ -94,16 +96,16 @@ abstract class BaseActivity : AppCompatActivity() { injectActivity(HabiticaBaseApplication.userComponent) setContentView(getContentView()) compositeSubscription = CompositeDisposable() - compositeSubscription.add( - notificationsManager.displayNotificationEvents.subscribe( - { - if (ShowNotificationInteractor(this, lifecycleScope).handleNotification(it)) { - compositeSubscription.add(userRepository.retrieveUser(false, true).subscribeWithErrorHandler {}) + compositeSubscription.add(notificationsManager.displayNotificationEvents.subscribe( + { + if (ShowNotificationInteractor(this, lifecycleScope).handleNotification(it)) { + lifecycleScope.launch(ExceptionHandler.coroutine()) { + userRepository.retrieveUser(false, true) } - }, - RxErrorHandler.handleEmptyError() - ) - ) + } + }, + ExceptionHandler.rx() + )) } override fun onRestart() { 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 db6cffa9b..4889bfcb8 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 @@ -23,8 +23,8 @@ import com.habitrpg.android.habitica.data.ChallengeRepository import com.habitrpg.android.habitica.data.SocialRepository 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.RxErrorHandler import com.habitrpg.android.habitica.models.social.Challenge import com.habitrpg.android.habitica.models.social.Group import com.habitrpg.android.habitica.models.tasks.Task @@ -41,6 +41,7 @@ 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 import kotlinx.coroutines.launch import java.util.UUID import javax.inject.Inject @@ -156,7 +157,7 @@ class ChallengeFormActivity : BaseActivity() { { throwable -> dialog?.dismiss() savingInProgress = false - RxErrorHandler.reportError(throwable) + ExceptionHandler.reportError(throwable) } ) ) @@ -337,36 +338,26 @@ class ChallengeFormActivity : BaseActivity() { } locationAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item) - compositeSubscription.add( - socialRepository.getUserGroups("guild").zipWith( - userRepository.getUserFlowable() - .map { it.party?.id ?: "" } - .distinctUntilChanged() - .flatMap { - if (it.isBlank()) { - return@flatMap Flowable.empty() - } - socialRepository.retrieveGroup(it) - } - ) { user, groups -> Pair(user, groups) } - .subscribe( - { groups -> - val mutableGroups = groups.first.toMutableList() - if (groups.first.firstOrNull { it.id == "00000000-0000-4000-A000-000000000000" } == null) { - val tavern = Group() - tavern.id = "00000000-0000-4000-A000-000000000000" - tavern.name = getString(R.string.public_challenge) - mutableGroups.add(0, tavern) - } - if (groups.second != null) { - mutableGroups.add(groups.second) - } - locationAdapter.clear() - locationAdapter.addAll(mutableGroups) - }, - RxErrorHandler.handleEmptyError() - ) - ) + lifecycleScope.launch(ExceptionHandler.coroutine()) { + 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) + } else { + null + } + if (groups.firstOrNull { it.id == "00000000-0000-4000-A000-000000000000" } == null) { + val tavern = Group() + tavern.id = "00000000-0000-4000-A000-000000000000" + tavern.name = getString(R.string.public_challenge) + groups.add(0, tavern) + } + if (party != null) { + groups.add(party) + } + locationAdapter.clear() + locationAdapter.addAll(groups) + } binding.challengeLocationSpinner.adapter = locationAdapter binding.challengeLocationSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener { @@ -399,7 +390,7 @@ class ChallengeFormActivity : BaseActivity() { addReward.text -> openNewTaskActivity(TaskType.REWARD, null) } }, - RxErrorHandler.handleEmptyError() + ExceptionHandler.rx() ) ) @@ -438,7 +429,7 @@ class ChallengeFormActivity : BaseActivity() { } checkPrizeAndMinimumForTavern() }, - RxErrorHandler.handleEmptyError() + ExceptionHandler.rx() ) challengeRepository.getChallengeTasks(it).subscribe( { tasks -> @@ -446,7 +437,7 @@ class ChallengeFormActivity : BaseActivity() { addOrUpdateTaskInList(task, true) } }, - RxErrorHandler.handleEmptyError() + ExceptionHandler.rx() ) } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/ClassSelectionActivity.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/ClassSelectionActivity.kt index b84ef5c54..492cce57c 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/ClassSelectionActivity.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/ClassSelectionActivity.kt @@ -8,24 +8,25 @@ import android.view.MenuItem import android.view.View import android.widget.TextView import androidx.core.content.ContextCompat +import androidx.lifecycle.lifecycleScope import androidx.navigation.navArgs import com.habitrpg.android.habitica.R import com.habitrpg.android.habitica.components.UserComponent import com.habitrpg.android.habitica.databinding.ActivityClassSelectionBinding -import com.habitrpg.android.habitica.helpers.RxErrorHandler +import com.habitrpg.android.habitica.helpers.ExceptionHandler import com.habitrpg.android.habitica.models.user.Gear import com.habitrpg.android.habitica.models.user.Items import com.habitrpg.android.habitica.models.user.Outfit import com.habitrpg.android.habitica.models.user.Preferences import com.habitrpg.android.habitica.models.user.User import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel +import com.habitrpg.android.habitica.ui.views.HabiticaIconsHelper import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaProgressDialog -import com.habitrpg.android.habitica.ui.views.HabiticaIconsHelper -import io.reactivex.rxjava3.functions.Consumer +import kotlinx.coroutines.launch import javax.inject.Inject -class ClassSelectionActivity : BaseActivity(), Consumer { +class ClassSelectionActivity : BaseActivity() { @Inject lateinit var userViewModel: MainUserViewModel @@ -85,10 +86,10 @@ class ClassSelectionActivity : BaseActivity(), Consumer { } if (!isInitialSelection) { - compositeSubscription.add( + lifecycleScope.launch(ExceptionHandler.coroutine()) { userRepository.changeClass() - .subscribe({ classWasUnset = true }, RxErrorHandler.handleEmptyError()) - ) + classWasUnset + } } binding.healerWrapper.setOnClickListener { newClass = "healer" } @@ -254,20 +255,26 @@ class ClassSelectionActivity : BaseActivity(), Consumer { private fun optOutOfClasses() { shouldFinish = true this.displayProgressDialog(getString(R.string.opting_out_progress)) - compositeSubscription.add(userRepository.disableClasses().subscribe(this, RxErrorHandler.handleEmptyError())) + lifecycleScope.launch(ExceptionHandler.coroutine()) { + userRepository.disableClasses() + dismiss() + } } private fun selectClass(selectedClass: String) { shouldFinish = true this.displayProgressDialog(getString(R.string.changing_class_progress)) - compositeSubscription.add(userRepository.changeClass(selectedClass).subscribe(this, RxErrorHandler.handleEmptyError())) + lifecycleScope.launch(ExceptionHandler.coroutine()) { + userRepository.changeClass(selectedClass) + dismiss() + } } private fun displayProgressDialog(progressText: String) { HabiticaProgressDialog.show(this, progressText) } - override fun accept(user: User) { + private fun dismiss() { if (shouldFinish == true) { progressDialog?.dismiss() finish() diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/DeathActivity.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/DeathActivity.kt index ae13256f3..70c204ba7 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/DeathActivity.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/DeathActivity.kt @@ -15,12 +15,13 @@ import com.habitrpg.android.habitica.extensions.observeOnce 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.RxErrorHandler +import com.habitrpg.android.habitica.helpers.ExceptionHandler import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel import com.habitrpg.android.habitica.ui.views.HabiticaIconsHelper import com.habitrpg.android.habitica.ui.views.ads.AdButton import com.habitrpg.common.habitica.helpers.Animations import com.plattysoft.leonids.ParticleSystem +import kotlinx.coroutines.launch import javax.inject.Inject class DeathActivity: BaseActivity() { @@ -59,7 +60,7 @@ class DeathActivity: BaseActivity() { compositeSubscription.add( userRepository.updateUser("stats.hp", 1).subscribe({ finish() - }, RxErrorHandler.handleEmptyError()) + }, ExceptionHandler.rx()) ) } handler.prepare { @@ -80,9 +81,10 @@ class DeathActivity: BaseActivity() { binding.restartButton.setOnClickListener { binding.restartButton.isEnabled = false - userRepository.revive().subscribe({ + lifecycleScope.launch(ExceptionHandler.coroutine()) { + userRepository.revive() finish() - }, RxErrorHandler.handleEmptyError()) + } } startAnimating() } 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 5b7855516..1fd8b47ce 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 @@ -13,6 +13,7 @@ import android.widget.TableRow import android.widget.TextView import androidx.core.content.ContextCompat 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.ApiClient @@ -20,8 +21,8 @@ import com.habitrpg.android.habitica.data.InventoryRepository import com.habitrpg.android.habitica.data.SocialRepository import com.habitrpg.android.habitica.databinding.ActivityFullProfileBinding 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.RxErrorHandler import com.habitrpg.android.habitica.helpers.UserStatComputer import com.habitrpg.android.habitica.models.Achievement import com.habitrpg.android.habitica.models.inventory.Equipment @@ -82,7 +83,12 @@ class FullProfileActivity : BaseActivity() { setTitle(R.string.profile_loading_data) - compositeSubscription.add(socialRepository.getMember(this.userID).subscribe({ this.updateView(it) }, RxErrorHandler.handleEmptyError())) + lifecycleScope.launch(ExceptionHandler.coroutine()) { + val member = socialRepository.retrieveMember(userID) + if (member != null) { + updateView(member) + } + } avatarWithBars = AvatarWithBarsViewModel(this, binding.avatarWithBars) binding.avatarWithBars.root.setBackgroundColor(ContextCompat.getColor(this, R.color.transparent)) @@ -96,15 +102,13 @@ class FullProfileActivity : BaseActivity() { 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))) } - compositeSubscription.add( - userRepository.getUserFlowable().subscribe( - { - blocks = it.inbox?.blocks ?: listOf() + lifecycleScope.launch(ExceptionHandler.coroutine()) { + userRepository.getUser() + .collect { + blocks = it?.inbox?.blocks ?: listOf() binding.blockedDisclaimerView.visibility = if (isUserBlocked()) View.VISIBLE else View.GONE - }, - RxErrorHandler.handleEmptyError() - ) - ) + } + } } override fun onDestroy() { @@ -168,13 +172,14 @@ class FullProfileActivity : BaseActivity() { private fun useBlock() { compositeSubscription.add( - socialRepository.blockMember(userID).flatMap { - userRepository.retrieveUser() - }.subscribe( + socialRepository.blockMember(userID).subscribe( { - invalidateOptionsMenu() + lifecycleScope.launch(ExceptionHandler.coroutine()) { + userRepository.retrieveUser() + invalidateOptionsMenu() + } }, - RxErrorHandler.handleEmptyError() + ExceptionHandler.rx() ) ) } @@ -227,16 +232,16 @@ class FullProfileActivity : BaseActivity() { avatarWithBars?.updateData(user) - compositeSubscription.add(loadItemDataByOutfit(user.equipped).subscribe({ gear -> this.gotGear(gear, user) }, RxErrorHandler.handleEmptyError())) + compositeSubscription.add(loadItemDataByOutfit(user.equipped).subscribe({ gear -> this.gotGear(gear, user) }, ExceptionHandler.rx())) if (user.preferences?.costume == true) { - compositeSubscription.add(loadItemDataByOutfit(user.costume).subscribe({ this.gotCostume(it) }, RxErrorHandler.handleEmptyError())) + compositeSubscription.add(loadItemDataByOutfit(user.costume).subscribe({ this.gotCostume(it) }, ExceptionHandler.rx())) } else { binding.costumeCard.visibility = View.GONE } // Load the members achievements now - compositeSubscription.add(socialRepository.getMemberAchievements(this.userID).subscribe({ this.fillAchievements(it) }, RxErrorHandler.handleEmptyError())) + compositeSubscription.add(socialRepository.getMemberAchievements(this.userID).subscribe({ this.fillAchievements(it) }, ExceptionHandler.rx())) } private fun updatePetsMountsView(user: Member) { diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/GiftGemsActivity.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/GiftGemsActivity.kt index 873598e5e..8917adaa1 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/GiftGemsActivity.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/GiftGemsActivity.kt @@ -3,6 +3,7 @@ package com.habitrpg.android.habitica.ui.activities import android.os.Bundle import android.view.View import androidx.fragment.app.Fragment +import androidx.lifecycle.lifecycleScope import androidx.navigation.navArgs import androidx.viewpager2.adapter.FragmentStateAdapter import com.google.android.material.tabs.TabLayoutMediator @@ -11,12 +12,14 @@ import com.habitrpg.android.habitica.components.UserComponent import com.habitrpg.android.habitica.data.SocialRepository import com.habitrpg.android.habitica.databinding.ActivityGiftGemsBinding 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.RxErrorHandler import com.habitrpg.android.habitica.models.members.Member import com.habitrpg.android.habitica.ui.fragments.purchases.GiftBalanceGemsFragment import com.habitrpg.android.habitica.ui.fragments.purchases.GiftPurchaseGemsFragment import com.habitrpg.android.habitica.ui.views.CurrencyView +import kotlinx.coroutines.flow.firstOrNull +import kotlinx.coroutines.launch import javax.inject.Inject class GiftGemsActivity : PurchaseActivity() { @@ -74,27 +77,17 @@ class GiftGemsActivity : PurchaseActivity() { setViewPagerAdapter() - compositeSubscription.add( - socialRepository.getMember(giftedUsername ?: giftedUserID).firstElement().subscribe( - { - giftedMember = it - giftedUserID = it.id - giftedUsername = it.username - purchaseFragment?.giftedMember = it - balanceFragment?.giftedMember = it - }, - RxErrorHandler.handleEmptyError() - ) - ) + lifecycleScope.launch(ExceptionHandler.coroutine()) { + val member = socialRepository.retrieveMember(giftedUsername ?: giftedUserID) ?: return@launch + giftedMember = member + giftedUserID = member.id + giftedUsername = member.username + purchaseFragment?.giftedMember = member + balanceFragment?.giftedMember = member - compositeSubscription.add( - userRepository.getUserFlowable().subscribe( - { - currencyView.value = it.gemCount.toDouble() - }, - RxErrorHandler.handleEmptyError() - ) - ) + val user = userRepository.getUser().firstOrNull() + currencyView.value = user?.gemCount?.toDouble() ?: 0.0 + } } private fun setViewPagerAdapter() { diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/GiftSubscriptionActivity.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/GiftSubscriptionActivity.kt index 365bbfd12..a429ade2c 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/GiftSubscriptionActivity.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/GiftSubscriptionActivity.kt @@ -3,6 +3,7 @@ package com.habitrpg.android.habitica.ui.activities import android.annotation.SuppressLint import android.os.Bundle import android.view.View +import androidx.lifecycle.lifecycleScope import androidx.navigation.navArgs import com.android.billingclient.api.SkuDetails import com.habitrpg.android.habitica.R @@ -10,15 +11,15 @@ import com.habitrpg.android.habitica.components.UserComponent import com.habitrpg.android.habitica.data.SocialRepository import com.habitrpg.android.habitica.databinding.ActivityGiftSubscriptionBinding 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.RxErrorHandler import com.habitrpg.android.habitica.ui.views.subscriptions.SubscriptionOptionView -import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext +import javax.inject.Inject class GiftSubscriptionActivity : PurchaseActivity() { @@ -70,20 +71,15 @@ class GiftSubscriptionActivity : PurchaseActivity() { binding.subscriptionButton.setOnClickListener { selectedSubscriptionSku?.let { sku -> purchaseSubscription(sku) } } - - compositeSubscription.add( - socialRepository.getMember(giftedUsername ?: giftedUserID).subscribe( - { - binding.avatarView.setAvatar(it) - binding.displayNameTextView.username = it.profile?.name - binding.displayNameTextView.tier = it.contributor?.level ?: 0 - binding.usernameTextView.text = "@${it.username}" - giftedUserID = it.id - giftedUsername = it.username - }, - RxErrorHandler.handleEmptyError() - ) - ) + lifecycleScope.launch(ExceptionHandler.coroutine()) { + val member = socialRepository.retrieveMember(giftedUsername ?: giftedUserID) ?: return@launch + binding.avatarView.setAvatar(member) + binding.displayNameTextView.username = member.profile?.name + binding.displayNameTextView.tier = member.contributor?.level ?: 0 + binding.usernameTextView.text = "@${member.username}" + giftedUserID = member.id + giftedUsername = member.username + } if (appConfigManager.activePromo()?.identifier == "g1g1") { binding.giftSubscriptionContainer.visibility = View.VISIBLE @@ -94,7 +90,7 @@ class GiftSubscriptionActivity : PurchaseActivity() { override fun onStart() { super.onStart() - CoroutineScope(Dispatchers.IO).launch { + CoroutineScope(Dispatchers.IO).launch(ExceptionHandler.coroutine()) { val subscriptions = purchaseHandler.getAllGiftSubscriptionProducts() skus = subscriptions withContext(Dispatchers.Main) { diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/GuidelinesActivity.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/GuidelinesActivity.kt index 0dd141463..99e4c2e17 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/GuidelinesActivity.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/GuidelinesActivity.kt @@ -5,7 +5,7 @@ import android.view.MenuItem import android.widget.TextView import com.habitrpg.android.habitica.R import com.habitrpg.android.habitica.components.UserComponent -import com.habitrpg.android.habitica.helpers.RxErrorHandler +import com.habitrpg.android.habitica.helpers.ExceptionHandler import com.habitrpg.common.habitica.helpers.setMarkdown import okhttp3.Call import okhttp3.Callback @@ -31,7 +31,7 @@ class GuidelinesActivity : BaseActivity() { val request = Request.Builder().url("https://s3.amazonaws.com/habitica-assets/mobileApp/endpoint/community-guidelines.md").build() client.newCall(request).enqueue(object : Callback { override fun onFailure(call: Call, e: IOException) { - RxErrorHandler.reportError(e) + ExceptionHandler.reportError(e) } @Throws(IOException::class) 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 269cef412..392a71189 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 @@ -12,7 +12,7 @@ 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.WidgetConfigureHabitButtonBinding -import com.habitrpg.android.habitica.helpers.RxErrorHandler +import com.habitrpg.android.habitica.helpers.ExceptionHandler import com.habitrpg.android.habitica.modules.AppModule import com.habitrpg.android.habitica.ui.adapter.SkillTasksRecyclerViewAdapter import com.habitrpg.android.habitica.widget.HabitButtonWidgetProvider @@ -77,11 +77,11 @@ class HabitButtonWidgetActivity : BaseActivity() { adapter = SkillTasksRecyclerViewAdapter() adapter?.getTaskSelectionEvents()?.subscribe( { task -> taskSelected(task.id) }, - RxErrorHandler.handleEmptyError() + ExceptionHandler.rx() )?.let { compositeSubscription.add(it) } binding.recyclerView.adapter = adapter - CoroutineScope(Dispatchers.Main + job).launch { + CoroutineScope(Dispatchers.Main + job).launch(ExceptionHandler.coroutine()) { adapter?.data = taskRepository.getTasks(TaskType.HABIT, userId, emptyArray()).firstOrNull() ?: listOf() } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/IntroActivity.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/IntroActivity.kt index 8f242f2ed..6610a3f62 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/IntroActivity.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/IntroActivity.kt @@ -10,14 +10,16 @@ import androidx.core.content.res.ResourcesCompat import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentManager import androidx.fragment.app.FragmentStatePagerAdapter +import androidx.lifecycle.lifecycleScope import androidx.viewpager.widget.ViewPager import com.habitrpg.android.habitica.R import com.habitrpg.android.habitica.components.UserComponent import com.habitrpg.android.habitica.data.ContentRepository import com.habitrpg.android.habitica.databinding.ActivityIntroBinding -import com.habitrpg.android.habitica.helpers.RxErrorHandler +import com.habitrpg.android.habitica.helpers.ExceptionHandler import com.habitrpg.android.habitica.ui.fragments.setup.IntroFragment import com.viewpagerindicator.IconPagerAdapter +import kotlinx.coroutines.launch import javax.inject.Inject class IntroActivity : BaseActivity(), View.OnClickListener, ViewPager.OnPageChangeListener { @@ -44,7 +46,9 @@ class IntroActivity : BaseActivity(), View.OnClickListener, ViewPager.OnPageChan binding.skipButton.setOnClickListener(this) binding.finishButton.setOnClickListener(this) - compositeSubscription.add(contentRepository.retrieveContent().subscribe({ }, RxErrorHandler.handleEmptyError())) + lifecycleScope.launch(ExceptionHandler.coroutine()) { + contentRepository.retrieveContent() + } window.statusBarColor = ContextCompat.getColor(this, R.color.black_20_alpha) window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS) 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 813f1ba2d..6ea4c7f23 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 @@ -41,7 +41,7 @@ import com.habitrpg.android.habitica.extensions.addOkButton 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.RxErrorHandler +import com.habitrpg.android.habitica.helpers.ExceptionHandler import com.habitrpg.android.habitica.models.user.User import com.habitrpg.android.habitica.ui.helpers.dismissKeyboard import com.habitrpg.android.habitica.ui.viewmodels.AuthenticationViewModel @@ -86,7 +86,7 @@ class LoginActivity : BaseActivity() { { handleAuthResponse(it) }, { hideProgress() - RxErrorHandler.reportError(it) + ExceptionHandler.reportError(it) } ) } else { @@ -101,7 +101,7 @@ class LoginActivity : BaseActivity() { { handleAuthResponse(it) }, { hideProgress() - RxErrorHandler.reportError(it) + ExceptionHandler.reportError(it) Log.d("LoginActivity", ": ${it.message}", it) } ) @@ -261,12 +261,12 @@ class LoginActivity : BaseActivity() { } catch (e: Exception) { // Wearable API is not available on this device. } - compositeSubscription.add( - userRepository.retrieveUser(true) - .subscribe({ - handleAuthResponse(it, response.newUser) - }, RxErrorHandler.handleEmptyError()) - ) + lifecycleScope.launch(ExceptionHandler.coroutine()) { + val user = userRepository.retrieveUser(true) + if (user != null) { + handleAuthResponse(user, response.newUser) + } + } } private fun handleAuthResponse(user: User, isNew: Boolean) { @@ -276,20 +276,15 @@ class LoginActivity : BaseActivity() { if (isRegistering) { FirebaseAnalytics.getInstance(this).logEvent("user_registered", null) } - compositeSubscription.add( - userRepository.retrieveUser(withTasks = true, forced = true) - .subscribe( - { - if (isNew) { - this.startSetupActivity() - } else { - this.startMainActivity() - AmplitudeManager.sendEvent("login", AmplitudeManager.EVENT_CATEGORY_BEHAVIOUR, AmplitudeManager.EVENT_HITTYPE_EVENT) - } - }, - RxErrorHandler.handleEmptyError() - ) - ) + lifecycleScope.launch(ExceptionHandler.coroutine()) { + userRepository.retrieveUser(true, true) + if (isNew) { + startSetupActivity() + } else { + startMainActivity() + AmplitudeManager.sendEvent("login", AmplitudeManager.EVENT_CATEGORY_BEHAVIOUR, AmplitudeManager.EVENT_HITTYPE_EVENT) + } + } } override fun onOptionsItemSelected(item: MenuItem): Boolean { @@ -455,7 +450,7 @@ 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() }, RxErrorHandler.handleEmptyError()) + userRepository.sendPasswordResetEmail(input.text.toString()).subscribe({ showPasswordEmailConfirmation() }, ExceptionHandler.rx()) } 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 a40af0bb9..3de91467f 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 @@ -37,9 +37,9 @@ 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 +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.RxErrorHandler import com.habitrpg.android.habitica.helpers.SoundManager import com.habitrpg.android.habitica.interactors.CheckClassSelectionUseCase import com.habitrpg.android.habitica.interactors.DisplayItemDropUseCase @@ -68,6 +68,7 @@ import com.habitrpg.shared.habitica.models.responses.TaskScoringResult import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.launch import java.util.Date import javax.inject.Inject @@ -130,8 +131,8 @@ open class MainActivity : BaseActivity(), SnackbarActivity { } try { launchTrace = FirebasePerformance.getInstance().newTrace("MainActivityLaunch") - } catch (e: Exception) { - // pass + } catch (e: IllegalStateException) { + ExceptionHandler.reportError(e) } launchTrace?.start() super.onCreate(savedInstanceState) @@ -393,16 +394,13 @@ open class MainActivity : BaseActivity(), SnackbarActivity { val quest = user.party?.quest if (quest?.completed?.isNotBlank() == true) { - compositeSubscription.add( - inventoryRepository.getQuestContent(user.party?.quest?.completed ?: "").firstElement().subscribe( - { - QuestCompletedDialog.showWithQuest(this, it) - - viewModel.updateUser("party.quest.completed", "") - }, - RxErrorHandler.handleEmptyError() - ) - ) + lifecycleScope.launch(ExceptionHandler.coroutine()) { + val questContent = inventoryRepository.getQuestContent(user.party?.quest?.completed ?: "").firstOrNull() + if (questContent != null) { + QuestCompletedDialog.showWithQuest(this@MainActivity, questContent) + } + viewModel.updateUser("party.quest.completed", "") + } } if (user.flags?.welcomed == false) { @@ -451,14 +449,14 @@ open class MainActivity : BaseActivity(), SnackbarActivity { viewModel.user.value, data.experienceDelta, data.healthDelta, data.goldDelta, data.manaDelta, damageValue, data.hasLeveledUp, data.level ) ) - .subscribe({ }, RxErrorHandler.handleEmptyError()) + .subscribe({ }, ExceptionHandler.rx()) ) } val showItemsFound = userQuestStatus == UserQuestStatus.QUEST_COLLECT compositeSubscription.add( displayItemDropUseCase.observable(DisplayItemDropUseCase.RequestValues(data, this, snackbarContainer, showItemsFound)) - .subscribe({ }, RxErrorHandler.handleEmptyError()) + .subscribe({ }, ExceptionHandler.rx()) ) } @@ -517,7 +515,7 @@ open class MainActivity : BaseActivity(), SnackbarActivity { startActivity(intent) } } catch (e: PackageManager.NameNotFoundException) { - RxErrorHandler.reportError(e) + ExceptionHandler.reportError(e) } } } 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 7bef8b85f..cc972c6cc 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 @@ -10,7 +10,7 @@ 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.RxErrorHandler +import com.habitrpg.android.habitica.helpers.ExceptionHandler import com.habitrpg.common.habitica.helpers.setMarkdown import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers import io.reactivex.rxjava3.schedulers.Schedulers @@ -76,7 +76,7 @@ class MaintenanceActivity : BaseActivity() { finish() } }, - RxErrorHandler.handleEmptyError() + ExceptionHandler.rx() ) ) } 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 95a444322..819453959 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 @@ -13,15 +13,17 @@ import android.widget.RatingBar import android.widget.TextView import androidx.activity.viewModels 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.InventoryRepository 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.RxErrorHandler -import com.habitrpg.common.habitica.models.Notification +import com.habitrpg.android.habitica.helpers.ExceptionHandler import com.habitrpg.android.habitica.models.inventory.QuestContent +import com.habitrpg.android.habitica.ui.viewmodels.NotificationsViewModel +import com.habitrpg.common.habitica.models.Notification import com.habitrpg.common.habitica.models.notifications.GroupTaskApprovedData import com.habitrpg.common.habitica.models.notifications.GroupTaskNeedsWorkData import com.habitrpg.common.habitica.models.notifications.GroupTaskRequiresApprovalData @@ -31,7 +33,8 @@ import com.habitrpg.common.habitica.models.notifications.NewStuffData import com.habitrpg.common.habitica.models.notifications.PartyInvitationData import com.habitrpg.common.habitica.models.notifications.QuestInvitationData import com.habitrpg.common.habitica.models.notifications.UnallocatedPointsData -import com.habitrpg.android.habitica.ui.viewmodels.NotificationsViewModel +import kotlinx.coroutines.flow.firstOrNull +import kotlinx.coroutines.launch import javax.inject.Inject class NotificationsActivity : BaseActivity(), androidx.swiperefreshlayout.widget.SwipeRefreshLayout.OnRefreshListener { @@ -68,7 +71,7 @@ class NotificationsActivity : BaseActivity(), androidx.swiperefreshlayout.widget this.setNotifications(it) viewModel.markNotificationsAsSeen(it) }, - RxErrorHandler.handleEmptyError() + ExceptionHandler.rx() ) ) @@ -90,14 +93,10 @@ class NotificationsActivity : BaseActivity(), androidx.swiperefreshlayout.widget override fun onRefresh() { binding.notificationsRefreshLayout.isRefreshing = true - compositeSubscription.add( - viewModel.refreshNotifications().subscribe( - { - binding.notificationsRefreshLayout.isRefreshing = false - }, - RxErrorHandler.handleEmptyError() - ) - ) + lifecycleScope.launch(ExceptionHandler.coroutine()) { + viewModel.refreshNotifications() + binding.notificationsRefreshLayout.isRefreshing = false + } } private fun setNotifications(notifications: List) { @@ -314,16 +313,12 @@ class NotificationsActivity : BaseActivity(), androidx.swiperefreshlayout.widget // hide view until we have loaded quest data and populated the values view?.visibility = View.GONE - compositeSubscription.add( - inventoryRepository.getQuestContent(data?.questKey ?: "") - .firstElement() - .subscribe( - { - updateQuestInvitationView(view, it) - }, - RxErrorHandler.handleEmptyError() - ) - ) + lifecycleScope.launch(ExceptionHandler.coroutine()) { + val questContent = inventoryRepository.getQuestContent(data?.questKey ?: "").firstOrNull() + if (questContent != null) { + updateQuestInvitationView(view, questContent) + } + } return view } 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 dcffeef5a..3d70206cb 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 @@ -15,7 +15,7 @@ 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.RxErrorHandler +import com.habitrpg.android.habitica.helpers.ExceptionHandler import com.habitrpg.android.habitica.ui.helpers.dismissKeyboard import com.habitrpg.common.habitica.helpers.setMarkdown import javax.inject.Inject @@ -98,7 +98,7 @@ class ReportMessageActivity : BaseActivity() { .doOnError { isReporting = false } .subscribe( { finish() }, - RxErrorHandler.handleEmptyError() + ExceptionHandler.rx() ) } } 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 c692bb219..99b23dbc3 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 @@ -14,23 +14,25 @@ import androidx.core.content.edit import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentManager import androidx.fragment.app.FragmentPagerAdapter +import androidx.lifecycle.lifecycleScope import androidx.preference.PreferenceManager import androidx.viewpager.widget.ViewPager import com.habitrpg.android.habitica.R -import com.habitrpg.common.habitica.api.HostConfig import com.habitrpg.android.habitica.components.UserComponent import com.habitrpg.android.habitica.data.ApiClient import com.habitrpg.android.habitica.data.InventoryRepository 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.RxErrorHandler +import com.habitrpg.android.habitica.helpers.ExceptionHandler 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 import com.habitrpg.android.habitica.ui.fragments.setup.WelcomeFragment +import com.habitrpg.common.habitica.api.HostConfig import com.viewpagerindicator.IconPagerAdapter import io.reactivex.rxjava3.core.BackpressureStrategy +import kotlinx.coroutines.launch import java.util.Calendar import java.util.Locale import javax.inject.Inject @@ -69,8 +71,12 @@ class SetupActivity : BaseActivity(), ViewPager.OnPageChangeListener { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - compositeSubscription.add(userRepository.getUserFlowable().subscribe({ this.onUserReceived(it) }, RxErrorHandler.handleEmptyError())) - compositeSubscription.add(userRepository.retrieveUser().subscribe({}, RxErrorHandler.handleEmptyError())) + lifecycleScope.launch(ExceptionHandler.coroutine()) { + userRepository.getUser().collect { onUserReceived(it) } + } + lifecycleScope.launch(ExceptionHandler.coroutine()) { + userRepository.retrieveUser(true, true) + } val additionalData = HashMap() additionalData["status"] = "displayed" AmplitudeManager.sendEvent("setup", AmplitudeManager.EVENT_CATEGORY_BEHAVIOUR, AmplitudeManager.EVENT_HITTYPE_EVENT, additionalData) @@ -80,7 +86,7 @@ class SetupActivity : BaseActivity(), ViewPager.OnPageChangeListener { if (language == currentDeviceLanguage) { compositeSubscription.add( apiClient.registrationLanguage(currentDeviceLanguage) - .subscribe({ }, RxErrorHandler.handleEmptyError()) + .subscribe({ }, ExceptionHandler.rx()) ) } } @@ -135,7 +141,7 @@ class SetupActivity : BaseActivity(), ViewPager.OnPageChangeListener { this.completedSetup = true createdTasks = true newTasks?.let { - this.taskRepository.createTasks(it).subscribe({ onUserReceived(user) }, RxErrorHandler.handleEmptyError()) + this.taskRepository.createTasks(it).subscribe({ onUserReceived(user) }, ExceptionHandler.rx()) } } else if (binding.viewPager.currentItem == 0) { @@ -211,7 +217,7 @@ class SetupActivity : BaseActivity(), ViewPager.OnPageChangeListener { } startMainActivity() }, - RxErrorHandler.handleEmptyError() + ExceptionHandler.rx() ) ) return @@ -235,7 +241,7 @@ class SetupActivity : BaseActivity(), ViewPager.OnPageChangeListener { compositeSubscription.add( userRepository.updateUser("profile.name", displayName) .flatMap { userRepository.updateLoginName(username).toFlowable() } - .subscribe({ }, RxErrorHandler.handleEmptyError()) + .subscribe({ }, ExceptionHandler.rx()) ) } 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 83dc310d1..4e158006d 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 @@ -10,9 +10,13 @@ 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.ActivitySkillMembersBinding -import com.habitrpg.android.habitica.helpers.RxErrorHandler +import com.habitrpg.android.habitica.helpers.ExceptionHandler import com.habitrpg.android.habitica.ui.adapter.social.PartyMemberRecyclerViewAdapter import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel +import kotlinx.coroutines.flow.filterNotNull +import kotlinx.coroutines.flow.flatMapConcat +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch import javax.inject.Inject @@ -54,16 +58,16 @@ class SkillMemberActivity : BaseActivity() { setResult(Activity.RESULT_OK, resultIntent) finish() }, - RxErrorHandler.handleEmptyError() + ExceptionHandler.rx() )?.let { compositeSubscription.add(it) } binding.recyclerView.adapter = viewAdapter - val user = userViewModel.user.value - lifecycleScope.launch { - socialRepository.getGroupMembers(user?.party?.id ?: "") - .collect { - viewAdapter?.data = it - } + lifecycleScope.launch(ExceptionHandler.coroutine()) { + userRepository.getUser() + .map { it?.party?.id } + .filterNotNull() + .flatMapLatest { socialRepository.getGroupMembers(it) } + .collect { viewAdapter?.data = it } } } } 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 013153777..176fe5364 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,7 +13,7 @@ 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.RxErrorHandler +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 @@ -60,7 +60,7 @@ class SkillTasksActivity : BaseActivity() { 1 -> TaskType.DAILY else -> TaskType.TODO } - compositeSubscription.add(fragment.getTaskSelectionEvents().subscribe({ task -> taskSelected(task) }, RxErrorHandler.handleEmptyError())) + compositeSubscription.add(fragment.getTaskSelectionEvents().subscribe({ task -> taskSelected(task) }, ExceptionHandler.rx())) 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 42e13b8b6..70fe0fc0a 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 @@ -22,6 +22,7 @@ import androidx.core.os.bundleOf import androidx.core.view.children import androidx.core.view.forEachIndexed import androidx.core.widget.NestedScrollView +import androidx.lifecycle.lifecycleScope import com.habitrpg.android.habitica.R import com.habitrpg.android.habitica.components.UserComponent import com.habitrpg.android.habitica.data.ChallengeRepository @@ -30,7 +31,9 @@ import com.habitrpg.android.habitica.data.TaskRepository import com.habitrpg.android.habitica.databinding.ActivityTaskFormBinding import com.habitrpg.android.habitica.extensions.OnChangeTextWatcher import com.habitrpg.android.habitica.extensions.addCancelButton -import com.habitrpg.android.habitica.helpers.RxErrorHandler +import com.habitrpg.common.habitica.extensions.dpToPx +import com.habitrpg.common.habitica.extensions.getThemeColor +import com.habitrpg.android.habitica.helpers.ExceptionHandler import com.habitrpg.android.habitica.helpers.TaskAlarmManager import com.habitrpg.android.habitica.models.Tag import com.habitrpg.android.habitica.models.social.Challenge @@ -46,6 +49,8 @@ import com.habitrpg.shared.habitica.models.tasks.HabitResetOption import com.habitrpg.shared.habitica.models.tasks.TaskType import io.realm.RealmList import java.util.Date +import kotlinx.coroutines.launch +import java.util.* import javax.inject.Inject class TaskFormActivity : BaseActivity() { @@ -160,7 +165,7 @@ class TaskFormActivity : BaseActivity() { tags = it setTagViews() }, - RxErrorHandler.handleEmptyError() + ExceptionHandler.rx() ) ) userViewModel.user.observe(this) { @@ -216,12 +221,12 @@ class TaskFormActivity : BaseActivity() { binding.challengeNameView.visibility = View.VISIBLE disableEditingForUneditableFieldsInChallengeTask() }, - RxErrorHandler.handleEmptyError() + ExceptionHandler.rx() ) ) } }, - RxErrorHandler.handleEmptyError() + ExceptionHandler.rx() ) ) } @@ -582,7 +587,7 @@ 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({ }, RxErrorHandler.handleEmptyError()) } + task?.id?.let { taskRepository.deleteTask(it).subscribe({ }, ExceptionHandler.rx()) } finish() } alert.addCancelButton() @@ -602,12 +607,14 @@ class TaskFormActivity : BaseActivity() { compositeSubscription.add( challengeRepository.leaveChallenge(it, "keep-all") .flatMap { taskRepository.deleteTask(task?.id ?: "") } - .flatMap { userRepository.retrieveUser(true, true) } .subscribe( { + lifecycleScope.launch(ExceptionHandler.coroutine()) { + userRepository.retrieveUser(true, true) + } finish() }, - RxErrorHandler.handleEmptyError() + ExceptionHandler.rx() ) ) } @@ -616,12 +623,14 @@ class TaskFormActivity : BaseActivity() { challenge?.let { compositeSubscription.add( challengeRepository.leaveChallenge(it, "remove-all") - .flatMap { userRepository.retrieveUser(true, true) } .subscribe( { + lifecycleScope.launch(ExceptionHandler.coroutine()) { + userRepository.retrieveUser(true, true) + } finish() }, - RxErrorHandler.handleEmptyError() + ExceptionHandler.rx() ) ) } @@ -629,7 +638,7 @@ class TaskFormActivity : BaseActivity() { alert.setExtraCloseButtonVisibility(View.VISIBLE) alert.show() }, - RxErrorHandler.handleEmptyError() + ExceptionHandler.rx() ) ) } @@ -648,28 +657,32 @@ class TaskFormActivity : BaseActivity() { 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") - .flatMap { userRepository.retrieveUser(true, true) } .subscribe( - { - finish() - }, - RxErrorHandler.handleEmptyError() - ) + { + lifecycleScope.launch(ExceptionHandler.coroutine()) { + userRepository.retrieveUser(true, true) + } + finish() + }, + ExceptionHandler.rx() + ) } dialog.addButton(this.getString(R.string.delete_x_tasks, taskCount), false, true) { _, _ -> taskRepository.unlinkAllTasks(task.challengeID, "remove-all") - .flatMap { userRepository.retrieveUser(true, true) } .subscribe( - { - finish() - }, - RxErrorHandler.handleEmptyError() - ) + { + lifecycleScope.launch(ExceptionHandler.coroutine()) { + userRepository.retrieveUser(true, true) + } + finish() + }, + ExceptionHandler.rx() + ) } dialog.setExtraCloseButtonVisibility(View.VISIBLE) dialog.show() }, - RxErrorHandler.handleEmptyError() + ExceptionHandler.rx() ) ) } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/VerifyUsernameActivity.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/VerifyUsernameActivity.kt index 1e9845793..83d7c26f4 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/VerifyUsernameActivity.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/VerifyUsernameActivity.kt @@ -6,17 +6,20 @@ 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.RxErrorHandler +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() { @@ -90,25 +93,23 @@ class VerifyUsernameActivity : BaseActivity() { binding.issuesTextView.visibility = View.VISIBLE binding.issuesTextView.text = it.issues.joinToString("\n") } - }, - { displayNameUsable, usernameUsable -> displayNameUsable && usernameUsable.isUsable } - ) + } + ) { displayNameUsable, usernameUsable -> displayNameUsable && usernameUsable.isUsable } .subscribe( { binding.confirmUsernameButton.isEnabled = it }, - RxErrorHandler.handleEmptyError() + ExceptionHandler.rx() ) ) - compositeSubscription.add( - userRepository.getUserFlowable().firstElement().subscribe { - binding.displayNameEditText.setText(it.profile?.name) - displayNameVerificationEvents.onNext(it.profile?.name ?: "") - binding.usernameEditText.setText(it.authentication?.localAuthentication?.username) - usernameVerificationEvents.onNext(it.username ?: "") - } - ) + 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() { @@ -118,7 +119,7 @@ class VerifyUsernameActivity : BaseActivity() { .flatMap { userRepository.updateLoginName(binding.usernameEditText.text.toString()).toFlowable() } .doOnComplete { showConfirmationAndFinish() } .doOnEach { binding.confirmUsernameButton.isClickable = true } - .subscribe({ }, RxErrorHandler.handleEmptyError()) + .subscribe({ }, ExceptionHandler.rx()) ) } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/social/InboxAdapter.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/social/InboxAdapter.kt index bfa0fe6f5..f9db46083 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/social/InboxAdapter.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/social/InboxAdapter.kt @@ -15,7 +15,7 @@ import io.reactivex.rxjava3.core.BackpressureStrategy import io.reactivex.rxjava3.core.Flowable import io.reactivex.rxjava3.subjects.PublishSubject -class InboxAdapter(private var user: User?, private var replyToUser: Member) : PagedListAdapter(DIFF_CALLBACK) { +class InboxAdapter(private var user: User?, private var replyToUser: Member?) : PagedListAdapter(DIFF_CALLBACK) { private val FIRST_MESSAGE = 0 private val NORMAL_MESSAGE = 1 @@ -43,7 +43,7 @@ class InboxAdapter(private var user: User?, private var replyToUser: Member) : P } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ChatRecyclerViewHolder { - return if (viewType == FIRST_MESSAGE) ChatRecyclerIntroViewHolder(parent.inflate(R.layout.tavern_chat_intro_item), replyToUser.id!!) + return if (viewType == FIRST_MESSAGE) ChatRecyclerIntroViewHolder(parent.inflate(R.layout.tavern_chat_intro_item), replyToUser?.id ?: "") else ChatRecyclerMessageViewHolder(parent.inflate(R.layout.chat_item), user?.id ?: "", false) } 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 c74df6041..215f9ebfe 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 @@ -16,7 +16,7 @@ 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.RxErrorHandler +import com.habitrpg.android.habitica.helpers.ExceptionHandler import com.habitrpg.android.habitica.ui.adapter.AchievementsAdapter import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel import kotlinx.coroutines.flow.combine @@ -93,7 +93,7 @@ class AchievementsFragment : BaseMainFragment return@combine Pair(achievements, questAchievements) }.combine(userRepository.getQuestAchievements() @@ -171,7 +171,7 @@ class AchievementsFragment : BaseMainFragment : BottomSheetDialogFragment( mainActivity.displayTutorialStep(step, tutorialTexts, tutorialCanBeDeferred) } }, - RxErrorHandler.handleEmptyError() + ExceptionHandler.rx() ) ) } 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 cdf345a05..5a39ddbb4 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 @@ -9,7 +9,7 @@ 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.RxErrorHandler +import com.habitrpg.android.habitica.helpers.ExceptionHandler import com.habitrpg.android.habitica.proxy.AnalyticsManager import com.habitrpg.android.habitica.ui.activities.MainActivity import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers @@ -84,7 +84,7 @@ abstract class BaseFragment : Fragment() { mainActivity.displayTutorialStep(step, tutorialTexts, tutorialCanBeDeferred) } }, - RxErrorHandler.handleEmptyError() + ExceptionHandler.rx() ) ) } 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 1660fe34f..c8b3792a7 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,11 @@ 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.common.habitica.extensions.getThemeColor import com.habitrpg.android.habitica.extensions.subscribeWithErrorHandler import com.habitrpg.android.habitica.helpers.AppConfigManager import com.habitrpg.android.habitica.helpers.MainNavigationController -import com.habitrpg.android.habitica.helpers.RxErrorHandler +import com.habitrpg.android.habitica.helpers.ExceptionHandler import com.habitrpg.android.habitica.models.WorldStateEvent import com.habitrpg.android.habitica.models.inventory.Item import com.habitrpg.android.habitica.models.promotions.HabiticaPromotion @@ -140,7 +141,7 @@ class NavigationDrawerFragment : DialogFragment() { { setSelection(it.transitionId, it.bundle, true) }, - RxErrorHandler.handleEmptyError() + ExceptionHandler.rx() ) ) subscriptions?.add( @@ -151,7 +152,7 @@ class NavigationDrawerFragment : DialogFragment() { } updatePromo() }, - RxErrorHandler.handleEmptyError() + ExceptionHandler.rx() ) ) @@ -172,12 +173,12 @@ class NavigationDrawerFragment : DialogFragment() { updateSeasonalMenuEntries(gearEvent, pair.second) } }, - RxErrorHandler.handleEmptyError() + ExceptionHandler.rx() ) ) if (configManager.enableTeamBoards()) { - lifecycleScope.launch { + lifecycleScope.launch(ExceptionHandler.coroutine()) { userRepository.getTeamPlans() .distinctUntilChanged() .collect { 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 045c5218a..237fbc1b8 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,7 @@ import com.habitrpg.android.habitica.databinding.FragmentStatsBinding import com.habitrpg.android.habitica.extensions.addOkButton import com.habitrpg.common.habitica.extensions.getThemeColor import com.habitrpg.android.habitica.extensions.setScaledPadding -import com.habitrpg.android.habitica.helpers.RxErrorHandler +import com.habitrpg.android.habitica.helpers.ExceptionHandler import com.habitrpg.android.habitica.helpers.UserStatComputer import com.habitrpg.shared.habitica.models.tasks.Attribute import com.habitrpg.android.habitica.models.user.Stats @@ -132,7 +132,7 @@ class StatsFragment : BaseMainFragment() { binding?.automaticAllocationSwitch?.setOnCheckedChangeListener { _, isChecked -> userRepository.updateUser("preferences.automaticAllocation", isChecked) - .subscribe({}, RxErrorHandler.handleEmptyError()) + .subscribe({}, ExceptionHandler.rx()) } binding?.strengthStatsView?.allocateAction = { allocatePoint(Attribute.STRENGTH) } @@ -163,7 +163,7 @@ class StatsFragment : BaseMainFragment() { userRepository.updateUser( "preferences.allocationMode", allocationMode - ).subscribe({}, RxErrorHandler.handleEmptyError()) + ).subscribe({}, ExceptionHandler.rx()) ) binding?.distributeEvenlyButton?.isChecked = allocationMode == Stats.AUTO_ALLOCATE_FLAT binding?.distributeClassButton?.isChecked = allocationMode == Stats.AUTO_ALLOCATE_CLASSBASED @@ -179,7 +179,7 @@ class StatsFragment : BaseMainFragment() { private fun allocatePoint(stat: Attribute) { compositeSubscription.add( - userRepository.allocatePoint(stat).subscribe({ }, RxErrorHandler.handleEmptyError()) + userRepository.allocatePoint(stat).subscribe({ }, ExceptionHandler.rx()) ) } @@ -326,7 +326,7 @@ class StatsFragment : BaseMainFragment() { binding?.constitutionStatsView?.equipmentValue = constitution binding?.perceptionStatsView?.equipmentValue = perception }, - RxErrorHandler.handleEmptyError() + ExceptionHandler.rx() ) ) } 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 36f2569ce..e0f464b6b 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 @@ -12,6 +12,7 @@ import android.view.ViewGroup import android.widget.CheckBox import androidx.core.content.ContextCompat import androidx.core.view.doOnLayout +import androidx.lifecycle.lifecycleScope import androidx.swiperefreshlayout.widget.SwipeRefreshLayout import com.google.android.flexbox.AlignItems import com.google.android.flexbox.FlexDirection.ROW @@ -24,7 +25,7 @@ import com.habitrpg.android.habitica.data.InventoryRepository import com.habitrpg.android.habitica.databinding.BottomSheetBackgroundsFilterBinding import com.habitrpg.android.habitica.databinding.FragmentRefreshRecyclerviewBinding import com.habitrpg.android.habitica.extensions.setTintWith -import com.habitrpg.android.habitica.helpers.RxErrorHandler +import com.habitrpg.android.habitica.helpers.ExceptionHandler import com.habitrpg.android.habitica.models.CustomizationFilter import com.habitrpg.android.habitica.models.inventory.Customization import com.habitrpg.android.habitica.models.user.OwnedCustomization @@ -41,6 +42,7 @@ 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.launch import javax.inject.Inject class AvatarCustomizationFragment : @@ -81,22 +83,19 @@ class AvatarCustomizationFragment : adapter.getSelectCustomizationEvents() .flatMap { customization -> if (customization.type == "background") { - val using = if (customization.identifier?.isBlank() != false) { - customization.unlockPath + activeCustomization - } else customization.unlockPath - userRepository.unlockPath(using, 0) - .flatMap { userRepository.retrieveUser(false, true, true) } + userRepository.unlockPath(customization) + //TODO: .flatMap { userRepository.retrieveUser(false, true, true) } } else { userRepository.useCustomization(customization.type ?: "", customization.category, customization.identifier ?: "") } } - .subscribe({ }, RxErrorHandler.handleEmptyError()) + .subscribe({ }, ExceptionHandler.rx()) ) compositeSubscription.add( this.inventoryRepository.getInAppRewards() .map { rewards -> rewards.map { it.key } } - .subscribe({ adapter.setPinnedItemKeys(it) }, RxErrorHandler.handleEmptyError()) + .subscribe({ adapter.setPinnedItemKeys(it) }, ExceptionHandler.rx()) ) return super.onCreateView(inflater, container, savedInstanceState) @@ -212,12 +211,12 @@ class AvatarCustomizationFragment : ) } }, - RxErrorHandler.handleEmptyError() + 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 }, RxErrorHandler.handleEmptyError())) + compositeSubscription.add(customizationRepository.getCustomizations(type, otherCategory, true).subscribe({ adapter.additionalSetItems = it }, ExceptionHandler.rx())) } } @@ -270,14 +269,10 @@ class AvatarCustomizationFragment : } override fun onRefresh() { - compositeSubscription.add( - userRepository.retrieveUser(withTasks = false, forced = true).subscribe( - { - binding?.refreshLayout?.isRefreshing = false - }, - RxErrorHandler.handleEmptyError() - ) - ) + lifecycleScope.launch(ExceptionHandler.coroutine()) { + userRepository.retrieveUser(true, true) + binding?.refreshLayout?.isRefreshing = false + } } fun showFilterDialog() { diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/customization/AvatarEquipmentFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/customization/AvatarEquipmentFragment.kt index b1ced47f0..21438f469 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/customization/AvatarEquipmentFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/customization/AvatarEquipmentFragment.kt @@ -4,13 +4,14 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.GridLayoutManager import androidx.swiperefreshlayout.widget.SwipeRefreshLayout import com.habitrpg.android.habitica.R import com.habitrpg.android.habitica.components.UserComponent import com.habitrpg.android.habitica.data.InventoryRepository import com.habitrpg.android.habitica.databinding.FragmentRefreshRecyclerviewBinding -import com.habitrpg.android.habitica.helpers.RxErrorHandler +import com.habitrpg.android.habitica.helpers.ExceptionHandler import com.habitrpg.android.habitica.models.responses.UnlockResponse import com.habitrpg.android.habitica.models.user.User import com.habitrpg.android.habitica.ui.adapter.CustomizationEquipmentRecyclerViewAdapter @@ -19,6 +20,7 @@ import com.habitrpg.android.habitica.ui.helpers.MarginDecoration import com.habitrpg.android.habitica.ui.helpers.SafeDefaultItemAnimator import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel import io.reactivex.rxjava3.core.Flowable +import kotlinx.coroutines.launch import javax.inject.Inject class AvatarEquipmentFragment : @@ -55,14 +57,14 @@ class AvatarEquipmentFragment : val key = (if (equipment.key?.isNotBlank() != true) activeEquipment else equipment.key) ?: "" inventoryRepository.equip(if (userViewModel.user.value?.preferences?.costume == true) "costume" else "equipped", key) } - .subscribe({ }, RxErrorHandler.handleEmptyError()) + .subscribe({ }, ExceptionHandler.rx()) ) compositeSubscription.add( adapter.getUnlockCustomizationEvents() .flatMap { Flowable.empty() } - .subscribe({ }, RxErrorHandler.handleEmptyError()) + .subscribe({ }, ExceptionHandler.rx()) ) return super.onCreateView(inflater, container, savedInstanceState) } @@ -110,7 +112,7 @@ class AvatarEquipmentFragment : { adapter.setEquipment(it) }, - RxErrorHandler.handleEmptyError() + ExceptionHandler.rx() ) ) } @@ -148,13 +150,9 @@ class AvatarEquipmentFragment : } override fun onRefresh() { - compositeSubscription.add( - userRepository.retrieveUser(false, true).subscribe( - { - binding?.refreshLayout?.isRefreshing = false - }, - RxErrorHandler.handleEmptyError() - ) - ) + lifecycleScope.launch(ExceptionHandler.coroutine()) { + userRepository.retrieveUser(true, true) + binding?.refreshLayout?.isRefreshing = false + } } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/customization/AvatarOverviewFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/customization/AvatarOverviewFragment.kt index bd88ff5fe..f40390f42 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/customization/AvatarOverviewFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/customization/AvatarOverviewFragment.kt @@ -8,7 +8,7 @@ import android.widget.AdapterView import com.habitrpg.android.habitica.components.UserComponent import com.habitrpg.android.habitica.databinding.FragmentAvatarOverviewBinding import com.habitrpg.android.habitica.helpers.MainNavigationController -import com.habitrpg.android.habitica.helpers.RxErrorHandler +import com.habitrpg.android.habitica.helpers.ExceptionHandler import com.habitrpg.android.habitica.models.user.User import com.habitrpg.android.habitica.ui.fragments.BaseMainFragment import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel @@ -99,7 +99,7 @@ class AvatarOverviewFragment : BaseMainFragment() compositeSubscription.add( userRepository.updateUser("preferences.size", newSize) - .subscribe({ }, RxErrorHandler.handleEmptyError()) + .subscribe({ }, ExceptionHandler.rx()) ) } 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 68275bca6..6dc48579f 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 @@ -4,6 +4,7 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.DividerItemDecoration import androidx.recyclerview.widget.LinearLayoutManager import androidx.swiperefreshlayout.widget.SwipeRefreshLayout @@ -12,11 +13,12 @@ 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.RxErrorHandler +import com.habitrpg.android.habitica.helpers.ExceptionHandler 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 kotlinx.coroutines.launch import javax.inject.Inject class EquipmentDetailFragment : @@ -45,7 +47,7 @@ class EquipmentDetailFragment : ): View? { compositeSubscription.add( this.adapter.equipEvents.flatMapMaybe { key -> inventoryRepository.equipGear(key, isCostume ?: false).firstElement() } - .subscribe({ }, RxErrorHandler.handleEmptyError()) + .subscribe({ }, ExceptionHandler.rx()) ) return super.onCreateView(inflater, container, savedInstanceState) } @@ -80,7 +82,7 @@ class EquipmentDetailFragment : binding?.recyclerView?.addItemDecoration(DividerItemDecoration(getActivity(), DividerItemDecoration.VERTICAL)) binding?.recyclerView?.itemAnimator = SafeDefaultItemAnimator() - type?.let { type -> inventoryRepository.getOwnedEquipment(type).subscribe({ this.adapter.data = it }, RxErrorHandler.handleEmptyError()) } + type?.let { type -> inventoryRepository.getOwnedEquipment(type).subscribe({ this.adapter.data = it }, ExceptionHandler.rx()) } } override fun onDestroy() { @@ -93,13 +95,9 @@ class EquipmentDetailFragment : } override fun onRefresh() { - compositeSubscription.add( - userRepository.retrieveUser(false, true).subscribe( - { - binding?.refreshLayout?.isRefreshing = false - }, - RxErrorHandler.handleEmptyError() - ) - ) + lifecycleScope.launch(ExceptionHandler.coroutine()) { + userRepository.retrieveUser(true, true) + binding?.refreshLayout?.isRefreshing = false + } } } 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 60b908cfa..b8a24cb9d 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 @@ -15,8 +15,8 @@ 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.RxErrorHandler import com.habitrpg.android.habitica.interactors.FeedPetUseCase import com.habitrpg.android.habitica.interactors.HatchPetUseCase import com.habitrpg.android.habitica.models.inventory.Egg @@ -31,11 +31,11 @@ import com.habitrpg.android.habitica.models.user.User import com.habitrpg.android.habitica.ui.activities.MainActivity import com.habitrpg.android.habitica.ui.adapter.inventory.ItemRecyclerAdapter import com.habitrpg.android.habitica.ui.fragments.BaseDialogFragment -import com.habitrpg.common.habitica.helpers.EmptyItem import com.habitrpg.android.habitica.ui.helpers.SafeDefaultItemAnimator -import com.habitrpg.common.habitica.extensions.loadImage import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel import com.habitrpg.android.habitica.ui.views.dialogs.OpenedMysteryitemDialog +import com.habitrpg.common.habitica.extensions.loadImage +import com.habitrpg.common.habitica.helpers.EmptyItem import io.reactivex.rxjava3.disposables.CompositeDisposable import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.map @@ -188,22 +188,24 @@ class ItemDialogFragment : BaseDialogFragment() { compositeSubscription.add( adapter.getSellItemFlowable() .flatMap { item -> inventoryRepository.sellItem(item) } - .subscribe({ }, RxErrorHandler.handleEmptyError()) + .subscribe({ }, ExceptionHandler.rx()) ) compositeSubscription.add( adapter.getQuestInvitationFlowable() .flatMap { quest -> inventoryRepository.inviteToQuest(quest) } - .flatMap { socialRepository.retrieveGroup("party") } .subscribe( { - if (isModal) { - dismiss() - } else { - MainNavigationController.navigate(R.id.partyFragment) + lifecycleScope.launch(ExceptionHandler.coroutine()) { + socialRepository.retrieveGroup("party") + if (isModal) { + dismiss() + } else { + MainNavigationController.navigate(R.id.partyFragment) + } } }, - RxErrorHandler.handleEmptyError() + ExceptionHandler.rx() ) ) compositeSubscription.add( @@ -219,13 +221,13 @@ class ItemDialogFragment : BaseDialogFragment() { dialog.binding.titleView.text = it.text dialog.binding.descriptionView.text = it.notes dialog.addButton(R.string.equip, true) { _, _ -> - inventoryRepository.equip("equipped", it.key ?: "").subscribe({}, RxErrorHandler.handleEmptyError()) + inventoryRepository.equip("equipped", it.key ?: "").subscribe({}, ExceptionHandler.rx()) } dialog.addCloseButton() dialog.enqueue() } } - .subscribe({ }, RxErrorHandler.handleEmptyError()) + .subscribe({ }, ExceptionHandler.rx()) ) compositeSubscription.add(adapter.hatchPetEvents.subscribeWithErrorHandler { hatchPet(it.first, it.second) }) compositeSubscription.add(adapter.feedPetEvents.subscribeWithErrorHandler { feedPet(it) }) @@ -285,7 +287,7 @@ class ItemDialogFragment : BaseDialogFragment() { else -> Egg::class.java } itemType?.let { type -> - lifecycleScope.launch { + lifecycleScope.launch(ExceptionHandler.coroutine()) { inventoryRepository.getOwnedItems(type) .onEach { items -> val filteredItems = if (isFeeding) { @@ -296,7 +298,7 @@ class ItemDialogFragment : BaseDialogFragment() { adapter?.data = filteredItems } .map { items -> items.mapNotNull { it.key } } - .map { inventoryRepository.getItemsFlowable(itemClass, it.toTypedArray()).firstOrNull() } + .map { inventoryRepository.getItems(itemClass, it.toTypedArray()).firstOrNull() } .collect { val itemMap = mutableMapOf() for (item in it ?: emptyList()) { @@ -306,10 +308,10 @@ class ItemDialogFragment : BaseDialogFragment() { } } - lifecycleScope.launch { + lifecycleScope.launch(ExceptionHandler.coroutine()) { inventoryRepository.getPets().collect { adapter?.setExistingPets(it) } } - lifecycleScope.launch { + lifecycleScope.launch(ExceptionHandler.coroutine()) { inventoryRepository.getOwnedPets().map { ownedPets -> val petMap = mutableMapOf() ownedPets.forEach { petMap[it.key ?: ""] = it } 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 863bb60a1..5eb2b0e9a 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 @@ -20,7 +20,7 @@ import com.habitrpg.android.habitica.extensions.addCloseButton import com.habitrpg.android.habitica.extensions.observeOnce import com.habitrpg.android.habitica.extensions.subscribeWithErrorHandler import com.habitrpg.android.habitica.helpers.MainNavigationController -import com.habitrpg.android.habitica.helpers.RxErrorHandler +import com.habitrpg.android.habitica.helpers.ExceptionHandler import com.habitrpg.android.habitica.interactors.HatchPetUseCase import com.habitrpg.android.habitica.models.inventory.Egg import com.habitrpg.android.habitica.models.inventory.Food @@ -37,13 +37,13 @@ import com.habitrpg.android.habitica.ui.activities.MainActivity import com.habitrpg.android.habitica.ui.activities.SkillMemberActivity import com.habitrpg.android.habitica.ui.adapter.inventory.ItemRecyclerAdapter import com.habitrpg.android.habitica.ui.fragments.BaseFragment -import com.habitrpg.common.habitica.helpers.EmptyItem import com.habitrpg.android.habitica.ui.helpers.SafeDefaultItemAnimator -import com.habitrpg.common.habitica.extensions.loadImage import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel import com.habitrpg.android.habitica.ui.views.HabiticaSnackbar import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog import com.habitrpg.android.habitica.ui.views.dialogs.OpenedMysteryitemDialog +import com.habitrpg.common.habitica.extensions.loadImage +import com.habitrpg.common.habitica.helpers.EmptyItem import io.reactivex.rxjava3.core.Flowable import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.map @@ -147,18 +147,18 @@ class ItemRecyclerFragment : BaseFragment(), SwipeRefreshL compositeSubscription.add( adapter.getSellItemFlowable() .flatMap { item -> inventoryRepository.sellItem(item) } - .subscribe({ }, RxErrorHandler.handleEmptyError()) + .subscribe({ }, ExceptionHandler.rx()) ) compositeSubscription.add( adapter.getQuestInvitationFlowable() .flatMap { quest -> inventoryRepository.inviteToQuest(quest) } - .flatMap { socialRepository.retrieveGroup("party") } + //.flatMap { socialRepository.retrieveGroup("party") } .subscribe( { MainNavigationController.navigate(R.id.partyFragment) }, - RxErrorHandler.handleEmptyError() + ExceptionHandler.rx() ) ) compositeSubscription.add( @@ -174,13 +174,13 @@ class ItemRecyclerFragment : BaseFragment(), SwipeRefreshL dialog.binding.titleView.text = it.text dialog.binding.descriptionView.text = it.notes dialog.addButton(R.string.equip, true) { _, _ -> - inventoryRepository.equip("equipped", it.key ?: "").subscribe({}, RxErrorHandler.handleEmptyError()) + inventoryRepository.equip("equipped", it.key ?: "").subscribe({}, ExceptionHandler.rx()) } dialog.addCloseButton() dialog.enqueue() } } - .subscribe({ }, RxErrorHandler.handleEmptyError()) + .subscribe({ }, ExceptionHandler.rx()) ) compositeSubscription.add(adapter.startHatchingEvents.subscribeWithErrorHandler { showHatchingDialog(it) }) compositeSubscription.add(adapter.hatchPetEvents.subscribeWithErrorHandler { hatchPet(it.first, it.second) }) @@ -211,12 +211,10 @@ class ItemRecyclerFragment : BaseFragment(), SwipeRefreshL override fun onRefresh() { binding?.refreshLayout?.isRefreshing = true - compositeSubscription.add( + lifecycleScope.launch(ExceptionHandler.coroutine()) { userRepository.retrieveUser(true, true) - .doOnTerminate { - binding?.refreshLayout?.isRefreshing = false - }.subscribe({ }, RxErrorHandler.handleEmptyError()) - ) + binding?.refreshLayout?.isRefreshing = false + } } private fun hatchPet(potion: HatchingPotion, egg: Egg) { @@ -237,34 +235,25 @@ class ItemRecyclerFragment : BaseFragment(), SwipeRefreshL alert?.setTitle(R.string.quest_party_required_title) alert?.setMessage(R.string.quest_party_required_description) alert?.addButton(R.string.create_new_party, true, false) { _, _ -> - socialRepository.createGroup( - getString(R.string.usernames_party, user?.profile?.name), - "", - user?.id, - "party", - "", - false - ) - .flatMap { - userRepository.retrieveUser(false, true) - .filter { it.hasParty } - .flatMap { socialRepository.retrieveGroup("party") } - .flatMap { group1 -> - socialRepository.retrieveGroupMembers( - group1.id, - true - ) - } - } - .subscribe( - { - MainNavigationController.navigate( - R.id.partyFragment, - bundleOf(Pair("partyID", user?.party?.id)) - ) - }, - RxErrorHandler.handleEmptyError() + lifecycleScope.launch(ExceptionHandler.coroutine()) { + val group = socialRepository.createGroup( + getString(R.string.usernames_party, user?.profile?.name), + "", + user?.id, + "party", + "", + false ) + val user = userRepository.retrieveUser(false, true) + if (user?.hasParty == true) { + val party = socialRepository.retrieveGroup("party") + socialRepository.retrieveGroupMembers(party?.id ?: "", true) + MainNavigationController.navigate( + R.id.partyFragment, + bundleOf(Pair("partyID", user.party?.id)) + ) + } + } } alert?.addButton(R.string.close, false) { _, _ -> alert.dismiss() @@ -282,14 +271,14 @@ class ItemRecyclerFragment : BaseFragment(), SwipeRefreshL else -> Egg::class.java } itemType?.let { type -> - lifecycleScope.launch { + lifecycleScope.launch(ExceptionHandler.coroutine()) { inventoryRepository.getOwnedItems(type) .onEach { items -> adapter?.data = items } .map { items -> items.mapNotNull { it.key } } .map { - inventoryRepository.getItemsFlowable(itemClass, it.toTypedArray()).firstOrNull() + inventoryRepository.getItems(itemClass, it.toTypedArray()).firstOrNull() } .collect { val itemMap = mutableMapOf() @@ -299,10 +288,10 @@ class ItemRecyclerFragment : BaseFragment(), SwipeRefreshL adapter?.items = itemMap } } - lifecycleScope.launch { + lifecycleScope.launch(ExceptionHandler.coroutine()) { inventoryRepository.getPets().collect { adapter?.setExistingPets(it) } } - lifecycleScope.launch { + lifecycleScope.launch(ExceptionHandler.coroutine()) { inventoryRepository.getOwnedPets().map { ownedPets -> val petMap = mutableMapOf() ownedPets.forEach { petMap[it.key ?: ""] = it } @@ -340,7 +329,7 @@ class ItemRecyclerFragment : BaseFragment(), SwipeRefreshL compositeSubscription.add( observable.subscribe( { this.displaySpecialItemResult(specialItem) }, - RxErrorHandler.handleEmptyError() + ExceptionHandler.rx() ) ) } 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 7682a4ce8..26adcc2f4 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 @@ -12,9 +12,8 @@ import com.habitrpg.android.habitica.components.UserComponent import com.habitrpg.android.habitica.data.InventoryRepository import com.habitrpg.android.habitica.data.SocialRepository import com.habitrpg.android.habitica.databinding.FragmentRefreshRecyclerviewBinding -import com.habitrpg.android.habitica.extensions.subscribeWithErrorHandler import com.habitrpg.android.habitica.helpers.AppConfigManager -import com.habitrpg.android.habitica.helpers.RxErrorHandler +import com.habitrpg.android.habitica.helpers.ExceptionHandler import com.habitrpg.android.habitica.models.shops.Shop import com.habitrpg.android.habitica.models.shops.ShopCategory import com.habitrpg.android.habitica.models.shops.ShopItem @@ -137,16 +136,11 @@ open class ShopFragment : BaseMainFragment() updateCurrencyView(it) } - lifecycleScope.launch { + lifecycleScope.launch(ExceptionHandler.coroutine()) { socialRepository.getGroup(Group.TAVERN_ID) .filter { it?.hasActiveQuest == true } - .filter { group -> - group?.quest?.rageStrikes?.any { it.key == shopIdentifier } ?: false - } - .filter { group -> - group?.quest?.rageStrikes?.filter { it.key == shopIdentifier } - ?.get(0)?.wasHit == true - } + .filter { group -> group?.quest?.rageStrikes?.any { it.key == shopIdentifier } ?: false } + .filter { group -> group?.quest?.rageStrikes?.filter { it.key == shopIdentifier }?.get(0)?.wasHit == true } .collect { adapter?.shopSpriteSuffix = "_" + it?.quest?.key } @@ -164,7 +158,8 @@ open class ShopFragment : BaseMainFragment() val alert = HabiticaAlertDialog(context) alert.setTitle(getString(R.string.class_confirmation_price, classIdentifier, 3)) alert.addButton(R.string.choose_class, true) { _, _ -> - userRepository.changeClass(classIdentifier).subscribeWithErrorHandler { + lifecycleScope.launch(ExceptionHandler.coroutine()) { + userRepository.changeClass(classIdentifier) } } alert.addButton(R.string.dialog_go_back, false) @@ -224,7 +219,7 @@ open class ShopFragment : BaseMainFragment() }, { binding?.recyclerView?.state = RecyclerViewState.FAILED - RxErrorHandler.reportError(it) + ExceptionHandler.reportError(it) }, { binding?.refreshLayout?.isRefreshing = false @@ -234,12 +229,12 @@ open class ShopFragment : BaseMainFragment() compositeSubscription.add( this.inventoryRepository.getOwnedItems() - .subscribe({ adapter?.setOwnedItems(it) }, RxErrorHandler.handleEmptyError()) + .subscribe({ adapter?.setOwnedItems(it) }, ExceptionHandler.rx()) ) compositeSubscription.add( this.inventoryRepository.getInAppRewards() .map { rewards -> rewards.map { it.key } } - .subscribe({ adapter?.setPinnedItemKeys(it) }, RxErrorHandler.handleEmptyError()) + .subscribe({ adapter?.setPinnedItemKeys(it) }, ExceptionHandler.rx()) ) } @@ -289,7 +284,7 @@ open class ShopFragment : BaseMainFragment() this.gearCategories = it.categories adapter?.gearCategories = it.categories }, - RxErrorHandler.handleEmptyError() + ExceptionHandler.rx() ) ) } 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 8d5188882..2d108d89a 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 @@ -11,7 +11,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.extensions.getTranslatedType -import com.habitrpg.android.habitica.helpers.RxErrorHandler +import com.habitrpg.android.habitica.helpers.ExceptionHandler import com.habitrpg.android.habitica.models.inventory.Mount import com.habitrpg.android.habitica.models.inventory.StableSection import com.habitrpg.android.habitica.models.user.OwnedMount @@ -102,7 +102,7 @@ class MountDetailRecyclerFragment : { adapter?.currentMount = it.currentMount }, - RxErrorHandler.handleEmptyError() + ExceptionHandler.rx() )?.let { compositeSubscription.add(it) } } userViewModel.user.observe(viewLifecycleOwner) { adapter?.currentMount = it?.currentMount } @@ -136,7 +136,7 @@ class MountDetailRecyclerFragment : private fun loadItems() { if (animalType != null || animalGroup != null) { - lifecycleScope.launch { + lifecycleScope.launch(ExceptionHandler.coroutine()) { val mounts = inventoryRepository.getMounts(animalType, animalGroup, animalColor).firstOrNull() ?: emptyList() inventoryRepository.getOwnedMounts().map { ownedMounts -> val mountMap = mutableMapOf() @@ -173,13 +173,9 @@ class MountDetailRecyclerFragment : } override fun onRefresh() { - compositeSubscription.add( - userRepository.retrieveUser(false, true).subscribe( - { - binding?.refreshLayout?.isRefreshing = false - }, - RxErrorHandler.handleEmptyError() - ) - ) + lifecycleScope.launch(ExceptionHandler.coroutine()) { + userRepository.retrieveUser(false, true) + binding?.refreshLayout?.isRefreshing = false + } } } 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 dd234a2e3..3ec178450 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 @@ -12,7 +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.extensions.subscribeWithErrorHandler -import com.habitrpg.android.habitica.helpers.RxErrorHandler +import com.habitrpg.android.habitica.helpers.ExceptionHandler import com.habitrpg.android.habitica.interactors.FeedPetUseCase import com.habitrpg.android.habitica.models.inventory.Egg import com.habitrpg.android.habitica.models.inventory.Food @@ -111,11 +111,11 @@ class PetDetailRecyclerFragment : } binding?.recyclerView?.layoutManager = layoutManager adapter.animalIngredientsRetriever = { animal, callback -> - lifecycleScope.launch { - val egg = inventoryRepository.getItemsFlowable(Egg::class.java, arrayOf(animal.animal)) + lifecycleScope.launch(ExceptionHandler.coroutine()) { + val egg = inventoryRepository.getItems(Egg::class.java, arrayOf(animal.animal)) .firstOrNull()?.firstOrNull() as? Egg val potion = - inventoryRepository.getItemsFlowable(HatchingPotion::class.java, arrayOf(animal.color)) + inventoryRepository.getItems(HatchingPotion::class.java, arrayOf(animal.color)) .firstOrNull()?.firstOrNull() as? HatchingPotion callback(Pair(egg, potion)) } @@ -131,7 +131,7 @@ class PetDetailRecyclerFragment : { adapter.currentPet = it.currentPet }, - RxErrorHandler.handleEmptyError() + ExceptionHandler.rx() ) ) userViewModel.user.observe(viewLifecycleOwner) { adapter.currentPet = it?.currentPet } @@ -140,7 +140,7 @@ class PetDetailRecyclerFragment : it.first, it.second ) - }, RxErrorHandler.handleEmptyError())) + }, ExceptionHandler.rx())) view.post { setGridSpanCount(view.width) } } @@ -171,7 +171,7 @@ class PetDetailRecyclerFragment : private fun loadItems() { if (animalType?.isNotEmpty() == true || animalGroup?.isNotEmpty() == true) { - lifecycleScope.launch { + lifecycleScope.launch(ExceptionHandler.coroutine()) { inventoryRepository.getOwnedMounts() .map { ownedMounts -> val mountMap = mutableMapOf() @@ -181,9 +181,9 @@ class PetDetailRecyclerFragment : } compositeSubscription.add( inventoryRepository.getOwnedItems(true) - .subscribe({ adapter.setOwnedItems(it) }, RxErrorHandler.handleEmptyError()) + .subscribe({ adapter.setOwnedItems(it) }, ExceptionHandler.rx()) ) - lifecycleScope.launch { + lifecycleScope.launch(ExceptionHandler.coroutine()) { val mounts = inventoryRepository.getMounts( animalType, animalGroup, @@ -256,13 +256,9 @@ class PetDetailRecyclerFragment : } override fun onRefresh() { - compositeSubscription.add( - userRepository.retrieveUser(false, true).subscribe( - { - binding?.refreshLayout?.isRefreshing = false - }, - RxErrorHandler.handleEmptyError() - ) - ) + lifecycleScope.launch(ExceptionHandler.coroutine()) { + userRepository.retrieveUser(false, true) + binding?.refreshLayout?.isRefreshing = false + } } } 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 34df1d531..ac4ae7502 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 @@ -14,7 +14,7 @@ import com.habitrpg.android.habitica.data.InventoryRepository 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.RxErrorHandler +import com.habitrpg.android.habitica.helpers.ExceptionHandler 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 @@ -106,9 +106,9 @@ class StableRecyclerFragment : if (adapter == null) { adapter = StableRecyclerAdapter() adapter?.animalIngredientsRetriever = { animal, callback -> - lifecycleScope.launch { - val egg = inventoryRepository.getItemsFlowable(Egg::class.java, arrayOf(animal.animal)).firstOrNull()?.firstOrNull() as? Egg - val potion = inventoryRepository.getItemsFlowable(HatchingPotion::class.java, arrayOf(animal.color)).firstOrNull()?.firstOrNull() as? HatchingPotion + lifecycleScope.launch(ExceptionHandler.coroutine()) { + val egg = inventoryRepository.getItems(Egg::class.java, arrayOf(animal.animal)).firstOrNull()?.firstOrNull() as? Egg + val potion = inventoryRepository.getItems(HatchingPotion::class.java, arrayOf(animal.color)).firstOrNull()?.firstOrNull() as? HatchingPotion callback(Pair(egg, potion)) } } @@ -121,7 +121,7 @@ class StableRecyclerFragment : compositeSubscription.add( it.getEquipFlowable() .flatMap { key -> inventoryRepository.equip(if (itemType == "pets") "pet" else "mount", key) } - .subscribe({ }, RxErrorHandler.handleEmptyError()) + .subscribe({ }, ExceptionHandler.rx()) ) } } @@ -179,13 +179,9 @@ class StableRecyclerFragment : } override fun onRefresh() { - compositeSubscription.add( - userRepository.retrieveUser(false, true).subscribe( - { - binding?.refreshLayout?.isRefreshing = false - }, - RxErrorHandler.handleEmptyError() - ) - ) + lifecycleScope.launch(ExceptionHandler.coroutine()) { + userRepository.retrieveUser(true, true) + binding?.refreshLayout?.isRefreshing = false + } } } 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 0fb50d6ac..d76d6d85f 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 @@ -14,6 +14,7 @@ import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat.getSystemService import androidx.core.util.PatternsCompat import androidx.core.view.isVisible +import androidx.lifecycle.lifecycleScope import androidx.preference.EditTextPreference import androidx.preference.Preference import com.google.android.material.textfield.TextInputLayout @@ -22,8 +23,10 @@ import com.habitrpg.android.habitica.R import com.habitrpg.android.habitica.data.ApiClient import com.habitrpg.android.habitica.extensions.addCancelButton import com.habitrpg.android.habitica.extensions.addCloseButton +import com.habitrpg.common.habitica.extensions.dpToPx +import com.habitrpg.common.habitica.extensions.layoutInflater +import com.habitrpg.android.habitica.helpers.ExceptionHandler import com.habitrpg.android.habitica.helpers.MainNavigationController -import com.habitrpg.android.habitica.helpers.RxErrorHandler import com.habitrpg.android.habitica.models.user.User import com.habitrpg.android.habitica.ui.activities.FixCharacterValuesActivity import com.habitrpg.android.habitica.ui.fragments.preferences.HabiticaAccountDialog.AccountUpdateConfirmed @@ -36,8 +39,7 @@ import com.habitrpg.android.habitica.ui.views.ValidatingEditText import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaProgressDialog import com.habitrpg.common.habitica.api.HostConfig -import com.habitrpg.common.habitica.extensions.dpToPx -import com.habitrpg.common.habitica.extensions.layoutInflater +import kotlinx.coroutines.launch import retrofit2.HttpException import javax.inject.Inject @@ -203,8 +205,11 @@ class AccountPreferenceFragment : dialog.setTitle(R.string.are_you_sure) dialog.addButton(R.string.disconnect, true) { _, _ -> apiClient.disconnectSocial(network) - .flatMap { userRepository.retrieveUser(true, true) } - .subscribe({ displayDisconnectSuccess(networkName) }, RxErrorHandler.handleEmptyError()) + .subscribe({ + lifecycleScope.launch(ExceptionHandler.coroutine()) { + userRepository.retrieveUser(true, true) + } + displayDisconnectSuccess(networkName) }, ExceptionHandler.rx()) } dialog.addCancelButton() dialog.show() @@ -252,7 +257,7 @@ class AccountPreferenceFragment : showSingleEntryDialog(value, title) { if (value != it) { userRepository.updateUser(path, it ?: "") - .subscribe({}, RxErrorHandler.handleEmptyError()) + .subscribe({}, ExceptionHandler.rx()) } } } @@ -278,9 +283,7 @@ class AccountPreferenceFragment : userRepository.updatePassword( oldPasswordEditText?.text ?: "", passwordEditText.text ?: "", - passwordRepeatEditText.text ?: "" - ) - .flatMap { userRepository.retrieveUser(true, true) } + passwordRepeatEditText.text ?: "") .subscribe( { (activity as? SnackbarActivity)?.showSnackbar( @@ -288,7 +291,7 @@ class AccountPreferenceFragment : displayType = HabiticaSnackbar.SnackbarDisplayType.SUCCESS ) }, - RxErrorHandler.handleEmptyError() + ExceptionHandler.rx() ) dialog.dismiss() } @@ -327,7 +330,6 @@ class AccountPreferenceFragment : 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 ?: "") - .flatMap { userRepository.retrieveUser(true, true) } .subscribe( { (activity as? SnackbarActivity)?.showSnackbar( @@ -335,7 +337,7 @@ class AccountPreferenceFragment : displayType = HabiticaSnackbar.SnackbarDisplayType.SUCCESS ) }, - RxErrorHandler.handleEmptyError() + ExceptionHandler.rx() ) dialog.dismiss() } @@ -366,12 +368,14 @@ class AccountPreferenceFragment : emailEditText?.showErrorIfNecessary() if (emailEditText?.isValid != true) return@addButton userRepository.updateEmail(emailEditText.text.toString(), passwordEditText?.text.toString()) - .flatMap { userRepository.retrieveUser(true, true) } .subscribe( { + lifecycleScope.launch(ExceptionHandler.coroutine()) { + userRepository.retrieveUser(true, true) + } configurePreference(findPreference("email"), emailEditText.text.toString()) }, - RxErrorHandler.handleEmptyError() + ExceptionHandler.rx() ) dialog.dismiss() } @@ -385,7 +389,7 @@ class AccountPreferenceFragment : private fun showLoginNameDialog() { showSingleEntryDialog(user?.username, getString(R.string.username)) { userRepository.updateLoginName(it ?: "") - .subscribe({}, RxErrorHandler.handleEmptyError()) + .subscribe({}, ExceptionHandler.rx()) } } @@ -450,7 +454,7 @@ class AccountPreferenceFragment : errorDialog?.addCloseButton() errorDialog?.show() } - RxErrorHandler.reportError(throwable) + ExceptionHandler.reportError(throwable) } ) } @@ -475,7 +479,7 @@ class AccountPreferenceFragment : dialog.setMessage(R.string.confirm_username_description) dialog.addButton(R.string.confirm, true) { _, _ -> userRepository.updateLoginName(user?.authentication?.localAuthentication?.username ?: "") - .subscribe({ }, RxErrorHandler.handleEmptyError()) + .subscribe({ }, ExceptionHandler.rx()) } dialog.addCancelButton() dialog.show() @@ -483,19 +487,10 @@ class AccountPreferenceFragment : private fun resetAccount() { val dialog = HabiticaProgressDialog.show(context, R.string.resetting_account) - compositeSubscription.add( - userRepository.resetAccount().subscribe({ - dialog?.dismiss() - accountDialog.dismiss() - (activity as? SnackbarActivity)?.showSnackbar( - content = context?.getString(R.string.account_reset), - displayType = HabiticaSnackbar.SnackbarDisplayType.SUCCESS - ) - }) { throwable -> - dialog?.dismiss() - RxErrorHandler.reportError(throwable) - } - ) + lifecycleScope.launch(ExceptionHandler.coroutine()) { + userRepository.resetAccount() + dialog?.dismiss() + } } private fun copyValue(name: String, value: CharSequence?) { diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/EmailNotificationsPreferencesFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/EmailNotificationsPreferencesFragment.kt index 0e1910c6c..8a8ed8b59 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/EmailNotificationsPreferencesFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/EmailNotificationsPreferencesFragment.kt @@ -4,7 +4,7 @@ import android.content.SharedPreferences import android.os.Bundle import androidx.preference.CheckBoxPreference import com.habitrpg.android.habitica.HabiticaBaseApplication -import com.habitrpg.android.habitica.helpers.RxErrorHandler +import com.habitrpg.android.habitica.helpers.ExceptionHandler import com.habitrpg.android.habitica.models.user.User class EmailNotificationsPreferencesFragment : BasePreferencesFragment(), SharedPreferences.OnSharedPreferenceChangeListener { @@ -73,7 +73,7 @@ class EmailNotificationsPreferencesFragment : BasePreferencesFragment(), SharedP else -> null } if (pathKey != null) { - compositeSubscription.add(userRepository.updateUser("preferences.emailNotifications.$pathKey", sharedPreferences.getBoolean(key, false)).subscribe({ }, RxErrorHandler.handleEmptyError())) + compositeSubscription.add(userRepository.updateUser("preferences.emailNotifications.$pathKey", sharedPreferences.getBoolean(key, false)).subscribe({ }, ExceptionHandler.rx())) } } } 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 2ef49786e..f63bf8362 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 @@ -20,7 +20,7 @@ import com.habitrpg.android.habitica.data.ApiClient import com.habitrpg.android.habitica.data.ContentRepository import com.habitrpg.android.habitica.extensions.addCancelButton import com.habitrpg.android.habitica.helpers.AppConfigManager -import com.habitrpg.android.habitica.helpers.RxErrorHandler +import com.habitrpg.android.habitica.helpers.ExceptionHandler import com.habitrpg.android.habitica.helpers.SoundManager import com.habitrpg.android.habitica.helpers.TaskAlarmManager import com.habitrpg.android.habitica.helpers.notifications.PushNotificationManager @@ -69,7 +69,7 @@ class PreferencesFragment : BasePreferencesFragment(), SharedPreferences.OnShare super.onViewCreated(view, savedInstanceState) listView.itemAnimator = null - userRepository.retrieveTeamPlans().subscribe({}, RxErrorHandler.handleEmptyError()) + userRepository.retrieveTeamPlans().subscribe({}, ExceptionHandler.rx()) } override fun setupPreferences() { @@ -152,20 +152,24 @@ class PreferencesFragment : BasePreferencesFragment(), SharedPreferences.OnShare (activity as? SnackbarActivity)?.showSnackbar( content = context?.getString(R.string.reloading_content) ) - contentRepository.retrieveContent(true).subscribe( - { - (activity as? SnackbarActivity)?.showSnackbar( - content = context?.getString(R.string.reloaded_content), - displayType = HabiticaSnackbar.SnackbarDisplayType.SUCCESS - ) - }, - RxErrorHandler.handleEmptyError() - ) + reloadContent(true) } } return super.onPreferenceTreeClick(preference) } + private fun reloadContent(withConfirmation: Boolean) { + lifecycleScope.launch(ExceptionHandler.coroutine()) { + contentRepository.retrieveContent(true) + if (withConfirmation) { + (activity as? SnackbarActivity)?.showSnackbar( + content = context?.getString(R.string.reloaded_content), + displayType = HabiticaSnackbar.SnackbarDisplayType.SUCCESS + ) + } + } + } + private fun logout() { context?.let { context -> val dialog = HabiticaAlertDialog(context) @@ -180,7 +184,9 @@ class PreferencesFragment : BasePreferencesFragment(), SharedPreferences.OnShare } private val classSelectionResult = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { - userRepository.retrieveUser(true, forced = true) + lifecycleScope.launch(ExceptionHandler.coroutine()) { + userRepository.retrieveUser(true, true) + } } override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String?) { @@ -216,7 +222,7 @@ class PreferencesFragment : BasePreferencesFragment(), SharedPreferences.OnShare "cds_time" -> { val timeval = sharedPreferences.getString("cds_time", "0") ?: "0" val hour = Integer.parseInt(timeval) - userRepository.changeCustomDayStart(hour).subscribe({ }, RxErrorHandler.handleEmptyError()) + userRepository.changeCustomDayStart(hour).subscribe({ }, ExceptionHandler.rx()) val preference = findPreference(key) preference?.summary = preference?.entry } @@ -234,8 +240,7 @@ class PreferencesFragment : BasePreferencesFragment(), SharedPreferences.OnShare } userRepository.updateLanguage(languageHelper.languageCode ?: "en") - .flatMap { contentRepository.retrieveContent(true) } - .subscribe({ }, RxErrorHandler.handleEmptyError()) + .subscribe({ reloadContent(false) }, ExceptionHandler.rx()) val intent = Intent(activity, MainActivity::class.java) this.startActivity(intent) @@ -246,7 +251,7 @@ class PreferencesFragment : BasePreferencesFragment(), SharedPreferences.OnShare if (newAudioTheme != null) { compositeSubscription.add( userRepository.updateUser("preferences.sound", newAudioTheme) - .subscribe({ }, RxErrorHandler.handleEmptyError()) + .subscribe({ }, ExceptionHandler.rx()) ) soundManager.soundTheme = newAudioTheme soundManager.preloadAllFiles() @@ -261,7 +266,7 @@ class PreferencesFragment : BasePreferencesFragment(), SharedPreferences.OnShare activity.reload() } "dailyDueDefaultView" -> userRepository.updateUser("preferences.dailyDueDefaultView", sharedPreferences.getBoolean(key, false)) - .subscribe({ }, RxErrorHandler.handleEmptyError()) + .subscribe({ }, ExceptionHandler.rx()) "server_url" -> { apiClient.updateServerUrl(sharedPreferences.getString(key, "")) findPreference(key)?.summary = sharedPreferences.getString(key, "") @@ -279,7 +284,7 @@ class PreferencesFragment : BasePreferencesFragment(), SharedPreferences.OnShare if (user?.inbox?.optOut != isDisabled) { compositeSubscription.add( userRepository.updateUser("inbox.optOut", isDisabled) - .subscribe({ }, RxErrorHandler.handleEmptyError()) + .subscribe({ }, ExceptionHandler.rx()) ) } } @@ -385,7 +390,7 @@ class PreferencesFragment : BasePreferencesFragment(), SharedPreferences.OnShare } else if (newValue == false && currentIds.contains(team.id)) { currentIds.remove(team.id) } - userRepository.updateUser("preferences.tasks.mirrorGroupTasks", currentIds).subscribe({}, RxErrorHandler.handleEmptyError()) + userRepository.updateUser("preferences.tasks.mirrorGroupTasks", currentIds).subscribe({}, ExceptionHandler.rx()) true } groupCategory?.addPreference(newPreference) diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/PushNotificationsPreferencesFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/PushNotificationsPreferencesFragment.kt index 97a6eb276..07a654ab3 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/PushNotificationsPreferencesFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/PushNotificationsPreferencesFragment.kt @@ -4,7 +4,7 @@ import android.content.SharedPreferences import android.os.Bundle import androidx.preference.CheckBoxPreference import com.habitrpg.android.habitica.HabiticaBaseApplication -import com.habitrpg.android.habitica.helpers.RxErrorHandler +import com.habitrpg.android.habitica.helpers.ExceptionHandler import com.habitrpg.android.habitica.models.user.User class PushNotificationsPreferencesFragment : BasePreferencesFragment(), SharedPreferences.OnSharedPreferenceChangeListener { @@ -75,7 +75,7 @@ class PushNotificationsPreferencesFragment : BasePreferencesFragment(), SharedPr else -> null } if (pathKey != null) { - compositeSubscription.add(userRepository.updateUser("preferences.pushNotifications.$pathKey", sharedPreferences.getBoolean(key, false)).subscribe({ }, RxErrorHandler.handleEmptyError())) + compositeSubscription.add(userRepository.updateUser("preferences.pushNotifications.$pathKey", sharedPreferences.getBoolean(key, false)).subscribe({ }, ExceptionHandler.rx())) } } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/purchases/GemsPurchaseFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/purchases/GemsPurchaseFragment.kt index 8ed0ae758..7bb35bc92 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/purchases/GemsPurchaseFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/purchases/GemsPurchaseFragment.kt @@ -15,6 +15,7 @@ import com.habitrpg.android.habitica.databinding.FragmentGemPurchaseBinding 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.models.promotions.PromoType @@ -99,7 +100,7 @@ class GemsPurchaseFragment : BaseFragment() { } private fun loadInventory() { - CoroutineScope(Dispatchers.IO).launch { + CoroutineScope(Dispatchers.IO).launch(ExceptionHandler.coroutine()) { val skus = purchaseHandler.getAllGemSKUs() withContext(Dispatchers.Main) { for (sku in skus) { 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 2639c94b4..6e6d6afd0 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 @@ -5,13 +5,15 @@ import android.os.Bundle 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.SocialRepository import com.habitrpg.android.habitica.data.UserRepository import com.habitrpg.android.habitica.databinding.FragmentGiftGemBalanceBinding -import com.habitrpg.android.habitica.helpers.RxErrorHandler +import com.habitrpg.android.habitica.helpers.ExceptionHandler 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,14 +66,16 @@ class GiftBalanceGemsFragment : BaseFragment() { giftedMember?.id?.let { compositeSubscription.add( socialRepository.transferGems(it, amount) - .flatMap { userRepository.retrieveUser(false, true) } .doOnError { isGifting = false } .subscribe( { - activity?.finish() - }, RxErrorHandler.handleEmptyError() + lifecycleScope.launch(ExceptionHandler.coroutine()) { + userRepository.retrieveUser(false) + } + activity?.finish() + }, ExceptionHandler.rx() ) ) } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/purchases/GiftPurchaseGemsFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/purchases/GiftPurchaseGemsFragment.kt index e62ba9fb8..2babff589 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/purchases/GiftPurchaseGemsFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/purchases/GiftPurchaseGemsFragment.kt @@ -9,16 +9,17 @@ import com.android.billingclient.api.SkuDetails import com.habitrpg.android.habitica.components.UserComponent import com.habitrpg.android.habitica.data.SocialRepository import com.habitrpg.android.habitica.databinding.FragmentGiftGemPurchaseBinding +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.models.members.Member import com.habitrpg.android.habitica.ui.GemPurchaseOptionsView import com.habitrpg.android.habitica.ui.fragments.BaseFragment -import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext +import javax.inject.Inject class GiftPurchaseGemsFragment : BaseFragment() { @@ -54,7 +55,7 @@ class GiftPurchaseGemsFragment : BaseFragment() } fun setupCheckout() { - CoroutineScope(Dispatchers.IO).launch { + CoroutineScope(Dispatchers.IO).launch(ExceptionHandler.coroutine()) { val skus = purchaseHandler?.getAllGemSKUs() withContext(Dispatchers.Main) { for (sku in skus ?: emptyList()) { 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 e330d57ce..330503222 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 @@ -8,6 +8,7 @@ import android.view.View import android.view.ViewGroup import android.widget.EditText import androidx.fragment.app.Fragment +import androidx.lifecycle.lifecycleScope import com.android.billingclient.api.SkuDetails import com.habitrpg.android.habitica.R import com.habitrpg.android.habitica.components.UserComponent @@ -15,20 +16,20 @@ import com.habitrpg.android.habitica.data.InventoryRepository import com.habitrpg.android.habitica.data.UserRepository import com.habitrpg.android.habitica.databinding.FragmentSubscriptionBinding import com.habitrpg.android.habitica.extensions.addCancelButton -import com.habitrpg.common.habitica.extensions.isUsingNightModeResources -import com.habitrpg.common.habitica.extensions.layoutInflater import com.habitrpg.android.habitica.helpers.AmplitudeManager import com.habitrpg.android.habitica.helpers.AppConfigManager import com.habitrpg.android.habitica.helpers.PurchaseHandler import com.habitrpg.android.habitica.helpers.PurchaseTypes -import com.habitrpg.android.habitica.helpers.RxErrorHandler +import com.habitrpg.android.habitica.helpers.ExceptionHandler import com.habitrpg.android.habitica.models.user.User import com.habitrpg.android.habitica.ui.activities.GiftSubscriptionActivity import com.habitrpg.android.habitica.ui.fragments.BaseFragment import com.habitrpg.android.habitica.ui.fragments.PromoInfoFragment -import com.habitrpg.common.habitica.extensions.loadImage import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog import com.habitrpg.android.habitica.ui.views.subscriptions.SubscriptionOptionView +import com.habitrpg.common.habitica.extensions.isUsingNightModeResources +import com.habitrpg.common.habitica.extensions.layoutInflater +import com.habitrpg.common.habitica.extensions.loadImage import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -93,7 +94,7 @@ class SubscriptionFragment : BaseFragment() { binding?.subBenefitsMysteryItemIcon?.loadImage("shop_set_mystery_${it.key?.split("_")?.last()}") binding?.subBenefitsMysteryItemText?.text = context?.getString(R.string.subscribe_listitem3_description_new, it.text) }, - RxErrorHandler.handleEmptyError() + ExceptionHandler.rx() ) ) @@ -108,15 +109,11 @@ class SubscriptionFragment : BaseFragment() { } private fun refresh() { - compositeSubscription.add( - userRepository.retrieveUser(withTasks = false, forced = true).subscribe( - { - this.setUser(it) - binding?.refreshLayout?.isRefreshing = false - }, - RxErrorHandler.handleEmptyError() - ) - ) + lifecycleScope.launch(ExceptionHandler.coroutine()) { + val user = userRepository.retrieveUser(true) + user?.let { setUser(it) } + binding?.refreshLayout?.isRefreshing = false + } } override fun injectFragment(component: UserComponent) { @@ -124,7 +121,7 @@ class SubscriptionFragment : BaseFragment() { } fun loadInventory() { - CoroutineScope(Dispatchers.IO).launch { + CoroutineScope(Dispatchers.IO).launch(ExceptionHandler.coroutine()) { val subscriptions = purchaseHandler.getAllSubscriptionProducts() skus = subscriptions withContext(Dispatchers.Main) { @@ -227,17 +224,16 @@ class SubscriptionFragment : BaseFragment() { } private fun checkIfNeedsCancellation() { - CoroutineScope(Dispatchers.IO).launch { + CoroutineScope(Dispatchers.IO).launch(ExceptionHandler.coroutine()) { val newestSubscription = purchaseHandler.checkForSubscription() if (user?.purchased?.plan?.paymentMethod == "Google" && user?.purchased?.plan?.isActive == true && user?.purchased?.plan?.dateTerminated == null && (newestSubscription?.isAutoRenewing != true) ) { - compositeSubscription.add( + lifecycleScope.launch(ExceptionHandler.coroutine()) { purchaseHandler.cancelSubscription() - .subscribe({ }, RxErrorHandler.handleEmptyError()) - ) + } } } } 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 6d17af42a..8746f1ca1 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 @@ -8,16 +8,20 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.core.content.ContextCompat +import androidx.lifecycle.lifecycleScope import com.habitrpg.android.habitica.R 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.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.firstOrNull +import kotlinx.coroutines.launch import java.util.concurrent.TimeUnit import javax.inject.Inject @@ -100,14 +104,13 @@ class WelcomeFragment : BaseFragment() { } ) - compositeSubscription.add( - userRepository.getUserFlowable().firstElement().subscribe { - binding?.displayNameEditText?.setText(it.profile?.name) - displayNameVerificationEvents.onNext(it.profile?.name ?: "") - binding?.usernameEditText?.setText(it.username) - usernameVerificationEvents.onNext(it.username ?: "") - } - ) + 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 ?: "") + } } override fun injectFragment(component: UserComponent) { 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 70be854dd..e677357d5 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,7 +9,7 @@ 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.RxErrorHandler +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 @@ -59,7 +59,7 @@ class SkillTasksRecyclerViewFragment : BaseFragment { taskSelectionEvents.onNext(it) }, - RxErrorHandler.handleEmptyError() + ExceptionHandler.rx() ) ) binding?.recyclerView?.adapter = adapter 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 c5d47dd86..428f6ef7c 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 @@ -14,7 +14,7 @@ import com.habitrpg.android.habitica.R import com.habitrpg.android.habitica.components.UserComponent import com.habitrpg.android.habitica.databinding.FragmentSkillsBinding import com.habitrpg.android.habitica.extensions.subscribeWithErrorHandler -import com.habitrpg.android.habitica.helpers.RxErrorHandler +import com.habitrpg.android.habitica.helpers.ExceptionHandler import com.habitrpg.android.habitica.models.Skill import com.habitrpg.android.habitica.models.responses.SkillResponse import com.habitrpg.android.habitica.models.user.User @@ -91,7 +91,7 @@ class SkillsFragment : BaseMainFragment() { allEntries.add(item) } return@combineLatest allEntries - }.subscribe({ skills -> adapter?.setSkillList(skills) }, RxErrorHandler.handleEmptyError()) + }.subscribe({ skills -> adapter?.setSkillList(skills) }, ExceptionHandler.rx()) } private fun onSkillSelected(skill: Skill) { @@ -128,7 +128,7 @@ class SkillsFragment : BaseMainFragment() { } } if (response.damage > 0) { - lifecycleScope.launch { + lifecycleScope.launch(ExceptionHandler.coroutine()) { delay(2000L) if (!isAdded) return@launch showSnackbar( @@ -140,7 +140,9 @@ class SkillsFragment : BaseMainFragment() { ) } } - compositeSubscription.add(userRepository.retrieveUser(false).subscribe({ }, RxErrorHandler.handleEmptyError())) + lifecycleScope.launch(ExceptionHandler.coroutine()) { + userRepository.retrieveUser(true) + } } private val taskSelectionResult = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { @@ -167,7 +169,7 @@ class SkillsFragment : BaseMainFragment() { compositeSubscription.add( observable.subscribe( { skillResponse -> this.displaySkillResult(skill, skillResponse) }, - RxErrorHandler.handleEmptyError() + ExceptionHandler.rx() ) ) } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/ChatFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/ChatFragment.kt index 174f229ee..62347b671 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/ChatFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/ChatFragment.kt @@ -13,8 +13,8 @@ import com.habitrpg.android.habitica.R import com.habitrpg.android.habitica.components.UserComponent import com.habitrpg.android.habitica.databinding.FragmentChatBinding 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.RxErrorHandler import com.habitrpg.android.habitica.models.social.ChatMessage import com.habitrpg.android.habitica.ui.activities.FullProfileActivity import com.habitrpg.android.habitica.ui.activities.MainActivity @@ -72,13 +72,13 @@ class ChatFragment() : BaseFragment() { compositeSubscription.add( adapter.getUserLabelClickFlowable().subscribe( { userId -> FullProfileActivity.open(userId) }, - RxErrorHandler.handleEmptyError() + ExceptionHandler.rx() ) ) - compositeSubscription.add(adapter.getDeleteMessageFlowable().subscribe({ this.showDeleteConfirmationDialog(it) }, RxErrorHandler.handleEmptyError())) - compositeSubscription.add(adapter.getFlagMessageClickFlowable().subscribe({ this.showFlagConfirmationDialog(it) }, RxErrorHandler.handleEmptyError())) - compositeSubscription.add(adapter.getReplyMessageEvents().subscribe({ setReplyTo(it) }, RxErrorHandler.handleEmptyError())) - compositeSubscription.add(adapter.getCopyMessageFlowable().subscribe({ this.copyMessageToClipboard(it) }, RxErrorHandler.handleEmptyError())) + compositeSubscription.add(adapter.getDeleteMessageFlowable().subscribe({ this.showDeleteConfirmationDialog(it) }, ExceptionHandler.rx())) + compositeSubscription.add(adapter.getFlagMessageClickFlowable().subscribe({ this.showFlagConfirmationDialog(it) }, ExceptionHandler.rx())) + compositeSubscription.add(adapter.getReplyMessageEvents().subscribe({ setReplyTo(it) }, ExceptionHandler.rx())) + compositeSubscription.add(adapter.getCopyMessageFlowable().subscribe({ this.copyMessageToClipboard(it) }, ExceptionHandler.rx())) adapter.onMessageLike = { viewModel?.likeMessage(it) } } @@ -141,7 +141,7 @@ class ChatFragment() : BaseFragment() { { refresh() }, - RxErrorHandler.handleEmptyError() + ExceptionHandler.rx() ) refresh() } 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 ffc46f7de..9c87e9eaf 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 @@ -12,6 +12,7 @@ import android.view.View import android.view.ViewGroup import androidx.appcompat.app.AlertDialog import androidx.fragment.app.viewModels +import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.RecyclerView import com.habitrpg.android.habitica.MainNavDirections import com.habitrpg.android.habitica.R @@ -20,14 +21,13 @@ import com.habitrpg.android.habitica.data.SocialRepository import com.habitrpg.android.habitica.databinding.FragmentInboxMessageListBinding 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.RxErrorHandler import com.habitrpg.android.habitica.models.social.ChatMessage import com.habitrpg.android.habitica.ui.activities.FullProfileActivity import com.habitrpg.android.habitica.ui.activities.MainActivity import com.habitrpg.android.habitica.ui.adapter.social.InboxAdapter import com.habitrpg.android.habitica.ui.fragments.BaseMainFragment -import com.habitrpg.android.habitica.ui.helpers.KeyboardUtil import com.habitrpg.android.habitica.ui.helpers.SafeDefaultItemAnimator import com.habitrpg.android.habitica.ui.viewmodels.InboxViewModel import com.habitrpg.android.habitica.ui.viewmodels.InboxViewModelFactory @@ -37,8 +37,12 @@ import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers import io.reactivex.rxjava3.core.Observable import io.reactivex.rxjava3.disposables.Disposable +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch import java.util.concurrent.TimeUnit import javax.inject.Inject +import kotlin.time.DurationUnit +import kotlin.time.toDuration class InboxMessageListFragment : BaseMainFragment() { @@ -84,48 +88,44 @@ class InboxMessageListFragment : BaseMainFragment + compositeSubscription.add( + adapter.getUserLabelClickFlowable().subscribe( + { + FullProfileActivity.open(it) + }, + ExceptionHandler.rx() + ) + ) + compositeSubscription.add(adapter.getDeleteMessageFlowable().subscribe({ showDeleteConfirmationDialog(it) }, ExceptionHandler.rx())) + compositeSubscription.add(adapter.getFlagMessageClickFlowable().subscribe({ showFlagConfirmationDialog(it) }, ExceptionHandler.rx())) + compositeSubscription.add(adapter.getCopyMessageFlowable().subscribe({ copyMessageToClipboard(it) }, ExceptionHandler.rx())) + } + } + + viewModel.messages.observe(viewLifecycleOwner) { + markMessagesAsRead(it) + chatAdapter?.submitList(it) } - compositeSubscription.add( - observable.subscribe( - { member -> - setReceivingUser(member.username, member.id) - activity?.title = member.displayName - chatAdapter = InboxAdapter(viewModel.user.value, member) - chatAdapter?.registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() { - override fun onItemRangeInserted(positionStart: Int, itemCount: Int) { - if (positionStart == 0) { - binding?.recyclerView?.scrollToPosition(0) - } - } - }) - viewModel.messages.observe(this.viewLifecycleOwner) { - markMessagesAsRead(it) - chatAdapter?.submitList(it) - } - binding?.recyclerView?.adapter = chatAdapter - binding?.recyclerView?.itemAnimator = SafeDefaultItemAnimator() - chatAdapter?.let { adapter -> - compositeSubscription.add( - adapter.getUserLabelClickFlowable().subscribe( - { - FullProfileActivity.open(it) - }, - RxErrorHandler.handleEmptyError() - ) - ) - compositeSubscription.add(adapter.getDeleteMessageFlowable().subscribe({ this.showDeleteConfirmationDialog(it) }, RxErrorHandler.handleEmptyError())) - compositeSubscription.add(adapter.getFlagMessageClickFlowable().subscribe({ this.showFlagConfirmationDialog(it) }, RxErrorHandler.handleEmptyError())) - compositeSubscription.add(adapter.getCopyMessageFlowable().subscribe({ this.copyMessageToClipboard(it) }, RxErrorHandler.handleEmptyError())) - } - }, - RxErrorHandler.handleEmptyError() - ) - ) binding?.chatBarView?.sendAction = { sendMessage(it) } binding?.chatBarView?.maxChatLength = configManager.maxChatLength() @@ -196,7 +196,7 @@ class InboxMessageListFragment : BaseMainFragment - socialRepository.postPrivateMessage(userID, chatText) - .delay(200, TimeUnit.MILLISECONDS) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe( - { - viewModel.invalidateDataSource() - }, - { error -> - RxErrorHandler.reportError(error) - binding?.let { - val alert = HabiticaAlertDialog(it.chatBarView.context) - alert.setTitle("You cannot reply to this conversation") - alert.setMessage("This user is unable to receive your private message") - alert.addOkButton() - alert.show() - } - binding?.chatBarView?.message = chatText - } - ) - KeyboardUtil.dismissKeyboard(getActivity()) + lifecycleScope.launch(ExceptionHandler.coroutine { error -> + ExceptionHandler.reportError(error) + binding?.let { + val alert = HabiticaAlertDialog(it.chatBarView.context) + alert.setTitle("You cannot reply to this conversation") + alert.setMessage("This user is unable to receive your private message") + alert.addOkButton() + alert.show() + } + binding?.chatBarView?.message = chatText + }) { + socialRepository.postPrivateMessage(userID, chatText) + delay(200.toDuration(DurationUnit.MILLISECONDS)) + viewModel.invalidateDataSource() + } } } @@ -274,7 +264,7 @@ class InboxMessageListFragment : BaseMainFragment socialRepository.deleteMessage(chatMessage).subscribe({ }, RxErrorHandler.handleEmptyError()) } + .setPositiveButton(R.string.yes) { _, _ -> socialRepository.deleteMessage(chatMessage).subscribe({ }, ExceptionHandler.rx()) } .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 e86c4aeef..d0dd57c43 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 @@ -9,6 +9,7 @@ import android.view.MenuItem import android.view.View import android.view.ViewGroup import android.widget.TextView +import androidx.lifecycle.lifecycleScope import com.habitrpg.android.habitica.BuildConfig import com.habitrpg.android.habitica.R import com.habitrpg.android.habitica.components.UserComponent @@ -17,15 +18,16 @@ import com.habitrpg.android.habitica.databinding.DialogChooseMessageRecipientBin import com.habitrpg.android.habitica.databinding.FragmentInboxBinding 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.RxErrorHandler import com.habitrpg.android.habitica.models.social.InboxConversation -import com.habitrpg.common.habitica.views.AvatarView import com.habitrpg.android.habitica.ui.fragments.BaseMainFragment import com.habitrpg.android.habitica.ui.helpers.dismissKeyboard import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel -import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog import com.habitrpg.android.habitica.ui.views.UsernameLabel +import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog +import com.habitrpg.common.habitica.views.AvatarView +import kotlinx.coroutines.launch import javax.inject.Inject class InboxOverviewFragment : BaseMainFragment(), androidx.swiperefreshlayout.widget.SwipeRefreshLayout.OnRefreshListener, View.OnClickListener { @@ -49,7 +51,7 @@ class InboxOverviewFragment : BaseMainFragment(), androidx savedInstanceState: Bundle? ): View? { this.hidesToolbar = true - compositeSubscription.add(this.socialRepository.markPrivateMessagesRead(null).subscribe({ }, RxErrorHandler.handleEmptyError())) + compositeSubscription.add(this.socialRepository.markPrivateMessagesRead(null).subscribe({ }, ExceptionHandler.rx())) return super.onCreateView(inflater, container, savedInstanceState) } @@ -67,14 +69,11 @@ class InboxOverviewFragment : BaseMainFragment(), androidx } private fun loadMessages() { - compositeSubscription.add( - socialRepository.getInboxConversations().subscribe( - { + lifecycleScope.launch(ExceptionHandler.coroutine()) { + socialRepository.getInboxConversations().collect { setInboxMessages(it) - }, - RxErrorHandler.handleEmptyError() - ) - ) + } + } } override fun onDestroy() { @@ -116,18 +115,17 @@ class InboxOverviewFragment : BaseMainFragment(), androidx binding.errorTextView.visibility = View.GONE binding.progressCircular.visibility = View.VISIBLE val username = binding.uuidEditText.text?.toString() ?: "" - socialRepository.getMemberWithUsername(username) - .subscribe( - { - alert.dismiss() - openInboxMessages("", username) - binding.progressCircular.visibility = View.GONE - }, - { - binding.errorTextView.visibility = View.VISIBLE - binding.progressCircular.visibility = View.GONE - } - ) + lifecycleScope.launch(ExceptionHandler.coroutine()) { + val member = socialRepository.retrieveMemberWithUsername(username) + if (member != null) { + alert.dismiss() + openInboxMessages("", username) + binding.progressCircular.visibility = View.GONE + } else { + binding.errorTextView.visibility = View.VISIBLE + binding.progressCircular.visibility = View.GONE + } + } } alert.addButton(getString(R.string.action_cancel), false) { _, _ -> thisActivity.dismissKeyboard() @@ -149,7 +147,7 @@ class InboxOverviewFragment : BaseMainFragment(), androidx { binding?.inboxRefreshLayout?.isRefreshing = false }, - RxErrorHandler.handleEmptyError() + ExceptionHandler.rx() ) ) } 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 3043f06e3..165f63010 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 @@ -10,14 +10,15 @@ import android.view.ViewGroup import android.widget.TextView import androidx.core.content.ContextCompat import androidx.core.text.toHtml +import androidx.lifecycle.lifecycleScope 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.data.SocialRepository 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.RxErrorHandler import com.habitrpg.android.habitica.models.inventory.Quest import com.habitrpg.android.habitica.models.inventory.QuestContent import com.habitrpg.android.habitica.models.members.Member @@ -28,6 +29,12 @@ import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog import com.habitrpg.common.habitica.extensions.loadImage import com.habitrpg.common.habitica.helpers.MarkdownParser +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.filterNotNull +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.launch import javax.inject.Inject import javax.inject.Named @@ -72,30 +79,22 @@ class QuestDetailFragment : BaseMainFragment() { binding?.questCancelButton?.setOnClickListener { onQuestCancel() } binding?.questLeaveButton?.setOnClickListener { onQuestLeave() } - compositeSubscription.add( - userRepository.getUserFlowable() - .map { - it.party?.id ?: "" - } - .skipWhile { it.isBlank() } + lifecycleScope.launch(ExceptionHandler.coroutine()) { + userRepository.getUser() + .map { it?.party?.id } + .filterNotNull() .distinctUntilChanged() - .flatMap { socialRepository.getGroupFlowable(it) } - .doOnNext { updateParty(it) } - .map { - it.quest?.key ?: "" - } - .skipWhile { - it.isBlank() - } + .flatMapLatest { socialRepository.getGroup(it) } + .onEach { updateParty(it) } + .map { it?.quest?.key } + .filterNotNull() .distinctUntilChanged() - .flatMap { inventoryRepository.getQuestContent(it) } - .subscribe( - { - updateQuestContent(it) - }, - RxErrorHandler.handleEmptyError() - ) - ) + .flatMapLatest { inventoryRepository.getQuestContent(it) } + .filterNotNull() + .collect { + updateQuestContent(it) + } + } } private fun updateParty(group: Group?) { @@ -105,16 +104,12 @@ class QuestDetailFragment : BaseMainFragment() { party = group quest = group.quest setQuestParticipants(group.quest?.participants) - compositeSubscription.add( - socialRepository.getMember(quest?.leader).subscribe( - { member -> - if (context != null && binding?.questLeaderView != null) { - binding?.questLeaderView?.text = context?.getString(R.string.quest_leader_header, member.displayName) - } - }, - RxErrorHandler.handleEmptyError() - ) - ) + 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) + } + } val user = userViewModel.user.value if (binding?.questResponseWrapper != null) { @@ -223,7 +218,7 @@ class QuestDetailFragment : BaseMainFragment() { val party = party if (party != null) { socialRepository.forceStartQuest(party) - .subscribe({ }, RxErrorHandler.handleEmptyError()) + .subscribe({ }, ExceptionHandler.rx()) } } alert.addButton(R.string.no, false) @@ -239,10 +234,12 @@ class QuestDetailFragment : BaseMainFragment() { .setMessage(R.string.quest_abort_message) .setPositiveButton(R.string.yes) { _, _ -> party?.id?.let { partyID -> - @Suppress("DEPRECATION") socialRepository.abortQuest(partyID) - .flatMap { userRepository.retrieveUser() } - .subscribe({ getActivity()?.supportFragmentManager?.popBackStack() }, RxErrorHandler.handleEmptyError()) + .subscribe({ + lifecycleScope.launch(ExceptionHandler.coroutine()) { + userRepository.retrieveUser(true) + } + getActivity()?.supportFragmentManager?.popBackStack() }, ExceptionHandler.rx()) } }.setNegativeButton(R.string.no) { _, _ -> } builder.show() @@ -251,10 +248,13 @@ class QuestDetailFragment : BaseMainFragment() { alert.setMessage(R.string.quest_cancel_message) alert.addButton(R.string.yes, true) { _, _ -> party?.id?.let { partyID -> - @Suppress("DEPRECATION") socialRepository.cancelQuest(partyID) - .flatMap { userRepository.retrieveUser() } - .subscribe({ getActivity()?.supportFragmentManager?.popBackStack() }, RxErrorHandler.handleEmptyError()) + .subscribe({ + lifecycleScope.launch(ExceptionHandler.coroutine()) { + userRepository.retrieveUser(true) + } + getActivity()?.supportFragmentManager?.popBackStack() + }, ExceptionHandler.rx()) } } alert.addButton(R.string.no, false) @@ -269,11 +269,13 @@ 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 -> - @Suppress("DEPRECATION") socialRepository.leaveQuest(partyID) - .flatMap { userRepository.retrieveUser() } - .flatMap { socialRepository.retrieveGroup(partyID) } - .subscribe({ getActivity()?.supportFragmentManager?.popBackStack() }, RxErrorHandler.handleEmptyError()) + .subscribe({ + lifecycleScope.launch(ExceptionHandler.coroutine()) { + socialRepository.retrieveGroup(partyID) + userRepository.retrieveUser(true) + } + getActivity()?.supportFragmentManager?.popBackStack() }, ExceptionHandler.rx()) } }.setNegativeButton(R.string.no) { _, _ -> } builder.show() diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/TavernDetailFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/TavernDetailFragment.kt index 76697e5fe..a1788d525 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/TavernDetailFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/TavernDetailFragment.kt @@ -11,6 +11,7 @@ import android.widget.FrameLayout import android.widget.TextView 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.InventoryRepository @@ -19,8 +20,8 @@ import com.habitrpg.android.habitica.data.UserRepository import com.habitrpg.android.habitica.databinding.FragmentTavernDetailBinding import com.habitrpg.android.habitica.extensions.setTintWith 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.RxErrorHandler import com.habitrpg.android.habitica.models.inventory.QuestContent import com.habitrpg.android.habitica.models.social.Group import com.habitrpg.android.habitica.models.user.User @@ -29,6 +30,11 @@ import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel import com.habitrpg.android.habitica.ui.views.UsernameLabel import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog import com.habitrpg.common.habitica.models.PlayerTier +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.firstOrNull +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.launch import javax.inject.Inject class TavernDetailFragment : BaseFragment() { @@ -73,33 +79,30 @@ class TavernDetailFragment : BaseFragment() { addPlayerTiers() bindButtons() - compositeSubscription.add( - socialRepository.getGroupFlowable(Group.TAVERN_ID) - .doOnNext { if (!it.hasActiveQuest) binding?.worldBossSection?.visibility = View.GONE } - .filter { it.hasActiveQuest } - .doOnNext { - binding?.questProgressView?.progress = it.quest + lifecycleScope.launch(ExceptionHandler.coroutine()) { + socialRepository.getGroup(Group.TAVERN_ID) + .onEach { if (it?.hasActiveQuest == false) binding?.worldBossSection?.visibility = View.GONE } + .filter { it != null && it.hasActiveQuest } + .onEach { + binding?.questProgressView?.progress = it?.quest binding?.shopHeader?.descriptionView?.setText(R.string.tavern_description_world_boss) - val filtered = it.quest?.rageStrikes?.filter { strike -> strike.key == "tavern" } - if (filtered?.size ?: 0 > 0 && filtered?.get(0)?.wasHit == true) { + val filtered = it?.quest?.rageStrikes?.filter { strike -> strike.key == "tavern" } + if ((filtered?.size ?: 0) > 0 && filtered?.get(0)?.wasHit == true) { val key = it.quest?.key if (key != null) { shopSpriteSuffix = key } } } - .flatMapMaybe { inventoryRepository.getQuestContent(it.quest?.key ?: "").firstElement() } - .subscribe( - { - binding?.questProgressView?.quest = it - binding?.worldBossSection?.visibility = View.VISIBLE - }, - RxErrorHandler.handleEmptyError() - ) - ) - - compositeSubscription.add(socialRepository.retrieveGroup(Group.TAVERN_ID).subscribe({ }, RxErrorHandler.handleEmptyError())) + .map { inventoryRepository.getQuestContent(it?.quest?.key ?: "").firstOrNull() } + .collect { + binding?.questProgressView?.quest = it + binding?.worldBossSection?.visibility = View.VISIBLE + } + } + lifecycleScope.launch(ExceptionHandler.coroutine()) { socialRepository.retrieveGroup(Group.TAVERN_ID) } + user?.let { binding?.questProgressView?.configure(it) } } @@ -112,7 +115,9 @@ class TavernDetailFragment : BaseFragment() { private fun bindButtons() { binding?.innButton?.setOnClickListener { - user?.let { user -> userRepository.sleep(user).subscribe({ }, RxErrorHandler.handleEmptyError()) } + lifecycleScope.launch(ExceptionHandler.coroutine()) { + user?.let { user -> userRepository.sleep(user) } + } } binding?.guidelinesButton?.setOnClickListener { MainNavigationController.navigate(R.id.guidelinesActivity) 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 d42a2462c..0e19c117c 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,6 +11,7 @@ import android.view.MenuItem import android.view.View import android.view.ViewGroup import androidx.core.net.toUri +import androidx.lifecycle.lifecycleScope import com.habitrpg.android.habitica.R import com.habitrpg.android.habitica.components.UserComponent import com.habitrpg.android.habitica.data.ChallengeRepository @@ -19,8 +20,8 @@ import com.habitrpg.android.habitica.databinding.DialogChallengeDetailTaskGroupB import com.habitrpg.android.habitica.databinding.FragmentChallengeDetailBinding 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.RxErrorHandler import com.habitrpg.android.habitica.models.members.Member import com.habitrpg.android.habitica.models.social.Challenge import com.habitrpg.android.habitica.models.tasks.Task @@ -32,11 +33,12 @@ import com.habitrpg.android.habitica.ui.viewHolders.tasks.HabitViewHolder 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.MainUserViewModel +import com.habitrpg.android.habitica.ui.views.HabiticaIconsHelper 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 com.habitrpg.android.habitica.ui.views.HabiticaIconsHelper +import kotlinx.coroutines.launch import retrofit2.HttpException import javax.inject.Inject @@ -101,10 +103,11 @@ class ChallengeDetailFragment : BaseMainFragment return@map (it.leaderId ?: "") } .filter { it.isNotEmpty() } - .flatMap { creatorID -> - return@flatMap socialRepository.getMember(creatorID) - } - .subscribe({ set(it) }, RxErrorHandler.handleEmptyError()) + .subscribe({ + lifecycleScope.launch(ExceptionHandler.coroutine()) { + set(socialRepository.retrieveMember(it)) + } + }, ExceptionHandler.rx()) ) compositeSubscription.add( challengeRepository.getChallengeTasks(id).subscribe( @@ -142,7 +145,7 @@ class ChallengeDetailFragment : BaseMainFragment addRewards(rewards) } }, - RxErrorHandler.handleEmptyError() + ExceptionHandler.rx() ) ) @@ -151,7 +154,7 @@ class ChallengeDetailFragment : BaseMainFragment { isMember -> setJoined(isMember) }, - RxErrorHandler.handleEmptyError() + ExceptionHandler.rx() ) ) } @@ -159,8 +162,11 @@ class ChallengeDetailFragment : BaseMainFragment binding?.joinButton?.setOnClickListener { challenge?.let { challenge -> challengeRepository.joinChallenge(challenge) - .flatMap { userRepository.retrieveUser(true) } - .subscribe({}, RxErrorHandler.handleEmptyError()) + .subscribe({ + lifecycleScope.launch(ExceptionHandler.coroutine()) { + userRepository.retrieveUser(true) + } + }, ExceptionHandler.rx()) } } binding?.leaveButton?.setOnClickListener { showChallengeLeaveDialog() } @@ -230,7 +236,7 @@ class ChallengeDetailFragment : BaseMainFragment if (it is HttpException && it.code() == 404) { MainNavigationController.navigateBack() } - RxErrorHandler.reportError(it) + ExceptionHandler.reportError(it) }) } } @@ -245,7 +251,8 @@ class ChallengeDetailFragment : BaseMainFragment binding?.participantCount?.text = challenge.memberCount.toString() } - private fun set(creator: Member) { + private fun set(creator: Member?) { + if (creator == null) return binding?.creatorAvatarview?.setAvatar(creator) binding?.creatorLabel?.tier = creator.contributor?.level ?: 0 binding?.creatorLabel?.username = creator.displayName @@ -335,11 +342,11 @@ 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({}, RxErrorHandler.handleEmptyError()) + challengeRepository.leaveChallenge(challenge, "keep-all").subscribe({}, ExceptionHandler.rx()) } alert.addButton(R.string.leave_delete_tasks, isPrimary = false, isDestructive = true) { _, _ -> val challenge = challenge ?: return@addButton - challengeRepository.leaveChallenge(challenge, "remove-all").subscribe({}, RxErrorHandler.handleEmptyError()) + challengeRepository.leaveChallenge(challenge, "remove-all").subscribe({}, ExceptionHandler.rx()) } 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 7816db106..5587f488d 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 @@ -4,6 +4,7 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.RecyclerView import com.habitrpg.android.habitica.R import com.habitrpg.android.habitica.components.UserComponent @@ -11,8 +12,8 @@ import com.habitrpg.android.habitica.data.ChallengeRepository import com.habitrpg.android.habitica.data.SocialRepository 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.RxErrorHandler import com.habitrpg.android.habitica.models.social.Challenge import com.habitrpg.android.habitica.models.social.Group import com.habitrpg.android.habitica.modules.AppModule @@ -21,7 +22,8 @@ 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 io.reactivex.rxjava3.kotlin.Flowables +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.launch import javax.inject.Inject import javax.inject.Named @@ -66,7 +68,7 @@ class ChallengeListFragment : BaseFragment() super.onViewCreated(view, savedInstanceState) challengeAdapter = ChallengesListViewAdapter(viewUserChallengesOnly, userId) - challengeAdapter?.getOpenDetailFragmentFlowable()?.subscribe({ openDetailFragment(it) }, RxErrorHandler.handleEmptyError()) + challengeAdapter?.getOpenDetailFragmentFlowable()?.subscribe({ openDetailFragment(it) }, ExceptionHandler.rx()) ?.let { compositeSubscription.add(it) } binding?.refreshLayout?.setOnRefreshListener(this) @@ -83,16 +85,15 @@ class ChallengeListFragment : BaseFragment() binding?.recyclerView?.setBackgroundResource(R.color.content_background) } - compositeSubscription.add( - Flowables.combineLatest(socialRepository.getGroupFlowable(Group.TAVERN_ID), socialRepository.getUserGroups("guild")).subscribe( - { - this.filterGroups = mutableListOf() - filterGroups?.add(it.first) - filterGroups?.addAll(it.second) - }, - RxErrorHandler.handleEmptyError() - ) - ) + lifecycleScope.launch(ExceptionHandler.coroutine()) { + 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) + } + } binding?.recyclerView?.itemAnimator = SafeDefaultItemAnimator() @@ -144,7 +145,7 @@ class ChallengeListFragment : BaseFragment() this.challenges = challenges challengeAdapter?.updateUnfilteredData(challenges) }, - RxErrorHandler.handleEmptyError() + ExceptionHandler.rx() ) ) } @@ -164,7 +165,7 @@ class ChallengeListFragment : BaseFragment() } nextPageToLoad += 1 }, - RxErrorHandler.handleEmptyError() + ExceptionHandler.rx() ) ) } 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 7b090ab0a..848024aa1 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 @@ -5,17 +5,19 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.appcompat.widget.SearchView +import androidx.lifecycle.lifecycleScope import androidx.swiperefreshlayout.widget.SwipeRefreshLayout 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.FragmentRefreshRecyclerviewBinding -import com.habitrpg.android.habitica.helpers.RxErrorHandler +import com.habitrpg.android.habitica.helpers.ExceptionHandler import com.habitrpg.android.habitica.ui.adapter.social.GuildListAdapter import com.habitrpg.android.habitica.ui.fragments.BaseFragment -import com.habitrpg.common.habitica.helpers.EmptyItem import com.habitrpg.android.habitica.ui.helpers.KeyboardUtil import com.habitrpg.android.habitica.ui.helpers.SafeDefaultItemAnimator +import com.habitrpg.common.habitica.helpers.EmptyItem +import kotlinx.coroutines.launch import javax.inject.Inject class GuildListFragment : BaseFragment(), SearchView.OnQueryTextListener, SearchView.OnCloseListener, SwipeRefreshLayout.OnRefreshListener { @@ -52,7 +54,12 @@ class GuildListFragment : BaseFragment(), Se viewAdapter.onlyShowUsersGuilds = onlyShowUsersGuilds if (onlyShowUsersGuilds) { - compositeSubscription.add(socialRepository.getUserGroups("guild").subscribe({ viewAdapter.setUnfilteredData(it) }, RxErrorHandler.handleEmptyError())) + lifecycleScope.launch(ExceptionHandler.coroutine()) { + socialRepository.getUserGroups("guild") + .collect { + viewAdapter.setUnfilteredData(it) + } + } } else { compositeSubscription.add( this.socialRepository.getPublicGuilds() @@ -60,7 +67,7 @@ class GuildListFragment : BaseFragment(), Se { groups -> this@GuildListFragment.viewAdapter.setUnfilteredData(groups) }, - RxErrorHandler.handleEmptyError() + ExceptionHandler.rx() ) ) } @@ -79,7 +86,7 @@ class GuildListFragment : BaseFragment(), Se { binding?.refreshLayout?.isRefreshing = false }, - RxErrorHandler.handleEmptyError() + ExceptionHandler.rx() ) ) } 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 eeb1199d5..f9ed1ea99 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 @@ -16,21 +16,23 @@ import android.widget.TextView import androidx.activity.result.contract.ActivityResultContracts import androidx.core.graphics.drawable.toBitmap import androidx.core.os.bundleOf +import androidx.lifecycle.lifecycleScope import com.google.firebase.analytics.FirebaseAnalytics 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.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.RxErrorHandler -import com.habitrpg.common.habitica.views.AvatarView import com.habitrpg.android.habitica.ui.activities.GroupFormActivity import com.habitrpg.android.habitica.ui.fragments.BaseMainFragment -import com.habitrpg.common.habitica.extensions.DataBindingUtils -import com.habitrpg.common.habitica.helpers.setMarkdown import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel import com.habitrpg.android.habitica.ui.views.HabiticaSnackbar +import com.habitrpg.common.habitica.extensions.DataBindingUtils +import com.habitrpg.common.habitica.helpers.setMarkdown +import com.habitrpg.common.habitica.views.AvatarView +import kotlinx.coroutines.launch import javax.inject.Inject import kotlin.math.roundToInt @@ -64,36 +66,34 @@ class NoPartyFragmentFragment : BaseMainFragment() { binding?.refreshLayout?.setOnRefreshListener { this.refresh() } binding?.invitationsView?.acceptCall = { - socialRepository.joinGroup(it) - .flatMap { userRepository.retrieveUser(false) } - .subscribe( - { - parentFragmentManager.popBackStack() - MainNavigationController.navigate( - R.id.partyFragment, - bundleOf(Pair("partyID", userViewModel.partyID)) - ) - }, - RxErrorHandler.handleEmptyError() + lifecycleScope.launch(ExceptionHandler.coroutine()) { + socialRepository.joinGroup(it) + userRepository.retrieveUser(false) + parentFragmentManager.popBackStack() + MainNavigationController.navigate( + R.id.partyFragment, + bundleOf(Pair("partyID", userViewModel.partyID)) ) + } } binding?.invitationsView?.rejectCall = { - socialRepository.rejectGroupInvite(it).subscribe({ }, RxErrorHandler.handleEmptyError()) + socialRepository.rejectGroupInvite(it).subscribe({ }, ExceptionHandler.rx()) binding?.invitationWrapper?.visibility = View.GONE } - binding?.invitationsView?.setLeader = { leader -> - compositeSubscription.add( - socialRepository.getMember(leader) - .subscribe( - { - binding?.root?.findViewById(R.id.groupleader_avatar_view)?.setAvatar(it) - binding?.root?.findViewById(R.id.groupleader_text_view)?.text = getString(R.string.invitation_title, it.displayName, binding?.invitationsView?.groupName) - }, - RxErrorHandler.handleEmptyError() - ) - ) + binding?.invitationsView?.setLeader = { leaderID -> + lifecycleScope.launch(ExceptionHandler.coroutine()) { + val leader = socialRepository.retrieveMember(leaderID) ?: return@launch + binding?.root?.findViewById(R.id.groupleader_avatar_view) + ?.setAvatar(leader) + binding?.root?.findViewById(R.id.groupleader_text_view)?.text = + getString( + R.string.invitation_title, + leader.displayName, + binding?.invitationsView?.groupName + ) + } } binding?.usernameTextview?.setOnClickListener { @@ -153,42 +153,38 @@ class NoPartyFragmentFragment : BaseMainFragment() { if (it.resultCode == Activity.RESULT_OK) { val bundle = it.data?.extras if (bundle?.getString("groupType") == "party") { - socialRepository.createGroup( - bundle.getString("name"), - bundle.getString("description"), - bundle.getString("leader"), - "party", - bundle.getString("privacy"), - bundle.getBoolean("leaderCreateChallenge") - ) - .flatMap { + lifecycleScope.launch(ExceptionHandler.coroutine()) { + val group = socialRepository.createGroup( + bundle.getString("name"), + bundle.getString("description"), + bundle.getString("leader"), + "party", + bundle.getString("privacy"), + bundle.getBoolean("leaderCreateChallenge") + ) userRepository.retrieveUser(false) + if (isAdded) { + parentFragmentManager.popBackStack() + } + MainNavigationController.navigate( + R.id.partyFragment, + bundleOf(Pair("partyID", userViewModel.partyID)) + ) } - .subscribe( - { - if (isAdded) { - parentFragmentManager.popBackStack() - } - MainNavigationController.navigate( - R.id.partyFragment, - bundleOf(Pair("partyID", userViewModel.partyID)) - ) - }, - RxErrorHandler.handleEmptyError() - ) } } } private fun refresh() { - compositeSubscription.add( - userRepository.retrieveUser(false, forced = true) - .filter { it.hasParty } - .flatMap { socialRepository.retrieveGroup("party") } - .flatMap { group1 -> socialRepository.retrieveGroupMembers(group1.id, true) } - .doOnComplete { binding?.refreshLayout?.isRefreshing = false } - .subscribe({ }, RxErrorHandler.handleEmptyError()) - ) + lifecycleScope.launch(ExceptionHandler.coroutine()) { + val user = userRepository.retrieveUser(false, true) + if (user?.hasParty == true) { + lifecycleScope.launch(ExceptionHandler.coroutine()) { + val group = socialRepository.retrieveGroup("party") + socialRepository.retrieveGroupMembers(group?.id ?: "", true) + } + } + } } override fun onDestroy() { 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 134d560ca..a24a53493 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 @@ -17,32 +17,33 @@ import com.habitrpg.android.habitica.data.InventoryRepository import com.habitrpg.android.habitica.data.SocialRepository import com.habitrpg.android.habitica.data.UserRepository import com.habitrpg.android.habitica.databinding.FragmentPartyDetailBinding -import com.habitrpg.common.habitica.extensions.dpToPx 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.RxErrorHandler import com.habitrpg.android.habitica.models.inventory.QuestContent import com.habitrpg.android.habitica.models.members.Member import com.habitrpg.android.habitica.models.social.Challenge import com.habitrpg.android.habitica.models.social.Group import com.habitrpg.android.habitica.models.user.User import com.habitrpg.android.habitica.modules.AppModule -import com.habitrpg.common.habitica.views.AvatarView import com.habitrpg.android.habitica.ui.activities.FullProfileActivity import com.habitrpg.android.habitica.ui.activities.MainActivity import com.habitrpg.android.habitica.ui.fragments.BaseFragment import com.habitrpg.android.habitica.ui.fragments.inventory.items.ItemDialogFragment -import com.habitrpg.common.habitica.extensions.DataBindingUtils import com.habitrpg.android.habitica.ui.helpers.dismissKeyboard -import com.habitrpg.common.habitica.extensions.loadImage -import com.habitrpg.common.habitica.helpers.setMarkdown import com.habitrpg.android.habitica.ui.viewHolders.GroupMemberViewHolder import com.habitrpg.android.habitica.ui.viewmodels.PartyViewModel import com.habitrpg.android.habitica.ui.views.HabiticaSnackbar import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog +import com.habitrpg.common.habitica.extensions.DataBindingUtils +import com.habitrpg.common.habitica.extensions.dpToPx +import com.habitrpg.common.habitica.extensions.loadImage +import com.habitrpg.common.habitica.helpers.setMarkdown +import com.habitrpg.common.habitica.views.AvatarView import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.launch import javax.inject.Inject import javax.inject.Named @@ -95,28 +96,29 @@ class PartyDetailFragment : BaseFragment() { binding?.invitationsView?.acceptCall = { viewModel?.joinGroup(it) { - compositeSubscription.add( - userRepository.retrieveUser(false) - .subscribe { user -> - parentFragmentManager.popBackStack() - MainNavigationController.navigate( - R.id.partyFragment, - bundleOf(Pair("partyID", user.party?.id)) - ) - } - ) + lifecycleScope.launch(ExceptionHandler.coroutine()) { + val user = userRepository.retrieveUser(false) + parentFragmentManager.popBackStack() + MainNavigationController.navigate( + R.id.partyFragment, + bundleOf(Pair("partyID", user?.party?.id)) + ) + } } } binding?.invitationsView?.rejectCall = { socialRepository.rejectGroupInvite(it) - .flatMap { userRepository.retrieveUser(false, true) } - .subscribe({ }, RxErrorHandler.handleEmptyError()) + .subscribe({ + lifecycleScope.launch(ExceptionHandler.coroutine()) { + userRepository.retrieveUser(false, true) + } + }, ExceptionHandler.rx()) } - viewModel?.getGroupData()?.observe(viewLifecycleOwner, { updateParty(it) }) - viewModel?.user?.observe(viewLifecycleOwner, { updateUser(it) }) - viewModel?.getMembersData()?.observe(viewLifecycleOwner, { updateMembersList(it) }) + viewModel?.getGroupData()?.observe(viewLifecycleOwner) { updateParty(it) } + viewModel?.user?.observe(viewLifecycleOwner) { updateUser(it) } + viewModel?.getMembersData()?.observe(viewLifecycleOwner) { updateMembersList(it) } } private fun refreshParty() { @@ -141,8 +143,10 @@ class PartyDetailFragment : BaseFragment() { binding?.questImageWrapper?.visibility = View.VISIBLE lifecycleScope.launch(Dispatchers.Main) { delay(500) - inventoryRepository.getQuestContent(party.quest?.key ?: "") - .subscribe({ this@PartyDetailFragment.updateQuestContent(it) }, RxErrorHandler.handleEmptyError()) + val content = inventoryRepository.getQuestContent(party.quest?.key ?: "").firstOrNull() + if (content != null) { + updateQuestContent(content) + } } } else { binding?.newQuestButton?.visibility = View.VISIBLE @@ -184,16 +188,17 @@ class PartyDetailFragment : BaseFragment() { val groupName = invitation.name leaderID.let { id -> - compositeSubscription.add( - socialRepository.getMember(id) - .subscribe( - { member -> - binding?.root?.findViewById(R.id.groupleader_avatar_view)?.setAvatar(member) - binding?.root?.findViewById(R.id.groupleader_text_view)?.text = getString(R.string.invitation_title, member.displayName, groupName) - }, - RxErrorHandler.handleEmptyError() + lifecycleScope.launch(ExceptionHandler.coroutine()) { + val member = socialRepository.retrieveMember(id) ?: return@launch + binding?.root?.findViewById(R.id.groupleader_avatar_view) + ?.setAvatar(member) + binding?.root?.findViewById(R.id.groupleader_text_view)?.text = + getString( + R.string.invitation_title, + member.displayName, + groupName ) - ) + } } view?.findViewById