Remove RxJava from all repositories

This commit is contained in:
Phillip Thelen 2022-11-16 14:02:58 +01:00
parent cedc06f1e9
commit c4a482eac8
141 changed files with 3548 additions and 3652 deletions

View file

@ -204,9 +204,6 @@
android:theme="@style/Theme.AppCompat.Light.NoActionBar" />
<activity android:name=".ui.activities.GemPurchaseActivity"
android:screenOrientation="unspecified" />
<activity android:name=".ui.activities.VerifyUsernameActivity"
android:screenOrientation="unspecified"
android:windowSoftInputMode="stateHidden" />
<receiver android:name=".receivers.NotificationPublisher" />
<receiver android:name=".receivers.TaskReceiver" android:enabled="true" android:exported="true">

View file

@ -134,7 +134,7 @@ internal class ItemRecyclerFragmentTest : FragmentTestCase<ItemRecyclerFragment,
@Test
fun canHatchPetWithEggs() {
val slot = CapturingSlot<HatchPetUseCase.RequestValues>()
every { hatchPetUseCase.observable(capture(slot)) } returns mockk(relaxed = true)
every { hatchPetUseCase.callInteractor(capture(slot)) } returns mockk(relaxed = true)
fragment.itemType = "eggs"
launchFragment()
screen {
@ -142,7 +142,7 @@ internal class ItemRecyclerFragmentTest : FragmentTestCase<ItemRecyclerFragment,
childWith<ItemItem> { withDescendant { withText("Wolf") } }.click()
KView { withText(R.string.hatch_with_potion) }.click()
KView { withText("Shade") }.click()
verify { hatchPetUseCase.observable(any()) }
verify { hatchPetUseCase.callInteractor(any()) }
slot.captured.egg.key shouldBe "Wolf"
slot.captured.potion.key shouldBe "Shade"
}
@ -152,7 +152,7 @@ internal class ItemRecyclerFragmentTest : FragmentTestCase<ItemRecyclerFragment,
@Test
fun canHatchPetWithPotions() {
val slot = CapturingSlot<HatchPetUseCase.RequestValues>()
every { hatchPetUseCase.observable(capture(slot)) } returns mockk(relaxed = true)
every { hatchPetUseCase.callInteractor(capture(slot)) } returns mockk(relaxed = true)
fragment.itemType = "hatchingPotions"
launchFragment()
screen {
@ -160,7 +160,7 @@ internal class ItemRecyclerFragmentTest : FragmentTestCase<ItemRecyclerFragment,
childWith<ItemItem> { withDescendant { withText("Shade") } }.click()
KView { withText(R.string.hatch_egg) }.click()
KView { withText("Wolf") }.click()
verify { hatchPetUseCase.observable(any()) }
verify { hatchPetUseCase.callInteractor(any()) }
slot.captured.egg.key shouldBe "Wolf"
slot.captured.potion.key shouldBe "Shade"
}

View file

@ -69,7 +69,7 @@ internal class PetDetailRecyclerFragmentTest :
@Test
fun canFeedPet() {
val slot = CapturingSlot<FeedPetUseCase.RequestValues>()
every { feedPetUseCase.observable(capture(slot)) } returns mockk(relaxed = true)
every { feedPetUseCase.callInteractor(capture(slot)) } returns mockk(relaxed = true)
every {
inventoryRepository.getPets(
any(),
@ -92,7 +92,7 @@ internal class PetDetailRecyclerFragmentTest :
childWith<PetItem> { withContentDescription("Skeleton Cactus") }.click()
KView { withText(R.string.feed) }.click()
KView { withText("Meat") }.click()
verify { feedPetUseCase.observable(any()) }
verify { feedPetUseCase.callInteractor(any()) }
slot.captured.pet.key shouldBe "Cactus-Skeleton"
slot.captured.food.key shouldBe "Meat"
}
@ -102,7 +102,7 @@ internal class PetDetailRecyclerFragmentTest :
@Test
fun canUseSaddle() {
val slot = CapturingSlot<FeedPetUseCase.RequestValues>()
every { feedPetUseCase.observable(capture(slot)) } returns mockk(relaxed = true)
every { feedPetUseCase.callInteractor(capture(slot)) } returns mockk(relaxed = true)
every {
inventoryRepository.getPets(
any(),
@ -122,7 +122,7 @@ internal class PetDetailRecyclerFragmentTest :
recycler {
childWith<PetItem> { withContentDescription("Shade Fox") }.click()
KView { withText(R.string.use_saddle) }.click()
verify { feedPetUseCase.observable(any()) }
verify { feedPetUseCase.callInteractor(any()) }
slot.captured.pet.key shouldBe "Fox-Shade"
slot.captured.food.key shouldBe "Saddle"
}

View file

@ -36,7 +36,6 @@ import com.habitrpg.shared.habitica.models.responses.FeedResponse
import com.habitrpg.shared.habitica.models.responses.Status
import com.habitrpg.shared.habitica.models.responses.TaskDirectionData
import com.habitrpg.shared.habitica.models.responses.VerifyUsernameResponse
import io.reactivex.rxjava3.core.Flowable
import retrofit2.http.Body
import retrofit2.http.DELETE
import retrofit2.http.GET
@ -60,7 +59,7 @@ interface ApiService {
@GET("inbox/messages")
suspend fun getInboxMessages(@Query("conversation") uuid: String, @Query("page") page: Int): HabitResponse<List<ChatMessage>>
@GET("inbox/conversations")
fun getInboxConversations(): Flowable<HabitResponse<List<InboxConversation>>>
suspend fun getInboxConversations(): HabitResponse<List<InboxConversation>>
@GET("tasks/user")
suspend fun getTasks(): HabitResponse<TaskList>
@ -75,13 +74,13 @@ interface ApiService {
suspend fun updateUser(@Body updateDictionary: Map<String, Any>): HabitResponse<User>
@PUT("user/")
fun registrationLanguage(@Header("Accept-Language") registrationLanguage: String): Flowable<HabitResponse<User>>
suspend fun registrationLanguage(@Header("Accept-Language") registrationLanguage: String): HabitResponse<User>
@GET("user/in-app-rewards")
suspend fun retrieveInAppRewards(): HabitResponse<List<ShopItem>>
@POST("user/equip/{type}/{key}")
fun equipItem(@Path("type") type: String, @Path("key") itemKey: String): Flowable<HabitResponse<Items>>
suspend fun equipItem(@Path("type") type: String, @Path("key") itemKey: String): HabitResponse<Items>
@POST("user/buy/{key}")
suspend fun buyItem(@Path("key") itemKey: String, @Body quantity: Map<String, Int>): HabitResponse<BuyResponse>
@ -106,72 +105,72 @@ interface ApiService {
suspend fun purchaseSpecialSpell(@Path("key") key: String): HabitResponse<Void>
@POST("user/sell/{type}/{key}")
fun sellItem(@Path("type") itemType: String, @Path("key") itemKey: String): Flowable<HabitResponse<User>>
suspend fun sellItem(@Path("type") itemType: String, @Path("key") itemKey: String): HabitResponse<User>
@POST("user/feed/{pet}/{food}")
fun feedPet(@Path("pet") petKey: String, @Path("food") foodKey: String): Flowable<HabitResponse<FeedResponse>>
suspend fun feedPet(@Path("pet") petKey: String, @Path("food") foodKey: String): HabitResponse<FeedResponse>
@POST("user/hatch/{egg}/{hatchingPotion}")
fun hatchPet(@Path("egg") eggKey: String, @Path("hatchingPotion") hatchingPotionKey: String): Flowable<HabitResponse<Items>>
suspend fun hatchPet(@Path("egg") eggKey: String, @Path("hatchingPotion") hatchingPotionKey: String): HabitResponse<Items>
@GET("tasks/user")
fun getTasks(@Query("type") type: String): Flowable<HabitResponse<TaskList>>
suspend fun getTasks(@Query("type") type: String): HabitResponse<TaskList>
@GET("tasks/user")
fun getTasks(@Query("type") type: String, @Query("dueDate") dueDate: String): Flowable<HabitResponse<TaskList>>
suspend fun getTasks(@Query("type") type: String, @Query("dueDate") dueDate: String): HabitResponse<TaskList>
@POST("user/unlock")
suspend fun unlockPath(@Query("path") path: String): HabitResponse<UnlockResponse>
@GET("tasks/{id}")
fun getTask(@Path("id") id: String): Flowable<HabitResponse<Task>>
suspend fun getTask(@Path("id") id: String): HabitResponse<Task>
@POST("tasks/{id}/score/{direction}")
suspend fun postTaskDirection(@Path("id") id: String, @Path("direction") direction: String): HabitResponse<TaskDirectionData>
@POST("tasks/bulk-score")
fun bulkScoreTasks(@Body data: List<Map<String, String>>): Flowable<HabitResponse<BulkTaskScoringData>>
suspend fun bulkScoreTasks(@Body data: List<Map<String, String>>): HabitResponse<BulkTaskScoringData>
@POST("tasks/{id}/move/to/{position}")
fun postTaskNewPosition(@Path("id") id: String, @Path("position") position: Int): Flowable<HabitResponse<List<String>>>
suspend fun postTaskNewPosition(@Path("id") id: String, @Path("position") position: Int): HabitResponse<List<String>>
@POST("tasks/{taskId}/checklist/{itemId}/score")
suspend fun scoreChecklistItem(@Path("taskId") taskId: String, @Path("itemId") itemId: String): HabitResponse<Task>
@POST("tasks/user")
fun createTask(@Body item: Task): Flowable<HabitResponse<Task>>
suspend fun createTask(@Body item: Task): HabitResponse<Task>
@POST("tasks/user")
fun createTasks(@Body tasks: List<Task>): Flowable<HabitResponse<List<Task>>>
suspend fun createTasks(@Body tasks: List<Task>): HabitResponse<List<Task>>
@PUT("tasks/{id}")
fun updateTask(@Path("id") id: String, @Body item: Task): Flowable<HabitResponse<Task>>
suspend fun updateTask(@Path("id") id: String, @Body item: Task): HabitResponse<Task>
@DELETE("tasks/{id}")
fun deleteTask(@Path("id") id: String): Flowable<HabitResponse<Void>>
suspend fun deleteTask(@Path("id") id: String): HabitResponse<Void>
@POST("tags")
fun createTag(@Body tag: Tag): Flowable<HabitResponse<Tag>>
suspend fun createTag(@Body tag: Tag): HabitResponse<Tag>
@PUT("tags/{id}")
fun updateTag(@Path("id") id: String, @Body tag: Tag): Flowable<HabitResponse<Tag>>
suspend fun updateTag(@Path("id") id: String, @Body tag: Tag): HabitResponse<Tag>
@DELETE("tags/{id}")
fun deleteTag(@Path("id") id: String): Flowable<HabitResponse<Void>>
suspend fun deleteTag(@Path("id") id: String): HabitResponse<Void>
@POST("user/auth/local/register")
fun registerUser(@Body auth: UserAuth): Flowable<HabitResponse<UserAuthResponse>>
suspend fun registerUser(@Body auth: UserAuth): HabitResponse<UserAuthResponse>
@POST("user/auth/local/login")
fun connectLocal(@Body auth: UserAuth): Flowable<HabitResponse<UserAuthResponse>>
suspend fun connectLocal(@Body auth: UserAuth): HabitResponse<UserAuthResponse>
@POST("user/auth/social")
fun connectSocial(@Body auth: UserAuthSocial): Flowable<HabitResponse<UserAuthResponse>>
suspend fun connectSocial(@Body auth: UserAuthSocial): HabitResponse<UserAuthResponse>
@DELETE("user/auth/social/{network}")
fun disconnectSocial(@Path("network") network: String): Flowable<HabitResponse<Void>>
suspend fun disconnectSocial(@Path("network") network: String): HabitResponse<Void>
@POST("user/auth/apple")
fun loginApple(@Body auth: Map<String, Any>): Flowable<HabitResponse<UserAuthResponse>>
suspend fun loginApple(@Body auth: Map<String, Any>): HabitResponse<UserAuthResponse>
@POST("user/sleep")
suspend fun sleep(): HabitResponse<Boolean>
@ -199,12 +198,12 @@ interface ApiService {
suspend fun disableClasses(): HabitResponse<User>
@POST("user/mark-pms-read")
fun markPrivateMessagesRead(): Flowable<Void>
suspend fun markPrivateMessagesRead(): Void
/* Group API */
@GET("groups")
fun listGroups(@Query("type") type: String): Flowable<HabitResponse<List<Group>>>
suspend fun listGroups(@Query("type") type: String): HabitResponse<List<Group>>
@GET("groups/{gid}")
suspend fun getGroup(@Path("gid") groupId: String): HabitResponse<Group>
@ -228,13 +227,13 @@ interface ApiService {
suspend fun leaveGroup(@Path("gid") groupId: String, @Query("keepChallenges") keepChallenges: String): HabitResponse<Void>
@POST("groups/{gid}/chat")
fun postGroupChat(@Path("gid") groupId: String, @Body message: Map<String, String>): Flowable<HabitResponse<PostChatMessageResult>>
suspend fun postGroupChat(@Path("gid") groupId: String, @Body message: Map<String, String>): HabitResponse<PostChatMessageResult>
@DELETE("groups/{gid}/chat/{messageId}")
fun deleteMessage(@Path("gid") groupId: String, @Path("messageId") messageId: String): Flowable<HabitResponse<Void>>
suspend fun deleteMessage(@Path("gid") groupId: String, @Path("messageId") messageId: String): HabitResponse<Void>
@DELETE("inbox/messages/{messageId}")
fun deleteInboxMessage(@Path("messageId") messageId: String): Flowable<HabitResponse<Void>>
suspend fun deleteInboxMessage(@Path("messageId") messageId: String): HabitResponse<Void>
@GET("groups/{gid}/members")
suspend fun getGroupMembers(
@ -251,59 +250,59 @@ interface ApiService {
// Like returns the full chat list
@POST("groups/{gid}/chat/{mid}/like")
fun likeMessage(@Path("gid") groupId: String, @Path("mid") mid: String): Flowable<HabitResponse<ChatMessage>>
suspend fun likeMessage(@Path("gid") groupId: String, @Path("mid") mid: String): HabitResponse<ChatMessage>
@POST("groups/{gid}/chat/{mid}/flag")
fun flagMessage(
suspend fun flagMessage(
@Path("gid") groupId: String,
@Path("mid") mid: String,
@Body data: Map<String, String>
): Flowable<HabitResponse<Void>>
): HabitResponse<Void>
@POST("groups/{gid}/chat/seen")
fun seenMessages(@Path("gid") groupId: String): Flowable<HabitResponse<Void>>
suspend fun seenMessages(@Path("gid") groupId: String): HabitResponse<Void>
@POST("groups/{gid}/invite")
fun inviteToGroup(@Path("gid") groupId: String, @Body inviteData: Map<String, Any>): Flowable<HabitResponse<List<Void>>>
suspend fun inviteToGroup(@Path("gid") groupId: String, @Body inviteData: Map<String, Any>): HabitResponse<List<Void>>
@POST("groups/{gid}/reject-invite")
fun rejectGroupInvite(@Path("gid") groupId: String): Flowable<HabitResponse<Void>>
suspend fun rejectGroupInvite(@Path("gid") groupId: String): HabitResponse<Void>
@POST("groups/{gid}/quests/accept")
fun acceptQuest(@Path("gid") groupId: String): Flowable<HabitResponse<Void>>
suspend fun acceptQuest(@Path("gid") groupId: String): HabitResponse<Void>
@POST("groups/{gid}/quests/reject")
fun rejectQuest(@Path("gid") groupId: String): Flowable<HabitResponse<Void>>
suspend fun rejectQuest(@Path("gid") groupId: String): HabitResponse<Void>
@POST("groups/{gid}/quests/cancel")
fun cancelQuest(@Path("gid") groupId: String): Flowable<HabitResponse<Void>>
suspend fun cancelQuest(@Path("gid") groupId: String): HabitResponse<Void>
@POST("groups/{gid}/quests/force-start")
fun forceStartQuest(@Path("gid") groupId: String, @Body group: Group): Flowable<HabitResponse<Quest>>
suspend fun forceStartQuest(@Path("gid") groupId: String, @Body group: Group): HabitResponse<Quest>
@POST("groups/{gid}/quests/invite/{questKey}")
fun inviteToQuest(@Path("gid") groupId: String, @Path("questKey") questKey: String): Flowable<HabitResponse<Quest>>
suspend fun inviteToQuest(@Path("gid") groupId: String, @Path("questKey") questKey: String): HabitResponse<Quest>
@POST("groups/{gid}/quests/abort")
fun abortQuest(@Path("gid") groupId: String): Flowable<HabitResponse<Quest>>
suspend fun abortQuest(@Path("gid") groupId: String): HabitResponse<Quest>
@POST("groups/{gid}/quests/leave")
fun leaveQuest(@Path("gid") groupId: String): Flowable<HabitResponse<Void>>
suspend fun leaveQuest(@Path("gid") groupId: String): HabitResponse<Void>
@POST("/iap/android/verify")
fun validatePurchase(@Body request: PurchaseValidationRequest): Flowable<HabitResponse<PurchaseValidationResult>>
suspend fun validatePurchase(@Body request: PurchaseValidationRequest): HabitResponse<PurchaseValidationResult>
@POST("/iap/android/subscribe")
fun validateSubscription(@Body request: PurchaseValidationRequest): Flowable<HabitResponse<Void>>
suspend fun validateSubscription(@Body request: PurchaseValidationRequest): HabitResponse<Void>
@GET("/iap/android/subscribe/cancel")
suspend fun cancelSubscription(): HabitResponse<Void>
@POST("/iap/android/norenew-subscribe")
fun validateNoRenewSubscription(@Body request: PurchaseValidationRequest): Flowable<HabitResponse<Void>>
suspend fun validateNoRenewSubscription(@Body request: PurchaseValidationRequest): HabitResponse<Void>
@POST("user/custom-day-start")
fun changeCustomDayStart(@Body updateObject: Map<String, Any>): Flowable<HabitResponse<User>>
suspend fun changeCustomDayStart(@Body updateObject: Map<String, Any>): HabitResponse<User>
// Members URL
@GET("members/{mid}")
@ -313,128 +312,128 @@ interface ApiService {
suspend fun getMemberWithUsername(@Path("username") username: String): HabitResponse<Member>
@GET("members/{mid}/achievements")
fun getMemberAchievements(@Path("mid") memberId: String, @Query("lang") language: String?): Flowable<HabitResponse<List<Achievement>>>
suspend fun getMemberAchievements(@Path("mid") memberId: String, @Query("lang") language: String?): HabitResponse<List<Achievement>>
@POST("members/send-private-message")
suspend fun postPrivateMessage(@Body messageDetails: Map<String, String>): HabitResponse<PostChatMessageResult>
@GET("members/find/{username}")
fun findUsernames(
suspend fun findUsernames(
@Path("username") username: String,
@Query("context") context: String?,
@Query("id") id: String?
): Flowable<HabitResponse<List<FindUsernameResult>>>
): HabitResponse<List<FindUsernameResult>>
@POST("members/flag-private-message/{mid}")
fun flagInboxMessage(@Path("mid") mid: String, @Body data: Map<String, String>): Flowable<HabitResponse<Void>>
suspend fun flagInboxMessage(@Path("mid") mid: String, @Body data: Map<String, String>): HabitResponse<Void>
@GET("shops/{identifier}")
fun retrieveShopInventory(@Path("identifier") identifier: String, @Query("lang") language: String?): Flowable<HabitResponse<Shop>>
suspend fun retrieveShopInventory(@Path("identifier") identifier: String, @Query("lang") language: String?): HabitResponse<Shop>
@GET("shops/market-gear")
fun retrieveMarketGear(@Query("lang") language: String?): Flowable<HabitResponse<Shop>>
suspend fun retrieveMarketGear(@Query("lang") language: String?): HabitResponse<Shop>
// Push notifications
@POST("user/push-devices")
fun addPushDevice(@Body pushDeviceData: Map<String, String>): Flowable<HabitResponse<List<Void>>>
suspend fun addPushDevice(@Body pushDeviceData: Map<String, String>): HabitResponse<List<Void>>
@DELETE("user/push-devices/{regId}")
fun deletePushDevice(@Path("regId") regId: String): Flowable<HabitResponse<List<Void>>>
suspend fun deletePushDevice(@Path("regId") regId: String): HabitResponse<List<Void>>
/* challenges api */
@GET("challenges/user")
fun getUserChallenges(@Query("page") page: Int?, @Query("member") memberOnly: Boolean): Flowable<HabitResponse<List<Challenge>>>
suspend fun getUserChallenges(@Query("page") page: Int?, @Query("member") memberOnly: Boolean): HabitResponse<List<Challenge>>
@GET("challenges/user")
fun getUserChallenges(@Query("page") page: Int?): Flowable<HabitResponse<List<Challenge>>>
suspend fun getUserChallenges(@Query("page") page: Int?): HabitResponse<List<Challenge>>
@GET("tasks/challenge/{challengeId}")
fun getChallengeTasks(@Path("challengeId") challengeId: String): Flowable<HabitResponse<TaskList>>
suspend fun getChallengeTasks(@Path("challengeId") challengeId: String): HabitResponse<TaskList>
@GET("challenges/{challengeId}")
fun getChallenge(@Path("challengeId") challengeId: String): Flowable<HabitResponse<Challenge>>
suspend fun getChallenge(@Path("challengeId") challengeId: String): HabitResponse<Challenge>
@POST("challenges/{challengeId}/join")
fun joinChallenge(@Path("challengeId") challengeId: String): Flowable<HabitResponse<Challenge>>
suspend fun joinChallenge(@Path("challengeId") challengeId: String): HabitResponse<Challenge>
@POST("challenges/{challengeId}/leave")
fun leaveChallenge(@Path("challengeId") challengeId: String, @Body body: LeaveChallengeBody): Flowable<HabitResponse<Void>>
suspend fun leaveChallenge(@Path("challengeId") challengeId: String, @Body body: LeaveChallengeBody): HabitResponse<Void>
@POST("challenges")
fun createChallenge(@Body challenge: Challenge): Flowable<HabitResponse<Challenge>>
suspend fun createChallenge(@Body challenge: Challenge): HabitResponse<Challenge>
@POST("tasks/challenge/{challengeId}")
fun createChallengeTasks(@Path("challengeId") challengeId: String, @Body tasks: List<Task>): Flowable<HabitResponse<List<Task>>>
suspend fun createChallengeTasks(@Path("challengeId") challengeId: String, @Body tasks: List<Task>): HabitResponse<List<Task>>
@POST("tasks/challenge/{challengeId}")
fun createChallengeTask(@Path("challengeId") challengeId: String, @Body task: Task): Flowable<HabitResponse<Task>>
suspend fun createChallengeTask(@Path("challengeId") challengeId: String, @Body task: Task): HabitResponse<Task>
@PUT("challenges/{challengeId}")
fun updateChallenge(@Path("challengeId") challengeId: String, @Body challenge: Challenge): Flowable<HabitResponse<Challenge>>
suspend fun updateChallenge(@Path("challengeId") challengeId: String, @Body challenge: Challenge): HabitResponse<Challenge>
@DELETE("challenges/{challengeId}")
fun deleteChallenge(@Path("challengeId") challengeId: String): Flowable<HabitResponse<Void>>
suspend fun deleteChallenge(@Path("challengeId") challengeId: String): HabitResponse<Void>
// DEBUG: These calls only work on a local development server
@POST("debug/add-ten-gems")
fun debugAddTenGems(): Flowable<HabitResponse<Void>>
suspend fun debugAddTenGems(): HabitResponse<Void>
// Notifications
@POST("notifications/{notificationId}/read")
fun readNotification(@Path("notificationId") notificationId: String): Flowable<HabitResponse<List<Any>>>
suspend fun readNotification(@Path("notificationId") notificationId: String): HabitResponse<List<Any>>
@POST("notifications/read")
fun readNotifications(@Body notificationIds: Map<String, List<String>>): Flowable<HabitResponse<List<Any>>>
suspend fun readNotifications(@Body notificationIds: Map<String, List<String>>): HabitResponse<List<Any>>
@POST("notifications/see")
fun seeNotifications(@Body notificationIds: Map<String, List<String>>): Flowable<HabitResponse<List<Any>>>
suspend fun seeNotifications(@Body notificationIds: Map<String, List<String>>): HabitResponse<List<Any>>
@POST("user/open-mystery-item")
fun openMysteryItem(): Flowable<HabitResponse<Equipment>>
suspend fun openMysteryItem(): HabitResponse<Equipment>
@POST("cron")
fun runCron(): Flowable<HabitResponse<Void>>
suspend fun runCron(): HabitResponse<Void>
@POST("user/reset")
fun resetAccount(): Flowable<HabitResponse<Void>>
suspend fun resetAccount(): HabitResponse<Void>
@HTTP(method = "DELETE", path = "user", hasBody = true)
fun deleteAccount(@Body body: Map<String, String>): Flowable<HabitResponse<Void>>
suspend fun deleteAccount(@Body body: Map<String, String>): HabitResponse<Void>
@GET("user/toggle-pinned-item/{pinType}/{path}")
suspend fun togglePinnedItem(@Path("pinType") pinType: String, @Path("path") path: String): HabitResponse<Void>
@POST("user/reset-password")
fun sendPasswordResetEmail(@Body data: Map<String, String>): Flowable<HabitResponse<Void>>
suspend fun sendPasswordResetEmail(@Body data: Map<String, String>): HabitResponse<Void>
@PUT("user/auth/update-username")
suspend fun updateLoginName(@Body data: Map<String, String>): HabitResponse<Void>
@POST("user/auth/verify-username")
fun verifyUsername(@Body data: Map<String, String>): Flowable<HabitResponse<VerifyUsernameResponse>>
suspend fun verifyUsername(@Body data: Map<String, String>): HabitResponse<VerifyUsernameResponse>
@PUT("user/auth/update-email")
fun updateEmail(@Body data: Map<String, String>): Flowable<HabitResponse<Void>>
suspend fun updateEmail(@Body data: Map<String, String>): HabitResponse<Void>
@PUT("user/auth/update-password")
fun updatePassword(@Body data: Map<String, String>): Flowable<HabitResponse<Void>>
suspend fun updatePassword(@Body data: Map<String, String>): HabitResponse<Void>
@POST("user/allocate")
fun allocatePoint(@Query("stat") stat: String): Flowable<HabitResponse<Stats>>
suspend fun allocatePoint(@Query("stat") stat: String): HabitResponse<Stats>
@POST("user/allocate-bulk")
fun bulkAllocatePoints(@Body stats: Map<String, Map<String, Int>>): Flowable<HabitResponse<Stats>>
suspend fun bulkAllocatePoints(@Body stats: Map<String, Map<String, Int>>): HabitResponse<Stats>
@POST("members/transfer-gems")
fun transferGems(@Body data: Map<String, Any>): Flowable<HabitResponse<Void>>
suspend fun transferGems(@Body data: Map<String, Any>): HabitResponse<Void>
@POST("tasks/unlink-all/{challengeID}")
fun unlinkAllTasks(@Path("challengeID") challengeID: String?, @Query("keep") keepOption: String): Flowable<HabitResponse<Void>>
suspend fun unlinkAllTasks(@Path("challengeID") challengeID: String?, @Query("keep") keepOption: String): HabitResponse<Void>
@POST("user/block/{userID}")
fun blockMember(@Path("userID") userID: String): Flowable<HabitResponse<List<String>>>
suspend fun blockMember(@Path("userID") userID: String): HabitResponse<List<String>>
@POST("user/reroll")
suspend fun reroll(): HabitResponse<User>
@ -442,7 +441,7 @@ interface ApiService {
// Team Plans
@GET("group-plans")
fun getTeamPlans(): Flowable<HabitResponse<List<TeamPlan>>>
suspend fun getTeamPlans(): HabitResponse<List<TeamPlan>>
@GET("tasks/group/{groupID}")
suspend fun getTeamPlanTasks(@Path("groupID") groupId: String): HabitResponse<TaskList>

View file

@ -1,16 +0,0 @@
package com.habitrpg.android.habitica.api;
import com.habitrpg.shared.habitica.models.responses.MaintenanceResponse;
import io.reactivex.rxjava3.core.Flowable;
import retrofit2.http.GET;
public interface MaintenanceApiService {
@GET("maintenance-android.json")
Flowable<MaintenanceResponse> getMaintenanceStatus();
@GET("deprecation-android.json")
Flowable<MaintenanceResponse> getDepricationStatus();
}

View file

@ -0,0 +1,12 @@
package com.habitrpg.android.habitica.api
import com.habitrpg.shared.habitica.models.responses.MaintenanceResponse
import retrofit2.http.GET
interface MaintenanceApiService {
@GET("maintenance-android.json")
suspend fun getMaintenanceStatus(): MaintenanceResponse?
@GET("deprecation-android.json")
suspend fun getDepricationStatus(): MaintenanceResponse?
}

View file

@ -40,7 +40,6 @@ import com.habitrpg.android.habitica.ui.activities.SkillTasksActivity;
import com.habitrpg.android.habitica.ui.activities.TaskFormActivity;
import com.habitrpg.android.habitica.ui.activities.TaskSummaryActivity;
import com.habitrpg.android.habitica.ui.activities.TaskSummaryViewModel;
import com.habitrpg.android.habitica.ui.activities.VerifyUsernameActivity;
import com.habitrpg.android.habitica.ui.adapter.social.challenges.ChallengeTasksRecyclerViewAdapter;
import com.habitrpg.android.habitica.ui.adapter.tasks.DailiesRecyclerViewHolder;
import com.habitrpg.android.habitica.ui.adapter.tasks.HabitsRecyclerViewAdapter;
@ -293,8 +292,6 @@ public interface UserComponent {
void inject(ChallengeDetailFragment challengeDetailFragment);
void inject(VerifyUsernameActivity verifyUsernameActivity);
void inject(GroupViewModel viewModel);
void inject(NotificationsViewModel viewModel);

View file

@ -27,7 +27,6 @@ import com.habitrpg.android.habitica.models.user.Items
import com.habitrpg.android.habitica.models.user.Stats
import com.habitrpg.android.habitica.models.user.User
import com.habitrpg.common.habitica.api.HostConfig
import com.habitrpg.common.habitica.models.HabitResponse
import com.habitrpg.common.habitica.models.PurchaseValidationRequest
import com.habitrpg.common.habitica.models.PurchaseValidationResult
import com.habitrpg.common.habitica.models.auth.UserAuthResponse
@ -36,8 +35,6 @@ import com.habitrpg.shared.habitica.models.responses.FeedResponse
import com.habitrpg.shared.habitica.models.responses.Status
import com.habitrpg.shared.habitica.models.responses.TaskDirectionData
import com.habitrpg.shared.habitica.models.responses.VerifyUsernameResponse
import io.reactivex.rxjava3.core.Flowable
import io.reactivex.rxjava3.core.FlowableTransformer
import retrofit2.HttpException
interface ApiClient {
@ -52,19 +49,19 @@ interface ApiClient {
/* challenges api */
fun getUserChallenges(page: Int, memberOnly: Boolean): Flowable<List<Challenge>>
suspend fun getUserChallenges(page: Int, memberOnly: Boolean): List<Challenge>?
suspend fun getWorldState(): WorldState?
fun setLanguageCode(languageCode: String)
var languageCode: String?
suspend fun getContent(language: String? = null): ContentResult?
suspend fun updateUser(updateDictionary: Map<String, Any>): User?
fun registrationLanguage(registrationLanguage: String): Flowable<User>
suspend fun registrationLanguage(registrationLanguage: String): User?
suspend fun retrieveInAppRewards(): List<ShopItem>?
fun equipItem(type: String, itemKey: String): Flowable<Items>
suspend fun equipItem(type: String, itemKey: String): Items?
suspend fun buyItem(itemKey: String, purchaseQuantity: Int): BuyResponse?
@ -76,51 +73,51 @@ interface ApiClient {
suspend fun purchaseQuest(key: String): Void?
suspend fun purchaseSpecialSpell(key: String): Void?
fun validateSubscription(request: PurchaseValidationRequest): Flowable<Any>
fun validateNoRenewSubscription(request: PurchaseValidationRequest): Flowable<Any>
suspend fun validateSubscription(request: PurchaseValidationRequest): Any?
suspend fun validateNoRenewSubscription(request: PurchaseValidationRequest): Any?
suspend fun cancelSubscription(): Void?
fun sellItem(itemType: String, itemKey: String): Flowable<User>
suspend fun sellItem(itemType: String, itemKey: String): User?
fun feedPet(petKey: String, foodKey: String): Flowable<FeedResponse>
suspend fun feedPet(petKey: String, foodKey: String): FeedResponse?
fun hatchPet(eggKey: String, hatchingPotionKey: String): Flowable<Items>
fun getTasks(type: String): Flowable<TaskList>
fun getTasks(type: String, dueDate: String): Flowable<TaskList>
suspend fun hatchPet(eggKey: String, hatchingPotionKey: String): Items?
suspend fun getTasks(type: String): TaskList?
suspend fun getTasks(type: String, dueDate: String): TaskList?
suspend fun unlockPath(path: String): UnlockResponse?
fun getTask(id: String): Flowable<Task>
suspend fun getTask(id: String): Task?
suspend fun postTaskDirection(id: String, direction: String): TaskDirectionData?
fun bulkScoreTasks(data: List<Map<String, String>>): Flowable<BulkTaskScoringData>
suspend fun bulkScoreTasks(data: List<Map<String, String>>): BulkTaskScoringData?
fun postTaskNewPosition(id: String, position: Int): Flowable<List<String>>
suspend fun postTaskNewPosition(id: String, position: Int): List<String>?
suspend fun scoreChecklistItem(taskId: String, itemId: String): Task?
fun createTask(item: Task): Flowable<Task>
suspend fun createTask(item: Task): Task?
fun createTasks(tasks: List<Task>): Flowable<List<Task>>
suspend fun createTasks(tasks: List<Task>): List<Task>?
fun updateTask(id: String, item: Task): Flowable<Task>
suspend fun updateTask(id: String, item: Task): Task?
fun deleteTask(id: String): Flowable<Void>
suspend fun deleteTask(id: String): Void?
fun createTag(tag: Tag): Flowable<Tag>
suspend fun createTag(tag: Tag): Tag?
fun updateTag(id: String, tag: Tag): Flowable<Tag>
suspend fun updateTag(id: String, tag: Tag): Tag?
fun deleteTag(id: String): Flowable<Void>
suspend fun deleteTag(id: String): Void?
fun registerUser(username: String, email: String, password: String, confirmPassword: String): Flowable<UserAuthResponse>
suspend fun registerUser(username: String, email: String, password: String, confirmPassword: String): UserAuthResponse?
fun connectUser(username: String, password: String): Flowable<UserAuthResponse>
suspend fun connectUser(username: String, password: String): UserAuthResponse?
fun connectSocial(network: String, userId: String, accessToken: String): Flowable<UserAuthResponse>
fun disconnectSocial(network: String): Flowable<Void>
suspend fun connectSocial(network: String, userId: String, accessToken: String): UserAuthResponse?
suspend fun disconnectSocial(network: String): Void?
fun loginApple(authToken: String): Flowable<UserAuthResponse>
suspend fun loginApple(authToken: String): UserAuthResponse?
suspend fun sleep(): Boolean?
suspend fun revive(): User?
@ -133,11 +130,11 @@ interface ApiClient {
suspend fun disableClasses(): User?
fun markPrivateMessagesRead(): Flowable<Void>
suspend fun markPrivateMessagesRead(): Void?
/* Group API */
fun listGroups(type: String): Flowable<List<Group>>
suspend fun listGroups(type: String): List<Group>?
suspend fun getGroup(groupId: String): Group?
@ -151,83 +148,83 @@ interface ApiClient {
suspend fun leaveGroup(groupId: String, keepChallenges: String): Void?
fun postGroupChat(groupId: String, message: Map<String, String>): Flowable<PostChatMessageResult>
suspend fun postGroupChat(groupId: String, message: Map<String, String>): PostChatMessageResult?
fun deleteMessage(groupId: String, messageId: String): Flowable<Void>
fun deleteInboxMessage(id: String): Flowable<Void>
suspend fun deleteMessage(groupId: String, messageId: String): Void?
suspend fun deleteInboxMessage(id: String): Void?
suspend fun getGroupMembers(groupId: String, includeAllPublicFields: Boolean?): List<Member>?
suspend fun getGroupMembers(groupId: String, includeAllPublicFields: Boolean?, lastId: String): List<Member>?
// Like returns the full chat list
fun likeMessage(groupId: String, mid: String): Flowable<ChatMessage>
suspend fun likeMessage(groupId: String, mid: String): ChatMessage?
fun flagMessage(groupId: String, mid: String, data: MutableMap<String, String>): Flowable<Void>
fun flagInboxMessage(mid: String, data: MutableMap<String, String>): Flowable<Void>
suspend fun flagMessage(groupId: String, mid: String, data: MutableMap<String, String>): Void?
suspend fun flagInboxMessage(mid: String, data: MutableMap<String, String>): Void?
fun seenMessages(groupId: String): Flowable<Void>
suspend fun seenMessages(groupId: String): Void?
fun inviteToGroup(groupId: String, inviteData: Map<String, Any>): Flowable<List<Void>>
suspend fun inviteToGroup(groupId: String, inviteData: Map<String, Any>): List<Void>?
fun rejectGroupInvite(groupId: String): Flowable<Void>
suspend fun rejectGroupInvite(groupId: String): Void?
fun acceptQuest(groupId: String): Flowable<Void>
suspend fun acceptQuest(groupId: String): Void?
fun rejectQuest(groupId: String): Flowable<Void>
suspend fun rejectQuest(groupId: String): Void?
fun cancelQuest(groupId: String): Flowable<Void>
suspend fun cancelQuest(groupId: String): Void?
fun forceStartQuest(groupId: String, group: Group): Flowable<Quest>
suspend fun forceStartQuest(groupId: String, group: Group): Quest?
fun inviteToQuest(groupId: String, questKey: String): Flowable<Quest>
suspend fun inviteToQuest(groupId: String, questKey: String): Quest?
fun abortQuest(groupId: String): Flowable<Quest>
suspend fun abortQuest(groupId: String): Quest?
fun leaveQuest(groupId: String): Flowable<Void>
suspend fun leaveQuest(groupId: String): Void?
fun validatePurchase(request: PurchaseValidationRequest): Flowable<PurchaseValidationResult>
suspend fun validatePurchase(request: PurchaseValidationRequest): PurchaseValidationResult?
fun changeCustomDayStart(updateObject: Map<String, Any>): Flowable<User>
suspend fun changeCustomDayStart(updateObject: Map<String, Any>): User?
// Members URL
suspend fun getMember(memberId: String): Member?
suspend fun getMemberWithUsername(username: String): Member?
fun getMemberAchievements(memberId: String): Flowable<List<Achievement>>
suspend fun getMemberAchievements(memberId: String): List<Achievement>?
suspend fun postPrivateMessage(messageDetails: Map<String, String>): PostChatMessageResult?
fun retrieveShopIventory(identifier: String): Flowable<Shop>
suspend fun retrieveShopIventory(identifier: String): Shop?
// Push notifications
fun addPushDevice(pushDeviceData: Map<String, String>): Flowable<List<Void>>
suspend fun addPushDevice(pushDeviceData: Map<String, String>): List<Void>?
fun deletePushDevice(regId: String): Flowable<List<Void>>
suspend fun deletePushDevice(regId: String): List<Void>?
fun getChallengeTasks(challengeId: String): Flowable<TaskList>
suspend fun getChallengeTasks(challengeId: String): TaskList?
fun getChallenge(challengeId: String): Flowable<Challenge>
suspend fun getChallenge(challengeId: String): Challenge?
fun joinChallenge(challengeId: String): Flowable<Challenge>
suspend fun joinChallenge(challengeId: String): Challenge?
fun leaveChallenge(challengeId: String, body: LeaveChallengeBody): Flowable<Void>
suspend fun leaveChallenge(challengeId: String, body: LeaveChallengeBody): Void?
fun createChallenge(challenge: Challenge): Flowable<Challenge>
suspend fun createChallenge(challenge: Challenge): Challenge?
fun createChallengeTasks(challengeId: String, tasks: List<Task>): Flowable<List<Task>>
fun createChallengeTask(challengeId: String, task: Task): Flowable<Task>
fun updateChallenge(challenge: Challenge): Flowable<Challenge>
fun deleteChallenge(challengeId: String): Flowable<Void>
suspend fun createChallengeTasks(challengeId: String, tasks: List<Task>): List<Task>?
suspend fun createChallengeTask(challengeId: String, task: Task): Task?
suspend fun updateChallenge(challenge: Challenge): Challenge?
suspend fun deleteChallenge(challengeId: String): Void?
// DEBUG: These calls only work on a local development server
fun debugAddTenGems(): Flowable<Void>
suspend fun debugAddTenGems(): Void?
// Notifications
fun readNotification(notificationId: String): Flowable<List<Any>>
fun readNotifications(notificationIds: Map<String, List<String>>): Flowable<List<Any>>
fun seeNotifications(notificationIds: Map<String, List<String>>): Flowable<List<Any>>
suspend fun readNotification(notificationId: String): List<Any>?
suspend fun readNotifications(notificationIds: Map<String, List<String>>): List<Any>?
suspend fun seeNotifications(notificationIds: Map<String, List<String>>): List<Any>?
fun getErrorResponse(throwable: HttpException): ErrorResponse
@ -237,42 +234,40 @@ interface ApiClient {
suspend fun retrieveUser(withTasks: Boolean = false): User?
suspend fun retrieveInboxMessages(uuid: String, page: Int): List<ChatMessage>?
fun retrieveInboxConversations(): Flowable<List<InboxConversation>>
suspend fun retrieveInboxConversations(): List<InboxConversation>?
fun <T : Any> configureApiCallObserver(): FlowableTransformer<HabitResponse<T>, T>
suspend fun openMysteryItem(): Equipment?
fun openMysteryItem(): Flowable<Equipment>
fun runCron(): Flowable<Void>
suspend fun runCron(): Void?
suspend fun reroll(): User?
fun resetAccount(): Flowable<Void>
fun deleteAccount(password: String): Flowable<Void>
suspend fun resetAccount(): Void?
suspend fun deleteAccount(password: String): Void?
suspend fun togglePinnedItem(pinType: String, path: String): Void?
fun sendPasswordResetEmail(email: String): Flowable<Void>
suspend fun sendPasswordResetEmail(email: String): Void?
suspend fun updateLoginName(newLoginName: String, password: String): Void?
suspend fun updateUsername(newLoginName: String): Void?
fun updateEmail(newEmail: String, password: String): Flowable<Void>
suspend fun updateEmail(newEmail: String, password: String): Void?
fun updatePassword(oldPassword: String, newPassword: String, newPasswordConfirmation: String): Flowable<Void>
suspend fun updatePassword(oldPassword: String, newPassword: String, newPasswordConfirmation: String): Void?
fun allocatePoint(stat: String): Flowable<Stats>
suspend fun allocatePoint(stat: String): Stats?
fun bulkAllocatePoints(strength: Int, intelligence: Int, constitution: Int, perception: Int): Flowable<Stats>
suspend fun bulkAllocatePoints(strength: Int, intelligence: Int, constitution: Int, perception: Int): Stats?
fun retrieveMarketGear(): Flowable<Shop>
fun verifyUsername(username: String): Flowable<VerifyUsernameResponse>
suspend fun retrieveMarketGear(): Shop?
suspend fun verifyUsername(username: String): VerifyUsernameResponse?
fun updateServerUrl(newAddress: String?)
fun findUsernames(username: String, context: String?, id: String?): Flowable<List<FindUsernameResult>>
suspend fun findUsernames(username: String, context: String?, id: String?): List<FindUsernameResult>?
fun transferGems(giftedID: String, amount: Int): Flowable<Void>
fun unlinkAllTasks(challengeID: String?, keepOption: String): Flowable<Void>
fun blockMember(userID: String): Flowable<List<String>>
fun getTeamPlans(): Flowable<List<TeamPlan>>
suspend fun transferGems(giftedID: String, amount: Int): Void?
suspend fun unlinkAllTasks(challengeID: String?, keepOption: String): Void?
suspend fun blockMember(userID: String): List<String>?
suspend fun getTeamPlans(): List<TeamPlan>?
suspend fun getTeamPlanTasks(teamID: String): TaskList?
}

View file

@ -4,18 +4,18 @@ import com.habitrpg.android.habitica.models.social.Challenge
import com.habitrpg.android.habitica.models.social.ChallengeMembership
import com.habitrpg.android.habitica.models.tasks.Task
import com.habitrpg.android.habitica.models.tasks.TaskList
import io.reactivex.rxjava3.core.Flowable
import kotlinx.coroutines.flow.Flow
interface ChallengeRepository : BaseRepository {
fun retrieveChallenges(page: Int = 0, memberOnly: Boolean): Flowable<List<Challenge>>
fun getChallenges(): Flowable<out List<Challenge>>
fun getChallenge(challengeId: String): Flowable<Challenge>
fun getChallengeTasks(challengeId: String): Flowable<out List<Task>>
suspend fun retrieveChallenges(page: Int = 0, memberOnly: Boolean): List<Challenge>?
fun getChallenges(): Flow<List<Challenge>>
fun getChallenge(challengeId: String): Flow<Challenge>
fun getChallengeTasks(challengeId: String): Flow<List<Task>>
fun retrieveChallenge(challengeID: String): Flowable<Challenge>
fun retrieveChallengeTasks(challengeID: String): Flowable<TaskList>
fun createChallenge(challenge: Challenge, taskList: List<Task>): Flowable<Challenge>
suspend fun retrieveChallenge(challengeID: String): Challenge?
suspend fun retrieveChallengeTasks(challengeID: String): TaskList?
suspend fun createChallenge(challenge: Challenge, taskList: List<Task>): Challenge?
/**
*
@ -26,22 +26,22 @@ interface ChallengeRepository : BaseRepository {
* @param removedTaskList tasks that has be to be removed
* @return Observable with the updated challenge
*/
fun updateChallenge(
suspend fun updateChallenge(
challenge: Challenge,
fullTaskList: List<Task>,
addedTaskList: List<Task>,
updatedTaskList: List<Task>,
removedTaskList: List<String>
): Flowable<Challenge>
): Challenge?
fun deleteChallenge(challengeId: String): Flowable<Void>
fun getUserChallenges(userId: String? = null): Flowable<out List<Challenge>>
suspend fun deleteChallenge(challengeId: String): Void?
fun getUserChallenges(userId: String? = null): Flow<List<Challenge>>
fun leaveChallenge(challenge: Challenge, keepTasks: String): Flowable<Void>
suspend fun leaveChallenge(challenge: Challenge, keepTasks: String): Void?
fun joinChallenge(challenge: Challenge): Flowable<Challenge>
suspend fun joinChallenge(challenge: Challenge): Challenge?
fun getChallengepMembership(id: String): Flowable<ChallengeMembership>
fun getChallengeMemberships(): Flowable<out List<ChallengeMembership>>
fun isChallengeMember(challengeID: String): Flowable<Boolean>
fun getChallengepMembership(id: String): Flow<ChallengeMembership>
fun getChallengeMemberships(): Flow<List<ChallengeMembership>>
fun isChallengeMember(challengeID: String): Flow<Boolean>
}

View file

@ -2,11 +2,11 @@ package com.habitrpg.android.habitica.data
import com.habitrpg.android.habitica.models.ContentResult
import com.habitrpg.android.habitica.models.WorldState
import io.reactivex.rxjava3.core.Flowable
import kotlinx.coroutines.flow.Flow
interface ContentRepository: BaseRepository {
suspend fun retrieveContent(forced: Boolean = false): ContentResult?
suspend fun retrieveWorldState(): WorldState?
fun getWorldState(): Flowable<WorldState>
fun getWorldState(): Flow<WorldState>
}

View file

@ -1,8 +1,8 @@
package com.habitrpg.android.habitica.data
import com.habitrpg.android.habitica.models.inventory.Customization
import io.reactivex.rxjava3.core.Flowable
import kotlinx.coroutines.flow.Flow
interface CustomizationRepository : BaseRepository {
fun getCustomizations(type: String, category: String?, onlyAvailable: Boolean): Flowable<out List<Customization>>
fun getCustomizations(type: String, category: String?, onlyAvailable: Boolean): Flow<List<Customization>>
}

View file

@ -1,9 +1,9 @@
package com.habitrpg.android.habitica.data
import com.habitrpg.android.habitica.models.FAQArticle
import io.reactivex.rxjava3.core.Flowable
import kotlinx.coroutines.flow.Flow
interface FAQRepository : BaseRepository {
fun getArticles(): Flowable<out List<FAQArticle>>
fun getArticle(position: Int): Flowable<FAQArticle>
fun getArticles(): Flow<List<FAQArticle>>
fun getArticle(position: Int): Flow<FAQArticle>
}

View file

@ -18,15 +18,14 @@ import com.habitrpg.android.habitica.models.user.OwnedMount
import com.habitrpg.android.habitica.models.user.OwnedPet
import com.habitrpg.android.habitica.models.user.User
import com.habitrpg.shared.habitica.models.responses.FeedResponse
import io.reactivex.rxjava3.core.Flowable
import kotlinx.coroutines.flow.Flow
interface InventoryRepository : BaseRepository {
fun getArmoireRemainingCount(): Long
fun getInAppRewards(): Flowable<out List<ShopItem>>
fun getOwnedEquipment(): Flowable<out List<Equipment>>
fun getInAppRewards(): Flow<List<ShopItem>>
fun getOwnedEquipment(): Flow<List<Equipment>>
fun getMounts(): Flow<List<Mount>>
@ -38,18 +37,18 @@ interface InventoryRepository : BaseRepository {
fun getQuestContent(key: String): Flow<QuestContent?>
fun getQuestContent(keys: List<String>): Flow<List<QuestContent>>
fun getEquipment(searchedKeys: List<String>): Flowable<out List<Equipment>>
fun getEquipment(searchedKeys: List<String>): Flow<List<Equipment>>
suspend fun retrieveInAppRewards(): List<ShopItem>?
fun getOwnedEquipment(type: String): Flowable<out List<Equipment>>
fun getEquipmentType(type: String, set: String): Flowable<out List<Equipment>>
fun getOwnedEquipment(type: String): Flow<List<Equipment>>
fun getEquipmentType(type: String, set: String): Flow<List<Equipment>>
fun getOwnedItems(itemType: String, includeZero: Boolean = false): Flow<List<OwnedItem>>
fun getOwnedItems(includeZero: Boolean = false): Flowable<Map<String, OwnedItem>>
fun getOwnedItems(includeZero: Boolean = false): Flow<Map<String, OwnedItem>>
fun getEquipment(key: String): Flowable<Equipment>
fun getEquipment(key: String): Flow<Equipment>
fun openMysteryItem(user: User?): Flowable<Equipment>
suspend fun openMysteryItem(user: User?): Equipment?
fun saveEquipment(equipment: Equipment)
fun getMounts(type: String?, group: String?, color: String?): Flow<List<Mount>>
@ -57,24 +56,24 @@ interface InventoryRepository : BaseRepository {
fun updateOwnedEquipment(user: User)
fun changeOwnedCount(type: String, key: String, amountToAdd: Int)
suspend fun changeOwnedCount(type: String, key: String, amountToAdd: Int)
fun sellItem(type: String, key: String): Flowable<User>
fun sellItem(item: OwnedItem): Flowable<User>
suspend fun sellItem(type: String, key: String): User?
suspend fun sellItem(item: OwnedItem): User?
fun equipGear(equipment: String, asCostume: Boolean): Flowable<Items>
fun equip(type: String, key: String): Flowable<Items>
suspend fun equipGear(equipment: String, asCostume: Boolean): Items?
suspend fun equip(type: String, key: String): Items?
fun feedPet(pet: Pet, food: Food): Flowable<FeedResponse>
suspend fun feedPet(pet: Pet, food: Food): FeedResponse?
fun hatchPet(egg: Egg, hatchingPotion: HatchingPotion, successFunction: () -> Unit): Flowable<Items>
suspend fun hatchPet(egg: Egg, hatchingPotion: HatchingPotion, successFunction: () -> Unit): Items?
fun inviteToQuest(quest: QuestContent): Flowable<Quest>
suspend fun inviteToQuest(quest: QuestContent): Quest?
suspend fun buyItem(user: User?, id: String, value: Double, purchaseQuantity: Int): BuyResponse?
fun retrieveShopInventory(identifier: String): Flowable<Shop>
fun retrieveMarketGear(): Flowable<Shop>
suspend fun retrieveShopInventory(identifier: String): Shop?
suspend fun retrieveMarketGear(): Shop?
suspend fun purchaseMysterySet(categoryIdentifier: String): Void?
@ -87,9 +86,8 @@ interface InventoryRepository : BaseRepository {
suspend fun togglePinnedItem(item: ShopItem): List<ShopItem>?
fun getItems(itemClass: Class<out Item>, keys: Array<String>): Flow<List<Item>>
fun getItemsFlowable(itemClass: Class<out Item>): Flowable<out List<Item>>
fun getItems(itemClass: Class<out Item>): Flow<List<Item>>
fun getLatestMysteryItem(): Flowable<Equipment>
fun getItem(type: String, key: String): Flowable<Item>
fun getAvailableLimitedItems(): Flowable<List<Item>>
fun getLatestMysteryItem(): Flow<Equipment>
fun getItem(type: String, key: String): Flow<Item>
fun getAvailableLimitedItems(): Flow<List<Item>>
}

View file

@ -10,35 +10,34 @@ import com.habitrpg.android.habitica.models.social.Group
import com.habitrpg.android.habitica.models.social.GroupMembership
import com.habitrpg.android.habitica.models.social.InboxConversation
import com.habitrpg.android.habitica.models.user.User
import io.reactivex.rxjava3.core.Flowable
import io.realm.RealmResults
import kotlinx.coroutines.flow.Flow
interface SocialRepository : BaseRepository {
fun getPublicGuilds(): Flowable<out List<Group>>
fun getPublicGuilds(): Flow<List<Group>>
fun getUserGroups(type: String?): Flow<List<Group>>
suspend fun retrieveGroupChat(groupId: String): List<ChatMessage>?
fun getGroupChat(groupId: String): Flowable<out List<ChatMessage>>
fun getGroupChat(groupId: String): Flow<out List<ChatMessage>>
fun markMessagesSeen(seenGroupId: String)
suspend fun markMessagesSeen(seenGroupId: String)
fun flagMessage(
suspend fun flagMessage(
chatMessageID: String,
additionalInfo: String,
groupID: String? = null
): Flowable<Void>
): Void?
fun likeMessage(chatMessage: ChatMessage): Flowable<ChatMessage>
suspend fun likeMessage(chatMessage: ChatMessage): ChatMessage?
fun deleteMessage(chatMessage: ChatMessage): Flowable<Void>
suspend fun deleteMessage(chatMessage: ChatMessage): Void?
fun postGroupChat(
suspend fun postGroupChat(
groupId: String,
messageObject: HashMap<String, String>
): Flowable<PostChatMessageResult>
): PostChatMessageResult?
fun postGroupChat(groupId: String, message: String): Flowable<PostChatMessageResult>
suspend fun postGroupChat(groupId: String, message: String): PostChatMessageResult?
suspend fun retrieveGroup(id: String): Group?
fun getGroup(id: String?): Flow<Group?>
@ -64,12 +63,12 @@ interface SocialRepository : BaseRepository {
leaderCreateChallenge: Boolean?
): Group?
fun retrieveGroups(type: String): Flowable<List<Group>>
fun getGroups(type: String): Flowable<out List<Group>>
suspend fun retrieveGroups(type: String): List<Group>?
fun getGroups(type: String): Flow<List<Group>>
fun getInboxMessages(replyToUserID: String?): Flow<RealmResults<ChatMessage>>
suspend fun retrieveInboxMessages(uuid: String, page: Int): List<ChatMessage>?
fun retrieveInboxConversations(): Flowable<List<InboxConversation>>
suspend fun retrieveInboxConversations(): List<InboxConversation>?
fun getInboxConversations(): Flow<RealmResults<InboxConversation>>
suspend fun postPrivateMessage(
recipientId: String,
@ -82,42 +81,42 @@ interface SocialRepository : BaseRepository {
suspend fun getGroupMembers(id: String): Flow<List<Member>>
suspend fun retrievePartyMembers(id: String, includeAllPublicFields: Boolean): List<Member>?
fun inviteToGroup(id: String, inviteData: Map<String, Any>): Flowable<List<Void>>
suspend fun inviteToGroup(id: String, inviteData: Map<String, Any>): List<Void>?
suspend fun retrieveMember(userId: String?): Member?
suspend fun retrieveMemberWithUsername(username: String?): Member?
fun findUsernames(
suspend fun findUsernames(
username: String,
context: String? = null,
id: String? = null
): Flowable<List<FindUsernameResult>>
): List<FindUsernameResult>?
fun markPrivateMessagesRead(user: User?): Flowable<Void>
suspend fun markPrivateMessagesRead(user: User?): Void?
fun markSomePrivateMessagesAsRead(user: User?, messages: List<ChatMessage>)
suspend fun transferGroupOwnership(groupID: String, userID: String): Group?
suspend fun removeMemberFromGroup(groupID: String, userID: String): List<Member>?
fun acceptQuest(user: User?, partyId: String = "party"): Flowable<Void>
fun rejectQuest(user: User?, partyId: String = "party"): Flowable<Void>
suspend fun acceptQuest(user: User?, partyId: String = "party"): Void?
suspend fun rejectQuest(user: User?, partyId: String = "party"): Void?
fun leaveQuest(partyId: String): Flowable<Void>
suspend fun leaveQuest(partyId: String): Void?
fun cancelQuest(partyId: String): Flowable<Void>
suspend fun cancelQuest(partyId: String): Void?
fun abortQuest(partyId: String): Flowable<Quest>
suspend fun abortQuest(partyId: String): Quest?
fun rejectGroupInvite(groupId: String): Flowable<Void>
suspend fun rejectGroupInvite(groupId: String): Void?
fun forceStartQuest(party: Group): Flowable<Quest>
suspend fun forceStartQuest(party: Group): Quest?
fun getMemberAchievements(userId: String?): Flowable<List<Achievement>>
suspend fun getMemberAchievements(userId: String?): List<Achievement>?
fun transferGems(giftedID: String, amount: Int): Flowable<Void>
suspend fun transferGems(giftedID: String, amount: Int): Void?
fun getGroupMembership(id: String): Flow<GroupMembership?>
fun getGroupMemberships(): Flowable<out List<GroupMembership>>
fun blockMember(userID: String): Flowable<List<String>>
fun getGroupMemberships(): Flow<List<GroupMembership>>
suspend fun blockMember(userID: String): List<String>?
}

View file

@ -1,19 +1,18 @@
package com.habitrpg.android.habitica.data
import com.habitrpg.android.habitica.models.Tag
import io.reactivex.rxjava3.core.Flowable
import io.reactivex.rxjava3.core.Single
import kotlinx.coroutines.flow.Flow
interface TagRepository : BaseRepository {
fun getTags(): Flowable<out List<Tag>>
fun getTags(userId: String): Flowable<out List<Tag>>
fun getTags(): Flow<List<Tag>>
fun getTags(userId: String): Flow<List<Tag>>
fun createTag(tag: Tag): Flowable<Tag>
fun updateTag(tag: Tag): Flowable<Tag>
fun deleteTag(id: String): Flowable<Void>
suspend fun createTag(tag: Tag): Tag?
suspend fun updateTag(tag: Tag): Tag?
suspend fun deleteTag(id: String): Void?
fun createTags(tags: Collection<Tag>): Single<List<Tag>>
fun updateTags(tags: Collection<Tag>): Single<List<Tag>>
fun deleteTags(tagIds: Collection<String>): Single<List<Void>>
suspend fun createTags(tags: Collection<Tag>): List<Tag>
suspend fun updateTags(tags: Collection<Tag>): List<Tag>
suspend fun deleteTags(tagIds: Collection<String>): List<Void>
}

View file

@ -8,19 +8,15 @@ import com.habitrpg.android.habitica.models.user.User
import com.habitrpg.shared.habitica.models.responses.TaskScoringResult
import com.habitrpg.shared.habitica.models.tasks.TaskType
import com.habitrpg.shared.habitica.models.tasks.TasksOrder
import io.reactivex.rxjava3.core.Flowable
import io.reactivex.rxjava3.core.Maybe
import io.reactivex.rxjava3.core.Single
import kotlinx.coroutines.flow.Flow
import java.util.Date
interface TaskRepository : BaseRepository {
fun getTasks(taskType: TaskType, userID: String? = null, includedGroupIDs: Array<String>): Flow<List<Task>>
fun getTasksFlowable(taskType: TaskType, userID: String? = null, includedGroupIDs: Array<String>): Flowable<out List<Task>>
fun saveTasks(userId: String, order: TasksOrder, tasks: TaskList)
suspend fun retrieveTasks(userId: String, tasksOrder: TasksOrder): TaskList?
fun retrieveTasks(userId: String, tasksOrder: TasksOrder, dueDate: Date): Flowable<TaskList>
suspend fun retrieveTasks(userId: String, tasksOrder: TasksOrder, dueDate: Date): TaskList?
suspend fun taskChecked(
user: User?,
@ -40,12 +36,12 @@ interface TaskRepository : BaseRepository {
fun getTask(taskId: String): Flow<Task>
fun getTaskCopy(taskId: String): Flow<Task>
fun createTask(task: Task, force: Boolean = false): Flowable<Task>
fun updateTask(task: Task, force: Boolean = false): Maybe<Task>?
fun deleteTask(taskId: String): Flowable<Void>
suspend fun createTask(task: Task, force: Boolean = false): Task?
suspend fun updateTask(task: Task, force: Boolean = false): Task?
suspend fun deleteTask(taskId: String): Void?
fun saveTask(task: Task)
fun createTasks(newTasks: List<Task>): Flowable<List<Task>>
suspend fun createTasks(newTasks: List<Task>): List<Task>?
fun markTaskCompleted(taskId: String, isCompleted: Boolean)
@ -53,7 +49,7 @@ interface TaskRepository : BaseRepository {
fun swapTaskPosition(firstPosition: Int, secondPosition: Int)
fun updateTaskPosition(taskType: TaskType, taskID: String, newPosition: Int): Maybe<List<String>>
suspend fun updateTaskPosition(taskType: TaskType, taskID: String, newPosition: Int): List<String>?
fun getUnmanagedTask(taskid: String): Flow<Task>
@ -65,10 +61,10 @@ interface TaskRepository : BaseRepository {
fun getTaskCopies(tasks: List<Task>): List<Task>
fun retrieveDailiesFromDate(date: Date): Flowable<TaskList>
fun retrieveCompletedTodos(userId: String? = null): Flowable<TaskList>
fun syncErroredTasks(): Single<List<Task>>
fun unlinkAllTasks(challengeID: String?, keepOption: String): Flowable<Void>
fun getTasksForChallenge(challengeID: String?): Flowable<out List<Task>>
fun bulkScoreTasks(data: List<Map<String, String>>): Flowable<BulkTaskScoringData>
suspend fun retrieveDailiesFromDate(date: Date): TaskList?
suspend fun retrieveCompletedTodos(userId: String? = null): TaskList?
suspend fun syncErroredTasks(): List<Task>?
suspend fun unlinkAllTasks(challengeID: String?, keepOption: String): Void?
fun getTasksForChallenge(challengeID: String?): Flow<out List<Task>>
suspend fun bulkScoreTasks(data: List<Map<String, String>>): BulkTaskScoringData?
}

View file

@ -1,10 +1,10 @@
package com.habitrpg.android.habitica.data
import com.habitrpg.android.habitica.models.TutorialStep
import io.reactivex.rxjava3.core.Flowable
import kotlinx.coroutines.flow.Flow
interface TutorialRepository : BaseRepository {
fun getTutorialStep(key: String): Flowable<TutorialStep>
fun getTutorialSteps(keys: List<String>): Flowable<out List<TutorialStep>>
fun getTutorialStep(key: String): Flow<TutorialStep>
fun getTutorialSteps(keys: List<String>): Flow<out List<TutorialStep>>
}

View file

@ -14,12 +14,10 @@ import com.habitrpg.android.habitica.models.user.User
import com.habitrpg.android.habitica.models.user.UserQuestStatus
import com.habitrpg.shared.habitica.models.responses.VerifyUsernameResponse
import com.habitrpg.shared.habitica.models.tasks.Attribute
import io.reactivex.rxjava3.core.Flowable
import kotlinx.coroutines.flow.Flow
interface UserRepository : BaseRepository {
fun getUser(): Flow<User?>
fun getUserFlowable(): Flowable<User>
fun getUser(userID: String): Flow<User?>
suspend fun updateUser(updateData: Map<String, Any>): User?
@ -33,9 +31,9 @@ interface UserRepository : BaseRepository {
suspend fun sleep(user: User): User?
fun getSkills(user: User): Flowable<out List<Skill>>
fun getSkills(user: User): Flow<List<Skill>>
fun getSpecialItems(user: User): Flowable<out List<Skill>>
fun getSpecialItems(user: User): Flow<List<Skill>>
suspend fun useSkill(key: String, target: String?, taskId: String): SkillResponse?
suspend fun useSkill(key: String, target: String?): SkillResponse?
@ -49,37 +47,37 @@ interface UserRepository : BaseRepository {
suspend fun runCron(tasks: MutableList<Task>)
suspend fun runCron()
fun readNotification(id: String): Flowable<List<Any>>
fun readNotifications(notificationIds: Map<String, List<String>>): Flowable<List<Any>>
fun seeNotifications(notificationIds: Map<String, List<String>>): Flowable<List<Any>>
suspend fun readNotification(id: String): List<Any>?
suspend fun readNotifications(notificationIds: Map<String, List<String>>): List<Any>?
suspend fun seeNotifications(notificationIds: Map<String, List<String>>): List<Any>?
fun changeCustomDayStart(dayStartTime: Int): Flowable<User>
suspend fun changeCustomDayStart(dayStartTime: Int): User?
suspend fun updateLanguage(languageCode: String): User?
suspend fun resetAccount(): User?
fun deleteAccount(password: String): Flowable<Void>
suspend fun deleteAccount(password: String): Void?
fun sendPasswordResetEmail(email: String): Flowable<Void>
suspend fun sendPasswordResetEmail(email: String): Void?
suspend fun updateLoginName(newLoginName: String, password: String? = null): User?
fun updateEmail(newEmail: String, password: String): Flowable<Void>
fun updatePassword(oldPassword: String, newPassword: String, newPasswordConfirmation: String): Flowable<Void>
fun verifyUsername(username: String): Flowable<VerifyUsernameResponse>
suspend fun updateEmail(newEmail: String, password: String): Void?
suspend fun updatePassword(oldPassword: String, newPassword: String, newPasswordConfirmation: String): Void?
suspend fun verifyUsername(username: String): VerifyUsernameResponse?
fun allocatePoint(stat: Attribute): Flowable<Stats>
fun bulkAllocatePoints(strength: Int, intelligence: Int, constitution: Int, perception: Int): Flowable<Stats>
suspend fun allocatePoint(stat: Attribute): Stats?
suspend fun bulkAllocatePoints(strength: Int, intelligence: Int, constitution: Int, perception: Int): Stats?
suspend fun useCustomization(type: String, category: String?, identifier: String): User?
fun retrieveAchievements(): Flowable<List<Achievement>>
suspend fun retrieveAchievements(): List<Achievement>?
fun getAchievements(): Flow<List<Achievement>>
fun getQuestAchievements(): Flow<List<QuestAchievement>>
fun getUserQuestStatus(): Flowable<UserQuestStatus>
fun getUserQuestStatus(): Flow<UserQuestStatus>
suspend fun reroll(): User?
fun retrieveTeamPlans(): Flowable<List<TeamPlan>>
suspend fun retrieveTeamPlans(): List<TeamPlan>?
fun getTeamPlans(): Flow<List<TeamPlan>>
suspend fun retrieveTeamPlan(teamID: String): Group?
fun getTeamPlan(teamID: String): Flowable<Group>
fun getTeamPlan(teamID: String): Flow<Group>
}

View file

@ -51,11 +51,7 @@ import com.habitrpg.shared.habitica.models.responses.FeedResponse
import com.habitrpg.shared.habitica.models.responses.Status
import com.habitrpg.shared.habitica.models.responses.TaskDirectionData
import com.habitrpg.shared.habitica.models.responses.VerifyUsernameResponse
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.core.Flowable
import io.reactivex.rxjava3.core.FlowableTransformer
import io.reactivex.rxjava3.functions.Consumer
import io.reactivex.rxjava3.schedulers.Schedulers
import okhttp3.Cache
import okhttp3.OkHttpClient
import okhttp3.Request
@ -87,17 +83,6 @@ class ApiClientImpl(
// I think we don't need the ApiClientImpl anymore we could just use ApiService
private lateinit var apiService: ApiService
private val apiCallTransformer = FlowableTransformer<HabitResponse<Any>, Any> { observable ->
observable
.filter { it.data != null }
.map { habitResponse ->
processResponse(habitResponse)
}
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnError(this)
}
private fun <T> processResponse(habitResponse: HabitResponse<T>): T? {
habitResponse.notifications?.let {
notificationsManager.setNotifications(it)
@ -114,7 +99,7 @@ class ApiClientImpl(
return null
}
private var languageCode: String? = null
override var languageCode: String? = null
private var lastAPICallURL: String? = null
private var hadError = false
@ -186,28 +171,28 @@ class ApiClientImpl(
}
}
override fun registerUser(
override suspend fun registerUser(
username: String,
email: String,
password: String,
confirmPassword: String
): Flowable<UserAuthResponse> {
): UserAuthResponse? {
val auth = UserAuth()
auth.username = username
auth.password = password
auth.confirmPassword = confirmPassword
auth.email = email
return this.apiService.registerUser(auth).compose(configureApiCallObserver())
return process { this.apiService.registerUser(auth) }
}
override fun connectUser(username: String, password: String): Flowable<UserAuthResponse> {
override suspend fun connectUser(username: String, password: String): UserAuthResponse? {
val auth = UserAuth()
auth.username = username
auth.password = password
return this.apiService.connectLocal(auth).compose(configureApiCallObserver())
return process { this.apiService.connectLocal(auth) }
}
override fun connectSocial(network: String, userId: String, accessToken: String): Flowable<UserAuthResponse> {
override suspend fun connectSocial(network: String, userId: String, accessToken: String): UserAuthResponse? {
val auth = UserAuthSocial()
auth.network = network
val authResponse = UserAuthSocialTokens()
@ -215,15 +200,15 @@ class ApiClientImpl(
authResponse.access_token = accessToken
auth.authResponse = authResponse
return this.apiService.connectSocial(auth).compose(configureApiCallObserver())
return process { this.apiService.connectSocial(auth) }
}
override fun disconnectSocial(network: String): Flowable<Void> {
return this.apiService.disconnectSocial(network).compose(configureApiCallObserver())
override suspend fun disconnectSocial(network: String): Void? {
return process { this.apiService.disconnectSocial(network) }
}
override fun loginApple(authToken: String): Flowable<UserAuthResponse> {
return apiService.loginApple(mapOf(Pair("code", authToken))).compose(configureApiCallObserver())
override suspend fun loginApple(authToken: String): UserAuthResponse? {
return process { apiService.loginApple(mapOf(Pair("code", authToken))) }
}
override fun accept(throwable: Throwable) {
@ -290,8 +275,8 @@ class ApiClientImpl(
return process { apiService.getInboxMessages(uuid, page) }
}
override fun retrieveInboxConversations(): Flowable<List<InboxConversation>> {
return apiService.getInboxConversations().compose(configureApiCallObserver())
override suspend fun retrieveInboxConversations(): List<InboxConversation>? {
return process { apiService.getInboxConversations() }
}
override fun hasAuthenticationKeys(): Boolean {
@ -330,11 +315,6 @@ class ApiClientImpl(
See here for more info: http://blog.danlew.net/2015/03/02/dont-break-the-chain/
*/
override fun <T : Any> configureApiCallObserver(): FlowableTransformer<HabitResponse<T>, T> {
@Suppress("UNCHECKED_CAST")
return apiCallTransformer as FlowableTransformer<HabitResponse<T>, T>
}
override fun updateAuthenticationCredentials(userID: String?, apiToken: String?) {
this.hostConfig.userID = userID ?: ""
this.hostConfig.apiKey = apiToken ?: ""
@ -342,10 +322,6 @@ class ApiClientImpl(
Amplitude.getInstance().userId = this.hostConfig.userID
}
override fun setLanguageCode(languageCode: String) {
this.languageCode = languageCode
}
override suspend fun getStatus(): Status? = process { apiService.getStatus() }
override suspend fun getContent(language: String?): ContentResult? {
@ -356,40 +332,40 @@ class ApiClientImpl(
return process { apiService.updateUser(updateDictionary) }
}
override fun registrationLanguage(registrationLanguage: String): Flowable<User> {
return apiService.registrationLanguage(registrationLanguage).compose(configureApiCallObserver())
override suspend fun registrationLanguage(registrationLanguage: String): User? {
return process { apiService.registrationLanguage(registrationLanguage) }
}
override suspend fun retrieveInAppRewards(): List<ShopItem>? {
return process { apiService.retrieveInAppRewards() }
}
override fun equipItem(type: String, itemKey: String): Flowable<Items> {
return apiService.equipItem(type, itemKey).compose(configureApiCallObserver())
override suspend fun equipItem(type: String, itemKey: String): Items? {
return process { apiService.equipItem(type, itemKey) }
}
override suspend fun buyItem(itemKey: String, purchaseQuantity: Int): BuyResponse? {
return process { apiService.buyItem(itemKey, mapOf(Pair("quantity", purchaseQuantity))) }
}
override fun unlinkAllTasks(challengeID: String?, keepOption: String): Flowable<Void> {
return apiService.unlinkAllTasks(challengeID, keepOption).compose(configureApiCallObserver())
override suspend fun unlinkAllTasks(challengeID: String?, keepOption: String): Void? {
return process { apiService.unlinkAllTasks(challengeID, keepOption) }
}
override fun blockMember(userID: String): Flowable<List<String>> {
return apiService.blockMember(userID).compose(configureApiCallObserver())
override suspend fun blockMember(userID: String): List<String>? {
return process { apiService.blockMember(userID) }
}
override suspend fun purchaseItem(type: String, itemKey: String, purchaseQuantity: Int): Void? {
return process { apiService.purchaseItem(type, itemKey, mapOf(Pair("quantity", purchaseQuantity))) }
}
override fun validateSubscription(request: PurchaseValidationRequest): Flowable<Any> {
return apiService.validateSubscription(request).compose(configureApiCallObserver())
override suspend fun validateSubscription(request: PurchaseValidationRequest): Any? {
return process { apiService.validateSubscription(request) }
}
override fun validateNoRenewSubscription(request: PurchaseValidationRequest): Flowable<Any> {
return apiService.validateNoRenewSubscription(request).compose(configureApiCallObserver())
override suspend fun validateNoRenewSubscription(request: PurchaseValidationRequest): Any? {
return process { apiService.validateNoRenewSubscription(request) }
}
override suspend fun cancelSubscription(): Void? {
@ -412,83 +388,80 @@ class ApiClientImpl(
return process { apiService.purchaseSpecialSpell(key) }
}
override fun sellItem(itemType: String, itemKey: String): Flowable<User> {
return apiService.sellItem(itemType, itemKey).compose(configureApiCallObserver())
override suspend fun sellItem(itemType: String, itemKey: String): User? {
return process { apiService.sellItem(itemType, itemKey) }
}
override fun feedPet(petKey: String, foodKey: String): Flowable<FeedResponse> {
return apiService.feedPet(petKey, foodKey)
.map {
it.data?.message = it.message
it
}
.compose(configureApiCallObserver())
override suspend fun feedPet(petKey: String, foodKey: String): FeedResponse? {
val response = apiService.feedPet(petKey, foodKey)
response.data?.message = response.message
return process { response }
}
override fun hatchPet(eggKey: String, hatchingPotionKey: String): Flowable<Items> {
return apiService.hatchPet(eggKey, hatchingPotionKey).compose(configureApiCallObserver())
override suspend fun hatchPet(eggKey: String, hatchingPotionKey: String): Items? {
return process { apiService.hatchPet(eggKey, hatchingPotionKey) }
}
override suspend fun getTasks(): TaskList? = process { apiService.getTasks() }
override fun getTasks(type: String): Flowable<TaskList> {
return apiService.getTasks(type).compose(configureApiCallObserver())
override suspend fun getTasks(type: String): TaskList? {
return process { apiService.getTasks(type) }
}
override fun getTasks(type: String, dueDate: String): Flowable<TaskList> {
return apiService.getTasks(type, dueDate).compose(configureApiCallObserver())
override suspend fun getTasks(type: String, dueDate: String): TaskList? {
return process { apiService.getTasks(type, dueDate) }
}
override suspend fun unlockPath(path: String): UnlockResponse? {
return process { apiService.unlockPath(path) }
}
override fun getTask(id: String): Flowable<Task> {
return apiService.getTask(id).compose(configureApiCallObserver())
override suspend fun getTask(id: String): Task? {
return process { apiService.getTask(id) }
}
override suspend fun postTaskDirection(id: String, direction: String): TaskDirectionData? {
return process { apiService.postTaskDirection(id, direction) }
}
override fun bulkScoreTasks(data: List<Map<String, String>>): Flowable<BulkTaskScoringData> {
return apiService.bulkScoreTasks(data).compose(configureApiCallObserver())
override suspend fun bulkScoreTasks(data: List<Map<String, String>>): BulkTaskScoringData? {
return process { apiService.bulkScoreTasks(data) }
}
override fun postTaskNewPosition(id: String, position: Int): Flowable<List<String>> {
return apiService.postTaskNewPosition(id, position).compose(configureApiCallObserver())
override suspend fun postTaskNewPosition(id: String, position: Int): List<String>? {
return process { apiService.postTaskNewPosition(id, position) }
}
override suspend fun scoreChecklistItem(taskId: String, itemId: String): Task? {
return process { apiService.scoreChecklistItem(taskId, itemId) }
}
override fun createTask(item: Task): Flowable<Task> {
return apiService.createTask(item).compose(configureApiCallObserver())
override suspend fun createTask(item: Task): Task? {
return process { apiService.createTask(item) }
}
override fun createTasks(tasks: List<Task>): Flowable<List<Task>> {
return apiService.createTasks(tasks).compose(configureApiCallObserver())
override suspend fun createTasks(tasks: List<Task>): List<Task>? {
return process { apiService.createTasks(tasks) }
}
override fun updateTask(id: String, item: Task): Flowable<Task> {
return apiService.updateTask(id, item).compose(configureApiCallObserver())
override suspend fun updateTask(id: String, item: Task): Task? {
return process { apiService.updateTask(id, item) }
}
override fun deleteTask(id: String): Flowable<Void> {
return apiService.deleteTask(id).compose(configureApiCallObserver())
override suspend fun deleteTask(id: String): Void? {
return process { apiService.deleteTask(id) }
}
override fun createTag(tag: Tag): Flowable<Tag> {
return apiService.createTag(tag).compose(configureApiCallObserver())
override suspend fun createTag(tag: Tag): Tag? {
return process { apiService.createTag(tag) }
}
override fun updateTag(id: String, tag: Tag): Flowable<Tag> {
return apiService.updateTag(id, tag).compose(configureApiCallObserver())
override suspend fun updateTag(id: String, tag: Tag): Tag? {
return process { apiService.updateTag(id, tag) }
}
override fun deleteTag(id: String): Flowable<Void> {
return apiService.deleteTag(id).compose(configureApiCallObserver())
override suspend fun deleteTag(id: String): Void? {
return process { apiService.deleteTag(id) }
}
override suspend fun sleep(): Boolean? = process { apiService.sleep() }
@ -515,16 +488,12 @@ class ApiClientImpl(
override suspend fun disableClasses(): User? = process { apiService.disableClasses() }
override fun markPrivateMessagesRead(): Flowable<Void> {
// This is necessary, because the API call returns weird data.
override suspend fun markPrivateMessagesRead(): Void {
return apiService.markPrivateMessagesRead()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnError(this)
}
override fun listGroups(type: String): Flowable<List<Group>> {
return apiService.listGroups(type).compose(configureApiCallObserver())
override suspend fun listGroups(type: String): List<Group>? {
return process { apiService.listGroups(type) }
}
override suspend fun getGroup(groupId: String): Group? {
@ -555,15 +524,15 @@ class ApiClientImpl(
return processResponse(apiService.leaveGroup(groupId, keepChallenges))
}
override fun postGroupChat(groupId: String, message: Map<String, String>): Flowable<PostChatMessageResult> {
return apiService.postGroupChat(groupId, message).compose(configureApiCallObserver())
override suspend fun postGroupChat(groupId: String, message: Map<String, String>): PostChatMessageResult? {
return process { apiService.postGroupChat(groupId, message) }
}
override fun deleteMessage(groupId: String, messageId: String): Flowable<Void> {
return apiService.deleteMessage(groupId, messageId).compose(configureApiCallObserver())
override suspend fun deleteMessage(groupId: String, messageId: String): Void? {
return process { apiService.deleteMessage(groupId, messageId) }
}
override fun deleteInboxMessage(id: String): Flowable<Void> {
return apiService.deleteInboxMessage(id).compose(configureApiCallObserver())
override suspend fun deleteInboxMessage(id: String): Void? {
return process { apiService.deleteInboxMessage(id) }
}
override suspend fun getGroupMembers(groupId: String, includeAllPublicFields: Boolean?): List<Member>? {
@ -574,181 +543,181 @@ class ApiClientImpl(
return processResponse(apiService.getGroupMembers(groupId, includeAllPublicFields, lastId))
}
override fun likeMessage(groupId: String, mid: String): Flowable<ChatMessage> {
return apiService.likeMessage(groupId, mid).compose(configureApiCallObserver())
override suspend fun likeMessage(groupId: String, mid: String): ChatMessage? {
return process { apiService.likeMessage(groupId, mid) }
}
override fun flagMessage(groupId: String, mid: String, data: MutableMap<String, String>): Flowable<Void> {
return apiService.flagMessage(groupId, mid, data).compose(configureApiCallObserver())
override suspend fun flagMessage(groupId: String, mid: String, data: MutableMap<String, String>): Void? {
return process { apiService.flagMessage(groupId, mid, data) }
}
override fun flagInboxMessage(mid: String, data: MutableMap<String, String>): Flowable<Void> {
return apiService.flagInboxMessage(mid, data).compose(configureApiCallObserver())
override suspend fun flagInboxMessage(mid: String, data: MutableMap<String, String>): Void? {
return process { apiService.flagInboxMessage(mid, data) }
}
override fun seenMessages(groupId: String): Flowable<Void> {
return apiService.seenMessages(groupId).compose(configureApiCallObserver())
override suspend fun seenMessages(groupId: String): Void? {
return process { apiService.seenMessages(groupId) }
}
override fun inviteToGroup(groupId: String, inviteData: Map<String, Any>): Flowable<List<Void>> {
return apiService.inviteToGroup(groupId, inviteData).compose(configureApiCallObserver())
override suspend fun inviteToGroup(groupId: String, inviteData: Map<String, Any>): List<Void>? {
return process { apiService.inviteToGroup(groupId, inviteData) }
}
override fun rejectGroupInvite(groupId: String): Flowable<Void> {
return apiService.rejectGroupInvite(groupId).compose(configureApiCallObserver())
override suspend fun rejectGroupInvite(groupId: String): Void? {
return process { apiService.rejectGroupInvite(groupId) }
}
override fun acceptQuest(groupId: String): Flowable<Void> {
return apiService.acceptQuest(groupId).compose(configureApiCallObserver())
override suspend fun acceptQuest(groupId: String): Void? {
return process { apiService.acceptQuest(groupId) }
}
override fun rejectQuest(groupId: String): Flowable<Void> {
return apiService.rejectQuest(groupId).compose(configureApiCallObserver())
override suspend fun rejectQuest(groupId: String): Void? {
return process { apiService.rejectQuest(groupId) }
}
override fun cancelQuest(groupId: String): Flowable<Void> {
return apiService.cancelQuest(groupId).compose(configureApiCallObserver())
override suspend fun cancelQuest(groupId: String): Void? {
return process { apiService.cancelQuest(groupId) }
}
override fun forceStartQuest(groupId: String, group: Group): Flowable<Quest> {
return apiService.forceStartQuest(groupId, group).compose(configureApiCallObserver())
override suspend fun forceStartQuest(groupId: String, group: Group): Quest? {
return process { apiService.forceStartQuest(groupId, group) }
}
override fun inviteToQuest(groupId: String, questKey: String): Flowable<Quest> {
return apiService.inviteToQuest(groupId, questKey).compose(configureApiCallObserver())
override suspend fun inviteToQuest(groupId: String, questKey: String): Quest? {
return process { apiService.inviteToQuest(groupId, questKey) }
}
override fun abortQuest(groupId: String): Flowable<Quest> {
return apiService.abortQuest(groupId).compose(configureApiCallObserver())
override suspend fun abortQuest(groupId: String): Quest? {
return process { apiService.abortQuest(groupId) }
}
override fun leaveQuest(groupId: String): Flowable<Void> {
return apiService.leaveQuest(groupId).compose(configureApiCallObserver())
override suspend fun leaveQuest(groupId: String): Void? {
return process { apiService.leaveQuest(groupId) }
}
override fun validatePurchase(request: PurchaseValidationRequest): Flowable<PurchaseValidationResult> {
return apiService.validatePurchase(request).compose(configureApiCallObserver())
override suspend fun validatePurchase(request: PurchaseValidationRequest): PurchaseValidationResult? {
return process { apiService.validatePurchase(request) }
}
override fun changeCustomDayStart(updateObject: Map<String, Any>): Flowable<User> {
return apiService.changeCustomDayStart(updateObject).compose(configureApiCallObserver())
override suspend fun changeCustomDayStart(updateObject: Map<String, Any>): User? {
return process { apiService.changeCustomDayStart(updateObject) }
}
override suspend fun getMember(memberId: String) = processResponse(apiService.getMember(memberId))
override suspend fun getMemberWithUsername(username: String) = processResponse(apiService.getMemberWithUsername(username))
override fun getMemberAchievements(memberId: String): Flowable<List<Achievement>> {
return apiService.getMemberAchievements(memberId, languageCode).compose(configureApiCallObserver())
override suspend fun getMemberAchievements(memberId: String): List<Achievement>? {
return process { apiService.getMemberAchievements(memberId, languageCode) }
}
override fun findUsernames(username: String, context: String?, id: String?): Flowable<List<FindUsernameResult>> {
return apiService.findUsernames(username, context, id).compose(configureApiCallObserver())
override suspend fun findUsernames(username: String, context: String?, id: String?): List<FindUsernameResult>? {
return process { apiService.findUsernames(username, context, id) }
}
override suspend fun postPrivateMessage(messageDetails: Map<String, String>): PostChatMessageResult? {
return process { apiService.postPrivateMessage(messageDetails) }
}
override fun retrieveShopIventory(identifier: String): Flowable<Shop> {
return apiService.retrieveShopInventory(identifier, languageCode).compose(configureApiCallObserver())
override suspend fun retrieveShopIventory(identifier: String): Shop? {
return process { apiService.retrieveShopInventory(identifier, languageCode) }
}
override fun addPushDevice(pushDeviceData: Map<String, String>): Flowable<List<Void>> {
return apiService.addPushDevice(pushDeviceData).compose(configureApiCallObserver())
override suspend fun addPushDevice(pushDeviceData: Map<String, String>): List<Void>? {
return process { apiService.addPushDevice(pushDeviceData) }
}
override fun deletePushDevice(regId: String): Flowable<List<Void>> {
return apiService.deletePushDevice(regId).compose(configureApiCallObserver())
override suspend fun deletePushDevice(regId: String): List<Void>? {
return process { apiService.deletePushDevice(regId) }
}
override fun getUserChallenges(page: Int, memberOnly: Boolean): Flowable<List<Challenge>> {
override suspend fun getUserChallenges(page: Int, memberOnly: Boolean): List<Challenge>? {
return if (memberOnly) {
apiService.getUserChallenges(page, memberOnly).compose(configureApiCallObserver())
process { apiService.getUserChallenges(page, memberOnly) }
} else {
apiService.getUserChallenges(page).compose(configureApiCallObserver())
process { apiService.getUserChallenges(page) }
}
}
override fun getChallengeTasks(challengeId: String): Flowable<TaskList> {
return apiService.getChallengeTasks(challengeId).compose(configureApiCallObserver())
override suspend fun getChallengeTasks(challengeId: String): TaskList? {
return process { apiService.getChallengeTasks(challengeId) }
}
override fun getChallenge(challengeId: String): Flowable<Challenge> {
return apiService.getChallenge(challengeId).compose(configureApiCallObserver())
override suspend fun getChallenge(challengeId: String): Challenge? {
return process { apiService.getChallenge(challengeId) }
}
override fun joinChallenge(challengeId: String): Flowable<Challenge> {
return apiService.joinChallenge(challengeId).compose(configureApiCallObserver())
override suspend fun joinChallenge(challengeId: String): Challenge? {
return process { apiService.joinChallenge(challengeId) }
}
override fun leaveChallenge(challengeId: String, body: LeaveChallengeBody): Flowable<Void> {
return apiService.leaveChallenge(challengeId, body).compose(configureApiCallObserver())
override suspend fun leaveChallenge(challengeId: String, body: LeaveChallengeBody): Void? {
return process { apiService.leaveChallenge(challengeId, body) }
}
override fun createChallenge(challenge: Challenge): Flowable<Challenge> {
return apiService.createChallenge(challenge).compose(configureApiCallObserver())
override suspend fun createChallenge(challenge: Challenge): Challenge? {
return process { apiService.createChallenge(challenge) }
}
override fun createChallengeTasks(challengeId: String, tasks: List<Task>): Flowable<List<Task>> {
return apiService.createChallengeTasks(challengeId, tasks).compose(configureApiCallObserver())
override suspend fun createChallengeTasks(challengeId: String, tasks: List<Task>): List<Task>? {
return process { apiService.createChallengeTasks(challengeId, tasks) }
}
override fun createChallengeTask(challengeId: String, task: Task): Flowable<Task> {
return apiService.createChallengeTask(challengeId, task).compose(configureApiCallObserver())
override suspend fun createChallengeTask(challengeId: String, task: Task): Task? {
return process { apiService.createChallengeTask(challengeId, task) }
}
override fun updateChallenge(challenge: Challenge): Flowable<Challenge> {
return apiService.updateChallenge(challenge.id ?: "", challenge).compose(configureApiCallObserver())
override suspend fun updateChallenge(challenge: Challenge): Challenge? {
return process { apiService.updateChallenge(challenge.id ?: "", challenge) }
}
override fun deleteChallenge(challengeId: String): Flowable<Void> {
return apiService.deleteChallenge(challengeId).compose(configureApiCallObserver())
override suspend fun deleteChallenge(challengeId: String): Void? {
return process { apiService.deleteChallenge(challengeId) }
}
override fun debugAddTenGems(): Flowable<Void> {
return apiService.debugAddTenGems().compose(configureApiCallObserver())
override suspend fun debugAddTenGems(): Void? {
return process { apiService.debugAddTenGems() }
}
override fun readNotification(notificationId: String): Flowable<List<Any>> {
return apiService.readNotification(notificationId).compose(configureApiCallObserver())
override suspend fun readNotification(notificationId: String): List<Any>? {
return process { apiService.readNotification(notificationId) }
}
override fun readNotifications(notificationIds: Map<String, List<String>>): Flowable<List<Any>> {
return apiService.readNotifications(notificationIds).compose(configureApiCallObserver())
override suspend fun readNotifications(notificationIds: Map<String, List<String>>): List<Any>? {
return process { apiService.readNotifications(notificationIds) }
}
override fun seeNotifications(notificationIds: Map<String, List<String>>): Flowable<List<Any>> {
return apiService.seeNotifications(notificationIds).compose(configureApiCallObserver())
override suspend fun seeNotifications(notificationIds: Map<String, List<String>>): List<Any>? {
return process { apiService.seeNotifications(notificationIds) }
}
override fun openMysteryItem(): Flowable<Equipment> {
return apiService.openMysteryItem().compose(configureApiCallObserver())
override suspend fun openMysteryItem(): Equipment? {
return process { apiService.openMysteryItem() }
}
override fun runCron(): Flowable<Void> {
return apiService.runCron().compose(configureApiCallObserver())
override suspend fun runCron(): Void? {
return process { apiService.runCron() }
}
override suspend fun reroll(): User? = process { apiService.reroll() }
override fun resetAccount(): Flowable<Void> {
return apiService.resetAccount().compose(configureApiCallObserver())
override suspend fun resetAccount(): Void? {
return process { apiService.resetAccount() }
}
override fun deleteAccount(password: String): Flowable<Void> {
override suspend fun deleteAccount(password: String): Void? {
val updateObject = HashMap<String, String>()
updateObject["password"] = password
return apiService.deleteAccount(updateObject).compose(configureApiCallObserver())
return process { apiService.deleteAccount(updateObject) }
}
override suspend fun togglePinnedItem(pinType: String, path: String): Void? {
return process { apiService.togglePinnedItem(pinType, path) }
}
override fun sendPasswordResetEmail(email: String): Flowable<Void> {
override suspend fun sendPasswordResetEmail(email: String): Void? {
val data = HashMap<String, String>()
data["email"] = email
return apiService.sendPasswordResetEmail(data).compose(configureApiCallObserver())
return process { apiService.sendPasswordResetEmail(data) }
}
override suspend fun updateLoginName(newLoginName: String, password: String): Void? {
@ -764,55 +733,55 @@ class ApiClientImpl(
return process { apiService.updateLoginName(updateObject) }
}
override fun verifyUsername(username: String): Flowable<VerifyUsernameResponse> {
override suspend fun verifyUsername(username: String): VerifyUsernameResponse? {
val updateObject = HashMap<String, String>()
updateObject["username"] = username
return this.apiService.verifyUsername(updateObject).compose(configureApiCallObserver())
return process { this.apiService.verifyUsername(updateObject) }
}
override fun updateEmail(newEmail: String, password: String): Flowable<Void> {
override suspend fun updateEmail(newEmail: String, password: String): Void? {
val updateObject = HashMap<String, String>()
updateObject["newEmail"] = newEmail
if (password.isNotBlank()) {
updateObject["password"] = password
}
return apiService.updateEmail(updateObject).compose(configureApiCallObserver())
return process { apiService.updateEmail(updateObject) }
}
override fun updatePassword(
override suspend fun updatePassword(
oldPassword: String,
newPassword: String,
newPasswordConfirmation: String
): Flowable<Void> {
): Void? {
val updateObject = HashMap<String, String>()
updateObject["password"] = oldPassword
updateObject["newPassword"] = newPassword
updateObject["confirmPassword"] = newPasswordConfirmation
return apiService.updatePassword(updateObject).compose(configureApiCallObserver())
return process { apiService.updatePassword(updateObject) }
}
override fun allocatePoint(stat: String): Flowable<Stats> {
return apiService.allocatePoint(stat).compose(configureApiCallObserver())
override suspend fun allocatePoint(stat: String): Stats? {
return process { apiService.allocatePoint(stat) }
}
override fun transferGems(giftedID: String, amount: Int): Flowable<Void> {
return apiService.transferGems(mapOf(Pair("toUserId", giftedID), Pair("gemAmount", amount))).compose(configureApiCallObserver())
override suspend fun transferGems(giftedID: String, amount: Int): Void? {
return process { apiService.transferGems(mapOf(Pair("toUserId", giftedID), Pair("gemAmount", amount))) }
}
override fun getTeamPlans(): Flowable<List<TeamPlan>> {
return apiService.getTeamPlans().compose(configureApiCallObserver())
override suspend fun getTeamPlans(): List<TeamPlan>? {
return process { apiService.getTeamPlans() }
}
override suspend fun getTeamPlanTasks(teamID: String): TaskList? {
return processResponse(apiService.getTeamPlanTasks(teamID))
}
override fun bulkAllocatePoints(
override suspend fun bulkAllocatePoints(
strength: Int,
intelligence: Int,
constitution: Int,
perception: Int
): Flowable<Stats> {
): Stats? {
val body = HashMap<String, Map<String, Int>>()
val stats = HashMap<String, Int>()
stats["str"] = strength
@ -820,11 +789,11 @@ class ApiClientImpl(
stats["con"] = constitution
stats["per"] = perception
body["stats"] = stats
return apiService.bulkAllocatePoints(body).compose(configureApiCallObserver())
return process { apiService.bulkAllocatePoints(body) }
}
override fun retrieveMarketGear(): Flowable<Shop> {
return apiService.retrieveMarketGear(languageCode).compose(configureApiCallObserver())
override suspend fun retrieveMarketGear(): Shop? {
return process { apiService.retrieveMarketGear(languageCode) }
}
override suspend fun getWorldState(): WorldState? = process { apiService.worldState() }

View file

@ -10,8 +10,7 @@ import com.habitrpg.android.habitica.models.tasks.Task
import com.habitrpg.android.habitica.models.tasks.TaskList
import com.habitrpg.shared.habitica.models.tasks.TaskType
import com.habitrpg.shared.habitica.models.tasks.TasksOrder
import io.reactivex.rxjava3.core.Flowable
import retrofit2.HttpException
import kotlinx.coroutines.flow.Flow
class ChallengeRepositoryImpl(
localRepository: ChallengeLocalRepository,
@ -19,47 +18,42 @@ class ChallengeRepositoryImpl(
userID: String
) : BaseRepositoryImpl<ChallengeLocalRepository>(localRepository, apiClient, userID), ChallengeRepository {
override fun isChallengeMember(challengeID: String): Flowable<Boolean> {
override fun isChallengeMember(challengeID: String): Flow<Boolean> {
return localRepository.isChallengeMember(userID, challengeID)
}
override fun getChallengepMembership(id: String): Flowable<ChallengeMembership> {
override fun getChallengepMembership(id: String): Flow<ChallengeMembership> {
return localRepository.getChallengeMembership(userID, id)
}
override fun getChallengeMemberships(): Flowable<out List<ChallengeMembership>> {
override fun getChallengeMemberships(): Flow<List<ChallengeMembership>> {
return localRepository.getChallengeMemberships(userID)
}
override fun getChallenge(challengeId: String): Flowable<Challenge> {
override fun getChallenge(challengeId: String): Flow<Challenge> {
return localRepository.getChallenge(challengeId)
}
override fun getChallengeTasks(challengeId: String): Flowable<out List<Task>> {
override fun getChallengeTasks(challengeId: String): Flow<List<Task>> {
return localRepository.getTasks(challengeId)
}
override fun retrieveChallenge(challengeID: String): Flowable<Challenge> {
return apiClient.getChallenge(challengeID).doOnNext {
localRepository.save(it)
}
.doOnError {
if (it is HttpException && it.code() == 404) {
localRepository.getChallenge(challengeID).firstElement().subscribe { challenge ->
localRepository.delete(challenge)
}
}
}
override suspend fun retrieveChallenge(challengeID: String): Challenge? {
val challenge = apiClient.getChallenge(challengeID) ?: return null
localRepository.save(challenge)
return challenge
}
override fun retrieveChallengeTasks(challengeID: String): Flowable<TaskList> {
return apiClient.getChallengeTasks(challengeID).doOnNext { tasks ->
override suspend fun retrieveChallengeTasks(challengeID: String): TaskList? {
val tasks = apiClient.getChallengeTasks(challengeID)
if (tasks != null) {
val taskList = tasks.tasks.values.toList()
taskList.forEach {
it.userId = challengeID
}
localRepository.save(taskList)
}
return tasks
}
private fun getTaskOrders(taskList: List<Task>): TasksOrder {
@ -81,75 +75,81 @@ class ChallengeRepositoryImpl(
return tasksOrder
}
private fun addChallengeTasks(challenge: Challenge, addedTaskList: List<Task>): Flowable<Challenge> {
return when {
addedTaskList.count() == 1 -> apiClient.createChallengeTask(challenge.id ?: "", addedTaskList[0]).map { challenge }
addedTaskList.count() > 1 -> apiClient.createChallengeTasks(challenge.id ?: "", addedTaskList).map { challenge }
else -> Flowable.just(challenge)
private suspend fun addChallengeTasks(challenge: Challenge, addedTaskList: List<Task>) {
when {
addedTaskList.count() == 1 -> apiClient.createChallengeTask(challenge.id ?: "", addedTaskList[0])
addedTaskList.count() > 1 -> apiClient.createChallengeTasks(challenge.id ?: "", addedTaskList)
}
}
override fun createChallenge(challenge: Challenge, taskList: List<Task>): Flowable<Challenge> {
override suspend fun createChallenge(challenge: Challenge, taskList: List<Task>): Challenge? {
challenge.tasksOrder = getTaskOrders(taskList)
return apiClient.createChallenge(challenge).flatMap {
addChallengeTasks(it, taskList)
val createdChallenge = apiClient.createChallenge(challenge)
if (createdChallenge != null) {
addChallengeTasks(createdChallenge, taskList)
}
return createdChallenge
}
override fun updateChallenge(
override suspend fun updateChallenge(
challenge: Challenge,
fullTaskList: List<Task>,
addedTaskList: List<Task>,
updatedTaskList: List<Task>,
removedTaskList: List<String>
): Flowable<Challenge> {
var flowable: Flowable<*> = Flowable.just("")
): Challenge? {
updatedTaskList
.map { localRepository.getUnmanagedCopy(it) }
.forEach { task ->
flowable = flowable.flatMap { apiClient.updateTask(task.id ?: "", task) }
apiClient.updateTask(task.id ?: "", task)
}
removedTaskList.forEach { task ->
flowable = flowable.flatMap { apiClient.deleteTask(task) }
apiClient.deleteTask(task)
}
if (addedTaskList.isNotEmpty()) {
flowable = flowable.flatMap { addChallengeTasks(challenge, addedTaskList) }
addChallengeTasks(challenge, addedTaskList)
}
challenge.tasksOrder = getTaskOrders(fullTaskList)
return flowable.flatMap { apiClient.updateChallenge(challenge) }
.doOnNext { localRepository.save(challenge) }
val updatedChallenges = apiClient.updateChallenge(challenge)
if (updatedChallenges != null) {
localRepository.save(updatedChallenges)
}
return updatedChallenges
}
override fun deleteChallenge(challengeId: String): Flowable<Void> {
override suspend fun deleteChallenge(challengeId: String): Void? {
return apiClient.deleteChallenge(challengeId)
}
override fun getChallenges(): Flowable<out List<Challenge>> {
override fun getChallenges(): Flow<List<Challenge>> {
return localRepository.challenges
}
override fun getUserChallenges(userId: String?): Flowable<out List<Challenge>> {
override fun getUserChallenges(userId: String?): Flow<List<Challenge>> {
return localRepository.getUserChallenges(userId ?: userID)
}
override fun retrieveChallenges(page: Int, memberOnly: Boolean): Flowable<List<Challenge>> {
return apiClient.getUserChallenges(page, memberOnly)
.doOnNext { localRepository.saveChallenges(it, page == 0, memberOnly, userID) }
override suspend fun retrieveChallenges(page: Int, memberOnly: Boolean): List<Challenge>? {
val challenges = apiClient.getUserChallenges(page, memberOnly)
if (challenges != null) {
localRepository.saveChallenges(challenges, page == 0, memberOnly, userID)
}
return challenges
}
override fun leaveChallenge(challenge: Challenge, keepTasks: String): Flowable<Void> {
return apiClient.leaveChallenge(challenge.id ?: "", LeaveChallengeBody(keepTasks))
.doOnNext { localRepository.setParticipating(userID, challenge.id ?: "", false) }
override suspend fun leaveChallenge(challenge: Challenge, keepTasks: String): Void? {
apiClient.leaveChallenge(challenge.id ?: "", LeaveChallengeBody(keepTasks))
localRepository.setParticipating(userID, challenge.id ?: "", false)
return null
}
override fun joinChallenge(challenge: Challenge): Flowable<Challenge> {
return apiClient.joinChallenge(challenge.id ?: "")
.doOnNext { localRepository.setParticipating(userID, challenge.id ?: "", true) }
override suspend fun joinChallenge(challenge: Challenge): Challenge? {
val returnedChallenge = apiClient.joinChallenge(challenge.id ?: "") ?: return null
localRepository.setParticipating(userID, returnedChallenge.id ?: "", true)
return returnedChallenge
}
}

View file

@ -8,8 +8,8 @@ import com.habitrpg.android.habitica.helpers.AprilFoolsHandler
import com.habitrpg.android.habitica.models.ContentResult
import com.habitrpg.android.habitica.models.WorldState
import com.habitrpg.android.habitica.models.inventory.SpecialItem
import io.reactivex.rxjava3.core.Flowable
import io.realm.RealmList
import kotlinx.coroutines.flow.Flow
import java.util.Date
class ContentRepositoryImpl<T : ContentLocalRepository>(
@ -52,7 +52,7 @@ class ContentRepositoryImpl<T : ContentLocalRepository>(
return null
}
override fun getWorldState(): Flowable<WorldState> {
override fun getWorldState(): Flow<WorldState> {
return localRepository.getWorldState()
}
}

View file

@ -4,7 +4,7 @@ import com.habitrpg.android.habitica.data.ApiClient
import com.habitrpg.android.habitica.data.CustomizationRepository
import com.habitrpg.android.habitica.data.local.CustomizationLocalRepository
import com.habitrpg.android.habitica.models.inventory.Customization
import io.reactivex.rxjava3.core.Flowable
import kotlinx.coroutines.flow.Flow
class CustomizationRepositoryImpl(
localRepository: CustomizationLocalRepository,
@ -12,7 +12,7 @@ class CustomizationRepositoryImpl(
userID: String
) : BaseRepositoryImpl<CustomizationLocalRepository>(localRepository, apiClient, userID), CustomizationRepository {
override fun getCustomizations(type: String, category: String?, onlyAvailable: Boolean): Flowable<out List<Customization>> {
override fun getCustomizations(type: String, category: String?, onlyAvailable: Boolean): Flow<List<Customization>> {
return localRepository.getCustomizations(type, category, onlyAvailable)
}
}

View file

@ -4,14 +4,14 @@ import com.habitrpg.android.habitica.data.ApiClient
import com.habitrpg.android.habitica.data.FAQRepository
import com.habitrpg.android.habitica.data.local.FAQLocalRepository
import com.habitrpg.android.habitica.models.FAQArticle
import io.reactivex.rxjava3.core.Flowable
import kotlinx.coroutines.flow.Flow
class FAQRepositoryImpl(localRepository: FAQLocalRepository, apiClient: ApiClient, userID: String) : BaseRepositoryImpl<FAQLocalRepository>(localRepository, apiClient, userID), FAQRepository {
override fun getArticle(position: Int): Flowable<FAQArticle> {
override fun getArticle(position: Int): Flow<FAQArticle> {
return localRepository.getArticle(position)
}
override fun getArticles(): Flowable<out List<FAQArticle>> {
override fun getArticles(): Flow<List<FAQArticle>> {
return localRepository.articles
}
}

View file

@ -22,8 +22,8 @@ import com.habitrpg.android.habitica.models.user.OwnedMount
import com.habitrpg.android.habitica.models.user.OwnedPet
import com.habitrpg.android.habitica.models.user.User
import com.habitrpg.shared.habitica.models.responses.FeedResponse
import io.reactivex.rxjava3.core.Flowable
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.firstOrNull
class InventoryRepositoryImpl(
localRepository: InventoryLocalRepository,
@ -35,7 +35,7 @@ class InventoryRepositoryImpl(
override fun getQuestContent(key: String) = localRepository.getQuestContent(key)
override fun getEquipment(searchedKeys: List<String>): Flowable<out List<Equipment>> {
override fun getEquipment(searchedKeys: List<String>): Flow<List<Equipment>> {
return localRepository.getEquipment(searchedKeys)
}
@ -43,7 +43,7 @@ class InventoryRepositoryImpl(
return localRepository.getArmoireRemainingCount()
}
override fun getInAppRewards(): Flowable<out List<ShopItem>> {
override fun getInAppRewards(): Flow<List<ShopItem>> {
return localRepository.getInAppRewards()
}
@ -55,15 +55,15 @@ class InventoryRepositoryImpl(
return rewards
}
override fun getOwnedEquipment(type: String): Flowable<out List<Equipment>> {
override fun getOwnedEquipment(type: String): Flow<List<Equipment>> {
return localRepository.getOwnedEquipment(type)
}
override fun getOwnedEquipment(): Flowable<out List<Equipment>> {
override fun getOwnedEquipment(): Flow<List<Equipment>> {
return localRepository.getOwnedEquipment()
}
override fun getEquipmentType(type: String, set: String): Flowable<out List<Equipment>> {
override fun getEquipmentType(type: String, set: String): Flow<List<Equipment>> {
return localRepository.getEquipmentType(type, set)
}
@ -71,36 +71,31 @@ class InventoryRepositoryImpl(
return localRepository.getOwnedItems(itemType, userID, includeZero)
}
override fun getOwnedItems(includeZero: Boolean): Flowable<Map<String, OwnedItem>> {
override fun getOwnedItems(includeZero: Boolean): Flow<Map<String, OwnedItem>> {
return localRepository.getOwnedItems(userID, includeZero)
}
override fun getItems(itemClass: Class<out Item>, keys: Array<String>): Flow<List<Item>> {
return localRepository.getItemsFlowable(itemClass, keys)
}
override fun getItemsFlowable(itemClass: Class<out Item>): Flowable<out List<Item>> {
return localRepository.getItemsFlowable(itemClass)
return localRepository.getItems(itemClass, keys)
}
override fun getItems(itemClass: Class<out Item>): Flow<List<Item>> {
return localRepository.getItems(itemClass)
}
override fun getEquipment(key: String): Flowable<Equipment> {
override fun getEquipment(key: String): Flow<Equipment> {
return localRepository.getEquipment(key)
}
override fun openMysteryItem(user: User?): Flowable<Equipment> {
return apiClient.openMysteryItem()
.flatMap { localRepository.getEquipment(it.key ?: "").firstElement().toFlowable() }
.doOnNext { itemData ->
val liveEquipment = localRepository.getLiveObject(itemData)
localRepository.executeTransaction {
liveEquipment?.owned = true
}
localRepository.decrementMysteryItemCount(user)
}
override suspend fun openMysteryItem(user: User?): Equipment? {
val item = apiClient.openMysteryItem()
val equipment = localRepository.getEquipment(item?.key ?: "").firstOrNull() ?: return null
val liveEquipment = localRepository.getLiveObject(equipment)
localRepository.executeTransaction {
liveEquipment?.owned = true
}
localRepository.decrementMysteryItemCount(user)
return equipment
}
override fun saveEquipment(equipment: Equipment) {
@ -135,44 +130,42 @@ class InventoryRepositoryImpl(
localRepository.updateOwnedEquipment(user)
}
override fun changeOwnedCount(type: String, key: String, amountToAdd: Int) {
override suspend fun changeOwnedCount(type: String, key: String, amountToAdd: Int) {
localRepository.changeOwnedCount(type, key, userID, amountToAdd)
}
override fun sellItem(type: String, key: String): Flowable<User> {
return localRepository.getOwnedItem(userID, type, key, true)
.flatMap { item -> sellItem(item) }
override suspend fun sellItem(type: String, key: String): User? {
val item = localRepository.getOwnedItem(userID, type, key, true).firstOrNull() ?: return null
return sellItem(item)
}
override fun sellItem(item: OwnedItem): Flowable<User> {
return localRepository.getItem(item.itemType ?: "", item.key ?: "")
.flatMap { newItem -> sellItem(newItem, item) }
override suspend fun sellItem(ownedItem: OwnedItem): User? {
val item = localRepository.getItem(ownedItem.itemType ?: "", ownedItem.key ?: "").firstOrNull() ?: return null
return sellItem(item, ownedItem)
}
override fun getLatestMysteryItem(): Flowable<Equipment> {
override fun getLatestMysteryItem(): Flow<Equipment> {
return localRepository.getLatestMysteryItem()
}
override fun getItem(type: String, key: String): Flowable<Item> {
override fun getItem(type: String, key: String): Flow<Item> {
return localRepository.getItem(type, key)
}
private fun sellItem(item: Item, ownedItem: OwnedItem): Flowable<User> {
private suspend fun sellItem(item: Item, ownedItem: OwnedItem): User? {
localRepository.executeTransaction {
val liveItem = localRepository.getLiveObject(ownedItem)
liveItem?.numberOwned = (liveItem?.numberOwned ?: 0) - 1
}
return apiClient.sellItem(item.type, item.key)
.map { user ->
localRepository.soldItem(userID, user)
}
val user = apiClient.sellItem(item.type, item.key) ?: return null
return localRepository.soldItem(userID, user)
}
override fun equipGear(equipment: String, asCostume: Boolean): Flowable<Items> {
override suspend fun equipGear(equipment: String, asCostume: Boolean): Items? {
return equip(if (asCostume) "costume" else "equipped", equipment)
}
override fun equip(type: String, key: String): Flowable<Items> {
override suspend fun equip(type: String, key: String): Items? {
val liveUser = localRepository.getLiveUser(userID)
if (liveUser != null) {
@ -199,47 +192,45 @@ class InventoryRepositoryImpl(
}
}
}
return apiClient.equipItem(type, key)
.doOnNext { items ->
if (liveUser == null) return@doOnNext
localRepository.modify(liveUser) { liveUser ->
val newEquipped = items.gear?.equipped
val oldEquipped = liveUser.items?.gear?.equipped
val newCostume = items.gear?.costume
val oldCostume = liveUser.items?.gear?.costume
newEquipped?.let { equipped -> oldEquipped?.updateWith(equipped) }
newCostume?.let { costume -> oldCostume?.updateWith(costume) }
liveUser.items?.currentMount = items.currentMount
liveUser.items?.currentPet = items.currentPet
liveUser.balance = liveUser.balance
}
}
val items = apiClient.equipItem(type, key) ?: return null
if (liveUser == null) return null
localRepository.modify(liveUser) { liveUser ->
val newEquipped = items.gear?.equipped
val oldEquipped = liveUser.items?.gear?.equipped
val newCostume = items.gear?.costume
val oldCostume = liveUser.items?.gear?.costume
newEquipped?.let { equipped -> oldEquipped?.updateWith(equipped) }
newCostume?.let { costume -> oldCostume?.updateWith(costume) }
liveUser.items?.currentMount = items.currentMount
liveUser.items?.currentPet = items.currentPet
liveUser.balance = liveUser.balance
}
return items
}
override fun feedPet(pet: Pet, food: Food): Flowable<FeedResponse> {
return apiClient.feedPet(pet.key ?: "", food.key)
.doOnNext { feedResponse ->
localRepository.feedPet(food.key, pet.key ?: "", feedResponse.value ?: 0, userID)
}
override suspend fun feedPet(pet: Pet, food: Food): FeedResponse? {
val feedResponse = apiClient.feedPet(pet.key ?: "", food.key) ?: return null
localRepository.feedPet(food.key, pet.key ?: "", feedResponse.value ?: 0, userID)
return feedResponse
}
override fun hatchPet(egg: Egg, hatchingPotion: HatchingPotion, successFunction: () -> Unit): Flowable<Items> {
override suspend fun hatchPet(egg: Egg, hatchingPotion: HatchingPotion, successFunction: () -> Unit): Items? {
if (appConfigManager.enableLocalChanges()) {
localRepository.hatchPet(egg.key, hatchingPotion.key, userID)
successFunction()
}
return apiClient.hatchPet(egg.key, hatchingPotion.key)
.doOnNext {
localRepository.save(it, userID)
if (!appConfigManager.enableLocalChanges()) {
successFunction()
}
}
val items = apiClient.hatchPet(egg.key, hatchingPotion.key) ?: return null
localRepository.save(items, userID)
if (!appConfigManager.enableLocalChanges()) {
successFunction()
}
return items
}
override fun inviteToQuest(quest: QuestContent): Flowable<Quest> {
return apiClient.inviteToQuest("party", quest.key)
.doOnNext { localRepository.changeOwnedCount("quests", quest.key, userID, -1) }
override suspend fun inviteToQuest(quest: QuestContent): Quest? {
val newQuest = apiClient.inviteToQuest("party", quest.key)
localRepository.changeOwnedCount("quests", quest.key, userID, -1)
return newQuest
}
override suspend fun buyItem(user: User?, id: String, value: Double, purchaseQuantity: Int): BuyResponse? {
@ -270,15 +261,15 @@ class InventoryRepositoryImpl(
return buyResponse
}
override fun getAvailableLimitedItems(): Flowable<List<Item>> {
override fun getAvailableLimitedItems(): Flow<List<Item>> {
return localRepository.getAvailableLimitedItems()
}
override fun retrieveShopInventory(identifier: String): Flowable<Shop> {
override suspend fun retrieveShopInventory(identifier: String): Shop? {
return apiClient.retrieveShopIventory(identifier)
}
override fun retrieveMarketGear(): Flowable<Shop> {
override suspend fun retrieveMarketGear(): Shop? {
return apiClient.retrieveMarketGear()
}

View file

@ -4,7 +4,6 @@ import com.habitrpg.android.habitica.BuildConfig
import com.habitrpg.android.habitica.data.ApiClient
import com.habitrpg.android.habitica.data.SocialRepository
import com.habitrpg.android.habitica.data.local.SocialLocalRepository
import com.habitrpg.android.habitica.helpers.ExceptionHandler
import com.habitrpg.android.habitica.models.Achievement
import com.habitrpg.android.habitica.models.inventory.Quest
import com.habitrpg.android.habitica.models.members.Member
@ -15,7 +14,6 @@ import com.habitrpg.android.habitica.models.social.Group
import com.habitrpg.android.habitica.models.social.GroupMembership
import com.habitrpg.android.habitica.models.social.InboxConversation
import com.habitrpg.android.habitica.models.user.User
import io.reactivex.rxjava3.core.Flowable
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.first
@ -38,13 +36,13 @@ class SocialRepositoryImpl(
return retrievePartyMembers(groupID, true)
}
override fun blockMember(userID: String): Flowable<List<String>> {
override suspend fun blockMember(userID: String): List<String>? {
return apiClient.blockMember(userID)
}
override fun getGroupMembership(id: String) = localRepository.getGroupMembership(userID, id)
override fun getGroupMemberships(): Flowable<out List<GroupMembership>> {
override fun getGroupMemberships(): Flow<List<GroupMembership>> {
return localRepository.getGroupMemberships(userID)
}
@ -54,18 +52,18 @@ class SocialRepositoryImpl(
return messages
}
override fun getGroupChat(groupId: String): Flowable<out List<ChatMessage>> {
override fun getGroupChat(groupId: String): Flow<List<ChatMessage>> {
return localRepository.getGroupChat(groupId)
}
override fun markMessagesSeen(seenGroupId: String) {
apiClient.seenMessages(seenGroupId).subscribe({ }, ExceptionHandler.rx())
override suspend fun markMessagesSeen(seenGroupId: String) {
apiClient.seenMessages(seenGroupId)
}
override fun flagMessage(chatMessageID: String, additionalInfo: String, groupID: String?): Flowable<Void> {
override suspend fun flagMessage(chatMessageID: String, additionalInfo: String, groupID: String?): Void? {
return when {
chatMessageID.isBlank() -> Flowable.empty()
userID == BuildConfig.ANDROID_TESTING_UUID -> Flowable.empty()
chatMessageID.isBlank() -> return null
userID == BuildConfig.ANDROID_TESTING_UUID -> return null
else -> {
val data = mutableMapOf<String, String>()
data["comment"] = additionalInfo
@ -78,38 +76,36 @@ class SocialRepositoryImpl(
}
}
override fun likeMessage(chatMessage: ChatMessage): Flowable<ChatMessage> {
override suspend fun likeMessage(chatMessage: ChatMessage): ChatMessage? {
if (chatMessage.id.isBlank()) {
return Flowable.empty()
return null
}
val liked = chatMessage.userLikesMessage(userID)
if (chatMessage.isManaged) {
localRepository.likeMessage(chatMessage, userID, !liked)
}
return apiClient.likeMessage(chatMessage.groupId ?: "", chatMessage.id)
.map {
it.groupId = chatMessage.groupId
it
}
val message = apiClient.likeMessage(chatMessage.groupId ?: "", chatMessage.id)
message?.groupId = chatMessage.groupId
return null
}
override fun deleteMessage(chatMessage: ChatMessage): Flowable<Void> {
return if (chatMessage.isInboxMessage) {
override suspend fun deleteMessage(chatMessage: ChatMessage): Void? {
if (chatMessage.isInboxMessage) {
apiClient.deleteInboxMessage(chatMessage.id)
} else {
apiClient.deleteMessage(chatMessage.groupId ?: "", chatMessage.id)
}.doOnNext { localRepository.deleteMessage(chatMessage.id) }
}
localRepository.deleteMessage(chatMessage.id)
return null
}
override fun postGroupChat(groupId: String, messageObject: HashMap<String, String>): Flowable<PostChatMessageResult> {
return apiClient.postGroupChat(groupId, messageObject)
.map { postChatMessageResult ->
postChatMessageResult.message.groupId = groupId
postChatMessageResult
}
override suspend fun postGroupChat(groupId: String, messageObject: HashMap<String, String>): PostChatMessageResult? {
val result = apiClient.postGroupChat(groupId, messageObject)
result?.message?.groupId = groupId
return result
}
override fun postGroupChat(groupId: String, message: String): Flowable<PostChatMessageResult> {
override suspend fun postGroupChat(groupId: String, message: String): PostChatMessageResult? {
val messageObject = HashMap<String, String>()
messageObject["message"] = message
return postGroupChat(groupId, messageObject)
@ -189,17 +185,16 @@ class SocialRepositoryImpl(
return apiClient.updateGroup(copiedGroup.id, copiedGroup)
}
override fun retrieveGroups(type: String): Flowable<List<Group>> {
return apiClient.listGroups(type)
.doOnNext { groups ->
if ("guilds" == type) {
val memberships = groups.map {
GroupMembership(userID, it.id)
}
localRepository.saveGroupMemberships(userID, memberships)
}
localRepository.save(groups)
override suspend fun retrieveGroups(type: String): List<Group>? {
val groups = apiClient.listGroups(type) ?: return null
if ("guilds" == type) {
val memberships = groups.map {
GroupMembership(userID, it.id)
}
localRepository.saveGroupMemberships(userID, memberships)
}
localRepository.save(groups)
return groups
}
override fun getGroups(type: String) = localRepository.getGroups(type)
@ -219,14 +214,14 @@ class SocialRepositoryImpl(
return messages
}
override fun retrieveInboxConversations(): Flowable<List<InboxConversation>> {
return apiClient.retrieveInboxConversations().doOnNext { conversations ->
localRepository.saveInboxConversations(userID, conversations)
}
override suspend fun retrieveInboxConversations(): List<InboxConversation>? {
val conversations = apiClient.retrieveInboxConversations() ?: return null
localRepository.saveInboxConversations(userID, conversations)
return conversations
}
override suspend fun postPrivateMessage(recipientId: String, messageObject: HashMap<String, String>): List<ChatMessage>? {
val message = apiClient.postPrivateMessage(messageObject)
apiClient.postPrivateMessage(messageObject)
return retrieveInboxMessages(recipientId, 0)
}
@ -246,7 +241,7 @@ class SocialRepositoryImpl(
return members
}
override fun inviteToGroup(id: String, inviteData: Map<String, Any>): Flowable<List<Void>> = apiClient.inviteToGroup(id, inviteData)
override suspend fun inviteToGroup(id: String, inviteData: Map<String, Any>) = apiClient.inviteToGroup(id, inviteData)
override suspend fun retrieveMember(userId: String?): Member? {
return if (userId == null) {
@ -264,11 +259,11 @@ class SocialRepositoryImpl(
return retrieveMember(username)
}
override fun findUsernames(username: String, context: String?, id: String?): Flowable<List<FindUsernameResult>> {
override suspend fun findUsernames(username: String, context: String?, id: String?): List<FindUsernameResult>? {
return apiClient.findUsernames(username, context, id)
}
override fun markPrivateMessagesRead(user: User?): Flowable<Void> {
override suspend fun markPrivateMessagesRead(user: User?): Void? {
if (user?.isManaged == true) {
localRepository.modify(user) {
it.inbox?.hasUserSeenInbox = true
@ -298,57 +293,57 @@ class SocialRepositoryImpl(
override fun getUserGroups(type: String?) = localRepository.getUserGroups(userID, type)
override fun acceptQuest(user: User?, partyId: String): Flowable<Void> {
return apiClient.acceptQuest(partyId)
.doOnNext {
user?.let {
localRepository.updateRSVPNeeded(it, false)
}
override suspend fun acceptQuest(user: User?, partyId: String): Void? {
apiClient.acceptQuest(partyId)
user?.let {
localRepository.updateRSVPNeeded(it, false)
}
return null
}
override fun rejectQuest(user: User?, partyId: String): Flowable<Void> {
return apiClient.rejectQuest(partyId)
.doOnNext { _ ->
user?.let {
localRepository.updateRSVPNeeded(it, false)
}
override suspend fun rejectQuest(user: User?, partyId: String): Void? {
apiClient.rejectQuest(partyId)
user?.let {
localRepository.updateRSVPNeeded(it, false)
}
return null
}
override fun leaveQuest(partyId: String): Flowable<Void> {
override suspend fun leaveQuest(partyId: String): Void? {
return apiClient.leaveQuest(partyId)
}
override fun cancelQuest(partyId: String): Flowable<Void> {
return apiClient.cancelQuest(partyId)
.doOnNext { localRepository.removeQuest(partyId) }
override suspend fun cancelQuest(partyId: String): Void? {
apiClient.cancelQuest(partyId)
localRepository.removeQuest(partyId)
return null
}
override fun abortQuest(partyId: String): Flowable<Quest> {
return apiClient.abortQuest(partyId)
.doOnNext { localRepository.removeQuest(partyId) }
override suspend fun abortQuest(partyId: String): Quest? {
val quest = apiClient.abortQuest(partyId)
localRepository.removeQuest(partyId)
return quest
}
override fun rejectGroupInvite(groupId: String): Flowable<Void> {
return apiClient.rejectGroupInvite(groupId)
.doOnNext {
localRepository.rejectGroupInvitation(userID, groupId)
}
override suspend fun rejectGroupInvite(groupId: String): Void? {
apiClient.rejectGroupInvite(groupId)
localRepository.rejectGroupInvitation(userID, groupId)
return null
}
override fun forceStartQuest(party: Group): Flowable<Quest> {
return apiClient.forceStartQuest(party.id, localRepository.getUnmanagedCopy(party))
.doOnNext { localRepository.setQuestActivity(party, true) }
override suspend fun forceStartQuest(party: Group): Quest? {
val quest = apiClient.forceStartQuest(party.id, localRepository.getUnmanagedCopy(party))
localRepository.setQuestActivity(party, true)
return quest
}
override fun getMemberAchievements(userId: String?): Flowable<List<Achievement>> {
override suspend fun getMemberAchievements(userId: String?): List<Achievement>? {
return if (userId == null) {
Flowable.empty()
null
} else apiClient.getMemberAchievements(userId)
}
override fun transferGems(giftedID: String, amount: Int): Flowable<Void> {
override suspend fun transferGems(giftedID: String, amount: Int): Void? {
return apiClient.transferGems(giftedID, amount)
}
}

View file

@ -4,58 +4,53 @@ import com.habitrpg.android.habitica.data.ApiClient
import com.habitrpg.android.habitica.data.TagRepository
import com.habitrpg.android.habitica.data.local.TagLocalRepository
import com.habitrpg.android.habitica.models.Tag
import io.reactivex.rxjava3.core.Flowable
import io.reactivex.rxjava3.core.Single
import kotlinx.coroutines.flow.Flow
class TagRepositoryImpl(localRepository: TagLocalRepository, apiClient: ApiClient, userID: String) : BaseRepositoryImpl<TagLocalRepository>(localRepository, apiClient, userID), TagRepository {
override fun getTags(): Flowable<out List<Tag>> {
override fun getTags(): Flow<List<Tag>> {
return getTags(userID)
}
override fun getTags(userId: String): Flowable<out List<Tag>> {
override fun getTags(userId: String): Flow<List<Tag>> {
return localRepository.getTags(userId)
}
override fun createTag(tag: Tag): Flowable<Tag> {
return apiClient.createTag(tag)
.doOnNext {
it.userId = userID
localRepository.save(it)
}
override suspend fun createTag(tag: Tag): Tag? {
val savedTag = apiClient.createTag(tag) ?: return null
savedTag.userId = userID
localRepository.save(savedTag)
return savedTag
}
override fun updateTag(tag: Tag): Flowable<Tag> {
return apiClient.updateTag(tag.id, tag)
.doOnNext {
it.userId = userID
localRepository.save(it)
}
override suspend fun updateTag(tag: Tag): Tag? {
val savedTag = apiClient.updateTag(tag.id, tag) ?: return null
savedTag.userId = userID
localRepository.save(savedTag)
return savedTag
}
override fun deleteTag(id: String): Flowable<Void> {
return apiClient.deleteTag(id)
.doOnNext {
localRepository.deleteTag(id)
}
override suspend fun deleteTag(id: String): Void? {
apiClient.deleteTag(id)
localRepository.deleteTag(id)
return null
}
override fun createTags(tags: Collection<Tag>): Single<List<Tag>> {
return Flowable.defer { Flowable.fromIterable(tags) }
.filter { tag -> tag.name.isNotEmpty() }
.flatMap { this.createTag(it) }
.toList()
override suspend fun createTags(tags: Collection<Tag>): List<Tag> {
return tags.mapNotNull {
createTag(it)
}
}
override fun updateTags(tags: Collection<Tag>): Single<List<Tag>> {
return Flowable.defer { Flowable.fromIterable(tags) }
.flatMap { this.updateTag(it) }
.toList()
override suspend fun updateTags(tags: Collection<Tag>): List<Tag> {
return tags.mapNotNull {
updateTag(it)
}
}
override fun deleteTags(tagIds: Collection<String>): Single<List<Void>> {
return Flowable.defer { Flowable.fromIterable(tagIds) }
.flatMap { this.deleteTag(it) }
.toList()
override suspend fun deleteTags(tagIds: Collection<String>): List<Void> {
return tagIds.mapNotNull {
deleteTag(it)
}
}
}

View file

@ -5,7 +5,7 @@ import com.habitrpg.android.habitica.data.ApiClient
import com.habitrpg.android.habitica.data.TaskRepository
import com.habitrpg.android.habitica.data.local.TaskLocalRepository
import com.habitrpg.android.habitica.helpers.AppConfigManager
import com.habitrpg.android.habitica.helpers.ExceptionHandler
import com.habitrpg.android.habitica.helpers.launchCatching
import com.habitrpg.android.habitica.interactors.ScoreTaskLocallyInteractor
import com.habitrpg.android.habitica.models.BaseMainObject
import com.habitrpg.android.habitica.models.responses.BulkTaskScoringData
@ -20,9 +20,7 @@ import com.habitrpg.shared.habitica.models.responses.TaskDirectionData
import com.habitrpg.shared.habitica.models.responses.TaskScoringResult
import com.habitrpg.shared.habitica.models.tasks.TaskType
import com.habitrpg.shared.habitica.models.tasks.TasksOrder
import io.reactivex.rxjava3.core.Flowable
import io.reactivex.rxjava3.core.Maybe
import io.reactivex.rxjava3.core.Single
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.flow.map
@ -43,9 +41,6 @@ class TaskRepositoryImpl(
override fun getTasks(taskType: TaskType, userID: String?, includedGroupIDs: Array<String>): Flow<List<Task>> =
this.localRepository.getTasks(taskType, userID ?: this.userID, includedGroupIDs)
override fun getTasksFlowable(taskType: TaskType, userID: String?, includedGroupIDs: Array<String>): Flowable<out List<Task>> =
this.localRepository.getTasksFlowable(taskType, userID ?: this.userID, includedGroupIDs)
override fun saveTasks(userId: String, order: TasksOrder, tasks: TaskList) {
localRepository.saveTasks(userId, order, tasks)
}
@ -56,18 +51,18 @@ class TaskRepositoryImpl(
return tasks
}
override fun retrieveCompletedTodos(userId: String?): Flowable<TaskList> {
return this.apiClient.getTasks("completedTodos")
.doOnNext { taskList ->
val tasks = taskList.tasks
this.localRepository.saveCompletedTodos(userId ?: this.userID, tasks.values)
}
override suspend fun retrieveCompletedTodos(userId: String?): TaskList? {
val taskList = this.apiClient.getTasks("completedTodos") ?: return null
val tasks = taskList.tasks
this.localRepository.saveCompletedTodos(userId ?: this.userID, tasks.values)
return taskList
}
override fun retrieveTasks(userId: String, tasksOrder: TasksOrder, dueDate: Date): Flowable<TaskList> {
override suspend fun retrieveTasks(userId: String, tasksOrder: TasksOrder, dueDate: Date): TaskList? {
val formatter = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZZZZZ", Locale.US)
return this.apiClient.getTasks("dailys", formatter.format(dueDate))
.doOnNext { res -> this.localRepository.saveTasks(userId, tasksOrder, res) }
val taskList = this.apiClient.getTasks("dailys", formatter.format(dueDate)) ?: return null
this.localRepository.saveTasks(userId, tasksOrder, taskList)
return taskList
}
@Suppress("ReturnCount")
@ -120,7 +115,7 @@ class TaskRepositoryImpl(
return result
}
override fun bulkScoreTasks(data: List<Map<String, String>>): Flowable<BulkTaskScoringData> {
override suspend fun bulkScoreTasks(data: List<Map<String, String>>): BulkTaskScoringData? {
return apiClient.bulkScoreTasks(data)
}
@ -212,10 +207,10 @@ class TaskRepositoryImpl(
override fun getTaskCopy(taskId: String) = localRepository.getTaskCopy(taskId)
override fun createTask(task: Task, force: Boolean): Flowable<Task> {
override suspend fun createTask(task: Task, force: Boolean): Task? {
val now = Date().time
if (lastTaskAction > now - 500 && !force) {
return Flowable.empty()
return null
}
lastTaskAction = now
@ -228,61 +223,56 @@ class TaskRepositoryImpl(
}
localRepository.saveSyncronous(task)
return apiClient.createTask(task)
.map { task1 ->
task1.dateCreated = Date()
task1
}
.doOnNext {
it.tags = task.tags
localRepository.save(it)
}
.doOnError {
task.hasErrored = true
task.isSaving = false
localRepository.saveSyncronous(task)
}
val savedTask = apiClient.createTask(task)
savedTask?.dateCreated = Date()
if (savedTask != null) {
savedTask.tags = task.tags
localRepository.save(savedTask)
} else {
task.hasErrored = true
task.isSaving = false
localRepository.saveSyncronous(task)
}
return savedTask
}
@Suppress("ReturnCount")
override fun updateTask(task: Task, force: Boolean): Maybe<Task> {
override suspend fun updateTask(task: Task, force: Boolean): Task? {
val now = Date().time
if ((lastTaskAction > now - 500 && !force) || !task.isValid) {
return Maybe.just(task)
return task
}
lastTaskAction = now
val id = task.id ?: return Maybe.just(task)
val id = task.id ?: return task
val unmanagedTask = localRepository.getUnmanagedCopy(task)
unmanagedTask.isSaving = true
unmanagedTask.hasErrored = false
localRepository.saveSyncronous(unmanagedTask)
return apiClient.updateTask(id, unmanagedTask).singleElement()
.map { task1 ->
task1.position = task.position
task1.id = task.id
task1
}
.doOnSuccess {
it.tags = task.tags
localRepository.save(it)
}
.doOnError {
unmanagedTask.hasErrored = true
unmanagedTask.isSaving = false
localRepository.saveSyncronous(unmanagedTask)
}
val savedTask = apiClient.updateTask(id, unmanagedTask)
savedTask?.position = task.position
savedTask?.id = task.id
if (savedTask != null) {
savedTask.tags = task.tags
localRepository.save(savedTask)
} else {
unmanagedTask.hasErrored = true
unmanagedTask.isSaving = false
localRepository.saveSyncronous(unmanagedTask)
}
return savedTask
}
override fun deleteTask(taskId: String): Flowable<Void> {
return apiClient.deleteTask(taskId)
.doOnNext { localRepository.deleteTask(taskId) }
override suspend fun deleteTask(taskId: String): Void? {
apiClient.deleteTask(taskId) ?: return null
localRepository.deleteTask(taskId)
return null
}
override fun saveTask(task: Task) {
localRepository.save(task)
}
override fun createTasks(newTasks: List<Task>): Flowable<List<Task>> = apiClient.createTasks(newTasks)
override suspend fun createTasks(newTasks: List<Task>) = apiClient.createTasks(newTasks)
override fun markTaskCompleted(taskId: String, isCompleted: Boolean) {
localRepository.markTaskCompleted(taskId, isCompleted)
@ -296,19 +286,24 @@ class TaskRepositoryImpl(
localRepository.swapTaskPosition(firstPosition, secondPosition)
}
override fun updateTaskPosition(taskType: TaskType, taskID: String, newPosition: Int): Maybe<List<String>> {
return apiClient.postTaskNewPosition(taskID, newPosition).firstElement()
.doOnSuccess { localRepository.updateTaskPositions(it) }
override suspend fun updateTaskPosition(taskType: TaskType, taskID: String, newPosition: Int): List<String>? {
val positions = apiClient.postTaskNewPosition(taskID, newPosition) ?: return null
localRepository.updateTaskPositions(positions)
return positions
}
override fun getUnmanagedTask(taskid: String) = getTask(taskid).map { localRepository.getUnmanagedCopy(it) }
override fun updateTaskInBackground(task: Task) {
updateTask(task).subscribe({ }, ExceptionHandler.rx())
MainScope().launchCatching {
updateTask(task)
}
}
override fun createTaskInBackground(task: Task) {
createTask(task).subscribe({ }, ExceptionHandler.rx())
MainScope().launchCatching {
createTask(task)
}
}
override fun getTaskCopies(userId: String): Flow<List<Task>> =
@ -316,29 +311,27 @@ class TaskRepositoryImpl(
override fun getTaskCopies(tasks: List<Task>): List<Task> = localRepository.getUnmanagedCopy(tasks)
override fun retrieveDailiesFromDate(date: Date): Flowable<TaskList> {
override suspend fun retrieveDailiesFromDate(date: Date): TaskList? {
val formatter = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZZZZZ", Locale.US)
return apiClient.getTasks("dailys", formatter.format(date))
}
override fun syncErroredTasks(): Single<List<Task>> {
return localRepository.getErroredTasks(userID).firstElement()
.flatMapPublisher { Flowable.fromIterable(it) }
.map { localRepository.getUnmanagedCopy(it) }
.flatMap {
return@flatMap if (it.isCreating) {
createTask(it, true)
} else {
updateTask(it, true).toFlowable()
}
}.toList()
override suspend fun syncErroredTasks(): List<Task>? {
val tasks = localRepository.getErroredTasks(userID).firstOrNull()
return tasks?.map { localRepository.getUnmanagedCopy(it) }?.mapNotNull {
if (it.isCreating) {
createTask(it, true)
} else {
updateTask(it, true)
}
}
}
override fun unlinkAllTasks(challengeID: String?, keepOption: String): Flowable<Void> {
override suspend fun unlinkAllTasks(challengeID: String?, keepOption: String): Void? {
return apiClient.unlinkAllTasks(challengeID, keepOption)
}
override fun getTasksForChallenge(challengeID: String?): Flowable<out List<Task>> {
override fun getTasksForChallenge(challengeID: String?): Flow<List<Task>> {
return localRepository.getTasksForChallenge(challengeID, userID)
}
}

View file

@ -4,7 +4,7 @@ import com.habitrpg.android.habitica.data.ApiClient
import com.habitrpg.android.habitica.data.TutorialRepository
import com.habitrpg.android.habitica.data.local.TutorialLocalRepository
import com.habitrpg.android.habitica.models.TutorialStep
import io.reactivex.rxjava3.core.Flowable
import kotlinx.coroutines.flow.Flow
class TutorialRepositoryImpl(
localRepository: TutorialLocalRepository,
@ -12,9 +12,9 @@ class TutorialRepositoryImpl(
userID: String
) : BaseRepositoryImpl<TutorialLocalRepository>(localRepository, apiClient, userID), TutorialRepository {
override fun getTutorialStep(key: String): Flowable<TutorialStep> =
override fun getTutorialStep(key: String): Flow<TutorialStep> =
localRepository.getTutorialStep(key)
override fun getTutorialSteps(keys: List<String>): Flowable<out List<TutorialStep>> =
override fun getTutorialSteps(keys: List<String>): Flow<List<TutorialStep>> =
localRepository.getTutorialSteps(keys)
}

View file

@ -4,12 +4,9 @@ import com.habitrpg.android.habitica.data.ApiClient
import com.habitrpg.android.habitica.data.TaskRepository
import com.habitrpg.android.habitica.data.UserRepository
import com.habitrpg.android.habitica.data.local.UserLocalRepository
import com.habitrpg.android.habitica.extensions.filterMapEmpty
import com.habitrpg.android.habitica.helpers.AppConfigManager
import com.habitrpg.android.habitica.helpers.ExceptionHandler
import com.habitrpg.android.habitica.models.Achievement
import com.habitrpg.android.habitica.models.QuestAchievement
import com.habitrpg.android.habitica.models.Skill
import com.habitrpg.android.habitica.models.TeamPlan
import com.habitrpg.android.habitica.models.inventory.Customization
import com.habitrpg.android.habitica.models.responses.SkillResponse
@ -21,17 +18,10 @@ import com.habitrpg.android.habitica.models.user.Stats
import com.habitrpg.android.habitica.models.user.User
import com.habitrpg.android.habitica.models.user.UserQuestStatus
import com.habitrpg.android.habitica.proxy.AnalyticsManager
import com.habitrpg.common.habitica.extensions.Optional
import com.habitrpg.shared.habitica.models.responses.TaskDirection
import com.habitrpg.shared.habitica.models.responses.VerifyUsernameResponse
import com.habitrpg.shared.habitica.models.tasks.Attribute
import io.reactivex.rxjava3.core.Flowable
import io.reactivex.rxjava3.core.Maybe
import io.reactivex.rxjava3.functions.BiFunction
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.withContext
import java.util.Date
import java.util.GregorianCalendar
import java.util.concurrent.TimeUnit
@ -48,8 +38,6 @@ class UserRepositoryImpl(
private var lastSync: Date? = null
override fun getUser(): Flow<User?> = getUser(userID)
override fun getUserFlowable(): Flowable<User> = localRepository.getUserFlowable(userID)
override fun getUser(userID: String): Flow<User?> = localRepository.getUser(userID)
private suspend fun updateUser(userID: String, updateData: Map<String, Any>): User? {
@ -123,11 +111,9 @@ class UserRepositoryImpl(
return user
}
override fun getSkills(user: User): Flowable<out List<Skill>> =
localRepository.getSkills(user)
override fun getSkills(user: User) = localRepository.getSkills(user)
override fun getSpecialItems(user: User): Flowable<out List<Skill>> =
localRepository.getSpecialItems(user)
override fun getSpecialItems(user: User) = localRepository.getSpecialItems(user)
override suspend fun useSkill(key: String, target: String?, taskId: String): SkillResponse? {
val response = apiClient.useSkill(key, target ?: "", taskId) ?: return null
@ -177,8 +163,8 @@ class UserRepositoryImpl(
runCron(ArrayList())
}
override fun readNotification(id: String): Flowable<List<Any>> = apiClient.readNotification(id)
override fun getUserQuestStatus(): Flowable<UserQuestStatus> {
override suspend fun readNotification(id: String) = apiClient.readNotification(id)
override fun getUserQuestStatus(): Flow<UserQuestStatus> {
return localRepository.getUserQuestStatus(userID)
}
@ -186,13 +172,11 @@ class UserRepositoryImpl(
return apiClient.reroll()
}
override fun readNotifications(notificationIds: Map<String, List<String>>): Flowable<List<Any>> =
apiClient.readNotifications(notificationIds)
override suspend fun readNotifications(notificationIds: Map<String, List<String>>) = apiClient.readNotifications(notificationIds)
override fun seeNotifications(notificationIds: Map<String, List<String>>): Flowable<List<Any>> =
apiClient.seeNotifications(notificationIds)
override suspend fun seeNotifications(notificationIds: Map<String, List<String>>) = apiClient.seeNotifications(notificationIds)
override fun changeCustomDayStart(dayStartTime: Int): Flowable<User> {
override suspend fun changeCustomDayStart(dayStartTime: Int): User? {
val updateObject = HashMap<String, Any>()
updateObject["dayStart"] = dayStartTime
return apiClient.changeCustomDayStart(updateObject)
@ -200,7 +184,7 @@ class UserRepositoryImpl(
override suspend fun updateLanguage(languageCode: String): User? {
val user = updateUser("preferences.language", languageCode)
apiClient.setLanguageCode(languageCode)
apiClient.languageCode = languageCode
return user
}
@ -209,11 +193,9 @@ class UserRepositoryImpl(
return retrieveUser(withTasks = true, forced = true)
}
override fun deleteAccount(password: String): Flowable<Void> =
apiClient.deleteAccount(password)
override suspend fun deleteAccount(password: String) = apiClient.deleteAccount(password)
override fun sendPasswordResetEmail(email: String): Flowable<Void> =
apiClient.sendPasswordResetEmail(email)
override suspend fun sendPasswordResetEmail(email: String) = apiClient.sendPasswordResetEmail(email)
override suspend fun updateLoginName(newLoginName: String, password: String?): User? {
if (password != null && password.isNotEmpty()) {
@ -229,35 +211,32 @@ class UserRepositoryImpl(
return user
}
override fun verifyUsername(username: String): Flowable<VerifyUsernameResponse> = apiClient.verifyUsername(username.trim())
override suspend fun verifyUsername(username: String) = apiClient.verifyUsername(username.trim())
override fun updateEmail(newEmail: String, password: String): Flowable<Void> =
apiClient.updateEmail(newEmail.trim(), password)
override suspend fun updateEmail(newEmail: String, password: String) = apiClient.updateEmail(newEmail.trim(), password)
override fun updatePassword(
override suspend fun updatePassword(
oldPassword: String,
newPassword: String,
newPasswordConfirmation: String
): Flowable<Void> =
apiClient.updatePassword(oldPassword.trim(), newPassword.trim(), newPasswordConfirmation.trim())
) = apiClient.updatePassword(oldPassword.trim(), newPassword.trim(), newPasswordConfirmation.trim())
override fun allocatePoint(stat: Attribute): Flowable<Stats> {
getLiveUserFlowable().firstElement().subscribe(
{ liveUser ->
localRepository.executeTransaction {
when (stat) {
Attribute.STRENGTH -> liveUser.stats?.strength = liveUser.stats?.strength?.inc()
Attribute.INTELLIGENCE -> liveUser.stats?.intelligence = liveUser.stats?.intelligence?.inc()
Attribute.CONSTITUTION -> liveUser.stats?.constitution = liveUser.stats?.constitution?.inc()
Attribute.PERCEPTION -> liveUser.stats?.per = liveUser.stats?.per?.inc()
}
liveUser.stats?.points = liveUser.stats?.points?.dec()
override suspend fun allocatePoint(stat: Attribute): Stats? {
val liveUser = getLiveUser()
if (liveUser != null) {
localRepository.executeTransaction {
when (stat) {
Attribute.STRENGTH -> liveUser.stats?.strength = liveUser.stats?.strength?.inc()
Attribute.INTELLIGENCE -> liveUser.stats?.intelligence = liveUser.stats?.intelligence?.inc()
Attribute.CONSTITUTION -> liveUser.stats?.constitution = liveUser.stats?.constitution?.inc()
Attribute.PERCEPTION -> liveUser.stats?.per = liveUser.stats?.per?.inc()
}
},
ExceptionHandler.rx()
)
return zipWithLiveUser(apiClient.allocatePoint(stat.value)) { stats, user ->
localRepository.modify(user) { liveUser ->
liveUser.stats?.points = liveUser.stats?.points?.dec()
}
}
val stats = apiClient.allocatePoint(stat.value) ?: return null
if (liveUser != null) {
localRepository.executeTransaction {
liveUser.stats?.strength = stats.strength
liveUser.stats?.constitution = stats.constitution
liveUser.stats?.per = stats.per
@ -265,17 +244,24 @@ class UserRepositoryImpl(
liveUser.stats?.points = stats.points
liveUser.stats?.mp = stats.mp
}
stats
}
return stats
}
override fun bulkAllocatePoints(
override suspend fun bulkAllocatePoints(
strength: Int,
intelligence: Int,
constitution: Int,
perception: Int
): Flowable<Stats> =
zipWithLiveUser(apiClient.bulkAllocatePoints(strength, intelligence, constitution, perception)) { stats, user ->
): Stats? {
val stats = apiClient.bulkAllocatePoints(
strength,
intelligence,
constitution,
perception
) ?: return null
val user = getLiveUser()
if (user != null) {
localRepository.modify(user) { liveUser ->
liveUser.stats?.strength = stats.strength
liveUser.stats?.constitution = stats.constitution
@ -284,63 +270,54 @@ class UserRepositoryImpl(
liveUser.stats?.points = stats.points
liveUser.stats?.mp = stats.mp
}
stats
}
return stats
}
override suspend fun runCron(tasks: MutableList<Task>) {
withContext(Dispatchers.Main) {
var observable: Maybe<Any> = localRepository.getUserFlowable(userID).firstElement()
.filter { it.needsCron }
.map { user ->
localRepository.modify(user) { liveUser ->
liveUser.needsCron = false
liveUser.lastCron = Date()
}
user
}
if (tasks.isNotEmpty()) {
val scoringList = mutableListOf<Map<String, String>>()
for (task in tasks) {
val map = mutableMapOf<String, String>()
map["id"] = task.id ?: ""
map["direction"] = TaskDirection.UP.text
scoringList.add(map)
}
observable = observable.flatMap { taskRepository.bulkScoreTasks(scoringList).firstElement() }
val user = getLiveUser()
if (user != null) {
localRepository.modify(user) { liveUser ->
liveUser.needsCron = false
liveUser.lastCron = Date()
}
observable.flatMap { apiClient.runCron().firstElement() }
// .flatMap {
// this.retrieveUser(withTasks = true, forced = true)
// }
.subscribe({ }, ExceptionHandler.rx())
}
if (tasks.isNotEmpty()) {
val scoringList = mutableListOf<Map<String, String>>()
for (task in tasks) {
val map = mutableMapOf<String, String>()
map["id"] = task.id ?: ""
map["direction"] = TaskDirection.UP.text
scoringList.add(map)
}
taskRepository.bulkScoreTasks(scoringList)
}
apiClient.runCron()
}
override suspend fun useCustomization(type: String, category: String?, identifier: String): User? {
if (appConfigManager.enableLocalChanges()) {
localRepository.getUserFlowable(userID).firstElement().subscribe(
{ liveUser ->
localRepository.modify(liveUser) { user ->
when (type) {
"skin" -> user.preferences?.skin = identifier
"shirt" -> user.preferences?.shirt = identifier
"hair" -> {
when (category) {
"color" -> user.preferences?.hair?.color = identifier
"flower" -> user.preferences?.hair?.flower = identifier.toInt()
"mustache" -> user.preferences?.hair?.mustache = identifier.toInt()
"beard" -> user.preferences?.hair?.beard = identifier.toInt()
"bangs" -> user.preferences?.hair?.bangs = identifier.toInt()
"base" -> user.preferences?.hair?.base = identifier.toInt()
}
val liveUser = getLiveUser()
if (liveUser != null) {
localRepository.modify(liveUser) { user ->
when (type) {
"skin" -> user.preferences?.skin = identifier
"shirt" -> user.preferences?.shirt = identifier
"hair" -> {
when (category) {
"color" -> user.preferences?.hair?.color = identifier
"flower" -> user.preferences?.hair?.flower = identifier.toInt()
"mustache" -> user.preferences?.hair?.mustache = identifier.toInt()
"beard" -> user.preferences?.hair?.beard = identifier.toInt()
"bangs" -> user.preferences?.hair?.bangs = identifier.toInt()
"base" -> user.preferences?.hair?.base = identifier.toInt()
}
"background" -> user.preferences?.background = identifier
"chair" -> user.preferences?.chair = identifier
}
"background" -> user.preferences?.background = identifier
"chair" -> user.preferences?.chair = identifier
}
},
ExceptionHandler.rx()
)
}
}
}
var updatePath = "preferences.$type"
if (category != null) {
@ -349,10 +326,10 @@ class UserRepositoryImpl(
return updateUser(updatePath, identifier)
}
override fun retrieveAchievements(): Flowable<List<Achievement>> {
return apiClient.getMemberAchievements(userID).doOnNext {
localRepository.save(it)
}
override suspend fun retrieveAchievements(): List<Achievement>? {
val achievements = apiClient.getMemberAchievements(userID) ?: return null
localRepository.save(achievements)
return achievements
}
override fun getAchievements(): Flow<List<Achievement>> {
@ -363,11 +340,11 @@ class UserRepositoryImpl(
return localRepository.getQuestAchievements(userID)
}
override fun retrieveTeamPlans(): Flowable<List<TeamPlan>> {
return apiClient.getTeamPlans().doOnNext { teams ->
teams.forEach { it.userID = userID }
localRepository.save(teams)
}
override suspend fun retrieveTeamPlans(): List<TeamPlan>? {
val teams = apiClient.getTeamPlans() ?: return null
teams.forEach { it.userID = userID }
localRepository.save(teams)
return teams
}
override fun getTeamPlans(): Flow<List<TeamPlan>> {
@ -389,25 +366,15 @@ class UserRepositoryImpl(
return team
}
override fun getTeamPlan(teamID: String): Flowable<Group> {
override fun getTeamPlan(teamID: String): Flow<Group> {
return localRepository.getTeamPlan(teamID)
}
private fun getLiveUserFlowable(): Flowable<User> {
return localRepository.getUserFlowable(userID)
.map { Optional(localRepository.getLiveObject(it)) }
.filterMapEmpty()
}
private suspend fun getLiveUser(): User? {
val user = localRepository.getUser(userID).firstOrNull() ?: return null
return localRepository.getLiveObject(user)
}
private fun <T : Any> zipWithLiveUser(flowable: Flowable<T>, mergeFunc: BiFunction<T, User, T>): Flowable<T> {
return Flowable.zip(flowable, getLiveUserFlowable().firstElement().toFlowable(), mergeFunc)
}
private fun mergeUser(oldUser: User?, newUser: User): User {
if (oldUser == null || !oldUser.isValid) {
return oldUser ?: newUser

View file

@ -3,15 +3,15 @@ package com.habitrpg.android.habitica.data.local
import com.habitrpg.android.habitica.models.social.Challenge
import com.habitrpg.android.habitica.models.social.ChallengeMembership
import com.habitrpg.android.habitica.models.tasks.Task
import io.reactivex.rxjava3.core.Flowable
import kotlinx.coroutines.flow.Flow
interface ChallengeLocalRepository : BaseLocalRepository {
val challenges: Flowable<out List<Challenge>>
fun getChallenge(id: String): Flowable<Challenge>
fun getTasks(challengeID: String): Flowable<out List<Task>>
val challenges: Flow<List<Challenge>>
fun getChallenge(id: String): Flow<Challenge>
fun getTasks(challengeID: String): Flow<List<Task>>
fun getUserChallenges(userId: String): Flowable<out List<Challenge>>
fun getUserChallenges(userId: String): Flow<List<Challenge>>
fun setParticipating(userID: String, challengeID: String, isParticipating: Boolean)
@ -21,7 +21,7 @@ interface ChallengeLocalRepository : BaseLocalRepository {
memberOnly: Boolean,
userID: String
)
fun getChallengeMembership(userId: String, id: String): Flowable<ChallengeMembership>
fun getChallengeMemberships(userId: String): Flowable<out List<ChallengeMembership>>
fun isChallengeMember(userID: String, challengeID: String): Flowable<Boolean>
fun getChallengeMembership(userId: String, id: String): Flow<ChallengeMembership>
fun getChallengeMemberships(userId: String): Flow<List<ChallengeMembership>>
fun isChallengeMember(userID: String, challengeID: String): Flow<Boolean>
}

View file

@ -3,9 +3,10 @@ package com.habitrpg.android.habitica.data.local
import com.habitrpg.android.habitica.models.ContentResult
import com.habitrpg.android.habitica.models.WorldState
import io.reactivex.rxjava3.core.Flowable
import kotlinx.coroutines.flow.Flow
interface ContentLocalRepository : BaseLocalRepository {
fun saveContent(contentResult: ContentResult)
fun saveWorldState(worldState: WorldState)
fun getWorldState(): Flowable<WorldState>
fun getWorldState(): Flow<WorldState>
}

View file

@ -1,8 +1,8 @@
package com.habitrpg.android.habitica.data.local
import com.habitrpg.android.habitica.models.inventory.Customization
import io.reactivex.rxjava3.core.Flowable
import kotlinx.coroutines.flow.Flow
interface CustomizationLocalRepository : ContentLocalRepository {
fun getCustomizations(type: String, category: String?, onlyAvailable: Boolean): Flowable<out List<Customization>>
fun getCustomizations(type: String, category: String?, onlyAvailable: Boolean): Flow<List<Customization>>
}

View file

@ -1,10 +1,10 @@
package com.habitrpg.android.habitica.data.local
import com.habitrpg.android.habitica.models.FAQArticle
import io.reactivex.rxjava3.core.Flowable
import kotlinx.coroutines.flow.Flow
interface FAQLocalRepository : ContentLocalRepository {
fun getArticle(position: Int): Flowable<FAQArticle>
fun getArticle(position: Int): Flow<FAQArticle>
val articles: Flowable<out List<FAQArticle>>
val articles: Flow<List<FAQArticle>>
}

View file

@ -11,13 +11,12 @@ import com.habitrpg.android.habitica.models.user.OwnedItem
import com.habitrpg.android.habitica.models.user.OwnedMount
import com.habitrpg.android.habitica.models.user.OwnedPet
import com.habitrpg.android.habitica.models.user.User
import io.reactivex.rxjava3.core.Flowable
import kotlinx.coroutines.flow.Flow
interface InventoryLocalRepository : ContentLocalRepository {
fun getArmoireRemainingCount(): Long
fun getOwnedEquipment(): Flowable<out List<Equipment>>
fun getOwnedEquipment(): Flow<List<Equipment>>
fun getMounts(): Flow<List<Mount>>
@ -27,31 +26,29 @@ interface InventoryLocalRepository : ContentLocalRepository {
fun getOwnedPets(userID: String): Flow<List<OwnedPet>>
fun getInAppRewards(): Flowable<out List<ShopItem>>
fun getInAppRewards(): Flow<List<ShopItem>>
fun getQuestContent(key: String): Flow<QuestContent?>
fun getQuestContent(keys: List<String>): Flow<List<QuestContent>>
fun getEquipment(searchedKeys: List<String>): Flowable<out List<Equipment>>
fun getEquipment(searchedKeys: List<String>): Flow<List<Equipment>>
fun getOwnedEquipment(type: String): Flowable<out List<Equipment>>
fun getOwnedEquipment(type: String): Flow<List<Equipment>>
fun getItemsFlowable(itemClass: Class<out Item>, keys: Array<String>): Flow<List<Item>>
fun getItemsFlowable(itemClass: Class<out Item>): Flowable<out List<Item>>
fun getOwnedItems(itemType: String, userID: String, includeZero: Boolean): Flow<List<OwnedItem>>
fun getOwnedItems(userID: String, includeZero: Boolean): Flowable<Map<String, OwnedItem>>
fun getEquipmentType(type: String, set: String): Flowable<out List<Equipment>>
fun getOwnedItems(userID: String, includeZero: Boolean): Flow<Map<String, OwnedItem>>
fun getEquipmentType(type: String, set: String): Flow<List<Equipment>>
fun getEquipment(key: String): Flowable<Equipment>
fun getEquipment(key: String): Flow<Equipment>
fun getMounts(type: String?, group: String?, color: String?): Flow<List<Mount>>
fun getPets(type: String?, group: String?, color: String?): Flow<List<Pet>>
fun updateOwnedEquipment(user: User)
fun changeOwnedCount(type: String, key: String, userID: String, amountToAdd: Int)
suspend fun changeOwnedCount(type: String, key: String, userID: String, amountToAdd: Int)
fun changeOwnedCount(item: OwnedItem, amountToAdd: Int?)
fun getItem(type: String, key: String): Flowable<Item>
fun getOwnedItem(userID: String, type: String, key: String, includeZero: Boolean): Flowable<OwnedItem>
fun getItem(type: String, key: String): Flow<Item>
fun getOwnedItem(userID: String, type: String, key: String, includeZero: Boolean): Flow<OwnedItem>
fun decrementMysteryItemCount(user: User?)
fun saveInAppRewards(onlineItems: List<ShopItem>)
@ -59,12 +56,14 @@ interface InventoryLocalRepository : ContentLocalRepository {
fun hatchPet(eggKey: String, potionKey: String, userID: String)
fun unhatchPet(eggKey: String, potionKey: String, userID: String)
fun feedPet(foodKey: String, petKey: String, feedValue: Int, userID: String)
fun getLatestMysteryItem(): Flowable<Equipment>
fun getLatestMysteryItem(): Flow<Equipment>
fun soldItem(userID: String, updatedUser: User): User
fun getAvailableLimitedItems(): Flowable<List<Item>>
fun getAvailableLimitedItems(): Flow<List<Item>>
fun save(items: Items, userID: String)
fun getLiveObject(obj: OwnedItem): OwnedItem?
fun getItems(itemClass: Class<out Item>): Flow<List<Item>>
fun getItems(itemClass: Class<out Item>, keys: Array<String>): Flow<List<Item>>
}

View file

@ -6,20 +6,19 @@ import com.habitrpg.android.habitica.models.social.Group
import com.habitrpg.android.habitica.models.social.GroupMembership
import com.habitrpg.android.habitica.models.social.InboxConversation
import com.habitrpg.android.habitica.models.user.User
import io.reactivex.rxjava3.core.Flowable
import io.realm.RealmResults
import kotlinx.coroutines.flow.Flow
interface SocialLocalRepository : BaseLocalRepository {
fun getPublicGuilds(): Flowable<out List<Group>>
fun getPublicGuilds(): Flow<List<Group>>
fun getUserGroups(userID: String, type: String?): Flow<List<Group>>
fun getGroups(type: String): Flowable<out List<Group>>
fun getGroups(type: String): Flow<List<Group>>
fun getGroup(id: String): Flow<Group?>
fun saveGroup(group: Group)
fun getGroupChat(groupId: String): Flowable<out List<ChatMessage>>
fun getGroupChat(groupId: String): Flow<List<ChatMessage>>
fun deleteMessage(id: String)
@ -41,7 +40,7 @@ interface SocialLocalRepository : BaseLocalRepository {
fun doesGroupExist(id: String): Boolean
fun updateMembership(userId: String, id: String, isMember: Boolean)
fun getGroupMembership(userId: String, id: String): Flow<GroupMembership?>
fun getGroupMemberships(userId: String): Flowable<out List<GroupMembership>>
fun getGroupMemberships(userId: String): Flow<List<GroupMembership>>
fun rejectGroupInvitation(userID: String, groupID: String)
fun getInboxMessages(userId: String, replyToUserID: String?): Flow<RealmResults<ChatMessage>>

View file

@ -2,9 +2,10 @@ package com.habitrpg.android.habitica.data.local
import com.habitrpg.android.habitica.models.Tag
import io.reactivex.rxjava3.core.Flowable
import kotlinx.coroutines.flow.Flow
interface TagLocalRepository : BaseLocalRepository {
fun getTags(userId: String): Flowable<out List<Tag>>
fun getTags(userId: String): Flow<List<Tag>>
fun deleteTag(tagID: String)
}

View file

@ -5,14 +5,12 @@ import com.habitrpg.android.habitica.models.tasks.TaskList
import com.habitrpg.android.habitica.models.user.User
import com.habitrpg.shared.habitica.models.tasks.TaskType
import com.habitrpg.shared.habitica.models.tasks.TasksOrder
import io.reactivex.rxjava3.core.Flowable
import io.reactivex.rxjava3.core.Maybe
import kotlinx.coroutines.flow.Flow
interface TaskLocalRepository : BaseLocalRepository {
fun getTasks(taskType: TaskType, userID: String, includedGroupIDs: Array<String>): Flow<List<Task>>
fun getTasksFlowable(taskType: TaskType, userID: String, includedGroupIDs: Array<String>): Flowable<out List<Task>>
fun getTasks(userId: String): Flow<List<Task>>
fun saveTasks(ownerID: String, tasksOrder: TasksOrder, tasks: TaskList)
@ -26,14 +24,13 @@ interface TaskLocalRepository : BaseLocalRepository {
fun swapTaskPosition(firstPosition: Int, secondPosition: Int)
fun getTaskAtPosition(taskType: String, position: Int): Flowable<Task>
fun getTaskAtPosition(taskType: String, position: Int): Flow<Task>
fun updateIsdue(daily: TaskList): Maybe<TaskList>
fun updateTaskPositions(taskOrder: List<String>)
fun saveCompletedTodos(userId: String, tasks: MutableCollection<Task>)
fun getErroredTasks(userID: String): Flowable<out List<Task>>
fun getUserFlowable(userID: String): Flowable<User>
fun getErroredTasks(userID: String): Flow<List<Task>>
fun getUser(userID: String): Flow<User>
fun getTasksForChallenge(challengeID: String?, userID: String?): Flowable<out List<Task>>
fun getTasksForChallenge(challengeID: String?, userID: String?): Flow<List<Task>>
}

View file

@ -1,10 +1,10 @@
package com.habitrpg.android.habitica.data.local
import com.habitrpg.android.habitica.models.TutorialStep
import io.reactivex.rxjava3.core.Flowable
import kotlinx.coroutines.flow.Flow
interface TutorialLocalRepository : BaseLocalRepository {
fun getTutorialStep(key: String): Flowable<TutorialStep>
fun getTutorialSteps(keys: List<String>): Flowable<out List<TutorialStep>>
fun getTutorialStep(key: String): Flow<TutorialStep>
fun getTutorialSteps(keys: List<String>): Flow<List<TutorialStep>>
}

View file

@ -9,7 +9,6 @@ import com.habitrpg.android.habitica.models.social.ChatMessage
import com.habitrpg.android.habitica.models.social.Group
import com.habitrpg.android.habitica.models.user.User
import com.habitrpg.android.habitica.models.user.UserQuestStatus
import io.reactivex.rxjava3.core.Flowable
import io.realm.RealmResults
import kotlinx.coroutines.flow.Flow
@ -18,18 +17,16 @@ interface UserLocalRepository : BaseLocalRepository {
suspend fun getTutorialSteps(): Flow<RealmResults<TutorialStep>>
fun getUser(userID: String): Flow<User?>
fun getUserFlowable(userID: String): Flowable<User>
fun saveUser(user: User, overrideExisting: Boolean = true)
fun saveMessages(messages: List<ChatMessage>)
fun getSkills(user: User): Flowable<out List<Skill>>
fun getSkills(user: User): Flow<List<Skill>>
fun getSpecialItems(user: User): Flowable<out List<Skill>>
fun getSpecialItems(user: User): Flow<List<Skill>>
fun getAchievements(): Flow<List<Achievement>>
fun getQuestAchievements(userID: String): Flow<List<QuestAchievement>>
fun getUserQuestStatus(userID: String): Flowable<UserQuestStatus>
fun getUserQuestStatus(userID: String): Flow<UserQuestStatus>
fun getTeamPlans(userID: String): Flow<List<TeamPlan>>
fun getTeamPlan(teamID: String): Flowable<Group>
fun getTeamPlan(teamID: String): Flow<Group>
}

View file

@ -1,84 +1,79 @@
package com.habitrpg.android.habitica.data.local.implementation
import com.habitrpg.android.habitica.data.local.ChallengeLocalRepository
import com.habitrpg.android.habitica.extensions.filterMap
import com.habitrpg.android.habitica.models.social.Challenge
import com.habitrpg.android.habitica.models.social.ChallengeMembership
import com.habitrpg.android.habitica.models.tasks.Task
import com.habitrpg.android.habitica.models.user.User
import hu.akarnokd.rxjava3.bridge.RxJavaBridge
import io.reactivex.rxjava3.core.Flowable
import io.realm.Realm
import io.realm.Sort
import io.realm.kotlin.toFlow
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.map
class RealmChallengeLocalRepository(realm: Realm) : RealmBaseLocalRepository(realm), ChallengeLocalRepository {
override fun isChallengeMember(userID: String, challengeID: String): Flowable<Boolean> = RxJavaBridge.toV3Flowable(
realm.where(ChallengeMembership::class.java)
override fun isChallengeMember(userID: String, challengeID: String): Flow<Boolean> = realm.where(ChallengeMembership::class.java)
.equalTo("userID", userID)
.equalTo("challengeID", challengeID)
.findAll()
.asFlowable()
.toFlow()
.filter { it.isLoaded }
).map { it.count() > 0 }
.map { it.count() > 0 }
override fun getChallengeMembership(userId: String, id: String): Flowable<ChallengeMembership> = RxJavaBridge.toV3Flowable(
realm.where(ChallengeMembership::class.java)
override fun getChallengeMembership(userId: String, id: String) = realm.where(ChallengeMembership::class.java)
.equalTo("userID", userId)
.equalTo("challengeID", id)
.findAll()
.asFlowable()
.toFlow()
.filter { it.isLoaded }
).filterMap { it.first() }
.map { it.first() }
.filterNotNull()
override fun getChallengeMemberships(userId: String): Flowable<out List<ChallengeMembership>> = RxJavaBridge.toV3Flowable(
realm.where(ChallengeMembership::class.java)
override fun getChallengeMemberships(userId: String) = realm.where(ChallengeMembership::class.java)
.equalTo("userID", userId)
.findAll()
.asFlowable()
.toFlow()
.filter { it.isLoaded }
)
override fun getChallenge(id: String): Flowable<Challenge> {
return RxJavaBridge.toV3Flowable(
realm.where(Challenge::class.java)
override fun getChallenge(id: String): Flow<Challenge> {
return realm.where(Challenge::class.java)
.equalTo("id", id)
.findAll()
.asFlowable()
.toFlow()
.filter { realmObject -> realmObject.isLoaded && realmObject.isNotEmpty() }
.map { it.first() }
)
.filterNotNull()
}
override fun getTasks(challengeID: String): Flowable<out List<Task>> {
return RxJavaBridge.toV3Flowable(
realm.where(Task::class.java)
override fun getTasks(challengeID: String): Flow<List<Task>> {
return realm.where(Task::class.java)
.equalTo("userId", challengeID)
.findAll()
.asFlowable()
.toFlow()
.filter { realmObject -> realmObject.isLoaded }
)
}
override val challenges: Flowable<out List<Challenge>>
get() = RxJavaBridge.toV3Flowable(
realm.where(Challenge::class.java)
override val challenges: Flow<List<Challenge>>
get() = realm.where(Challenge::class.java)
.isNotNull("name")
.sort("official", Sort.DESCENDING, "createdAt", Sort.DESCENDING)
.findAll()
.asFlowable()
.toFlow()
.filter { it.isLoaded }
)
override fun getUserChallenges(userId: String): Flowable<out List<Challenge>> {
return RxJavaBridge.toV3Flowable(
realm.where(ChallengeMembership::class.java)
@OptIn(ExperimentalCoroutinesApi::class)
override fun getUserChallenges(userId: String): Flow<List<Challenge>> {
return realm.where(ChallengeMembership::class.java)
.equalTo("userID", userId)
.findAll()
.asFlowable()
.toFlow()
.filter { it.isLoaded }
)
.flatMap { it ->
.flatMapLatest { it ->
val ids = it.map {
return@map it.challengeID
}.toTypedArray()
@ -91,7 +86,7 @@ class RealmChallengeLocalRepository(realm: Realm) : RealmBaseLocalRepository(rea
.endGroup()
.sort("official", Sort.DESCENDING, "createdAt", Sort.DESCENDING)
.findAll()
.asFlowable()
.toFlow()
.filter { it.isLoaded }
}
}

View file

@ -5,9 +5,12 @@ import com.habitrpg.android.habitica.models.ContentResult
import com.habitrpg.android.habitica.models.WorldState
import com.habitrpg.android.habitica.models.inventory.Quest
import com.habitrpg.android.habitica.models.social.Group
import hu.akarnokd.rxjava3.bridge.RxJavaBridge
import io.reactivex.rxjava3.core.Flowable
import io.realm.Realm
import io.realm.kotlin.toFlow
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.map
open class RealmContentLocalRepository(realm: Realm) : RealmBaseLocalRepository(realm), ContentLocalRepository {
@ -34,14 +37,13 @@ open class RealmContentLocalRepository(realm: Realm) : RealmBaseLocalRepository(
}
}
override fun getWorldState(): Flowable<WorldState> {
return RxJavaBridge.toV3Flowable(
realm.where(WorldState::class.java)
override fun getWorldState(): Flow<WorldState> {
return realm.where(WorldState::class.java)
.findAll()
.asFlowable()
.toFlow()
.filter { it.isLoaded && it.size > 0 }
.map { it.first() }
)
.filterNotNull()
}
override fun saveWorldState(worldState: WorldState) {

View file

@ -2,14 +2,15 @@ package com.habitrpg.android.habitica.data.local.implementation
import com.habitrpg.android.habitica.data.local.CustomizationLocalRepository
import com.habitrpg.android.habitica.models.inventory.Customization
import hu.akarnokd.rxjava3.bridge.RxJavaBridge
import io.reactivex.rxjava3.core.Flowable
import io.realm.Realm
import io.realm.kotlin.toFlow
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.filter
import java.util.Date
class RealmCustomizationLocalRepository(realm: Realm) : RealmContentLocalRepository(realm), CustomizationLocalRepository {
override fun getCustomizations(type: String, category: String?, onlyAvailable: Boolean): Flowable<out List<Customization>> {
override fun getCustomizations(type: String, category: String?, onlyAvailable: Boolean): Flow<List<Customization>> {
var query = realm.where(Customization::class.java)
.equalTo("type", type)
.equalTo("category", category)
@ -28,13 +29,10 @@ class RealmCustomizationLocalRepository(realm: Realm) : RealmContentLocalReposit
.endGroup()
.endGroup()
}
return RxJavaBridge.toV3Flowable(
query
return query
.sort("customizationSet")
.findAll()
.asFlowable()
.toFlow()
.filter { it.isLoaded }
.map { it }
)
}
}

View file

@ -2,27 +2,27 @@ package com.habitrpg.android.habitica.data.local.implementation
import com.habitrpg.android.habitica.data.local.FAQLocalRepository
import com.habitrpg.android.habitica.models.FAQArticle
import hu.akarnokd.rxjava3.bridge.RxJavaBridge
import io.reactivex.rxjava3.core.Flowable
import io.realm.Realm
import io.realm.kotlin.toFlow
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.map
class RealmFAQLocalRepository(realm: Realm) : RealmContentLocalRepository(realm), FAQLocalRepository {
override fun getArticle(position: Int): Flowable<FAQArticle> {
return RxJavaBridge.toV3Flowable(
realm.where(FAQArticle::class.java)
override fun getArticle(position: Int): Flow<FAQArticle> {
return realm.where(FAQArticle::class.java)
.equalTo("position", position)
.findAll()
.asFlowable()
.toFlow()
.filter { it.isLoaded && it.count() > 0 }
.map { it.first() }
)
.map { it.firstOrNull() }
.filterNotNull()
}
override val articles: Flowable<out List<FAQArticle>>
get() = RxJavaBridge.toV3Flowable(
realm.where(FAQArticle::class.java)
override val articles: Flow<List<FAQArticle>>
get() = realm.where(FAQArticle::class.java)
.findAll()
.asFlowable()
.toFlow()
.filter { it.isLoaded }
)
}

View file

@ -1,7 +1,6 @@
package com.habitrpg.android.habitica.data.local.implementation
import com.habitrpg.android.habitica.data.local.InventoryLocalRepository
import com.habitrpg.android.habitica.helpers.ExceptionHandler
import com.habitrpg.android.habitica.models.inventory.Egg
import com.habitrpg.android.habitica.models.inventory.Equipment
import com.habitrpg.android.habitica.models.inventory.Food
@ -17,44 +16,44 @@ import com.habitrpg.android.habitica.models.user.OwnedItem
import com.habitrpg.android.habitica.models.user.OwnedMount
import com.habitrpg.android.habitica.models.user.OwnedPet
import com.habitrpg.android.habitica.models.user.User
import hu.akarnokd.rxjava3.bridge.RxJavaBridge
import io.reactivex.rxjava3.core.Flowable
import io.realm.Realm
import io.realm.RealmObject
import io.realm.Sort
import io.realm.kotlin.toFlow
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.flow.map
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
class RealmInventoryLocalRepository(realm: Realm) : RealmContentLocalRepository(realm), InventoryLocalRepository {
class RealmInventoryLocalRepository(realm: Realm) : RealmContentLocalRepository(realm),
InventoryLocalRepository {
override fun getQuestContent(keys: List<String>): Flow<List<QuestContent>> {
return realm.where(QuestContent::class.java)
.`in`("key", keys.toTypedArray())
.findAll()
.toFlow()
.filter { it.isLoaded }
.`in`("key", keys.toTypedArray())
.findAll()
.toFlow()
.filter { it.isLoaded }
}
override fun getQuestContent(key: String): Flow<QuestContent?> {
return realm.where(QuestContent::class.java).equalTo("key", key)
.findAll()
.toFlow()
.filter { content -> content.isLoaded && content.isValid && !content.isEmpty() }
.map { content -> content.first() }
.findAll()
.toFlow()
.filter { content -> content.isLoaded && content.isValid && !content.isEmpty() }
.map { content -> content.first() }
}
override fun getEquipment(searchedKeys: List<String>): Flowable<out List<Equipment>> {
return RxJavaBridge.toV3Flowable(
realm.where(Equipment::class.java)
.`in`("key", searchedKeys.toTypedArray())
.findAll()
.asFlowable()
.filter { it.isLoaded }
)
override fun getEquipment(searchedKeys: List<String>): Flow<out List<Equipment>> {
return realm.where(Equipment::class.java)
.`in`("key", searchedKeys.toTypedArray())
.findAll()
.toFlow()
.filter { it.isLoaded }
}
override fun getArmoireRemainingCount(): Long {
@ -68,39 +67,37 @@ class RealmInventoryLocalRepository(realm: Realm) : RealmContentLocalRepository(
.count()
}
override fun getOwnedEquipment(type: String): Flowable<out List<Equipment>> {
return RxJavaBridge.toV3Flowable(
realm.where(Equipment::class.java)
.equalTo("type", type)
.equalTo("owned", true)
.findAll()
.asFlowable()
.filter { it.isLoaded }
)
override fun getOwnedEquipment(type: String): Flow<out List<Equipment>> {
return realm.where(Equipment::class.java)
.equalTo("type", type)
.equalTo("owned", true)
.findAll()
.toFlow()
.filter { it.isLoaded }
}
override fun getOwnedEquipment(): Flowable<out List<Equipment>> {
return RxJavaBridge.toV3Flowable(
realm.where(Equipment::class.java)
.equalTo("owned", true)
.findAll()
.asFlowable()
.filter { it.isLoaded }
)
override fun getOwnedEquipment(): Flow<out List<Equipment>> {
return realm.where(Equipment::class.java)
.equalTo("owned", true)
.findAll()
.toFlow()
.filter { it.isLoaded }
}
override fun getEquipmentType(type: String, set: String): Flowable<out List<Equipment>> {
return RxJavaBridge.toV3Flowable(
realm.where(Equipment::class.java)
.equalTo("type", type)
.equalTo("gearSet", set)
.findAll()
.asFlowable()
.filter { it.isLoaded }
)
override fun getEquipmentType(type: String, set: String): Flow<out List<Equipment>> {
return realm.where(Equipment::class.java)
.equalTo("type", type)
.equalTo("gearSet", set)
.findAll()
.toFlow()
.filter { it.isLoaded }
}
override fun getOwnedItems(itemType: String, userID: String, includeZero: Boolean): Flow<List<OwnedItem>> {
override fun getOwnedItems(
itemType: String,
userID: String,
includeZero: Boolean
): Flow<List<OwnedItem>> {
return queryUser(userID).map {
val items = when (itemType) {
"eggs" -> it?.items?.eggs
@ -118,56 +115,49 @@ class RealmInventoryLocalRepository(realm: Realm) : RealmContentLocalRepository(
}
}
override fun getItemsFlowable(itemClass: Class<out Item>, keys: Array<String>): Flow<List<Item>> {
return realm.where(itemClass).`in`("key", keys).findAll().toFlow()
.filter { it.isLoaded }
}
override fun getItemsFlowable(itemClass: Class<out Item>): Flowable<out List<Item>> {
return RxJavaBridge.toV3Flowable(
realm.where(itemClass).findAll().asFlowable()
.filter { it.isLoaded }
)
}
override fun getItems(itemClass: Class<out Item>): Flow<List<Item>> {
return realm.where(itemClass).findAll().toFlow()
.filter { it.isLoaded }
}
override fun getOwnedItems(userID: String, includeZero: Boolean): Flowable<Map<String, OwnedItem>> {
return queryUserFlowable(userID).map {
val items = HashMap<String, OwnedItem>()
it.items?.eggs?.forEach { items[it.key + "-" + it.itemType] = it }
it.items?.food?.forEach { items[it.key + "-" + it.itemType] = it }
it.items?.hatchingPotions?.forEach { items[it.key + "-" + it.itemType] = it }
it.items?.quests?.forEach { items[it.key + "-" + it.itemType] = it }
if (includeZero) {
items
} else {
items.filter { it.value.numberOwned > 0 }
}
}
override fun getItems(itemClass: Class<out Item>, keys: Array<String>): Flow<List<Item>> {
return realm.where(itemClass).`in`("key", keys).findAll().toFlow()
.filter { it.isLoaded }
}
override fun getEquipment(key: String): Flowable<Equipment> {
return RxJavaBridge.toV3Flowable(
realm.where(Equipment::class.java)
.equalTo("key", key)
.findAll()
.asFlowable()
.filter { realmObject -> realmObject.isLoaded && realmObject.isNotEmpty() }
.map { it.first() }
.cast(Equipment::class.java)
)
override fun getOwnedItems(userID: String, includeZero: Boolean): Flow<Map<String, OwnedItem>> {
return queryUser(userID)
.filterNotNull()
.map {
val items = HashMap<String, OwnedItem>()
it.items?.eggs?.forEach { items[it.key + "-" + it.itemType] = it }
it.items?.food?.forEach { items[it.key + "-" + it.itemType] = it }
it.items?.hatchingPotions?.forEach { items[it.key + "-" + it.itemType] = it }
it.items?.quests?.forEach { items[it.key + "-" + it.itemType] = it }
if (includeZero) {
items
} else {
items.filter { it.value.numberOwned > 0 }
}
}
}
override fun getEquipment(key: String): Flow<Equipment> {
return realm.where(Equipment::class.java)
.equalTo("key", key)
.findAll()
.toFlow()
.filter { realmObject -> realmObject.isLoaded && realmObject.isNotEmpty() }
.map { it.first() }
.filterNotNull()
}
override fun getMounts(): Flow<List<Mount>> {
return realm.where(Mount::class.java)
.sort("type", Sort.ASCENDING, "animal", Sort.ASCENDING)
.findAll()
.toFlow()
.filter { it.isLoaded }
.sort("type", Sort.ASCENDING, "animal", Sort.ASCENDING)
.findAll()
.toFlow()
.filter { it.isLoaded }
}
override fun getMounts(type: String?, group: String?, color: String?): Flow<List<Mount>> {
@ -183,8 +173,8 @@ class RealmInventoryLocalRepository(realm: Realm) : RealmContentLocalRepository(
query = query.equalTo("color", color)
}
return query.findAll()
.toFlow()
.filter { it.isLoaded }
.toFlow()
.filter { it.isLoaded }
}
override fun getOwnedMounts(userID: String): Flow<List<OwnedMount>> {
@ -198,10 +188,10 @@ class RealmInventoryLocalRepository(realm: Realm) : RealmContentLocalRepository(
override fun getPets(): Flow<List<Pet>> {
return realm.where(Pet::class.java)
.sort("type", Sort.ASCENDING, "animal", Sort.ASCENDING)
.findAll()
.toFlow()
.filter { it.isLoaded }
.sort("type", Sort.ASCENDING, "animal", Sort.ASCENDING)
.findAll()
.toFlow()
.filter { it.isLoaded }
}
override fun getPets(type: String?, group: String?, color: String?): Flow<List<Pet>> {
@ -217,15 +207,15 @@ class RealmInventoryLocalRepository(realm: Realm) : RealmContentLocalRepository(
query = query.equalTo("color", color)
}
return query.findAll()
.toFlow()
.filter { it.isLoaded }
.toFlow()
.filter { it.isLoaded }
}
override fun getOwnedPets(userID: String): Flow<List<OwnedPet>> {
return realm.where(User::class.java)
.equalTo("id", userID)
.findAll()
.toFlow()
.equalTo("id", userID)
.findAll()
.toFlow()
.filter { it.isLoaded && it.isValid && !it.isEmpty() }
.map {
it.first()?.items?.pets?.filter {
@ -237,8 +227,16 @@ class RealmInventoryLocalRepository(realm: Realm) : RealmContentLocalRepository(
override fun updateOwnedEquipment(user: User) {
}
override fun changeOwnedCount(type: String, key: String, userID: String, amountToAdd: Int) {
getOwnedItem(userID, type, key, true).firstElement().subscribe({ changeOwnedCount(it, amountToAdd) }, ExceptionHandler.rx())
override suspend fun changeOwnedCount(
type: String,
key: String,
userID: String,
amountToAdd: Int
) {
val item = getOwnedItem(userID, type, key, true).firstOrNull()
if (item != null) {
changeOwnedCount(item, amountToAdd)
}
}
override fun changeOwnedCount(item: OwnedItem, amountToAdd: Int?) {
@ -248,29 +246,36 @@ class RealmInventoryLocalRepository(realm: Realm) : RealmContentLocalRepository(
}
}
override fun getOwnedItem(userID: String, type: String, key: String, includeZero: Boolean): Flowable<OwnedItem> {
return queryUserFlowable(userID).map {
var items = (
when (type) {
"eggs" -> it.items?.eggs
"hatchingPotions" -> it.items?.hatchingPotions
"food" -> it.items?.food
"quests" -> it.items?.quests
else -> emptyList()
} ?: emptyList()
)
items = items.filter { it.key == key }
if (includeZero) {
items
} else {
items.filter { it.numberOwned > 0 }
override fun getOwnedItem(
userID: String,
type: String,
key: String,
includeZero: Boolean
): Flow<OwnedItem> {
return queryUser(userID)
.filterNotNull()
.map {
var items = (
when (type) {
"eggs" -> it.items?.eggs
"hatchingPotions" -> it.items?.hatchingPotions
"food" -> it.items?.food
"quests" -> it.items?.quests
else -> emptyList()
} ?: emptyList()
)
items = items.filter { it.key == key }
if (includeZero) {
items
} else {
items.filter { it.numberOwned > 0 }
}
}
}
.filter { it.isNotEmpty() }
.map { it.first() }
}
override fun getItem(type: String, key: String): Flowable<Item> {
override fun getItem(type: String, key: String): Flow<Item> {
val itemClass: Class<out RealmObject> = when (type) {
"eggs" -> Egg::class.java
"hatchingPotions" -> HatchingPotion::class.java
@ -279,14 +284,12 @@ class RealmInventoryLocalRepository(realm: Realm) : RealmContentLocalRepository(
"special" -> SpecialItem::class.java
else -> Egg::class.java
}
return RxJavaBridge.toV3Flowable(
realm.where(itemClass).equalTo("key", key)
.findAll()
.asFlowable()
.filter { realmObject -> realmObject.isLoaded && realmObject.isNotEmpty() }
.map { it.first() }
.cast(Item::class.java)
)
return realm.where(itemClass).equalTo("key", key)
.findAll()
.toFlow()
.filter { realmObject -> realmObject.isLoaded && realmObject.isNotEmpty() }
.map { it.firstOrNull() as? Item }
.filterNotNull()
}
override fun decrementMysteryItemCount(user: User?) {
@ -301,18 +304,17 @@ class RealmInventoryLocalRepository(realm: Realm) : RealmContentLocalRepository(
item.numberOwned = item.numberOwned - 1
}
if (liveUser?.isValid == true) {
liveUser.purchased?.plan?.mysteryItemCount = (user.purchased?.plan?.mysteryItemCount ?: 0) - 1
liveUser.purchased?.plan?.mysteryItemCount =
(user.purchased?.plan?.mysteryItemCount ?: 0) - 1
}
}
}
override fun getInAppRewards(): Flowable<out List<ShopItem>> {
return RxJavaBridge.toV3Flowable(
realm.where(ShopItem::class.java)
.findAll()
.asFlowable()
.filter { it.isLoaded }
)
override fun getInAppRewards(): Flow<List<ShopItem>> {
return realm.where(ShopItem::class.java)
.findAll()
.toFlow()
.filter { it.isLoaded }
}
override fun saveInAppRewards(onlineItems: List<ShopItem>) {
@ -333,7 +335,8 @@ class RealmInventoryLocalRepository(realm: Realm) : RealmContentLocalRepository(
newPet.trained = 5
val user = realm.where(User::class.java).equalTo("id", userID).findFirst() ?: return
val egg = user.items?.eggs?.firstOrNull { it.key == eggKey } ?: return
val hatchingPotion = user.items?.hatchingPotions?.firstOrNull { it.key == potionKey } ?: return
val hatchingPotion =
user.items?.hatchingPotions?.firstOrNull { it.key == potionKey } ?: return
executeTransaction {
egg.numberOwned -= 1
hatchingPotion.numberOwned -= 1
@ -344,7 +347,8 @@ class RealmInventoryLocalRepository(realm: Realm) : RealmContentLocalRepository(
override fun getLiveObject(obj: OwnedItem): OwnedItem? {
if (isClosed) return null
if (!obj.isManaged) return obj
return realm.where(OwnedItem::class.java).equalTo("key", obj.key).equalTo("itemType", obj.itemType).findFirst()
return realm.where(OwnedItem::class.java).equalTo("key", obj.key)
.equalTo("itemType", obj.itemType).findFirst()
}
override fun save(items: Items, userID: String) {
@ -359,7 +363,8 @@ class RealmInventoryLocalRepository(realm: Realm) : RealmContentLocalRepository(
val pet = realm.where(OwnedPet::class.java).equalTo("key", "$eggKey-$potionKey").findFirst()
val user = realm.where(User::class.java).equalTo("id", userID).findFirst() ?: return
val egg = user.items?.eggs?.firstOrNull { it.key == eggKey } ?: return
val hatchingPotion = user.items?.hatchingPotions?.firstOrNull { it.key == potionKey } ?: return
val hatchingPotion =
user.items?.hatchingPotions?.firstOrNull { it.key == potionKey } ?: return
executeTransaction {
egg.numberOwned += 1
hatchingPotion.numberOwned += 1
@ -384,21 +389,19 @@ class RealmInventoryLocalRepository(realm: Realm) : RealmContentLocalRepository(
}
}
override fun getLatestMysteryItem(): Flowable<Equipment> {
return RxJavaBridge.toV3Flowable(
realm.where(Equipment::class.java)
.contains("key", "mystery_2")
.sort("mystery", Sort.DESCENDING)
.findAll()
.asFlowable()
.filter { it.isLoaded && it.size > 0 }
.map {
val format = SimpleDateFormat("yyyyMM", Locale.US)
it.first {
it.key?.contains(format.format(Date())) == true
}
override fun getLatestMysteryItem(): Flow<Equipment> {
return realm.where(Equipment::class.java)
.contains("key", "mystery_2")
.sort("mystery", Sort.DESCENDING)
.findAll()
.toFlow()
.filter { it.isLoaded && it.size > 0 }
.map {
val format = SimpleDateFormat("yyyyMM", Locale.US)
it.first {
it.key?.contains(format.format(Date())) == true
}
)
}
}
override fun soldItem(userID: String, updatedUser: User): User {
@ -418,32 +421,42 @@ class RealmInventoryLocalRepository(realm: Realm) : RealmContentLocalRepository(
return user
}
override fun getAvailableLimitedItems(): Flowable<List<Item>> {
return Flowable.combineLatest(
realm.where(Egg::class.java)
.lessThan("event.start", Date())
.greaterThan("event.end", Date())
.findAll().asFlowable(),
realm.where(Food::class.java)
.lessThan("event.start", Date())
.greaterThan("event.end", Date())
.findAll().asFlowable(),
realm.where(HatchingPotion::class.java)
.lessThan("event.start", Date())
.greaterThan("event.end", Date())
.findAll().asFlowable(),
realm.where(QuestContent::class.java)
.lessThan("event.start", Date())
.greaterThan("event.end", Date())
.findAll().asFlowable(),
{ eggs, food, potions, quests ->
override fun getAvailableLimitedItems(): Flow<List<Item>> {
return realm.where(Egg::class.java)
.lessThan("event.start", Date())
.greaterThan("event.end", Date())
.findAll().toFlow()
.map {
val items = mutableListOf<Item>()
items.addAll(eggs)
items.addAll(food)
items.addAll(potions)
items.addAll(quests)
items.addAll(it)
items
}
.combine(
realm.where(Food::class.java)
.lessThan("event.start", Date())
.greaterThan("event.end", Date())
.findAll().toFlow()
) { items, food ->
items.addAll(food)
items
}
.combine(
realm.where(HatchingPotion::class.java)
.lessThan("event.start", Date())
.greaterThan("event.end", Date())
.findAll().toFlow()
) { items, food ->
items.addAll(food)
items
}
.combine(
realm.where(QuestContent::class.java)
.lessThan("event.start", Date())
.greaterThan("event.end", Date())
.findAll().toFlow()
) { items, food ->
items.addAll(food)
items
}
)
}
}

View file

@ -9,8 +9,6 @@ import com.habitrpg.android.habitica.models.social.Group
import com.habitrpg.android.habitica.models.social.GroupMembership
import com.habitrpg.android.habitica.models.social.InboxConversation
import com.habitrpg.android.habitica.models.user.User
import hu.akarnokd.rxjava3.bridge.RxJavaBridge
import io.reactivex.rxjava3.core.Flowable
import io.realm.Realm
import io.realm.Sort
import io.realm.kotlin.toFlow
@ -30,13 +28,11 @@ class RealmSocialLocalRepository(realm: Realm) : RealmBaseLocalRepository(realm)
.filter { it.isLoaded && it.isNotEmpty() }
.map { it.first() }
override fun getGroupMemberships(userId: String): Flowable<out List<GroupMembership>> = RxJavaBridge.toV3Flowable(
realm.where(GroupMembership::class.java)
override fun getGroupMemberships(userId: String): Flow<List<GroupMembership>> = realm.where(GroupMembership::class.java)
.equalTo("userID", userId)
.findAll()
.asFlowable()
.toFlow()
.filter { it.isLoaded }
)
override fun updateMembership(userId: String, id: String, isMember: Boolean) {
if (isMember) {
@ -123,16 +119,14 @@ class RealmSocialLocalRepository(realm: Realm) : RealmBaseLocalRepository(realm)
}
}
override fun getPublicGuilds(): Flowable<out List<Group>> = RxJavaBridge.toV3Flowable(
realm.where(Group::class.java)
override fun getPublicGuilds() = realm.where(Group::class.java)
.equalTo("type", "guild")
.equalTo("privacy", "public")
.notEqualTo("id", Group.TAVERN_ID)
.sort("memberCount", Sort.DESCENDING)
.findAll()
.asFlowable()
.toFlow()
.filter { it.isLoaded }
)
@OptIn(ExperimentalCoroutinesApi::class)
override fun getUserGroups(userID: String, type: String?) = realm.where(GroupMembership::class.java)
@ -155,14 +149,12 @@ class RealmSocialLocalRepository(realm: Realm) : RealmBaseLocalRepository(realm)
.toFlow()
}
override fun getGroups(type: String): Flowable<out List<Group>> {
return RxJavaBridge.toV3Flowable(
realm.where(Group::class.java)
override fun getGroups(type: String): Flow<List<Group>> {
return realm.where(Group::class.java)
.equalTo("type", type)
.findAll()
.asFlowable()
.toFlow()
.filter { it.isLoaded }
)
}
override fun getGroup(id: String): Flow<Group?> {
@ -174,15 +166,13 @@ class RealmSocialLocalRepository(realm: Realm) : RealmBaseLocalRepository(realm)
.map { groups -> groups.first() }
}
override fun getGroupChat(groupId: String): Flowable<out List<ChatMessage>> {
return RxJavaBridge.toV3Flowable(
realm.where(ChatMessage::class.java)
override fun getGroupChat(groupId: String): Flow<List<ChatMessage>> {
return realm.where(ChatMessage::class.java)
.equalTo("groupId", groupId)
.sort("timestamp", Sort.DESCENDING)
.findAll()
.asFlowable()
.toFlow()
.filter { it.isLoaded }
)
}
override fun deleteMessage(id: String) {

View file

@ -2,9 +2,9 @@ package com.habitrpg.android.habitica.data.local.implementation
import com.habitrpg.android.habitica.data.local.TagLocalRepository
import com.habitrpg.android.habitica.models.Tag
import hu.akarnokd.rxjava3.bridge.RxJavaBridge
import io.reactivex.rxjava3.core.Flowable
import io.realm.Realm
import io.realm.kotlin.toFlow
import kotlinx.coroutines.flow.Flow
class RealmTagLocalRepository(realm: Realm) : RealmBaseLocalRepository(realm), TagLocalRepository {
override fun deleteTag(tagID: String) {
@ -12,9 +12,7 @@ class RealmTagLocalRepository(realm: Realm) : RealmBaseLocalRepository(realm), T
executeTransaction { tags.deleteAllFromRealm() }
}
override fun getTags(userId: String): Flowable<out List<Tag>> {
return RxJavaBridge.toV3Flowable(
realm.where(Tag::class.java).equalTo("userId", userId).findAll().asFlowable()
)
override fun getTags(userId: String): Flow<List<Tag>> {
return realm.where(Tag::class.java).equalTo("userId", userId).findAll().toFlow()
}
}

View file

@ -8,7 +8,6 @@ import com.habitrpg.android.habitica.models.tasks.TaskList
import com.habitrpg.android.habitica.models.user.User
import com.habitrpg.shared.habitica.models.tasks.TaskType
import com.habitrpg.shared.habitica.models.tasks.TasksOrder
import hu.akarnokd.rxjava3.bridge.RxJavaBridge
import io.reactivex.rxjava3.core.Flowable
import io.reactivex.rxjava3.core.Maybe
import io.realm.Realm
@ -30,13 +29,6 @@ class RealmTaskLocalRepository(realm: Realm) : RealmBaseLocalRepository(realm),
.filter { it.isLoaded }
}
override fun getTasksFlowable(taskType: TaskType, userID: String, includedGroupIDs: Array<String>): Flowable<out List<Task>> {
if (realm.isClosed) return Flowable.empty()
return RxJavaBridge.toV3Flowable(findTasks(taskType, userID, includedGroupIDs)
.asFlowable()
.filter { it.isLoaded })
}
private fun findTasks(
taskType: TaskType,
ownerID: String,
@ -226,16 +218,13 @@ class RealmTaskLocalRepository(realm: Realm) : RealmBaseLocalRepository(realm),
}
}
override fun getTaskAtPosition(taskType: String, position: Int): Flowable<Task> {
return RxJavaBridge.toV3Flowable(
realm.where(Task::class.java).equalTo("typeValue", taskType).equalTo("position", position)
override fun getTaskAtPosition(taskType: String, position: Int): Flow<Task> {
return realm.where(Task::class.java).equalTo("typeValue", taskType).equalTo("position", position)
.findAll()
.asFlowable()
.toFlow()
.filter { realmObject -> realmObject.isLoaded && realmObject.isNotEmpty() }
.map { it.first() }
.filter { realmObject -> realmObject.isLoaded }
.cast(Task::class.java)
)
.filterNotNull()
}
override fun updateIsdue(daily: TaskList): Maybe<TaskList> {
@ -258,27 +247,14 @@ class RealmTaskLocalRepository(realm: Realm) : RealmBaseLocalRepository(realm),
}
}
override fun getErroredTasks(userID: String): Flowable<out List<Task>> {
return RxJavaBridge.toV3Flowable(
realm.where(Task::class.java)
override fun getErroredTasks(userID: String): Flow<List<Task>> {
return realm.where(Task::class.java)
.equalTo("userId", userID)
.equalTo("hasErrored", true)
.sort("position")
.findAll()
.asFlowable()
.toFlow()
.filter { it.isLoaded }
).retry(1)
}
override fun getUserFlowable(userID: String): Flowable<User> {
return RxJavaBridge.toV3Flowable(
realm.where(User::class.java)
.equalTo("id", userID)
.findAll()
.asFlowable()
.filter { realmObject -> realmObject.isLoaded && realmObject.isValid && !realmObject.isEmpty() }
.map { users -> users.first() }
)
}
override fun getUser(userID: String): Flow<User> {
@ -291,15 +267,12 @@ class RealmTaskLocalRepository(realm: Realm) : RealmBaseLocalRepository(realm),
.filterNotNull()
}
override fun getTasksForChallenge(challengeID: String?, userID: String?): Flowable<out List<Task>> {
return RxJavaBridge.toV3Flowable(
realm.where(Task::class.java)
override fun getTasksForChallenge(challengeID: String?, userID: String?): Flow<List<Task>> {
return realm.where(Task::class.java)
.equalTo("challengeID", challengeID)
.equalTo("userId", userID)
.findAll()
.asFlowable()
.toFlow()
.filter { it.isLoaded }
)
.retry(1)
}
}

View file

@ -2,31 +2,32 @@ package com.habitrpg.android.habitica.data.local.implementation
import com.habitrpg.android.habitica.data.local.TutorialLocalRepository
import com.habitrpg.android.habitica.models.TutorialStep
import hu.akarnokd.rxjava3.bridge.RxJavaBridge
import io.reactivex.rxjava3.core.Flowable
import io.realm.Realm
import io.realm.kotlin.toFlow
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.map
class RealmTutorialLocalRepository(realm: Realm) : RealmBaseLocalRepository(realm), TutorialLocalRepository {
override fun getTutorialStep(key: String): Flowable<TutorialStep> {
if (realm.isClosed) return Flowable.empty()
return RxJavaBridge.toV3Flowable(
realm.where(TutorialStep::class.java).equalTo("identifier", key)
override fun getTutorialStep(key: String): Flow<TutorialStep> {
if (realm.isClosed) return emptyFlow()
return realm.where(TutorialStep::class.java).equalTo("identifier", key)
.findAll()
.asFlowable()
.toFlow()
.filter { realmObject -> realmObject.isLoaded && realmObject.isValid && realmObject.isNotEmpty() }
.map { steps -> steps.first() }
)
.filterNotNull()
}
override fun getTutorialSteps(keys: List<String>): Flowable<out List<TutorialStep>> {
if (realm.isClosed) return Flowable.empty()
return RxJavaBridge.toV3Flowable(
realm.where(TutorialStep::class.java)
override fun getTutorialSteps(keys: List<String>): Flow<out List<TutorialStep>> {
if (realm.isClosed) return emptyFlow()
return realm.where(TutorialStep::class.java)
.`in`("identifier", keys.toTypedArray())
.findAll()
.asFlowable()
.toFlow()
.filter { it.isLoaded }
)
}
}

View file

@ -13,36 +13,40 @@ import com.habitrpg.android.habitica.models.social.Group
import com.habitrpg.android.habitica.models.user.User
import com.habitrpg.android.habitica.models.user.UserQuestStatus
import hu.akarnokd.rxjava3.bridge.RxJavaBridge
import io.reactivex.rxjava3.core.Flowable
import io.realm.Realm
import io.realm.kotlin.toFlow
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.map
class RealmUserLocalRepository(realm: Realm) : RealmBaseLocalRepository(realm),
UserLocalRepository {
override fun getUserQuestStatus(userID: String): Flowable<UserQuestStatus> {
return getUserFlowable(userID)
@OptIn(ExperimentalCoroutinesApi::class)
override fun getUserQuestStatus(userID: String): Flow<UserQuestStatus> {
return getUser(userID)
.filterNotNull()
.map { it.party?.id ?: "" }
.distinctUntilChanged()
.filter { it.isNotBlank() }
.flatMap {
RxJavaBridge.toV3Flowable(
.flatMapLatest {
realm.where(Group::class.java)
.equalTo("id", it)
.findAll()
.asFlowable()
.toFlow()
.filter { groups -> groups.size > 0 }
).filterMap { it.first() }
.map { it.firstOrNull() }
.filterNotNull()
}
.map {
when {
it.quest?.members?.find { questMember -> questMember.key == userID } === null -> UserQuestStatus.NO_QUEST
it.quest?.progress?.collect?.isNotEmpty()
?: false -> UserQuestStatus.QUEST_COLLECT
it.quest?.progress?.hp ?: 0.0 > 0.0 -> UserQuestStatus.QUEST_BOSS
(it.quest?.progress?.hp ?: 0.0) > 0.0 -> UserQuestStatus.QUEST_BOSS
else -> UserQuestStatus.QUEST_UNKNOWN
}
}
@ -79,17 +83,6 @@ class RealmUserLocalRepository(realm: Realm) : RealmBaseLocalRepository(realm),
.map { users -> users.first() }
}
override fun getUserFlowable(userID: String): Flowable<User> {
if (realm.isClosed) return Flowable.empty()
return RxJavaBridge.toV3Flowable(
realm.where(User::class.java)
.equalTo("id", userID)
.findAll()
.asFlowable()
.filter { realmObject -> realmObject.isLoaded && realmObject.isValid && !realmObject.isEmpty() }
.map { users -> users.first() })
}
override fun saveUser(user: User, overrideExisting: Boolean) {
if (realm.isClosed) return
val oldUser = realm.where(User::class.java)
@ -134,45 +127,40 @@ class RealmUserLocalRepository(realm: Realm) : RealmBaseLocalRepository(realm),
.filter { it.isLoaded }
}
override fun getTeamPlan(teamID: String): Flowable<Group> {
if (realm.isClosed) return Flowable.empty()
return RxJavaBridge.toV3Flowable(
realm.where(Group::class.java)
override fun getTeamPlan(teamID: String): Flow<Group> {
if (realm.isClosed) return emptyFlow()
return realm.where(Group::class.java)
.equalTo("id", teamID)
.findAll()
.asFlowable()
.toFlow()
.filter { realmObject -> realmObject.isLoaded && realmObject.isValid && !realmObject.isEmpty() }
.map { teams -> teams.first() }
)
.map { teams -> teams.firstOrNull() }
.filterNotNull()
}
override fun getSkills(user: User): Flowable<out List<Skill>> {
override fun getSkills(user: User): Flow<List<Skill>> {
val habitClass =
if (user.preferences?.disableClasses == true) "none" else user.stats?.habitClass
return RxJavaBridge.toV3Flowable(
realm.where(Skill::class.java)
return realm.where(Skill::class.java)
.equalTo("habitClass", habitClass)
.sort("lvl")
.findAll()
.asFlowable()
.toFlow()
.filter { it.isLoaded }
)
}
override fun getSpecialItems(user: User): Flowable<out List<Skill>> {
override fun getSpecialItems(user: User): Flow<List<Skill>> {
val specialItems = user.items?.special
val ownedItems = ArrayList<String>()
for (key in listOf("snowball", "shinySeed", "seafoam", "spookySparkles")) {
if (specialItems?.firstOrNull() { it.key == key }?.numberOwned ?: 0 > 0) {
if ((specialItems?.firstOrNull { it.key == key }?.numberOwned ?: 0) > 0) {
ownedItems.add(key)
}
}
return RxJavaBridge.toV3Flowable(
realm.where(Skill::class.java)
return realm.where(Skill::class.java)
.`in`("key", ownedItems.toTypedArray())
.findAll()
.asFlowable()
.toFlow()
.filter { it.isLoaded }
)
}
}

View file

@ -12,6 +12,7 @@ import com.habitrpg.android.habitica.models.promotions.HabiticaPromotion
import com.habitrpg.android.habitica.models.promotions.HabiticaWebPromotion
import com.habitrpg.android.habitica.models.promotions.getHabiticaPromotionFromKey
import com.habitrpg.common.habitica.helpers.AppTestingLevel
import kotlinx.coroutines.MainScope
class AppConfigManager(contentRepository: ContentRepository?): com.habitrpg.common.habitica.helpers.AppConfigManager() {
@ -19,12 +20,11 @@ class AppConfigManager(contentRepository: ContentRepository?): com.habitrpg.comm
init {
try {
contentRepository?.getWorldState()?.subscribe(
{
worldState = it
},
ExceptionHandler.rx()
)
MainScope().launchCatching {
contentRepository?.getWorldState()?.collect {
worldState = it
}
}
} catch (_: java.lang.IllegalStateException) {
// pass
}

View file

@ -56,6 +56,8 @@ class ExceptionHandler {
}
}
fun CoroutineScope.launchCatching(function: suspend CoroutineScope.() -> Unit) {
launch((ExceptionHandler.coroutine()), block = function)
fun CoroutineScope.launchCatching(errorHandler: ((Throwable) -> Unit)? = null, function: suspend CoroutineScope.() -> Unit) {
launch((ExceptionHandler.coroutine {
errorHandler?.invoke(it)
}), block = function)
}

View file

@ -3,56 +3,50 @@ package com.habitrpg.android.habitica.helpers
import android.content.Context
import androidx.core.app.NotificationManagerCompat
import com.habitrpg.android.habitica.data.ApiClient
import com.habitrpg.common.habitica.models.Notification
import com.habitrpg.android.habitica.models.tasks.Task
import io.reactivex.rxjava3.core.BackpressureStrategy
import io.reactivex.rxjava3.core.Flowable
import io.reactivex.rxjava3.subjects.BehaviorSubject
import io.reactivex.rxjava3.subjects.PublishSubject
import com.habitrpg.common.habitica.models.Notification
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.filterNotNull
import java.lang.ref.WeakReference
import java.util.Date
interface NotificationsManager {
val displayNotificationEvents: Flowable<Notification>
val displayNotificationEvents: Flow<Notification>
var apiClient: WeakReference<ApiClient>?
fun setNotifications(current: List<Notification>)
fun getNotifications(): Flowable<List<Notification>>
fun getNotifications(): Flow<List<Notification>>
fun getNotification(id: String): Notification?
fun dismissTaskNotification(context: Context, task: Task)
}
class MainNotificationsManager: NotificationsManager {
private val displayNotificationSubject = PublishSubject.create<Notification>()
private val seenNotifications: MutableMap<String, Boolean>
override var apiClient: WeakReference<ApiClient>? = null
private val notifications: BehaviorSubject<List<Notification>>
private var lastNotificationHandling: Date? = null
override val displayNotificationEvents: Flowable<Notification>
get() {
return displayNotificationSubject.toFlowable(BackpressureStrategy.DROP)
}
val _notifications = MutableStateFlow<List<Notification>?>(null)
val _displaynotificationEvents = MutableStateFlow<Notification?>(null)
override val displayNotificationEvents: Flow<Notification> = _displaynotificationEvents.filterNotNull()
init {
this.seenNotifications = HashMap()
this.notifications = BehaviorSubject.create()
}
override fun setNotifications(current: List<Notification>) {
this.notifications.onNext(current)
_notifications.value = current
this.handlePopupNotifications(current)
}
override fun getNotifications(): Flowable<List<Notification>> {
return this.notifications.startWithArray(emptyList())
.toFlowable(BackpressureStrategy.LATEST)
override fun getNotifications(): Flow<List<Notification>> {
return _notifications.filterNotNull()
}
override fun getNotification(id: String): Notification? {
return this.notifications.value?.find { it.id == id }
return _notifications.value?.find { it.id == id }
}
override fun dismissTaskNotification(context: Context, task: Task) {
@ -109,7 +103,7 @@ class MainNotificationsManager: NotificationsManager {
}
if (notificationDisplayed) {
displayNotificationSubject.onNext(it)
_displaynotificationEvents.value = it
this.seenNotifications[it.id] = true
readNotification(it)
}
@ -119,7 +113,8 @@ class MainNotificationsManager: NotificationsManager {
}
private fun readNotification(notification: Notification) {
apiClient?.get()?.readNotification(notification.id)
?.subscribe({ }, ExceptionHandler.rx())
MainScope().launchCatching {
apiClient?.get()?.readNotification(notification.id)
}
}
}

View file

@ -72,10 +72,13 @@ class PurchaseHandler(
val mostRecentSub = findMostRecentSubscription(purchases)
val plan = userViewModel.user.value?.purchased?.plan
for (purchase in purchases) {
if (plan?.isActive == true && PurchaseTypes.allSubscriptionTypes.contains(purchase.skus.firstOrNull())) {
if (plan?.isActive == true && PurchaseTypes.allSubscriptionTypes.contains(
purchase.skus.firstOrNull()
)
) {
if ((plan.additionalData?.data?.orderId == purchase.orderId &&
((plan.dateTerminated != null) == purchase.isAutoRenewing)) ||
mostRecentSub?.orderId != purchase.orderId
((plan.dateTerminated != null) == purchase.isAutoRenewing)) ||
mostRecentSub?.orderId != purchase.orderId
) {
return
}
@ -138,8 +141,8 @@ class PurchaseHandler(
billingClient.endConnection()
}
fun queryPurchases(){
if (billingClientState == BillingClientState.READY){
fun queryPurchases() {
if (billingClientState == BillingClientState.READY) {
billingClient.queryPurchasesAsync(
BillingClient.SkuType.SUBS,
this@PurchaseHandler
@ -185,7 +188,12 @@ class PurchaseHandler(
return skuDetailsResult.skuDetailsList
}
fun purchase(activity: Activity, skuDetails: SkuDetails, recipient: String? = null, isSaleGemPurchase: Boolean = false) {
fun purchase(
activity: Activity,
skuDetails: SkuDetails,
recipient: String? = null,
isSaleGemPurchase: Boolean = false
) {
this.isSaleGemPurchase = isSaleGemPurchase
recipient?.let {
addGift(skuDetails.sku, it)
@ -216,41 +224,50 @@ class PurchaseHandler(
when {
PurchaseTypes.allGemTypes.contains(sku) -> {
val validationRequest = buildValidationRequest(purchase)
apiClient.validatePurchase(validationRequest).subscribe({
processedPurchase(purchase)
val gift = removeGift(sku)
CoroutineScope(Dispatchers.IO).launch(ExceptionHandler.coroutine()) {
consume(purchase)
MainScope().launchCatching {
try {
apiClient.validatePurchase(validationRequest)
processedPurchase(purchase)
val gift = removeGift(sku)
CoroutineScope(Dispatchers.IO).launch(ExceptionHandler.coroutine()) {
consume(purchase)
}
displayConfirmationDialog(purchase, gift?.second)
} catch (throwable: Throwable) {
handleError(throwable, purchase)
}
displayConfirmationDialog(purchase, gift?.second)
}) { throwable: Throwable ->
handleError(throwable, purchase)
}
}
PurchaseTypes.allSubscriptionNoRenewTypes.contains(sku) -> {
val validationRequest = buildValidationRequest(purchase)
apiClient.validateNoRenewSubscription(validationRequest).subscribe({
processedPurchase(purchase)
val gift = removeGift(sku)
CoroutineScope(Dispatchers.IO).launch(ExceptionHandler.coroutine()) {
consume(purchase)
MainScope().launchCatching {
try {
apiClient.validateNoRenewSubscription(validationRequest)
processedPurchase(purchase)
val gift = removeGift(sku)
CoroutineScope(Dispatchers.IO).launch(ExceptionHandler.coroutine()) {
consume(purchase)
}
displayConfirmationDialog(purchase, gift?.second)
} catch (throwable: Throwable) {
handleError(throwable, purchase)
}
displayConfirmationDialog(purchase, gift?.second)
}) { throwable: Throwable ->
handleError(throwable, purchase)
}
}
PurchaseTypes.allSubscriptionTypes.contains(sku) -> {
val validationRequest = buildValidationRequest(purchase)
apiClient.validateSubscription(validationRequest).subscribe({
processedPurchase(purchase)
analyticsManager.logEvent("user_subscribed", bundleOf(Pair("sku", sku)))
CoroutineScope(Dispatchers.IO).launch(ExceptionHandler.coroutine()) {
acknowledgePurchase(purchase)
MainScope().launchCatching {
try {
apiClient.validateSubscription(validationRequest)
processedPurchase(purchase)
analyticsManager.logEvent("user_subscribed", bundleOf(Pair("sku", sku)))
CoroutineScope(Dispatchers.IO).launch(ExceptionHandler.coroutine()) {
acknowledgePurchase(purchase)
}
displayConfirmationDialog(purchase)
} catch (throwable: Throwable) {
handleError(throwable, purchase)
}
displayConfirmationDialog(purchase)
}) { throwable: Throwable ->
handleError(throwable, purchase)
}
}
}
@ -320,7 +337,8 @@ class PurchaseHandler(
}
private fun findMostRecentSubscription(purchasesList: List<Purchase>): Purchase? {
val purchases = purchasesList.filter { it.isAcknowledged }.sortedByDescending { it.purchaseTime }
val purchases =
purchasesList.filter { it.isAcknowledged }.sortedByDescending { it.purchaseTime }
var fallback: Purchase? = null
// If there is a subscription that is still active, prioritise that. Otherwise return the most recent one.
for (purchase in purchases) {
@ -380,18 +398,29 @@ class PurchaseHandler(
val message = when {
PurchaseTypes.allSubscriptionNoRenewTypes.contains(sku) -> {
title = context.getString(R.string.gift_confirmation_title)
context.getString(R.string.gift_confirmation_text_sub, giftedTo, durationString(sku))
context.getString(
R.string.gift_confirmation_text_sub,
giftedTo,
durationString(sku)
)
}
PurchaseTypes.allSubscriptionTypes.contains(sku) -> {
if (sku == PurchaseTypes.Subscription1Month) {
context.getString(R.string.subscription_confirmation)
} else {
context.getString(R.string.subscription_confirmation_multiple, durationString(sku))
context.getString(
R.string.subscription_confirmation_multiple,
durationString(sku)
)
}
}
PurchaseTypes.allGemTypes.contains(sku) && giftedTo != null -> {
title = context.getString(R.string.gift_confirmation_title)
context.getString(R.string.gift_confirmation_text_gems_new, giftedTo, gemAmountString(sku))
context.getString(
R.string.gift_confirmation_text_gems_new,
giftedTo,
gemAmountString(sku)
)
}
PurchaseTypes.allGemTypes.contains(sku) && giftedTo == null -> {
context.getString(R.string.gem_purchase_confirmation, gemAmountString(sku))

View file

@ -25,7 +25,7 @@ class UserStatComputer {
var stats: String? = null
}
fun computeClassBonus(equipmentList: List<Equipment>, user: Avatar): List<StatsRow> {
fun computeClassBonus(equipmentList: List<Equipment>?, user: Avatar): List<StatsRow> {
val skillRows = ArrayList<StatsRow>()
var strAttributes = 0f
@ -39,7 +39,7 @@ class UserStatComputer {
var perClassBonus = 0f
// Summarize stats and fill equipment table
for (i in equipmentList) {
for (i in equipmentList ?: emptyList()) {
val strength = i.str
val intelligence = i._int
val constitution = i.con

View file

@ -7,8 +7,9 @@ import com.google.firebase.messaging.FirebaseMessaging
import com.google.firebase.messaging.RemoteMessage
import com.habitrpg.android.habitica.data.ApiClient
import com.habitrpg.android.habitica.helpers.AmplitudeManager
import com.habitrpg.android.habitica.helpers.ExceptionHandler
import com.habitrpg.android.habitica.helpers.launchCatching
import com.habitrpg.android.habitica.models.user.User
import kotlinx.coroutines.MainScope
class PushNotificationManager(
var apiClient: ApiClient,
@ -51,14 +52,18 @@ class PushNotificationManager(
val pushDeviceData = HashMap<String, String>()
pushDeviceData["regId"] = this.refreshedToken
pushDeviceData["type"] = "android"
apiClient.addPushDevice(pushDeviceData).subscribe({ }, ExceptionHandler.rx())
MainScope().launchCatching {
apiClient.addPushDevice(pushDeviceData)
}
}
fun removePushDeviceUsingStoredToken() {
if (this.refreshedToken.isEmpty() || !userHasPushDevice()) {
return
}
apiClient.deletePushDevice(this.refreshedToken).subscribe({ }, ExceptionHandler.rx())
MainScope().launchCatching {
apiClient.deletePushDevice(refreshedToken)
}
}
private fun userHasPushDevice(): Boolean {

View file

@ -6,27 +6,21 @@ import android.os.Bundle
import com.habitrpg.android.habitica.executors.PostExecutionThread
import com.habitrpg.android.habitica.models.user.User
import com.habitrpg.android.habitica.ui.activities.ClassSelectionActivity
import io.reactivex.rxjava3.core.Flowable
import javax.inject.Inject
class CheckClassSelectionUseCase @Inject constructor(postExecutionThread: PostExecutionThread) : UseCase<CheckClassSelectionUseCase.RequestValues, Void>(postExecutionThread) {
class CheckClassSelectionUseCase @Inject constructor(postExecutionThread: PostExecutionThread) : FlowUseCase<CheckClassSelectionUseCase.RequestValues, Unit>() {
override fun buildUseCaseObservable(requestValues: RequestValues): Flowable<Void> {
return Flowable.defer {
val user = requestValues.user
if (requestValues.currentClass == null) {
if (user?.stats?.lvl ?: 0 >= 9 &&
user?.preferences?.disableClasses != true &&
user?.flags?.classSelected != true
) {
displayClassSelectionActivity(true, null, requestValues.activity)
}
} else {
displayClassSelectionActivity(requestValues.isInitialSelection, requestValues.currentClass, requestValues.activity)
override suspend fun run(requestValues: RequestValues) {
val user = requestValues.user
if (requestValues.currentClass == null) {
if ((user?.stats?.lvl ?: 0) >= 9 &&
user?.preferences?.disableClasses != true &&
user?.flags?.classSelected != true
) {
displayClassSelectionActivity(true, null, requestValues.activity)
}
Flowable.empty()
} else {
displayClassSelectionActivity(requestValues.isInitialSelection, requestValues.currentClass, requestValues.activity)
}
}
@ -49,5 +43,5 @@ class CheckClassSelectionUseCase @Inject constructor(postExecutionThread: PostEx
val isInitialSelection: Boolean,
val currentClass: String?,
val activity: Activity
) : UseCase.RequestValues
) : FlowUseCase.RequestValues
}

View file

@ -3,11 +3,9 @@ package com.habitrpg.android.habitica.interactors
import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.executors.PostExecutionThread
import com.habitrpg.android.habitica.helpers.SoundManager
import com.habitrpg.android.habitica.ui.views.HabiticaSnackbar
import com.habitrpg.shared.habitica.models.responses.TaskScoringResult
import io.reactivex.rxjava3.core.Flowable
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.delay
@ -15,32 +13,35 @@ import kotlinx.coroutines.launch
import javax.inject.Inject
class DisplayItemDropUseCase @Inject
constructor(private val soundManager: SoundManager, postExecutionThread: PostExecutionThread) : UseCase<DisplayItemDropUseCase.RequestValues, Void>(postExecutionThread) {
constructor(private val soundManager: SoundManager):
FlowUseCase<DisplayItemDropUseCase.RequestValues, Unit>() {
override fun buildUseCaseObservable(requestValues: RequestValues): Flowable<Void> {
return Flowable.defer {
val data = requestValues.data
val snackbarText = StringBuilder(data?.drop?.dialog ?: "")
override suspend fun run(requestValues: RequestValues) {
val data = requestValues.data
val snackbarText = StringBuilder(data?.drop?.dialog ?: "")
if ((data?.questItemsFound ?: 0) > 0 && requestValues.showQuestItems) {
if (snackbarText.isNotEmpty())
snackbarText.append('\n')
snackbarText.append(requestValues.context.getString(R.string.quest_items_found, data!!.questItemsFound))
}
if (snackbarText.isNotEmpty()) {
MainScope().launch(context = Dispatchers.Main) {
delay(3000L)
HabiticaSnackbar.showSnackbar(
requestValues.snackbarTargetView,
snackbarText, HabiticaSnackbar.SnackbarDisplayType.DROP, true
)
soundManager.loadAndPlayAudio(SoundManager.SoundItemDrop)
}
}
Flowable.empty()
if ((data?.questItemsFound ?: 0) > 0 && requestValues.showQuestItems) {
if (snackbarText.isNotEmpty())
snackbarText.append('\n')
snackbarText.append(
requestValues.context.getString(
R.string.quest_items_found,
data!!.questItemsFound
)
)
}
if (snackbarText.isNotEmpty()) {
MainScope().launch(context = Dispatchers.Main) {
delay(3000L)
HabiticaSnackbar.showSnackbar(
requestValues.snackbarTargetView,
snackbarText, HabiticaSnackbar.SnackbarDisplayType.DROP, true
)
soundManager.loadAndPlayAudio(SoundManager.SoundItemDrop)
}
}
return
}
class RequestValues(
@ -48,5 +49,5 @@ constructor(private val soundManager: SoundManager, postExecutionThread: PostExe
val context: AppCompatActivity,
val snackbarTargetView: ViewGroup,
val showQuestItems: Boolean
) : UseCase.RequestValues
) : FlowUseCase.RequestValues
}

View file

@ -7,75 +7,75 @@ import android.view.View
import android.widget.FrameLayout
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.data.InventoryRepository
import com.habitrpg.android.habitica.executors.PostExecutionThread
import com.habitrpg.android.habitica.models.inventory.Food
import com.habitrpg.android.habitica.models.inventory.Pet
import com.habitrpg.shared.habitica.models.responses.FeedResponse
import com.habitrpg.android.habitica.ui.activities.BaseActivity
import com.habitrpg.common.habitica.extensions.loadImage
import com.habitrpg.common.habitica.views.PixelArtView
import com.habitrpg.android.habitica.ui.views.SnackbarActivity
import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog
import io.reactivex.rxjava3.core.Flowable
import com.habitrpg.common.habitica.extensions.loadImage
import com.habitrpg.common.habitica.views.PixelArtView
import com.habitrpg.shared.habitica.models.responses.FeedResponse
import javax.inject.Inject
class FeedPetUseCase @Inject
constructor(
private val inventoryRepository: InventoryRepository,
postExecutionThread: PostExecutionThread
) : UseCase<FeedPetUseCase.RequestValues, FeedResponse>(postExecutionThread) {
override fun buildUseCaseObservable(requestValues: FeedPetUseCase.RequestValues): Flowable<FeedResponse> {
return inventoryRepository.feedPet(requestValues.pet, requestValues.food)
.doOnNext { feedResponse ->
(requestValues.context as? SnackbarActivity)?.showSnackbar(content = feedResponse.message)
if (feedResponse.value == -1) {
val mountWrapper =
View.inflate(
requestValues.context,
R.layout.pet_imageview,
null
) as? FrameLayout
val mountImageView =
mountWrapper?.findViewById(R.id.pet_imageview) as? PixelArtView
) : FlowUseCase<FeedPetUseCase.RequestValues, FeedResponse?>() {
override suspend fun run(requestValues: FeedPetUseCase.RequestValues): FeedResponse? {
val feedResponse = inventoryRepository.feedPet(requestValues.pet, requestValues.food)
(requestValues.context as? SnackbarActivity)?.showSnackbar(content = feedResponse?.message)
if (feedResponse?.value == -1) {
val mountWrapper =
View.inflate(
requestValues.context,
R.layout.pet_imageview,
null
) as? FrameLayout
val mountImageView =
mountWrapper?.findViewById(R.id.pet_imageview) as? PixelArtView
mountImageView?.loadImage("Mount_Icon_" + requestValues.pet.key)
val dialog = HabiticaAlertDialog(requestValues.context)
dialog.setTitle(
requestValues.context.getString(
R.string.evolved_pet_title,
requestValues.pet.text
)
mountImageView?.loadImage("Mount_Icon_" + requestValues.pet.key)
val dialog = HabiticaAlertDialog(requestValues.context)
dialog.setTitle(
requestValues.context.getString(
R.string.evolved_pet_title,
requestValues.pet.text
)
)
dialog.setAdditionalContentView(mountWrapper)
dialog.addButton(R.string.onwards, true)
dialog.addButton(R.string.share, false) { hatchingDialog, _ ->
val message =
requestValues.context.getString(
R.string.share_raised,
requestValues.pet.text
)
dialog.setAdditionalContentView(mountWrapper)
dialog.addButton(R.string.onwards, true)
dialog.addButton(R.string.share, false) { hatchingDialog, _ ->
val message =
requestValues.context.getString(
R.string.share_raised,
requestValues.pet.text
)
val mountImageSideLength = 99
val sharedImage = Bitmap.createBitmap(
mountImageSideLength,
mountImageSideLength,
Bitmap.Config.ARGB_8888
)
val canvas = Canvas(sharedImage)
mountImageView?.drawable?.setBounds(
0,
0,
mountImageSideLength,
mountImageSideLength
)
mountImageView?.drawable?.draw(canvas)
(requestValues.context as? BaseActivity)?.shareContent("raisedPet", message, sharedImage)
hatchingDialog.dismiss()
}
dialog.enqueue()
}
val mountImageSideLength = 99
val sharedImage = Bitmap.createBitmap(
mountImageSideLength,
mountImageSideLength,
Bitmap.Config.ARGB_8888
)
val canvas = Canvas(sharedImage)
mountImageView?.drawable?.setBounds(
0,
0,
mountImageSideLength,
mountImageSideLength
)
mountImageView?.drawable?.draw(canvas)
(requestValues.context as? BaseActivity)?.shareContent(
"raisedPet",
message,
sharedImage
)
hatchingDialog.dismiss()
}
dialog.enqueue()
}
return feedResponse
}
class RequestValues(val pet: Pet, val food: Food, val context: Context) :
UseCase.RequestValues
FlowUseCase.RequestValues
}

View file

@ -7,24 +7,21 @@ import android.view.View
import android.widget.FrameLayout
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.data.InventoryRepository
import com.habitrpg.android.habitica.executors.PostExecutionThread
import com.habitrpg.android.habitica.helpers.ExceptionHandler
import com.habitrpg.android.habitica.helpers.launchCatching
import com.habitrpg.android.habitica.models.inventory.Egg
import com.habitrpg.android.habitica.models.inventory.HatchingPotion
import com.habitrpg.android.habitica.models.user.Items
import com.habitrpg.android.habitica.ui.activities.BaseActivity
import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog
import com.habitrpg.common.habitica.extensions.loadImage
import com.habitrpg.common.habitica.views.PixelArtView
import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog
import io.reactivex.rxjava3.core.Flowable
import kotlinx.coroutines.MainScope
import javax.inject.Inject
class HatchPetUseCase @Inject
constructor(
private val inventoryRepository: InventoryRepository,
postExecutionThread: PostExecutionThread
) : UseCase<HatchPetUseCase.RequestValues, Items>(postExecutionThread) {
override fun buildUseCaseObservable(requestValues: RequestValues): Flowable<Items> {
private val inventoryRepository: InventoryRepository) : FlowUseCase<HatchPetUseCase.RequestValues, Items?>() {
override suspend fun run(requestValues: RequestValues): Items? {
return inventoryRepository.hatchPet(requestValues.egg, requestValues.potion) {
val petWrapper = View.inflate(requestValues.context, R.layout.pet_imageview, null) as? FrameLayout
val petImageView = petWrapper?.findViewById(R.id.pet_imageview) as? PixelArtView
@ -36,8 +33,9 @@ constructor(
dialog.setTitle(requestValues.context.getString(R.string.hatched_pet_title, potionName, eggName))
dialog.setAdditionalContentView(petWrapper)
dialog.addButton(R.string.equip, true) { _, _ ->
inventoryRepository.equip("pet", requestValues.egg.key + "-" + requestValues.potion.key)
.subscribe({}, ExceptionHandler.rx())
MainScope().launchCatching {
inventoryRepository.equip("pet", requestValues.egg.key + "-" + requestValues.potion.key)
}
}
dialog.addButton(R.string.share, false) { hatchingDialog, _ ->
val message = requestValues.context.getString(R.string.share_hatched, potionName, eggName)
@ -54,5 +52,5 @@ constructor(
}
}
class RequestValues(val potion: HatchingPotion, val egg: Egg, val context: Context) : UseCase.RequestValues
class RequestValues(val potion: HatchingPotion, val egg: Egg, val context: Context) : FlowUseCase.RequestValues
}

View file

@ -4,97 +4,94 @@ import android.graphics.Bitmap
import android.view.ViewGroup
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.databinding.DialogLevelup10Binding
import com.habitrpg.android.habitica.executors.PostExecutionThread
import com.habitrpg.android.habitica.helpers.ExceptionHandler
import com.habitrpg.android.habitica.helpers.SoundManager
import com.habitrpg.android.habitica.helpers.launchCatching
import com.habitrpg.android.habitica.models.user.Stats
import com.habitrpg.android.habitica.models.user.User
import com.habitrpg.common.habitica.views.AvatarView
import com.habitrpg.android.habitica.ui.activities.BaseActivity
import com.habitrpg.android.habitica.ui.views.HabiticaIconsHelper
import com.habitrpg.android.habitica.ui.views.HabiticaSnackbar
import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog
import io.reactivex.rxjava3.core.Flowable
import com.habitrpg.common.habitica.views.AvatarView
import kotlinx.coroutines.MainScope
import javax.inject.Inject
class LevelUpUseCase @Inject
constructor(
private val soundManager: SoundManager,
postExecutionThread: PostExecutionThread,
private val checkClassSelectionUseCase: CheckClassSelectionUseCase
) : UseCase<LevelUpUseCase.RequestValues, Stats>(postExecutionThread) {
) : FlowUseCase<LevelUpUseCase.RequestValues, Stats?>() {
override fun buildUseCaseObservable(requestValues: RequestValues): Flowable<Stats> {
return Flowable.defer {
soundManager.loadAndPlayAudio(SoundManager.SoundLevelUp)
override suspend fun run(requestValues: RequestValues): Stats? {
soundManager.loadAndPlayAudio(SoundManager.SoundLevelUp)
val suppressedModals = requestValues.user.preferences?.suppressModals
val suppressedModals = requestValues.user.preferences?.suppressModals
if (requestValues.newLevel == 10) {
val binding = DialogLevelup10Binding.inflate(requestValues.activity.layoutInflater)
binding.healerIconView.setImageBitmap(HabiticaIconsHelper.imageOfHealerLightBg())
binding.mageIconView.setImageBitmap(HabiticaIconsHelper.imageOfMageLightBg())
binding.rogueIconView.setImageBitmap(HabiticaIconsHelper.imageOfRogueLightBg())
binding.warriorIconView.setImageBitmap(HabiticaIconsHelper.imageOfWarriorLightBg())
if (requestValues.newLevel == 10) {
val binding = DialogLevelup10Binding.inflate(requestValues.activity.layoutInflater)
binding.healerIconView.setImageBitmap(HabiticaIconsHelper.imageOfHealerLightBg())
binding.mageIconView.setImageBitmap(HabiticaIconsHelper.imageOfMageLightBg())
binding.rogueIconView.setImageBitmap(HabiticaIconsHelper.imageOfRogueLightBg())
binding.warriorIconView.setImageBitmap(HabiticaIconsHelper.imageOfWarriorLightBg())
val alert = HabiticaAlertDialog(requestValues.activity)
alert.setTitle(requestValues.activity.getString(R.string.levelup_header, requestValues.newLevel))
alert.setAdditionalContentView(binding.root)
alert.addButton(R.string.select_class, true) { _, _ ->
val alert = HabiticaAlertDialog(requestValues.activity)
alert.setTitle(requestValues.activity.getString(R.string.levelup_header, requestValues.newLevel))
alert.setAdditionalContentView(binding.root)
alert.addButton(R.string.select_class, true) { _, _ ->
MainScope().launchCatching {
showClassSelection(requestValues)
}
alert.addButton(R.string.not_now, false)
alert.isCelebratory = true
if (!requestValues.activity.isFinishing) {
alert.enqueue()
}
} else {
if (suppressedModals?.levelUp == true) {
HabiticaSnackbar.showSnackbar(
requestValues.snackbarTargetView,
requestValues.activity.getString(R.string.levelup_header, requestValues.newLevel),
HabiticaSnackbar.SnackbarDisplayType.SUCCESS, true
)
return@defer Flowable.just<Stats>(requestValues.user.stats)
}
val customView = requestValues.activity.layoutInflater.inflate(R.layout.dialog_levelup, null)
if (customView != null) {
val dialogAvatarView = customView.findViewById<AvatarView>(R.id.avatarView)
dialogAvatarView.setAvatar(requestValues.user)
}
val message = requestValues.activity.getString(R.string.share_levelup, requestValues.newLevel)
val avatarView = AvatarView(requestValues.activity, showBackground = true, showMount = true, showPet = true)
avatarView.setAvatar(requestValues.user)
var sharedImage: Bitmap? = null
avatarView.onAvatarImageReady { image ->
sharedImage = image
}
val alert = HabiticaAlertDialog(requestValues.activity)
alert.setTitle(requestValues.activity.getString(R.string.levelup_header, requestValues.newLevel))
alert.setAdditionalContentView(customView)
alert.addButton(R.string.onwards, true) { _, _ ->
showClassSelection(requestValues)
}
alert.addButton(R.string.share, false) { _, _ ->
requestValues.activity.shareContent("levelup", message, sharedImage)
}
alert.isCelebratory = true
if (!requestValues.activity.isFinishing) {
alert.enqueue()
}
}
alert.addButton(R.string.not_now, false)
alert.isCelebratory = true
Flowable.just(requestValues.user.stats!!)
if (!requestValues.activity.isFinishing) {
alert.enqueue()
}
} else {
if (suppressedModals?.levelUp == true) {
HabiticaSnackbar.showSnackbar(
requestValues.snackbarTargetView,
requestValues.activity.getString(R.string.levelup_header, requestValues.newLevel),
HabiticaSnackbar.SnackbarDisplayType.SUCCESS, true
)
return requestValues.user.stats
}
val customView = requestValues.activity.layoutInflater.inflate(R.layout.dialog_levelup, null)
if (customView != null) {
val dialogAvatarView = customView.findViewById<AvatarView>(R.id.avatarView)
dialogAvatarView.setAvatar(requestValues.user)
}
val message = requestValues.activity.getString(R.string.share_levelup, requestValues.newLevel)
val avatarView = AvatarView(requestValues.activity, showBackground = true, showMount = true, showPet = true)
avatarView.setAvatar(requestValues.user)
var sharedImage: Bitmap? = null
avatarView.onAvatarImageReady { image ->
sharedImage = image
}
val alert = HabiticaAlertDialog(requestValues.activity)
alert.setTitle(requestValues.activity.getString(R.string.levelup_header, requestValues.newLevel))
alert.setAdditionalContentView(customView)
alert.addButton(R.string.onwards, true) { _, _ ->
MainScope().launchCatching {
showClassSelection(requestValues)
}
}
alert.addButton(R.string.share, false) { _, _ ->
requestValues.activity.shareContent("levelup", message, sharedImage)
}
alert.isCelebratory = true
if (!requestValues.activity.isFinishing) {
alert.enqueue()
}
}
return requestValues.user.stats
}
private fun showClassSelection(requestValues: RequestValues) {
checkClassSelectionUseCase.observable(CheckClassSelectionUseCase.RequestValues(requestValues.user, true, null, requestValues.activity))
.subscribe({ }, ExceptionHandler.rx())
private suspend fun showClassSelection(requestValues: RequestValues) {
checkClassSelectionUseCase.callInteractor(CheckClassSelectionUseCase.RequestValues(requestValues.user, true, null, requestValues.activity))
}
class RequestValues(
@ -102,7 +99,7 @@ constructor(
val level: Int?,
val activity: BaseActivity,
val snackbarTargetView: ViewGroup
) : UseCase.RequestValues {
) : FlowUseCase.RequestValues {
val newLevel: Int = level ?: 0
}
}

View file

@ -13,7 +13,6 @@ import androidx.core.content.ContextCompat
import androidx.core.util.Pair
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.data.UserRepository
import com.habitrpg.android.habitica.executors.PostExecutionThread
import com.habitrpg.android.habitica.models.user.Stats
import com.habitrpg.android.habitica.models.user.User
import com.habitrpg.android.habitica.ui.activities.BaseActivity
@ -21,39 +20,30 @@ import com.habitrpg.android.habitica.ui.views.HabiticaIconsHelper
import com.habitrpg.android.habitica.ui.views.HabiticaSnackbar
import com.habitrpg.android.habitica.ui.views.HabiticaSnackbar.SnackbarDisplayType
import com.habitrpg.shared.habitica.extensions.round
import io.reactivex.rxjava3.core.Flowable
import javax.inject.Inject
import kotlin.math.abs
class NotifyUserUseCase @Inject
constructor(
postExecutionThread: PostExecutionThread,
private val levelUpUseCase: LevelUpUseCase,
private val userRepository: UserRepository
) : UseCase<NotifyUserUseCase.RequestValues, Stats>(postExecutionThread) {
) : FlowUseCase<NotifyUserUseCase.RequestValues, Stats?>() {
override fun buildUseCaseObservable(requestValues: RequestValues): Flowable<Stats> {
return Flowable.defer {
if (requestValues.user == null) {
return@defer Flowable.empty<Stats>()
}
val stats = requestValues.user.stats
val pair = getNotificationAndAddStatsToUser(requestValues.context, requestValues.xp, requestValues.hp, requestValues.gold, requestValues.mp, requestValues.questDamage, requestValues.user)
val view = pair.first
val type = pair.second
if (view != null && type != null) {
HabiticaSnackbar.showSnackbar(requestValues.snackbarTargetView, null, null, view, type)
}
if (requestValues.hasLeveledUp == true) {
return@defer levelUpUseCase.observable(LevelUpUseCase.RequestValues(requestValues.user, requestValues.level, requestValues.context, requestValues.snackbarTargetView))
// TODO: .flatMap { userRepository.retrieveUser(true) }
.flatMap { userRepository.getUserFlowable().firstElement().toFlowable() }
.map { it.stats }
} else {
return@defer Flowable.just(stats)
}
override suspend fun run(requestValues: RequestValues): Stats? {
if (requestValues.user == null) {
return null
}
val pair = getNotificationAndAddStatsToUser(requestValues.context, requestValues.xp, requestValues.hp, requestValues.gold, requestValues.mp, requestValues.questDamage, requestValues.user)
val view = pair.first
val type = pair.second
if (view != null && type != null) {
HabiticaSnackbar.showSnackbar(requestValues.snackbarTargetView, null, null, view, type)
}
if (requestValues.hasLeveledUp == true) {
levelUpUseCase.callInteractor(LevelUpUseCase.RequestValues(requestValues.user, requestValues.level, requestValues.context, requestValues.snackbarTargetView))
userRepository.retrieveUser(true)
}
return requestValues.user.stats
}
class RequestValues(
@ -67,7 +57,7 @@ constructor(
val questDamage: Double?,
val hasLeveledUp: Boolean?,
val level: Int?
) : UseCase.RequestValues
) : FlowUseCase.RequestValues
companion object {

View file

@ -18,8 +18,8 @@ abstract class UseCase<Q : UseCase.RequestValues?, T: Any> protected constructor
abstract class FlowUseCase<Q : FlowUseCase.RequestValues?, T> {
protected abstract suspend fun run(requestValues: Q): T
suspend fun observable(requestValues: Q): T {
return withContext(Dispatchers.IO) {
suspend fun callInteractor(requestValues: Q): T {
return withContext(Dispatchers.Main) {
run(requestValues)
}
}

View file

@ -15,6 +15,7 @@ import com.habitrpg.android.habitica.data.SocialRepository
import com.habitrpg.android.habitica.data.TaskRepository
import com.habitrpg.android.habitica.data.UserRepository
import com.habitrpg.android.habitica.helpers.ExceptionHandler
import com.habitrpg.android.habitica.helpers.launchCatching
import com.habitrpg.android.habitica.interactors.NotifyUserUseCase
import com.habitrpg.android.habitica.models.user.User
import kotlinx.coroutines.MainScope
@ -65,15 +66,20 @@ class LocalNotificationActionReceiver : BroadcastReceiver() {
}
context?.getString(R.string.reject_party_invite) -> {
groupID?.let {
socialRepository.rejectGroupInvite(it)
.subscribe({ }, ExceptionHandler.rx())
MainScope().launchCatching {
socialRepository.rejectGroupInvite(it)
}
}
}
context?.getString(R.string.accept_quest_invite) -> {
socialRepository.acceptQuest(user).subscribe({ }, ExceptionHandler.rx())
MainScope().launchCatching {
socialRepository.acceptQuest(user)
}
}
context?.getString(R.string.reject_quest_invite) -> {
socialRepository.rejectQuest(user).subscribe({ }, ExceptionHandler.rx())
MainScope().launchCatching {
socialRepository.rejectQuest(user)
}
}
context?.getString(R.string.accept_guild_invite) -> {
groupID?.let {
@ -84,21 +90,20 @@ class LocalNotificationActionReceiver : BroadcastReceiver() {
}
context?.getString(R.string.reject_guild_invite) -> {
groupID?.let {
socialRepository.rejectGroupInvite(it)
.subscribe({ }, ExceptionHandler.rx())
MainScope().launchCatching {
socialRepository.rejectGroupInvite(it)
}
}
}
context?.getString(R.string.group_message_reply) -> {
groupID?.let {
getMessageText(context?.getString(R.string.group_message_reply))?.let { message ->
socialRepository.postGroupChat(it, message).subscribe(
{
context?.let { c ->
NotificationManagerCompat.from(c).cancel(it.hashCode())
}
},
ExceptionHandler.rx()
)
MainScope().launchCatching {
socialRepository.postGroupChat(it, message)
context?.let { c ->
NotificationManagerCompat.from(c).cancel(it.hashCode())
}
}
}
}
}

View file

@ -15,13 +15,12 @@ import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.data.TaskRepository
import com.habitrpg.android.habitica.data.UserRepository
import com.habitrpg.android.habitica.extensions.withImmutableFlag
import com.habitrpg.android.habitica.helpers.ExceptionHandler
import com.habitrpg.android.habitica.helpers.TaskAlarmManager
import com.habitrpg.android.habitica.models.tasks.Task
import com.habitrpg.android.habitica.models.user.User
import com.habitrpg.android.habitica.helpers.launchCatching
import com.habitrpg.android.habitica.ui.activities.MainActivity
import com.habitrpg.shared.habitica.models.tasks.TaskType
import io.reactivex.rxjava3.functions.BiFunction
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.flow.firstOrNull
import java.util.Calendar
import java.util.Date
import java.util.Random
@ -57,26 +56,20 @@ class NotificationPublisher : BroadcastReceiver() {
}
val checkDailies = intent.getBooleanExtra(CHECK_DAILIES, false)
if (checkDailies) {
taskRepository.getTasksFlowable(TaskType.DAILY, null, emptyArray()).firstElement().zipWith(
userRepository.getUserFlowable().firstElement(),
BiFunction<List<Task>, User, Pair<List<Task>, User>> { tasks, user ->
return@BiFunction Pair(tasks, user)
MainScope().launchCatching {
val tasks = taskRepository.getTasks(TaskType.DAILY, null, emptyArray()).firstOrNull()
val user = userRepository.getUser().firstOrNull()
var showNotifications = false
for (task in tasks ?: emptyList()) {
if (task.checkIfDue()) {
showNotifications = true
break
}
}
).subscribe(
{ pair ->
var showNotifications = false
for (task in pair.first) {
if (task.checkIfDue()) {
showNotifications = true
break
}
}
if (showNotifications) {
notify(intent, buildNotification(wasInactive, pair.second.authentication?.timestamps?.createdAt))
}
},
ExceptionHandler.rx()
)
if (showNotifications) {
notify(intent, buildNotification(wasInactive, user?.authentication?.timestamps?.createdAt))
}
}
} else {
notify(intent, buildNotification(wasInactive))
}

View file

@ -18,6 +18,7 @@ import com.habitrpg.android.habitica.helpers.AdHandler
import com.habitrpg.android.habitica.helpers.AdType
import com.habitrpg.android.habitica.helpers.AppConfigManager
import com.habitrpg.android.habitica.helpers.ExceptionHandler
import com.habitrpg.android.habitica.helpers.launchCatching
import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel
import com.habitrpg.android.habitica.ui.views.ads.AdButton
import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaBottomSheetDialog
@ -25,6 +26,7 @@ import com.habitrpg.common.habitica.extensions.dpToPx
import com.habitrpg.common.habitica.extensions.loadImage
import com.habitrpg.common.habitica.helpers.Animations
import com.plattysoft.leonids.ParticleSystem
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.launch
import java.util.Locale
import javax.inject.Inject
@ -115,7 +117,9 @@ class ArmoireActivity : BaseActivity() {
finish()
}
binding.equipButton.setOnClickListener {
equipmentKey?.let { it1 -> inventoryRepository.equip("equipped", it1).subscribe({}, ExceptionHandler.rx()) }
equipmentKey?.let { it1 ->
MainScope().launchCatching { inventoryRepository.equip("equipped", it1) }
}
finish()
}
binding.dropRateButton.setOnClickListener {

View file

@ -29,6 +29,7 @@ import com.habitrpg.android.habitica.data.UserRepository
import com.habitrpg.android.habitica.extensions.updateStatusBarColor
import com.habitrpg.android.habitica.helpers.ExceptionHandler
import com.habitrpg.android.habitica.helpers.NotificationsManager
import com.habitrpg.android.habitica.helpers.launchCatching
import com.habitrpg.android.habitica.interactors.ShowNotificationInteractor
import com.habitrpg.android.habitica.proxy.AnalyticsManager
import com.habitrpg.android.habitica.ui.helpers.ToolbarColorHelper
@ -36,7 +37,6 @@ import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog
import com.habitrpg.common.habitica.extensions.getThemeColor
import com.habitrpg.common.habitica.extensions.isUsingNightModeResources
import com.habitrpg.common.habitica.helpers.LanguageHelper
import io.reactivex.rxjava3.disposables.CompositeDisposable
import kotlinx.coroutines.launch
import java.util.Date
import java.util.Locale
@ -66,8 +66,6 @@ abstract class BaseActivity : AppCompatActivity() {
return (getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater).inflate(layoutResId ?: 0, null)
}
var compositeSubscription = CompositeDisposable()
private val habiticaApplication: HabiticaApplication
get() = application as HabiticaApplication
@ -95,17 +93,15 @@ abstract class BaseActivity : AppCompatActivity() {
getLayoutResId()?.let {
setContentView(getContentView(it))
}
compositeSubscription = CompositeDisposable()
compositeSubscription.add(notificationsManager.displayNotificationEvents.subscribe(
{
if (ShowNotificationInteractor(this, lifecycleScope).handleNotification(it)) {
lifecycleScope.launch(ExceptionHandler.coroutine()) {
userRepository.retrieveUser(false, true)
lifecycleScope.launchCatching {
notificationsManager.displayNotificationEvents.collect {
if (ShowNotificationInteractor(this@BaseActivity, lifecycleScope).handleNotification(it)) {
lifecycleScope.launch(ExceptionHandler.coroutine()) {
userRepository.retrieveUser(false, true)
}
}
}
},
ExceptionHandler.rx()
))
}
}
override fun onRestart() {
@ -212,10 +208,6 @@ abstract class BaseActivity : AppCompatActivity() {
override fun onDestroy() {
destroyed = true
if (!compositeSubscription.isDisposed) {
compositeSubscription.dispose()
}
super.onDestroy()
}

View file

@ -25,6 +25,7 @@ import com.habitrpg.android.habitica.databinding.ActivityCreateChallengeBinding
import com.habitrpg.android.habitica.extensions.addCloseButton
import com.habitrpg.android.habitica.helpers.ExceptionHandler
import com.habitrpg.android.habitica.helpers.MainNavigationController
import com.habitrpg.android.habitica.helpers.launchCatching
import com.habitrpg.android.habitica.models.social.Challenge
import com.habitrpg.android.habitica.models.social.Group
import com.habitrpg.android.habitica.models.tasks.Task
@ -38,7 +39,6 @@ import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog
import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaProgressDialog
import com.habitrpg.common.habitica.extensions.getThemeColor
import com.habitrpg.shared.habitica.models.tasks.TaskType
import io.reactivex.rxjava3.core.Flowable
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.firstOrNull
@ -52,8 +52,10 @@ class ChallengeFormActivity : BaseActivity() {
@Inject
internal lateinit var challengeRepository: ChallengeRepository
@Inject
internal lateinit var socialRepository: SocialRepository
@Inject
internal lateinit var userViewModel: MainUserViewModel
@ -69,6 +71,7 @@ class ChallengeFormActivity : BaseActivity() {
private val removedTasks = HashMap<String, Task>()
override var overrideModernHeader: Boolean? = true
// Add {*} Items
private lateinit var addHabit: Task
private lateinit var addDaily: Task
@ -130,37 +133,34 @@ class ChallengeFormActivity : BaseActivity() {
savingInProgress = true
val dialog = HabiticaProgressDialog.show(this, R.string.saving)
val observable: Flowable<Challenge> = if (editMode) {
updateChallenge()
} else {
createChallenge()
}
lifecycleScope.launchCatching({
dialog?.dismiss()
savingInProgress = false
ExceptionHandler.reportError(it)
}) {
val challenge = if (editMode) {
updateChallenge()
} else {
createChallenge()
}
compositeSubscription.add(
observable
.flatMap {
challengeId = it.id
challengeRepository.retrieveChallenges(0, true)
challengeId = challenge?.id
challengeRepository.retrieveChallenges(0, true)
dialog?.dismiss()
savingInProgress = false
finish()
if (!editMode) {
lifecycleScope.launch(context = Dispatchers.Main) {
delay(500L)
MainNavigationController.navigate(
ChallengesOverviewFragmentDirections.openChallengeDetail(
challengeId ?: ""
)
)
}
.subscribe(
{
dialog?.dismiss()
savingInProgress = false
finish()
if (!editMode) {
lifecycleScope.launch(context = Dispatchers.Main) {
delay(500L)
MainNavigationController.navigate(ChallengesOverviewFragmentDirections.openChallengeDetail(challengeId ?: ""))
}
}
},
{ throwable ->
dialog?.dismiss()
savingInProgress = false
ExceptionHandler.reportError(throwable)
}
)
)
}
}
} else if (item.itemId == android.R.id.home) {
finish()
return true
@ -229,13 +229,11 @@ class ChallengeFormActivity : BaseActivity() {
openTaskDisabled = false,
taskActionsDisabled = true
).also { challengeTasks = it }
compositeSubscription.add(
challengeTasks.taskOpenEvents.subscribe {
if (it.isValid) {
openNewTaskActivity(it.type, it)
}
challengeTasks.onTaskOpen = {
if (it.isValid) {
openNewTaskActivity(it.type, it)
}
)
}
locationAdapter = GroupArrayAdapter(this)
if (bundle != null) {
@ -339,7 +337,8 @@ class ChallengeFormActivity : BaseActivity() {
locationAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
lifecycleScope.launch(ExceptionHandler.coroutine()) {
val groups = socialRepository.getUserGroups("guild").firstOrNull()?.toMutableList() ?: return@launch
val groups = socialRepository.getUserGroups("guild").firstOrNull()?.toMutableList()
?: return@launch
val partyID = userRepository.getUser().firstOrNull()?.party?.id
val party = if (partyID?.isNotBlank() == true) {
socialRepository.retrieveGroup(partyID)
@ -360,13 +359,20 @@ class ChallengeFormActivity : BaseActivity() {
}
binding.challengeLocationSpinner.adapter = locationAdapter
binding.challengeLocationSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(adapterView: AdapterView<*>, view: View?, i: Int, l: Long) {
checkPrizeAndMinimumForTavern()
}
binding.challengeLocationSpinner.onItemSelectedListener =
object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(
adapterView: AdapterView<*>,
view: View?,
i: Int,
l: Long
) {
checkPrizeAndMinimumForTavern()
}
override fun onNothingSelected(adapterView: AdapterView<*>) { /* no-on */ }
}
override fun onNothingSelected(adapterView: AdapterView<*>) { /* no-on */
}
}
binding.createChallengePrize.setOnKeyListener { _, _, _ ->
checkPrizeAndMinimumForTavern()
@ -380,21 +386,17 @@ class ChallengeFormActivity : BaseActivity() {
taskList.add(addReward)
challengeTasks.setTasks(taskList)
compositeSubscription.add(
challengeTasks.addItemObservable().subscribe(
{ t ->
when (t.text) {
addHabit.text -> openNewTaskActivity(TaskType.HABIT, null)
addDaily.text -> openNewTaskActivity(TaskType.DAILY, null)
addTodo.text -> openNewTaskActivity(TaskType.TODO, null)
addReward.text -> openNewTaskActivity(TaskType.REWARD, null)
}
},
ExceptionHandler.rx()
)
)
challengeTasks.onTaskOpen = { t ->
when (t.text) {
addHabit.text -> openNewTaskActivity(TaskType.HABIT, null)
addDaily.text -> openNewTaskActivity(TaskType.DAILY, null)
addTodo.text -> openNewTaskActivity(TaskType.TODO, null)
addReward.text -> openNewTaskActivity(TaskType.REWARD, null)
}
}
binding.createChallengeTaskList.addOnItemTouchListener(object : androidx.recyclerview.widget.RecyclerView.SimpleOnItemTouchListener() {
binding.createChallengeTaskList.addOnItemTouchListener(object :
androidx.recyclerview.widget.RecyclerView.SimpleOnItemTouchListener() {
override fun onInterceptTouchEvent(
rv: androidx.recyclerview.widget.RecyclerView,
e: MotionEvent
@ -404,13 +406,14 @@ class ChallengeFormActivity : BaseActivity() {
}
})
binding.createChallengeTaskList.adapter = challengeTasks
binding.createChallengeTaskList.layoutManager = androidx.recyclerview.widget.LinearLayoutManager(this)
binding.createChallengeTaskList.layoutManager =
androidx.recyclerview.widget.LinearLayoutManager(this)
}
private fun fillControlsByChallenge() {
challengeId?.let {
challengeRepository.getChallenge(it).subscribe(
{ challenge ->
lifecycleScope.launchCatching {
challengeRepository.getChallenge(it).collect { challenge ->
groupID = challenge.groupId
editMode = true
binding.createChallengeTitle.setText(challenge.name)
@ -428,17 +431,15 @@ class ChallengeFormActivity : BaseActivity() {
}
}
checkPrizeAndMinimumForTavern()
},
ExceptionHandler.rx()
)
challengeRepository.getChallengeTasks(it).subscribe(
{ tasks ->
}
}
lifecycleScope.launchCatching {
challengeRepository.getChallengeTasks(it).collect { tasks ->
tasks.forEach { task ->
addOrUpdateTaskInList(task, true)
}
},
ExceptionHandler.rx()
)
}
}
}
}
@ -460,16 +461,17 @@ class ChallengeFormActivity : BaseActivity() {
newTaskResult.launch(intent)
}
private val newTaskResult = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
if (it.resultCode == Activity.RESULT_OK) {
val task = it.data?.getParcelableExtra<Task>(TaskFormActivity.PARCELABLE_TASK)
if (task != null) {
addOrUpdateTaskInList(task)
private val newTaskResult =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
if (it.resultCode == Activity.RESULT_OK) {
val task = it.data?.getParcelableExtra<Task>(TaskFormActivity.PARCELABLE_TASK)
if (task != null) {
addOrUpdateTaskInList(task)
}
}
}
}
private fun createChallenge(): Flowable<Challenge> {
private suspend fun createChallenge(): Challenge? {
val c = challengeData
val taskList = challengeTasks.taskList
@ -481,7 +483,7 @@ class ChallengeFormActivity : BaseActivity() {
return challengeRepository.createChallenge(c, taskList)
}
private fun updateChallenge(): Flowable<Challenge> {
private suspend fun updateChallenge(): Challenge? {
val c = challengeData
val taskList = challengeTasks.taskList
@ -528,7 +530,8 @@ class ChallengeFormActivity : BaseActivity() {
return editText.text.toString()
}
private class GroupArrayAdapter(context: Context) : ArrayAdapter<Group>(context, android.R.layout.simple_spinner_item) {
private class GroupArrayAdapter(context: Context) :
ArrayAdapter<Group>(context, android.R.layout.simple_spinner_item) {
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
val checkedTextView = super.getView(position, convertView, parent) as? TextView
@ -537,7 +540,8 @@ class ChallengeFormActivity : BaseActivity() {
}
override fun getDropDownView(position: Int, convertView: View?, parent: ViewGroup): View {
val checkedTextView = super.getDropDownView(position, convertView, parent) as? AppCompatCheckedTextView
val checkedTextView =
super.getDropDownView(position, convertView, parent) as? AppCompatCheckedTextView
checkedTextView?.text = getItem(position)?.name
return checkedTextView ?: View(context)
}

View file

@ -24,6 +24,7 @@ import com.habitrpg.android.habitica.extensions.addCancelButton
import com.habitrpg.android.habitica.helpers.ExceptionHandler
import com.habitrpg.android.habitica.helpers.MainNavigationController
import com.habitrpg.android.habitica.helpers.UserStatComputer
import com.habitrpg.android.habitica.helpers.launchCatching
import com.habitrpg.android.habitica.models.Achievement
import com.habitrpg.android.habitica.models.inventory.Equipment
import com.habitrpg.android.habitica.models.members.Member
@ -39,10 +40,10 @@ import com.habitrpg.common.habitica.extensions.loadImage
import com.habitrpg.common.habitica.helpers.RecyclerViewState
import com.habitrpg.common.habitica.helpers.setMarkdown
import com.habitrpg.common.habitica.views.PixelArtView
import io.reactivex.rxjava3.core.Flowable
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.launch
import java.text.SimpleDateFormat
import javax.inject.Inject
@ -54,8 +55,10 @@ class FullProfileActivity : BaseActivity() {
@Inject
lateinit var inventoryRepository: InventoryRepository
@Inject
lateinit var apiClient: ApiClient
@Inject
lateinit var socialRepository: SocialRepository
@ -91,7 +94,12 @@ class FullProfileActivity : BaseActivity() {
}
avatarWithBars = AvatarWithBarsViewModel(this, binding.avatarWithBars)
binding.avatarWithBars.root.setBackgroundColor(ContextCompat.getColor(this, R.color.transparent))
binding.avatarWithBars.root.setBackgroundColor(
ContextCompat.getColor(
this,
R.color.transparent
)
)
binding.avatarWithBars.hpBar.barBackgroundColor = getThemeColor(R.color.window_background)
binding.avatarWithBars.xpBar.barBackgroundColor = getThemeColor(R.color.window_background)
binding.avatarWithBars.mpBar.barBackgroundColor = getThemeColor(R.color.window_background)
@ -100,13 +108,24 @@ class FullProfileActivity : BaseActivity() {
binding.attributesCardView.setOnClickListener { toggleAttributeDetails() }
binding.sendMessageButton.setOnClickListener { showSendMessageToUserDialog() }
binding.giftGemsButton.setOnClickListener { MainNavigationController.navigate(R.id.giftGemsActivity, bundleOf(Pair("userID", userID), Pair("username", null))) }
binding.giftSubscriptionButton.setOnClickListener { MainNavigationController.navigate(R.id.giftSubscriptionActivity, bundleOf(Pair("userID", userID), Pair("username", null))) }
binding.giftGemsButton.setOnClickListener {
MainNavigationController.navigate(
R.id.giftGemsActivity,
bundleOf(Pair("userID", userID), Pair("username", null))
)
}
binding.giftSubscriptionButton.setOnClickListener {
MainNavigationController.navigate(
R.id.giftSubscriptionActivity,
bundleOf(Pair("userID", userID), Pair("username", null))
)
}
lifecycleScope.launch(ExceptionHandler.coroutine()) {
userRepository.getUser()
.collect {
blocks = it?.inbox?.blocks ?: listOf()
binding.blockedDisclaimerView.visibility = if (isUserBlocked()) View.VISIBLE else View.GONE
binding.blockedDisclaimerView.visibility =
if (isUserBlocked()) View.VISIBLE else View.GONE
}
}
}
@ -139,22 +158,26 @@ class FullProfileActivity : BaseActivity() {
true
}
R.id.copy_username -> {
val clipboard = this.getSystemService(Context.CLIPBOARD_SERVICE) as? android.content.ClipboardManager
val clipboard =
this.getSystemService(Context.CLIPBOARD_SERVICE) as? android.content.ClipboardManager
val clip = android.content.ClipData.newPlainText(username, username)
clipboard?.setPrimaryClip(clip)
HabiticaSnackbar.showSnackbar(
this@FullProfileActivity.binding.scrollView.getChildAt(0) as ViewGroup,
String.format(getString(R.string.username_copied), userDisplayName), SnackbarDisplayType.NORMAL
String.format(getString(R.string.username_copied), userDisplayName),
SnackbarDisplayType.NORMAL
)
true
}
R.id.copy_userid -> {
val clipboard = this.getSystemService(Context.CLIPBOARD_SERVICE) as? android.content.ClipboardManager
val clipboard =
this.getSystemService(Context.CLIPBOARD_SERVICE) as? android.content.ClipboardManager
val clip = android.content.ClipData.newPlainText(userID, userID)
clipboard?.setPrimaryClip(clip)
HabiticaSnackbar.showSnackbar(
this@FullProfileActivity.binding.scrollView.getChildAt(0) as ViewGroup,
String.format(getString(R.string.id_copied), userDisplayName), SnackbarDisplayType.NORMAL
String.format(getString(R.string.id_copied), userDisplayName),
SnackbarDisplayType.NORMAL
)
true
}
@ -171,17 +194,11 @@ class FullProfileActivity : BaseActivity() {
}
private fun useBlock() {
compositeSubscription.add(
socialRepository.blockMember(userID).subscribe(
{
lifecycleScope.launch(ExceptionHandler.coroutine()) {
userRepository.retrieveUser()
invalidateOptionsMenu()
}
},
ExceptionHandler.rx()
)
)
lifecycleScope.launchCatching {
socialRepository.blockMember(userID)
userRepository.retrieveUser()
invalidateOptionsMenu()
}
}
private fun showBlockDialog() {
@ -199,7 +216,10 @@ class FullProfileActivity : BaseActivity() {
finish()
MainScope().launch(context = Dispatchers.Main) {
delay(500L)
MainNavigationController.navigate(R.id.inboxMessageListFragment, bundleOf(Pair("username", username), Pair("userID", userID)))
MainNavigationController.navigate(
R.id.inboxMessageListFragment,
bundleOf(Pair("username", username), Pair("userID", userID))
)
}
}
@ -226,22 +246,33 @@ class FullProfileActivity : BaseActivity() {
binding.blurbTextView.movementMethod = LinkMovementMethod.getInstance()
}
user.authentication?.timestamps?.createdAt?.let { binding.joinedView.text = dateFormatter.format(it) }
user.authentication?.timestamps?.lastLoggedIn?.let { binding.lastLoginView.text = dateFormatter.format(it) }
user.authentication?.timestamps?.createdAt?.let {
binding.joinedView.text = dateFormatter.format(it)
}
user.authentication?.timestamps?.lastLoggedIn?.let {
binding.lastLoginView.text = dateFormatter.format(it)
}
binding.totalCheckinsView.text = user.loginIncentives.toString()
avatarWithBars?.updateData(user)
compositeSubscription.add(loadItemDataByOutfit(user.equipped).subscribe({ gear -> this.gotGear(gear, user) }, ExceptionHandler.rx()))
lifecycleScope.launchCatching {
loadItemDataByOutfit(user.equipped).collect { gear -> gotGear(gear, user) }
}
if (user.preferences?.costume == true) {
compositeSubscription.add(loadItemDataByOutfit(user.costume).subscribe({ this.gotCostume(it) }, ExceptionHandler.rx()))
lifecycleScope.launchCatching {
loadItemDataByOutfit(user.costume).collect { gotCostume(it) }
}
} else {
binding.costumeCard.visibility = View.GONE
}
// Load the members achievements now
compositeSubscription.add(socialRepository.getMemberAchievements(this.userID).subscribe({ this.fillAchievements(it) }, ExceptionHandler.rx()))
lifecycleScope.launchCatching {
val achievements = socialRepository.getMemberAchievements(userID)
fillAchievements(achievements)
}
}
private fun updatePetsMountsView(user: Member) {
@ -252,9 +283,9 @@ class FullProfileActivity : BaseActivity() {
if (user.currentMount?.isNotBlank() == true) binding.currentMountDrawee.loadImage("Mount_Icon_" + user.currentMount)
}
// endregion
// endregion
// region Attributes
// region Attributes
private fun fillAchievements(achievements: List<Achievement>?) {
if (achievements == null) {
@ -262,23 +293,36 @@ class FullProfileActivity : BaseActivity() {
}
val items = ArrayList<Any>()
fillAchievements(R.string.basic_achievements, achievements.filter { it.category == "basic" }, items)
fillAchievements(R.string.seasonal_achievements, achievements.filter { it.category == "seasonal" }, items)
fillAchievements(R.string.special_achievements, achievements.filter { it.category == "special" }, items)
fillAchievements(
R.string.basic_achievements,
achievements.filter { it.category == "basic" },
items
)
fillAchievements(
R.string.seasonal_achievements,
achievements.filter { it.category == "seasonal" },
items
)
fillAchievements(
R.string.special_achievements,
achievements.filter { it.category == "special" },
items
)
val adapter = AchievementProfileAdapter()
adapter.setItemList(items)
val layoutManager = androidx.recyclerview.widget.GridLayoutManager(this, 3)
layoutManager.spanSizeLookup = object : androidx.recyclerview.widget.GridLayoutManager.SpanSizeLookup() {
override fun getSpanSize(position: Int): Int {
return if (adapter.getItemViewType(position) == 0) {
layoutManager.spanCount
} else {
1
layoutManager.spanSizeLookup =
object : androidx.recyclerview.widget.GridLayoutManager.SpanSizeLookup() {
override fun getSpanSize(position: Int): Int {
return if (adapter.getItemViewType(position) == 0) {
layoutManager.spanCount
} else {
1
}
}
}
}
binding.achievementGroupList.layoutManager = layoutManager
binding.achievementGroupList.adapter = adapter
@ -292,7 +336,9 @@ class FullProfileActivity : BaseActivity() {
) {
// Order by ID first
val achievementList = ArrayList(achievements)
achievementList.sortWith { achievement, t1 -> achievement.index.toDouble().compareTo(t1.index.toDouble()) }
achievementList.sortWith { achievement, t1 ->
achievement.index.toDouble().compareTo(t1.index.toDouble())
}
targetList.add(getString(labelID))
targetList.addAll(achievementList)
@ -318,8 +364,14 @@ class FullProfileActivity : BaseActivity() {
}
}
private fun addEquipmentRow(table: TableLayout, gearKey: String?, text: String?, stats: String?) {
val gearRow = layoutInflater.inflate(R.layout.profile_gear_tablerow, table, false) as? TableRow
private fun addEquipmentRow(
table: TableLayout,
gearKey: String?,
text: String?,
stats: String?
) {
val gearRow =
layoutInflater.inflate(R.layout.profile_gear_tablerow, table, false) as? TableRow
val draweeView = gearRow?.findViewById<PixelArtView>(R.id.gear_drawee)
@ -348,7 +400,7 @@ class FullProfileActivity : BaseActivity() {
)
}
private fun loadItemDataByOutfit(outfit: Outfit?): Flowable<out List<Equipment>> {
private fun loadItemDataByOutfit(outfit: Outfit?): Flow<List<Equipment>> {
val outfitList = ArrayList<String>()
if (outfit != null) {
outfitList.add(outfit.armor)
@ -381,7 +433,15 @@ class FullProfileActivity : BaseActivity() {
if (row is UserStatComputer.EquipmentRow) {
addEquipmentRow(binding.equipmentTableLayout, row.gearKey, row.text, row.stats)
} else if (row is UserStatComputer.AttributeRow) {
addAttributeRow(getString(row.labelId), row.strVal, row.intVal, row.conVal, row.perVal, row.roundDown, row.summary)
addAttributeRow(
getString(row.labelId),
row.strVal,
row.intVal,
row.conVal,
row.perVal,
row.roundDown,
row.summary
)
}
}
@ -400,7 +460,11 @@ class FullProfileActivity : BaseActivity() {
val buffs = stats.buffs
addAttributeRow(
getString(R.string.profile_allocated), stats.strength?.toFloat() ?: 0f, stats.intelligence?.toFloat() ?: 0f, stats.constitution?.toFloat() ?: 0f, stats.per?.toFloat() ?: 0f,
getString(R.string.profile_allocated),
stats.strength?.toFloat() ?: 0f,
stats.intelligence?.toFloat() ?: 0f,
stats.constitution?.toFloat() ?: 0f,
stats.per?.toFloat() ?: 0f,
roundDown = true,
isSummary = false
)
@ -408,11 +472,23 @@ class FullProfileActivity : BaseActivity() {
getString(R.string.buffs),
buffs?.str
?: 0f,
buffs?._int ?: 0f, buffs?.con ?: 0f, buffs?.per ?: 0f, roundDown = true, isSummary = false
buffs?._int ?: 0f,
buffs?.con ?: 0f,
buffs?.per ?: 0f,
roundDown = true,
isSummary = false
)
// Summary row
addAttributeRow("", attributeStrSum, attributeIntSum, attributeConSum, attributePerSum, roundDown = false, isSummary = true)
addAttributeRow(
"",
attributeStrSum,
attributeIntSum,
attributeConSum,
attributePerSum,
roundDown = false,
isSummary = true
)
}
private fun addAttributeRow(
@ -424,7 +500,11 @@ class FullProfileActivity : BaseActivity() {
roundDown: Boolean,
isSummary: Boolean
) {
val tableRow = layoutInflater.inflate(R.layout.profile_attributetablerow, binding.attributesTableLayout, false) as? TableRow ?: return
val tableRow = layoutInflater.inflate(
R.layout.profile_attributetablerow,
binding.attributesTableLayout,
false
) as? TableRow ?: return
val keyTextView = tableRow.findViewById<TextView>(R.id.tv_attribute_type)
keyTextView?.text = label
@ -475,9 +555,9 @@ class FullProfileActivity : BaseActivity() {
}
}
// endregion
// endregion
// region Navigation
// region Navigation
override fun onSupportNavigateUp(): Boolean {
finish()
@ -488,9 +568,9 @@ class FullProfileActivity : BaseActivity() {
finish()
}
// endregion
// endregion
// region BaseActivity-Overrides
// region BaseActivity-Overrides
override fun getLayoutResId(): Int {
return R.layout.activity_full_profile
@ -517,5 +597,5 @@ class FullProfileActivity : BaseActivity() {
}
}
// endregion
// endregion
}

View file

@ -75,10 +75,9 @@ class HabitButtonWidgetActivity : BaseActivity() {
}
adapter = SkillTasksRecyclerViewAdapter()
adapter?.getTaskSelectionEvents()?.subscribe(
{ task -> taskSelected(task.id) },
ExceptionHandler.rx()
)?.let { compositeSubscription.add(it) }
adapter?.onTaskSelection = {
taskSelected(it.id)
}
binding.recyclerView.adapter = adapter
CoroutineScope(Dispatchers.Main + job).launch(ExceptionHandler.coroutine()) {

View file

@ -42,6 +42,7 @@ import com.habitrpg.android.habitica.extensions.updateStatusBarColor
import com.habitrpg.android.habitica.helpers.AmplitudeManager
import com.habitrpg.android.habitica.helpers.AppConfigManager
import com.habitrpg.android.habitica.helpers.ExceptionHandler
import com.habitrpg.android.habitica.helpers.launchCatching
import com.habitrpg.android.habitica.models.user.User
import com.habitrpg.android.habitica.ui.helpers.dismissKeyboard
import com.habitrpg.android.habitica.ui.viewmodels.AuthenticationViewModel
@ -81,14 +82,15 @@ class LoginActivity : BaseActivity() {
showValidationError(getString(R.string.password_too_short, configManager.minimumPasswordLength()))
return@OnClickListener
}
apiClient.registerUser(username, email, password, confirmPassword)
.subscribe(
{ handleAuthResponse(it) },
{
hideProgress()
ExceptionHandler.reportError(it)
}
)
lifecycleScope.launch(ExceptionHandler.coroutine {
hideProgress()
ExceptionHandler.reportError(it)
}) {
val response = apiClient.registerUser(username, email, password, confirmPassword)
if (response != null) {
handleAuthResponse(response)
}
}
} else {
val username: String = binding.username.text.toString().trim { it <= ' ' }
val password: String = binding.password.text.toString()
@ -97,14 +99,15 @@ class LoginActivity : BaseActivity() {
return@OnClickListener
}
Log.d("LoginActivity", ": $username, $password")
apiClient.connectUser(username, password).subscribe(
{ handleAuthResponse(it) },
{
hideProgress()
ExceptionHandler.reportError(it)
Log.d("LoginActivity", ": ${it.message}", it)
lifecycleScope.launch(ExceptionHandler.coroutine {
hideProgress()
ExceptionHandler.reportError(it)
}) {
val response = apiClient.connectUser(username, password)
if (response != null) {
handleAuthResponse(response)
}
)
}
}
}
@ -450,7 +453,10 @@ class LoginActivity : BaseActivity() {
alertDialog.setMessage(R.string.forgot_password_description)
alertDialog.setAdditionalContentView(input)
alertDialog.addButton(R.string.send, true) { _, _ ->
userRepository.sendPasswordResetEmail(input.text.toString()).subscribe({ showPasswordEmailConfirmation() }, ExceptionHandler.rx())
lifecycleScope.launchCatching {
userRepository.sendPasswordResetEmail(input.text.toString())
showPasswordEmailConfirmation()
}
}
alertDialog.addCancelButton()
alertDialog.show()

View file

@ -22,7 +22,6 @@ import androidx.navigation.NavDestination
import androidx.navigation.findNavController
import androidx.navigation.fragment.NavHostFragment
import com.google.android.gms.wearable.Wearable
import com.google.android.material.composethemeadapter.MdcTheme
import com.google.firebase.perf.FirebasePerformance
import com.habitrpg.android.habitica.BuildConfig
import com.habitrpg.android.habitica.R
@ -33,7 +32,6 @@ import com.habitrpg.android.habitica.data.TaskRepository
import com.habitrpg.android.habitica.databinding.ActivityMainBinding
import com.habitrpg.android.habitica.extensions.hideKeyboard
import com.habitrpg.android.habitica.extensions.observeOnce
import com.habitrpg.android.habitica.extensions.subscribeWithErrorHandler
import com.habitrpg.android.habitica.extensions.updateStatusBarColor
import com.habitrpg.android.habitica.helpers.AmplitudeManager
import com.habitrpg.android.habitica.helpers.AppConfigManager
@ -41,6 +39,7 @@ import com.habitrpg.android.habitica.helpers.ExceptionHandler
import com.habitrpg.android.habitica.helpers.MainNavigationController
import com.habitrpg.android.habitica.helpers.NotificationOpenHandler
import com.habitrpg.android.habitica.helpers.SoundManager
import com.habitrpg.android.habitica.helpers.launchCatching
import com.habitrpg.android.habitica.interactors.CheckClassSelectionUseCase
import com.habitrpg.android.habitica.interactors.DisplayItemDropUseCase
import com.habitrpg.android.habitica.interactors.NotifyUserUseCase
@ -154,11 +153,11 @@ open class MainActivity : BaseActivity(), SnackbarActivity {
viewModel.user.observe(this) {
setUserData(it)
}
compositeSubscription.add(
userRepository.getUserQuestStatus().subscribeWithErrorHandler {
lifecycleScope.launchCatching {
userRepository.getUserQuestStatus().collect {
userQuestStatus = it
}
)
}
val drawerLayout = findViewById<DrawerLayout>(R.id.drawer_layout)
drawerFragment = supportFragmentManager.findFragmentById(R.id.navigation_drawer) as? NavigationDrawerFragment
@ -383,7 +382,7 @@ open class MainActivity : BaseActivity(), SnackbarActivity {
if (user != null) {
val preferences = user.preferences
preferences?.language?.let { apiClient.setLanguageCode(it) }
preferences?.language?.let { apiClient.languageCode = it }
if (preferences?.language != viewModel.preferenceLanguage) {
viewModel.preferenceLanguage = preferences?.language
}
@ -392,11 +391,6 @@ open class MainActivity : BaseActivity(), SnackbarActivity {
displayDeathDialogIfNeeded()
YesterdailyDialog.showDialogIfNeeded(this, user.id, userRepository, taskRepository)
if (user.flags?.verifiedUsername == false && isActivityVisible) {
val intent = Intent(this, VerifyUsernameActivity::class.java)
startActivity(intent)
}
val quest = user.party?.quest
if (quest?.completed?.isNotBlank() == true) {
lifecycleScope.launch(ExceptionHandler.coroutine()) {
@ -447,22 +441,35 @@ open class MainActivity : BaseActivity(), SnackbarActivity {
UserQuestStatus.QUEST_BOSS -> data.questDamage
else -> 0.0
}
compositeSubscription.add(
notifyUserUseCase.observable(
lifecycleScope.launchCatching {
notifyUserUseCase.callInteractor(
NotifyUserUseCase.RequestValues(
this, snackbarContainer,
viewModel.user.value, data.experienceDelta, data.healthDelta, data.goldDelta, data.manaDelta, damageValue, data.hasLeveledUp, data.level
this@MainActivity,
snackbarContainer,
viewModel.user.value,
data.experienceDelta,
data.healthDelta,
data.goldDelta,
data.manaDelta,
damageValue,
data.hasLeveledUp,
data.level
)
)
.subscribe({ }, ExceptionHandler.rx())
)
}
}
val showItemsFound = userQuestStatus == UserQuestStatus.QUEST_COLLECT
compositeSubscription.add(
displayItemDropUseCase.observable(DisplayItemDropUseCase.RequestValues(data, this, snackbarContainer, showItemsFound))
.subscribe({ }, ExceptionHandler.rx())
)
lifecycleScope.launchCatching {
displayItemDropUseCase.callInteractor(
DisplayItemDropUseCase.RequestValues(
data,
this@MainActivity,
snackbarContainer,
showItemsFound
)
)
}
}
private var lastDeathDialogDisplay = 0L

View file

@ -5,15 +5,14 @@ import android.os.Bundle
import android.text.method.LinkMovementMethod
import android.view.View
import androidx.core.net.toUri
import androidx.lifecycle.lifecycleScope
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.api.MaintenanceApiService
import com.habitrpg.android.habitica.components.UserComponent
import com.habitrpg.android.habitica.data.ApiClient
import com.habitrpg.android.habitica.databinding.ActivityMaintenanceBinding
import com.habitrpg.android.habitica.helpers.ExceptionHandler
import com.habitrpg.android.habitica.helpers.launchCatching
import com.habitrpg.common.habitica.helpers.setMarkdown
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.schedulers.Schedulers
import javax.inject.Inject
class MaintenanceActivity : BaseActivity() {
@ -66,19 +65,12 @@ class MaintenanceActivity : BaseActivity() {
override fun onResume() {
super.onResume()
if (!isDeprecationNotice) {
compositeSubscription.add(
this.maintenanceService.maintenanceStatus
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{ maintenanceResponse ->
if (maintenanceResponse.activeMaintenance == false) {
finish()
}
},
ExceptionHandler.rx()
)
)
lifecycleScope.launchCatching {
val maintenanceResponse = maintenanceService.getMaintenanceStatus()
if (maintenanceResponse?.activeMaintenance == false) {
finish()
}
}
}
}

View file

@ -21,6 +21,7 @@ import com.habitrpg.android.habitica.data.SocialRepository
import com.habitrpg.android.habitica.databinding.ActivityNotificationsBinding
import com.habitrpg.android.habitica.extensions.fromHtml
import com.habitrpg.android.habitica.helpers.ExceptionHandler
import com.habitrpg.android.habitica.helpers.launchCatching
import com.habitrpg.android.habitica.models.inventory.QuestContent
import com.habitrpg.android.habitica.ui.viewmodels.NotificationsViewModel
import com.habitrpg.common.habitica.models.Notification
@ -65,15 +66,12 @@ class NotificationsActivity : BaseActivity(), androidx.swiperefreshlayout.widget
inflater = getSystemService(Context.LAYOUT_INFLATER_SERVICE) as? LayoutInflater
compositeSubscription.add(
viewModel.getNotifications().subscribe(
{
this.setNotifications(it)
viewModel.markNotificationsAsSeen(it)
},
ExceptionHandler.rx()
)
)
lifecycleScope.launchCatching {
viewModel.getNotifications().collect {
setNotifications(it)
viewModel.markNotificationsAsSeen(it)
}
}
binding.notificationsRefreshLayout.setOnRefreshListener(this)
}

View file

@ -8,16 +8,18 @@ import android.os.Bundle
import android.view.View
import androidx.annotation.RequiresApi
import androidx.core.content.ContextCompat
import androidx.lifecycle.lifecycleScope
import androidx.navigation.navArgs
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.components.UserComponent
import com.habitrpg.android.habitica.data.SocialRepository
import com.habitrpg.android.habitica.databinding.ActivityReportMessageBinding
import com.habitrpg.common.habitica.extensions.getThemeColor
import com.habitrpg.android.habitica.helpers.ExceptionHandler
import com.habitrpg.android.habitica.ui.helpers.dismissKeyboard
import com.habitrpg.common.habitica.extensions.getThemeColor
import com.habitrpg.common.habitica.helpers.setMarkdown
import kotlinx.coroutines.launch
import javax.inject.Inject
class ReportMessageActivity : BaseActivity() {
@ -94,12 +96,11 @@ class ReportMessageActivity : BaseActivity() {
}
isReporting = true
messageID?.let {
socialRepository.flagMessage(it, binding.additionalInfoEdittext.text.toString(), groupID)
.doOnError { isReporting = false }
.subscribe(
{ finish() },
ExceptionHandler.rx()
)
lifecycleScope.launch(ExceptionHandler.coroutine {
isReporting = false
}) {
finish()
}
}
}

View file

@ -25,6 +25,7 @@ import com.habitrpg.android.habitica.data.TaskRepository
import com.habitrpg.android.habitica.databinding.ActivitySetupBinding
import com.habitrpg.android.habitica.helpers.AmplitudeManager
import com.habitrpg.android.habitica.helpers.ExceptionHandler
import com.habitrpg.android.habitica.helpers.launchCatching
import com.habitrpg.android.habitica.models.user.User
import com.habitrpg.android.habitica.ui.fragments.setup.AvatarSetupFragment
import com.habitrpg.android.habitica.ui.fragments.setup.TaskSetupFragment
@ -84,10 +85,9 @@ class SetupActivity : BaseActivity(), ViewPager.OnPageChangeListener {
val currentDeviceLanguage = Locale.getDefault().language
for (language in resources.getStringArray(R.array.LanguageValues)) {
if (language == currentDeviceLanguage) {
compositeSubscription.add(
lifecycleScope.launchCatching {
apiClient.registrationLanguage(currentDeviceLanguage)
.subscribe({ }, ExceptionHandler.rx())
)
}
}
}
@ -141,7 +141,9 @@ class SetupActivity : BaseActivity(), ViewPager.OnPageChangeListener {
this.completedSetup = true
createdTasks = true
newTasks?.let {
this.taskRepository.createTasks(it).subscribe({ onUserReceived(user) }, ExceptionHandler.rx())
lifecycleScope.launchCatching {
taskRepository.createTasks(it)
}
}
} else if (binding.viewPager.currentItem == 0) {

View file

@ -11,6 +11,7 @@ import com.habitrpg.android.habitica.components.UserComponent
import com.habitrpg.android.habitica.data.SocialRepository
import com.habitrpg.android.habitica.databinding.ActivitySkillMembersBinding
import com.habitrpg.android.habitica.helpers.ExceptionHandler
import com.habitrpg.android.habitica.helpers.launchCatching
import com.habitrpg.android.habitica.ui.adapter.social.PartyMemberRecyclerViewAdapter
import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel
import kotlinx.coroutines.flow.filterNotNull
@ -50,15 +51,14 @@ class SkillMemberActivity : BaseActivity() {
private fun loadMemberList() {
binding.recyclerView.layoutManager = LinearLayoutManager(this)
viewAdapter = PartyMemberRecyclerViewAdapter()
viewAdapter?.getUserClickedEvents()?.subscribe(
{ userId ->
viewAdapter?.onUserClicked = {
lifecycleScope.launchCatching {
val resultIntent = Intent()
resultIntent.putExtra("member_id", userId)
resultIntent.putExtra("member_id", it)
setResult(Activity.RESULT_OK, resultIntent)
finish()
},
ExceptionHandler.rx()
)?.let { compositeSubscription.add(it) }
}
}
binding.recyclerView.adapter = viewAdapter
lifecycleScope.launch(ExceptionHandler.coroutine()) {

View file

@ -13,11 +13,10 @@ import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.components.UserComponent
import com.habitrpg.android.habitica.data.TaskRepository
import com.habitrpg.android.habitica.databinding.ActivitySkillTasksBinding
import com.habitrpg.android.habitica.helpers.ExceptionHandler
import com.habitrpg.android.habitica.models.tasks.Task
import com.habitrpg.shared.habitica.models.tasks.TaskType
import com.habitrpg.android.habitica.modules.AppModule
import com.habitrpg.android.habitica.ui.fragments.skills.SkillTasksRecyclerViewFragment
import com.habitrpg.shared.habitica.models.tasks.TaskType
import javax.inject.Inject
import javax.inject.Named
@ -60,7 +59,9 @@ class SkillTasksActivity : BaseActivity() {
1 -> TaskType.DAILY
else -> TaskType.TODO
}
compositeSubscription.add(fragment.getTaskSelectionEvents().subscribe({ task -> taskSelected(task) }, ExceptionHandler.rx()))
fragment.onTaskSelection = {
taskSelected(it)
}
viewFragmentsDictionary.put(position, fragment)
return fragment
}

View file

@ -6,6 +6,7 @@ import android.content.SharedPreferences
import android.content.res.ColorStateList
import android.graphics.Typeface
import android.graphics.drawable.ColorDrawable
import android.os.Build
import android.os.Bundle
import android.os.Handler
import android.view.Menu
@ -33,6 +34,7 @@ import com.habitrpg.android.habitica.extensions.OnChangeTextWatcher
import com.habitrpg.android.habitica.extensions.addCancelButton
import com.habitrpg.android.habitica.helpers.ExceptionHandler
import com.habitrpg.android.habitica.helpers.TaskAlarmManager
import com.habitrpg.android.habitica.helpers.launchCatching
import com.habitrpg.android.habitica.models.Tag
import com.habitrpg.android.habitica.models.social.Challenge
import com.habitrpg.android.habitica.models.tasks.Task
@ -47,6 +49,7 @@ import com.habitrpg.shared.habitica.models.tasks.HabitResetOption
import com.habitrpg.shared.habitica.models.tasks.TaskType
import io.realm.RealmList
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import java.util.Date
import javax.inject.Inject
@ -56,16 +59,22 @@ class TaskFormActivity : BaseActivity() {
private lateinit var binding: ActivityTaskFormBinding
private var userScrolled: Boolean = false
private var isSaving: Boolean = false
@Inject
lateinit var taskRepository: TaskRepository
@Inject
lateinit var tagRepository: TagRepository
@Inject
lateinit var taskAlarmManager: TaskAlarmManager
@Inject
lateinit var challengeRepository: ChallengeRepository
@Inject
lateinit var sharedPreferences: SharedPreferences
@Inject
lateinit var userViewModel: MainUserViewModel
@ -135,18 +144,23 @@ class TaskFormActivity : BaseActivity() {
super.onCreate(savedInstanceState)
if (forcedTheme == "yellow") {
binding.taskDifficultyButtons.textTintColor = ContextCompat.getColor(this, R.color.text_yellow)
binding.habitScoringButtons.textTintColor = ContextCompat.getColor(this, R.color.text_yellow)
binding.taskDifficultyButtons.textTintColor =
ContextCompat.getColor(this, R.color.text_yellow)
binding.habitScoringButtons.textTintColor =
ContextCompat.getColor(this, R.color.text_yellow)
} else if (forcedTheme == "taskform") {
binding.taskDifficultyButtons.textTintColor = ContextCompat.getColor(this, R.color.text_brand_neon)
binding.habitScoringButtons.textTintColor = ContextCompat.getColor(this, R.color.text_brand_neon)
binding.taskDifficultyButtons.textTintColor =
ContextCompat.getColor(this, R.color.text_brand_neon)
binding.habitScoringButtons.textTintColor =
ContextCompat.getColor(this, R.color.text_brand_neon)
}
setSupportActionBar(binding.toolbar)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
supportActionBar?.setDisplayShowHomeEnabled(true)
tintColor = getThemeColor(R.attr.taskFormTint)
val upperTintColor = if (forcedTheme == "taskform") getThemeColor(R.attr.taskFormTint) else getThemeColor(R.attr.colorAccent)
val upperTintColor =
if (forcedTheme == "taskform") getThemeColor(R.attr.taskFormTint) else getThemeColor(R.attr.colorAccent)
supportActionBar?.setBackgroundDrawable(ColorDrawable(upperTintColor))
binding.upperTextWrapper.setBackgroundColor(upperTintColor)
@ -155,19 +169,17 @@ class TaskFormActivity : BaseActivity() {
taskType = TaskType.from(bundle.getString(TASK_TYPE_KEY)) ?: TaskType.HABIT
preselectedTags = bundle.getStringArrayList(SELECTED_TAGS_KEY)
compositeSubscription.add(
lifecycleScope.launchCatching {
tagRepository.getTags()
.map { tagRepository.getUnmanagedCopy(it) }
.subscribe(
{
tags = it
setTagViews()
},
ExceptionHandler.rx()
)
)
.collect {
tags = it
setTagViews()
}
}
userViewModel.user.observe(this) {
usesTaskAttributeStats = it?.preferences?.allocationMode == "taskbased" && it.preferences?.automaticAllocation == true
usesTaskAttributeStats =
it?.preferences?.allocationMode == "taskbased" && it.preferences?.automaticAllocation == true
configureForm()
}
@ -188,7 +200,8 @@ class TaskFormActivity : BaseActivity() {
binding.statConstitutionButton.setOnClickListener { selectedStat = Attribute.CONSTITUTION }
binding.statPerceptionButton.setOnClickListener { selectedStat = Attribute.PERCEPTION }
binding.scrollView.setOnTouchListener { view, event ->
userScrolled = view == binding.scrollView && (event.action == MotionEvent.ACTION_SCROLL || event.action == MotionEvent.ACTION_MOVE)
userScrolled =
view == binding.scrollView && (event.action == MotionEvent.ACTION_SCROLL || event.action == MotionEvent.ACTION_MOVE)
return@setOnTouchListener false
}
binding.scrollView.setOnScrollChangeListener { _: NestedScrollView?, _: Int, _: Int, _: Int, _: Int ->
@ -202,31 +215,33 @@ class TaskFormActivity : BaseActivity() {
taskId != null -> {
isCreating = false
lifecycleScope.launch(ExceptionHandler.coroutine()) {
val task = taskRepository.getUnmanagedTask(taskId).firstOrNull() ?: return@launch
val task =
taskRepository.getUnmanagedTask(taskId).firstOrNull() ?: return@launch
if (!task.isValid) return@launch
this@TaskFormActivity.task = task
initialTaskInstance = task
// tintColor = ContextCompat.getColor(this, it.mediumTaskColor)
fillForm(task)
task.challengeID?.let { challengeID ->
compositeSubscription.add(
challengeRepository.retrieveChallenge(challengeID)
.subscribe(
{ challenge ->
this@TaskFormActivity.challenge = challenge
binding.challengeNameView.text = getString(R.string.challenge_task_name, challenge.name)
binding.challengeNameView.visibility = View.VISIBLE
disableEditingForUneditableFieldsInChallengeTask()
},
ExceptionHandler.rx()
)
)
lifecycleScope.launchCatching {
val challenge = challengeRepository.retrieveChallenge(challengeID)
?: return@launchCatching
this@TaskFormActivity.challenge = challenge
binding.challengeNameView.text =
getString(R.string.challenge_task_name, challenge.name)
binding.challengeNameView.visibility = View.VISIBLE
disableEditingForUneditableFieldsInChallengeTask()
}
}
}
}
bundle.containsKey(PARCELABLE_TASK) -> {
isCreating = false
task = bundle.getParcelable(PARCELABLE_TASK, Task::class.java)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
task = bundle.getParcelable(PARCELABLE_TASK, Task::class.java)
} else {
task = bundle.getParcelable(PARCELABLE_TASK)
}
task?.let { fillForm(it) }
}
else -> {
@ -249,7 +264,8 @@ class TaskFormActivity : BaseActivity() {
override fun loadTheme(sharedPreferences: SharedPreferences, forced: Boolean) {
super.loadTheme(sharedPreferences, forced)
val upperTintColor = if (forcedTheme == "taskform") getThemeColor(R.attr.taskFormTint) else getThemeColor(R.attr.colorAccent)
val upperTintColor =
if (forcedTheme == "taskform") getThemeColor(R.attr.taskFormTint) else getThemeColor(R.attr.colorAccent)
window.statusBarColor = upperTintColor
}
@ -275,8 +291,8 @@ class TaskFormActivity : BaseActivity() {
alert.dismiss()
}
alert.setOnDismissListener {
isDiscardCancelled = true
}
isDiscardCancelled = true
}
alert.show()
} else {
super.onBackPressed()
@ -315,13 +331,15 @@ class TaskFormActivity : BaseActivity() {
binding.habitScoringButtons.visibility = habitViewsVisibility
binding.habitResetStreakTitleView.visibility = habitViewsVisibility
binding.habitResetStreakButtons.visibility = habitViewsVisibility
(binding.habitAdjustNegativeStreakView.parent as ViewGroup).visibility = habitViewsVisibility
(binding.habitAdjustNegativeStreakView.parent as ViewGroup).visibility =
habitViewsVisibility
if (taskType == TaskType.HABIT) {
binding.habitScoringButtons.isPositive = true
binding.habitScoringButtons.isNegative = false
}
val habitDailyVisibility = if (taskType == TaskType.DAILY || taskType == TaskType.HABIT) View.VISIBLE else View.GONE
val habitDailyVisibility =
if (taskType == TaskType.DAILY || taskType == TaskType.HABIT) View.VISIBLE else View.GONE
binding.adjustStreakTitleView.visibility = habitDailyVisibility
binding.adjustStreakWrapper.visibility = habitDailyVisibility
if (taskType == TaskType.HABIT) {
@ -332,13 +350,18 @@ class TaskFormActivity : BaseActivity() {
binding.adjustStreakTitleView.text = getString(R.string.adjust_streak)
}
val todoDailyViewsVisibility = if (taskType == TaskType.DAILY || taskType == TaskType.TODO) View.VISIBLE else View.GONE
val todoDailyViewsVisibility =
if (taskType == TaskType.DAILY || taskType == TaskType.TODO) View.VISIBLE else View.GONE
binding.checklistTitleView.visibility = if (isChallengeTask) View.GONE else todoDailyViewsVisibility
binding.checklistContainer.visibility = if (isChallengeTask) View.GONE else todoDailyViewsVisibility
binding.checklistTitleView.visibility =
if (isChallengeTask) View.GONE else todoDailyViewsVisibility
binding.checklistContainer.visibility =
if (isChallengeTask) View.GONE else todoDailyViewsVisibility
binding.remindersTitleView.visibility = if (isChallengeTask) View.GONE else todoDailyViewsVisibility
binding.remindersContainer.visibility = if (isChallengeTask) View.GONE else todoDailyViewsVisibility
binding.remindersTitleView.visibility =
if (isChallengeTask) View.GONE else todoDailyViewsVisibility
binding.remindersContainer.visibility =
if (isChallengeTask) View.GONE else todoDailyViewsVisibility
binding.remindersContainer.taskType = taskType
binding.remindersContainer.firstDayOfWeek = firstDayOfWeek
@ -405,12 +428,15 @@ class TaskFormActivity : BaseActivity() {
binding.habitScoringButtons.isPositive = task.up ?: false
binding.habitScoringButtons.isNegative = task.down ?: false
task.frequency?.let {
binding.habitResetStreakButtons.selectedResetOption = HabitResetOption.from(it) ?: HabitResetOption.DAILY
binding.habitResetStreakButtons.selectedResetOption =
HabitResetOption.from(it) ?: HabitResetOption.DAILY
}
binding.habitAdjustPositiveStreakView.setText((task.counterUp ?: 0).toString())
binding.habitAdjustNegativeStreakView.setText((task.counterDown ?: 0).toString())
(binding.habitAdjustPositiveStreakView.parent as ViewGroup).visibility = if (task.up == true) View.VISIBLE else View.GONE
(binding.habitAdjustNegativeStreakView.parent as ViewGroup).visibility = if (task.down == true) View.VISIBLE else View.GONE
(binding.habitAdjustPositiveStreakView.parent as ViewGroup).visibility =
if (task.up == true) View.VISIBLE else View.GONE
(binding.habitAdjustNegativeStreakView.parent as ViewGroup).visibility =
if (task.down == true) View.VISIBLE else View.GONE
if (task.up != true && task.down != true) {
binding.adjustStreakTitleView.visibility = View.GONE
binding.adjustStreakWrapper.visibility = View.GONE
@ -440,13 +466,24 @@ class TaskFormActivity : BaseActivity() {
private fun setSelectedAttribute(attributeName: Attribute) {
if (!usesTaskAttributeStats) return
configureStatsButton(binding.statStrengthButton, attributeName == Attribute.STRENGTH)
configureStatsButton(binding.statIntelligenceButton, attributeName == Attribute.INTELLIGENCE)
configureStatsButton(binding.statConstitutionButton, attributeName == Attribute.CONSTITUTION)
configureStatsButton(
binding.statIntelligenceButton,
attributeName == Attribute.INTELLIGENCE
)
configureStatsButton(
binding.statConstitutionButton,
attributeName == Attribute.CONSTITUTION
)
configureStatsButton(binding.statPerceptionButton, attributeName == Attribute.PERCEPTION)
}
private fun configureStatsButton(button: TextView, isSelected: Boolean) {
button.background.setTint(if (isSelected) tintColor else ContextCompat.getColor(this, R.color.taskform_gray))
button.background.setTint(
if (isSelected) tintColor else ContextCompat.getColor(
this,
R.color.taskform_gray
)
)
val textColorID = if (isSelected) R.color.window_background else R.color.text_secondary
button.setTextColor(ContextCompat.getColor(this, textColorID))
if (isSelected) {
@ -578,7 +615,11 @@ class TaskFormActivity : BaseActivity() {
alert.setTitle(R.string.are_you_sure)
alert.addButton(R.string.delete_task, true) { _, _ ->
if (task?.isValid != true) return@addButton
task?.id?.let { taskRepository.deleteTask(it).subscribe({ }, ExceptionHandler.rx()) }
task?.id?.let {
lifecycleScope.launchCatching {
taskRepository.deleteTask(it)
}
}
finish()
}
alert.addCancelButton()
@ -586,52 +627,46 @@ class TaskFormActivity : BaseActivity() {
}
private fun showChallengeDeleteTask() {
compositeSubscription.add(
taskRepository.getTasksForChallenge(task?.challengeID).firstElement().subscribe(
{ tasks ->
val taskCount = tasks.size
val alert = HabiticaAlertDialog(this)
alert.setTitle(getString(R.string.delete_challenge_task_title))
alert.setMessage(getString(R.string.delete_challenge_task_description, taskCount, challenge?.name ?: ""))
alert.addButton(R.string.leave_delete_task, isPrimary = true, isDestructive = true) { _, _ ->
challenge?.let {
compositeSubscription.add(
challengeRepository.leaveChallenge(it, "keep-all")
.flatMap { taskRepository.deleteTask(task?.id ?: "") }
.subscribe(
{
lifecycleScope.launch(ExceptionHandler.coroutine()) {
userRepository.retrieveUser(true, true)
}
finish()
},
ExceptionHandler.rx()
)
)
}
}
alert.addButton(getString(R.string.leave_delete_x_tasks, taskCount), isPrimary = false, isDestructive = true) { _, _ ->
challenge?.let {
compositeSubscription.add(
challengeRepository.leaveChallenge(it, "remove-all")
.subscribe(
{
lifecycleScope.launch(ExceptionHandler.coroutine()) {
userRepository.retrieveUser(true, true)
}
finish()
},
ExceptionHandler.rx()
)
)
}
}
alert.setExtraCloseButtonVisibility(View.VISIBLE)
alert.show()
},
ExceptionHandler.rx()
lifecycleScope.launchCatching {
val tasks = taskRepository.getTasksForChallenge(task?.challengeID).firstOrNull() ?: return@launchCatching
val taskCount = tasks.size
val alert = HabiticaAlertDialog(this@TaskFormActivity)
alert.setTitle(getString(R.string.delete_challenge_task_title))
alert.setMessage(
getString(
R.string.delete_challenge_task_description,
taskCount,
challenge?.name ?: ""
)
)
)
alert.addButton(
R.string.leave_delete_task,
isPrimary = true,
isDestructive = true
) { _, _ ->
challenge?.let {
lifecycleScope.launchCatching {
challengeRepository.leaveChallenge(it, "keep-all")
taskRepository.deleteTask(task?.id ?: "")
userRepository.retrieveUser(true, true)
}
}
}
alert.addButton(
getString(R.string.leave_delete_x_tasks, taskCount),
isPrimary = false,
isDestructive = true
) { _, _ ->
challenge?.let {
lifecycleScope.launchCatching {
challengeRepository.leaveChallenge(it, "remove-all")
userRepository.retrieveUser(true, true)
}
}
}
alert.setExtraCloseButtonVisibility(View.VISIBLE)
alert.show()
}
}
private fun showBrokenChallengeDialog() {
@ -639,43 +674,40 @@ class TaskFormActivity : BaseActivity() {
if (!task.isValid) {
return
}
compositeSubscription.add(
taskRepository.getTasksForChallenge(task.challengeID).subscribe(
{ tasks ->
lifecycleScope.launchCatching {
val tasks = taskRepository.getTasksForChallenge(task.challengeID).firstOrNull() ?: return@launchCatching
val taskCount = tasks.size
val dialog = HabiticaAlertDialog(this)
val dialog = HabiticaAlertDialog(this@TaskFormActivity)
dialog.setTitle(R.string.broken_challenge)
dialog.setMessage(this.getString(R.string.broken_challenge_description, taskCount))
dialog.addButton(this.getString(R.string.keep_x_tasks, taskCount), true) { _, _ ->
taskRepository.unlinkAllTasks(task.challengeID, "keep-all")
.subscribe(
{
lifecycleScope.launch(ExceptionHandler.coroutine()) {
userRepository.retrieveUser(true, true)
}
finish()
},
ExceptionHandler.rx()
dialog.setMessage(
getString(
R.string.broken_challenge_description,
taskCount
)
)
dialog.addButton(
getString(R.string.keep_x_tasks, taskCount),
true
) { _, _ ->
lifecycleScope.launchCatching {
taskRepository.unlinkAllTasks(task.challengeID, "keep-all")
userRepository.retrieveUser(true, true)
}
}
dialog.addButton(this.getString(R.string.delete_x_tasks, taskCount), false, true) { _, _ ->
dialog.addButton(
getString(R.string.delete_x_tasks, taskCount),
false,
true
) { _, _ ->
lifecycleScope.launchCatching {
taskRepository.unlinkAllTasks(task.challengeID, "remove-all")
.subscribe(
{
lifecycleScope.launch(ExceptionHandler.coroutine()) {
userRepository.retrieveUser(true, true)
}
finish()
},
ExceptionHandler.rx()
)
userRepository.retrieveUser(true, true)
}
}
dialog.setExtraCloseButtonVisibility(View.VISIBLE)
dialog.show()
},
ExceptionHandler.rx()
)
)
}
}
private fun disableEditingForUneditableFieldsInChallengeTask() {

View file

@ -1,135 +0,0 @@
package com.habitrpg.android.habitica.ui.activities
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.Drawable
import android.os.Bundle
import android.text.method.LinkMovementMethod
import android.view.View
import androidx.core.content.ContextCompat
import androidx.lifecycle.lifecycleScope
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.components.UserComponent
import com.habitrpg.android.habitica.databinding.ActivityVerifyUsernameBinding
import com.habitrpg.android.habitica.extensions.OnChangeTextWatcher
import com.habitrpg.android.habitica.extensions.runDelayed
import com.habitrpg.android.habitica.helpers.ExceptionHandler
import com.habitrpg.android.habitica.ui.views.HabiticaIconsHelper
import com.habitrpg.android.habitica.ui.views.HabiticaSnackbar
import io.reactivex.rxjava3.core.BackpressureStrategy
import io.reactivex.rxjava3.core.Flowable
import io.reactivex.rxjava3.subjects.PublishSubject
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.launch
import java.util.concurrent.TimeUnit
class VerifyUsernameActivity : BaseActivity() {
private lateinit var binding: ActivityVerifyUsernameBinding
private val displayNameVerificationEvents = PublishSubject.create<String>()
private val usernameVerificationEvents = PublishSubject.create<String>()
private val checkmarkIcon: Drawable by lazy {
BitmapDrawable(resources, HabiticaIconsHelper.imageOfCheckmark(ContextCompat.getColor(this, R.color.text_green), 1f))
}
private val alertIcon: Drawable by lazy {
BitmapDrawable(resources, HabiticaIconsHelper.imageOfAlertIcon())
}
override fun getLayoutResId(): Int {
return R.layout.activity_verify_username
}
override fun getContentView(layoutResId: Int?): View {
binding = ActivityVerifyUsernameBinding.inflate(layoutInflater)
return binding.root
}
override fun injectActivity(component: UserComponent?) {
component?.inject(this)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding.wikiTextView.movementMethod = LinkMovementMethod.getInstance()
binding.footerTextView.movementMethod = LinkMovementMethod.getInstance()
binding.confirmUsernameButton.setOnClickListener { confirmNames() }
binding.displayNameEditText.addTextChangedListener(
OnChangeTextWatcher { p0, _, _, _ ->
displayNameVerificationEvents.onNext(p0.toString())
}
)
binding.usernameEditText.addTextChangedListener(
OnChangeTextWatcher { p0, _, _, _ ->
usernameVerificationEvents.onNext(p0.toString())
}
)
compositeSubscription.add(
Flowable.combineLatest(
displayNameVerificationEvents.toFlowable(BackpressureStrategy.DROP)
.map { it.length in 1..30 }
.doOnNext {
if (it) {
binding.displayNameEditText.setCompoundDrawablesWithIntrinsicBounds(null, null, checkmarkIcon, null)
binding.issuesTextView.visibility = View.GONE
} else {
binding.displayNameEditText.setCompoundDrawablesWithIntrinsicBounds(null, null, alertIcon, null)
binding.issuesTextView.visibility = View.VISIBLE
binding.issuesTextView.text = getString(R.string.display_name_length_error)
}
},
usernameVerificationEvents.toFlowable(BackpressureStrategy.DROP)
.throttleLast(1, TimeUnit.SECONDS)
.flatMap { userRepository.verifyUsername(binding.usernameEditText.text.toString()) }
.doOnNext {
if (it.isUsable) {
binding.usernameEditText.setCompoundDrawablesWithIntrinsicBounds(null, null, checkmarkIcon, null)
binding.issuesTextView.visibility = View.GONE
} else {
binding.usernameEditText.setCompoundDrawablesWithIntrinsicBounds(null, null, alertIcon, null)
binding.issuesTextView.visibility = View.VISIBLE
binding.issuesTextView.text = it.issues.joinToString("\n")
}
}
) { displayNameUsable, usernameUsable -> displayNameUsable && usernameUsable.isUsable }
.subscribe(
{
binding.confirmUsernameButton.isEnabled = it
},
ExceptionHandler.rx()
)
)
lifecycleScope.launch(ExceptionHandler.coroutine()) {
val user = userRepository.getUser().firstOrNull()
binding.displayNameEditText.setText(user?.profile?.name)
displayNameVerificationEvents.onNext(user?.profile?.name ?: "")
binding.usernameEditText.setText(user?.authentication?.localAuthentication?.username)
usernameVerificationEvents.onNext(user?.username ?: "")
}
}
private fun confirmNames() {
binding.confirmUsernameButton.isClickable = false
lifecycleScope.launch(ExceptionHandler.coroutine()) {
userRepository.updateUser("profile.name", binding.displayNameEditText.text.toString())
userRepository.updateLoginName(binding.usernameEditText.text.toString())
showConfirmationAndFinish()
binding.confirmUsernameButton.isClickable = true
}
}
private fun showConfirmationAndFinish() {
HabiticaSnackbar.showSnackbar(binding.snackbarView, getString(R.string.username_confirmed), HabiticaSnackbar.SnackbarDisplayType.SUCCESS)
runDelayed(3, TimeUnit.SECONDS) {
finish()
}
}
override fun onBackPressed() {
moveTaskToBack(true)
}
}

View file

@ -8,14 +8,11 @@ import androidx.recyclerview.widget.RecyclerView
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.databinding.SkillTaskItemCardBinding
import com.habitrpg.android.habitica.models.tasks.Task
import io.reactivex.rxjava3.core.BackpressureStrategy
import io.reactivex.rxjava3.core.Flowable
import io.reactivex.rxjava3.subjects.PublishSubject
import java.util.UUID
class SkillTasksRecyclerViewAdapter : BaseRecyclerViewAdapter<Task, SkillTasksRecyclerViewAdapter.TaskViewHolder>() {
private val taskSelectionEvents = PublishSubject.create<Task>()
var onTaskSelection: ((Task) -> Unit)? = null
override fun getItemId(position: Int): Long {
val task = getItem(position)
@ -35,10 +32,6 @@ class SkillTasksRecyclerViewAdapter : BaseRecyclerViewAdapter<Task, SkillTasksRe
holder.bindHolder(data[position])
}
fun getTaskSelectionEvents(): Flowable<Task> {
return taskSelectionEvents.toFlowable(BackpressureStrategy.DROP)
}
inner class TaskViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView), View.OnClickListener {
private val binding = SkillTaskItemCardBinding.bind(itemView)
var task: Task? = null
@ -63,7 +56,7 @@ class SkillTasksRecyclerViewAdapter : BaseRecyclerViewAdapter<Task, SkillTasksRe
override fun onClick(v: View) {
if (v == itemView) {
task?.let {
taskSelectionEvents.onNext(it)
onTaskSelection?.invoke(it)
}
}
}

View file

@ -12,9 +12,8 @@ import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.databinding.GearListItemBinding
import com.habitrpg.android.habitica.models.inventory.Equipment
import com.habitrpg.android.habitica.ui.adapter.BaseRecyclerViewAdapter
import com.habitrpg.common.habitica.extensions.loadImage
import com.habitrpg.android.habitica.ui.views.HabiticaIconsHelper
import io.reactivex.rxjava3.subjects.PublishSubject
import com.habitrpg.common.habitica.extensions.loadImage
class EquipmentRecyclerViewAdapter : BaseRecyclerViewAdapter<Equipment, EquipmentRecyclerViewAdapter.GearViewHolder>() {
@ -22,7 +21,7 @@ class EquipmentRecyclerViewAdapter : BaseRecyclerViewAdapter<Equipment, Equipmen
var isCostume: Boolean? = null
var type: String? = null
val equipEvents: PublishSubject<String> = PublishSubject.create()
var onEquip: ((String) -> Unit)? = null
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): GearViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.gear_list_item, parent, false)
@ -45,7 +44,7 @@ class EquipmentRecyclerViewAdapter : BaseRecyclerViewAdapter<Equipment, Equipmen
itemView.setOnClickListener {
val key = gear?.key
if (key != null) {
equipEvents.onNext(key)
onEquip?.invoke(key)
equippedGear = if (key == equippedGear) {
type + "_base_0"
} else {

View file

@ -24,9 +24,6 @@ import com.habitrpg.android.habitica.ui.menu.BottomSheetMenuItem
import com.habitrpg.android.habitica.ui.views.dialogs.DetailDialog
import com.habitrpg.common.habitica.extensions.layoutInflater
import com.habitrpg.common.habitica.extensions.loadImage
import io.reactivex.rxjava3.core.BackpressureStrategy
import io.reactivex.rxjava3.core.Flowable
import io.reactivex.rxjava3.subjects.PublishSubject
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
@ -46,31 +43,14 @@ class ItemRecyclerAdapter(val context: Context) : BaseRecyclerViewAdapter<OwnedI
notifyDataSetChanged()
}
private val sellItemEvents = PublishSubject.create<OwnedItem>()
private val questInvitationEvents = PublishSubject.create<QuestContent>()
private val openMysteryItemEvents = PublishSubject.create<Item>()
private val startHatchingSubject = PublishSubject.create<Item>()
private val hatchPetSubject = PublishSubject.create<Pair<HatchingPotion, Egg>>()
private val feedPetSubject = PublishSubject.create<Food>()
private val createNewPartySubject = PublishSubject.create<Boolean>()
private val useSpecialSubject = PublishSubject.create<SpecialItem>()
fun getSellItemFlowable(): Flowable<OwnedItem> {
return sellItemEvents.toFlowable(BackpressureStrategy.DROP)
}
fun getQuestInvitationFlowable(): Flowable<QuestContent> {
return questInvitationEvents.toFlowable(BackpressureStrategy.DROP)
}
fun getOpenMysteryItemFlowable(): Flowable<Item> {
return openMysteryItemEvents.toFlowable(BackpressureStrategy.DROP)
}
val startHatchingEvents: Flowable<Item> = startHatchingSubject.toFlowable(BackpressureStrategy.DROP)
val hatchPetEvents: Flowable<Pair<HatchingPotion, Egg>> = hatchPetSubject.toFlowable(BackpressureStrategy.DROP)
val feedPetEvents: Flowable<Food> = feedPetSubject.toFlowable(BackpressureStrategy.DROP)
val startNewPartyEvents: Flowable<Boolean> = createNewPartySubject.toFlowable(BackpressureStrategy.DROP)
val useSpecialEvents: Flowable<SpecialItem> = useSpecialSubject.toFlowable(BackpressureStrategy.DROP)
var onSellItem: ((OwnedItem) -> Unit)? = null
var onQuestInvitation: ((QuestContent) -> Unit)? = null
var onOpenMysteryItem: ((Item) -> Unit)? = null
var onStartHatching: ((Item) -> Unit)? = null
var onHatchPet: ((HatchingPotion, Egg) -> Unit)? = null
var onFeedPet: ((Food) -> Unit)? = null
var onCreateNewParty: (() -> Unit)? = null
var onUseSpecialItem: ((SpecialItem) -> Unit)? = null
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemViewHolder {
return ItemViewHolder(ItemItemBinding.inflate(context.layoutInflater, parent, false))
@ -188,12 +168,12 @@ class ItemRecyclerAdapter(val context: Context) : BaseRecyclerViewAdapter<OwnedI
menu.setSelectionRunnable { index ->
item?.let { selectedItem ->
if (!(selectedItem is QuestContent || selectedItem is SpecialItem) && index == 0) {
ownedItem?.let { selectedOwnedItem -> sellItemEvents.onNext(selectedOwnedItem) }
ownedItem?.let { selectedOwnedItem -> onSellItem?.invoke(selectedOwnedItem) }
return@let
}
when (selectedItem) {
is Egg -> item?.let { startHatchingSubject.onNext(it) }
is HatchingPotion -> startHatchingSubject.onNext(selectedItem)
is Egg -> item?.let { onStartHatching?.invoke(it) }
is HatchingPotion -> onStartHatching?.invoke(selectedItem)
is QuestContent -> {
if (index == 0) {
val dialog = DetailDialog(context)
@ -201,17 +181,17 @@ class ItemRecyclerAdapter(val context: Context) : BaseRecyclerViewAdapter<OwnedI
dialog.show()
} else {
if (user?.hasParty == true) {
questInvitationEvents.onNext(selectedItem)
onQuestInvitation?.invoke(selectedItem)
} else {
createNewPartySubject.onNext(true)
onCreateNewParty?.invoke()
}
}
}
is SpecialItem ->
if (item?.key != "inventory_present") {
useSpecialSubject.onNext(selectedItem)
onUseSpecialItem?.invoke(selectedItem)
} else {
openMysteryItemEvents.onNext(selectedItem)
onOpenMysteryItem?.invoke(selectedItem)
}
}
}
@ -224,17 +204,17 @@ class ItemRecyclerAdapter(val context: Context) : BaseRecyclerViewAdapter<OwnedI
item?.let { firstItem ->
if (firstItem is Egg) {
(hatchingItem as? HatchingPotion)?.let { potion ->
hatchPetSubject.onNext(Pair(potion, firstItem))
onHatchPet?.invoke(potion, firstItem)
}
} else if (firstItem is HatchingPotion) {
(hatchingItem as? Egg)?.let { egg ->
hatchPetSubject.onNext(Pair(firstItem, egg))
onHatchPet?.invoke(firstItem, egg)
}
}
return@let
}
} else if (isFeeding) {
feedPetSubject.onNext(item as Food?)
(item as Food?)?.let { onFeedPet?.invoke(it) }
fragment?.dismiss()
}
}

View file

@ -6,11 +6,10 @@ import com.habitrpg.android.habitica.models.inventory.StableSection
import com.habitrpg.android.habitica.models.user.OwnedMount
import com.habitrpg.android.habitica.ui.viewHolders.MountViewHolder
import com.habitrpg.android.habitica.ui.viewHolders.SectionViewHolder
import io.reactivex.rxjava3.core.BackpressureStrategy
import io.reactivex.rxjava3.core.Flowable
import io.reactivex.rxjava3.subjects.PublishSubject
class MountDetailRecyclerAdapter : androidx.recyclerview.widget.RecyclerView.Adapter<androidx.recyclerview.widget.RecyclerView.ViewHolder>() {
var onEquip: ((String) -> Unit)? = null
private var ownedMounts: Map<String, OwnedMount>? = null
private val equipEvents = PublishSubject.create<String>()
@ -27,14 +26,10 @@ class MountDetailRecyclerAdapter : androidx.recyclerview.widget.RecyclerView.Ada
this.notifyDataSetChanged()
}
fun getEquipFlowable(): Flowable<String> {
return equipEvents.toFlowable(BackpressureStrategy.DROP)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): androidx.recyclerview.widget.RecyclerView.ViewHolder =
when (viewType) {
1 -> SectionViewHolder(parent)
else -> MountViewHolder(parent, equipEvents)
else -> MountViewHolder(parent, onEquip)
}
override fun onBindViewHolder(

View file

@ -5,7 +5,6 @@ import android.view.ViewGroup
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.databinding.CanHatchItemBinding
import com.habitrpg.android.habitica.extensions.inflate
import com.habitrpg.common.habitica.helpers.Animations
import com.habitrpg.android.habitica.models.inventory.Animal
import com.habitrpg.android.habitica.models.inventory.Egg
import com.habitrpg.android.habitica.models.inventory.Food
@ -16,15 +15,16 @@ import com.habitrpg.android.habitica.models.inventory.StableSection
import com.habitrpg.android.habitica.models.user.OwnedItem
import com.habitrpg.android.habitica.models.user.OwnedMount
import com.habitrpg.android.habitica.models.user.OwnedPet
import com.habitrpg.common.habitica.extensions.loadImage
import com.habitrpg.android.habitica.ui.viewHolders.PetViewHolder
import com.habitrpg.android.habitica.ui.viewHolders.SectionViewHolder
import com.habitrpg.android.habitica.ui.views.dialogs.PetSuggestHatchDialog
import io.reactivex.rxjava3.core.BackpressureStrategy
import io.reactivex.rxjava3.core.Flowable
import com.habitrpg.common.habitica.extensions.loadImage
import com.habitrpg.common.habitica.helpers.Animations
import io.reactivex.rxjava3.subjects.PublishSubject
class PetDetailRecyclerAdapter : androidx.recyclerview.widget.RecyclerView.Adapter<androidx.recyclerview.widget.RecyclerView.ViewHolder>() {
var onFeed: ((Pet, Food?) -> Unit)? = null
var onEquip: ((String) -> Unit)? = null
private var existingMounts: List<Mount>? = null
private var ownedPets: Map<String, OwnedPet>? = null
private var ownedMounts: Map<String, OwnedMount>? = null
@ -45,12 +45,6 @@ class PetDetailRecyclerAdapter : androidx.recyclerview.widget.RecyclerView.Adapt
this.notifyDataSetChanged()
}
fun getEquipFlowable(): Flowable<String> {
return equipEvents.toFlowable(BackpressureStrategy.DROP)
}
var feedFlowable: Flowable<Pair<Pet, Food?>> = feedEvents.toFlowable(BackpressureStrategy.DROP)
var animalIngredientsRetriever: ((Animal, ((Pair<Egg?, HatchingPotion?>) -> Unit)) -> Unit)? = null
private fun canRaiseToMount(pet: Pet): Boolean {
@ -74,7 +68,7 @@ class PetDetailRecyclerAdapter : androidx.recyclerview.widget.RecyclerView.Adapt
when (viewType) {
1 -> SectionViewHolder(parent)
2 -> CanHatchViewHolder(parent, animalIngredientsRetriever)
else -> PetViewHolder(parent, equipEvents, feedEvents, animalIngredientsRetriever)
else -> PetViewHolder(parent, onEquip, onFeed, animalIngredientsRetriever)
}
override fun onBindViewHolder(

View file

@ -25,16 +25,13 @@ import com.habitrpg.android.habitica.ui.viewHolders.PetViewHolder
import com.habitrpg.android.habitica.ui.viewHolders.SectionViewHolder
import com.habitrpg.common.habitica.extensions.loadImage
import com.habitrpg.common.habitica.views.PixelArtView
import io.reactivex.rxjava3.core.BackpressureStrategy
import io.reactivex.rxjava3.core.Flowable
import io.reactivex.rxjava3.subjects.PublishSubject
class StableRecyclerAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
var shopSpriteSuffix: String? = null
private var eggs: Map<String, Egg> = mapOf()
var animalIngredientsRetriever: ((Animal, ((Pair<Egg?, HatchingPotion?>) -> Unit)) -> Unit)? = null
private val feedEvents = PublishSubject.create<Pair<Pet, Food?>>()
var onFeed: ((Pet, Food?) -> Unit)? = null
var itemType: String? = null
var currentPet: String? = null
set(value) {
@ -46,16 +43,13 @@ class StableRecyclerAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
field = value
notifyDataSetChanged()
}
private val equipEvents = PublishSubject.create<String>()
var onEquip: ((String) -> Unit)? = null
private var existingMounts: List<Mount>? = null
private var ownedMounts: Map<String, OwnedMount>? = null
private var ownedItems: Map<String, OwnedItem>? = null
private var ownsSaddles: Boolean = false
private var itemList: List<Any> = ArrayList()
fun getEquipFlowable(): Flowable<String> {
return equipEvents.toFlowable(BackpressureStrategy.DROP)
}
private fun canRaiseToMount(pet: Pet): Boolean {
if (pet.type == "special") return false
@ -101,8 +95,8 @@ class StableRecyclerAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
1 -> SectionViewHolder(parent)
4 -> StableViewHolder(parent.inflate(R.layout.pet_overview_item))
5 -> StableViewHolder(parent.inflate(R.layout.mount_overview_item))
2 -> PetViewHolder(parent, equipEvents, feedEvents, animalIngredientsRetriever)
3 -> MountViewHolder(parent, equipEvents)
2 -> PetViewHolder(parent, onEquip, onFeed, animalIngredientsRetriever)
3 -> MountViewHolder(parent, onEquip)
else -> StableHeaderViewHolder(parent)
}

View file

@ -12,9 +12,6 @@ import com.habitrpg.android.habitica.extensions.inflate
import com.habitrpg.android.habitica.extensions.setTintWith
import com.habitrpg.android.habitica.models.SetupCustomization
import com.habitrpg.android.habitica.models.user.User
import io.reactivex.rxjava3.core.BackpressureStrategy
import io.reactivex.rxjava3.core.Flowable
import io.reactivex.rxjava3.subjects.PublishSubject
internal class CustomizationSetupAdapter : RecyclerView.Adapter<CustomizationSetupAdapter.CustomizationViewHolder>() {
@ -22,8 +19,7 @@ internal class CustomizationSetupAdapter : RecyclerView.Adapter<CustomizationSet
var user: User? = null
private var customizationList: List<SetupCustomization> = emptyList()
private val equipGearEventSubject = PublishSubject.create<String>()
val equipGearEvents: Flowable<String> = equipGearEventSubject.toFlowable(BackpressureStrategy.DROP)
var onEquipGear: ((String) -> Unit)? = null
var onUpdateUser: ((Map<String, Any>) -> Unit)? = null
fun setCustomizationList(newCustomizationList: List<SetupCustomization>) {
@ -122,7 +118,7 @@ internal class CustomizationSetupAdapter : RecyclerView.Adapter<CustomizationSet
} else {
selectedCustomization.key
}
key?.let { equipGearEventSubject.onNext(it) }
key?.let { onEquipGear?.invoke(it) }
} else {
val updateData = HashMap<String, Any>()
val updatePath = "preferences." + selectedCustomization.path

View file

@ -6,15 +6,12 @@ import com.habitrpg.android.habitica.extensions.inflate
import com.habitrpg.android.habitica.models.members.Member
import com.habitrpg.android.habitica.ui.adapter.BaseRecyclerViewAdapter
import com.habitrpg.android.habitica.ui.viewHolders.GroupMemberViewHolder
import io.reactivex.rxjava3.core.BackpressureStrategy
import io.reactivex.rxjava3.core.Flowable
import io.reactivex.rxjava3.subjects.PublishSubject
class PartyMemberRecyclerViewAdapter : BaseRecyclerViewAdapter<Member, GroupMemberViewHolder>() {
var leaderID: String? = null
private val userClickedEvents = PublishSubject.create<String>()
var onUserClicked: ((String) -> Unit)? = null
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): GroupMemberViewHolder {
return GroupMemberViewHolder(parent.inflate(R.layout.party_member))
@ -23,11 +20,7 @@ class PartyMemberRecyclerViewAdapter : BaseRecyclerViewAdapter<Member, GroupMemb
override fun onBindViewHolder(holder: GroupMemberViewHolder, position: Int) {
holder.bind(data[position], leaderID, null)
holder.onClickEvent = {
userClickedEvents.onNext(data[position].id ?: "")
onUserClicked?.invoke(data[position].id ?: "")
}
}
fun getUserClickedEvents(): Flowable<String> {
return userClickedEvents.toFlowable(BackpressureStrategy.DROP)
}
}

View file

@ -17,9 +17,6 @@ import com.habitrpg.android.habitica.ui.viewHolders.tasks.RewardViewHolder
import com.habitrpg.android.habitica.ui.viewHolders.tasks.TodoViewHolder
import com.habitrpg.android.habitica.ui.viewmodels.TasksViewModel
import com.habitrpg.shared.habitica.models.tasks.TaskType
import io.reactivex.rxjava3.core.BackpressureStrategy
import io.reactivex.rxjava3.core.Flowable
import io.reactivex.rxjava3.subjects.PublishSubject
class ChallengeTasksRecyclerViewAdapter(
viewModel: TasksViewModel,
@ -30,13 +27,11 @@ class ChallengeTasksRecyclerViewAdapter(
private val taskActionsDisabled: Boolean
) : BaseTasksRecyclerViewAdapter<BindableViewHolder<Task>>(TaskType.HABIT, viewModel, layoutResource, newContext, userID) {
private val addItemSubject = PublishSubject.create<Task>()
val taskList: MutableList<Task>
get() = content?.map { t -> t }?.toMutableList() ?: mutableListOf()
private var taskOpenEventsSubject = PublishSubject.create<Task>()
val taskOpenEvents: Flowable<Task> = taskOpenEventsSubject.toFlowable(BackpressureStrategy.LATEST)
var onAddItem: ((Task) -> Unit)? = null
var onTaskOpen: ((Task) -> Unit)? = null
override fun injectThis(component: UserComponent) {
component.inject(this)
@ -50,14 +45,10 @@ class ChallengeTasksRecyclerViewAdapter(
TaskType.DAILY -> TYPE_DAILY
TaskType.TODO -> TYPE_TODO
TaskType.REWARD -> TYPE_REWARD
else -> if (addItemSubject.hasObservers() && task?.id == "addtask") TYPE_ADD_ITEM else TYPE_HEADER
else -> if (task?.id == "addtask") TYPE_ADD_ITEM else TYPE_HEADER
}
}
fun addItemObservable(): Flowable<Task> {
return addItemSubject.toFlowable(BackpressureStrategy.BUFFER)
}
fun addTaskUnder(taskToAdd: Task, taskAbove: Task?): Int {
val position = content?.indexOfFirst { t -> t.id == taskAbove?.id } ?: 0
@ -70,18 +61,18 @@ class ChallengeTasksRecyclerViewAdapter(
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BindableViewHolder<Task> {
val viewHolder: BindableViewHolder<Task> = when (viewType) {
TYPE_HABIT -> HabitViewHolder(getContentView(parent, R.layout.habit_item_card), { _, _ -> }, { }, { task ->
taskOpenEventsSubject.onNext(task)
onTaskOpen?.invoke(task)
}, null)
TYPE_DAILY -> DailyViewHolder(getContentView(parent, R.layout.daily_item_card), { _, _ -> }, { _, _ -> }, { }, { task ->
taskOpenEventsSubject.onNext(task)
onTaskOpen?.invoke(task)
}, null)
TYPE_TODO -> TodoViewHolder(getContentView(parent, R.layout.todo_item_card), { _, _ -> }, { _, _ -> }, { }, { task ->
taskOpenEventsSubject.onNext(task)
onTaskOpen?.invoke(task)
}, null)
TYPE_REWARD -> RewardViewHolder(getContentView(parent, R.layout.reward_item_card), { _, _ -> }, { }, { task ->
taskOpenEventsSubject.onNext(task)
onTaskOpen?.invoke(task)
}, null)
TYPE_ADD_ITEM -> AddItemViewHolder(getContentView(parent, R.layout.challenge_add_task_item), addItemSubject)
TYPE_ADD_ITEM -> AddItemViewHolder(getContentView(parent, R.layout.challenge_add_task_item), onAddItem)
else -> DividerViewHolder(getContentView(parent, R.layout.challenge_task_divider))
}
@ -113,7 +104,7 @@ class ChallengeTasksRecyclerViewAdapter(
inner class AddItemViewHolder internal constructor(
itemView: View,
private val callback: PublishSubject<Task>
private val callback: ((Task) -> Unit)?
) : BindableViewHolder<Task>(itemView) {
private val addBtn: Button = itemView.findViewById(R.id.btn_add_task)
@ -121,7 +112,7 @@ class ChallengeTasksRecyclerViewAdapter(
init {
addBtn.isClickable = true
addBtn.setOnClickListener { newTask?.let { callback.onNext(it) } }
addBtn.setOnClickListener { newTask?.let { callback?.invoke(it) } }
}
override fun bind(

View file

@ -17,6 +17,7 @@ import com.habitrpg.android.habitica.components.UserComponent
import com.habitrpg.android.habitica.data.InventoryRepository
import com.habitrpg.android.habitica.databinding.FragmentRefreshRecyclerviewBinding
import com.habitrpg.android.habitica.helpers.ExceptionHandler
import com.habitrpg.android.habitica.helpers.launchCatching
import com.habitrpg.android.habitica.ui.adapter.AchievementsAdapter
import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel
import kotlinx.coroutines.flow.combine
@ -167,12 +168,8 @@ class AchievementsFragment : BaseMainFragment<FragmentRefreshRecyclerviewBinding
}
override fun onRefresh() {
compositeSubscription.add(
userRepository.retrieveAchievements().subscribe(
{
},
ExceptionHandler.rx(), { binding?.refreshLayout?.isRefreshing = false }
)
)
lifecycleScope.launchCatching {
userRepository.retrieveAchievements()
}
}
}

View file

@ -4,19 +4,21 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.lifecycle.lifecycleScope
import androidx.viewbinding.ViewBinding
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
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.AmplitudeManager
import com.habitrpg.android.habitica.helpers.ExceptionHandler
import com.habitrpg.android.habitica.helpers.launchCatching
import com.habitrpg.android.habitica.ui.activities.MainActivity
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.disposables.CompositeDisposable
import io.reactivex.rxjava3.functions.Consumer
import java.util.concurrent.TimeUnit
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.firstOrNull
import javax.inject.Inject
import kotlin.time.DurationUnit
import kotlin.time.toDuration
abstract class BaseDialogFragment<VB : ViewBinding> : BottomSheetDialogFragment() {
@ -68,21 +70,19 @@ abstract class BaseDialogFragment<VB : ViewBinding> : BottomSheetDialogFragment(
private fun showTutorialIfNeeded() {
if (view != null) {
if (this.tutorialStepIdentifier != null) {
compositeSubscription.add(
tutorialRepository.getTutorialStep(this.tutorialStepIdentifier ?: "").firstElement()
.delay(1, TimeUnit.SECONDS)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
Consumer { step ->
if (step.isValid && step.isManaged && step.shouldDisplay) {
val mainActivity = activity as? MainActivity ?: return@Consumer
mainActivity.displayTutorialStep(step, tutorialTexts, tutorialCanBeDeferred)
}
},
ExceptionHandler.rx()
tutorialStepIdentifier?.let { identifier ->
lifecycleScope.launchCatching {
val step = tutorialRepository.getTutorialStep(identifier).firstOrNull()
delay(1.toDuration(DurationUnit.SECONDS))
if (step?.isValid == true && step.isManaged && step.shouldDisplay) {
val mainActivity = activity as? MainActivity ?: return@launchCatching
mainActivity.displayTutorialStep(
step,
tutorialTexts,
tutorialCanBeDeferred
)
)
}
}
}
}
}

View file

@ -5,17 +5,20 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import androidx.viewbinding.ViewBinding
import com.habitrpg.android.habitica.HabiticaBaseApplication
import com.habitrpg.android.habitica.components.UserComponent
import com.habitrpg.android.habitica.data.TutorialRepository
import com.habitrpg.android.habitica.helpers.ExceptionHandler
import com.habitrpg.android.habitica.helpers.launchCatching
import com.habitrpg.android.habitica.proxy.AnalyticsManager
import com.habitrpg.android.habitica.ui.activities.MainActivity
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.disposables.CompositeDisposable
import java.util.concurrent.TimeUnit
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.firstOrNull
import javax.inject.Inject
import kotlin.time.DurationUnit
import kotlin.time.toDuration
abstract class BaseFragment<VB : ViewBinding> : Fragment() {
@ -72,21 +75,18 @@ abstract class BaseFragment<VB : ViewBinding> : Fragment() {
private fun showTutorialIfNeeded() {
tutorialStepIdentifier?.let { identifier ->
compositeSubscription.add(
tutorialRepository.getTutorialStep(identifier)
.firstElement()
.delay(1, TimeUnit.SECONDS)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{ step ->
if (step.isValid && step.isManaged && step.shouldDisplay) {
val mainActivity = activity as? MainActivity ?: return@subscribe
mainActivity.displayTutorialStep(step, tutorialTexts, tutorialCanBeDeferred)
}
},
ExceptionHandler.rx()
lifecycleScope.launchCatching {
val step = tutorialRepository.getTutorialStep(identifier).firstOrNull()
delay(1.toDuration(DurationUnit.SECONDS))
if (step?.isValid == true && step.isManaged && step.shouldDisplay) {
val mainActivity = activity as? MainActivity ?: return@launchCatching
mainActivity.displayTutorialStep(
step,
tutorialTexts,
tutorialCanBeDeferred
)
)
}
}
}
}

View file

@ -29,10 +29,10 @@ import com.habitrpg.android.habitica.databinding.DrawerMainBinding
import com.habitrpg.android.habitica.extensions.getMinuteOrSeconds
import com.habitrpg.android.habitica.extensions.getRemainingString
import com.habitrpg.android.habitica.extensions.getShortRemainingString
import com.habitrpg.android.habitica.extensions.subscribeWithErrorHandler
import com.habitrpg.android.habitica.helpers.AppConfigManager
import com.habitrpg.android.habitica.helpers.ExceptionHandler
import com.habitrpg.android.habitica.helpers.MainNavigationController
import com.habitrpg.android.habitica.helpers.launchCatching
import com.habitrpg.android.habitica.models.WorldStateEvent
import com.habitrpg.android.habitica.models.inventory.Item
import com.habitrpg.android.habitica.models.promotions.HabiticaPromotion
@ -47,11 +47,11 @@ import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel
import com.habitrpg.android.habitica.ui.viewmodels.NotificationsViewModel
import com.habitrpg.android.habitica.ui.views.HabiticaSnackbar
import com.habitrpg.common.habitica.extensions.getThemeColor
import io.reactivex.rxjava3.core.Flowable
import io.reactivex.rxjava3.disposables.CompositeDisposable
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.launch
import java.util.Calendar
import java.util.Date
@ -68,16 +68,22 @@ class NavigationDrawerFragment : DialogFragment() {
@Inject
lateinit var socialRepository: SocialRepository
@Inject
lateinit var inventoryRepository: InventoryRepository
@Inject
lateinit var userRepository: UserRepository
@Inject
lateinit var configManager: AppConfigManager
@Inject
lateinit var contentRepository: ContentRepository
@Inject
lateinit var sharedPreferences: SharedPreferences
@Inject
lateinit var userViewModel: MainUserViewModel
@ -101,7 +107,10 @@ class NavigationDrawerFragment : DialogFragment() {
override fun onCreate(savedInstanceState: Bundle?) {
val context = context
adapter = if (context != null) {
NavigationDrawerAdapter(context.getThemeColor(R.attr.colorPrimary), context.getThemeColor(R.attr.colorPrimaryOffset))
NavigationDrawerAdapter(
context.getThemeColor(R.attr.colorPrimary),
context.getThemeColor(R.attr.colorPrimaryOffset)
)
} else {
NavigationDrawerAdapter(0, 0)
}
@ -130,8 +139,10 @@ class NavigationDrawerFragment : DialogFragment() {
binding = DrawerMainBinding.bind(view)
binding?.recyclerView?.adapter = adapter
binding?.recyclerView?.layoutManager = androidx.recyclerview.widget.LinearLayoutManager(context)
(binding?.recyclerView?.itemAnimator as? SimpleItemAnimator)?.supportsChangeAnimations = false
binding?.recyclerView?.layoutManager =
androidx.recyclerview.widget.LinearLayoutManager(context)
(binding?.recyclerView?.itemAnimator as? SimpleItemAnimator)?.supportsChangeAnimations =
false
initializeMenuItems()
subscriptions?.add(
@ -154,26 +165,25 @@ class NavigationDrawerFragment : DialogFragment() {
)
)
subscriptions?.add(
Flowable.combineLatest(
contentRepository.getWorldState(), inventoryRepository.getAvailableLimitedItems()
) { state, items ->
return@combineLatest Pair(state, items)
}.subscribe(
{ pair ->
lifecycleScope.launchCatching {
contentRepository.getWorldState()
.combine(
inventoryRepository.getAvailableLimitedItems()
) { state, items -> Pair(state, items) }
.collect { pair ->
val gearEvent = pair.first.events.firstOrNull { it.gear }
createUpdatingJob("seasonal", {
gearEvent?.isCurrentlyActive == true || pair.second.isNotEmpty()
}, {
val diff = (gearEvent?.end?.time ?: 0) - Date().time
if (diff < (1.toDuration(DurationUnit.HOURS).inWholeMilliseconds)) 1.toDuration(DurationUnit.SECONDS) else 1.toDuration(DurationUnit.MINUTES)
if (diff < (1.toDuration(DurationUnit.HOURS).inWholeMilliseconds)) 1.toDuration(
DurationUnit.SECONDS
) else 1.toDuration(DurationUnit.MINUTES)
}) {
updateSeasonalMenuEntries(gearEvent, pair.second)
}
},
ExceptionHandler.rx()
)
)
}
}
userViewModel.user.observe(viewLifecycleOwner) {
if (it != null) {
@ -181,8 +191,22 @@ class NavigationDrawerFragment : DialogFragment() {
}
}
binding?.messagesButtonWrapper?.setOnClickListener { setSelection(R.id.inboxFragment, null, true, preventReselection = false) }
binding?.settingsButtonWrapper?.setOnClickListener { setSelection(R.id.prefsActivity, null, true, preventReselection = false) }
binding?.messagesButtonWrapper?.setOnClickListener {
setSelection(
R.id.inboxFragment,
null,
true,
preventReselection = false
)
}
binding?.settingsButtonWrapper?.setOnClickListener {
setSelection(
R.id.prefsActivity,
null,
true,
preventReselection = false
)
}
binding?.notificationsButtonWrapper?.setOnClickListener { startNotificationsActivity() }
}
@ -219,7 +243,8 @@ class NavigationDrawerFragment : DialogFragment() {
shop.pillText = context?.getString(R.string.open)
if (gearEvent?.isCurrentlyActive == true) {
shop.isVisible = true
shop.subtitle = context?.getString(R.string.open_for, gearEvent.end?.getShortRemainingString())
shop.subtitle =
context?.getString(R.string.open_for, gearEvent.end?.getShortRemainingString())
} else {
shop.isVisible = false
}
@ -228,7 +253,10 @@ class NavigationDrawerFragment : DialogFragment() {
private fun updateUser(user: User) {
binding?.avatarView?.setOnClickListener {
MainNavigationController.navigate(R.id.openProfileActivity, bundleOf(Pair("userID", user.id)))
MainNavigationController.navigate(
R.id.openProfileActivity,
bundleOf(Pair("userID", user.id))
)
}
setMessagesCount(user.inbox)
@ -289,7 +317,8 @@ class NavigationDrawerFragment : DialogFragment() {
val daysDiff = TimeUnit.MILLISECONDS.toDays(msDiff)
if (daysDiff <= 30) {
context?.let {
subscriptionItem?.subtitle = user.purchased?.plan?.dateTerminated?.getRemainingString(it.resources)
subscriptionItem?.subtitle =
user.purchased?.plan?.dateTerminated?.getRemainingString(it.resources)
subscriptionItem?.subtitleTextColor = when {
daysDiff <= 2 -> ContextCompat.getColor(it, R.color.red_100)
daysDiff <= 7 -> ContextCompat.getColor(it, R.color.brand_400)
@ -337,36 +366,182 @@ class NavigationDrawerFragment : DialogFragment() {
private fun initializeMenuItems() {
val items = ArrayList<HabiticaDrawerItem>()
context?.let { context ->
items.add(HabiticaDrawerItem(R.id.tasksFragment, SIDEBAR_TASKS, context.getString(R.string.sidebar_tasks)))
items.add(HabiticaDrawerItem(R.id.skillsFragment, SIDEBAR_SKILLS, context.getString(R.string.sidebar_skills)))
items.add(HabiticaDrawerItem(R.id.statsFragment, SIDEBAR_STATS, context.getString(R.string.sidebar_stats)))
items.add(HabiticaDrawerItem(R.id.achievementsFragment, SIDEBAR_ACHIEVEMENTS, context.getString(R.string.sidebar_achievements)))
items.add(
HabiticaDrawerItem(
R.id.tasksFragment,
SIDEBAR_TASKS,
context.getString(R.string.sidebar_tasks)
)
)
items.add(
HabiticaDrawerItem(
R.id.skillsFragment,
SIDEBAR_SKILLS,
context.getString(R.string.sidebar_skills)
)
)
items.add(
HabiticaDrawerItem(
R.id.statsFragment,
SIDEBAR_STATS,
context.getString(R.string.sidebar_stats)
)
)
items.add(
HabiticaDrawerItem(
R.id.achievementsFragment,
SIDEBAR_ACHIEVEMENTS,
context.getString(R.string.sidebar_achievements)
)
)
items.add(HabiticaDrawerItem(0, SIDEBAR_INVENTORY, context.getString(R.string.sidebar_shops), isHeader = true))
items.add(HabiticaDrawerItem(R.id.marketFragment, SIDEBAR_SHOPS_MARKET, context.getString(R.string.market)))
items.add(HabiticaDrawerItem(R.id.questShopFragment, SIDEBAR_SHOPS_QUEST, context.getString(R.string.questShop)))
val seasonalShopEntry = HabiticaDrawerItem(R.id.seasonalShopFragment, SIDEBAR_SHOPS_SEASONAL, context.getString(R.string.seasonalShop))
items.add(
HabiticaDrawerItem(
0,
SIDEBAR_INVENTORY,
context.getString(R.string.sidebar_shops),
isHeader = true
)
)
items.add(
HabiticaDrawerItem(
R.id.marketFragment,
SIDEBAR_SHOPS_MARKET,
context.getString(R.string.market)
)
)
items.add(
HabiticaDrawerItem(
R.id.questShopFragment,
SIDEBAR_SHOPS_QUEST,
context.getString(R.string.questShop)
)
)
val seasonalShopEntry = HabiticaDrawerItem(
R.id.seasonalShopFragment,
SIDEBAR_SHOPS_SEASONAL,
context.getString(R.string.seasonalShop)
)
seasonalShopEntry.isVisible = false
items.add(seasonalShopEntry)
items.add(HabiticaDrawerItem(R.id.timeTravelersShopFragment, SIDEBAR_SHOPS_TIMETRAVEL, context.getString(R.string.timeTravelers)))
items.add(
HabiticaDrawerItem(
R.id.timeTravelersShopFragment,
SIDEBAR_SHOPS_TIMETRAVEL,
context.getString(R.string.timeTravelers)
)
)
items.add(HabiticaDrawerItem(0, SIDEBAR_INVENTORY, context.getString(R.string.sidebar_section_inventory), isHeader = true))
items.add(HabiticaDrawerItem(R.id.avatarOverviewFragment, SIDEBAR_AVATAR, context.getString(R.string.sidebar_avatar_equipment)))
items.add(HabiticaDrawerItem(R.id.itemsFragment, SIDEBAR_ITEMS, context.getString(R.string.sidebar_items)))
items.add(HabiticaDrawerItem(R.id.stableFragment, SIDEBAR_STABLE, context.getString(R.string.sidebar_stable)))
items.add(HabiticaDrawerItem(R.id.gemPurchaseActivity, SIDEBAR_GEMS, context.getString(R.string.sidebar_gems)))
items.add(HabiticaDrawerItem(R.id.subscriptionPurchaseActivity, SIDEBAR_SUBSCRIPTION, context.getString(R.string.sidebar_subscription)))
items.add(
HabiticaDrawerItem(
0,
SIDEBAR_INVENTORY,
context.getString(R.string.sidebar_section_inventory),
isHeader = true
)
)
items.add(
HabiticaDrawerItem(
R.id.avatarOverviewFragment,
SIDEBAR_AVATAR,
context.getString(R.string.sidebar_avatar_equipment)
)
)
items.add(
HabiticaDrawerItem(
R.id.itemsFragment,
SIDEBAR_ITEMS,
context.getString(R.string.sidebar_items)
)
)
items.add(
HabiticaDrawerItem(
R.id.stableFragment,
SIDEBAR_STABLE,
context.getString(R.string.sidebar_stable)
)
)
items.add(
HabiticaDrawerItem(
R.id.gemPurchaseActivity,
SIDEBAR_GEMS,
context.getString(R.string.sidebar_gems)
)
)
items.add(
HabiticaDrawerItem(
R.id.subscriptionPurchaseActivity,
SIDEBAR_SUBSCRIPTION,
context.getString(R.string.sidebar_subscription)
)
)
items.add(HabiticaDrawerItem(0, SIDEBAR_SOCIAL, context.getString(R.string.sidebar_section_social), isHeader = true))
items.add(HabiticaDrawerItem(R.id.partyFragment, SIDEBAR_PARTY, context.getString(R.string.sidebar_party)))
items.add(HabiticaDrawerItem(R.id.tavernFragment, SIDEBAR_TAVERN, context.getString(R.string.sidebar_tavern)))
items.add(HabiticaDrawerItem(R.id.guildOverviewFragment, SIDEBAR_GUILDS, context.getString(R.string.sidebar_guilds)))
items.add(HabiticaDrawerItem(R.id.challengesOverviewFragment, SIDEBAR_CHALLENGES, context.getString(R.string.sidebar_challenges)))
items.add(
HabiticaDrawerItem(
0,
SIDEBAR_SOCIAL,
context.getString(R.string.sidebar_section_social),
isHeader = true
)
)
items.add(
HabiticaDrawerItem(
R.id.partyFragment,
SIDEBAR_PARTY,
context.getString(R.string.sidebar_party)
)
)
items.add(
HabiticaDrawerItem(
R.id.tavernFragment,
SIDEBAR_TAVERN,
context.getString(R.string.sidebar_tavern)
)
)
items.add(
HabiticaDrawerItem(
R.id.guildOverviewFragment,
SIDEBAR_GUILDS,
context.getString(R.string.sidebar_guilds)
)
)
items.add(
HabiticaDrawerItem(
R.id.challengesOverviewFragment,
SIDEBAR_CHALLENGES,
context.getString(R.string.sidebar_challenges)
)
)
items.add(HabiticaDrawerItem(0, SIDEBAR_ABOUT_HEADER, context.getString(R.string.sidebar_about), isHeader = true))
items.add(HabiticaDrawerItem(R.id.newsFragment, SIDEBAR_NEWS, context.getString(R.string.sidebar_news)))
items.add(HabiticaDrawerItem(R.id.supportMainFragment, SIDEBAR_HELP, context.getString(R.string.sidebar_help)))
items.add(HabiticaDrawerItem(R.id.aboutFragment, SIDEBAR_ABOUT, context.getString(R.string.sidebar_about)))
items.add(
HabiticaDrawerItem(
0,
SIDEBAR_ABOUT_HEADER,
context.getString(R.string.sidebar_about),
isHeader = true
)
)
items.add(
HabiticaDrawerItem(
R.id.newsFragment,
SIDEBAR_NEWS,
context.getString(R.string.sidebar_news)
)
)
items.add(
HabiticaDrawerItem(
R.id.supportMainFragment,
SIDEBAR_HELP,
context.getString(R.string.sidebar_help)
)
)
items.add(
HabiticaDrawerItem(
R.id.aboutFragment,
SIDEBAR_ABOUT,
context.getString(R.string.sidebar_about)
)
)
}
val promoItem = HabiticaDrawerItem(R.id.subscriptionPurchaseActivity, SIDEBAR_PROMO)
@ -375,7 +550,8 @@ class NavigationDrawerFragment : DialogFragment() {
items.add(promoItem)
if (configManager.showSubscriptionBanner()) {
val item = HabiticaDrawerItem(R.id.subscriptionPurchaseActivity, SIDEBAR_SUBSCRIPTION_PROMO)
val item =
HabiticaDrawerItem(R.id.subscriptionPurchaseActivity, SIDEBAR_SUBSCRIPTION_PROMO)
item.itemViewType = 2
items.add(item)
}
@ -421,14 +597,15 @@ class NavigationDrawerFragment : DialogFragment() {
}
}
private val notificationClickResult = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
if (it.resultCode == Activity.RESULT_OK) {
(activity as? MainActivity)?.notificationsViewModel?.click(
it.data?.getStringExtra("notificationId") ?: "",
MainNavigationController
)
private val notificationClickResult =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
if (it.resultCode == Activity.RESULT_OK) {
(activity as? MainActivity)?.notificationsViewModel?.click(
it.data?.getStringExtra("notificationId") ?: "",
MainNavigationController
)
}
}
}
/**
* Users of this fragment must call this method to set UP the navigation drawer interactions.
@ -448,22 +625,22 @@ class NavigationDrawerFragment : DialogFragment() {
this.drawerLayout?.setDrawerShadow(R.drawable.drawer_shadow, GravityCompat.START)
// set UP the drawer's list view with items and click listener
subscriptions?.add(
viewModel.getNotificationCount().subscribeWithErrorHandler {
lifecycleScope.launchCatching {
viewModel.getNotificationCount().collect {
setNotificationsCount(it)
}
)
subscriptions?.add(
viewModel.allNotificationsSeen().subscribeWithErrorHandler {
}
lifecycleScope.launchCatching {
viewModel.allNotificationsSeen().collect {
setNotificationsSeen(it)
}
)
subscriptions?.add(
viewModel.getHasPartyNotification().subscribeWithErrorHandler {
}
lifecycleScope.launchCatching {
viewModel.getHasPartyNotification().collect {
val partyMenuItem = getItemWithIdentifier(SIDEBAR_PARTY)
partyMenuItem?.showBubble = it
}
)
}
}
fun openDrawer() {
@ -590,15 +767,20 @@ class NavigationDrawerFragment : DialogFragment() {
}
if (promotedItem == null) return@let
promotedItem.pillText = context?.getString(R.string.sale)
promotedItem.pillBackground = context?.let { activePromo.pillBackgroundDrawable(it) }
promotedItem.pillBackground =
context?.let { activePromo.pillBackgroundDrawable(it) }
createUpdatingJob(activePromo.promoType.name, {
activePromo.isActive
}, {
val diff = (activePromo.endDate.time - Date().time).toDuration(DurationUnit.SECONDS)
val diff =
(activePromo.endDate.time - Date().time).toDuration(DurationUnit.SECONDS)
1.toDuration(diff.getMinuteOrSeconds())
}) {
if (activePromo.isActive) {
promotedItem.subtitle = context?.getString(R.string.sale_ends_in, activePromo.endDate.getShortRemainingString())
promotedItem.subtitle = context?.getString(
R.string.sale_ends_in,
activePromo.endDate.getShortRemainingString()
)
updateItem(promotedItem)
} else {
promotedItem.subtitle = null

View file

@ -13,7 +13,6 @@ import com.habitrpg.android.habitica.data.InventoryRepository
import com.habitrpg.android.habitica.databinding.FragmentStatsBinding
import com.habitrpg.android.habitica.extensions.addOkButton
import com.habitrpg.android.habitica.extensions.setScaledPadding
import com.habitrpg.android.habitica.helpers.ExceptionHandler
import com.habitrpg.android.habitica.helpers.UserStatComputer
import com.habitrpg.android.habitica.helpers.launchCatching
import com.habitrpg.android.habitica.models.user.Stats
@ -24,6 +23,7 @@ import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog
import com.habitrpg.android.habitica.ui.views.stats.BulkAllocateStatsDialog
import com.habitrpg.common.habitica.extensions.getThemeColor
import com.habitrpg.shared.habitica.models.tasks.Attribute
import kotlinx.coroutines.flow.firstOrNull
import javax.inject.Inject
import kotlin.math.min
@ -181,9 +181,9 @@ class StatsFragment : BaseMainFragment<FragmentStatsBinding>() {
}
private fun allocatePoint(stat: Attribute) {
compositeSubscription.add(
userRepository.allocatePoint(stat).subscribe({ }, ExceptionHandler.rx())
)
lifecycleScope.launchCatching {
userRepository.allocatePoint(stat)
}
}
private fun updateAttributePoints(user: User) {
@ -263,74 +263,68 @@ class StatsFragment : BaseMainFragment<FragmentStatsBinding>() {
outfitList.add(thisOutfit.weapon)
}
compositeSubscription.add(
inventoryRepository.getEquipment(outfitList).firstElement()
.retry(1)
.subscribe(
{
val levelStat = min((user.stats?.lvl ?: 0) / 2.0f, 50f).toInt()
lifecycleScope.launchCatching {
val equipment = inventoryRepository.getEquipment(outfitList).firstOrNull()
val levelStat = min((user.stats?.lvl ?: 0) / 2.0f, 50f).toInt()
totalStrength = levelStat
totalIntelligence = levelStat
totalConstitution = levelStat
totalPerception = levelStat
totalStrength = levelStat
totalIntelligence = levelStat
totalConstitution = levelStat
totalPerception = levelStat
binding?.strengthStatsView?.levelValue = levelStat
binding?.intelligenceStatsView?.levelValue = levelStat
binding?.constitutionStatsView?.levelValue = levelStat
binding?.perceptionStatsView?.levelValue = levelStat
binding?.strengthStatsView?.levelValue = levelStat
binding?.intelligenceStatsView?.levelValue = levelStat
binding?.constitutionStatsView?.levelValue = levelStat
binding?.perceptionStatsView?.levelValue = levelStat
totalStrength += user.stats?.buffs?.str?.toInt() ?: 0
totalIntelligence += user.stats?.buffs?._int?.toInt() ?: 0
totalConstitution += user.stats?.buffs?.con?.toInt() ?: 0
totalPerception += user.stats?.buffs?.per?.toInt() ?: 0
binding?.strengthStatsView?.buffValue = user.stats?.buffs?.str?.toInt() ?: 0
binding?.intelligenceStatsView?.buffValue =
user.stats?.buffs?._int?.toInt() ?: 0
binding?.constitutionStatsView?.buffValue =
user.stats?.buffs?.con?.toInt() ?: 0
binding?.perceptionStatsView?.buffValue =
user.stats?.buffs?.per?.toInt() ?: 0
totalStrength += user.stats?.buffs?.str?.toInt() ?: 0
totalIntelligence += user.stats?.buffs?._int?.toInt() ?: 0
totalConstitution += user.stats?.buffs?.con?.toInt() ?: 0
totalPerception += user.stats?.buffs?.per?.toInt() ?: 0
binding?.strengthStatsView?.buffValue = user.stats?.buffs?.str?.toInt() ?: 0
binding?.intelligenceStatsView?.buffValue =
user.stats?.buffs?._int?.toInt() ?: 0
binding?.constitutionStatsView?.buffValue =
user.stats?.buffs?.con?.toInt() ?: 0
binding?.perceptionStatsView?.buffValue =
user.stats?.buffs?.per?.toInt() ?: 0
totalStrength += user.stats?.strength ?: 0
totalIntelligence += user.stats?.intelligence ?: 0
totalConstitution += user.stats?.constitution ?: 0
totalPerception += user.stats?.per ?: 0
binding?.strengthStatsView?.allocatedValue = user.stats?.strength ?: 0
binding?.intelligenceStatsView?.allocatedValue =
user.stats?.intelligence ?: 0
binding?.constitutionStatsView?.allocatedValue =
user.stats?.constitution ?: 0
binding?.perceptionStatsView?.allocatedValue = user.stats?.per ?: 0
val userStatComputer = UserStatComputer()
val statsRows = userStatComputer.computeClassBonus(it, user)
totalStrength += user.stats?.strength ?: 0
totalIntelligence += user.stats?.intelligence ?: 0
totalConstitution += user.stats?.constitution ?: 0
totalPerception += user.stats?.per ?: 0
binding?.strengthStatsView?.allocatedValue = user.stats?.strength ?: 0
binding?.intelligenceStatsView?.allocatedValue =
user.stats?.intelligence ?: 0
binding?.constitutionStatsView?.allocatedValue =
user.stats?.constitution ?: 0
binding?.perceptionStatsView?.allocatedValue = user.stats?.per ?: 0
val userStatComputer = UserStatComputer()
val statsRows = userStatComputer.computeClassBonus(equipment, user)
var strength = 0
var intelligence = 0
var constitution = 0
var perception = 0
var strength = 0
var intelligence = 0
var constitution = 0
var perception = 0
for (row in statsRows) {
if (row.javaClass == UserStatComputer.AttributeRow::class.java) {
val attributeRow = row as UserStatComputer.AttributeRow
strength += attributeRow.strVal.toInt()
intelligence += attributeRow.intVal.toInt()
constitution += attributeRow.conVal.toInt()
perception += attributeRow.perVal.toInt()
}
}
for (row in statsRows) {
if (row.javaClass == UserStatComputer.AttributeRow::class.java) {
val attributeRow = row as UserStatComputer.AttributeRow
strength += attributeRow.strVal.toInt()
intelligence += attributeRow.intVal.toInt()
constitution += attributeRow.conVal.toInt()
perception += attributeRow.perVal.toInt()
}
}
totalStrength += strength
totalIntelligence += intelligence
totalConstitution += constitution
totalPerception += perception
binding?.strengthStatsView?.equipmentValue = strength
binding?.intelligenceStatsView?.equipmentValue = intelligence
binding?.constitutionStatsView?.equipmentValue = constitution
binding?.perceptionStatsView?.equipmentValue = perception
},
ExceptionHandler.rx()
)
)
totalStrength += strength
totalIntelligence += intelligence
totalConstitution += constitution
totalPerception += perception
binding?.strengthStatsView?.equipmentValue = strength
binding?.intelligenceStatsView?.equipmentValue = intelligence
binding?.constitutionStatsView?.equipmentValue = constitution
binding?.perceptionStatsView?.equipmentValue = perception
}
}
}

View file

@ -39,10 +39,9 @@ import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel
import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaBottomSheetDialog
import com.habitrpg.common.habitica.extensions.dpToPx
import com.habitrpg.common.habitica.extensions.getThemeColor
import io.reactivex.rxjava3.core.BackpressureStrategy
import io.reactivex.rxjava3.kotlin.combineLatest
import io.reactivex.rxjava3.subjects.BehaviorSubject
import io.reactivex.rxjava3.subjects.PublishSubject
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import javax.inject.Inject
@ -71,8 +70,8 @@ class AvatarCustomizationFragment :
internal var adapter: CustomizationRecyclerViewAdapter = CustomizationRecyclerViewAdapter()
internal var layoutManager: FlexboxLayoutManager = FlexboxLayoutManager(activity, ROW)
private val currentFilter = BehaviorSubject.create<CustomizationFilter>()
private val ownedCustomizations = PublishSubject.create<List<OwnedCustomization>>()
private val currentFilter = MutableStateFlow<CustomizationFilter>(CustomizationFilter(false, type != "background"))
private val ownedCustomizations = MutableStateFlow<List<OwnedCustomization>>(emptyList())
override fun onCreateView(
inflater: LayoutInflater,
@ -95,11 +94,11 @@ class AvatarCustomizationFragment :
}
}
compositeSubscription.add(
this.inventoryRepository.getInAppRewards()
lifecycleScope.launchCatching {
inventoryRepository.getInAppRewards()
.map { rewards -> rewards.map { it.key } }
.subscribe({ adapter.setPinnedItemKeys(it) }, ExceptionHandler.rx())
)
.collect { adapter.setPinnedItemKeys(it) }
}
return super.onCreateView(inflater, container, savedInstanceState)
}
@ -127,7 +126,6 @@ class AvatarCustomizationFragment :
this.loadCustomizations()
userViewModel.user.observe(viewLifecycleOwner) { updateUser(it) }
currentFilter.onNext(CustomizationFilter(false, type != "background"))
binding?.recyclerView?.doOnLayout {
adapter.columnCount = it.width / (80.dpToPx(context))
@ -182,15 +180,13 @@ class AvatarCustomizationFragment :
private fun loadCustomizations() {
val type = this.type ?: return
compositeSubscription.add(
lifecycleScope.launchCatching {
customizationRepository.getCustomizations(type, category, false)
.combineLatest(
currentFilter.toFlowable(BackpressureStrategy.DROP),
ownedCustomizations.toFlowable(BackpressureStrategy.DROP)
)
.subscribe(
{ (customizations, filter, ownedCustomizations) ->
adapter.ownedCustomizations = ownedCustomizations.map { it.key + "_" + it.type + "_" + it.category }
.combine(currentFilter) { customizations, filter -> Pair(customizations, filter) }
.combine(ownedCustomizations) { pair, ownedCustomizations -> Triple(pair.first, pair.second, ownedCustomizations) }
.collect { (customizations, filter, ownedCustomizations) ->
adapter.ownedCustomizations =
ownedCustomizations.map { it.key + "_" + it.type + "_" + it.category }
if (filter.isFiltering) {
val displayedCustomizations = mutableListOf<Customization>()
for (customization in customizations) {
@ -213,13 +209,15 @@ class AvatarCustomizationFragment :
}
)
}
},
ExceptionHandler.rx()
)
)
}
}
if (type == "hair" && (category == "beard" || category == "mustache")) {
val otherCategory = if (category == "mustache") "beard" else "mustache"
compositeSubscription.add(customizationRepository.getCustomizations(type, otherCategory, true).subscribe({ adapter.additionalSetItems = it }, ExceptionHandler.rx()))
lifecycleScope.launchCatching {
customizationRepository.getCustomizations(type, otherCategory, true).collect {
adapter.additionalSetItems = it
}
}
}
}
@ -236,7 +234,7 @@ class AvatarCustomizationFragment :
fun updateUser(user: User?) {
if (user == null) return
this.updateActiveCustomization(user)
ownedCustomizations.onNext(user.purchased?.customizations?.filter { it.type == this.type && it.purchased })
ownedCustomizations.value = user.purchased?.customizations?.filter { it.type == this.type && it.purchased } ?: emptyList()
this.adapter.userSize = user.preferences?.size
this.adapter.hairColor = user.preferences?.hair?.color
this.adapter.gemBalance = user.gemCount
@ -286,17 +284,17 @@ class AvatarCustomizationFragment :
binding.showMeWrapper.check(if (filter.onlyPurchased) R.id.show_purchased_button else R.id.show_all_button)
binding.showMeWrapper.setOnCheckedChangeListener { _, checkedId ->
filter.onlyPurchased = checkedId == R.id.show_purchased_button
currentFilter.onNext(filter)
currentFilter.value = filter
}
binding.clearButton.setOnClickListener {
currentFilter.onNext(CustomizationFilter(false, type != "background"))
currentFilter.value = CustomizationFilter(false, type != "background")
dialog.dismiss()
}
if (type == "background") {
binding.sortByWrapper.check(if (filter.ascending) R.id.oldest_button else R.id.newest_button)
binding.sortByWrapper.setOnCheckedChangeListener { _, checkedId ->
filter.ascending = checkedId == R.id.oldest_button
currentFilter.onNext(filter)
currentFilter.value = filter
}
configureMonthFilterButton(binding.januaryButton, 1, filter)
configureMonthFilterButton(binding.febuaryButton, 2, filter)
@ -333,7 +331,7 @@ class AvatarCustomizationFragment :
button.typeface = Typeface.create("sans-serif-medium", Typeface.NORMAL)
filter.months.add(identifier)
}
currentFilter.onNext(filter)
currentFilter.value = filter
}
}
}

View file

@ -12,12 +12,13 @@ import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.components.UserComponent
import com.habitrpg.android.habitica.data.InventoryRepository
import com.habitrpg.android.habitica.databinding.FragmentRefreshRecyclerviewBinding
import com.habitrpg.android.habitica.helpers.MainNavigationController
import com.habitrpg.android.habitica.helpers.ExceptionHandler
import com.habitrpg.android.habitica.helpers.MainNavigationController
import com.habitrpg.android.habitica.helpers.launchCatching
import com.habitrpg.android.habitica.ui.adapter.inventory.EquipmentRecyclerViewAdapter
import com.habitrpg.android.habitica.ui.fragments.BaseMainFragment
import com.habitrpg.common.habitica.helpers.EmptyItem
import com.habitrpg.android.habitica.ui.helpers.SafeDefaultItemAnimator
import com.habitrpg.common.habitica.helpers.EmptyItem
import kotlinx.coroutines.launch
import javax.inject.Inject
@ -45,10 +46,11 @@ class EquipmentDetailFragment :
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
compositeSubscription.add(
this.adapter.equipEvents.flatMapMaybe { key -> inventoryRepository.equipGear(key, isCostume ?: false).firstElement() }
.subscribe({ }, ExceptionHandler.rx())
)
adapter.onEquip = {
lifecycleScope.launchCatching {
inventoryRepository.equipGear(it, isCostume ?: false)
}
}
return super.onCreateView(inflater, container, savedInstanceState)
}
@ -82,7 +84,11 @@ class EquipmentDetailFragment :
binding?.recyclerView?.addItemDecoration(DividerItemDecoration(getActivity(), DividerItemDecoration.VERTICAL))
binding?.recyclerView?.itemAnimator = SafeDefaultItemAnimator()
type?.let { type -> inventoryRepository.getOwnedEquipment(type).subscribe({ this.adapter.data = it }, ExceptionHandler.rx()) }
type?.let { type ->
lifecycleScope.launchCatching {
inventoryRepository.getOwnedEquipment(type).collect { adapter.data = it }
}
}
}
override fun onDestroy() {

View file

@ -14,9 +14,9 @@ import com.habitrpg.android.habitica.data.UserRepository
import com.habitrpg.android.habitica.databinding.FragmentItemsDialogBinding
import com.habitrpg.android.habitica.extensions.addCloseButton
import com.habitrpg.android.habitica.extensions.observeOnce
import com.habitrpg.android.habitica.extensions.subscribeWithErrorHandler
import com.habitrpg.android.habitica.helpers.ExceptionHandler
import com.habitrpg.android.habitica.helpers.MainNavigationController
import com.habitrpg.android.habitica.helpers.launchCatching
import com.habitrpg.android.habitica.interactors.FeedPetUseCase
import com.habitrpg.android.habitica.interactors.HatchPetUseCase
import com.habitrpg.android.habitica.models.inventory.Egg
@ -183,69 +183,53 @@ class ItemDialogFragment : BaseDialogFragment<FragmentItemsDialogBinding>() {
adapter?.feedingPet = this.feedingPet
}
binding?.recyclerView?.adapter = adapter
adapter?.let { adapter ->
compositeSubscription.add(
adapter.getSellItemFlowable()
.flatMap { item -> inventoryRepository.sellItem(item) }
.subscribe({ }, ExceptionHandler.rx())
)
compositeSubscription.add(
adapter.getQuestInvitationFlowable()
.flatMap { quest -> inventoryRepository.inviteToQuest(quest) }
.subscribe(
{
lifecycleScope.launch(ExceptionHandler.coroutine()) {
socialRepository.retrieveGroup("party")
if (isModal) {
dismiss()
} else {
MainNavigationController.navigate(R.id.partyFragment)
}
}
},
ExceptionHandler.rx()
)
)
compositeSubscription.add(
adapter.getOpenMysteryItemFlowable()
.flatMap { inventoryRepository.openMysteryItem(user) }
.doOnNext {
val activity = activity as? MainActivity
if (activity != null) {
val dialog = OpenedMysteryitemDialog(activity)
dialog.isCelebratory = true
dialog.setTitle(R.string.mystery_item_title)
dialog.binding.iconView.loadImage("shop_${it.key}")
dialog.binding.titleView.text = it.text
dialog.binding.descriptionView.text = it.notes
dialog.addButton(R.string.equip, true) { _, _ ->
inventoryRepository.equip("equipped", it.key ?: "").subscribe({}, ExceptionHandler.rx())
}
dialog.addCloseButton()
dialog.enqueue()
adapter?.onSellItem = {
lifecycleScope.launchCatching {
inventoryRepository.sellItem(it)
}
}
adapter?.onQuestInvitation = {
lifecycleScope.launchCatching {
inventoryRepository.inviteToQuest(it)
MainNavigationController.navigate(R.id.partyFragment)
}
}
adapter?.onOpenMysteryItem = {
lifecycleScope.launchCatching {
val item = inventoryRepository.openMysteryItem(user) ?: return@launchCatching
val activity = activity as? MainActivity
if (activity != null) {
val dialog = OpenedMysteryitemDialog(activity)
dialog.isCelebratory = true
dialog.setTitle(R.string.mystery_item_title)
dialog.binding.iconView.loadImage("shop_${it.key}")
dialog.binding.titleView.text = item.text
dialog.binding.descriptionView.text = item.notes
dialog.addButton(R.string.equip, true) { _, _ ->
lifecycleScope.launchCatching {
inventoryRepository.equip("equipped", it.key)
}
}
.subscribe({ }, ExceptionHandler.rx())
)
compositeSubscription.add(adapter.hatchPetEvents.subscribeWithErrorHandler { hatchPet(it.first, it.second) })
compositeSubscription.add(adapter.feedPetEvents.subscribeWithErrorHandler { feedPet(it) })
dialog.addCloseButton()
dialog.enqueue()
}
}
}
adapter?.onHatchPet = { pet, egg -> hatchPet(pet, egg) }
}
}
private fun feedPet(food: Food) {
val pet = feedingPet ?: return
val activity = activity ?: return
parentSubscription?.add(
feedPetUseCase.observable(
lifecycleScope.launchCatching {
feedPetUseCase.callInteractor(
FeedPetUseCase.RequestValues(
pet, food,
activity
)
).subscribeWithErrorHandler {}
)
)
}
}
override fun onResume() {
@ -267,14 +251,14 @@ class ItemDialogFragment : BaseDialogFragment<FragmentItemsDialogBinding>() {
private fun hatchPet(potion: HatchingPotion, egg: Egg) {
dismiss()
val activity = activity ?: return
parentSubscription?.add(
hatchPetUseCase.observable(
activity.lifecycleScope.launchCatching {
hatchPetUseCase.callInteractor(
HatchPetUseCase.RequestValues(
potion, egg,
activity
)
).subscribeWithErrorHandler {}
)
)
}
}
private fun loadItems() {

View file

@ -18,7 +18,6 @@ import com.habitrpg.android.habitica.data.UserRepository
import com.habitrpg.android.habitica.databinding.FragmentItemsBinding
import com.habitrpg.android.habitica.extensions.addCloseButton
import com.habitrpg.android.habitica.extensions.observeOnce
import com.habitrpg.android.habitica.extensions.subscribeWithErrorHandler
import com.habitrpg.android.habitica.helpers.ExceptionHandler
import com.habitrpg.android.habitica.helpers.MainNavigationController
import com.habitrpg.android.habitica.helpers.launchCatching
@ -141,50 +140,42 @@ class ItemRecyclerFragment : BaseFragment<FragmentItemsBinding>(), SwipeRefreshL
adapter = ItemRecyclerAdapter(context)
}
binding?.recyclerView?.adapter = adapter
adapter?.useSpecialEvents?.subscribeWithErrorHandler { onSpecialItemSelected(it) }?.let { compositeSubscription.add(it) }
adapter?.let { adapter ->
compositeSubscription.add(
adapter.getSellItemFlowable()
.flatMap { item -> inventoryRepository.sellItem(item) }
.subscribe({ }, ExceptionHandler.rx())
)
compositeSubscription.add(
adapter.getQuestInvitationFlowable()
.flatMap { quest -> inventoryRepository.inviteToQuest(quest) }
//.flatMap { socialRepository.retrieveGroup("party") }
.subscribe(
{
MainNavigationController.navigate(R.id.partyFragment)
},
ExceptionHandler.rx()
)
)
compositeSubscription.add(
adapter.getOpenMysteryItemFlowable()
.flatMap { inventoryRepository.openMysteryItem(user) }
.doOnNext {
val activity = activity as? MainActivity
if (activity != null) {
val dialog = OpenedMysteryitemDialog(activity)
dialog.isCelebratory = true
dialog.setTitle(R.string.mystery_item_title)
dialog.binding.iconView.loadImage("shop_${it.key}")
dialog.binding.titleView.text = it.text
dialog.binding.descriptionView.text = it.notes
dialog.addButton(R.string.equip, true) { _, _ ->
inventoryRepository.equip("equipped", it.key ?: "").subscribe({}, ExceptionHandler.rx())
}
dialog.addCloseButton()
dialog.enqueue()
adapter?.onUseSpecialItem = { onSpecialItemSelected(it) }
adapter?.onSellItem = {
lifecycleScope.launchCatching {
inventoryRepository.sellItem(it)
}
}
adapter?.onQuestInvitation = {
lifecycleScope.launchCatching {
inventoryRepository.inviteToQuest(it)
MainNavigationController.navigate(R.id.partyFragment)
}
}
adapter?.onOpenMysteryItem = {
lifecycleScope.launchCatching {
val item = inventoryRepository.openMysteryItem(user) ?: return@launchCatching
val activity = activity as? MainActivity
if (activity != null) {
val dialog = OpenedMysteryitemDialog(activity)
dialog.isCelebratory = true
dialog.setTitle(R.string.mystery_item_title)
dialog.binding.iconView.loadImage("shop_${it.key}")
dialog.binding.titleView.text = item.text
dialog.binding.descriptionView.text = item.notes
dialog.addButton(R.string.equip, true) { _, _ ->
lifecycleScope.launchCatching {
inventoryRepository.equip("equipped", it.key)
}
}
.subscribe({ }, ExceptionHandler.rx())
)
compositeSubscription.add(adapter.startHatchingEvents.subscribeWithErrorHandler { showHatchingDialog(it) })
compositeSubscription.add(adapter.hatchPetEvents.subscribeWithErrorHandler { hatchPet(it.first, it.second) })
compositeSubscription.addAll(adapter.startNewPartyEvents.subscribeWithErrorHandler { createNewParty() })
dialog.addCloseButton()
dialog.enqueue()
}
}
}
adapter?.onStartHatching = { showHatchingDialog(it) }
adapter?.onHatchPet = { pet, egg -> hatchPet(pet, egg) }
adapter?.onCreateNewParty = { createNewParty() }
}
}
@ -218,14 +209,14 @@ class ItemRecyclerFragment : BaseFragment<FragmentItemsBinding>(), SwipeRefreshL
private fun hatchPet(potion: HatchingPotion, egg: Egg) {
(activity as? BaseActivity)?.let {
compositeSubscription.add(
hatchPetUseCase.observable(
lifecycleScope.launchCatching {
hatchPetUseCase.callInteractor(
HatchPetUseCase.RequestValues(
potion, egg,
it
)
).subscribeWithErrorHandler {}
)
)
}
}
}

View file

@ -14,6 +14,7 @@ import com.habitrpg.android.habitica.data.SocialRepository
import com.habitrpg.android.habitica.databinding.FragmentRefreshRecyclerviewBinding
import com.habitrpg.android.habitica.helpers.AppConfigManager
import com.habitrpg.android.habitica.helpers.ExceptionHandler
import com.habitrpg.android.habitica.helpers.launchCatching
import com.habitrpg.android.habitica.models.shops.Shop
import com.habitrpg.android.habitica.models.shops.ShopCategory
import com.habitrpg.android.habitica.models.shops.ShopItem
@ -26,8 +27,8 @@ import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel
import com.habitrpg.android.habitica.ui.views.CurrencyViews
import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog
import com.habitrpg.common.habitica.helpers.RecyclerViewState
import io.reactivex.rxjava3.kotlin.combineLatest
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import javax.inject.Inject
@ -180,62 +181,48 @@ open class ShopFragment : BaseMainFragment<FragmentRefreshRecyclerviewBinding>()
Shop.SEASONAL_SHOP -> "seasonal"
else -> ""
}
compositeSubscription.add(
this.inventoryRepository.retrieveShopInventory(shopUrl)
.map { shop1 ->
if (shop1.identifier == Shop.MARKET) {
val user = userViewModel.user.value
val specialCategory = ShopCategory()
specialCategory.text = getString(R.string.special)
if (user?.isValid == true && user.purchased?.plan?.isActive == true) {
val item = ShopItem.makeGemItem(context?.resources)
item.limitedNumberLeft = user.purchased?.plan?.numberOfGemsLeft
specialCategory.items.add(item)
}
specialCategory.items.add(ShopItem.makeFortifyItem(context?.resources))
shop1.categories.add(specialCategory)
}
when (shop1.identifier) {
Shop.TIME_TRAVELERS_SHOP -> {
formatTimeTravelersShop(shop1)
}
Shop.SEASONAL_SHOP -> {
shop1.categories.sortWith(
compareBy<ShopCategory> { it.items.size != 1 }
.thenBy { it.items.firstOrNull()?.currency != "gold" }
.thenByDescending { it.items.firstOrNull()?.event?.end }
)
shop1
}
else -> {
shop1
}
}
lifecycleScope.launchCatching({
binding?.recyclerView?.state = RecyclerViewState.FAILED
}) {
val shop1 = inventoryRepository.retrieveShopInventory(shopUrl) ?: return@launchCatching
if (shop1.identifier == Shop.MARKET) {
val user = userViewModel.user.value
val specialCategory = ShopCategory()
specialCategory.text = getString(R.string.special)
if (user?.isValid == true && user.purchased?.plan?.isActive == true) {
val item = ShopItem.makeGemItem(context?.resources)
item.limitedNumberLeft = user.purchased?.plan?.numberOfGemsLeft
specialCategory.items.add(item)
}
.subscribe(
{
this.shop = it
this.adapter?.setShop(it)
},
{
binding?.recyclerView?.state = RecyclerViewState.FAILED
ExceptionHandler.reportError(it)
},
{
binding?.refreshLayout?.isRefreshing = false
}
)
)
specialCategory.items.add(ShopItem.makeFortifyItem(context?.resources))
shop1.categories.add(specialCategory)
}
when (shop1.identifier) {
Shop.TIME_TRAVELERS_SHOP -> {
formatTimeTravelersShop(shop1)
}
Shop.SEASONAL_SHOP -> {
shop1.categories.sortWith(
compareBy<ShopCategory> { it.items.size != 1 }
.thenBy { it.items.firstOrNull()?.currency != "gold" }
.thenByDescending { it.items.firstOrNull()?.event?.end }
)
}
}
shop = shop1
adapter?.setShop(shop1)
binding?.refreshLayout?.isRefreshing = false
}
compositeSubscription.add(
this.inventoryRepository.getOwnedItems()
.subscribe({ adapter?.setOwnedItems(it) }, ExceptionHandler.rx())
)
compositeSubscription.add(
this.inventoryRepository.getInAppRewards()
lifecycleScope.launchCatching {
inventoryRepository.getOwnedItems()
.collect { adapter?.setOwnedItems(it) }
}
lifecycleScope.launchCatching {
inventoryRepository.getInAppRewards()
.map { rewards -> rewards.map { it.key } }
.subscribe({ adapter?.setPinnedItemKeys(it) }, ExceptionHandler.rx())
)
.collect { adapter?.setPinnedItemKeys(it) }
}
}
private fun formatTimeTravelersShop(shop: Shop): Shop {
@ -264,29 +251,22 @@ open class ShopFragment : BaseMainFragment<FragmentRefreshRecyclerviewBinding>()
}
private fun loadMarketGear() {
compositeSubscription.add(
inventoryRepository.retrieveMarketGear()
.combineLatest(
inventoryRepository.getOwnedEquipment().map { equipment -> equipment.map { it.key } }
)
.map { (shop, equipment) ->
for (category in shop.categories) {
lifecycleScope.launchCatching {
val shop = inventoryRepository.retrieveMarketGear()
inventoryRepository.getOwnedEquipment()
.map { equipment -> equipment.map { it.key } }
.collect { equipment ->
for (category in shop?.categories ?: emptyList()) {
val items = category.items.asSequence().filter {
!equipment.contains(it.key)
}.sortedBy { it.locked }.toList()
category.items.clear()
category.items.addAll(items)
}
shop
gearCategories = shop?.categories
adapter?.gearCategories = shop?.categories ?: mutableListOf()
}
.subscribe(
{
this.gearCategories = it.categories
adapter?.gearCategories = it.categories
},
ExceptionHandler.rx()
)
)
}
}
override fun injectFragment(component: UserComponent) {

View file

@ -12,6 +12,7 @@ import com.habitrpg.android.habitica.data.InventoryRepository
import com.habitrpg.android.habitica.databinding.FragmentRefreshRecyclerviewBinding
import com.habitrpg.android.habitica.extensions.getTranslatedType
import com.habitrpg.android.habitica.helpers.ExceptionHandler
import com.habitrpg.android.habitica.helpers.launchCatching
import com.habitrpg.android.habitica.models.inventory.Mount
import com.habitrpg.android.habitica.models.inventory.StableSection
import com.habitrpg.android.habitica.models.user.OwnedMount
@ -97,13 +98,12 @@ class MountDetailRecyclerFragment :
binding?.recyclerView?.itemAnimator = SafeDefaultItemAnimator()
this.loadItems()
adapter?.getEquipFlowable()?.flatMap { key -> inventoryRepository.equip("mount", key) }
?.subscribe(
{
adapter?.currentMount = it.currentMount
},
ExceptionHandler.rx()
)?.let { compositeSubscription.add(it) }
adapter?.onEquip = {
lifecycleScope.launchCatching {
val items = inventoryRepository.equip("mount", it)
adapter?.currentMount = items?.currentMount
}
}
}
userViewModel.user.observe(viewLifecycleOwner) { adapter?.currentMount = it?.currentMount }

View file

@ -13,6 +13,7 @@ import com.habitrpg.android.habitica.databinding.FragmentRefreshRecyclerviewBind
import com.habitrpg.android.habitica.extensions.getTranslatedType
import com.habitrpg.android.habitica.extensions.subscribeWithErrorHandler
import com.habitrpg.android.habitica.helpers.ExceptionHandler
import com.habitrpg.android.habitica.helpers.launchCatching
import com.habitrpg.android.habitica.interactors.FeedPetUseCase
import com.habitrpg.android.habitica.models.inventory.Egg
import com.habitrpg.android.habitica.models.inventory.Food
@ -124,23 +125,19 @@ class PetDetailRecyclerFragment :
binding?.recyclerView?.itemAnimator = SafeDefaultItemAnimator()
this.loadItems()
compositeSubscription.add(
adapter.getEquipFlowable()
.flatMap { key -> inventoryRepository.equip("pet", key) }
.subscribe(
{
adapter.currentPet = it.currentPet
},
ExceptionHandler.rx()
)
)
adapter.onEquip = {
lifecycleScope.launchCatching {
val items = inventoryRepository.equip("pet", it)
adapter.currentPet = items?.currentPet
}
}
userViewModel.user.observe(viewLifecycleOwner) { adapter.currentPet = it?.currentPet }
compositeSubscription.add(adapter.feedFlowable.subscribe({
adapter.onFeed = { pet, food ->
showFeedingDialog(
it.first,
it.second
pet,
food
)
}, ExceptionHandler.rx()))
}
view.post { setGridSpanCount(view.width) }
}
@ -179,10 +176,9 @@ class PetDetailRecyclerFragment :
return@map mountMap
}.collect { adapter.setOwnedMounts(it) }
}
compositeSubscription.add(
inventoryRepository.getOwnedItems(true)
.subscribe({ adapter.setOwnedItems(it) }, ExceptionHandler.rx())
)
lifecycleScope.launchCatching {
inventoryRepository.getOwnedItems(true).collect { adapter.setOwnedItems(it) }
}
lifecycleScope.launch(ExceptionHandler.coroutine()) {
val mounts = inventoryRepository.getMounts(
animalType,
@ -231,14 +227,14 @@ class PetDetailRecyclerFragment :
private fun showFeedingDialog(pet: Pet, food: Food?) {
if (food != null) {
val context = activity ?: context ?: return
compositeSubscription.add(
feedPetUseCase.observable(
lifecycleScope.launchCatching {
feedPetUseCase.callInteractor(
FeedPetUseCase.RequestValues(
pet, food,
context
)
).subscribeWithErrorHandler {}
)
)
}
return
}
val fragment = ItemDialogFragment()

Some files were not shown because too many files have changed in this diff Show more