Begin migrating from RxJava to coroutines

This commit is contained in:
Phillip Thelen 2022-09-26 14:01:51 +02:00
parent bf5bb9939b
commit 03d5648003
138 changed files with 1788 additions and 1912 deletions

View file

@ -3,7 +3,6 @@ package com.habitrpg.android.habitica
import android.content.SharedPreferences
import androidx.lifecycle.MutableLiveData
import com.habitrpg.android.habitica.api.GSonFactoryCreator
import com.habitrpg.common.habitica.api.HostConfig
import com.habitrpg.android.habitica.api.MaintenanceApiService
import com.habitrpg.android.habitica.data.ApiClient
import com.habitrpg.android.habitica.data.ContentRepository
@ -14,9 +13,9 @@ import com.habitrpg.android.habitica.data.TaskRepository
import com.habitrpg.android.habitica.data.TutorialRepository
import com.habitrpg.android.habitica.data.UserRepository
import com.habitrpg.android.habitica.helpers.AppConfigManager
import com.habitrpg.android.habitica.helpers.ExceptionHandler
import com.habitrpg.android.habitica.helpers.MainNavigationController
import com.habitrpg.android.habitica.helpers.NotificationsManager
import com.habitrpg.android.habitica.helpers.RxErrorHandler
import com.habitrpg.android.habitica.helpers.SoundManager
import com.habitrpg.android.habitica.interactors.FeedPetUseCase
import com.habitrpg.android.habitica.interactors.HatchPetUseCase
@ -29,22 +28,23 @@ import com.habitrpg.android.habitica.models.inventory.QuestContent
import com.habitrpg.android.habitica.models.user.User
import com.habitrpg.android.habitica.proxy.AnalyticsManager
import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel
import com.habitrpg.common.habitica.api.HostConfig
import com.kaspersky.kaspresso.testcases.api.testcase.TestCase
import io.mockk.clearAllMocks
import io.mockk.every
import io.mockk.mockk
import io.mockk.mockkObject
import io.mockk.slot
import io.reactivex.rxjava3.core.BackpressureStrategy
import io.reactivex.rxjava3.core.Flowable
import io.reactivex.rxjava3.subjects.PublishSubject
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.flowOf
import org.junit.Before
import java.io.InputStreamReader
import java.lang.reflect.Type
import kotlin.reflect.KCallable
import kotlin.reflect.KMutableProperty1
import kotlin.reflect.full.starProjectedType
import kotlin.reflect.jvm.javaField
import org.junit.Before
open class HabiticaTestCase : TestCase() {
val gson = GSonFactoryCreator.createGson()
@ -68,8 +68,7 @@ open class HabiticaTestCase : TestCase() {
val hatchPetUseCase: HatchPetUseCase = mockk(relaxed = true)
val feedPetUseCase: FeedPetUseCase = mockk(relaxed = true)
val userSubject = PublishSubject.create<User>()
val userEvents: Flowable<User> = userSubject.toFlowable(BackpressureStrategy.DROP)
val userState = MutableStateFlow<User?>(null)
var user = User()
lateinit var content: ContentResult
@ -85,27 +84,27 @@ open class HabiticaTestCase : TestCase() {
user = loadJsonFile("user", User::class.java)
user.stats?.lvl = 20
user.stats?.points = 30
every { userRepository.getUser() } returns userEvents
every { userRepository.getUser() } returns userState
every { userViewModel.user } returns MutableLiveData<User?>(user)
mockkObject(RxErrorHandler)
every { RxErrorHandler.reportError(capture(errorSlot)) } answers {
mockkObject(ExceptionHandler)
every { ExceptionHandler.reportError(capture(errorSlot)) } answers {
throw errorSlot.captured
}
every { socialRepository.getUnmanagedCopy(capture(unmanagedSlot)) } answers { unmanagedSlot.captured }
content = loadJsonFile("content", ContentResult::class.java)
every { inventoryRepository.getPets() } returns Flowable.just(content.pets)
every { inventoryRepository.getMounts() } returns Flowable.just(content.mounts)
every { inventoryRepository.getPets() } returns flowOf(content.pets)
every { inventoryRepository.getMounts() } returns flowOf(content.mounts)
every { inventoryRepository.getItemsFlowable(Food::class.java) } returns Flowable.just(content.food)
every { inventoryRepository.getItemsFlowable(Egg::class.java) } returns Flowable.just(content.eggs)
every { inventoryRepository.getItemsFlowable(HatchingPotion::class.java) } returns Flowable.just(content.hatchingPotions)
every { inventoryRepository.getItemsFlowable(QuestContent::class.java) } returns Flowable.just(content.quests)
every { inventoryRepository.getItemsFlowable(Food::class.java, any()) } returns Flowable.just(content.food)
every { inventoryRepository.getItemsFlowable(Egg::class.java, any()) } answers {
Flowable.just(content.eggs)
every { inventoryRepository.getItems(Food::class.java, any()) } returns flowOf(content.food)
every { inventoryRepository.getItems(Egg::class.java, any()) } answers {
flowOf(content.eggs)
}
every { inventoryRepository.getItemsFlowable(HatchingPotion::class.java, any()) } returns Flowable.just(content.hatchingPotions)
every { inventoryRepository.getItemsFlowable(QuestContent::class.java, any()) } returns Flowable.just(content.quests)
every { inventoryRepository.getItems(HatchingPotion::class.java, any()) } returns flowOf(content.hatchingPotions)
every { inventoryRepository.getItems(QuestContent::class.java, any()) } returns flowOf(content.quests)
}
internal fun <T> loadJsonFile(s: String, type: Type): T {

View file

@ -63,7 +63,7 @@ class StatsFragmentTest : FragmentTestCase<StatsFragment, FragmentStatsBinding,
fun setUpUser() {
user.stats?.lvl = 20
user.stats?.points = 30
userSubject.onNext(user)
userState.onNext(user)
every { inventoryRepository.getEquipment(listOf()) } returns Flowable.just(listOf())
}

View file

@ -115,7 +115,7 @@ internal class ItemRecyclerFragmentTest : FragmentTestCase<ItemRecyclerFragment,
items = (items + items).sortedBy { it.key }
Flowable.just(items)
}
every { inventoryRepository.getItemsFlowable(Food::class.java, any()) } answers {
every { inventoryRepository.getItems(Food::class.java, any()) } answers {
Flowable.just((content.eggs + content.eggs).sortedBy { it.key })
}
fragment.itemType = "food"

View file

@ -29,7 +29,7 @@ import com.habitrpg.android.habitica.components.AppComponent
import com.habitrpg.android.habitica.components.UserComponent
import com.habitrpg.android.habitica.data.ApiClient
import com.habitrpg.android.habitica.helpers.AdHandler
import com.habitrpg.android.habitica.helpers.RxErrorHandler
import com.habitrpg.android.habitica.helpers.ExceptionHandler
import com.habitrpg.android.habitica.helpers.notifications.PushNotificationManager
import com.habitrpg.android.habitica.modules.UserModule
import com.habitrpg.android.habitica.modules.UserRepositoryModule
@ -86,7 +86,7 @@ abstract class HabiticaBaseApplication : Application(), Application.ActivityLife
}
setupCoil()
RxErrorHandler.init(analyticsManager)
ExceptionHandler.init(analyticsManager)
FirebaseAnalytics.getInstance(this).setUserProperty("app_testing_level", BuildConfig.TESTING_LEVEL)
@ -120,7 +120,6 @@ abstract class HabiticaBaseApplication : Application(), Application.ActivityLife
.deleteRealmIfMigrationNeeded()
.allowWritesOnUiThread(true)
.compactOnLaunch { totalBytes, usedBytes ->
// Compact if the file is over 100MB in size and less than 50% 'used'
val oneHundredMB = 50 * 1024 * 1024
(totalBytes > oneHundredMB) && (usedBytes / totalBytes) < 0.5

View file

@ -26,13 +26,13 @@ import com.habitrpg.android.habitica.models.tasks.TaskList
import com.habitrpg.android.habitica.models.user.Items
import com.habitrpg.android.habitica.models.user.Stats
import com.habitrpg.android.habitica.models.user.User
import com.habitrpg.common.habitica.models.HabitResponse
import com.habitrpg.common.habitica.models.PurchaseValidationRequest
import com.habitrpg.common.habitica.models.PurchaseValidationResult
import com.habitrpg.common.habitica.models.auth.UserAuth
import com.habitrpg.common.habitica.models.auth.UserAuthResponse
import com.habitrpg.common.habitica.models.auth.UserAuthSocial
import com.habitrpg.shared.habitica.models.responses.FeedResponse
import com.habitrpg.common.habitica.models.HabitResponse
import com.habitrpg.shared.habitica.models.responses.Status
import com.habitrpg.shared.habitica.models.responses.TaskDirectionData
import com.habitrpg.shared.habitica.models.responses.VerifyUsernameResponse
@ -49,27 +49,27 @@ import retrofit2.http.Query
@JvmSuppressWildcards
interface ApiService {
@get:GET("status")
val status: Flowable<HabitResponse<Status>>
@GET("status")
suspend fun getStatus(): HabitResponse<Status>
/* user API */
@get:GET("user/")
val user: Flowable<HabitResponse<User>>
@GET("user/")
suspend fun getUser(): HabitResponse<User>
@GET("inbox/messages")
fun getInboxMessages(@Query("conversation") uuid: String, @Query("page") page: Int): Flowable<HabitResponse<List<ChatMessage>>>
suspend fun getInboxMessages(@Query("conversation") uuid: String, @Query("page") page: Int): HabitResponse<List<ChatMessage>>
@GET("inbox/conversations")
fun getInboxConversations(): Flowable<HabitResponse<List<InboxConversation>>>
@get:GET("tasks/user")
val tasks: Flowable<HabitResponse<TaskList>>
@GET("tasks/user")
suspend fun getTasks(): HabitResponse<TaskList>
@get:GET("world-state")
val worldState: Flowable<HabitResponse<WorldState>>
@GET("world-state")
suspend fun worldState(): HabitResponse<WorldState>
@GET("content")
fun getContent(@Query("language") language: String?): Flowable<HabitResponse<ContentResult>>
suspend fun getContent(@Query("language") language: String?): HabitResponse<ContentResult>
@PUT("user/")
fun updateUser(@Body updateDictionary: Map<String, Any>): Flowable<HabitResponse<User>>
@ -177,10 +177,10 @@ interface ApiService {
fun loginApple(@Body auth: Map<String, Any>): Flowable<HabitResponse<UserAuthResponse>>
@POST("user/sleep")
fun sleep(): Flowable<HabitResponse<Boolean>>
suspend fun sleep(): HabitResponse<Boolean>
@POST("user/revive")
fun revive(): Flowable<HabitResponse<User>>
suspend fun revive(): HabitResponse<User>
@POST("user/class/cast/{skill}")
fun useSkill(
@ -193,13 +193,13 @@ interface ApiService {
fun useSkill(@Path("skill") skillName: String, @Query("targetType") targetType: String): Flowable<HabitResponse<SkillResponse>>
@POST("user/change-class")
fun changeClass(): Flowable<HabitResponse<User>>
suspend fun changeClass(): HabitResponse<User>
@POST("user/change-class")
fun changeClass(@Query("class") className: String): Flowable<HabitResponse<User>>
suspend fun changeClass(@Query("class") className: String): HabitResponse<User>
@POST("user/disable-classes")
fun disableClasses(): Flowable<HabitResponse<User>>
suspend fun disableClasses(): HabitResponse<User>
@POST("user/mark-pms-read")
fun markPrivateMessagesRead(): Flowable<Void>
@ -210,25 +210,25 @@ interface ApiService {
fun listGroups(@Query("type") type: String): Flowable<HabitResponse<List<Group>>>
@GET("groups/{gid}")
fun getGroup(@Path("gid") groupId: String): Flowable<HabitResponse<Group>>
suspend fun getGroup(@Path("gid") groupId: String): HabitResponse<Group>
@POST("groups")
fun createGroup(@Body item: Group): Flowable<HabitResponse<Group>>
suspend fun createGroup(@Body item: Group): HabitResponse<Group>
@PUT("groups/{id}")
fun updateGroup(@Path("id") id: String, @Body item: Group): Flowable<HabitResponse<Group>>
suspend fun updateGroup(@Path("id") id: String, @Body item: Group): HabitResponse<Group>
@POST("groups/{groupID}/removeMember/{userID}")
fun removeMemberFromGroup(@Path("groupID") groupID: String, @Path("userID") userID: String): Flowable<HabitResponse<Void>>
suspend fun removeMemberFromGroup(@Path("groupID") groupID: String, @Path("userID") userID: String): HabitResponse<Void>
@GET("groups/{gid}/chat")
fun listGroupChat(@Path("gid") groupId: String): Flowable<HabitResponse<List<ChatMessage>>>
suspend fun listGroupChat(@Path("gid") groupId: String): HabitResponse<List<ChatMessage>>
@POST("groups/{gid}/join")
fun joinGroup(@Path("gid") groupId: String): Flowable<HabitResponse<Group>>
suspend fun joinGroup(@Path("gid") groupId: String): HabitResponse<Group>
@POST("groups/{gid}/leave")
fun leaveGroup(@Path("gid") groupId: String, @Query("keepChallenges") keepChallenges: String): Flowable<HabitResponse<Void>>
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>>
@ -240,17 +240,17 @@ interface ApiService {
fun deleteInboxMessage(@Path("messageId") messageId: String): Flowable<HabitResponse<Void>>
@GET("groups/{gid}/members")
fun getGroupMembers(
suspend fun getGroupMembers(
@Path("gid") groupId: String,
@Query("includeAllPublicFields") includeAllPublicFields: Boolean?
): Flowable<HabitResponse<List<Member>>>
): HabitResponse<List<Member>>
@GET("groups/{gid}/members")
fun getGroupMembers(
suspend fun getGroupMembers(
@Path("gid") groupId: String,
@Query("includeAllPublicFields") includeAllPublicFields: Boolean?,
@Query("lastId") lastId: String
): Flowable<HabitResponse<List<Member>>>
): HabitResponse<List<Member>>
// Like returns the full chat list
@POST("groups/{gid}/chat/{mid}/like")
@ -300,7 +300,7 @@ interface ApiService {
fun validateSubscription(@Body request: PurchaseValidationRequest): Flowable<HabitResponse<Void>>
@GET("/iap/android/subscribe/cancel")
fun cancelSubscription(): Flowable<HabitResponse<Void>>
suspend fun cancelSubscription(): HabitResponse<Void>
@POST("/iap/android/norenew-subscribe")
fun validateNoRenewSubscription(@Body request: PurchaseValidationRequest): Flowable<HabitResponse<Void>>
@ -310,16 +310,16 @@ interface ApiService {
// Members URL
@GET("members/{mid}")
fun getMember(@Path("mid") memberId: String): Flowable<HabitResponse<Member>>
suspend fun getMember(@Path("mid") memberId: String): HabitResponse<Member>
@GET("members/username/{username}")
fun getMemberWithUsername(@Path("username") username: String): Flowable<HabitResponse<Member>>
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>>>
@POST("members/send-private-message")
fun postPrivateMessage(@Body messageDetails: Map<String, String>): Flowable<HabitResponse<PostChatMessageResult>>
suspend fun postPrivateMessage(@Body messageDetails: Map<String, String>): HabitResponse<PostChatMessageResult>
@GET("members/find/{username}")
fun findUsernames(
@ -440,7 +440,7 @@ interface ApiService {
fun blockMember(@Path("userID") userID: String): Flowable<HabitResponse<List<String>>>
@POST("user/reroll")
fun reroll(): Flowable<HabitResponse<User>>
suspend fun reroll(): HabitResponse<User>
// Team Plans
@ -448,5 +448,5 @@ interface ApiService {
fun getTeamPlans(): Flowable<HabitResponse<List<TeamPlan>>>
@GET("tasks/group/{groupID}")
fun getTeamPlanTasks(@Path("groupID") groupId: String): Flowable<HabitResponse<TaskList>>
suspend fun getTeamPlanTasks(@Path("groupID") groupId: String): HabitResponse<TaskList>
}

View file

@ -27,12 +27,12 @@ import com.habitrpg.android.habitica.models.user.Items
import com.habitrpg.android.habitica.models.user.Stats
import com.habitrpg.android.habitica.models.user.User
import com.habitrpg.common.habitica.api.HostConfig
import com.habitrpg.common.habitica.models.HabitResponse
import com.habitrpg.common.habitica.models.PurchaseValidationRequest
import com.habitrpg.common.habitica.models.PurchaseValidationResult
import com.habitrpg.common.habitica.models.auth.UserAuthResponse
import com.habitrpg.shared.habitica.models.responses.ErrorResponse
import com.habitrpg.shared.habitica.models.responses.FeedResponse
import com.habitrpg.common.habitica.models.HabitResponse
import com.habitrpg.shared.habitica.models.responses.Status
import com.habitrpg.shared.habitica.models.responses.TaskDirectionData
import com.habitrpg.shared.habitica.models.responses.VerifyUsernameResponse
@ -44,23 +44,19 @@ interface ApiClient {
val hostConfig: HostConfig
val status: Flowable<Status>
val content: Flowable<ContentResult>
suspend fun getStatus(): Status?
/* user API */
val user: Flowable<User>
val tasks: Flowable<TaskList>
suspend fun getTasks(): TaskList?
/* challenges api */
fun getUserChallenges(page: Int, memberOnly: Boolean): Flowable<List<Challenge>>
val worldState: Flowable<WorldState>
suspend fun getWorldState(): WorldState?
fun setLanguageCode(languageCode: String)
fun getContent(language: String): Flowable<ContentResult>
suspend fun getContent(language: String? = null): ContentResult?
fun updateUser(updateDictionary: Map<String, Any>): Flowable<User>
@ -83,7 +79,7 @@ interface ApiClient {
fun purchaseSpecialSpell(key: String): Flowable<Void>
fun validateSubscription(request: PurchaseValidationRequest): Flowable<Any>
fun validateNoRenewSubscription(request: PurchaseValidationRequest): Flowable<Any>
fun cancelSubscription(): Flowable<Void>
suspend fun cancelSubscription(): Void?
fun sellItem(itemType: String, itemKey: String): Flowable<User>
@ -127,19 +123,16 @@ interface ApiClient {
fun loginApple(authToken: String): Flowable<UserAuthResponse>
fun sleep(): Flowable<Boolean>
fun revive(): Flowable<User>
suspend fun sleep(): Boolean?
suspend fun revive(): User?
fun useSkill(skillName: String, targetType: String, targetId: String): Flowable<SkillResponse>
fun useSkill(skillName: String, targetType: String): Flowable<SkillResponse>
fun changeClass(): Flowable<User>
suspend fun changeClass(className: String?): User?
fun changeClass(className: String): Flowable<User>
fun disableClasses(): Flowable<User>
suspend fun disableClasses(): User?
fun markPrivateMessagesRead(): Flowable<Void>
@ -147,26 +140,26 @@ interface ApiClient {
fun listGroups(type: String): Flowable<List<Group>>
fun getGroup(groupId: String): Flowable<Group>
suspend fun getGroup(groupId: String): Group?
fun createGroup(group: Group): Flowable<Group>
fun updateGroup(id: String, item: Group): Flowable<Group>
fun removeMemberFromGroup(groupID: String, userID: String): Flowable<Void>
suspend fun createGroup(group: Group): Group?
suspend fun updateGroup(id: String, item: Group): Group?
suspend fun removeMemberFromGroup(groupID: String, userID: String): Void?
fun listGroupChat(groupId: String): Flowable<List<ChatMessage>>
suspend fun listGroupChat(groupId: String): List<ChatMessage>?
fun joinGroup(groupId: String): Flowable<Group>
suspend fun joinGroup(groupId: String): Group?
fun leaveGroup(groupId: String, keepChallenges: String): Flowable<Void>
suspend fun leaveGroup(groupId: String, keepChallenges: String): Void?
fun postGroupChat(groupId: String, message: Map<String, String>): Flowable<PostChatMessageResult>
fun deleteMessage(groupId: String, messageId: String): Flowable<Void>
fun deleteInboxMessage(id: String): Flowable<Void>
fun getGroupMembers(groupId: String, includeAllPublicFields: Boolean?): Flowable<List<Member>>
suspend fun getGroupMembers(groupId: String, includeAllPublicFields: Boolean?): List<Member>?
fun getGroupMembers(groupId: String, includeAllPublicFields: Boolean?, lastId: String): Flowable<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>
@ -199,12 +192,12 @@ interface ApiClient {
fun changeCustomDayStart(updateObject: Map<String, Any>): Flowable<User>
// Members URL
fun getMember(memberId: String): Flowable<Member>
fun getMemberWithUsername(username: String): Flowable<Member>
suspend fun getMember(memberId: String): Member?
suspend fun getMemberWithUsername(username: String): Member?
fun getMemberAchievements(memberId: String): Flowable<List<Achievement>>
fun postPrivateMessage(messageDetails: Map<String, String>): Flowable<PostChatMessageResult>
suspend fun postPrivateMessage(messageDetails: Map<String, String>): PostChatMessageResult?
fun retrieveShopIventory(identifier: String): Flowable<Shop>
@ -243,8 +236,8 @@ interface ApiClient {
fun hasAuthenticationKeys(): Boolean
fun retrieveUser(withTasks: Boolean): Flowable<User>
fun retrieveInboxMessages(uuid: String, page: Int): Flowable<List<ChatMessage>>
suspend fun retrieveUser(withTasks: Boolean = false): User?
suspend fun retrieveInboxMessages(uuid: String, page: Int): List<ChatMessage>?
fun retrieveInboxConversations(): Flowable<List<InboxConversation>>
fun <T : Any> configureApiCallObserver(): FlowableTransformer<HabitResponse<T>, T>
@ -253,7 +246,7 @@ interface ApiClient {
fun runCron(): Flowable<Void>
fun reroll(): Flowable<User>
suspend fun reroll(): User?
fun resetAccount(): Flowable<Void>
fun deleteAccount(password: String): Flowable<Void>
@ -282,5 +275,5 @@ interface ApiClient {
fun unlinkAllTasks(challengeID: String?, keepOption: String): Flowable<Void>
fun blockMember(userID: String): Flowable<List<String>>
fun getTeamPlans(): Flowable<List<TeamPlan>>
fun getTeamPlanTasks(teamID: String): Flowable<TaskList>
suspend fun getTeamPlanTasks(teamID: String): TaskList?
}

View file

@ -4,9 +4,9 @@ import com.habitrpg.android.habitica.models.ContentResult
import com.habitrpg.android.habitica.models.WorldState
import io.reactivex.rxjava3.core.Flowable
interface ContentRepository : BaseRepository {
fun retrieveContent(forced: Boolean = false): Flowable<ContentResult>
interface ContentRepository: BaseRepository {
suspend fun retrieveContent(forced: Boolean = false): ContentResult?
fun retrieveWorldState(): Flowable<WorldState>
suspend fun retrieveWorldState(): WorldState?
fun getWorldState(): Flowable<WorldState>
}

View file

@ -35,7 +35,7 @@ interface InventoryRepository : BaseRepository {
fun getPets(): Flow<List<Pet>>
fun getOwnedPets(): Flow<List<OwnedPet>>
fun getQuestContent(key: String): Flowable<QuestContent>
fun getQuestContent(key: String): Flow<QuestContent?>
fun getQuestContent(keys: List<String>): Flow<List<QuestContent>>
fun getEquipment(searchedKeys: List<String>): Flowable<out List<Equipment>>
@ -86,7 +86,7 @@ interface InventoryRepository : BaseRepository {
fun purchaseItem(purchaseType: String, key: String, purchaseQuantity: Int): Flowable<Void>
fun togglePinnedItem(item: ShopItem): Flowable<List<ShopItem>>
fun getItemsFlowable(itemClass: Class<out Item>, keys: Array<String>): Flow<List<Item>>
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>

View file

@ -11,14 +11,14 @@ import com.habitrpg.android.habitica.models.social.GroupMembership
import com.habitrpg.android.habitica.models.social.InboxConversation
import com.habitrpg.android.habitica.models.user.User
import io.reactivex.rxjava3.core.Flowable
import io.reactivex.rxjava3.core.Single
import io.realm.RealmResults
import kotlinx.coroutines.flow.Flow
interface SocialRepository : BaseRepository {
fun getPublicGuilds(): Flowable<out List<Group>>
fun getUserGroups(type: String?): Flowable<out List<Group>>
fun retrieveGroupChat(groupId: String): Single<List<ChatMessage>>
fun getUserGroups(type: String?): Flow<List<Group>>
suspend fun retrieveGroupChat(groupId: String): List<ChatMessage>?
fun getGroupChat(groupId: String): Flowable<out List<ChatMessage>>
fun markMessagesSeen(seenGroupId: String)
@ -40,52 +40,51 @@ interface SocialRepository : BaseRepository {
fun postGroupChat(groupId: String, message: String): Flowable<PostChatMessageResult>
fun retrieveGroup(id: String): Flowable<Group>
suspend fun retrieveGroup(id: String): Group?
fun getGroup(id: String?): Flow<Group?>
fun getGroupFlowable(id: String?): Flowable<Group>
fun leaveGroup(id: String?, keepChallenges: Boolean): Flowable<Group>
suspend fun leaveGroup(id: String?, keepChallenges: Boolean): Group?
fun joinGroup(id: String?): Flowable<Group>
suspend fun joinGroup(id: String?): Group?
fun createGroup(
suspend fun createGroup(
name: String?,
description: String?,
leader: String?,
type: String?,
privacy: String?,
leaderCreateChallenge: Boolean?
): Flowable<Group>
): Group?
fun updateGroup(
suspend fun updateGroup(
group: Group?,
name: String?,
description: String?,
leader: String?,
leaderCreateChallenge: Boolean?
): Flowable<Group>
): Group?
fun retrieveGroups(type: String): Flowable<List<Group>>
fun getGroups(type: String): Flowable<out List<Group>>
fun getInboxMessages(replyToUserID: String?): Flowable<out List<ChatMessage>>
fun retrieveInboxMessages(uuid: String, page: Int): Flowable<List<ChatMessage>>
fun getInboxMessages(replyToUserID: String?): Flow<RealmResults<ChatMessage>>
suspend fun retrieveInboxMessages(uuid: String, page: Int): List<ChatMessage>?
fun retrieveInboxConversations(): Flowable<List<InboxConversation>>
fun getInboxConversations(): Flowable<out List<InboxConversation>>
fun postPrivateMessage(
fun getInboxConversations(): Flow<RealmResults<InboxConversation>>
suspend fun postPrivateMessage(
recipientId: String,
messageObject: HashMap<String, String>
): Flowable<List<ChatMessage>>
): List<ChatMessage>?
fun postPrivateMessage(recipientId: String, message: String): Flowable<List<ChatMessage>>
suspend fun postPrivateMessage(recipientId: String, message: String): List<ChatMessage>?
fun getGroupMembers(id: String): Flow<List<Member>>
fun retrieveGroupMembers(id: String, includeAllPublicFields: Boolean): Flowable<List<Member>>
suspend fun getGroupMembers(id: String): Flow<List<Member>>
suspend fun retrieveGroupMembers(id: String, includeAllPublicFields: Boolean): List<Member>?
fun inviteToGroup(id: String, inviteData: Map<String, Any>): Flowable<List<Void>>
fun getMember(userId: String?): Flowable<Member>
fun getMemberWithUsername(username: String?): Flowable<Member>
suspend fun retrieveMember(userId: String?): Member?
suspend fun retrieveMemberWithUsername(username: String?): Member?
fun findUsernames(
username: String,
@ -97,8 +96,8 @@ interface SocialRepository : BaseRepository {
fun markSomePrivateMessagesAsRead(user: User?, messages: List<ChatMessage>)
fun transferGroupOwnership(groupID: String, userID: String): Flowable<Group>
fun removeMemberFromGroup(groupID: String, userID: String): Flowable<List<Member>>
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>
@ -117,7 +116,7 @@ interface SocialRepository : BaseRepository {
fun transferGems(giftedID: String, amount: Int): Flowable<Void>
fun getGroupMembership(id: String): Flowable<GroupMembership>
fun getGroupMembership(id: String): Flow<GroupMembership?>
fun getGroupMemberships(): Flowable<out List<GroupMembership>>
fun blockMember(userID: String): Flowable<List<String>>
}

View file

@ -19,7 +19,7 @@ interface TaskRepository : BaseRepository {
fun getTasksFlowable(taskType: TaskType, userID: String? = null, includedGroupIDs: Array<String>): Flowable<out List<Task>>
fun saveTasks(userId: String, order: TasksOrder, tasks: TaskList)
fun retrieveTasks(userId: String, tasksOrder: TasksOrder): Flowable<TaskList>
suspend fun retrieveTasks(userId: String, tasksOrder: TasksOrder): TaskList?
fun retrieveTasks(userId: String, tasksOrder: TasksOrder, dueDate: Date): Flowable<TaskList>
fun taskChecked(

View file

@ -26,18 +26,13 @@ interface UserRepository : BaseRepository {
fun updateUser(updateData: Map<String, Any>): Flowable<User>
fun updateUser(key: String, value: Any): Flowable<User>
fun retrieveUser(withTasks: Boolean): Flowable<User>
fun retrieveUser(
withTasks: Boolean = false,
forced: Boolean = false,
overrideExisting: Boolean = false
): Flowable<User>
suspend fun retrieveUser(withTasks: Boolean = false, forced: Boolean = false, overrideExisting: Boolean = false): User?
fun revive(): Flowable<User>
suspend fun revive(): User?
fun resetTutorial(): Maybe<User>
fun sleep(user: User): Flowable<User>
suspend fun sleep(user: User): User?
fun getSkills(user: User): Flowable<out List<Skill>>
@ -46,17 +41,14 @@ interface UserRepository : BaseRepository {
fun useSkill(key: String, target: String?, taskId: String): Flowable<SkillResponse>
fun useSkill(key: String, target: String?): Flowable<SkillResponse>
fun changeClass(): Flowable<User>
fun disableClasses(): Flowable<User>
fun changeClass(selectedClass: String): Flowable<User>
suspend fun disableClasses(): User?
suspend fun changeClass(selectedClass: String? = null): User?
fun unlockPath(path: String, price: Int): Flowable<UnlockResponse>
fun unlockPath(customization: Customization): Flowable<UnlockResponse>
fun runCron(tasks: MutableList<Task>)
fun runCron()
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>>
@ -66,7 +58,7 @@ interface UserRepository : BaseRepository {
fun updateLanguage(languageCode: String): Flowable<User>
fun resetAccount(): Flowable<User>
suspend fun resetAccount(): User?
fun deleteAccount(password: String): Flowable<Void>
fun sendPasswordResetEmail(email: String): Flowable<Void>
@ -86,9 +78,9 @@ interface UserRepository : BaseRepository {
fun getUserQuestStatus(): Flowable<UserQuestStatus>
fun reroll(): Flowable<User>
suspend fun reroll(): User?
fun retrieveTeamPlans(): Flowable<List<TeamPlan>>
fun getTeamPlans(): Flow<List<TeamPlan>>
fun retrieveTeamPlan(teamID: String): Flowable<Group>
suspend fun retrieveTeamPlan(teamID: String): Group?
fun getTeamPlan(teamID: String): Flowable<Group>
}

View file

@ -9,7 +9,6 @@ import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.api.ApiService
import com.habitrpg.android.habitica.api.GSonFactoryCreator
import com.habitrpg.android.habitica.data.ApiClient
import com.habitrpg.android.habitica.extensions.filterMap
import com.habitrpg.android.habitica.helpers.NotificationsManager
import com.habitrpg.android.habitica.models.Achievement
import com.habitrpg.android.habitica.models.ContentResult
@ -40,6 +39,7 @@ import com.habitrpg.android.habitica.models.user.User
import com.habitrpg.android.habitica.proxy.AnalyticsManager
import com.habitrpg.common.habitica.api.HostConfig
import com.habitrpg.common.habitica.api.Server
import com.habitrpg.common.habitica.models.HabitResponse
import com.habitrpg.common.habitica.models.PurchaseValidationRequest
import com.habitrpg.common.habitica.models.PurchaseValidationResult
import com.habitrpg.common.habitica.models.auth.UserAuth
@ -48,7 +48,6 @@ import com.habitrpg.common.habitica.models.auth.UserAuthSocial
import com.habitrpg.common.habitica.models.auth.UserAuthSocialTokens
import com.habitrpg.shared.habitica.models.responses.ErrorResponse
import com.habitrpg.shared.habitica.models.responses.FeedResponse
import com.habitrpg.common.habitica.models.HabitResponse
import com.habitrpg.shared.habitica.models.responses.Status
import com.habitrpg.shared.habitica.models.responses.TaskDirectionData
import com.habitrpg.shared.habitica.models.responses.VerifyUsernameResponse
@ -90,19 +89,31 @@ class ApiClientImpl(
private val apiCallTransformer = FlowableTransformer<HabitResponse<Any>, Any> { observable ->
observable
.filterMap { habitResponse ->
habitResponse.notifications?.let {
notificationsManager.setNotifications(it)
}
if (hadError) {
hideConnectionProblemDialog()
}
habitResponse.data
.filter { it.data != null }
.map { habitResponse ->
processResponse(habitResponse)
}
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnError(this)
}
private fun <T> processResponse(habitResponse: HabitResponse<T>): T? {
habitResponse.notifications?.let {
notificationsManager.setNotifications(it)
}
return habitResponse.data
}
suspend fun <T> handleSuspendCall(apiCall: suspend () -> HabitResponse<T>): T? {
try {
return processResponse(apiCall())
} catch (throwable: Throwable) {
accept(throwable)
}
return null
}
private var languageCode: String? = null
private var lastAPICallURL: String? = null
private var hadError = false
@ -268,25 +279,15 @@ class ApiClientImpl(
}
}
override fun retrieveUser(withTasks: Boolean): Flowable<User> {
var userObservable = this.user
if (withTasks) {
val tasksObservable = this.tasks
userObservable = Flowable.zip(
userObservable, tasksObservable
) { habitRPGUser, tasks ->
habitRPGUser.tasks = tasks
habitRPGUser
}
}
return userObservable
override suspend fun retrieveUser(withTasks: Boolean): User? {
val user = handleSuspendCall { apiService.getUser() }
val tasks = getTasks()
user?.tasks = tasks
return user
}
override fun retrieveInboxMessages(uuid: String, page: Int): Flowable<List<ChatMessage>> {
return apiService.getInboxMessages(uuid, page).compose(configureApiCallObserver())
override suspend fun retrieveInboxMessages(uuid: String, page: Int): List<ChatMessage>? {
return handleSuspendCall { apiService.getInboxMessages(uuid, page) }
}
override fun retrieveInboxConversations(): Flowable<List<InboxConversation>> {
@ -345,16 +346,12 @@ class ApiClientImpl(
this.languageCode = languageCode
}
override val status: Flowable<Status>
get() = apiService.status.compose(configureApiCallObserver())
override suspend fun getStatus(): Status? = handleSuspendCall { apiService.getStatus() }
override fun getContent(language: String): Flowable<ContentResult> {
return apiService.getContent(language).compose(configureApiCallObserver())
override suspend fun getContent(language: String?): ContentResult? {
return handleSuspendCall { apiService.getContent(language) }
}
override val user: Flowable<User>
get() = apiService.user.compose(configureApiCallObserver())
override fun updateUser(updateDictionary: Map<String, Any>): Flowable<User> {
return apiService.updateUser(updateDictionary).compose(configureApiCallObserver())
}
@ -399,8 +396,8 @@ class ApiClientImpl(
return apiService.validateNoRenewSubscription(request).compose(configureApiCallObserver())
}
override fun cancelSubscription(): Flowable<Void> {
return apiService.cancelSubscription().compose(configureApiCallObserver())
override suspend fun cancelSubscription(): Void? {
return processResponse(apiService.cancelSubscription())
}
override fun purchaseHourglassItem(type: String, itemKey: String): Flowable<Void> {
@ -436,8 +433,7 @@ class ApiClientImpl(
return apiService.hatchPet(eggKey, hatchingPotionKey).compose(configureApiCallObserver())
}
override val tasks: Flowable<TaskList>
get() = apiService.tasks.compose(configureApiCallObserver())
override suspend fun getTasks(): TaskList? = handleSuspendCall { apiService.getTasks() }
override fun getTasks(type: String): Flowable<TaskList> {
return apiService.getTasks(type).compose(configureApiCallObserver())
@ -499,13 +495,9 @@ class ApiClientImpl(
return apiService.deleteTag(id).compose(configureApiCallObserver())
}
override fun sleep(): Flowable<Boolean> {
return apiService.sleep().compose(configureApiCallObserver())
}
override suspend fun sleep(): Boolean? = handleSuspendCall { apiService.sleep() }
override fun revive(): Flowable<User> {
return apiService.revive().compose(configureApiCallObserver())
}
override suspend fun revive(): User? = handleSuspendCall { apiService.revive() }
override fun useSkill(skillName: String, targetType: String, targetId: String): Flowable<SkillResponse> {
return apiService.useSkill(skillName, targetType, targetId).compose(configureApiCallObserver())
@ -515,17 +507,17 @@ class ApiClientImpl(
return apiService.useSkill(skillName, targetType).compose(configureApiCallObserver())
}
override fun changeClass(): Flowable<User> {
return apiService.changeClass().compose(configureApiCallObserver())
override suspend fun changeClass(className: String?): User? {
return handleSuspendCall {
if (className != null) {
apiService.changeClass(className)
} else {
apiService.changeClass()
}
}
}
override fun changeClass(className: String): Flowable<User> {
return apiService.changeClass(className).compose(configureApiCallObserver())
}
override fun disableClasses(): Flowable<User> {
return apiService.disableClasses().compose(configureApiCallObserver())
}
override suspend fun disableClasses(): User? = handleSuspendCall { apiService.disableClasses() }
override fun markPrivateMessagesRead(): Flowable<Void> {
// This is necessary, because the API call returns weird data.
@ -539,32 +531,32 @@ class ApiClientImpl(
return apiService.listGroups(type).compose(configureApiCallObserver())
}
override fun getGroup(groupId: String): Flowable<Group> {
return apiService.getGroup(groupId).compose(configureApiCallObserver())
override suspend fun getGroup(groupId: String): Group? {
return processResponse(apiService.getGroup(groupId))
}
override fun createGroup(group: Group): Flowable<Group> {
return apiService.createGroup(group).compose(configureApiCallObserver())
override suspend fun createGroup(group: Group): Group? {
return processResponse(apiService.createGroup(group))
}
override fun updateGroup(id: String, item: Group): Flowable<Group> {
return apiService.updateGroup(id, item).compose(configureApiCallObserver())
override suspend fun updateGroup(id: String, item: Group): Group? {
return processResponse(apiService.updateGroup(id, item))
}
override fun removeMemberFromGroup(groupID: String, userID: String): Flowable<Void> {
return apiService.removeMemberFromGroup(groupID, userID).compose(configureApiCallObserver())
override suspend fun removeMemberFromGroup(groupID: String, userID: String): Void? {
return processResponse(apiService.removeMemberFromGroup(groupID, userID))
}
override fun listGroupChat(groupId: String): Flowable<List<ChatMessage>> {
return apiService.listGroupChat(groupId).compose(configureApiCallObserver())
override suspend fun listGroupChat(groupId: String): List<ChatMessage>? {
return processResponse(apiService.listGroupChat(groupId))
}
override fun joinGroup(groupId: String): Flowable<Group> {
return apiService.joinGroup(groupId).compose(configureApiCallObserver())
override suspend fun joinGroup(groupId: String): Group? {
return processResponse(apiService.joinGroup(groupId))
}
override fun leaveGroup(groupId: String, keepChallenges: String): Flowable<Void> {
return apiService.leaveGroup(groupId, keepChallenges).compose(configureApiCallObserver())
override suspend fun leaveGroup(groupId: String, keepChallenges: String): Void? {
return processResponse(apiService.leaveGroup(groupId, keepChallenges))
}
override fun postGroupChat(groupId: String, message: Map<String, String>): Flowable<PostChatMessageResult> {
@ -578,12 +570,12 @@ class ApiClientImpl(
return apiService.deleteInboxMessage(id).compose(configureApiCallObserver())
}
override fun getGroupMembers(groupId: String, includeAllPublicFields: Boolean?): Flowable<List<Member>> {
return apiService.getGroupMembers(groupId, includeAllPublicFields).compose(configureApiCallObserver())
override suspend fun getGroupMembers(groupId: String, includeAllPublicFields: Boolean?): List<Member>? {
return processResponse(apiService.getGroupMembers(groupId, includeAllPublicFields))
}
override fun getGroupMembers(groupId: String, includeAllPublicFields: Boolean?, lastId: String): Flowable<List<Member>> {
return apiService.getGroupMembers(groupId, includeAllPublicFields, lastId).compose(configureApiCallObserver())
override suspend fun getGroupMembers(groupId: String, includeAllPublicFields: Boolean?, lastId: String): List<Member>? {
return processResponse(apiService.getGroupMembers(groupId, includeAllPublicFields, lastId))
}
override fun likeMessage(groupId: String, mid: String): Flowable<ChatMessage> {
@ -646,13 +638,8 @@ class ApiClientImpl(
return apiService.changeCustomDayStart(updateObject).compose(configureApiCallObserver())
}
override fun getMember(memberId: String): Flowable<Member> {
return apiService.getMember(memberId).compose(configureApiCallObserver())
}
override fun getMemberWithUsername(username: String): Flowable<Member> {
return apiService.getMemberWithUsername(username).compose(configureApiCallObserver())
}
override suspend fun getMember(memberId: String) = processResponse(apiService.getMember(memberId))
override suspend fun getMemberWithUsername(username: String) = processResponse(apiService.getMemberWithUsername(username))
override fun getMemberAchievements(memberId: String): Flowable<List<Achievement>> {
return apiService.getMemberAchievements(memberId, languageCode).compose(configureApiCallObserver())
@ -662,8 +649,8 @@ class ApiClientImpl(
return apiService.findUsernames(username, context, id).compose(configureApiCallObserver())
}
override fun postPrivateMessage(messageDetails: Map<String, String>): Flowable<PostChatMessageResult> {
return apiService.postPrivateMessage(messageDetails).compose(configureApiCallObserver())
override suspend fun postPrivateMessage(messageDetails: Map<String, String>): PostChatMessageResult? {
return handleSuspendCall { apiService.postPrivateMessage(messageDetails) }
}
override fun retrieveShopIventory(identifier: String): Flowable<Shop> {
@ -738,9 +725,6 @@ class ApiClientImpl(
return apiService.seeNotifications(notificationIds).compose(configureApiCallObserver())
}
override val content: Flowable<ContentResult>
get() = apiService.getContent(languageCode).compose(configureApiCallObserver())
override fun openMysteryItem(): Flowable<Equipment> {
return apiService.openMysteryItem().compose(configureApiCallObserver())
}
@ -749,9 +733,7 @@ class ApiClientImpl(
return apiService.runCron().compose(configureApiCallObserver())
}
override fun reroll(): Flowable<User> {
return apiService.reroll().compose(configureApiCallObserver())
}
override suspend fun reroll(): User? = handleSuspendCall { apiService.reroll() }
override fun resetAccount(): Flowable<Void> {
return apiService.resetAccount().compose(configureApiCallObserver())
@ -825,8 +807,8 @@ class ApiClientImpl(
return apiService.getTeamPlans().compose(configureApiCallObserver())
}
override fun getTeamPlanTasks(teamID: String): Flowable<TaskList> {
return apiService.getTeamPlanTasks(teamID).compose(configureApiCallObserver())
override suspend fun getTeamPlanTasks(teamID: String): TaskList? {
return processResponse(apiService.getTeamPlanTasks(teamID))
}
override fun bulkAllocatePoints(
@ -849,8 +831,7 @@ class ApiClientImpl(
return apiService.retrieveMarketGear(languageCode).compose(configureApiCallObserver())
}
override val worldState: Flowable<WorldState>
get() = apiService.worldState.compose(configureApiCallObserver())
override suspend fun getWorldState(): WorldState? = handleSuspendCall { apiService.worldState() }
companion object {
fun createGsonFactory(): GsonConverterFactory {

View file

@ -9,6 +9,7 @@ import com.habitrpg.android.habitica.models.ContentResult
import com.habitrpg.android.habitica.models.WorldState
import com.habitrpg.android.habitica.models.inventory.SpecialItem
import io.reactivex.rxjava3.core.Flowable
import io.realm.RealmList
import java.util.Date
class ContentRepositoryImpl<T : ContentLocalRepository>(
@ -22,34 +23,33 @@ class ContentRepositoryImpl<T : ContentLocalRepository>(
private var lastContentSync = 0L
private var lastWorldStateSync = 0L
override fun retrieveContent(forced: Boolean): Flowable<ContentResult> {
override suspend fun retrieveContent(forced: Boolean): ContentResult? {
val now = Date().time
return if (forced || now - this.lastContentSync > 300000) {
if (forced || now - this.lastContentSync > 300000) {
val content = apiClient.getContent() ?: return null
lastContentSync = now
apiClient.content.doOnNext {
it.special.add(mysteryItem)
localRepository.saveContent(it)
}
} else {
Flowable.just(ContentResult())
content.special = RealmList()
content.special.add(mysteryItem)
localRepository.saveContent(content)
return content
}
return null
}
override fun retrieveWorldState(): Flowable<WorldState> {
override suspend fun retrieveWorldState(): WorldState? {
val now = Date().time
return if (now - this.lastWorldStateSync > 3600000) {
if (now - this.lastWorldStateSync > 3600000) {
val state = apiClient.getWorldState() ?: return null
lastWorldStateSync = now
apiClient.worldState.doOnNext {
localRepository.saveWorldState(it)
for (event in it.events) {
if (event.aprilFools != null && event.isCurrentlyActive) {
AprilFoolsHandler.handle(event.aprilFools, event.end)
}
localRepository.save(state)
for (event in state.events) {
if (event.aprilFools != null && event.isCurrentlyActive) {
AprilFoolsHandler.handle(event.aprilFools, event.end)
}
}
} else {
Flowable.just(WorldState())
return state
}
return null
}
override fun getWorldState(): Flowable<WorldState> {

View file

@ -31,13 +31,9 @@ class InventoryRepositoryImpl(
userID: String,
var appConfigManager: AppConfigManager
) : BaseRepositoryImpl<InventoryLocalRepository>(localRepository, apiClient, userID), InventoryRepository {
override fun getQuestContent(keys: List<String>): Flow<List<QuestContent>> {
return localRepository.getQuestContent(keys)
}
override fun getQuestContent(keys: List<String>) = localRepository.getQuestContent(keys)
override fun getQuestContent(key: String): Flowable<QuestContent> {
return localRepository.getQuestContent(key)
}
override fun getQuestContent(key: String) = localRepository.getQuestContent(key)
override fun getEquipment(searchedKeys: List<String>): Flowable<out List<Equipment>> {
return localRepository.getEquipment(searchedKeys)
@ -75,7 +71,7 @@ class InventoryRepositoryImpl(
return localRepository.getOwnedItems(userID, includeZero)
}
override fun getItemsFlowable(itemClass: Class<out Item>, keys: Array<String>): Flow<List<Item>> {
override fun getItems(itemClass: Class<out Item>, keys: Array<String>): Flow<List<Item>> {
return localRepository.getItemsFlowable(itemClass, keys)
}

View file

@ -4,7 +4,7 @@ import com.habitrpg.android.habitica.BuildConfig
import com.habitrpg.android.habitica.data.ApiClient
import com.habitrpg.android.habitica.data.SocialRepository
import com.habitrpg.android.habitica.data.local.SocialLocalRepository
import com.habitrpg.android.habitica.helpers.RxErrorHandler
import com.habitrpg.android.habitica.helpers.ExceptionHandler
import com.habitrpg.android.habitica.models.Achievement
import com.habitrpg.android.habitica.models.inventory.Quest
import com.habitrpg.android.habitica.models.members.Member
@ -16,12 +16,10 @@ import com.habitrpg.android.habitica.models.social.GroupMembership
import com.habitrpg.android.habitica.models.social.InboxConversation
import com.habitrpg.android.habitica.models.user.User
import io.reactivex.rxjava3.core.Flowable
import io.reactivex.rxjava3.core.Single
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
import retrofit2.HttpException
import kotlinx.coroutines.flow.firstOrNull
import java.util.UUID
class SocialRepositoryImpl(
@ -29,45 +27,31 @@ class SocialRepositoryImpl(
apiClient: ApiClient,
userID: String
) : BaseRepositoryImpl<SocialLocalRepository>(localRepository, apiClient, userID), SocialRepository {
override fun transferGroupOwnership(groupID: String, userID: String): Flowable<Group> {
return localRepository.getGroupFlowable(groupID)
.map {
val group = localRepository.getUnmanagedCopy(it)
group.leaderID = userID
group
}
.flatMap {
apiClient.updateGroup(it.id, it)
}
override suspend fun transferGroupOwnership(groupID: String, userID: String): Group? {
val group = localRepository.getGroup(groupID).first()?.let { localRepository.getUnmanagedCopy(it) }
group?.leaderID = userID
return group?.let { apiClient.updateGroup(groupID, it) }
}
override fun removeMemberFromGroup(groupID: String, userID: String): Flowable<List<Member>> {
return apiClient.removeMemberFromGroup(groupID, userID)
.flatMap {
retrieveGroupMembers(groupID, true)
}
override suspend fun removeMemberFromGroup(groupID: String, userID: String): List<Member>? {
apiClient.removeMemberFromGroup(groupID, userID)
return retrieveGroupMembers(groupID, true)
}
override fun blockMember(userID: String): Flowable<List<String>> {
return apiClient.blockMember(userID)
}
override fun getGroupMembership(id: String): Flowable<GroupMembership> {
return localRepository.getGroupMembership(userID, id)
}
override fun getGroupMembership(id: String) = localRepository.getGroupMembership(userID, id)
override fun getGroupMemberships(): Flowable<out List<GroupMembership>> {
return localRepository.getGroupMemberships(userID)
}
override fun retrieveGroupChat(groupId: String): Single<List<ChatMessage>> {
return apiClient.listGroupChat(groupId)
.flatMap { Flowable.fromIterable(it) }
.map { chatMessage ->
chatMessage.groupId = groupId
chatMessage
}
.toList()
override suspend fun retrieveGroupChat(groupId: String): List<ChatMessage>? {
val messages = apiClient.listGroupChat(groupId)
messages?.forEach { it.groupId = groupId }
return messages
}
override fun getGroupChat(groupId: String): Flowable<out List<ChatMessage>> {
@ -75,7 +59,7 @@ class SocialRepositoryImpl(
}
override fun markMessagesSeen(seenGroupId: String) {
apiClient.seenMessages(seenGroupId).subscribe({ }, RxErrorHandler.handleEmptyError())
apiClient.seenMessages(seenGroupId).subscribe({ }, ExceptionHandler.rx())
}
override fun flagMessage(chatMessageID: String, additionalInfo: String, groupID: String?): Flowable<Void> {
@ -131,76 +115,70 @@ class SocialRepositoryImpl(
return postGroupChat(groupId, messageObject)
}
override fun retrieveGroup(id: String): Flowable<Group> {
return Flowable.zip(
apiClient.getGroup(id).doOnNext { localRepository.saveGroup(it) },
retrieveGroupChat(id)
.toFlowable()
) { group, _ ->
group
}.doOnError {
if (it is HttpException && it.code() == 404) {
MainScope().launch {
val group = localRepository.getGroup(id).first()
if (group != null) {
localRepository.delete(group)
}
}
}
}
override suspend fun retrieveGroup(id: String): Group? {
val group = apiClient.getGroup(id)
group?.let { localRepository.saveGroup(it) }
retrieveGroupChat(id)
return group
}
override fun getGroup(id: String?) = id?.let { localRepository.getGroup(it) } ?: emptyFlow()
override fun getGroupFlowable(id: String?): Flowable<Group> = id?.let { localRepository.getGroupFlowable(it) } ?: Flowable.empty()
override fun leaveGroup(id: String?, keepChallenges: Boolean): Flowable<Group> {
override fun getGroup(id: String?): Flow<Group?> {
if (id?.isNotBlank() != true) {
return Flowable.empty()
return emptyFlow()
}
return apiClient.leaveGroup(id, if (keepChallenges) "remain-in-challenges" else "leave-challenges")
.doOnNext { localRepository.updateMembership(userID, id, false) }
.flatMapMaybe { localRepository.getGroupFlowable(id).firstElement() }
return localRepository.getGroup(id)
}
override fun joinGroup(id: String?): Flowable<Group> {
override suspend fun leaveGroup(id: String?, keepChallenges: Boolean): Group? {
if (id?.isNotBlank() != true) {
return Flowable.empty()
return null
}
return apiClient.joinGroup(id)
.doOnNext { group ->
localRepository.updateMembership(userID, id, true)
localRepository.save(group)
}
apiClient.leaveGroup(id, if (keepChallenges) "remain-in-challenges" else "leave-challenges")
localRepository.updateMembership(userID, id, false)
return localRepository.getGroup(id).firstOrNull()
}
override fun createGroup(
override suspend fun joinGroup(id: String?): Group? {
if (id?.isNotBlank() != true) {
return null
}
val group = apiClient.joinGroup(id)
group?.let {
localRepository.updateMembership(userID, id, true)
localRepository.save(group)
}
return group
}
override suspend fun createGroup(
name: String?,
description: String?,
leader: String?,
type: String?,
privacy: String?,
leaderCreateChallenge: Boolean?
): Flowable<Group> {
): Group? {
val group = Group()
group.name = name
group.description = description
group.type = type
group.leaderID = leader
group.privacy = privacy
return apiClient.createGroup(group).doOnNext {
localRepository.save(it)
}
val savedGroup = apiClient.createGroup(group)
savedGroup?.let { localRepository.save(it) }
return savedGroup
}
override fun updateGroup(
override suspend fun updateGroup(
group: Group?,
name: String?,
description: String?,
leader: String?,
leaderCreateChallenge: Boolean?
): Flowable<Group> {
): Group? {
if (group == null) {
return Flowable.empty()
return null
}
val copiedGroup = localRepository.getUnmanagedCopy(group)
copiedGroup.name = name
@ -224,21 +202,21 @@ class SocialRepositoryImpl(
}
}
override fun getGroups(type: String): Flowable<out List<Group>> = localRepository.getGroups(type)
override fun getGroups(type: String) = localRepository.getGroups(type)
override fun getPublicGuilds(): Flowable<out List<Group>> = localRepository.getPublicGuilds()
override fun getPublicGuilds() = localRepository.getPublicGuilds()
override fun getInboxConversations(): Flowable<out List<InboxConversation>> = localRepository.getInboxConversation(userID)
override fun getInboxConversations() = localRepository.getInboxConversation(userID)
override fun getInboxMessages(replyToUserID: String?): Flowable<out List<ChatMessage>> = localRepository.getInboxMessages(userID, replyToUserID)
override fun getInboxMessages(replyToUserID: String?) = localRepository.getInboxMessages(userID, replyToUserID)
override fun retrieveInboxMessages(uuid: String, page: Int): Flowable<List<ChatMessage>> {
return apiClient.retrieveInboxMessages(uuid, page).doOnNext { messages ->
messages.forEach {
it.isInboxMessage = true
}
localRepository.saveInboxMessages(userID, uuid, messages, page)
override suspend fun retrieveInboxMessages(uuid: String, page: Int): List<ChatMessage>? {
val messages = apiClient.retrieveInboxMessages(uuid, page) ?: return null
messages.forEach {
it.isInboxMessage = true
}
localRepository.saveInboxMessages(userID, uuid, messages, page)
return messages
}
override fun retrieveInboxConversations(): Flowable<List<InboxConversation>> {
@ -247,29 +225,31 @@ class SocialRepositoryImpl(
}
}
override fun postPrivateMessage(recipientId: String, messageObject: HashMap<String, String>): Flowable<List<ChatMessage>> {
return apiClient.postPrivateMessage(messageObject).flatMap { retrieveInboxMessages(recipientId, 0) }
override suspend fun postPrivateMessage(recipientId: String, messageObject: HashMap<String, String>): List<ChatMessage>? {
val message = apiClient.postPrivateMessage(messageObject)
return retrieveInboxMessages(recipientId, 0)
}
override fun postPrivateMessage(recipientId: String, message: String): Flowable<List<ChatMessage>> {
override suspend fun postPrivateMessage(recipientId: String, message: String): List<ChatMessage>? {
val messageObject = HashMap<String, String>()
messageObject["message"] = message
messageObject["toUserId"] = recipientId
return postPrivateMessage(recipientId, messageObject)
}
override fun getGroupMembers(id: String) = localRepository.getGroupMembers(id)
override suspend fun getGroupMembers(id: String) = localRepository.getGroupMembers(id)
override fun retrieveGroupMembers(id: String, includeAllPublicFields: Boolean): Flowable<List<Member>> {
return apiClient.getGroupMembers(id, includeAllPublicFields)
.doOnNext { members -> localRepository.saveGroupMembers(id, members) }
override suspend fun retrieveGroupMembers(id: String, includeAllPublicFields: Boolean): List<Member>? {
val members = apiClient.getGroupMembers(id, includeAllPublicFields)
members?.let { localRepository.saveGroupMembers(id, it) }
return members
}
override fun inviteToGroup(id: String, inviteData: Map<String, Any>): Flowable<List<Void>> = apiClient.inviteToGroup(id, inviteData)
override fun getMember(userId: String?): Flowable<Member> {
override suspend fun retrieveMember(userId: String?): Member? {
return if (userId == null) {
Flowable.empty()
null
} else {
try {
apiClient.getMember(UUID.fromString(userId).toString())
@ -279,8 +259,8 @@ class SocialRepositoryImpl(
}
}
override fun getMemberWithUsername(username: String?): Flowable<Member> {
return getMember(username)
override suspend fun retrieveMemberWithUsername(username: String?): Member? {
return retrieveMember(username)
}
override fun findUsernames(username: String, context: String?, id: String?): Flowable<List<FindUsernameResult>> {
@ -315,7 +295,7 @@ class SocialRepositoryImpl(
}
}
override fun getUserGroups(type: String?): Flowable<out List<Group>> = localRepository.getUserGroups(userID, type)
override fun getUserGroups(type: String?) = localRepository.getUserGroups(userID, type)
override fun acceptQuest(user: User?, partyId: String): Flowable<Void> {
return apiClient.acceptQuest(partyId)

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.RxErrorHandler
import com.habitrpg.android.habitica.helpers.ExceptionHandler
import com.habitrpg.android.habitica.interactors.ScoreTaskLocallyInteractor
import com.habitrpg.android.habitica.models.BaseMainObject
import com.habitrpg.android.habitica.models.responses.BulkTaskScoringData
@ -49,9 +49,10 @@ class TaskRepositoryImpl(
localRepository.saveTasks(userId, order, tasks)
}
override fun retrieveTasks(userId: String, tasksOrder: TasksOrder): Flowable<TaskList> {
return this.apiClient.tasks
.doOnNext { res -> this.localRepository.saveTasks(userId, tasksOrder, res) }
override suspend fun retrieveTasks(userId: String, tasksOrder: TasksOrder): TaskList? {
val tasks = apiClient.getTasks() ?: return null
this.localRepository.saveTasks(userId, tasksOrder, tasks)
return tasks
}
override fun retrieveCompletedTodos(userId: String?): Flowable<TaskList> {
@ -309,11 +310,11 @@ class TaskRepositoryImpl(
getTask(taskid).map { localRepository.getUnmanagedCopy(it) }
override fun updateTaskInBackground(task: Task) {
updateTask(task).subscribe({ }, RxErrorHandler.handleEmptyError())
updateTask(task).subscribe({ }, ExceptionHandler.rx())
}
override fun createTaskInBackground(task: Task) {
createTask(task).subscribe({ }, RxErrorHandler.handleEmptyError())
createTask(task).subscribe({ }, ExceptionHandler.rx())
}
override fun getTaskCopies(userId: String): Flow<List<Task>> =

View file

@ -1,6 +1,5 @@
package com.habitrpg.android.habitica.data.implementation
import androidx.core.os.bundleOf
import com.habitrpg.android.habitica.data.ApiClient
import com.habitrpg.android.habitica.data.TaskRepository
import com.habitrpg.android.habitica.data.UserRepository
@ -8,7 +7,7 @@ import com.habitrpg.android.habitica.data.local.UserLocalRepository
import com.habitrpg.android.habitica.models.user.UserQuestStatus
import com.habitrpg.android.habitica.extensions.filterMapEmpty
import com.habitrpg.android.habitica.helpers.AppConfigManager
import com.habitrpg.android.habitica.helpers.RxErrorHandler
import com.habitrpg.android.habitica.helpers.ExceptionHandler
import com.habitrpg.android.habitica.models.Achievement
import com.habitrpg.android.habitica.models.QuestAchievement
import com.habitrpg.android.habitica.models.Skill
@ -28,7 +27,9 @@ import com.habitrpg.shared.habitica.models.tasks.Attribute
import io.reactivex.rxjava3.core.Flowable
import io.reactivex.rxjava3.core.Maybe
import io.reactivex.rxjava3.functions.BiFunction
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.withContext
import java.util.Date
import java.util.GregorianCalendar
import java.util.concurrent.TimeUnit
@ -70,45 +71,39 @@ class UserRepositoryImpl(
return updateUser(userID, key, value)
}
override fun retrieveUser(withTasks: Boolean): Flowable<User> =
retrieveUser(withTasks, false)
@Suppress("ReturnCount")
override fun retrieveUser(withTasks: Boolean, forced: Boolean, overrideExisting: Boolean): Flowable<User> {
override suspend fun retrieveUser(withTasks: Boolean, forced: Boolean, overrideExisting: Boolean): User? {
// Only retrieve again after 3 minutes or it's forced.
if (forced || this.lastSync == null || Date().time - (this.lastSync?.time ?: 0) > 180000) {
val user = apiClient.retrieveUser(withTasks) ?: return null
lastSync = Date()
return apiClient.retrieveUser(withTasks)
.doOnNext { localRepository.saveUser(it, overrideExisting) }
.doOnNext { user ->
if (withTasks) {
val id = user.id
val tasksOrder = user.tasksOrder
val tasks = user.tasks
if (id != null && tasksOrder != null && tasks != null) {
taskRepository.saveTasks(id, tasksOrder, tasks)
}
}
}
.flatMap { user ->
val calendar = GregorianCalendar()
val timeZone = calendar.timeZone
val offset = -TimeUnit.MINUTES.convert(timeZone.getOffset(calendar.timeInMillis).toLong(), TimeUnit.MILLISECONDS)
if (offset.toInt() != (user.preferences?.timezoneOffset ?: 0)) {
return@flatMap updateUser(user.id ?: "", "preferences.timezoneOffset", offset.toString())
} else {
return@flatMap Flowable.just(user)
}
localRepository.saveUser(user)
if (withTasks) {
val id = user.id
val tasksOrder = user.tasksOrder
val tasks = user.tasks
if (id != null && tasksOrder != null && tasks != null) {
taskRepository.saveTasks(id, tasksOrder, tasks)
}
}
val calendar = GregorianCalendar()
val timeZone = calendar.timeZone
val offset = -TimeUnit.MINUTES.convert(timeZone.getOffset(calendar.timeInMillis).toLong(), TimeUnit.MILLISECONDS)
/*if (offset.toInt() != user.preferences?.timezoneOffset ?: 0) {
return@flatMap updateUser(user.id ?: "", "preferences.timezoneOffset", offset.toString())
} else {
return@flatMap Flowable.just(user)
}*/
return user
} else {
return localRepository.getUserFlowable(userID).take(1)
return null
}
}
override fun revive(): Flowable<User> = zipWithLiveUser(apiClient.revive()) { newUser, user ->
mergeUser(user, newUser)
override suspend fun revive(): User? {
apiClient.revive()
return retrieveUser(false, true)
}
.flatMap { retrieveUser(false, true) }
override fun resetTutorial(): Maybe<User> {
return localRepository.getTutorialSteps()
@ -123,9 +118,13 @@ class UserRepositoryImpl(
.flatMap { updateData -> updateUser(updateData).firstElement() }
}
override fun sleep(user: User): Flowable<User> {
localRepository.modify(user) { it.preferences?.sleep = !(it.preferences?.sleep ?: false) }
return apiClient.sleep().map { user }
override suspend fun sleep(user: User): User {
val newValue = !(user.preferences?.sleep ?: false)
localRepository.modify(user) { it.preferences?.sleep = newValue }
if (apiClient.sleep() != true) {
localRepository.modify(user) { it.preferences?.sleep = !newValue }
}
return user
}
override fun getSkills(user: User): Flowable<out List<Skill>> =
@ -137,7 +136,7 @@ class UserRepositoryImpl(
override fun useSkill(key: String, target: String?, taskId: String): Flowable<SkillResponse> {
return zipWithLiveUser(apiClient.useSkill(key, target ?: "", taskId)) { response, user ->
response.hpDiff = (response.user?.stats?.hp ?: 0.0) - (user.stats?.hp ?: 0.0)
response.expDiff = (response.user?.stats?.exp ?: 0.0) - (user.stats?.exp ?: 0.0)
response.expDiff =(response.user?.stats?.exp ?: 0.0) - (user.stats?.exp ?: 0.0)
response.goldDiff = (response.user?.stats?.gp ?: 0.0) - (user.stats?.gp ?: 0.0)
response.damage = (response.user?.party?.quest?.progress?.up ?: 0.0f) - (user.party?.quest?.progress?.up ?: 0.0f)
response.user?.let { mergeUser(user, it) }
@ -148,7 +147,7 @@ class UserRepositoryImpl(
override fun useSkill(key: String, target: String?): Flowable<SkillResponse> {
return zipWithLiveUser(apiClient.useSkill(key, target ?: "")) { response, user ->
response.hpDiff = (response.user?.stats?.hp ?: 0.0) - (user.stats?.hp ?: 0.0)
response.expDiff = (response.user?.stats?.exp ?: 0.0) - (user.stats?.exp ?: 0.0)
response.expDiff =(response.user?.stats?.exp ?: 0.0) - (user.stats?.exp ?: 0.0)
response.goldDiff = (response.user?.stats?.gp ?: 0.0) - (user.stats?.gp ?: 0.0)
response.damage = (response.user?.party?.quest?.progress?.up ?: 0.0f) - (user.party?.quest?.progress?.up ?: 0.0f)
response.user?.let { mergeUser(user, it) }
@ -156,12 +155,15 @@ class UserRepositoryImpl(
}
}
override fun changeClass(): Flowable<User> = apiClient.changeClass().flatMap { retrieveUser(withTasks = false, forced = true) }
override suspend fun disableClasses(): User? = apiClient.disableClasses()
override fun disableClasses(): Flowable<User> = apiClient.disableClasses().flatMap { retrieveUser(withTasks = false, forced = true) }
override suspend fun changeClass(selectedClass: String?): User? {
return apiClient.changeClass(selectedClass)
}
override fun changeClass(selectedClass: String): Flowable<User> = apiClient.changeClass(selectedClass)
.flatMap { retrieveUser(false) }
override fun unlockPath(customization: Customization): Flowable<UnlockResponse> {
return unlockPath(customization.path, customization.price ?: 0)
}
override fun unlockPath(path: String, price: Int): Flowable<UnlockResponse> {
return zipWithLiveUser(apiClient.unlockPath(path)) { unlockResponse, copiedUser ->
@ -175,11 +177,7 @@ class UserRepositoryImpl(
}
}
override fun unlockPath(customization: Customization): Flowable<UnlockResponse> {
return unlockPath(customization.unlockPath, customization.price ?: 0)
}
override fun runCron() {
override suspend fun runCron() {
runCron(ArrayList())
}
@ -188,9 +186,8 @@ class UserRepositoryImpl(
return localRepository.getUserQuestStatus(userID)
}
override fun reroll(): Flowable<User> {
override suspend fun reroll(): User? {
return apiClient.reroll()
.flatMap { retrieveUser(true, true, true) }
}
override fun readNotifications(notificationIds: Map<String, List<String>>): Flowable<List<Any>> =
@ -210,8 +207,9 @@ class UserRepositoryImpl(
.doOnNext { apiClient.setLanguageCode(languageCode) }
}
override fun resetAccount(): Flowable<User> {
return apiClient.resetAccount().flatMap { retrieveUser(withTasks = true, forced = true) }
override suspend fun resetAccount(): User? {
apiClient.resetAccount()
return retrieveUser(withTasks = true, forced = true)
}
override fun deleteAccount(password: String): Flowable<Void> =
@ -262,7 +260,7 @@ class UserRepositoryImpl(
liveUser.stats?.points = liveUser.stats?.points?.dec()
}
},
RxErrorHandler.handleEmptyError()
ExceptionHandler.rx()
)
return zipWithLiveUser(apiClient.allocatePoint(stat.value)) { stats, user ->
localRepository.modify(user) { liveUser ->
@ -295,26 +293,33 @@ class UserRepositoryImpl(
stats
}
override fun runCron(tasks: MutableList<Task>) {
var observable: Maybe<Any> = localRepository.getUserFlowable(userID).firstElement()
.filter { it.needsCron }
.map { user ->
localRepository.modify(user) { liveUser ->
liveUser.needsCron = false
liveUser.lastCron = Date()
override suspend fun runCron(tasks: MutableList<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
}
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() }
}
if (tasks.isNotEmpty()) {
val scoringList = tasks.map { mapOf(Pair("id", it.id ?: ""), Pair("direction", TaskDirection.UP.text)) }
observable = observable.flatMap { taskRepository.bulkScoreTasks(scoringList).firstElement() }
observable.flatMap { apiClient.runCron().firstElement() }
// .flatMap {
// this.retrieveUser(withTasks = true, forced = true)
// }
.subscribe({ }, ExceptionHandler.rx())
}
observable.flatMap { apiClient.runCron().firstElement() }
.flatMap { this.retrieveUser(withTasks = true, forced = true).firstElement() }
.subscribe({ }, {
analyticsManager.logEvent("cron failed", bundleOf(Pair("error", it.localizedMessage)))
RxErrorHandler.reportError(it)
})
}
override fun useCustomization(type: String, category: String?, identifier: String): Flowable<User> {
@ -340,7 +345,7 @@ class UserRepositoryImpl(
}
}
},
RxErrorHandler.handleEmptyError()
ExceptionHandler.rx()
)
}
var updatePath = "preferences.$type"
@ -375,22 +380,17 @@ class UserRepositoryImpl(
return localRepository.getTeamPlans(userID)
}
override fun retrieveTeamPlan(teamID: String): Flowable<Group> {
return Flowable.zip(
apiClient.getGroup(teamID), apiClient.getTeamPlanTasks(teamID)
) { team, tasks ->
team.tasks = tasks
team
override suspend fun retrieveTeamPlan(teamID: String): Group? {
val team = apiClient.getGroup(teamID) ?: return null
team.tasks = apiClient.getTeamPlanTasks(teamID)
localRepository.save(team)
val id = team.id
val tasksOrder = team.tasksOrder
val tasks = team.tasks
if (id.isNotBlank() && tasksOrder != null && tasks != null) {
taskRepository.saveTasks(id, tasksOrder, tasks)
}
.doOnNext { localRepository.save(it) }
.doOnNext { team ->
val id = team.id
val tasksOrder = team.tasksOrder
val tasks = team.tasks
if (id.isNotBlank() && tasksOrder != null && tasks != null) {
taskRepository.saveTasks(id, tasksOrder, tasks)
}
}
return team
}
override fun getTeamPlan(teamID: String): Flowable<Group> {

View file

@ -28,7 +28,7 @@ interface InventoryLocalRepository : ContentLocalRepository {
fun getOwnedPets(userID: String): Flow<List<OwnedPet>>
fun getInAppRewards(): Flowable<out List<ShopItem>>
fun getQuestContent(key: String): Flowable<QuestContent>
fun getQuestContent(key: String): Flow<QuestContent?>
fun getQuestContent(keys: List<String>): Flow<List<QuestContent>>
fun getEquipment(searchedKeys: List<String>): Flowable<out List<Equipment>>

View file

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

View file

@ -1,7 +1,7 @@
package com.habitrpg.android.habitica.data.local.implementation
import com.habitrpg.android.habitica.data.local.InventoryLocalRepository
import com.habitrpg.android.habitica.helpers.RxErrorHandler
import com.habitrpg.android.habitica.helpers.ExceptionHandler
import com.habitrpg.android.habitica.models.inventory.Egg
import com.habitrpg.android.habitica.models.inventory.Equipment
import com.habitrpg.android.habitica.models.inventory.Food
@ -39,14 +39,12 @@ class RealmInventoryLocalRepository(realm: Realm) : RealmContentLocalRepository(
.filter { it.isLoaded }
}
override fun getQuestContent(key: String): Flowable<QuestContent> {
return RxJavaBridge.toV3Flowable(
realm.where(QuestContent::class.java).equalTo("key", key)
override fun getQuestContent(key: String): Flow<QuestContent?> {
return realm.where(QuestContent::class.java).equalTo("key", key)
.findAll()
.asFlowable()
.toFlow()
.filter { content -> content.isLoaded && content.isValid && !content.isEmpty() }
.map { content -> content.first() }
)
}
override fun getEquipment(searchedKeys: List<String>): Flowable<out List<Equipment>> {
@ -240,7 +238,7 @@ class RealmInventoryLocalRepository(realm: Realm) : RealmContentLocalRepository(
}
override fun changeOwnedCount(type: String, key: String, userID: String, amountToAdd: Int) {
getOwnedItem(userID, type, key, true).firstElement().subscribe({ changeOwnedCount(it, amountToAdd) }, RxErrorHandler.handleEmptyError())
getOwnedItem(userID, type, key, true).firstElement().subscribe({ changeOwnedCount(it, amountToAdd) }, ExceptionHandler.rx())
}
override fun changeOwnedCount(item: OwnedItem, amountToAdd: Int?) {

View file

@ -14,21 +14,21 @@ import io.reactivex.rxjava3.core.Flowable
import io.realm.Realm
import io.realm.Sort
import io.realm.kotlin.toFlow
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.map
class RealmSocialLocalRepository(realm: Realm) : RealmBaseLocalRepository(realm), SocialLocalRepository {
override fun getGroupMembership(userId: String, id: String): Flowable<GroupMembership> = RxJavaBridge.toV3Flowable(
realm.where(GroupMembership::class.java)
override fun getGroupMembership(userId: String, id: String) = realm.where(GroupMembership::class.java)
.equalTo("userID", userId)
.equalTo("groupID", id)
.findAll()
.asFlowable()
.toFlow()
.filter { it.isLoaded && it.isNotEmpty() }
.map { it.first() }
)
override fun getGroupMemberships(userId: String): Flowable<out List<GroupMembership>> = RxJavaBridge.toV3Flowable(
realm.where(GroupMembership::class.java)
@ -134,29 +134,25 @@ class RealmSocialLocalRepository(realm: Realm) : RealmBaseLocalRepository(realm)
.filter { it.isLoaded }
)
override fun getUserGroups(userID: String, type: String?): Flowable<out List<Group>> = RxJavaBridge.toV3Flowable(
realm.where(GroupMembership::class.java)
@OptIn(ExperimentalCoroutinesApi::class)
override fun getUserGroups(userID: String, type: String?) = realm.where(GroupMembership::class.java)
.equalTo("userID", userID)
.findAll()
.asFlowable()
.filter { it.isLoaded }
)
.flatMap { memberships ->
RxJavaBridge.toV3Flowable(
realm.where(Group::class.java)
.equalTo("type", type ?: "guild")
.notEqualTo("id", Group.TAVERN_ID)
.`in`(
"id",
memberships.map {
return@map it.groupID
}.toTypedArray()
)
.sort("memberCount", Sort.DESCENDING)
.findAll()
.asFlowable()
.filter { it.isLoaded }
)
.toFlow()
.filter { it.isLoaded }
.flatMapLatest { memberships ->
realm.where(Group::class.java)
.equalTo("type", type ?: "guild")
.notEqualTo("id", Group.TAVERN_ID)
.`in`(
"id",
memberships.map {
return@map it.groupID
}.toTypedArray()
)
.sort("memberCount", Sort.DESCENDING)
.findAll()
.toFlow()
}
override fun getGroups(type: String): Flowable<out List<Group>> {
@ -169,17 +165,6 @@ class RealmSocialLocalRepository(realm: Realm) : RealmBaseLocalRepository(realm)
)
}
override fun getGroupFlowable(id: String): Flowable<Group> {
return RxJavaBridge.toV3Flowable(
realm.where(Group::class.java)
.equalTo("id", id)
.findAll()
.asFlowable()
.filter { group -> group.isLoaded && group.isValid && !group.isEmpty() }
.map { groups -> groups.first() }
)
}
override fun getGroup(id: String): Flow<Group?> {
return realm.where(Group::class.java)
.equalTo("id", id)
@ -205,13 +190,10 @@ class RealmSocialLocalRepository(realm: Realm) : RealmBaseLocalRepository(realm)
executeTransaction { chatMessage?.deleteFromRealm() }
}
override fun getGroupMembers(partyId: String): Flow<List<Member>> {
return realm.where(Member::class.java)
override fun getGroupMembers(partyId: String) = realm.where(Member::class.java)
.equalTo("party.id", partyId)
.findAll()
.toFlow()
.filter { it.isLoaded }
}
.toFlow()
override fun updateRSVPNeeded(user: User?, newValue: Boolean) {
executeTransaction { user?.party?.quest?.RSVPNeeded = newValue }
@ -302,27 +284,19 @@ class RealmSocialLocalRepository(realm: Realm) : RealmBaseLocalRepository(realm)
return party != null && party.isValid
}
override fun getInboxMessages(userId: String, replyToUserID: String?): Flowable<out List<ChatMessage>> {
return RxJavaBridge.toV3Flowable(
realm.where(ChatMessage::class.java)
override fun getInboxMessages(userId: String, replyToUserID: String?) = realm.where(ChatMessage::class.java)
.equalTo("isInboxMessage", true)
.equalTo("uuid", replyToUserID)
.equalTo("userID", userId)
.sort("timestamp", Sort.DESCENDING)
.findAll()
.asFlowable()
.toFlow()
.filter { it.isLoaded }
)
}
override fun getInboxConversation(userId: String): Flowable<out List<InboxConversation>> {
return RxJavaBridge.toV3Flowable(
realm.where(InboxConversation::class.java)
override fun getInboxConversation(userId: String) = realm.where(InboxConversation::class.java)
.equalTo("userID", userId)
.sort("timestamp", Sort.DESCENDING)
.findAll()
.asFlowable()
.toFlow()
.filter { it.isLoaded }
)
}
}

View file

@ -1,10 +1,10 @@
package com.habitrpg.android.habitica.extensions
import com.habitrpg.android.habitica.helpers.RxErrorHandler
import com.habitrpg.android.habitica.helpers.ExceptionHandler
import io.reactivex.rxjava3.core.Flowable
import io.reactivex.rxjava3.disposables.Disposable
import io.reactivex.rxjava3.functions.Consumer
fun <T : Any> Flowable<T>.subscribeWithErrorHandler(function: Consumer<T>): Disposable {
return subscribe(function, RxErrorHandler.handleEmptyError())
return subscribe(function, ExceptionHandler.rx())
}

View file

@ -22,7 +22,7 @@ class AppConfigManager(contentRepository: ContentRepository?): com.habitrpg.comm
{
worldState = it
},
RxErrorHandler.handleEmptyError()
ExceptionHandler.rx()
)
}

View file

@ -4,24 +4,31 @@ import android.util.Log
import com.habitrpg.android.habitica.BuildConfig
import com.habitrpg.android.habitica.proxy.AnalyticsManager
import io.reactivex.rxjava3.functions.Consumer
import java.io.EOFException
import java.io.IOException
import kotlinx.coroutines.CoroutineExceptionHandler
import okhttp3.internal.http2.ConnectionShutdownException
import retrofit2.HttpException
import java.io.EOFException
import java.io.IOException
class RxErrorHandler {
class ExceptionHandler {
private var analyticsManager: AnalyticsManager? = null
companion object {
private var instance: RxErrorHandler? = null
private var instance = ExceptionHandler()
fun init(analyticsManager: AnalyticsManager) {
instance = RxErrorHandler()
instance?.analyticsManager = analyticsManager
instance.analyticsManager = analyticsManager
}
fun handleEmptyError(): Consumer<Throwable> {
fun coroutine(handler: ((Throwable) -> Unit)? = null): CoroutineExceptionHandler {
return CoroutineExceptionHandler { _, throwable ->
reportError(throwable)
handler?.invoke(throwable)
}
}
fun rx(): Consumer<Throwable> {
// Can't be turned into a lambda, because it then doesn't work for some reason.
return Consumer { reportError(it) }
}
@ -40,7 +47,7 @@ class RxErrorHandler {
!retrofit2.adapter.rxjava3.HttpException::class.java.isAssignableFrom(throwable.javaClass) &&
throwable !is ConnectionShutdownException
) {
instance?.analyticsManager?.logException(throwable)
instance.analyticsManager?.logException(throwable)
}
}
}

View file

@ -120,6 +120,6 @@ class MainNotificationsManager: NotificationsManager {
private fun readNotification(notification: Notification) {
apiClient?.get()?.readNotification(notification.id)
?.subscribe({ }, RxErrorHandler.handleEmptyError())
?.subscribe({ }, ExceptionHandler.rx())
}
}

View file

@ -25,7 +25,6 @@ import com.habitrpg.android.habitica.HabiticaBaseApplication
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.data.ApiClient
import com.habitrpg.android.habitica.extensions.addOkButton
import com.habitrpg.android.habitica.extensions.subscribeWithErrorHandler
import com.habitrpg.android.habitica.models.user.User
import com.habitrpg.android.habitica.proxy.AnalyticsManager
import com.habitrpg.android.habitica.ui.activities.PurchaseActivity
@ -34,9 +33,9 @@ import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog
import com.habitrpg.common.habitica.models.IAPGift
import com.habitrpg.common.habitica.models.PurchaseValidationRequest
import com.habitrpg.common.habitica.models.Transaction
import io.reactivex.rxjava3.core.Flowable
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@ -88,7 +87,7 @@ class PurchaseHandler(
return
}
BillingClient.BillingResponseCode.ITEM_ALREADY_OWNED -> {
CoroutineScope(Dispatchers.IO).launch {
CoroutineScope(Dispatchers.IO).launch(ExceptionHandler.coroutine()) {
for (purchase in purchases) {
consume(purchase)
}
@ -220,7 +219,7 @@ class PurchaseHandler(
apiClient.validatePurchase(validationRequest).subscribe({
processedPurchase(purchase)
val gift = removeGift(sku)
CoroutineScope(Dispatchers.IO).launch {
CoroutineScope(Dispatchers.IO).launch(ExceptionHandler.coroutine()) {
consume(purchase)
}
displayConfirmationDialog(purchase, gift?.second)
@ -233,7 +232,7 @@ class PurchaseHandler(
apiClient.validateNoRenewSubscription(validationRequest).subscribe({
processedPurchase(purchase)
val gift = removeGift(sku)
CoroutineScope(Dispatchers.IO).launch {
CoroutineScope(Dispatchers.IO).launch(ExceptionHandler.coroutine()) {
consume(purchase)
}
displayConfirmationDialog(purchase, gift?.second)
@ -246,7 +245,7 @@ class PurchaseHandler(
apiClient.validateSubscription(validationRequest).subscribe({
processedPurchase(purchase)
analyticsManager.logEvent("user_subscribed", bundleOf(Pair("sku", sku)))
CoroutineScope(Dispatchers.IO).launch {
CoroutineScope(Dispatchers.IO).launch(ExceptionHandler.coroutine()) {
acknowledgePurchase(purchase)
}
displayConfirmationDialog(purchase)
@ -269,7 +268,9 @@ class PurchaseHandler(
}
private fun processedPurchase(purchase: Purchase) {
userViewModel.userRepository.retrieveUser(false, true).subscribeWithErrorHandler {}
MainScope().launch(ExceptionHandler.coroutine()) {
userViewModel.userRepository.retrieveUser(false, true)
}
}
private fun buildValidationRequest(purchase: Purchase): PurchaseValidationRequest {
@ -297,7 +298,7 @@ class PurchaseHandler(
if (res.message != null && res.message == "RECEIPT_ALREADY_USED") {
processedPurchase(purchase)
removeGift(purchase.skus.firstOrNull())
CoroutineScope(Dispatchers.IO).launch {
CoroutineScope(Dispatchers.IO).launch(ExceptionHandler.coroutine()) {
consume(purchase)
}
return
@ -332,9 +333,9 @@ class PurchaseHandler(
return fallback
}
fun cancelSubscription(): Flowable<User> {
return apiClient.cancelSubscription()
.flatMap { userViewModel.userRepository.retrieveUser(false, true) }
suspend fun cancelSubscription(): User? {
apiClient.cancelSubscription()
return userViewModel.userRepository.retrieveUser(false, true)
}
private fun durationString(sku: String): String {
@ -371,7 +372,7 @@ class PurchaseHandler(
}
private fun displayConfirmationDialog(purchase: Purchase, giftedTo: String? = null) {
CoroutineScope(Dispatchers.Main).launch {
CoroutineScope(Dispatchers.Main).launch(ExceptionHandler.coroutine()) {
val application = (context as? HabiticaBaseApplication)
?: (context.applicationContext as? HabiticaBaseApplication) ?: return@launch
val sku = purchase.skus.firstOrNull() ?: return@launch

View file

@ -48,7 +48,7 @@ class SoundFile(val theme: String, private val fileName: String) {
player?.start()
} catch (e: IllegalStateException) {
} catch (e: Exception) {
RxErrorHandler.reportError(e)
ExceptionHandler.reportError(e)
}
}
}

View file

@ -34,7 +34,7 @@ class SoundManager {
soundFiles.add(SoundFile(soundTheme, SoundReward))
soundFiles.add(SoundFile(soundTheme, SoundTodo))
soundFileLoader.download(soundFiles)
.subscribe({}, RxErrorHandler.handleEmptyError())
.subscribe({}, ExceptionHandler.rx())
}
fun loadAndPlayAudio(type: String) {
@ -54,7 +54,7 @@ class SoundManager {
loadedSoundFiles[type] = file
file.play()
},
RxErrorHandler.handleEmptyError()
ExceptionHandler.rx()
)
}
}

View file

@ -58,7 +58,7 @@ class TaskAlarmManager(
taskRepository.getTaskCopy(taskId)
.filter { task -> task.isValid && task.isManaged && TaskType.DAILY == task.type }
.firstElement()
.subscribe({ this.setAlarmsForTask(it) }, RxErrorHandler.handleEmptyError())
.subscribe({ this.setAlarmsForTask(it) }, ExceptionHandler.rx())
}
suspend fun scheduleAllSavedAlarms(preventDailyReminder: Boolean) {

View file

@ -21,7 +21,7 @@ class HabiticaFirebaseMessagingService : FirebaseMessagingService() {
pushNotificationManager.displayNotification(remoteMessage)
if (remoteMessage.data["identifier"]?.contains(PushNotificationManager.WON_CHALLENGE_PUSH_NOTIFICATION_KEY) == true) {
// userRepository.retrieveUser(true).subscribe({}, RxErrorHandler.handleEmptyError())
// userRepository.retrieveUser(true).subscribe({}, ExceptionHandler.rx())
}
}
}

View file

@ -7,7 +7,7 @@ import com.google.firebase.messaging.FirebaseMessaging
import com.google.firebase.messaging.RemoteMessage
import com.habitrpg.android.habitica.data.ApiClient
import com.habitrpg.android.habitica.helpers.AmplitudeManager
import com.habitrpg.android.habitica.helpers.RxErrorHandler
import com.habitrpg.android.habitica.helpers.ExceptionHandler
import com.habitrpg.android.habitica.models.user.User
class PushNotificationManager(
@ -46,7 +46,7 @@ class PushNotificationManager(
val pushDeviceData = HashMap<String, String>()
pushDeviceData["regId"] = this.refreshedToken
pushDeviceData["type"] = "android"
apiClient.addPushDevice(pushDeviceData).subscribe({ }, RxErrorHandler.handleEmptyError())
apiClient.addPushDevice(pushDeviceData).subscribe({ }, ExceptionHandler.rx())
}
}
@ -54,7 +54,7 @@ class PushNotificationManager(
if (this.refreshedToken.isEmpty()) {
return
}
apiClient.deletePushDevice(this.refreshedToken).subscribe({ }, RxErrorHandler.handleEmptyError())
apiClient.deletePushDevice(this.refreshedToken).subscribe({ }, ExceptionHandler.rx())
}
private fun userHasPushDevice(): Boolean {

View file

@ -8,7 +8,7 @@ import android.widget.FrameLayout
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.data.InventoryRepository
import com.habitrpg.android.habitica.executors.PostExecutionThread
import com.habitrpg.android.habitica.helpers.RxErrorHandler
import com.habitrpg.android.habitica.helpers.ExceptionHandler
import com.habitrpg.android.habitica.models.inventory.Egg
import com.habitrpg.android.habitica.models.inventory.HatchingPotion
import com.habitrpg.android.habitica.models.user.Items
@ -37,7 +37,7 @@ constructor(
dialog.setAdditionalContentView(petWrapper)
dialog.addButton(R.string.equip, true) { _, _ ->
inventoryRepository.equip("pet", requestValues.egg.key + "-" + requestValues.potion.key)
.subscribe({}, RxErrorHandler.handleEmptyError())
.subscribe({}, ExceptionHandler.rx())
}
dialog.addButton(R.string.share, false) { hatchingDialog, _ ->
val message = requestValues.context.getString(R.string.share_hatched, potionName, eggName)

View file

@ -5,7 +5,7 @@ import android.view.ViewGroup
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.databinding.DialogLevelup10Binding
import com.habitrpg.android.habitica.executors.PostExecutionThread
import com.habitrpg.android.habitica.helpers.RxErrorHandler
import com.habitrpg.android.habitica.helpers.ExceptionHandler
import com.habitrpg.android.habitica.helpers.SoundManager
import com.habitrpg.android.habitica.models.user.Stats
import com.habitrpg.android.habitica.models.user.User
@ -94,7 +94,7 @@ constructor(
private fun showClassSelection(requestValues: RequestValues) {
checkClassSelectionUseCase.observable(CheckClassSelectionUseCase.RequestValues(requestValues.user, true, null, requestValues.activity))
.subscribe({ }, RxErrorHandler.handleEmptyError())
.subscribe({ }, ExceptionHandler.rx())
}
class RequestValues(

View file

@ -14,14 +14,13 @@ import androidx.core.util.Pair
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.data.UserRepository
import com.habitrpg.android.habitica.executors.PostExecutionThread
import com.habitrpg.android.habitica.extensions.filterMap
import com.habitrpg.shared.habitica.extensions.round
import com.habitrpg.android.habitica.models.user.Stats
import com.habitrpg.android.habitica.models.user.User
import com.habitrpg.android.habitica.ui.activities.BaseActivity
import com.habitrpg.android.habitica.ui.views.HabiticaIconsHelper
import com.habitrpg.android.habitica.ui.views.HabiticaSnackbar
import com.habitrpg.android.habitica.ui.views.HabiticaSnackbar.SnackbarDisplayType
import com.habitrpg.shared.habitica.extensions.round
import io.reactivex.rxjava3.core.Flowable
import javax.inject.Inject
import kotlin.math.abs
@ -48,8 +47,9 @@ constructor(
}
if (requestValues.hasLeveledUp == true) {
return@defer levelUpUseCase.observable(LevelUpUseCase.RequestValues(requestValues.user, requestValues.level, requestValues.context, requestValues.snackbarTargetView))
.flatMap { userRepository.retrieveUser(true) }
.filterMap { it.stats }
// TODO: .flatMap { userRepository.retrieveUser(true) }
.flatMap { userRepository.getUserFlowable().firstElement().toFlowable() }
.map { it.stats }
} else {
return@defer Flowable.just(stats)
}

View file

@ -7,6 +7,7 @@ import android.widget.TextView
import androidx.lifecycle.LifecycleCoroutineScope
import com.google.firebase.analytics.FirebaseAnalytics
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.helpers.ExceptionHandler
import com.habitrpg.android.habitica.models.user.User
import com.habitrpg.android.habitica.ui.views.HabiticaSnackbar
import com.habitrpg.android.habitica.ui.views.SnackbarActivity
@ -126,7 +127,7 @@ class ShowNotificationInteractor(
} else {
200
}
lifecycleScope.launch {
lifecycleScope.launch(ExceptionHandler.coroutine()) {
delay(delayTime)
lifecycleScope.launch(context = Dispatchers.Main) {
val dialog = AchievementDialog(activity)

View file

@ -5,7 +5,7 @@ import android.os.Parcelable
import android.text.Spanned
import com.google.gson.annotations.SerializedName
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.helpers.RxErrorHandler
import com.habitrpg.android.habitica.helpers.ExceptionHandler
import com.habitrpg.android.habitica.models.BaseMainObject
import com.habitrpg.android.habitica.models.Tag
import com.habitrpg.common.habitica.helpers.MarkdownParser
@ -436,7 +436,7 @@ open class Task : RealmObject, BaseMainObject, Parcelable, BaseTask {
i += 1
}
} catch (e: JSONException) {
RxErrorHandler.reportError(e)
ExceptionHandler.reportError(e)
}
}
this.weeksOfMonth = weeksOfMonth.toList()
@ -465,7 +465,7 @@ open class Task : RealmObject, BaseMainObject, Parcelable, BaseTask {
i += 1
}
} catch (e: JSONException) {
RxErrorHandler.reportError(e)
ExceptionHandler.reportError(e)
}
}
this.daysOfMonth = daysOfMonth

View file

@ -1,44 +0,0 @@
package com.habitrpg.android.habitica.modules;
import android.content.Context;
import android.content.SharedPreferences;
import com.habitrpg.android.habitica.BuildConfig;
import com.habitrpg.android.habitica.data.TaskRepository;
import com.habitrpg.android.habitica.data.UserRepository;
import com.habitrpg.android.habitica.helpers.TaskAlarmManager;
import com.habitrpg.android.habitica.helpers.UserScope;
import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel;
import javax.inject.Named;
import dagger.Module;
import dagger.Provides;
@Module
public class UserModule {
public static final String NAMED_USER_ID = "userId";
@Provides
@UserScope
TaskAlarmManager providesTaskAlarmManager(Context context, TaskRepository taskRepository, @Named(NAMED_USER_ID) String userId) {
return new TaskAlarmManager(context, taskRepository, userId);
}
@Provides
@Named(NAMED_USER_ID)
@UserScope
public String providesUserID(SharedPreferences sharedPreferences) {
if (BuildConfig.DEBUG && !BuildConfig.TEST_USER_ID.isEmpty()) {
return BuildConfig.TEST_USER_ID;
} else {
return sharedPreferences.getString("UserID", "");
}
}
@Provides
@UserScope
MainUserViewModel providesUserViewModel(String userID, UserRepository userRepository) {
return new MainUserViewModel(userID, userRepository);
}
}

View file

@ -0,0 +1,50 @@
package com.habitrpg.android.habitica.modules
import android.content.Context
import android.content.SharedPreferences
import com.habitrpg.android.habitica.BuildConfig
import com.habitrpg.android.habitica.data.SocialRepository
import com.habitrpg.android.habitica.data.TaskRepository
import com.habitrpg.android.habitica.data.UserRepository
import com.habitrpg.android.habitica.helpers.TaskAlarmManager
import com.habitrpg.android.habitica.helpers.UserScope
import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel
import dagger.Module
import dagger.Provides
import javax.inject.Named
@Module
class UserModule {
@Provides
@UserScope
fun providesTaskAlarmManager(
context: Context,
taskRepository: TaskRepository,
@Named(NAMED_USER_ID) userId: String
): TaskAlarmManager {
return TaskAlarmManager(context, taskRepository, userId)
}
@Provides
@Named(NAMED_USER_ID)
@UserScope
fun providesUserID(sharedPreferences: SharedPreferences): String {
return if (BuildConfig.DEBUG && BuildConfig.TEST_USER_ID.isNotEmpty()) {
BuildConfig.TEST_USER_ID
} else {
sharedPreferences.getString("UserID", "") ?: ""
}
}
@Provides
@UserScope
fun providesUserViewModel(
@Named(NAMED_USER_ID) userID: String,
userRepository: UserRepository,
socialRepository: SocialRepository
) = MainUserViewModel(userID, userRepository, socialRepository)
companion object {
const val NAMED_USER_ID = "userId"
}
}

View file

@ -14,9 +14,11 @@ import com.habitrpg.android.habitica.data.ApiClient
import com.habitrpg.android.habitica.data.SocialRepository
import com.habitrpg.android.habitica.data.TaskRepository
import com.habitrpg.android.habitica.data.UserRepository
import com.habitrpg.android.habitica.helpers.RxErrorHandler
import com.habitrpg.android.habitica.helpers.ExceptionHandler
import com.habitrpg.android.habitica.interactors.NotifyUserUseCase
import com.habitrpg.android.habitica.models.user.User
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.launch
import javax.inject.Inject
class LocalNotificationActionReceiver : BroadcastReceiver() {
@ -56,30 +58,34 @@ class LocalNotificationActionReceiver : BroadcastReceiver() {
when (action) {
context?.getString(R.string.accept_party_invite) -> {
groupID?.let {
socialRepository.joinGroup(it).subscribe({ }, RxErrorHandler.handleEmptyError())
MainScope().launch(ExceptionHandler.coroutine()) {
socialRepository.joinGroup(it)
}
}
}
context?.getString(R.string.reject_party_invite) -> {
groupID?.let {
socialRepository.rejectGroupInvite(it)
.subscribe({ }, RxErrorHandler.handleEmptyError())
.subscribe({ }, ExceptionHandler.rx())
}
}
context?.getString(R.string.accept_quest_invite) -> {
socialRepository.acceptQuest(user).subscribe({ }, RxErrorHandler.handleEmptyError())
socialRepository.acceptQuest(user).subscribe({ }, ExceptionHandler.rx())
}
context?.getString(R.string.reject_quest_invite) -> {
socialRepository.rejectQuest(user).subscribe({ }, RxErrorHandler.handleEmptyError())
socialRepository.rejectQuest(user).subscribe({ }, ExceptionHandler.rx())
}
context?.getString(R.string.accept_guild_invite) -> {
groupID?.let {
socialRepository.joinGroup(it).subscribe({ }, RxErrorHandler.handleEmptyError())
MainScope().launch(ExceptionHandler.coroutine()) {
socialRepository.joinGroup(it)
}
}
}
context?.getString(R.string.reject_guild_invite) -> {
groupID?.let {
socialRepository.rejectGroupInvite(it)
.subscribe({ }, RxErrorHandler.handleEmptyError())
.subscribe({ }, ExceptionHandler.rx())
}
}
context?.getString(R.string.group_message_reply) -> {
@ -91,7 +97,7 @@ class LocalNotificationActionReceiver : BroadcastReceiver() {
NotificationManagerCompat.from(c).cancel(it.hashCode())
}
},
RxErrorHandler.handleEmptyError()
ExceptionHandler.rx()
)
}
}
@ -99,8 +105,9 @@ class LocalNotificationActionReceiver : BroadcastReceiver() {
context?.getString(R.string.inbox_message_reply) -> {
senderID?.let {
getMessageText(context?.getString(R.string.inbox_message_reply))?.let { message ->
socialRepository.postPrivateMessage(it, message)
.subscribe({ }, RxErrorHandler.handleEmptyError())
MainScope().launch(ExceptionHandler.coroutine()) {
socialRepository.postPrivateMessage(it, message)
}
}
}
}
@ -109,13 +116,13 @@ class LocalNotificationActionReceiver : BroadcastReceiver() {
taskRepository.taskChecked(null, it, up = true, force = false) {
}.subscribe({
val pair = NotifyUserUseCase.getNotificationAndAddStatsToUserAsText(
it?.experienceDelta,
it?.healthDelta,
it?.goldDelta,
it?.manaDelta
it.experienceDelta,
it.healthDelta,
it.goldDelta,
it.manaDelta
)
showToast(pair.first)
}, RxErrorHandler.handleEmptyError())
}, ExceptionHandler.rx())
}
}
}

View file

@ -15,7 +15,7 @@ import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.data.TaskRepository
import com.habitrpg.android.habitica.data.UserRepository
import com.habitrpg.android.habitica.extensions.withImmutableFlag
import com.habitrpg.android.habitica.helpers.RxErrorHandler
import com.habitrpg.android.habitica.helpers.ExceptionHandler
import com.habitrpg.android.habitica.helpers.TaskAlarmManager
import com.habitrpg.android.habitica.models.tasks.Task
import com.habitrpg.android.habitica.models.user.User
@ -76,7 +76,7 @@ class NotificationPublisher : BroadcastReceiver() {
notify(intent, buildNotification(wasInactive, pair.second.authentication?.timestamps?.createdAt))
}
},
RxErrorHandler.handleEmptyError()
ExceptionHandler.rx()
)
} else {
notify(intent, buildNotification(wasInactive))

View file

@ -5,10 +5,10 @@ import android.content.Context
import android.content.Intent
import android.content.SharedPreferences
import com.habitrpg.android.habitica.HabiticaBaseApplication
import com.habitrpg.android.habitica.helpers.ExceptionHandler
import com.habitrpg.android.habitica.helpers.TaskAlarmManager
import com.habitrpg.shared.habitica.HLogger
import com.habitrpg.shared.habitica.LogLevel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.launch
import javax.inject.Inject
@ -25,7 +25,7 @@ class TaskAlarmBootReceiver : BroadcastReceiver() {
return
}
HabiticaBaseApplication.userComponent?.inject(this)
MainScope().launch(Dispatchers.Main) {
MainScope().launch(ExceptionHandler.coroutine()) {
taskAlarmManager.scheduleAllSavedAlarms(sharedPreferences.getBoolean("preventDailyReminder", false))
}
HLogger.log(LogLevel.INFO, this::javaClass.name, "onReceive")

View file

@ -13,7 +13,7 @@ import com.habitrpg.android.habitica.HabiticaBaseApplication
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.data.TaskRepository
import com.habitrpg.android.habitica.extensions.withImmutableFlag
import com.habitrpg.android.habitica.helpers.RxErrorHandler
import com.habitrpg.android.habitica.helpers.ExceptionHandler
import com.habitrpg.android.habitica.helpers.TaskAlarmManager
import com.habitrpg.android.habitica.models.tasks.Task
import com.habitrpg.shared.habitica.models.tasks.TaskType
@ -52,7 +52,7 @@ class TaskReceiver : BroadcastReceiver() {
createNotification(context, it)
},
RxErrorHandler.handleEmptyError()
ExceptionHandler.rx()
)
}
}

View file

@ -17,7 +17,8 @@ import com.habitrpg.android.habitica.databinding.ActivityArmoireBinding
import com.habitrpg.android.habitica.helpers.AdHandler
import com.habitrpg.android.habitica.helpers.AdType
import com.habitrpg.android.habitica.helpers.AppConfigManager
import com.habitrpg.android.habitica.helpers.RxErrorHandler
import com.habitrpg.android.habitica.helpers.ExceptionHandler
import com.habitrpg.common.habitica.extensions.loadImage
import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel
import com.habitrpg.android.habitica.ui.views.ads.AdButton
import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaBottomSheetDialog
@ -93,7 +94,7 @@ class ArmoireActivity : BaseActivity() {
binding.adButton.visibility = View.INVISIBLE
hasAnimatedChanges = false
gold = null
}, RxErrorHandler.handleEmptyError())
}, ExceptionHandler.rx())
)
}
handler.prepare {
@ -116,7 +117,7 @@ class ArmoireActivity : BaseActivity() {
finish()
}
binding.equipButton.setOnClickListener {
equipmentKey?.let { it1 -> inventoryRepository.equip("equipped", it1).subscribe({}, RxErrorHandler.handleEmptyError()) }
equipmentKey?.let { it1 -> inventoryRepository.equip("equipped", it1).subscribe({}, ExceptionHandler.rx()) }
finish()
}
binding.dropRateButton.setOnClickListener {

View file

@ -26,10 +26,11 @@ import com.habitrpg.android.habitica.HabiticaBaseApplication
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.components.UserComponent
import com.habitrpg.android.habitica.data.UserRepository
import com.habitrpg.android.habitica.extensions.subscribeWithErrorHandler
import com.habitrpg.common.habitica.extensions.getThemeColor
import com.habitrpg.common.habitica.extensions.isUsingNightModeResources
import com.habitrpg.android.habitica.extensions.updateStatusBarColor
import com.habitrpg.android.habitica.helpers.NotificationsManager
import com.habitrpg.android.habitica.helpers.RxErrorHandler
import com.habitrpg.android.habitica.helpers.ExceptionHandler
import com.habitrpg.android.habitica.interactors.ShowNotificationInteractor
import com.habitrpg.android.habitica.proxy.AnalyticsManager
import com.habitrpg.android.habitica.ui.helpers.ToolbarColorHelper
@ -38,6 +39,7 @@ import com.habitrpg.common.habitica.extensions.getThemeColor
import com.habitrpg.common.habitica.extensions.isUsingNightModeResources
import com.habitrpg.common.habitica.helpers.LanguageHelper
import io.reactivex.rxjava3.disposables.CompositeDisposable
import kotlinx.coroutines.launch
import java.util.Date
import java.util.Locale
import javax.inject.Inject
@ -94,16 +96,16 @@ abstract class BaseActivity : AppCompatActivity() {
injectActivity(HabiticaBaseApplication.userComponent)
setContentView(getContentView())
compositeSubscription = CompositeDisposable()
compositeSubscription.add(
notificationsManager.displayNotificationEvents.subscribe(
{
if (ShowNotificationInteractor(this, lifecycleScope).handleNotification(it)) {
compositeSubscription.add(userRepository.retrieveUser(false, true).subscribeWithErrorHandler {})
compositeSubscription.add(notificationsManager.displayNotificationEvents.subscribe(
{
if (ShowNotificationInteractor(this, lifecycleScope).handleNotification(it)) {
lifecycleScope.launch(ExceptionHandler.coroutine()) {
userRepository.retrieveUser(false, true)
}
},
RxErrorHandler.handleEmptyError()
)
)
}
},
ExceptionHandler.rx()
))
}
override fun onRestart() {

View file

@ -23,8 +23,8 @@ import com.habitrpg.android.habitica.data.ChallengeRepository
import com.habitrpg.android.habitica.data.SocialRepository
import com.habitrpg.android.habitica.databinding.ActivityCreateChallengeBinding
import com.habitrpg.android.habitica.extensions.addCloseButton
import com.habitrpg.android.habitica.helpers.ExceptionHandler
import com.habitrpg.android.habitica.helpers.MainNavigationController
import com.habitrpg.android.habitica.helpers.RxErrorHandler
import com.habitrpg.android.habitica.models.social.Challenge
import com.habitrpg.android.habitica.models.social.Group
import com.habitrpg.android.habitica.models.tasks.Task
@ -41,6 +41,7 @@ import com.habitrpg.shared.habitica.models.tasks.TaskType
import io.reactivex.rxjava3.core.Flowable
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.launch
import java.util.UUID
import javax.inject.Inject
@ -156,7 +157,7 @@ class ChallengeFormActivity : BaseActivity() {
{ throwable ->
dialog?.dismiss()
savingInProgress = false
RxErrorHandler.reportError(throwable)
ExceptionHandler.reportError(throwable)
}
)
)
@ -337,36 +338,26 @@ class ChallengeFormActivity : BaseActivity() {
}
locationAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
compositeSubscription.add(
socialRepository.getUserGroups("guild").zipWith(
userRepository.getUserFlowable()
.map { it.party?.id ?: "" }
.distinctUntilChanged()
.flatMap {
if (it.isBlank()) {
return@flatMap Flowable.empty<Group>()
}
socialRepository.retrieveGroup(it)
}
) { user, groups -> Pair(user, groups) }
.subscribe(
{ groups ->
val mutableGroups = groups.first.toMutableList()
if (groups.first.firstOrNull { it.id == "00000000-0000-4000-A000-000000000000" } == null) {
val tavern = Group()
tavern.id = "00000000-0000-4000-A000-000000000000"
tavern.name = getString(R.string.public_challenge)
mutableGroups.add(0, tavern)
}
if (groups.second != null) {
mutableGroups.add(groups.second)
}
locationAdapter.clear()
locationAdapter.addAll(mutableGroups)
},
RxErrorHandler.handleEmptyError()
)
)
lifecycleScope.launch(ExceptionHandler.coroutine()) {
val groups = socialRepository.getUserGroups("guild").firstOrNull()?.toMutableList() ?: return@launch
val partyID = userRepository.getUser().firstOrNull()?.party?.id
val party = if (partyID?.isNotBlank() == true) {
socialRepository.retrieveGroup(partyID)
} else {
null
}
if (groups.firstOrNull { it.id == "00000000-0000-4000-A000-000000000000" } == null) {
val tavern = Group()
tavern.id = "00000000-0000-4000-A000-000000000000"
tavern.name = getString(R.string.public_challenge)
groups.add(0, tavern)
}
if (party != null) {
groups.add(party)
}
locationAdapter.clear()
locationAdapter.addAll(groups)
}
binding.challengeLocationSpinner.adapter = locationAdapter
binding.challengeLocationSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
@ -399,7 +390,7 @@ class ChallengeFormActivity : BaseActivity() {
addReward.text -> openNewTaskActivity(TaskType.REWARD, null)
}
},
RxErrorHandler.handleEmptyError()
ExceptionHandler.rx()
)
)
@ -438,7 +429,7 @@ class ChallengeFormActivity : BaseActivity() {
}
checkPrizeAndMinimumForTavern()
},
RxErrorHandler.handleEmptyError()
ExceptionHandler.rx()
)
challengeRepository.getChallengeTasks(it).subscribe(
{ tasks ->
@ -446,7 +437,7 @@ class ChallengeFormActivity : BaseActivity() {
addOrUpdateTaskInList(task, true)
}
},
RxErrorHandler.handleEmptyError()
ExceptionHandler.rx()
)
}
}

View file

@ -8,24 +8,25 @@ import android.view.MenuItem
import android.view.View
import android.widget.TextView
import androidx.core.content.ContextCompat
import androidx.lifecycle.lifecycleScope
import androidx.navigation.navArgs
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.components.UserComponent
import com.habitrpg.android.habitica.databinding.ActivityClassSelectionBinding
import com.habitrpg.android.habitica.helpers.RxErrorHandler
import com.habitrpg.android.habitica.helpers.ExceptionHandler
import com.habitrpg.android.habitica.models.user.Gear
import com.habitrpg.android.habitica.models.user.Items
import com.habitrpg.android.habitica.models.user.Outfit
import com.habitrpg.android.habitica.models.user.Preferences
import com.habitrpg.android.habitica.models.user.User
import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel
import com.habitrpg.android.habitica.ui.views.HabiticaIconsHelper
import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog
import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaProgressDialog
import com.habitrpg.android.habitica.ui.views.HabiticaIconsHelper
import io.reactivex.rxjava3.functions.Consumer
import kotlinx.coroutines.launch
import javax.inject.Inject
class ClassSelectionActivity : BaseActivity(), Consumer<User> {
class ClassSelectionActivity : BaseActivity() {
@Inject
lateinit var userViewModel: MainUserViewModel
@ -85,10 +86,10 @@ class ClassSelectionActivity : BaseActivity(), Consumer<User> {
}
if (!isInitialSelection) {
compositeSubscription.add(
lifecycleScope.launch(ExceptionHandler.coroutine()) {
userRepository.changeClass()
.subscribe({ classWasUnset = true }, RxErrorHandler.handleEmptyError())
)
classWasUnset
}
}
binding.healerWrapper.setOnClickListener { newClass = "healer" }
@ -254,20 +255,26 @@ class ClassSelectionActivity : BaseActivity(), Consumer<User> {
private fun optOutOfClasses() {
shouldFinish = true
this.displayProgressDialog(getString(R.string.opting_out_progress))
compositeSubscription.add(userRepository.disableClasses().subscribe(this, RxErrorHandler.handleEmptyError()))
lifecycleScope.launch(ExceptionHandler.coroutine()) {
userRepository.disableClasses()
dismiss()
}
}
private fun selectClass(selectedClass: String) {
shouldFinish = true
this.displayProgressDialog(getString(R.string.changing_class_progress))
compositeSubscription.add(userRepository.changeClass(selectedClass).subscribe(this, RxErrorHandler.handleEmptyError()))
lifecycleScope.launch(ExceptionHandler.coroutine()) {
userRepository.changeClass(selectedClass)
dismiss()
}
}
private fun displayProgressDialog(progressText: String) {
HabiticaProgressDialog.show(this, progressText)
}
override fun accept(user: User) {
private fun dismiss() {
if (shouldFinish == true) {
progressDialog?.dismiss()
finish()

View file

@ -15,12 +15,13 @@ import com.habitrpg.android.habitica.extensions.observeOnce
import com.habitrpg.android.habitica.helpers.AdHandler
import com.habitrpg.android.habitica.helpers.AdType
import com.habitrpg.android.habitica.helpers.AppConfigManager
import com.habitrpg.android.habitica.helpers.RxErrorHandler
import com.habitrpg.android.habitica.helpers.ExceptionHandler
import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel
import com.habitrpg.android.habitica.ui.views.HabiticaIconsHelper
import com.habitrpg.android.habitica.ui.views.ads.AdButton
import com.habitrpg.common.habitica.helpers.Animations
import com.plattysoft.leonids.ParticleSystem
import kotlinx.coroutines.launch
import javax.inject.Inject
class DeathActivity: BaseActivity() {
@ -59,7 +60,7 @@ class DeathActivity: BaseActivity() {
compositeSubscription.add(
userRepository.updateUser("stats.hp", 1).subscribe({
finish()
}, RxErrorHandler.handleEmptyError())
}, ExceptionHandler.rx())
)
}
handler.prepare {
@ -80,9 +81,10 @@ class DeathActivity: BaseActivity() {
binding.restartButton.setOnClickListener {
binding.restartButton.isEnabled = false
userRepository.revive().subscribe({
lifecycleScope.launch(ExceptionHandler.coroutine()) {
userRepository.revive()
finish()
}, RxErrorHandler.handleEmptyError())
}
}
startAnimating()
}

View file

@ -13,6 +13,7 @@ import android.widget.TableRow
import android.widget.TextView
import androidx.core.content.ContextCompat
import androidx.core.os.bundleOf
import androidx.lifecycle.lifecycleScope
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.components.UserComponent
import com.habitrpg.android.habitica.data.ApiClient
@ -20,8 +21,8 @@ import com.habitrpg.android.habitica.data.InventoryRepository
import com.habitrpg.android.habitica.data.SocialRepository
import com.habitrpg.android.habitica.databinding.ActivityFullProfileBinding
import com.habitrpg.android.habitica.extensions.addCancelButton
import com.habitrpg.android.habitica.helpers.ExceptionHandler
import com.habitrpg.android.habitica.helpers.MainNavigationController
import com.habitrpg.android.habitica.helpers.RxErrorHandler
import com.habitrpg.android.habitica.helpers.UserStatComputer
import com.habitrpg.android.habitica.models.Achievement
import com.habitrpg.android.habitica.models.inventory.Equipment
@ -82,7 +83,12 @@ class FullProfileActivity : BaseActivity() {
setTitle(R.string.profile_loading_data)
compositeSubscription.add(socialRepository.getMember(this.userID).subscribe({ this.updateView(it) }, RxErrorHandler.handleEmptyError()))
lifecycleScope.launch(ExceptionHandler.coroutine()) {
val member = socialRepository.retrieveMember(userID)
if (member != null) {
updateView(member)
}
}
avatarWithBars = AvatarWithBarsViewModel(this, binding.avatarWithBars)
binding.avatarWithBars.root.setBackgroundColor(ContextCompat.getColor(this, R.color.transparent))
@ -96,15 +102,13 @@ class FullProfileActivity : BaseActivity() {
binding.sendMessageButton.setOnClickListener { showSendMessageToUserDialog() }
binding.giftGemsButton.setOnClickListener { MainNavigationController.navigate(R.id.giftGemsActivity, bundleOf(Pair("userID", userID), Pair("username", null))) }
binding.giftSubscriptionButton.setOnClickListener { MainNavigationController.navigate(R.id.giftSubscriptionActivity, bundleOf(Pair("userID", userID), Pair("username", null))) }
compositeSubscription.add(
userRepository.getUserFlowable().subscribe(
{
blocks = it.inbox?.blocks ?: listOf()
lifecycleScope.launch(ExceptionHandler.coroutine()) {
userRepository.getUser()
.collect {
blocks = it?.inbox?.blocks ?: listOf()
binding.blockedDisclaimerView.visibility = if (isUserBlocked()) View.VISIBLE else View.GONE
},
RxErrorHandler.handleEmptyError()
)
)
}
}
}
override fun onDestroy() {
@ -168,13 +172,14 @@ class FullProfileActivity : BaseActivity() {
private fun useBlock() {
compositeSubscription.add(
socialRepository.blockMember(userID).flatMap {
userRepository.retrieveUser()
}.subscribe(
socialRepository.blockMember(userID).subscribe(
{
invalidateOptionsMenu()
lifecycleScope.launch(ExceptionHandler.coroutine()) {
userRepository.retrieveUser()
invalidateOptionsMenu()
}
},
RxErrorHandler.handleEmptyError()
ExceptionHandler.rx()
)
)
}
@ -227,16 +232,16 @@ class FullProfileActivity : BaseActivity() {
avatarWithBars?.updateData(user)
compositeSubscription.add(loadItemDataByOutfit(user.equipped).subscribe({ gear -> this.gotGear(gear, user) }, RxErrorHandler.handleEmptyError()))
compositeSubscription.add(loadItemDataByOutfit(user.equipped).subscribe({ gear -> this.gotGear(gear, user) }, ExceptionHandler.rx()))
if (user.preferences?.costume == true) {
compositeSubscription.add(loadItemDataByOutfit(user.costume).subscribe({ this.gotCostume(it) }, RxErrorHandler.handleEmptyError()))
compositeSubscription.add(loadItemDataByOutfit(user.costume).subscribe({ this.gotCostume(it) }, ExceptionHandler.rx()))
} else {
binding.costumeCard.visibility = View.GONE
}
// Load the members achievements now
compositeSubscription.add(socialRepository.getMemberAchievements(this.userID).subscribe({ this.fillAchievements(it) }, RxErrorHandler.handleEmptyError()))
compositeSubscription.add(socialRepository.getMemberAchievements(this.userID).subscribe({ this.fillAchievements(it) }, ExceptionHandler.rx()))
}
private fun updatePetsMountsView(user: Member) {

View file

@ -3,6 +3,7 @@ package com.habitrpg.android.habitica.ui.activities
import android.os.Bundle
import android.view.View
import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import androidx.navigation.navArgs
import androidx.viewpager2.adapter.FragmentStateAdapter
import com.google.android.material.tabs.TabLayoutMediator
@ -11,12 +12,14 @@ import com.habitrpg.android.habitica.components.UserComponent
import com.habitrpg.android.habitica.data.SocialRepository
import com.habitrpg.android.habitica.databinding.ActivityGiftGemsBinding
import com.habitrpg.android.habitica.helpers.AppConfigManager
import com.habitrpg.android.habitica.helpers.ExceptionHandler
import com.habitrpg.android.habitica.helpers.PurchaseHandler
import com.habitrpg.android.habitica.helpers.RxErrorHandler
import com.habitrpg.android.habitica.models.members.Member
import com.habitrpg.android.habitica.ui.fragments.purchases.GiftBalanceGemsFragment
import com.habitrpg.android.habitica.ui.fragments.purchases.GiftPurchaseGemsFragment
import com.habitrpg.android.habitica.ui.views.CurrencyView
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.launch
import javax.inject.Inject
class GiftGemsActivity : PurchaseActivity() {
@ -74,27 +77,17 @@ class GiftGemsActivity : PurchaseActivity() {
setViewPagerAdapter()
compositeSubscription.add(
socialRepository.getMember(giftedUsername ?: giftedUserID).firstElement().subscribe(
{
giftedMember = it
giftedUserID = it.id
giftedUsername = it.username
purchaseFragment?.giftedMember = it
balanceFragment?.giftedMember = it
},
RxErrorHandler.handleEmptyError()
)
)
lifecycleScope.launch(ExceptionHandler.coroutine()) {
val member = socialRepository.retrieveMember(giftedUsername ?: giftedUserID) ?: return@launch
giftedMember = member
giftedUserID = member.id
giftedUsername = member.username
purchaseFragment?.giftedMember = member
balanceFragment?.giftedMember = member
compositeSubscription.add(
userRepository.getUserFlowable().subscribe(
{
currencyView.value = it.gemCount.toDouble()
},
RxErrorHandler.handleEmptyError()
)
)
val user = userRepository.getUser().firstOrNull()
currencyView.value = user?.gemCount?.toDouble() ?: 0.0
}
}
private fun setViewPagerAdapter() {

View file

@ -3,6 +3,7 @@ package com.habitrpg.android.habitica.ui.activities
import android.annotation.SuppressLint
import android.os.Bundle
import android.view.View
import androidx.lifecycle.lifecycleScope
import androidx.navigation.navArgs
import com.android.billingclient.api.SkuDetails
import com.habitrpg.android.habitica.R
@ -10,15 +11,15 @@ import com.habitrpg.android.habitica.components.UserComponent
import com.habitrpg.android.habitica.data.SocialRepository
import com.habitrpg.android.habitica.databinding.ActivityGiftSubscriptionBinding
import com.habitrpg.android.habitica.helpers.AppConfigManager
import com.habitrpg.android.habitica.helpers.ExceptionHandler
import com.habitrpg.android.habitica.helpers.PurchaseHandler
import com.habitrpg.android.habitica.helpers.PurchaseTypes
import com.habitrpg.android.habitica.helpers.RxErrorHandler
import com.habitrpg.android.habitica.ui.views.subscriptions.SubscriptionOptionView
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import javax.inject.Inject
class GiftSubscriptionActivity : PurchaseActivity() {
@ -70,20 +71,15 @@ class GiftSubscriptionActivity : PurchaseActivity() {
binding.subscriptionButton.setOnClickListener {
selectedSubscriptionSku?.let { sku -> purchaseSubscription(sku) }
}
compositeSubscription.add(
socialRepository.getMember(giftedUsername ?: giftedUserID).subscribe(
{
binding.avatarView.setAvatar(it)
binding.displayNameTextView.username = it.profile?.name
binding.displayNameTextView.tier = it.contributor?.level ?: 0
binding.usernameTextView.text = "@${it.username}"
giftedUserID = it.id
giftedUsername = it.username
},
RxErrorHandler.handleEmptyError()
)
)
lifecycleScope.launch(ExceptionHandler.coroutine()) {
val member = socialRepository.retrieveMember(giftedUsername ?: giftedUserID) ?: return@launch
binding.avatarView.setAvatar(member)
binding.displayNameTextView.username = member.profile?.name
binding.displayNameTextView.tier = member.contributor?.level ?: 0
binding.usernameTextView.text = "@${member.username}"
giftedUserID = member.id
giftedUsername = member.username
}
if (appConfigManager.activePromo()?.identifier == "g1g1") {
binding.giftSubscriptionContainer.visibility = View.VISIBLE
@ -94,7 +90,7 @@ class GiftSubscriptionActivity : PurchaseActivity() {
override fun onStart() {
super.onStart()
CoroutineScope(Dispatchers.IO).launch {
CoroutineScope(Dispatchers.IO).launch(ExceptionHandler.coroutine()) {
val subscriptions = purchaseHandler.getAllGiftSubscriptionProducts()
skus = subscriptions
withContext(Dispatchers.Main) {

View file

@ -5,7 +5,7 @@ import android.view.MenuItem
import android.widget.TextView
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.components.UserComponent
import com.habitrpg.android.habitica.helpers.RxErrorHandler
import com.habitrpg.android.habitica.helpers.ExceptionHandler
import com.habitrpg.common.habitica.helpers.setMarkdown
import okhttp3.Call
import okhttp3.Callback
@ -31,7 +31,7 @@ class GuidelinesActivity : BaseActivity() {
val request = Request.Builder().url("https://s3.amazonaws.com/habitica-assets/mobileApp/endpoint/community-guidelines.md").build()
client.newCall(request).enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
RxErrorHandler.reportError(e)
ExceptionHandler.reportError(e)
}
@Throws(IOException::class)

View file

@ -12,7 +12,7 @@ import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.components.UserComponent
import com.habitrpg.android.habitica.data.TaskRepository
import com.habitrpg.android.habitica.databinding.WidgetConfigureHabitButtonBinding
import com.habitrpg.android.habitica.helpers.RxErrorHandler
import com.habitrpg.android.habitica.helpers.ExceptionHandler
import com.habitrpg.android.habitica.modules.AppModule
import com.habitrpg.android.habitica.ui.adapter.SkillTasksRecyclerViewAdapter
import com.habitrpg.android.habitica.widget.HabitButtonWidgetProvider
@ -77,11 +77,11 @@ class HabitButtonWidgetActivity : BaseActivity() {
adapter = SkillTasksRecyclerViewAdapter()
adapter?.getTaskSelectionEvents()?.subscribe(
{ task -> taskSelected(task.id) },
RxErrorHandler.handleEmptyError()
ExceptionHandler.rx()
)?.let { compositeSubscription.add(it) }
binding.recyclerView.adapter = adapter
CoroutineScope(Dispatchers.Main + job).launch {
CoroutineScope(Dispatchers.Main + job).launch(ExceptionHandler.coroutine()) {
adapter?.data = taskRepository.getTasks(TaskType.HABIT, userId, emptyArray()).firstOrNull() ?: listOf()
}
}

View file

@ -10,14 +10,16 @@ import androidx.core.content.res.ResourcesCompat
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager
import androidx.fragment.app.FragmentStatePagerAdapter
import androidx.lifecycle.lifecycleScope
import androidx.viewpager.widget.ViewPager
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.components.UserComponent
import com.habitrpg.android.habitica.data.ContentRepository
import com.habitrpg.android.habitica.databinding.ActivityIntroBinding
import com.habitrpg.android.habitica.helpers.RxErrorHandler
import com.habitrpg.android.habitica.helpers.ExceptionHandler
import com.habitrpg.android.habitica.ui.fragments.setup.IntroFragment
import com.viewpagerindicator.IconPagerAdapter
import kotlinx.coroutines.launch
import javax.inject.Inject
class IntroActivity : BaseActivity(), View.OnClickListener, ViewPager.OnPageChangeListener {
@ -44,7 +46,9 @@ class IntroActivity : BaseActivity(), View.OnClickListener, ViewPager.OnPageChan
binding.skipButton.setOnClickListener(this)
binding.finishButton.setOnClickListener(this)
compositeSubscription.add(contentRepository.retrieveContent().subscribe({ }, RxErrorHandler.handleEmptyError()))
lifecycleScope.launch(ExceptionHandler.coroutine()) {
contentRepository.retrieveContent()
}
window.statusBarColor = ContextCompat.getColor(this, R.color.black_20_alpha)
window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS)

View file

@ -41,7 +41,7 @@ import com.habitrpg.android.habitica.extensions.addOkButton
import com.habitrpg.android.habitica.extensions.updateStatusBarColor
import com.habitrpg.android.habitica.helpers.AmplitudeManager
import com.habitrpg.android.habitica.helpers.AppConfigManager
import com.habitrpg.android.habitica.helpers.RxErrorHandler
import com.habitrpg.android.habitica.helpers.ExceptionHandler
import com.habitrpg.android.habitica.models.user.User
import com.habitrpg.android.habitica.ui.helpers.dismissKeyboard
import com.habitrpg.android.habitica.ui.viewmodels.AuthenticationViewModel
@ -86,7 +86,7 @@ class LoginActivity : BaseActivity() {
{ handleAuthResponse(it) },
{
hideProgress()
RxErrorHandler.reportError(it)
ExceptionHandler.reportError(it)
}
)
} else {
@ -101,7 +101,7 @@ class LoginActivity : BaseActivity() {
{ handleAuthResponse(it) },
{
hideProgress()
RxErrorHandler.reportError(it)
ExceptionHandler.reportError(it)
Log.d("LoginActivity", ": ${it.message}", it)
}
)
@ -261,12 +261,12 @@ class LoginActivity : BaseActivity() {
} catch (e: Exception) {
// Wearable API is not available on this device.
}
compositeSubscription.add(
userRepository.retrieveUser(true)
.subscribe({
handleAuthResponse(it, response.newUser)
}, RxErrorHandler.handleEmptyError())
)
lifecycleScope.launch(ExceptionHandler.coroutine()) {
val user = userRepository.retrieveUser(true)
if (user != null) {
handleAuthResponse(user, response.newUser)
}
}
}
private fun handleAuthResponse(user: User, isNew: Boolean) {
@ -276,20 +276,15 @@ class LoginActivity : BaseActivity() {
if (isRegistering) {
FirebaseAnalytics.getInstance(this).logEvent("user_registered", null)
}
compositeSubscription.add(
userRepository.retrieveUser(withTasks = true, forced = true)
.subscribe(
{
if (isNew) {
this.startSetupActivity()
} else {
this.startMainActivity()
AmplitudeManager.sendEvent("login", AmplitudeManager.EVENT_CATEGORY_BEHAVIOUR, AmplitudeManager.EVENT_HITTYPE_EVENT)
}
},
RxErrorHandler.handleEmptyError()
)
)
lifecycleScope.launch(ExceptionHandler.coroutine()) {
userRepository.retrieveUser(true, true)
if (isNew) {
startSetupActivity()
} else {
startMainActivity()
AmplitudeManager.sendEvent("login", AmplitudeManager.EVENT_CATEGORY_BEHAVIOUR, AmplitudeManager.EVENT_HITTYPE_EVENT)
}
}
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
@ -455,7 +450,7 @@ class LoginActivity : BaseActivity() {
alertDialog.setMessage(R.string.forgot_password_description)
alertDialog.setAdditionalContentView(input)
alertDialog.addButton(R.string.send, true) { _, _ ->
userRepository.sendPasswordResetEmail(input.text.toString()).subscribe({ showPasswordEmailConfirmation() }, RxErrorHandler.handleEmptyError())
userRepository.sendPasswordResetEmail(input.text.toString()).subscribe({ showPasswordEmailConfirmation() }, ExceptionHandler.rx())
}
alertDialog.addCancelButton()
alertDialog.show()

View file

@ -37,9 +37,9 @@ import com.habitrpg.android.habitica.extensions.subscribeWithErrorHandler
import com.habitrpg.android.habitica.extensions.updateStatusBarColor
import com.habitrpg.android.habitica.helpers.AmplitudeManager
import com.habitrpg.android.habitica.helpers.AppConfigManager
import com.habitrpg.android.habitica.helpers.ExceptionHandler
import com.habitrpg.android.habitica.helpers.MainNavigationController
import com.habitrpg.android.habitica.helpers.NotificationOpenHandler
import com.habitrpg.android.habitica.helpers.RxErrorHandler
import com.habitrpg.android.habitica.helpers.SoundManager
import com.habitrpg.android.habitica.interactors.CheckClassSelectionUseCase
import com.habitrpg.android.habitica.interactors.DisplayItemDropUseCase
@ -68,6 +68,7 @@ import com.habitrpg.shared.habitica.models.responses.TaskScoringResult
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.launch
import java.util.Date
import javax.inject.Inject
@ -130,8 +131,8 @@ open class MainActivity : BaseActivity(), SnackbarActivity {
}
try {
launchTrace = FirebasePerformance.getInstance().newTrace("MainActivityLaunch")
} catch (e: Exception) {
// pass
} catch (e: IllegalStateException) {
ExceptionHandler.reportError(e)
}
launchTrace?.start()
super.onCreate(savedInstanceState)
@ -393,16 +394,13 @@ open class MainActivity : BaseActivity(), SnackbarActivity {
val quest = user.party?.quest
if (quest?.completed?.isNotBlank() == true) {
compositeSubscription.add(
inventoryRepository.getQuestContent(user.party?.quest?.completed ?: "").firstElement().subscribe(
{
QuestCompletedDialog.showWithQuest(this, it)
viewModel.updateUser("party.quest.completed", "")
},
RxErrorHandler.handleEmptyError()
)
)
lifecycleScope.launch(ExceptionHandler.coroutine()) {
val questContent = inventoryRepository.getQuestContent(user.party?.quest?.completed ?: "").firstOrNull()
if (questContent != null) {
QuestCompletedDialog.showWithQuest(this@MainActivity, questContent)
}
viewModel.updateUser("party.quest.completed", "")
}
}
if (user.flags?.welcomed == false) {
@ -451,14 +449,14 @@ open class MainActivity : BaseActivity(), SnackbarActivity {
viewModel.user.value, data.experienceDelta, data.healthDelta, data.goldDelta, data.manaDelta, damageValue, data.hasLeveledUp, data.level
)
)
.subscribe({ }, RxErrorHandler.handleEmptyError())
.subscribe({ }, ExceptionHandler.rx())
)
}
val showItemsFound = userQuestStatus == UserQuestStatus.QUEST_COLLECT
compositeSubscription.add(
displayItemDropUseCase.observable(DisplayItemDropUseCase.RequestValues(data, this, snackbarContainer, showItemsFound))
.subscribe({ }, RxErrorHandler.handleEmptyError())
.subscribe({ }, ExceptionHandler.rx())
)
}
@ -517,7 +515,7 @@ open class MainActivity : BaseActivity(), SnackbarActivity {
startActivity(intent)
}
} catch (e: PackageManager.NameNotFoundException) {
RxErrorHandler.reportError(e)
ExceptionHandler.reportError(e)
}
}
}

View file

@ -10,7 +10,7 @@ import com.habitrpg.android.habitica.api.MaintenanceApiService
import com.habitrpg.android.habitica.components.UserComponent
import com.habitrpg.android.habitica.data.ApiClient
import com.habitrpg.android.habitica.databinding.ActivityMaintenanceBinding
import com.habitrpg.android.habitica.helpers.RxErrorHandler
import com.habitrpg.android.habitica.helpers.ExceptionHandler
import com.habitrpg.common.habitica.helpers.setMarkdown
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.schedulers.Schedulers
@ -76,7 +76,7 @@ class MaintenanceActivity : BaseActivity() {
finish()
}
},
RxErrorHandler.handleEmptyError()
ExceptionHandler.rx()
)
)
}

View file

@ -13,15 +13,17 @@ import android.widget.RatingBar
import android.widget.TextView
import androidx.activity.viewModels
import androidx.core.content.ContextCompat
import androidx.lifecycle.lifecycleScope
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.components.UserComponent
import com.habitrpg.android.habitica.data.InventoryRepository
import com.habitrpg.android.habitica.data.SocialRepository
import com.habitrpg.android.habitica.databinding.ActivityNotificationsBinding
import com.habitrpg.android.habitica.extensions.fromHtml
import com.habitrpg.android.habitica.helpers.RxErrorHandler
import com.habitrpg.common.habitica.models.Notification
import com.habitrpg.android.habitica.helpers.ExceptionHandler
import com.habitrpg.android.habitica.models.inventory.QuestContent
import com.habitrpg.android.habitica.ui.viewmodels.NotificationsViewModel
import com.habitrpg.common.habitica.models.Notification
import com.habitrpg.common.habitica.models.notifications.GroupTaskApprovedData
import com.habitrpg.common.habitica.models.notifications.GroupTaskNeedsWorkData
import com.habitrpg.common.habitica.models.notifications.GroupTaskRequiresApprovalData
@ -31,7 +33,8 @@ import com.habitrpg.common.habitica.models.notifications.NewStuffData
import com.habitrpg.common.habitica.models.notifications.PartyInvitationData
import com.habitrpg.common.habitica.models.notifications.QuestInvitationData
import com.habitrpg.common.habitica.models.notifications.UnallocatedPointsData
import com.habitrpg.android.habitica.ui.viewmodels.NotificationsViewModel
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.launch
import javax.inject.Inject
class NotificationsActivity : BaseActivity(), androidx.swiperefreshlayout.widget.SwipeRefreshLayout.OnRefreshListener {
@ -68,7 +71,7 @@ class NotificationsActivity : BaseActivity(), androidx.swiperefreshlayout.widget
this.setNotifications(it)
viewModel.markNotificationsAsSeen(it)
},
RxErrorHandler.handleEmptyError()
ExceptionHandler.rx()
)
)
@ -90,14 +93,10 @@ class NotificationsActivity : BaseActivity(), androidx.swiperefreshlayout.widget
override fun onRefresh() {
binding.notificationsRefreshLayout.isRefreshing = true
compositeSubscription.add(
viewModel.refreshNotifications().subscribe(
{
binding.notificationsRefreshLayout.isRefreshing = false
},
RxErrorHandler.handleEmptyError()
)
)
lifecycleScope.launch(ExceptionHandler.coroutine()) {
viewModel.refreshNotifications()
binding.notificationsRefreshLayout.isRefreshing = false
}
}
private fun setNotifications(notifications: List<Notification>) {
@ -314,16 +313,12 @@ class NotificationsActivity : BaseActivity(), androidx.swiperefreshlayout.widget
// hide view until we have loaded quest data and populated the values
view?.visibility = View.GONE
compositeSubscription.add(
inventoryRepository.getQuestContent(data?.questKey ?: "")
.firstElement()
.subscribe(
{
updateQuestInvitationView(view, it)
},
RxErrorHandler.handleEmptyError()
)
)
lifecycleScope.launch(ExceptionHandler.coroutine()) {
val questContent = inventoryRepository.getQuestContent(data?.questKey ?: "").firstOrNull()
if (questContent != null) {
updateQuestInvitationView(view, questContent)
}
}
return view
}

View file

@ -15,7 +15,7 @@ import com.habitrpg.android.habitica.components.UserComponent
import com.habitrpg.android.habitica.data.SocialRepository
import com.habitrpg.android.habitica.databinding.ActivityReportMessageBinding
import com.habitrpg.common.habitica.extensions.getThemeColor
import com.habitrpg.android.habitica.helpers.RxErrorHandler
import com.habitrpg.android.habitica.helpers.ExceptionHandler
import com.habitrpg.android.habitica.ui.helpers.dismissKeyboard
import com.habitrpg.common.habitica.helpers.setMarkdown
import javax.inject.Inject
@ -98,7 +98,7 @@ class ReportMessageActivity : BaseActivity() {
.doOnError { isReporting = false }
.subscribe(
{ finish() },
RxErrorHandler.handleEmptyError()
ExceptionHandler.rx()
)
}
}

View file

@ -14,23 +14,25 @@ import androidx.core.content.edit
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager
import androidx.fragment.app.FragmentPagerAdapter
import androidx.lifecycle.lifecycleScope
import androidx.preference.PreferenceManager
import androidx.viewpager.widget.ViewPager
import com.habitrpg.android.habitica.R
import com.habitrpg.common.habitica.api.HostConfig
import com.habitrpg.android.habitica.components.UserComponent
import com.habitrpg.android.habitica.data.ApiClient
import com.habitrpg.android.habitica.data.InventoryRepository
import com.habitrpg.android.habitica.data.TaskRepository
import com.habitrpg.android.habitica.databinding.ActivitySetupBinding
import com.habitrpg.android.habitica.helpers.AmplitudeManager
import com.habitrpg.android.habitica.helpers.RxErrorHandler
import com.habitrpg.android.habitica.helpers.ExceptionHandler
import com.habitrpg.android.habitica.models.user.User
import com.habitrpg.android.habitica.ui.fragments.setup.AvatarSetupFragment
import com.habitrpg.android.habitica.ui.fragments.setup.TaskSetupFragment
import com.habitrpg.android.habitica.ui.fragments.setup.WelcomeFragment
import com.habitrpg.common.habitica.api.HostConfig
import com.viewpagerindicator.IconPagerAdapter
import io.reactivex.rxjava3.core.BackpressureStrategy
import kotlinx.coroutines.launch
import java.util.Calendar
import java.util.Locale
import javax.inject.Inject
@ -69,8 +71,12 @@ class SetupActivity : BaseActivity(), ViewPager.OnPageChangeListener {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
compositeSubscription.add(userRepository.getUserFlowable().subscribe({ this.onUserReceived(it) }, RxErrorHandler.handleEmptyError()))
compositeSubscription.add(userRepository.retrieveUser().subscribe({}, RxErrorHandler.handleEmptyError()))
lifecycleScope.launch(ExceptionHandler.coroutine()) {
userRepository.getUser().collect { onUserReceived(it) }
}
lifecycleScope.launch(ExceptionHandler.coroutine()) {
userRepository.retrieveUser(true, true)
}
val additionalData = HashMap<String, Any>()
additionalData["status"] = "displayed"
AmplitudeManager.sendEvent("setup", AmplitudeManager.EVENT_CATEGORY_BEHAVIOUR, AmplitudeManager.EVENT_HITTYPE_EVENT, additionalData)
@ -80,7 +86,7 @@ class SetupActivity : BaseActivity(), ViewPager.OnPageChangeListener {
if (language == currentDeviceLanguage) {
compositeSubscription.add(
apiClient.registrationLanguage(currentDeviceLanguage)
.subscribe({ }, RxErrorHandler.handleEmptyError())
.subscribe({ }, ExceptionHandler.rx())
)
}
}
@ -135,7 +141,7 @@ class SetupActivity : BaseActivity(), ViewPager.OnPageChangeListener {
this.completedSetup = true
createdTasks = true
newTasks?.let {
this.taskRepository.createTasks(it).subscribe({ onUserReceived(user) }, RxErrorHandler.handleEmptyError())
this.taskRepository.createTasks(it).subscribe({ onUserReceived(user) }, ExceptionHandler.rx())
}
} else if (binding.viewPager.currentItem == 0) {
@ -211,7 +217,7 @@ class SetupActivity : BaseActivity(), ViewPager.OnPageChangeListener {
}
startMainActivity()
},
RxErrorHandler.handleEmptyError()
ExceptionHandler.rx()
)
)
return
@ -235,7 +241,7 @@ class SetupActivity : BaseActivity(), ViewPager.OnPageChangeListener {
compositeSubscription.add(
userRepository.updateUser("profile.name", displayName)
.flatMap { userRepository.updateLoginName(username).toFlowable() }
.subscribe({ }, RxErrorHandler.handleEmptyError())
.subscribe({ }, ExceptionHandler.rx())
)
}

View file

@ -10,9 +10,13 @@ import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.components.UserComponent
import com.habitrpg.android.habitica.data.SocialRepository
import com.habitrpg.android.habitica.databinding.ActivitySkillMembersBinding
import com.habitrpg.android.habitica.helpers.RxErrorHandler
import com.habitrpg.android.habitica.helpers.ExceptionHandler
import com.habitrpg.android.habitica.ui.adapter.social.PartyMemberRecyclerViewAdapter
import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.flatMapConcat
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import javax.inject.Inject
@ -54,16 +58,16 @@ class SkillMemberActivity : BaseActivity() {
setResult(Activity.RESULT_OK, resultIntent)
finish()
},
RxErrorHandler.handleEmptyError()
ExceptionHandler.rx()
)?.let { compositeSubscription.add(it) }
binding.recyclerView.adapter = viewAdapter
val user = userViewModel.user.value
lifecycleScope.launch {
socialRepository.getGroupMembers(user?.party?.id ?: "")
.collect {
viewAdapter?.data = it
}
lifecycleScope.launch(ExceptionHandler.coroutine()) {
userRepository.getUser()
.map { it?.party?.id }
.filterNotNull()
.flatMapLatest { socialRepository.getGroupMembers(it) }
.collect { viewAdapter?.data = it }
}
}
}

View file

@ -13,7 +13,7 @@ import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.components.UserComponent
import com.habitrpg.android.habitica.data.TaskRepository
import com.habitrpg.android.habitica.databinding.ActivitySkillTasksBinding
import com.habitrpg.android.habitica.helpers.RxErrorHandler
import com.habitrpg.android.habitica.helpers.ExceptionHandler
import com.habitrpg.android.habitica.models.tasks.Task
import com.habitrpg.shared.habitica.models.tasks.TaskType
import com.habitrpg.android.habitica.modules.AppModule
@ -60,7 +60,7 @@ class SkillTasksActivity : BaseActivity() {
1 -> TaskType.DAILY
else -> TaskType.TODO
}
compositeSubscription.add(fragment.getTaskSelectionEvents().subscribe({ task -> taskSelected(task) }, RxErrorHandler.handleEmptyError()))
compositeSubscription.add(fragment.getTaskSelectionEvents().subscribe({ task -> taskSelected(task) }, ExceptionHandler.rx()))
viewFragmentsDictionary.put(position, fragment)
return fragment
}

View file

@ -22,6 +22,7 @@ import androidx.core.os.bundleOf
import androidx.core.view.children
import androidx.core.view.forEachIndexed
import androidx.core.widget.NestedScrollView
import androidx.lifecycle.lifecycleScope
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.components.UserComponent
import com.habitrpg.android.habitica.data.ChallengeRepository
@ -30,7 +31,9 @@ import com.habitrpg.android.habitica.data.TaskRepository
import com.habitrpg.android.habitica.databinding.ActivityTaskFormBinding
import com.habitrpg.android.habitica.extensions.OnChangeTextWatcher
import com.habitrpg.android.habitica.extensions.addCancelButton
import com.habitrpg.android.habitica.helpers.RxErrorHandler
import com.habitrpg.common.habitica.extensions.dpToPx
import com.habitrpg.common.habitica.extensions.getThemeColor
import com.habitrpg.android.habitica.helpers.ExceptionHandler
import com.habitrpg.android.habitica.helpers.TaskAlarmManager
import com.habitrpg.android.habitica.models.Tag
import com.habitrpg.android.habitica.models.social.Challenge
@ -46,6 +49,8 @@ import com.habitrpg.shared.habitica.models.tasks.HabitResetOption
import com.habitrpg.shared.habitica.models.tasks.TaskType
import io.realm.RealmList
import java.util.Date
import kotlinx.coroutines.launch
import java.util.*
import javax.inject.Inject
class TaskFormActivity : BaseActivity() {
@ -160,7 +165,7 @@ class TaskFormActivity : BaseActivity() {
tags = it
setTagViews()
},
RxErrorHandler.handleEmptyError()
ExceptionHandler.rx()
)
)
userViewModel.user.observe(this) {
@ -216,12 +221,12 @@ class TaskFormActivity : BaseActivity() {
binding.challengeNameView.visibility = View.VISIBLE
disableEditingForUneditableFieldsInChallengeTask()
},
RxErrorHandler.handleEmptyError()
ExceptionHandler.rx()
)
)
}
},
RxErrorHandler.handleEmptyError()
ExceptionHandler.rx()
)
)
}
@ -582,7 +587,7 @@ class TaskFormActivity : BaseActivity() {
alert.setTitle(R.string.are_you_sure)
alert.addButton(R.string.delete_task, true) { _, _ ->
if (task?.isValid != true) return@addButton
task?.id?.let { taskRepository.deleteTask(it).subscribe({ }, RxErrorHandler.handleEmptyError()) }
task?.id?.let { taskRepository.deleteTask(it).subscribe({ }, ExceptionHandler.rx()) }
finish()
}
alert.addCancelButton()
@ -602,12 +607,14 @@ class TaskFormActivity : BaseActivity() {
compositeSubscription.add(
challengeRepository.leaveChallenge(it, "keep-all")
.flatMap { taskRepository.deleteTask(task?.id ?: "") }
.flatMap { userRepository.retrieveUser(true, true) }
.subscribe(
{
lifecycleScope.launch(ExceptionHandler.coroutine()) {
userRepository.retrieveUser(true, true)
}
finish()
},
RxErrorHandler.handleEmptyError()
ExceptionHandler.rx()
)
)
}
@ -616,12 +623,14 @@ class TaskFormActivity : BaseActivity() {
challenge?.let {
compositeSubscription.add(
challengeRepository.leaveChallenge(it, "remove-all")
.flatMap { userRepository.retrieveUser(true, true) }
.subscribe(
{
lifecycleScope.launch(ExceptionHandler.coroutine()) {
userRepository.retrieveUser(true, true)
}
finish()
},
RxErrorHandler.handleEmptyError()
ExceptionHandler.rx()
)
)
}
@ -629,7 +638,7 @@ class TaskFormActivity : BaseActivity() {
alert.setExtraCloseButtonVisibility(View.VISIBLE)
alert.show()
},
RxErrorHandler.handleEmptyError()
ExceptionHandler.rx()
)
)
}
@ -648,28 +657,32 @@ class TaskFormActivity : BaseActivity() {
dialog.setMessage(this.getString(R.string.broken_challenge_description, taskCount))
dialog.addButton(this.getString(R.string.keep_x_tasks, taskCount), true) { _, _ ->
taskRepository.unlinkAllTasks(task.challengeID, "keep-all")
.flatMap { userRepository.retrieveUser(true, true) }
.subscribe(
{
finish()
},
RxErrorHandler.handleEmptyError()
)
{
lifecycleScope.launch(ExceptionHandler.coroutine()) {
userRepository.retrieveUser(true, true)
}
finish()
},
ExceptionHandler.rx()
)
}
dialog.addButton(this.getString(R.string.delete_x_tasks, taskCount), false, true) { _, _ ->
taskRepository.unlinkAllTasks(task.challengeID, "remove-all")
.flatMap { userRepository.retrieveUser(true, true) }
.subscribe(
{
finish()
},
RxErrorHandler.handleEmptyError()
)
{
lifecycleScope.launch(ExceptionHandler.coroutine()) {
userRepository.retrieveUser(true, true)
}
finish()
},
ExceptionHandler.rx()
)
}
dialog.setExtraCloseButtonVisibility(View.VISIBLE)
dialog.show()
},
RxErrorHandler.handleEmptyError()
ExceptionHandler.rx()
)
)
}

View file

@ -6,17 +6,20 @@ import android.os.Bundle
import android.text.method.LinkMovementMethod
import android.view.View
import androidx.core.content.ContextCompat
import androidx.lifecycle.lifecycleScope
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.components.UserComponent
import com.habitrpg.android.habitica.databinding.ActivityVerifyUsernameBinding
import com.habitrpg.android.habitica.extensions.OnChangeTextWatcher
import com.habitrpg.android.habitica.extensions.runDelayed
import com.habitrpg.android.habitica.helpers.RxErrorHandler
import com.habitrpg.android.habitica.helpers.ExceptionHandler
import com.habitrpg.android.habitica.ui.views.HabiticaIconsHelper
import com.habitrpg.android.habitica.ui.views.HabiticaSnackbar
import io.reactivex.rxjava3.core.BackpressureStrategy
import io.reactivex.rxjava3.core.Flowable
import io.reactivex.rxjava3.subjects.PublishSubject
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.launch
import java.util.concurrent.TimeUnit
class VerifyUsernameActivity : BaseActivity() {
@ -90,25 +93,23 @@ class VerifyUsernameActivity : BaseActivity() {
binding.issuesTextView.visibility = View.VISIBLE
binding.issuesTextView.text = it.issues.joinToString("\n")
}
},
{ displayNameUsable, usernameUsable -> displayNameUsable && usernameUsable.isUsable }
)
}
) { displayNameUsable, usernameUsable -> displayNameUsable && usernameUsable.isUsable }
.subscribe(
{
binding.confirmUsernameButton.isEnabled = it
},
RxErrorHandler.handleEmptyError()
ExceptionHandler.rx()
)
)
compositeSubscription.add(
userRepository.getUserFlowable().firstElement().subscribe {
binding.displayNameEditText.setText(it.profile?.name)
displayNameVerificationEvents.onNext(it.profile?.name ?: "")
binding.usernameEditText.setText(it.authentication?.localAuthentication?.username)
usernameVerificationEvents.onNext(it.username ?: "")
}
)
lifecycleScope.launch(ExceptionHandler.coroutine()) {
val user = userRepository.getUser().firstOrNull()
binding.displayNameEditText.setText(user?.profile?.name)
displayNameVerificationEvents.onNext(user?.profile?.name ?: "")
binding.usernameEditText.setText(user?.authentication?.localAuthentication?.username)
usernameVerificationEvents.onNext(user?.username ?: "")
}
}
private fun confirmNames() {
@ -118,7 +119,7 @@ class VerifyUsernameActivity : BaseActivity() {
.flatMap { userRepository.updateLoginName(binding.usernameEditText.text.toString()).toFlowable() }
.doOnComplete { showConfirmationAndFinish() }
.doOnEach { binding.confirmUsernameButton.isClickable = true }
.subscribe({ }, RxErrorHandler.handleEmptyError())
.subscribe({ }, ExceptionHandler.rx())
)
}

View file

@ -15,7 +15,7 @@ import io.reactivex.rxjava3.core.BackpressureStrategy
import io.reactivex.rxjava3.core.Flowable
import io.reactivex.rxjava3.subjects.PublishSubject
class InboxAdapter(private var user: User?, private var replyToUser: Member) : PagedListAdapter<ChatMessage, ChatRecyclerViewHolder>(DIFF_CALLBACK) {
class InboxAdapter(private var user: User?, private var replyToUser: Member?) : PagedListAdapter<ChatMessage, ChatRecyclerViewHolder>(DIFF_CALLBACK) {
private val FIRST_MESSAGE = 0
private val NORMAL_MESSAGE = 1
@ -43,7 +43,7 @@ class InboxAdapter(private var user: User?, private var replyToUser: Member) : P
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ChatRecyclerViewHolder {
return if (viewType == FIRST_MESSAGE) ChatRecyclerIntroViewHolder(parent.inflate(R.layout.tavern_chat_intro_item), replyToUser.id!!)
return if (viewType == FIRST_MESSAGE) ChatRecyclerIntroViewHolder(parent.inflate(R.layout.tavern_chat_intro_item), replyToUser?.id ?: "")
else ChatRecyclerMessageViewHolder(parent.inflate(R.layout.chat_item), user?.id ?: "", false)
}

View file

@ -16,7 +16,7 @@ import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.components.UserComponent
import com.habitrpg.android.habitica.data.InventoryRepository
import com.habitrpg.android.habitica.databinding.FragmentRefreshRecyclerviewBinding
import com.habitrpg.android.habitica.helpers.RxErrorHandler
import com.habitrpg.android.habitica.helpers.ExceptionHandler
import com.habitrpg.android.habitica.ui.adapter.AchievementsAdapter
import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel
import kotlinx.coroutines.flow.combine
@ -93,7 +93,7 @@ class AchievementsFragment : BaseMainFragment<FragmentRefreshRecyclerviewBinding
binding?.refreshLayout?.setOnRefreshListener(this)
lifecycleScope.launch {
lifecycleScope.launch(ExceptionHandler.coroutine()) {
userRepository.getAchievements().combine(userRepository.getQuestAchievements()) { achievements, questAchievements ->
return@combine Pair(achievements, questAchievements)
}.combine(userRepository.getQuestAchievements()
@ -171,7 +171,7 @@ class AchievementsFragment : BaseMainFragment<FragmentRefreshRecyclerviewBinding
userRepository.retrieveAchievements().subscribe(
{
},
RxErrorHandler.handleEmptyError(), { binding?.refreshLayout?.isRefreshing = false }
ExceptionHandler.rx(), { binding?.refreshLayout?.isRefreshing = false }
)
)
}

View file

@ -10,7 +10,7 @@ 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.RxErrorHandler
import com.habitrpg.android.habitica.helpers.ExceptionHandler
import com.habitrpg.android.habitica.ui.activities.MainActivity
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.disposables.CompositeDisposable
@ -80,7 +80,7 @@ abstract class BaseDialogFragment<VB : ViewBinding> : BottomSheetDialogFragment(
mainActivity.displayTutorialStep(step, tutorialTexts, tutorialCanBeDeferred)
}
},
RxErrorHandler.handleEmptyError()
ExceptionHandler.rx()
)
)
}

View file

@ -9,7 +9,7 @@ import androidx.viewbinding.ViewBinding
import com.habitrpg.android.habitica.HabiticaBaseApplication
import com.habitrpg.android.habitica.components.UserComponent
import com.habitrpg.android.habitica.data.TutorialRepository
import com.habitrpg.android.habitica.helpers.RxErrorHandler
import com.habitrpg.android.habitica.helpers.ExceptionHandler
import com.habitrpg.android.habitica.proxy.AnalyticsManager
import com.habitrpg.android.habitica.ui.activities.MainActivity
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
@ -84,7 +84,7 @@ abstract class BaseFragment<VB : ViewBinding> : Fragment() {
mainActivity.displayTutorialStep(step, tutorialTexts, tutorialCanBeDeferred)
}
},
RxErrorHandler.handleEmptyError()
ExceptionHandler.rx()
)
)
}

View file

@ -29,10 +29,11 @@ import com.habitrpg.android.habitica.databinding.DrawerMainBinding
import com.habitrpg.android.habitica.extensions.getMinuteOrSeconds
import com.habitrpg.android.habitica.extensions.getRemainingString
import com.habitrpg.android.habitica.extensions.getShortRemainingString
import com.habitrpg.common.habitica.extensions.getThemeColor
import com.habitrpg.android.habitica.extensions.subscribeWithErrorHandler
import com.habitrpg.android.habitica.helpers.AppConfigManager
import com.habitrpg.android.habitica.helpers.MainNavigationController
import com.habitrpg.android.habitica.helpers.RxErrorHandler
import com.habitrpg.android.habitica.helpers.ExceptionHandler
import com.habitrpg.android.habitica.models.WorldStateEvent
import com.habitrpg.android.habitica.models.inventory.Item
import com.habitrpg.android.habitica.models.promotions.HabiticaPromotion
@ -140,7 +141,7 @@ class NavigationDrawerFragment : DialogFragment() {
{
setSelection(it.transitionId, it.bundle, true)
},
RxErrorHandler.handleEmptyError()
ExceptionHandler.rx()
)
)
subscriptions?.add(
@ -151,7 +152,7 @@ class NavigationDrawerFragment : DialogFragment() {
}
updatePromo()
},
RxErrorHandler.handleEmptyError()
ExceptionHandler.rx()
)
)
@ -172,12 +173,12 @@ class NavigationDrawerFragment : DialogFragment() {
updateSeasonalMenuEntries(gearEvent, pair.second)
}
},
RxErrorHandler.handleEmptyError()
ExceptionHandler.rx()
)
)
if (configManager.enableTeamBoards()) {
lifecycleScope.launch {
lifecycleScope.launch(ExceptionHandler.coroutine()) {
userRepository.getTeamPlans()
.distinctUntilChanged()
.collect {

View file

@ -13,7 +13,7 @@ import com.habitrpg.android.habitica.databinding.FragmentStatsBinding
import com.habitrpg.android.habitica.extensions.addOkButton
import com.habitrpg.common.habitica.extensions.getThemeColor
import com.habitrpg.android.habitica.extensions.setScaledPadding
import com.habitrpg.android.habitica.helpers.RxErrorHandler
import com.habitrpg.android.habitica.helpers.ExceptionHandler
import com.habitrpg.android.habitica.helpers.UserStatComputer
import com.habitrpg.shared.habitica.models.tasks.Attribute
import com.habitrpg.android.habitica.models.user.Stats
@ -132,7 +132,7 @@ class StatsFragment : BaseMainFragment<FragmentStatsBinding>() {
binding?.automaticAllocationSwitch?.setOnCheckedChangeListener { _, isChecked ->
userRepository.updateUser("preferences.automaticAllocation", isChecked)
.subscribe({}, RxErrorHandler.handleEmptyError())
.subscribe({}, ExceptionHandler.rx())
}
binding?.strengthStatsView?.allocateAction = { allocatePoint(Attribute.STRENGTH) }
@ -163,7 +163,7 @@ class StatsFragment : BaseMainFragment<FragmentStatsBinding>() {
userRepository.updateUser(
"preferences.allocationMode",
allocationMode
).subscribe({}, RxErrorHandler.handleEmptyError())
).subscribe({}, ExceptionHandler.rx())
)
binding?.distributeEvenlyButton?.isChecked = allocationMode == Stats.AUTO_ALLOCATE_FLAT
binding?.distributeClassButton?.isChecked = allocationMode == Stats.AUTO_ALLOCATE_CLASSBASED
@ -179,7 +179,7 @@ class StatsFragment : BaseMainFragment<FragmentStatsBinding>() {
private fun allocatePoint(stat: Attribute) {
compositeSubscription.add(
userRepository.allocatePoint(stat).subscribe({ }, RxErrorHandler.handleEmptyError())
userRepository.allocatePoint(stat).subscribe({ }, ExceptionHandler.rx())
)
}
@ -326,7 +326,7 @@ class StatsFragment : BaseMainFragment<FragmentStatsBinding>() {
binding?.constitutionStatsView?.equipmentValue = constitution
binding?.perceptionStatsView?.equipmentValue = perception
},
RxErrorHandler.handleEmptyError()
ExceptionHandler.rx()
)
)
}

View file

@ -12,6 +12,7 @@ import android.view.ViewGroup
import android.widget.CheckBox
import androidx.core.content.ContextCompat
import androidx.core.view.doOnLayout
import androidx.lifecycle.lifecycleScope
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import com.google.android.flexbox.AlignItems
import com.google.android.flexbox.FlexDirection.ROW
@ -24,7 +25,7 @@ import com.habitrpg.android.habitica.data.InventoryRepository
import com.habitrpg.android.habitica.databinding.BottomSheetBackgroundsFilterBinding
import com.habitrpg.android.habitica.databinding.FragmentRefreshRecyclerviewBinding
import com.habitrpg.android.habitica.extensions.setTintWith
import com.habitrpg.android.habitica.helpers.RxErrorHandler
import com.habitrpg.android.habitica.helpers.ExceptionHandler
import com.habitrpg.android.habitica.models.CustomizationFilter
import com.habitrpg.android.habitica.models.inventory.Customization
import com.habitrpg.android.habitica.models.user.OwnedCustomization
@ -41,6 +42,7 @@ import io.reactivex.rxjava3.core.BackpressureStrategy
import io.reactivex.rxjava3.kotlin.combineLatest
import io.reactivex.rxjava3.subjects.BehaviorSubject
import io.reactivex.rxjava3.subjects.PublishSubject
import kotlinx.coroutines.launch
import javax.inject.Inject
class AvatarCustomizationFragment :
@ -81,22 +83,19 @@ class AvatarCustomizationFragment :
adapter.getSelectCustomizationEvents()
.flatMap { customization ->
if (customization.type == "background") {
val using = if (customization.identifier?.isBlank() != false) {
customization.unlockPath + activeCustomization
} else customization.unlockPath
userRepository.unlockPath(using, 0)
.flatMap { userRepository.retrieveUser(false, true, true) }
userRepository.unlockPath(customization)
//TODO: .flatMap { userRepository.retrieveUser(false, true, true) }
} else {
userRepository.useCustomization(customization.type ?: "", customization.category, customization.identifier ?: "")
}
}
.subscribe({ }, RxErrorHandler.handleEmptyError())
.subscribe({ }, ExceptionHandler.rx())
)
compositeSubscription.add(
this.inventoryRepository.getInAppRewards()
.map { rewards -> rewards.map { it.key } }
.subscribe({ adapter.setPinnedItemKeys(it) }, RxErrorHandler.handleEmptyError())
.subscribe({ adapter.setPinnedItemKeys(it) }, ExceptionHandler.rx())
)
return super.onCreateView(inflater, container, savedInstanceState)
@ -212,12 +211,12 @@ class AvatarCustomizationFragment :
)
}
},
RxErrorHandler.handleEmptyError()
ExceptionHandler.rx()
)
)
if (type == "hair" && (category == "beard" || category == "mustache")) {
val otherCategory = if (category == "mustache") "beard" else "mustache"
compositeSubscription.add(customizationRepository.getCustomizations(type, otherCategory, true).subscribe({ adapter.additionalSetItems = it }, RxErrorHandler.handleEmptyError()))
compositeSubscription.add(customizationRepository.getCustomizations(type, otherCategory, true).subscribe({ adapter.additionalSetItems = it }, ExceptionHandler.rx()))
}
}
@ -270,14 +269,10 @@ class AvatarCustomizationFragment :
}
override fun onRefresh() {
compositeSubscription.add(
userRepository.retrieveUser(withTasks = false, forced = true).subscribe(
{
binding?.refreshLayout?.isRefreshing = false
},
RxErrorHandler.handleEmptyError()
)
)
lifecycleScope.launch(ExceptionHandler.coroutine()) {
userRepository.retrieveUser(true, true)
binding?.refreshLayout?.isRefreshing = false
}
}
fun showFilterDialog() {

View file

@ -4,13 +4,14 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.GridLayoutManager
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.components.UserComponent
import com.habitrpg.android.habitica.data.InventoryRepository
import com.habitrpg.android.habitica.databinding.FragmentRefreshRecyclerviewBinding
import com.habitrpg.android.habitica.helpers.RxErrorHandler
import com.habitrpg.android.habitica.helpers.ExceptionHandler
import com.habitrpg.android.habitica.models.responses.UnlockResponse
import com.habitrpg.android.habitica.models.user.User
import com.habitrpg.android.habitica.ui.adapter.CustomizationEquipmentRecyclerViewAdapter
@ -19,6 +20,7 @@ import com.habitrpg.android.habitica.ui.helpers.MarginDecoration
import com.habitrpg.android.habitica.ui.helpers.SafeDefaultItemAnimator
import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel
import io.reactivex.rxjava3.core.Flowable
import kotlinx.coroutines.launch
import javax.inject.Inject
class AvatarEquipmentFragment :
@ -55,14 +57,14 @@ class AvatarEquipmentFragment :
val key = (if (equipment.key?.isNotBlank() != true) activeEquipment else equipment.key) ?: ""
inventoryRepository.equip(if (userViewModel.user.value?.preferences?.costume == true) "costume" else "equipped", key)
}
.subscribe({ }, RxErrorHandler.handleEmptyError())
.subscribe({ }, ExceptionHandler.rx())
)
compositeSubscription.add(
adapter.getUnlockCustomizationEvents()
.flatMap<UnlockResponse> {
Flowable.empty()
}
.subscribe({ }, RxErrorHandler.handleEmptyError())
.subscribe({ }, ExceptionHandler.rx())
)
return super.onCreateView(inflater, container, savedInstanceState)
}
@ -110,7 +112,7 @@ class AvatarEquipmentFragment :
{
adapter.setEquipment(it)
},
RxErrorHandler.handleEmptyError()
ExceptionHandler.rx()
)
)
}
@ -148,13 +150,9 @@ class AvatarEquipmentFragment :
}
override fun onRefresh() {
compositeSubscription.add(
userRepository.retrieveUser(false, true).subscribe(
{
binding?.refreshLayout?.isRefreshing = false
},
RxErrorHandler.handleEmptyError()
)
)
lifecycleScope.launch(ExceptionHandler.coroutine()) {
userRepository.retrieveUser(true, true)
binding?.refreshLayout?.isRefreshing = false
}
}
}

View file

@ -8,7 +8,7 @@ import android.widget.AdapterView
import com.habitrpg.android.habitica.components.UserComponent
import com.habitrpg.android.habitica.databinding.FragmentAvatarOverviewBinding
import com.habitrpg.android.habitica.helpers.MainNavigationController
import com.habitrpg.android.habitica.helpers.RxErrorHandler
import com.habitrpg.android.habitica.helpers.ExceptionHandler
import com.habitrpg.android.habitica.models.user.User
import com.habitrpg.android.habitica.ui.fragments.BaseMainFragment
import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel
@ -99,7 +99,7 @@ class AvatarOverviewFragment : BaseMainFragment<FragmentAvatarOverviewBinding>()
compositeSubscription.add(
userRepository.updateUser("preferences.size", newSize)
.subscribe({ }, RxErrorHandler.handleEmptyError())
.subscribe({ }, ExceptionHandler.rx())
)
}

View file

@ -4,6 +4,7 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
@ -12,11 +13,12 @@ import com.habitrpg.android.habitica.components.UserComponent
import com.habitrpg.android.habitica.data.InventoryRepository
import com.habitrpg.android.habitica.databinding.FragmentRefreshRecyclerviewBinding
import com.habitrpg.android.habitica.helpers.MainNavigationController
import com.habitrpg.android.habitica.helpers.RxErrorHandler
import com.habitrpg.android.habitica.helpers.ExceptionHandler
import com.habitrpg.android.habitica.ui.adapter.inventory.EquipmentRecyclerViewAdapter
import com.habitrpg.android.habitica.ui.fragments.BaseMainFragment
import com.habitrpg.common.habitica.helpers.EmptyItem
import com.habitrpg.android.habitica.ui.helpers.SafeDefaultItemAnimator
import kotlinx.coroutines.launch
import javax.inject.Inject
class EquipmentDetailFragment :
@ -45,7 +47,7 @@ class EquipmentDetailFragment :
): View? {
compositeSubscription.add(
this.adapter.equipEvents.flatMapMaybe { key -> inventoryRepository.equipGear(key, isCostume ?: false).firstElement() }
.subscribe({ }, RxErrorHandler.handleEmptyError())
.subscribe({ }, ExceptionHandler.rx())
)
return super.onCreateView(inflater, container, savedInstanceState)
}
@ -80,7 +82,7 @@ class EquipmentDetailFragment :
binding?.recyclerView?.addItemDecoration(DividerItemDecoration(getActivity(), DividerItemDecoration.VERTICAL))
binding?.recyclerView?.itemAnimator = SafeDefaultItemAnimator()
type?.let { type -> inventoryRepository.getOwnedEquipment(type).subscribe({ this.adapter.data = it }, RxErrorHandler.handleEmptyError()) }
type?.let { type -> inventoryRepository.getOwnedEquipment(type).subscribe({ this.adapter.data = it }, ExceptionHandler.rx()) }
}
override fun onDestroy() {
@ -93,13 +95,9 @@ class EquipmentDetailFragment :
}
override fun onRefresh() {
compositeSubscription.add(
userRepository.retrieveUser(false, true).subscribe(
{
binding?.refreshLayout?.isRefreshing = false
},
RxErrorHandler.handleEmptyError()
)
)
lifecycleScope.launch(ExceptionHandler.coroutine()) {
userRepository.retrieveUser(true, true)
binding?.refreshLayout?.isRefreshing = false
}
}
}

View file

@ -15,8 +15,8 @@ import com.habitrpg.android.habitica.databinding.FragmentItemsDialogBinding
import com.habitrpg.android.habitica.extensions.addCloseButton
import com.habitrpg.android.habitica.extensions.observeOnce
import com.habitrpg.android.habitica.extensions.subscribeWithErrorHandler
import com.habitrpg.android.habitica.helpers.ExceptionHandler
import com.habitrpg.android.habitica.helpers.MainNavigationController
import com.habitrpg.android.habitica.helpers.RxErrorHandler
import com.habitrpg.android.habitica.interactors.FeedPetUseCase
import com.habitrpg.android.habitica.interactors.HatchPetUseCase
import com.habitrpg.android.habitica.models.inventory.Egg
@ -31,11 +31,11 @@ import com.habitrpg.android.habitica.models.user.User
import com.habitrpg.android.habitica.ui.activities.MainActivity
import com.habitrpg.android.habitica.ui.adapter.inventory.ItemRecyclerAdapter
import com.habitrpg.android.habitica.ui.fragments.BaseDialogFragment
import com.habitrpg.common.habitica.helpers.EmptyItem
import com.habitrpg.android.habitica.ui.helpers.SafeDefaultItemAnimator
import com.habitrpg.common.habitica.extensions.loadImage
import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel
import com.habitrpg.android.habitica.ui.views.dialogs.OpenedMysteryitemDialog
import com.habitrpg.common.habitica.extensions.loadImage
import com.habitrpg.common.habitica.helpers.EmptyItem
import io.reactivex.rxjava3.disposables.CompositeDisposable
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.flow.map
@ -188,22 +188,24 @@ class ItemDialogFragment : BaseDialogFragment<FragmentItemsDialogBinding>() {
compositeSubscription.add(
adapter.getSellItemFlowable()
.flatMap { item -> inventoryRepository.sellItem(item) }
.subscribe({ }, RxErrorHandler.handleEmptyError())
.subscribe({ }, ExceptionHandler.rx())
)
compositeSubscription.add(
adapter.getQuestInvitationFlowable()
.flatMap { quest -> inventoryRepository.inviteToQuest(quest) }
.flatMap { socialRepository.retrieveGroup("party") }
.subscribe(
{
if (isModal) {
dismiss()
} else {
MainNavigationController.navigate(R.id.partyFragment)
lifecycleScope.launch(ExceptionHandler.coroutine()) {
socialRepository.retrieveGroup("party")
if (isModal) {
dismiss()
} else {
MainNavigationController.navigate(R.id.partyFragment)
}
}
},
RxErrorHandler.handleEmptyError()
ExceptionHandler.rx()
)
)
compositeSubscription.add(
@ -219,13 +221,13 @@ class ItemDialogFragment : BaseDialogFragment<FragmentItemsDialogBinding>() {
dialog.binding.titleView.text = it.text
dialog.binding.descriptionView.text = it.notes
dialog.addButton(R.string.equip, true) { _, _ ->
inventoryRepository.equip("equipped", it.key ?: "").subscribe({}, RxErrorHandler.handleEmptyError())
inventoryRepository.equip("equipped", it.key ?: "").subscribe({}, ExceptionHandler.rx())
}
dialog.addCloseButton()
dialog.enqueue()
}
}
.subscribe({ }, RxErrorHandler.handleEmptyError())
.subscribe({ }, ExceptionHandler.rx())
)
compositeSubscription.add(adapter.hatchPetEvents.subscribeWithErrorHandler { hatchPet(it.first, it.second) })
compositeSubscription.add(adapter.feedPetEvents.subscribeWithErrorHandler { feedPet(it) })
@ -285,7 +287,7 @@ class ItemDialogFragment : BaseDialogFragment<FragmentItemsDialogBinding>() {
else -> Egg::class.java
}
itemType?.let { type ->
lifecycleScope.launch {
lifecycleScope.launch(ExceptionHandler.coroutine()) {
inventoryRepository.getOwnedItems(type)
.onEach { items ->
val filteredItems = if (isFeeding) {
@ -296,7 +298,7 @@ class ItemDialogFragment : BaseDialogFragment<FragmentItemsDialogBinding>() {
adapter?.data = filteredItems
}
.map { items -> items.mapNotNull { it.key } }
.map { inventoryRepository.getItemsFlowable(itemClass, it.toTypedArray()).firstOrNull() }
.map { inventoryRepository.getItems(itemClass, it.toTypedArray()).firstOrNull() }
.collect {
val itemMap = mutableMapOf<String, Item>()
for (item in it ?: emptyList()) {
@ -306,10 +308,10 @@ class ItemDialogFragment : BaseDialogFragment<FragmentItemsDialogBinding>() {
}
}
lifecycleScope.launch {
lifecycleScope.launch(ExceptionHandler.coroutine()) {
inventoryRepository.getPets().collect { adapter?.setExistingPets(it) }
}
lifecycleScope.launch {
lifecycleScope.launch(ExceptionHandler.coroutine()) {
inventoryRepository.getOwnedPets().map { ownedPets ->
val petMap = mutableMapOf<String, OwnedPet>()
ownedPets.forEach { petMap[it.key ?: ""] = it }

View file

@ -20,7 +20,7 @@ import com.habitrpg.android.habitica.extensions.addCloseButton
import com.habitrpg.android.habitica.extensions.observeOnce
import com.habitrpg.android.habitica.extensions.subscribeWithErrorHandler
import com.habitrpg.android.habitica.helpers.MainNavigationController
import com.habitrpg.android.habitica.helpers.RxErrorHandler
import com.habitrpg.android.habitica.helpers.ExceptionHandler
import com.habitrpg.android.habitica.interactors.HatchPetUseCase
import com.habitrpg.android.habitica.models.inventory.Egg
import com.habitrpg.android.habitica.models.inventory.Food
@ -37,13 +37,13 @@ import com.habitrpg.android.habitica.ui.activities.MainActivity
import com.habitrpg.android.habitica.ui.activities.SkillMemberActivity
import com.habitrpg.android.habitica.ui.adapter.inventory.ItemRecyclerAdapter
import com.habitrpg.android.habitica.ui.fragments.BaseFragment
import com.habitrpg.common.habitica.helpers.EmptyItem
import com.habitrpg.android.habitica.ui.helpers.SafeDefaultItemAnimator
import com.habitrpg.common.habitica.extensions.loadImage
import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel
import com.habitrpg.android.habitica.ui.views.HabiticaSnackbar
import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog
import com.habitrpg.android.habitica.ui.views.dialogs.OpenedMysteryitemDialog
import com.habitrpg.common.habitica.extensions.loadImage
import com.habitrpg.common.habitica.helpers.EmptyItem
import io.reactivex.rxjava3.core.Flowable
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.flow.map
@ -147,18 +147,18 @@ class ItemRecyclerFragment : BaseFragment<FragmentItemsBinding>(), SwipeRefreshL
compositeSubscription.add(
adapter.getSellItemFlowable()
.flatMap { item -> inventoryRepository.sellItem(item) }
.subscribe({ }, RxErrorHandler.handleEmptyError())
.subscribe({ }, ExceptionHandler.rx())
)
compositeSubscription.add(
adapter.getQuestInvitationFlowable()
.flatMap { quest -> inventoryRepository.inviteToQuest(quest) }
.flatMap { socialRepository.retrieveGroup("party") }
//.flatMap { socialRepository.retrieveGroup("party") }
.subscribe(
{
MainNavigationController.navigate(R.id.partyFragment)
},
RxErrorHandler.handleEmptyError()
ExceptionHandler.rx()
)
)
compositeSubscription.add(
@ -174,13 +174,13 @@ class ItemRecyclerFragment : BaseFragment<FragmentItemsBinding>(), SwipeRefreshL
dialog.binding.titleView.text = it.text
dialog.binding.descriptionView.text = it.notes
dialog.addButton(R.string.equip, true) { _, _ ->
inventoryRepository.equip("equipped", it.key ?: "").subscribe({}, RxErrorHandler.handleEmptyError())
inventoryRepository.equip("equipped", it.key ?: "").subscribe({}, ExceptionHandler.rx())
}
dialog.addCloseButton()
dialog.enqueue()
}
}
.subscribe({ }, RxErrorHandler.handleEmptyError())
.subscribe({ }, ExceptionHandler.rx())
)
compositeSubscription.add(adapter.startHatchingEvents.subscribeWithErrorHandler { showHatchingDialog(it) })
compositeSubscription.add(adapter.hatchPetEvents.subscribeWithErrorHandler { hatchPet(it.first, it.second) })
@ -211,12 +211,10 @@ class ItemRecyclerFragment : BaseFragment<FragmentItemsBinding>(), SwipeRefreshL
override fun onRefresh() {
binding?.refreshLayout?.isRefreshing = true
compositeSubscription.add(
lifecycleScope.launch(ExceptionHandler.coroutine()) {
userRepository.retrieveUser(true, true)
.doOnTerminate {
binding?.refreshLayout?.isRefreshing = false
}.subscribe({ }, RxErrorHandler.handleEmptyError())
)
binding?.refreshLayout?.isRefreshing = false
}
}
private fun hatchPet(potion: HatchingPotion, egg: Egg) {
@ -237,34 +235,25 @@ class ItemRecyclerFragment : BaseFragment<FragmentItemsBinding>(), SwipeRefreshL
alert?.setTitle(R.string.quest_party_required_title)
alert?.setMessage(R.string.quest_party_required_description)
alert?.addButton(R.string.create_new_party, true, false) { _, _ ->
socialRepository.createGroup(
getString(R.string.usernames_party, user?.profile?.name),
"",
user?.id,
"party",
"",
false
)
.flatMap {
userRepository.retrieveUser(false, true)
.filter { it.hasParty }
.flatMap { socialRepository.retrieveGroup("party") }
.flatMap { group1 ->
socialRepository.retrieveGroupMembers(
group1.id,
true
)
}
}
.subscribe(
{
MainNavigationController.navigate(
R.id.partyFragment,
bundleOf(Pair("partyID", user?.party?.id))
)
},
RxErrorHandler.handleEmptyError()
lifecycleScope.launch(ExceptionHandler.coroutine()) {
val group = socialRepository.createGroup(
getString(R.string.usernames_party, user?.profile?.name),
"",
user?.id,
"party",
"",
false
)
val user = userRepository.retrieveUser(false, true)
if (user?.hasParty == true) {
val party = socialRepository.retrieveGroup("party")
socialRepository.retrieveGroupMembers(party?.id ?: "", true)
MainNavigationController.navigate(
R.id.partyFragment,
bundleOf(Pair("partyID", user.party?.id))
)
}
}
}
alert?.addButton(R.string.close, false) { _, _ ->
alert.dismiss()
@ -282,14 +271,14 @@ class ItemRecyclerFragment : BaseFragment<FragmentItemsBinding>(), SwipeRefreshL
else -> Egg::class.java
}
itemType?.let { type ->
lifecycleScope.launch {
lifecycleScope.launch(ExceptionHandler.coroutine()) {
inventoryRepository.getOwnedItems(type)
.onEach { items ->
adapter?.data = items
}
.map { items -> items.mapNotNull { it.key } }
.map {
inventoryRepository.getItemsFlowable(itemClass, it.toTypedArray()).firstOrNull()
inventoryRepository.getItems(itemClass, it.toTypedArray()).firstOrNull()
}
.collect {
val itemMap = mutableMapOf<String, Item>()
@ -299,10 +288,10 @@ class ItemRecyclerFragment : BaseFragment<FragmentItemsBinding>(), SwipeRefreshL
adapter?.items = itemMap
}
}
lifecycleScope.launch {
lifecycleScope.launch(ExceptionHandler.coroutine()) {
inventoryRepository.getPets().collect { adapter?.setExistingPets(it) }
}
lifecycleScope.launch {
lifecycleScope.launch(ExceptionHandler.coroutine()) {
inventoryRepository.getOwnedPets().map { ownedPets ->
val petMap = mutableMapOf<String, OwnedPet>()
ownedPets.forEach { petMap[it.key ?: ""] = it }
@ -340,7 +329,7 @@ class ItemRecyclerFragment : BaseFragment<FragmentItemsBinding>(), SwipeRefreshL
compositeSubscription.add(
observable.subscribe(
{ this.displaySpecialItemResult(specialItem) },
RxErrorHandler.handleEmptyError()
ExceptionHandler.rx()
)
)
}

View file

@ -12,9 +12,8 @@ import com.habitrpg.android.habitica.components.UserComponent
import com.habitrpg.android.habitica.data.InventoryRepository
import com.habitrpg.android.habitica.data.SocialRepository
import com.habitrpg.android.habitica.databinding.FragmentRefreshRecyclerviewBinding
import com.habitrpg.android.habitica.extensions.subscribeWithErrorHandler
import com.habitrpg.android.habitica.helpers.AppConfigManager
import com.habitrpg.android.habitica.helpers.RxErrorHandler
import com.habitrpg.android.habitica.helpers.ExceptionHandler
import com.habitrpg.android.habitica.models.shops.Shop
import com.habitrpg.android.habitica.models.shops.ShopCategory
import com.habitrpg.android.habitica.models.shops.ShopItem
@ -137,16 +136,11 @@ open class ShopFragment : BaseMainFragment<FragmentRefreshRecyclerviewBinding>()
updateCurrencyView(it)
}
lifecycleScope.launch {
lifecycleScope.launch(ExceptionHandler.coroutine()) {
socialRepository.getGroup(Group.TAVERN_ID)
.filter { it?.hasActiveQuest == true }
.filter { group ->
group?.quest?.rageStrikes?.any { it.key == shopIdentifier } ?: false
}
.filter { group ->
group?.quest?.rageStrikes?.filter { it.key == shopIdentifier }
?.get(0)?.wasHit == true
}
.filter { group -> group?.quest?.rageStrikes?.any { it.key == shopIdentifier } ?: false }
.filter { group -> group?.quest?.rageStrikes?.filter { it.key == shopIdentifier }?.get(0)?.wasHit == true }
.collect {
adapter?.shopSpriteSuffix = "_" + it?.quest?.key
}
@ -164,7 +158,8 @@ open class ShopFragment : BaseMainFragment<FragmentRefreshRecyclerviewBinding>()
val alert = HabiticaAlertDialog(context)
alert.setTitle(getString(R.string.class_confirmation_price, classIdentifier, 3))
alert.addButton(R.string.choose_class, true) { _, _ ->
userRepository.changeClass(classIdentifier).subscribeWithErrorHandler {
lifecycleScope.launch(ExceptionHandler.coroutine()) {
userRepository.changeClass(classIdentifier)
}
}
alert.addButton(R.string.dialog_go_back, false)
@ -224,7 +219,7 @@ open class ShopFragment : BaseMainFragment<FragmentRefreshRecyclerviewBinding>()
},
{
binding?.recyclerView?.state = RecyclerViewState.FAILED
RxErrorHandler.reportError(it)
ExceptionHandler.reportError(it)
},
{
binding?.refreshLayout?.isRefreshing = false
@ -234,12 +229,12 @@ open class ShopFragment : BaseMainFragment<FragmentRefreshRecyclerviewBinding>()
compositeSubscription.add(
this.inventoryRepository.getOwnedItems()
.subscribe({ adapter?.setOwnedItems(it) }, RxErrorHandler.handleEmptyError())
.subscribe({ adapter?.setOwnedItems(it) }, ExceptionHandler.rx())
)
compositeSubscription.add(
this.inventoryRepository.getInAppRewards()
.map { rewards -> rewards.map { it.key } }
.subscribe({ adapter?.setPinnedItemKeys(it) }, RxErrorHandler.handleEmptyError())
.subscribe({ adapter?.setPinnedItemKeys(it) }, ExceptionHandler.rx())
)
}
@ -289,7 +284,7 @@ open class ShopFragment : BaseMainFragment<FragmentRefreshRecyclerviewBinding>()
this.gearCategories = it.categories
adapter?.gearCategories = it.categories
},
RxErrorHandler.handleEmptyError()
ExceptionHandler.rx()
)
)
}

View file

@ -11,7 +11,7 @@ import com.habitrpg.android.habitica.components.UserComponent
import com.habitrpg.android.habitica.data.InventoryRepository
import com.habitrpg.android.habitica.databinding.FragmentRefreshRecyclerviewBinding
import com.habitrpg.android.habitica.extensions.getTranslatedType
import com.habitrpg.android.habitica.helpers.RxErrorHandler
import com.habitrpg.android.habitica.helpers.ExceptionHandler
import com.habitrpg.android.habitica.models.inventory.Mount
import com.habitrpg.android.habitica.models.inventory.StableSection
import com.habitrpg.android.habitica.models.user.OwnedMount
@ -102,7 +102,7 @@ class MountDetailRecyclerFragment :
{
adapter?.currentMount = it.currentMount
},
RxErrorHandler.handleEmptyError()
ExceptionHandler.rx()
)?.let { compositeSubscription.add(it) }
}
userViewModel.user.observe(viewLifecycleOwner) { adapter?.currentMount = it?.currentMount }
@ -136,7 +136,7 @@ class MountDetailRecyclerFragment :
private fun loadItems() {
if (animalType != null || animalGroup != null) {
lifecycleScope.launch {
lifecycleScope.launch(ExceptionHandler.coroutine()) {
val mounts = inventoryRepository.getMounts(animalType, animalGroup, animalColor).firstOrNull() ?: emptyList()
inventoryRepository.getOwnedMounts().map { ownedMounts ->
val mountMap = mutableMapOf<String, OwnedMount>()
@ -173,13 +173,9 @@ class MountDetailRecyclerFragment :
}
override fun onRefresh() {
compositeSubscription.add(
userRepository.retrieveUser(false, true).subscribe(
{
binding?.refreshLayout?.isRefreshing = false
},
RxErrorHandler.handleEmptyError()
)
)
lifecycleScope.launch(ExceptionHandler.coroutine()) {
userRepository.retrieveUser(false, true)
binding?.refreshLayout?.isRefreshing = false
}
}
}

View file

@ -12,7 +12,7 @@ import com.habitrpg.android.habitica.data.InventoryRepository
import com.habitrpg.android.habitica.databinding.FragmentRefreshRecyclerviewBinding
import com.habitrpg.android.habitica.extensions.getTranslatedType
import com.habitrpg.android.habitica.extensions.subscribeWithErrorHandler
import com.habitrpg.android.habitica.helpers.RxErrorHandler
import com.habitrpg.android.habitica.helpers.ExceptionHandler
import com.habitrpg.android.habitica.interactors.FeedPetUseCase
import com.habitrpg.android.habitica.models.inventory.Egg
import com.habitrpg.android.habitica.models.inventory.Food
@ -111,11 +111,11 @@ class PetDetailRecyclerFragment :
}
binding?.recyclerView?.layoutManager = layoutManager
adapter.animalIngredientsRetriever = { animal, callback ->
lifecycleScope.launch {
val egg = inventoryRepository.getItemsFlowable(Egg::class.java, arrayOf(animal.animal))
lifecycleScope.launch(ExceptionHandler.coroutine()) {
val egg = inventoryRepository.getItems(Egg::class.java, arrayOf(animal.animal))
.firstOrNull()?.firstOrNull() as? Egg
val potion =
inventoryRepository.getItemsFlowable(HatchingPotion::class.java, arrayOf(animal.color))
inventoryRepository.getItems(HatchingPotion::class.java, arrayOf(animal.color))
.firstOrNull()?.firstOrNull() as? HatchingPotion
callback(Pair(egg, potion))
}
@ -131,7 +131,7 @@ class PetDetailRecyclerFragment :
{
adapter.currentPet = it.currentPet
},
RxErrorHandler.handleEmptyError()
ExceptionHandler.rx()
)
)
userViewModel.user.observe(viewLifecycleOwner) { adapter.currentPet = it?.currentPet }
@ -140,7 +140,7 @@ class PetDetailRecyclerFragment :
it.first,
it.second
)
}, RxErrorHandler.handleEmptyError()))
}, ExceptionHandler.rx()))
view.post { setGridSpanCount(view.width) }
}
@ -171,7 +171,7 @@ class PetDetailRecyclerFragment :
private fun loadItems() {
if (animalType?.isNotEmpty() == true || animalGroup?.isNotEmpty() == true) {
lifecycleScope.launch {
lifecycleScope.launch(ExceptionHandler.coroutine()) {
inventoryRepository.getOwnedMounts()
.map { ownedMounts ->
val mountMap = mutableMapOf<String, OwnedMount>()
@ -181,9 +181,9 @@ class PetDetailRecyclerFragment :
}
compositeSubscription.add(
inventoryRepository.getOwnedItems(true)
.subscribe({ adapter.setOwnedItems(it) }, RxErrorHandler.handleEmptyError())
.subscribe({ adapter.setOwnedItems(it) }, ExceptionHandler.rx())
)
lifecycleScope.launch {
lifecycleScope.launch(ExceptionHandler.coroutine()) {
val mounts = inventoryRepository.getMounts(
animalType,
animalGroup,
@ -256,13 +256,9 @@ class PetDetailRecyclerFragment :
}
override fun onRefresh() {
compositeSubscription.add(
userRepository.retrieveUser(false, true).subscribe(
{
binding?.refreshLayout?.isRefreshing = false
},
RxErrorHandler.handleEmptyError()
)
)
lifecycleScope.launch(ExceptionHandler.coroutine()) {
userRepository.retrieveUser(false, true)
binding?.refreshLayout?.isRefreshing = false
}
}
}

View file

@ -14,7 +14,7 @@ import com.habitrpg.android.habitica.data.InventoryRepository
import com.habitrpg.android.habitica.data.UserRepository
import com.habitrpg.android.habitica.databinding.FragmentRefreshRecyclerviewBinding
import com.habitrpg.android.habitica.helpers.AppConfigManager
import com.habitrpg.android.habitica.helpers.RxErrorHandler
import com.habitrpg.android.habitica.helpers.ExceptionHandler
import com.habitrpg.android.habitica.models.inventory.Egg
import com.habitrpg.android.habitica.models.inventory.HatchingPotion
import com.habitrpg.android.habitica.ui.adapter.inventory.StableRecyclerAdapter
@ -106,9 +106,9 @@ class StableRecyclerFragment :
if (adapter == null) {
adapter = StableRecyclerAdapter()
adapter?.animalIngredientsRetriever = { animal, callback ->
lifecycleScope.launch {
val egg = inventoryRepository.getItemsFlowable(Egg::class.java, arrayOf(animal.animal)).firstOrNull()?.firstOrNull() as? Egg
val potion = inventoryRepository.getItemsFlowable(HatchingPotion::class.java, arrayOf(animal.color)).firstOrNull()?.firstOrNull() as? HatchingPotion
lifecycleScope.launch(ExceptionHandler.coroutine()) {
val egg = inventoryRepository.getItems(Egg::class.java, arrayOf(animal.animal)).firstOrNull()?.firstOrNull() as? Egg
val potion = inventoryRepository.getItems(HatchingPotion::class.java, arrayOf(animal.color)).firstOrNull()?.firstOrNull() as? HatchingPotion
callback(Pair(egg, potion))
}
}
@ -121,7 +121,7 @@ class StableRecyclerFragment :
compositeSubscription.add(
it.getEquipFlowable()
.flatMap { key -> inventoryRepository.equip(if (itemType == "pets") "pet" else "mount", key) }
.subscribe({ }, RxErrorHandler.handleEmptyError())
.subscribe({ }, ExceptionHandler.rx())
)
}
}
@ -179,13 +179,9 @@ class StableRecyclerFragment :
}
override fun onRefresh() {
compositeSubscription.add(
userRepository.retrieveUser(false, true).subscribe(
{
binding?.refreshLayout?.isRefreshing = false
},
RxErrorHandler.handleEmptyError()
)
)
lifecycleScope.launch(ExceptionHandler.coroutine()) {
userRepository.retrieveUser(true, true)
binding?.refreshLayout?.isRefreshing = false
}
}
}

View file

@ -14,6 +14,7 @@ import androidx.core.content.ContextCompat
import androidx.core.content.ContextCompat.getSystemService
import androidx.core.util.PatternsCompat
import androidx.core.view.isVisible
import androidx.lifecycle.lifecycleScope
import androidx.preference.EditTextPreference
import androidx.preference.Preference
import com.google.android.material.textfield.TextInputLayout
@ -22,8 +23,10 @@ import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.data.ApiClient
import com.habitrpg.android.habitica.extensions.addCancelButton
import com.habitrpg.android.habitica.extensions.addCloseButton
import com.habitrpg.common.habitica.extensions.dpToPx
import com.habitrpg.common.habitica.extensions.layoutInflater
import com.habitrpg.android.habitica.helpers.ExceptionHandler
import com.habitrpg.android.habitica.helpers.MainNavigationController
import com.habitrpg.android.habitica.helpers.RxErrorHandler
import com.habitrpg.android.habitica.models.user.User
import com.habitrpg.android.habitica.ui.activities.FixCharacterValuesActivity
import com.habitrpg.android.habitica.ui.fragments.preferences.HabiticaAccountDialog.AccountUpdateConfirmed
@ -36,8 +39,7 @@ import com.habitrpg.android.habitica.ui.views.ValidatingEditText
import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog
import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaProgressDialog
import com.habitrpg.common.habitica.api.HostConfig
import com.habitrpg.common.habitica.extensions.dpToPx
import com.habitrpg.common.habitica.extensions.layoutInflater
import kotlinx.coroutines.launch
import retrofit2.HttpException
import javax.inject.Inject
@ -203,8 +205,11 @@ class AccountPreferenceFragment :
dialog.setTitle(R.string.are_you_sure)
dialog.addButton(R.string.disconnect, true) { _, _ ->
apiClient.disconnectSocial(network)
.flatMap { userRepository.retrieveUser(true, true) }
.subscribe({ displayDisconnectSuccess(networkName) }, RxErrorHandler.handleEmptyError())
.subscribe({
lifecycleScope.launch(ExceptionHandler.coroutine()) {
userRepository.retrieveUser(true, true)
}
displayDisconnectSuccess(networkName) }, ExceptionHandler.rx())
}
dialog.addCancelButton()
dialog.show()
@ -252,7 +257,7 @@ class AccountPreferenceFragment :
showSingleEntryDialog(value, title) {
if (value != it) {
userRepository.updateUser(path, it ?: "")
.subscribe({}, RxErrorHandler.handleEmptyError())
.subscribe({}, ExceptionHandler.rx())
}
}
}
@ -278,9 +283,7 @@ class AccountPreferenceFragment :
userRepository.updatePassword(
oldPasswordEditText?.text ?: "",
passwordEditText.text ?: "",
passwordRepeatEditText.text ?: ""
)
.flatMap { userRepository.retrieveUser(true, true) }
passwordRepeatEditText.text ?: "")
.subscribe(
{
(activity as? SnackbarActivity)?.showSnackbar(
@ -288,7 +291,7 @@ class AccountPreferenceFragment :
displayType = HabiticaSnackbar.SnackbarDisplayType.SUCCESS
)
},
RxErrorHandler.handleEmptyError()
ExceptionHandler.rx()
)
dialog.dismiss()
}
@ -327,7 +330,6 @@ class AccountPreferenceFragment :
if ((showEmail && emailEditText?.isValid != true) || passwordEditText?.isValid != true || passwordRepeatEditText?.isValid != true) return@addButton
val email = if (showEmail) emailEditText?.text else user?.authentication?.findFirstSocialEmail()
apiClient.registerUser(user?.username ?: "", email ?: "", passwordEditText.text ?: "", passwordRepeatEditText?.text ?: "")
.flatMap { userRepository.retrieveUser(true, true) }
.subscribe(
{
(activity as? SnackbarActivity)?.showSnackbar(
@ -335,7 +337,7 @@ class AccountPreferenceFragment :
displayType = HabiticaSnackbar.SnackbarDisplayType.SUCCESS
)
},
RxErrorHandler.handleEmptyError()
ExceptionHandler.rx()
)
dialog.dismiss()
}
@ -366,12 +368,14 @@ class AccountPreferenceFragment :
emailEditText?.showErrorIfNecessary()
if (emailEditText?.isValid != true) return@addButton
userRepository.updateEmail(emailEditText.text.toString(), passwordEditText?.text.toString())
.flatMap { userRepository.retrieveUser(true, true) }
.subscribe(
{
lifecycleScope.launch(ExceptionHandler.coroutine()) {
userRepository.retrieveUser(true, true)
}
configurePreference(findPreference("email"), emailEditText.text.toString())
},
RxErrorHandler.handleEmptyError()
ExceptionHandler.rx()
)
dialog.dismiss()
}
@ -385,7 +389,7 @@ class AccountPreferenceFragment :
private fun showLoginNameDialog() {
showSingleEntryDialog(user?.username, getString(R.string.username)) {
userRepository.updateLoginName(it ?: "")
.subscribe({}, RxErrorHandler.handleEmptyError())
.subscribe({}, ExceptionHandler.rx())
}
}
@ -450,7 +454,7 @@ class AccountPreferenceFragment :
errorDialog?.addCloseButton()
errorDialog?.show()
}
RxErrorHandler.reportError(throwable)
ExceptionHandler.reportError(throwable)
}
)
}
@ -475,7 +479,7 @@ class AccountPreferenceFragment :
dialog.setMessage(R.string.confirm_username_description)
dialog.addButton(R.string.confirm, true) { _, _ ->
userRepository.updateLoginName(user?.authentication?.localAuthentication?.username ?: "")
.subscribe({ }, RxErrorHandler.handleEmptyError())
.subscribe({ }, ExceptionHandler.rx())
}
dialog.addCancelButton()
dialog.show()
@ -483,19 +487,10 @@ class AccountPreferenceFragment :
private fun resetAccount() {
val dialog = HabiticaProgressDialog.show(context, R.string.resetting_account)
compositeSubscription.add(
userRepository.resetAccount().subscribe({
dialog?.dismiss()
accountDialog.dismiss()
(activity as? SnackbarActivity)?.showSnackbar(
content = context?.getString(R.string.account_reset),
displayType = HabiticaSnackbar.SnackbarDisplayType.SUCCESS
)
}) { throwable ->
dialog?.dismiss()
RxErrorHandler.reportError(throwable)
}
)
lifecycleScope.launch(ExceptionHandler.coroutine()) {
userRepository.resetAccount()
dialog?.dismiss()
}
}
private fun copyValue(name: String, value: CharSequence?) {

View file

@ -4,7 +4,7 @@ import android.content.SharedPreferences
import android.os.Bundle
import androidx.preference.CheckBoxPreference
import com.habitrpg.android.habitica.HabiticaBaseApplication
import com.habitrpg.android.habitica.helpers.RxErrorHandler
import com.habitrpg.android.habitica.helpers.ExceptionHandler
import com.habitrpg.android.habitica.models.user.User
class EmailNotificationsPreferencesFragment : BasePreferencesFragment(), SharedPreferences.OnSharedPreferenceChangeListener {
@ -73,7 +73,7 @@ class EmailNotificationsPreferencesFragment : BasePreferencesFragment(), SharedP
else -> null
}
if (pathKey != null) {
compositeSubscription.add(userRepository.updateUser("preferences.emailNotifications.$pathKey", sharedPreferences.getBoolean(key, false)).subscribe({ }, RxErrorHandler.handleEmptyError()))
compositeSubscription.add(userRepository.updateUser("preferences.emailNotifications.$pathKey", sharedPreferences.getBoolean(key, false)).subscribe({ }, ExceptionHandler.rx()))
}
}
}

View file

@ -20,7 +20,7 @@ import com.habitrpg.android.habitica.data.ApiClient
import com.habitrpg.android.habitica.data.ContentRepository
import com.habitrpg.android.habitica.extensions.addCancelButton
import com.habitrpg.android.habitica.helpers.AppConfigManager
import com.habitrpg.android.habitica.helpers.RxErrorHandler
import com.habitrpg.android.habitica.helpers.ExceptionHandler
import com.habitrpg.android.habitica.helpers.SoundManager
import com.habitrpg.android.habitica.helpers.TaskAlarmManager
import com.habitrpg.android.habitica.helpers.notifications.PushNotificationManager
@ -69,7 +69,7 @@ class PreferencesFragment : BasePreferencesFragment(), SharedPreferences.OnShare
super.onViewCreated(view, savedInstanceState)
listView.itemAnimator = null
userRepository.retrieveTeamPlans().subscribe({}, RxErrorHandler.handleEmptyError())
userRepository.retrieveTeamPlans().subscribe({}, ExceptionHandler.rx())
}
override fun setupPreferences() {
@ -152,20 +152,24 @@ class PreferencesFragment : BasePreferencesFragment(), SharedPreferences.OnShare
(activity as? SnackbarActivity)?.showSnackbar(
content = context?.getString(R.string.reloading_content)
)
contentRepository.retrieveContent(true).subscribe(
{
(activity as? SnackbarActivity)?.showSnackbar(
content = context?.getString(R.string.reloaded_content),
displayType = HabiticaSnackbar.SnackbarDisplayType.SUCCESS
)
},
RxErrorHandler.handleEmptyError()
)
reloadContent(true)
}
}
return super.onPreferenceTreeClick(preference)
}
private fun reloadContent(withConfirmation: Boolean) {
lifecycleScope.launch(ExceptionHandler.coroutine()) {
contentRepository.retrieveContent(true)
if (withConfirmation) {
(activity as? SnackbarActivity)?.showSnackbar(
content = context?.getString(R.string.reloaded_content),
displayType = HabiticaSnackbar.SnackbarDisplayType.SUCCESS
)
}
}
}
private fun logout() {
context?.let { context ->
val dialog = HabiticaAlertDialog(context)
@ -180,7 +184,9 @@ class PreferencesFragment : BasePreferencesFragment(), SharedPreferences.OnShare
}
private val classSelectionResult = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
userRepository.retrieveUser(true, forced = true)
lifecycleScope.launch(ExceptionHandler.coroutine()) {
userRepository.retrieveUser(true, true)
}
}
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String?) {
@ -216,7 +222,7 @@ class PreferencesFragment : BasePreferencesFragment(), SharedPreferences.OnShare
"cds_time" -> {
val timeval = sharedPreferences.getString("cds_time", "0") ?: "0"
val hour = Integer.parseInt(timeval)
userRepository.changeCustomDayStart(hour).subscribe({ }, RxErrorHandler.handleEmptyError())
userRepository.changeCustomDayStart(hour).subscribe({ }, ExceptionHandler.rx())
val preference = findPreference<ListPreference>(key)
preference?.summary = preference?.entry
}
@ -234,8 +240,7 @@ class PreferencesFragment : BasePreferencesFragment(), SharedPreferences.OnShare
}
userRepository.updateLanguage(languageHelper.languageCode ?: "en")
.flatMap { contentRepository.retrieveContent(true) }
.subscribe({ }, RxErrorHandler.handleEmptyError())
.subscribe({ reloadContent(false) }, ExceptionHandler.rx())
val intent = Intent(activity, MainActivity::class.java)
this.startActivity(intent)
@ -246,7 +251,7 @@ class PreferencesFragment : BasePreferencesFragment(), SharedPreferences.OnShare
if (newAudioTheme != null) {
compositeSubscription.add(
userRepository.updateUser("preferences.sound", newAudioTheme)
.subscribe({ }, RxErrorHandler.handleEmptyError())
.subscribe({ }, ExceptionHandler.rx())
)
soundManager.soundTheme = newAudioTheme
soundManager.preloadAllFiles()
@ -261,7 +266,7 @@ class PreferencesFragment : BasePreferencesFragment(), SharedPreferences.OnShare
activity.reload()
}
"dailyDueDefaultView" -> userRepository.updateUser("preferences.dailyDueDefaultView", sharedPreferences.getBoolean(key, false))
.subscribe({ }, RxErrorHandler.handleEmptyError())
.subscribe({ }, ExceptionHandler.rx())
"server_url" -> {
apiClient.updateServerUrl(sharedPreferences.getString(key, ""))
findPreference<Preference>(key)?.summary = sharedPreferences.getString(key, "")
@ -279,7 +284,7 @@ class PreferencesFragment : BasePreferencesFragment(), SharedPreferences.OnShare
if (user?.inbox?.optOut != isDisabled) {
compositeSubscription.add(
userRepository.updateUser("inbox.optOut", isDisabled)
.subscribe({ }, RxErrorHandler.handleEmptyError())
.subscribe({ }, ExceptionHandler.rx())
)
}
}
@ -385,7 +390,7 @@ class PreferencesFragment : BasePreferencesFragment(), SharedPreferences.OnShare
} else if (newValue == false && currentIds.contains(team.id)) {
currentIds.remove(team.id)
}
userRepository.updateUser("preferences.tasks.mirrorGroupTasks", currentIds).subscribe({}, RxErrorHandler.handleEmptyError())
userRepository.updateUser("preferences.tasks.mirrorGroupTasks", currentIds).subscribe({}, ExceptionHandler.rx())
true
}
groupCategory?.addPreference(newPreference)

View file

@ -4,7 +4,7 @@ import android.content.SharedPreferences
import android.os.Bundle
import androidx.preference.CheckBoxPreference
import com.habitrpg.android.habitica.HabiticaBaseApplication
import com.habitrpg.android.habitica.helpers.RxErrorHandler
import com.habitrpg.android.habitica.helpers.ExceptionHandler
import com.habitrpg.android.habitica.models.user.User
class PushNotificationsPreferencesFragment : BasePreferencesFragment(), SharedPreferences.OnSharedPreferenceChangeListener {
@ -75,7 +75,7 @@ class PushNotificationsPreferencesFragment : BasePreferencesFragment(), SharedPr
else -> null
}
if (pathKey != null) {
compositeSubscription.add(userRepository.updateUser("preferences.pushNotifications.$pathKey", sharedPreferences.getBoolean(key, false)).subscribe({ }, RxErrorHandler.handleEmptyError()))
compositeSubscription.add(userRepository.updateUser("preferences.pushNotifications.$pathKey", sharedPreferences.getBoolean(key, false)).subscribe({ }, ExceptionHandler.rx()))
}
}
}

View file

@ -15,6 +15,7 @@ import com.habitrpg.android.habitica.databinding.FragmentGemPurchaseBinding
import com.habitrpg.android.habitica.extensions.addCancelButton
import com.habitrpg.android.habitica.helpers.AmplitudeManager
import com.habitrpg.android.habitica.helpers.AppConfigManager
import com.habitrpg.android.habitica.helpers.ExceptionHandler
import com.habitrpg.android.habitica.helpers.PurchaseHandler
import com.habitrpg.android.habitica.helpers.PurchaseTypes
import com.habitrpg.android.habitica.models.promotions.PromoType
@ -99,7 +100,7 @@ class GemsPurchaseFragment : BaseFragment<FragmentGemPurchaseBinding>() {
}
private fun loadInventory() {
CoroutineScope(Dispatchers.IO).launch {
CoroutineScope(Dispatchers.IO).launch(ExceptionHandler.coroutine()) {
val skus = purchaseHandler.getAllGemSKUs()
withContext(Dispatchers.Main) {
for (sku in skus) {

View file

@ -5,13 +5,15 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.lifecycle.lifecycleScope
import com.habitrpg.android.habitica.components.UserComponent
import com.habitrpg.android.habitica.data.SocialRepository
import com.habitrpg.android.habitica.data.UserRepository
import com.habitrpg.android.habitica.databinding.FragmentGiftGemBalanceBinding
import com.habitrpg.android.habitica.helpers.RxErrorHandler
import com.habitrpg.android.habitica.helpers.ExceptionHandler
import com.habitrpg.android.habitica.models.members.Member
import com.habitrpg.android.habitica.ui.fragments.BaseFragment
import kotlinx.coroutines.launch
import javax.inject.Inject
class GiftBalanceGemsFragment : BaseFragment<FragmentGiftGemBalanceBinding>() {
@ -64,14 +66,16 @@ class GiftBalanceGemsFragment : BaseFragment<FragmentGiftGemBalanceBinding>() {
giftedMember?.id?.let {
compositeSubscription.add(
socialRepository.transferGems(it, amount)
.flatMap { userRepository.retrieveUser(false, true) }
.doOnError {
isGifting = false
}
.subscribe(
{
activity?.finish()
}, RxErrorHandler.handleEmptyError()
lifecycleScope.launch(ExceptionHandler.coroutine()) {
userRepository.retrieveUser(false)
}
activity?.finish()
}, ExceptionHandler.rx()
)
)
}

View file

@ -9,16 +9,17 @@ import com.android.billingclient.api.SkuDetails
import com.habitrpg.android.habitica.components.UserComponent
import com.habitrpg.android.habitica.data.SocialRepository
import com.habitrpg.android.habitica.databinding.FragmentGiftGemPurchaseBinding
import com.habitrpg.android.habitica.helpers.ExceptionHandler
import com.habitrpg.android.habitica.helpers.PurchaseHandler
import com.habitrpg.android.habitica.helpers.PurchaseTypes
import com.habitrpg.android.habitica.models.members.Member
import com.habitrpg.android.habitica.ui.GemPurchaseOptionsView
import com.habitrpg.android.habitica.ui.fragments.BaseFragment
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import javax.inject.Inject
class GiftPurchaseGemsFragment : BaseFragment<FragmentGiftGemPurchaseBinding>() {
@ -54,7 +55,7 @@ class GiftPurchaseGemsFragment : BaseFragment<FragmentGiftGemPurchaseBinding>()
}
fun setupCheckout() {
CoroutineScope(Dispatchers.IO).launch {
CoroutineScope(Dispatchers.IO).launch(ExceptionHandler.coroutine()) {
val skus = purchaseHandler?.getAllGemSKUs()
withContext(Dispatchers.Main) {
for (sku in skus ?: emptyList()) {

View file

@ -8,6 +8,7 @@ import android.view.View
import android.view.ViewGroup
import android.widget.EditText
import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import com.android.billingclient.api.SkuDetails
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.components.UserComponent
@ -15,20 +16,20 @@ import com.habitrpg.android.habitica.data.InventoryRepository
import com.habitrpg.android.habitica.data.UserRepository
import com.habitrpg.android.habitica.databinding.FragmentSubscriptionBinding
import com.habitrpg.android.habitica.extensions.addCancelButton
import com.habitrpg.common.habitica.extensions.isUsingNightModeResources
import com.habitrpg.common.habitica.extensions.layoutInflater
import com.habitrpg.android.habitica.helpers.AmplitudeManager
import com.habitrpg.android.habitica.helpers.AppConfigManager
import com.habitrpg.android.habitica.helpers.PurchaseHandler
import com.habitrpg.android.habitica.helpers.PurchaseTypes
import com.habitrpg.android.habitica.helpers.RxErrorHandler
import com.habitrpg.android.habitica.helpers.ExceptionHandler
import com.habitrpg.android.habitica.models.user.User
import com.habitrpg.android.habitica.ui.activities.GiftSubscriptionActivity
import com.habitrpg.android.habitica.ui.fragments.BaseFragment
import com.habitrpg.android.habitica.ui.fragments.PromoInfoFragment
import com.habitrpg.common.habitica.extensions.loadImage
import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog
import com.habitrpg.android.habitica.ui.views.subscriptions.SubscriptionOptionView
import com.habitrpg.common.habitica.extensions.isUsingNightModeResources
import com.habitrpg.common.habitica.extensions.layoutInflater
import com.habitrpg.common.habitica.extensions.loadImage
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@ -93,7 +94,7 @@ class SubscriptionFragment : BaseFragment<FragmentSubscriptionBinding>() {
binding?.subBenefitsMysteryItemIcon?.loadImage("shop_set_mystery_${it.key?.split("_")?.last()}")
binding?.subBenefitsMysteryItemText?.text = context?.getString(R.string.subscribe_listitem3_description_new, it.text)
},
RxErrorHandler.handleEmptyError()
ExceptionHandler.rx()
)
)
@ -108,15 +109,11 @@ class SubscriptionFragment : BaseFragment<FragmentSubscriptionBinding>() {
}
private fun refresh() {
compositeSubscription.add(
userRepository.retrieveUser(withTasks = false, forced = true).subscribe(
{
this.setUser(it)
binding?.refreshLayout?.isRefreshing = false
},
RxErrorHandler.handleEmptyError()
)
)
lifecycleScope.launch(ExceptionHandler.coroutine()) {
val user = userRepository.retrieveUser(true)
user?.let { setUser(it) }
binding?.refreshLayout?.isRefreshing = false
}
}
override fun injectFragment(component: UserComponent) {
@ -124,7 +121,7 @@ class SubscriptionFragment : BaseFragment<FragmentSubscriptionBinding>() {
}
fun loadInventory() {
CoroutineScope(Dispatchers.IO).launch {
CoroutineScope(Dispatchers.IO).launch(ExceptionHandler.coroutine()) {
val subscriptions = purchaseHandler.getAllSubscriptionProducts()
skus = subscriptions
withContext(Dispatchers.Main) {
@ -227,17 +224,16 @@ class SubscriptionFragment : BaseFragment<FragmentSubscriptionBinding>() {
}
private fun checkIfNeedsCancellation() {
CoroutineScope(Dispatchers.IO).launch {
CoroutineScope(Dispatchers.IO).launch(ExceptionHandler.coroutine()) {
val newestSubscription = purchaseHandler.checkForSubscription()
if (user?.purchased?.plan?.paymentMethod == "Google" &&
user?.purchased?.plan?.isActive == true &&
user?.purchased?.plan?.dateTerminated == null &&
(newestSubscription?.isAutoRenewing != true)
) {
compositeSubscription.add(
lifecycleScope.launch(ExceptionHandler.coroutine()) {
purchaseHandler.cancelSubscription()
.subscribe({ }, RxErrorHandler.handleEmptyError())
)
}
}
}
}

View file

@ -8,16 +8,20 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.content.ContextCompat
import androidx.lifecycle.lifecycleScope
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.components.UserComponent
import com.habitrpg.android.habitica.data.UserRepository
import com.habitrpg.android.habitica.databinding.FragmentWelcomeBinding
import com.habitrpg.android.habitica.extensions.OnChangeTextWatcher
import com.habitrpg.android.habitica.extensions.subscribeWithErrorHandler
import com.habitrpg.android.habitica.helpers.ExceptionHandler
import com.habitrpg.android.habitica.ui.fragments.BaseFragment
import com.habitrpg.android.habitica.ui.views.HabiticaIconsHelper
import io.reactivex.rxjava3.core.BackpressureStrategy
import io.reactivex.rxjava3.subjects.PublishSubject
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.launch
import java.util.concurrent.TimeUnit
import javax.inject.Inject
@ -100,14 +104,13 @@ class WelcomeFragment : BaseFragment<FragmentWelcomeBinding>() {
}
)
compositeSubscription.add(
userRepository.getUserFlowable().firstElement().subscribe {
binding?.displayNameEditText?.setText(it.profile?.name)
displayNameVerificationEvents.onNext(it.profile?.name ?: "")
binding?.usernameEditText?.setText(it.username)
usernameVerificationEvents.onNext(it.username ?: "")
}
)
lifecycleScope.launch(ExceptionHandler.coroutine()) {
val user = userRepository.getUser().firstOrNull()
binding?.displayNameEditText?.setText(user?.profile?.name)
displayNameVerificationEvents.onNext(user?.profile?.name ?: "")
binding?.usernameEditText?.setText(user?.authentication?.localAuthentication?.username)
usernameVerificationEvents.onNext(user?.username ?: "")
}
}
override fun injectFragment(component: UserComponent) {

View file

@ -9,7 +9,7 @@ import androidx.recyclerview.widget.LinearLayoutManager
import com.habitrpg.android.habitica.components.UserComponent
import com.habitrpg.android.habitica.data.TaskRepository
import com.habitrpg.android.habitica.databinding.FragmentRecyclerviewBinding
import com.habitrpg.android.habitica.helpers.RxErrorHandler
import com.habitrpg.android.habitica.helpers.ExceptionHandler
import com.habitrpg.android.habitica.models.tasks.Task
import com.habitrpg.android.habitica.ui.adapter.SkillTasksRecyclerViewAdapter
import com.habitrpg.android.habitica.ui.fragments.BaseFragment
@ -59,7 +59,7 @@ class SkillTasksRecyclerViewFragment : BaseFragment<FragmentRecyclerviewBinding>
{
taskSelectionEvents.onNext(it)
},
RxErrorHandler.handleEmptyError()
ExceptionHandler.rx()
)
)
binding?.recyclerView?.adapter = adapter

View file

@ -14,7 +14,7 @@ import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.components.UserComponent
import com.habitrpg.android.habitica.databinding.FragmentSkillsBinding
import com.habitrpg.android.habitica.extensions.subscribeWithErrorHandler
import com.habitrpg.android.habitica.helpers.RxErrorHandler
import com.habitrpg.android.habitica.helpers.ExceptionHandler
import com.habitrpg.android.habitica.models.Skill
import com.habitrpg.android.habitica.models.responses.SkillResponse
import com.habitrpg.android.habitica.models.user.User
@ -91,7 +91,7 @@ class SkillsFragment : BaseMainFragment<FragmentSkillsBinding>() {
allEntries.add(item)
}
return@combineLatest allEntries
}.subscribe({ skills -> adapter?.setSkillList(skills) }, RxErrorHandler.handleEmptyError())
}.subscribe({ skills -> adapter?.setSkillList(skills) }, ExceptionHandler.rx())
}
private fun onSkillSelected(skill: Skill) {
@ -128,7 +128,7 @@ class SkillsFragment : BaseMainFragment<FragmentSkillsBinding>() {
}
}
if (response.damage > 0) {
lifecycleScope.launch {
lifecycleScope.launch(ExceptionHandler.coroutine()) {
delay(2000L)
if (!isAdded) return@launch
showSnackbar(
@ -140,7 +140,9 @@ class SkillsFragment : BaseMainFragment<FragmentSkillsBinding>() {
)
}
}
compositeSubscription.add(userRepository.retrieveUser(false).subscribe({ }, RxErrorHandler.handleEmptyError()))
lifecycleScope.launch(ExceptionHandler.coroutine()) {
userRepository.retrieveUser(true)
}
}
private val taskSelectionResult = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
@ -167,7 +169,7 @@ class SkillsFragment : BaseMainFragment<FragmentSkillsBinding>() {
compositeSubscription.add(
observable.subscribe(
{ skillResponse -> this.displaySkillResult(skill, skillResponse) },
RxErrorHandler.handleEmptyError()
ExceptionHandler.rx()
)
)
}

View file

@ -13,8 +13,8 @@ import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.components.UserComponent
import com.habitrpg.android.habitica.databinding.FragmentChatBinding
import com.habitrpg.android.habitica.helpers.AppConfigManager
import com.habitrpg.android.habitica.helpers.ExceptionHandler
import com.habitrpg.android.habitica.helpers.MainNavigationController
import com.habitrpg.android.habitica.helpers.RxErrorHandler
import com.habitrpg.android.habitica.models.social.ChatMessage
import com.habitrpg.android.habitica.ui.activities.FullProfileActivity
import com.habitrpg.android.habitica.ui.activities.MainActivity
@ -72,13 +72,13 @@ class ChatFragment() : BaseFragment<FragmentChatBinding>() {
compositeSubscription.add(
adapter.getUserLabelClickFlowable().subscribe(
{ userId -> FullProfileActivity.open(userId) },
RxErrorHandler.handleEmptyError()
ExceptionHandler.rx()
)
)
compositeSubscription.add(adapter.getDeleteMessageFlowable().subscribe({ this.showDeleteConfirmationDialog(it) }, RxErrorHandler.handleEmptyError()))
compositeSubscription.add(adapter.getFlagMessageClickFlowable().subscribe({ this.showFlagConfirmationDialog(it) }, RxErrorHandler.handleEmptyError()))
compositeSubscription.add(adapter.getReplyMessageEvents().subscribe({ setReplyTo(it) }, RxErrorHandler.handleEmptyError()))
compositeSubscription.add(adapter.getCopyMessageFlowable().subscribe({ this.copyMessageToClipboard(it) }, RxErrorHandler.handleEmptyError()))
compositeSubscription.add(adapter.getDeleteMessageFlowable().subscribe({ this.showDeleteConfirmationDialog(it) }, ExceptionHandler.rx()))
compositeSubscription.add(adapter.getFlagMessageClickFlowable().subscribe({ this.showFlagConfirmationDialog(it) }, ExceptionHandler.rx()))
compositeSubscription.add(adapter.getReplyMessageEvents().subscribe({ setReplyTo(it) }, ExceptionHandler.rx()))
compositeSubscription.add(adapter.getCopyMessageFlowable().subscribe({ this.copyMessageToClipboard(it) }, ExceptionHandler.rx()))
adapter.onMessageLike = { viewModel?.likeMessage(it) }
}
@ -141,7 +141,7 @@ class ChatFragment() : BaseFragment<FragmentChatBinding>() {
{
refresh()
},
RxErrorHandler.handleEmptyError()
ExceptionHandler.rx()
)
refresh()
}

View file

@ -12,6 +12,7 @@ import android.view.View
import android.view.ViewGroup
import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.viewModels
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.RecyclerView
import com.habitrpg.android.habitica.MainNavDirections
import com.habitrpg.android.habitica.R
@ -20,14 +21,13 @@ import com.habitrpg.android.habitica.data.SocialRepository
import com.habitrpg.android.habitica.databinding.FragmentInboxMessageListBinding
import com.habitrpg.android.habitica.extensions.addOkButton
import com.habitrpg.android.habitica.helpers.AppConfigManager
import com.habitrpg.android.habitica.helpers.ExceptionHandler
import com.habitrpg.android.habitica.helpers.MainNavigationController
import com.habitrpg.android.habitica.helpers.RxErrorHandler
import com.habitrpg.android.habitica.models.social.ChatMessage
import com.habitrpg.android.habitica.ui.activities.FullProfileActivity
import com.habitrpg.android.habitica.ui.activities.MainActivity
import com.habitrpg.android.habitica.ui.adapter.social.InboxAdapter
import com.habitrpg.android.habitica.ui.fragments.BaseMainFragment
import com.habitrpg.android.habitica.ui.helpers.KeyboardUtil
import com.habitrpg.android.habitica.ui.helpers.SafeDefaultItemAnimator
import com.habitrpg.android.habitica.ui.viewmodels.InboxViewModel
import com.habitrpg.android.habitica.ui.viewmodels.InboxViewModelFactory
@ -37,8 +37,12 @@ import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.core.Observable
import io.reactivex.rxjava3.disposables.Disposable
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import java.util.concurrent.TimeUnit
import javax.inject.Inject
import kotlin.time.DurationUnit
import kotlin.time.toDuration
class InboxMessageListFragment : BaseMainFragment<FragmentInboxMessageListBinding>() {
@ -84,48 +88,44 @@ class InboxMessageListFragment : BaseMainFragment<FragmentInboxMessageListBindin
layoutManager.reverseLayout = true
layoutManager.stackFromEnd = false
binding?.recyclerView?.layoutManager = layoutManager
val observable = if (replyToUserUUID?.isNotBlank() == true) {
apiClient.getMember(replyToUserUUID!!)
} else {
apiClient.getMemberWithUsername(chatRoomUser ?: "")
lifecycleScope.launch(ExceptionHandler.coroutine()) {
val member = if (replyToUserUUID?.isNotBlank() == true) {
apiClient.getMember(replyToUserUUID!!)
} else {
apiClient.getMemberWithUsername(chatRoomUser ?: "")
}
setReceivingUser(member?.username, member?.id)
activity?.title = member?.displayName
chatAdapter = InboxAdapter(viewModel.user.value, member)
chatAdapter?.registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() {
override fun onItemRangeInserted(positionStart: Int, itemCount: Int) {
if (positionStart == 0) {
binding?.recyclerView?.scrollToPosition(0)
}
}
})
binding?.recyclerView?.adapter = chatAdapter
binding?.recyclerView?.itemAnimator = SafeDefaultItemAnimator()
chatAdapter?.let { adapter ->
compositeSubscription.add(
adapter.getUserLabelClickFlowable().subscribe(
{
FullProfileActivity.open(it)
},
ExceptionHandler.rx()
)
)
compositeSubscription.add(adapter.getDeleteMessageFlowable().subscribe({ showDeleteConfirmationDialog(it) }, ExceptionHandler.rx()))
compositeSubscription.add(adapter.getFlagMessageClickFlowable().subscribe({ showFlagConfirmationDialog(it) }, ExceptionHandler.rx()))
compositeSubscription.add(adapter.getCopyMessageFlowable().subscribe({ copyMessageToClipboard(it) }, ExceptionHandler.rx()))
}
}
viewModel.messages.observe(viewLifecycleOwner) {
markMessagesAsRead(it)
chatAdapter?.submitList(it)
}
compositeSubscription.add(
observable.subscribe(
{ member ->
setReceivingUser(member.username, member.id)
activity?.title = member.displayName
chatAdapter = InboxAdapter(viewModel.user.value, member)
chatAdapter?.registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() {
override fun onItemRangeInserted(positionStart: Int, itemCount: Int) {
if (positionStart == 0) {
binding?.recyclerView?.scrollToPosition(0)
}
}
})
viewModel.messages.observe(this.viewLifecycleOwner) {
markMessagesAsRead(it)
chatAdapter?.submitList(it)
}
binding?.recyclerView?.adapter = chatAdapter
binding?.recyclerView?.itemAnimator = SafeDefaultItemAnimator()
chatAdapter?.let { adapter ->
compositeSubscription.add(
adapter.getUserLabelClickFlowable().subscribe(
{
FullProfileActivity.open(it)
},
RxErrorHandler.handleEmptyError()
)
)
compositeSubscription.add(adapter.getDeleteMessageFlowable().subscribe({ this.showDeleteConfirmationDialog(it) }, RxErrorHandler.handleEmptyError()))
compositeSubscription.add(adapter.getFlagMessageClickFlowable().subscribe({ this.showFlagConfirmationDialog(it) }, RxErrorHandler.handleEmptyError()))
compositeSubscription.add(adapter.getCopyMessageFlowable().subscribe({ this.copyMessageToClipboard(it) }, RxErrorHandler.handleEmptyError()))
}
},
RxErrorHandler.handleEmptyError()
)
)
binding?.chatBarView?.sendAction = { sendMessage(it) }
binding?.chatBarView?.maxChatLength = configManager.maxChatLength()
@ -196,7 +196,7 @@ class InboxMessageListFragment : BaseMainFragment<FragmentInboxMessageListBindin
{
refreshConversation()
},
RxErrorHandler.handleEmptyError()
ExceptionHandler.rx()
)
refreshConversation()
}
@ -210,39 +210,29 @@ class InboxMessageListFragment : BaseMainFragment<FragmentInboxMessageListBindin
private fun refreshConversation() {
if (viewModel.memberID?.isNotBlank() != true) { return }
compositeSubscription.add(
this.socialRepository.retrieveInboxMessages(replyToUserUUID ?: "", 0)
.subscribe(
{}, RxErrorHandler.handleEmptyError(),
{
viewModel.invalidateDataSource()
}
)
)
lifecycleScope.launch(ExceptionHandler.coroutine()) {
socialRepository.retrieveInboxMessages(replyToUserUUID ?: "", 0)
viewModel.invalidateDataSource()
}
}
private fun sendMessage(chatText: String) {
viewModel.memberID?.let { userID ->
socialRepository.postPrivateMessage(userID, chatText)
.delay(200, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{
viewModel.invalidateDataSource()
},
{ error ->
RxErrorHandler.reportError(error)
binding?.let {
val alert = HabiticaAlertDialog(it.chatBarView.context)
alert.setTitle("You cannot reply to this conversation")
alert.setMessage("This user is unable to receive your private message")
alert.addOkButton()
alert.show()
}
binding?.chatBarView?.message = chatText
}
)
KeyboardUtil.dismissKeyboard(getActivity())
lifecycleScope.launch(ExceptionHandler.coroutine { error ->
ExceptionHandler.reportError(error)
binding?.let {
val alert = HabiticaAlertDialog(it.chatBarView.context)
alert.setTitle("You cannot reply to this conversation")
alert.setMessage("This user is unable to receive your private message")
alert.addOkButton()
alert.show()
}
binding?.chatBarView?.message = chatText
}) {
socialRepository.postPrivateMessage(userID, chatText)
delay(200.toDuration(DurationUnit.MILLISECONDS))
viewModel.invalidateDataSource()
}
}
}
@ -274,7 +264,7 @@ class InboxMessageListFragment : BaseMainFragment<FragmentInboxMessageListBindin
.setTitle(R.string.confirm_delete_tag_title)
.setMessage(R.string.confirm_delete_tag_message)
.setIcon(android.R.drawable.ic_dialog_alert)
.setPositiveButton(R.string.yes) { _, _ -> socialRepository.deleteMessage(chatMessage).subscribe({ }, RxErrorHandler.handleEmptyError()) }
.setPositiveButton(R.string.yes) { _, _ -> socialRepository.deleteMessage(chatMessage).subscribe({ }, ExceptionHandler.rx()) }
.setNegativeButton(R.string.no, null).show()
}
}

View file

@ -9,6 +9,7 @@ import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.lifecycle.lifecycleScope
import com.habitrpg.android.habitica.BuildConfig
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.components.UserComponent
@ -17,15 +18,16 @@ import com.habitrpg.android.habitica.databinding.DialogChooseMessageRecipientBin
import com.habitrpg.android.habitica.databinding.FragmentInboxBinding
import com.habitrpg.android.habitica.extensions.getAgoString
import com.habitrpg.android.habitica.helpers.AppConfigManager
import com.habitrpg.android.habitica.helpers.ExceptionHandler
import com.habitrpg.android.habitica.helpers.MainNavigationController
import com.habitrpg.android.habitica.helpers.RxErrorHandler
import com.habitrpg.android.habitica.models.social.InboxConversation
import com.habitrpg.common.habitica.views.AvatarView
import com.habitrpg.android.habitica.ui.fragments.BaseMainFragment
import com.habitrpg.android.habitica.ui.helpers.dismissKeyboard
import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel
import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog
import com.habitrpg.android.habitica.ui.views.UsernameLabel
import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog
import com.habitrpg.common.habitica.views.AvatarView
import kotlinx.coroutines.launch
import javax.inject.Inject
class InboxOverviewFragment : BaseMainFragment<FragmentInboxBinding>(), androidx.swiperefreshlayout.widget.SwipeRefreshLayout.OnRefreshListener, View.OnClickListener {
@ -49,7 +51,7 @@ class InboxOverviewFragment : BaseMainFragment<FragmentInboxBinding>(), androidx
savedInstanceState: Bundle?
): View? {
this.hidesToolbar = true
compositeSubscription.add(this.socialRepository.markPrivateMessagesRead(null).subscribe({ }, RxErrorHandler.handleEmptyError()))
compositeSubscription.add(this.socialRepository.markPrivateMessagesRead(null).subscribe({ }, ExceptionHandler.rx()))
return super.onCreateView(inflater, container, savedInstanceState)
}
@ -67,14 +69,11 @@ class InboxOverviewFragment : BaseMainFragment<FragmentInboxBinding>(), androidx
}
private fun loadMessages() {
compositeSubscription.add(
socialRepository.getInboxConversations().subscribe(
{
lifecycleScope.launch(ExceptionHandler.coroutine()) {
socialRepository.getInboxConversations().collect {
setInboxMessages(it)
},
RxErrorHandler.handleEmptyError()
)
)
}
}
}
override fun onDestroy() {
@ -116,18 +115,17 @@ class InboxOverviewFragment : BaseMainFragment<FragmentInboxBinding>(), androidx
binding.errorTextView.visibility = View.GONE
binding.progressCircular.visibility = View.VISIBLE
val username = binding.uuidEditText.text?.toString() ?: ""
socialRepository.getMemberWithUsername(username)
.subscribe(
{
alert.dismiss()
openInboxMessages("", username)
binding.progressCircular.visibility = View.GONE
},
{
binding.errorTextView.visibility = View.VISIBLE
binding.progressCircular.visibility = View.GONE
}
)
lifecycleScope.launch(ExceptionHandler.coroutine()) {
val member = socialRepository.retrieveMemberWithUsername(username)
if (member != null) {
alert.dismiss()
openInboxMessages("", username)
binding.progressCircular.visibility = View.GONE
} else {
binding.errorTextView.visibility = View.VISIBLE
binding.progressCircular.visibility = View.GONE
}
}
}
alert.addButton(getString(R.string.action_cancel), false) { _, _ ->
thisActivity.dismissKeyboard()
@ -149,7 +147,7 @@ class InboxOverviewFragment : BaseMainFragment<FragmentInboxBinding>(), androidx
{
binding?.inboxRefreshLayout?.isRefreshing = false
},
RxErrorHandler.handleEmptyError()
ExceptionHandler.rx()
)
)
}

View file

@ -10,14 +10,15 @@ import android.view.ViewGroup
import android.widget.TextView
import androidx.core.content.ContextCompat
import androidx.core.text.toHtml
import androidx.lifecycle.lifecycleScope
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.components.UserComponent
import com.habitrpg.android.habitica.data.InventoryRepository
import com.habitrpg.android.habitica.data.SocialRepository
import com.habitrpg.android.habitica.databinding.FragmentQuestDetailBinding
import com.habitrpg.android.habitica.extensions.fromHtml
import com.habitrpg.android.habitica.helpers.ExceptionHandler
import com.habitrpg.android.habitica.helpers.HapticFeedbackManager
import com.habitrpg.android.habitica.helpers.RxErrorHandler
import com.habitrpg.android.habitica.models.inventory.Quest
import com.habitrpg.android.habitica.models.inventory.QuestContent
import com.habitrpg.android.habitica.models.members.Member
@ -28,6 +29,12 @@ import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel
import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog
import com.habitrpg.common.habitica.extensions.loadImage
import com.habitrpg.common.habitica.helpers.MarkdownParser
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import javax.inject.Inject
import javax.inject.Named
@ -72,30 +79,22 @@ class QuestDetailFragment : BaseMainFragment<FragmentQuestDetailBinding>() {
binding?.questCancelButton?.setOnClickListener { onQuestCancel() }
binding?.questLeaveButton?.setOnClickListener { onQuestLeave() }
compositeSubscription.add(
userRepository.getUserFlowable()
.map {
it.party?.id ?: ""
}
.skipWhile { it.isBlank() }
lifecycleScope.launch(ExceptionHandler.coroutine()) {
userRepository.getUser()
.map { it?.party?.id }
.filterNotNull()
.distinctUntilChanged()
.flatMap { socialRepository.getGroupFlowable(it) }
.doOnNext { updateParty(it) }
.map {
it.quest?.key ?: ""
}
.skipWhile {
it.isBlank()
}
.flatMapLatest { socialRepository.getGroup(it) }
.onEach { updateParty(it) }
.map { it?.quest?.key }
.filterNotNull()
.distinctUntilChanged()
.flatMap { inventoryRepository.getQuestContent(it) }
.subscribe(
{
updateQuestContent(it)
},
RxErrorHandler.handleEmptyError()
)
)
.flatMapLatest { inventoryRepository.getQuestContent(it) }
.filterNotNull()
.collect {
updateQuestContent(it)
}
}
}
private fun updateParty(group: Group?) {
@ -105,16 +104,12 @@ class QuestDetailFragment : BaseMainFragment<FragmentQuestDetailBinding>() {
party = group
quest = group.quest
setQuestParticipants(group.quest?.participants)
compositeSubscription.add(
socialRepository.getMember(quest?.leader).subscribe(
{ member ->
if (context != null && binding?.questLeaderView != null) {
binding?.questLeaderView?.text = context?.getString(R.string.quest_leader_header, member.displayName)
}
},
RxErrorHandler.handleEmptyError()
)
)
lifecycleScope.launch(ExceptionHandler.coroutine()) {
val member = socialRepository.retrieveMember(quest?.leader)
if (context != null && binding?.questLeaderView != null) {
binding?.questLeaderView?.text = context?.getString(R.string.quest_leader_header, member?.displayName)
}
}
val user = userViewModel.user.value
if (binding?.questResponseWrapper != null) {
@ -223,7 +218,7 @@ class QuestDetailFragment : BaseMainFragment<FragmentQuestDetailBinding>() {
val party = party
if (party != null) {
socialRepository.forceStartQuest(party)
.subscribe({ }, RxErrorHandler.handleEmptyError())
.subscribe({ }, ExceptionHandler.rx())
}
}
alert.addButton(R.string.no, false)
@ -239,10 +234,12 @@ class QuestDetailFragment : BaseMainFragment<FragmentQuestDetailBinding>() {
.setMessage(R.string.quest_abort_message)
.setPositiveButton(R.string.yes) { _, _ ->
party?.id?.let { partyID ->
@Suppress("DEPRECATION")
socialRepository.abortQuest(partyID)
.flatMap { userRepository.retrieveUser() }
.subscribe({ getActivity()?.supportFragmentManager?.popBackStack() }, RxErrorHandler.handleEmptyError())
.subscribe({
lifecycleScope.launch(ExceptionHandler.coroutine()) {
userRepository.retrieveUser(true)
}
getActivity()?.supportFragmentManager?.popBackStack() }, ExceptionHandler.rx())
}
}.setNegativeButton(R.string.no) { _, _ -> }
builder.show()
@ -251,10 +248,13 @@ class QuestDetailFragment : BaseMainFragment<FragmentQuestDetailBinding>() {
alert.setMessage(R.string.quest_cancel_message)
alert.addButton(R.string.yes, true) { _, _ ->
party?.id?.let { partyID ->
@Suppress("DEPRECATION")
socialRepository.cancelQuest(partyID)
.flatMap { userRepository.retrieveUser() }
.subscribe({ getActivity()?.supportFragmentManager?.popBackStack() }, RxErrorHandler.handleEmptyError())
.subscribe({
lifecycleScope.launch(ExceptionHandler.coroutine()) {
userRepository.retrieveUser(true)
}
getActivity()?.supportFragmentManager?.popBackStack()
}, ExceptionHandler.rx())
}
}
alert.addButton(R.string.no, false)
@ -269,11 +269,13 @@ class QuestDetailFragment : BaseMainFragment<FragmentQuestDetailBinding>() {
.setMessage(if (quest?.active == true) R.string.quest_leave_message else R.string.quest_leave_message_nostart)
.setPositiveButton(R.string.yes) { _, _ ->
party?.id?.let { partyID ->
@Suppress("DEPRECATION")
socialRepository.leaveQuest(partyID)
.flatMap { userRepository.retrieveUser() }
.flatMap { socialRepository.retrieveGroup(partyID) }
.subscribe({ getActivity()?.supportFragmentManager?.popBackStack() }, RxErrorHandler.handleEmptyError())
.subscribe({
lifecycleScope.launch(ExceptionHandler.coroutine()) {
socialRepository.retrieveGroup(partyID)
userRepository.retrieveUser(true)
}
getActivity()?.supportFragmentManager?.popBackStack() }, ExceptionHandler.rx())
}
}.setNegativeButton(R.string.no) { _, _ -> }
builder.show()

View file

@ -11,6 +11,7 @@ import android.widget.FrameLayout
import android.widget.TextView
import androidx.appcompat.app.AlertDialog
import androidx.core.content.ContextCompat
import androidx.lifecycle.lifecycleScope
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.components.UserComponent
import com.habitrpg.android.habitica.data.InventoryRepository
@ -19,8 +20,8 @@ import com.habitrpg.android.habitica.data.UserRepository
import com.habitrpg.android.habitica.databinding.FragmentTavernDetailBinding
import com.habitrpg.android.habitica.extensions.setTintWith
import com.habitrpg.android.habitica.helpers.AppConfigManager
import com.habitrpg.android.habitica.helpers.ExceptionHandler
import com.habitrpg.android.habitica.helpers.MainNavigationController
import com.habitrpg.android.habitica.helpers.RxErrorHandler
import com.habitrpg.android.habitica.models.inventory.QuestContent
import com.habitrpg.android.habitica.models.social.Group
import com.habitrpg.android.habitica.models.user.User
@ -29,6 +30,11 @@ import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel
import com.habitrpg.android.habitica.ui.views.UsernameLabel
import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog
import com.habitrpg.common.habitica.models.PlayerTier
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import javax.inject.Inject
class TavernDetailFragment : BaseFragment<FragmentTavernDetailBinding>() {
@ -73,33 +79,30 @@ class TavernDetailFragment : BaseFragment<FragmentTavernDetailBinding>() {
addPlayerTiers()
bindButtons()
compositeSubscription.add(
socialRepository.getGroupFlowable(Group.TAVERN_ID)
.doOnNext { if (!it.hasActiveQuest) binding?.worldBossSection?.visibility = View.GONE }
.filter { it.hasActiveQuest }
.doOnNext {
binding?.questProgressView?.progress = it.quest
lifecycleScope.launch(ExceptionHandler.coroutine()) {
socialRepository.getGroup(Group.TAVERN_ID)
.onEach { if (it?.hasActiveQuest == false) binding?.worldBossSection?.visibility = View.GONE }
.filter { it != null && it.hasActiveQuest }
.onEach {
binding?.questProgressView?.progress = it?.quest
binding?.shopHeader?.descriptionView?.setText(R.string.tavern_description_world_boss)
val filtered = it.quest?.rageStrikes?.filter { strike -> strike.key == "tavern" }
if (filtered?.size ?: 0 > 0 && filtered?.get(0)?.wasHit == true) {
val filtered = it?.quest?.rageStrikes?.filter { strike -> strike.key == "tavern" }
if ((filtered?.size ?: 0) > 0 && filtered?.get(0)?.wasHit == true) {
val key = it.quest?.key
if (key != null) {
shopSpriteSuffix = key
}
}
}
.flatMapMaybe { inventoryRepository.getQuestContent(it.quest?.key ?: "").firstElement() }
.subscribe(
{
binding?.questProgressView?.quest = it
binding?.worldBossSection?.visibility = View.VISIBLE
},
RxErrorHandler.handleEmptyError()
)
)
compositeSubscription.add(socialRepository.retrieveGroup(Group.TAVERN_ID).subscribe({ }, RxErrorHandler.handleEmptyError()))
.map { inventoryRepository.getQuestContent(it?.quest?.key ?: "").firstOrNull() }
.collect {
binding?.questProgressView?.quest = it
binding?.worldBossSection?.visibility = View.VISIBLE
}
}
lifecycleScope.launch(ExceptionHandler.coroutine()) { socialRepository.retrieveGroup(Group.TAVERN_ID) }
user?.let { binding?.questProgressView?.configure(it) }
}
@ -112,7 +115,9 @@ class TavernDetailFragment : BaseFragment<FragmentTavernDetailBinding>() {
private fun bindButtons() {
binding?.innButton?.setOnClickListener {
user?.let { user -> userRepository.sleep(user).subscribe({ }, RxErrorHandler.handleEmptyError()) }
lifecycleScope.launch(ExceptionHandler.coroutine()) {
user?.let { user -> userRepository.sleep(user) }
}
}
binding?.guidelinesButton?.setOnClickListener {
MainNavigationController.navigate(R.id.guidelinesActivity)

View file

@ -11,6 +11,7 @@ import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import androidx.core.net.toUri
import androidx.lifecycle.lifecycleScope
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.components.UserComponent
import com.habitrpg.android.habitica.data.ChallengeRepository
@ -19,8 +20,8 @@ import com.habitrpg.android.habitica.databinding.DialogChallengeDetailTaskGroupB
import com.habitrpg.android.habitica.databinding.FragmentChallengeDetailBinding
import com.habitrpg.android.habitica.extensions.addCloseButton
import com.habitrpg.android.habitica.extensions.inflate
import com.habitrpg.android.habitica.helpers.ExceptionHandler
import com.habitrpg.android.habitica.helpers.MainNavigationController
import com.habitrpg.android.habitica.helpers.RxErrorHandler
import com.habitrpg.android.habitica.models.members.Member
import com.habitrpg.android.habitica.models.social.Challenge
import com.habitrpg.android.habitica.models.tasks.Task
@ -32,11 +33,12 @@ import com.habitrpg.android.habitica.ui.viewHolders.tasks.HabitViewHolder
import com.habitrpg.android.habitica.ui.viewHolders.tasks.RewardViewHolder
import com.habitrpg.android.habitica.ui.viewHolders.tasks.TodoViewHolder
import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel
import com.habitrpg.android.habitica.ui.views.HabiticaIconsHelper
import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog
import com.habitrpg.common.habitica.helpers.EmojiParser
import com.habitrpg.common.habitica.helpers.setMarkdown
import com.habitrpg.shared.habitica.models.tasks.TaskType
import com.habitrpg.android.habitica.ui.views.HabiticaIconsHelper
import kotlinx.coroutines.launch
import retrofit2.HttpException
import javax.inject.Inject
@ -101,10 +103,11 @@ class ChallengeDetailFragment : BaseMainFragment<FragmentChallengeDetailBinding>
return@map (it.leaderId ?: "")
}
.filter { it.isNotEmpty() }
.flatMap { creatorID ->
return@flatMap socialRepository.getMember(creatorID)
}
.subscribe({ set(it) }, RxErrorHandler.handleEmptyError())
.subscribe({
lifecycleScope.launch(ExceptionHandler.coroutine()) {
set(socialRepository.retrieveMember(it))
}
}, ExceptionHandler.rx())
)
compositeSubscription.add(
challengeRepository.getChallengeTasks(id).subscribe(
@ -142,7 +145,7 @@ class ChallengeDetailFragment : BaseMainFragment<FragmentChallengeDetailBinding>
addRewards(rewards)
}
},
RxErrorHandler.handleEmptyError()
ExceptionHandler.rx()
)
)
@ -151,7 +154,7 @@ class ChallengeDetailFragment : BaseMainFragment<FragmentChallengeDetailBinding>
{ isMember ->
setJoined(isMember)
},
RxErrorHandler.handleEmptyError()
ExceptionHandler.rx()
)
)
}
@ -159,8 +162,11 @@ class ChallengeDetailFragment : BaseMainFragment<FragmentChallengeDetailBinding>
binding?.joinButton?.setOnClickListener {
challenge?.let { challenge ->
challengeRepository.joinChallenge(challenge)
.flatMap { userRepository.retrieveUser(true) }
.subscribe({}, RxErrorHandler.handleEmptyError())
.subscribe({
lifecycleScope.launch(ExceptionHandler.coroutine()) {
userRepository.retrieveUser(true)
}
}, ExceptionHandler.rx())
}
}
binding?.leaveButton?.setOnClickListener { showChallengeLeaveDialog() }
@ -230,7 +236,7 @@ class ChallengeDetailFragment : BaseMainFragment<FragmentChallengeDetailBinding>
if (it is HttpException && it.code() == 404) {
MainNavigationController.navigateBack()
}
RxErrorHandler.reportError(it)
ExceptionHandler.reportError(it)
})
}
}
@ -245,7 +251,8 @@ class ChallengeDetailFragment : BaseMainFragment<FragmentChallengeDetailBinding>
binding?.participantCount?.text = challenge.memberCount.toString()
}
private fun set(creator: Member) {
private fun set(creator: Member?) {
if (creator == null) return
binding?.creatorAvatarview?.setAvatar(creator)
binding?.creatorLabel?.tier = creator.contributor?.level ?: 0
binding?.creatorLabel?.username = creator.displayName
@ -335,11 +342,11 @@ class ChallengeDetailFragment : BaseMainFragment<FragmentChallengeDetailBinding>
alert.setMessage(this.getString(R.string.challenge_leave_description))
alert.addButton(R.string.leave_keep_tasks, true) { _, _ ->
val challenge = challenge ?: return@addButton
challengeRepository.leaveChallenge(challenge, "keep-all").subscribe({}, RxErrorHandler.handleEmptyError())
challengeRepository.leaveChallenge(challenge, "keep-all").subscribe({}, ExceptionHandler.rx())
}
alert.addButton(R.string.leave_delete_tasks, isPrimary = false, isDestructive = true) { _, _ ->
val challenge = challenge ?: return@addButton
challengeRepository.leaveChallenge(challenge, "remove-all").subscribe({}, RxErrorHandler.handleEmptyError())
challengeRepository.leaveChallenge(challenge, "remove-all").subscribe({}, ExceptionHandler.rx())
}
alert.setExtraCloseButtonVisibility(View.VISIBLE)
alert.show()

View file

@ -4,6 +4,7 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.RecyclerView
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.components.UserComponent
@ -11,8 +12,8 @@ import com.habitrpg.android.habitica.data.ChallengeRepository
import com.habitrpg.android.habitica.data.SocialRepository
import com.habitrpg.android.habitica.data.UserRepository
import com.habitrpg.android.habitica.databinding.FragmentRefreshRecyclerviewBinding
import com.habitrpg.android.habitica.helpers.ExceptionHandler
import com.habitrpg.android.habitica.helpers.MainNavigationController
import com.habitrpg.android.habitica.helpers.RxErrorHandler
import com.habitrpg.android.habitica.models.social.Challenge
import com.habitrpg.android.habitica.models.social.Group
import com.habitrpg.android.habitica.modules.AppModule
@ -21,7 +22,8 @@ import com.habitrpg.android.habitica.ui.fragments.BaseFragment
import com.habitrpg.android.habitica.ui.helpers.SafeDefaultItemAnimator
import com.habitrpg.common.habitica.helpers.EmptyItem
import io.reactivex.rxjava3.core.Flowable
import io.reactivex.rxjava3.kotlin.Flowables
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.launch
import javax.inject.Inject
import javax.inject.Named
@ -66,7 +68,7 @@ class ChallengeListFragment : BaseFragment<FragmentRefreshRecyclerviewBinding>()
super.onViewCreated(view, savedInstanceState)
challengeAdapter = ChallengesListViewAdapter(viewUserChallengesOnly, userId)
challengeAdapter?.getOpenDetailFragmentFlowable()?.subscribe({ openDetailFragment(it) }, RxErrorHandler.handleEmptyError())
challengeAdapter?.getOpenDetailFragmentFlowable()?.subscribe({ openDetailFragment(it) }, ExceptionHandler.rx())
?.let { compositeSubscription.add(it) }
binding?.refreshLayout?.setOnRefreshListener(this)
@ -83,16 +85,15 @@ class ChallengeListFragment : BaseFragment<FragmentRefreshRecyclerviewBinding>()
binding?.recyclerView?.setBackgroundResource(R.color.content_background)
}
compositeSubscription.add(
Flowables.combineLatest(socialRepository.getGroupFlowable(Group.TAVERN_ID), socialRepository.getUserGroups("guild")).subscribe(
{
this.filterGroups = mutableListOf()
filterGroups?.add(it.first)
filterGroups?.addAll(it.second)
},
RxErrorHandler.handleEmptyError()
)
)
lifecycleScope.launch(ExceptionHandler.coroutine()) {
socialRepository.getGroup(Group.TAVERN_ID).combine(socialRepository.getUserGroups("guild")) { tavern, guilds ->
return@combine Pair(tavern, guilds)
}.collect {
this@ChallengeListFragment.filterGroups = mutableListOf()
it.first?.let { tavern -> filterGroups?.add(tavern) }
filterGroups?.addAll(it.second)
}
}
binding?.recyclerView?.itemAnimator = SafeDefaultItemAnimator()
@ -144,7 +145,7 @@ class ChallengeListFragment : BaseFragment<FragmentRefreshRecyclerviewBinding>()
this.challenges = challenges
challengeAdapter?.updateUnfilteredData(challenges)
},
RxErrorHandler.handleEmptyError()
ExceptionHandler.rx()
)
)
}
@ -164,7 +165,7 @@ class ChallengeListFragment : BaseFragment<FragmentRefreshRecyclerviewBinding>()
}
nextPageToLoad += 1
},
RxErrorHandler.handleEmptyError()
ExceptionHandler.rx()
)
)
}

View file

@ -5,17 +5,19 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.widget.SearchView
import androidx.lifecycle.lifecycleScope
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.components.UserComponent
import com.habitrpg.android.habitica.data.SocialRepository
import com.habitrpg.android.habitica.databinding.FragmentRefreshRecyclerviewBinding
import com.habitrpg.android.habitica.helpers.RxErrorHandler
import com.habitrpg.android.habitica.helpers.ExceptionHandler
import com.habitrpg.android.habitica.ui.adapter.social.GuildListAdapter
import com.habitrpg.android.habitica.ui.fragments.BaseFragment
import com.habitrpg.common.habitica.helpers.EmptyItem
import com.habitrpg.android.habitica.ui.helpers.KeyboardUtil
import com.habitrpg.android.habitica.ui.helpers.SafeDefaultItemAnimator
import com.habitrpg.common.habitica.helpers.EmptyItem
import kotlinx.coroutines.launch
import javax.inject.Inject
class GuildListFragment : BaseFragment<FragmentRefreshRecyclerviewBinding>(), SearchView.OnQueryTextListener, SearchView.OnCloseListener, SwipeRefreshLayout.OnRefreshListener {
@ -52,7 +54,12 @@ class GuildListFragment : BaseFragment<FragmentRefreshRecyclerviewBinding>(), Se
viewAdapter.onlyShowUsersGuilds = onlyShowUsersGuilds
if (onlyShowUsersGuilds) {
compositeSubscription.add(socialRepository.getUserGroups("guild").subscribe({ viewAdapter.setUnfilteredData(it) }, RxErrorHandler.handleEmptyError()))
lifecycleScope.launch(ExceptionHandler.coroutine()) {
socialRepository.getUserGroups("guild")
.collect {
viewAdapter.setUnfilteredData(it)
}
}
} else {
compositeSubscription.add(
this.socialRepository.getPublicGuilds()
@ -60,7 +67,7 @@ class GuildListFragment : BaseFragment<FragmentRefreshRecyclerviewBinding>(), Se
{ groups ->
this@GuildListFragment.viewAdapter.setUnfilteredData(groups)
},
RxErrorHandler.handleEmptyError()
ExceptionHandler.rx()
)
)
}
@ -79,7 +86,7 @@ class GuildListFragment : BaseFragment<FragmentRefreshRecyclerviewBinding>(), Se
{
binding?.refreshLayout?.isRefreshing = false
},
RxErrorHandler.handleEmptyError()
ExceptionHandler.rx()
)
)
}

View file

@ -16,21 +16,23 @@ import android.widget.TextView
import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.graphics.drawable.toBitmap
import androidx.core.os.bundleOf
import androidx.lifecycle.lifecycleScope
import com.google.firebase.analytics.FirebaseAnalytics
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.components.UserComponent
import com.habitrpg.android.habitica.data.SocialRepository
import com.habitrpg.android.habitica.databinding.FragmentNoPartyBinding
import com.habitrpg.android.habitica.helpers.AppConfigManager
import com.habitrpg.android.habitica.helpers.ExceptionHandler
import com.habitrpg.android.habitica.helpers.MainNavigationController
import com.habitrpg.android.habitica.helpers.RxErrorHandler
import com.habitrpg.common.habitica.views.AvatarView
import com.habitrpg.android.habitica.ui.activities.GroupFormActivity
import com.habitrpg.android.habitica.ui.fragments.BaseMainFragment
import com.habitrpg.common.habitica.extensions.DataBindingUtils
import com.habitrpg.common.habitica.helpers.setMarkdown
import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel
import com.habitrpg.android.habitica.ui.views.HabiticaSnackbar
import com.habitrpg.common.habitica.extensions.DataBindingUtils
import com.habitrpg.common.habitica.helpers.setMarkdown
import com.habitrpg.common.habitica.views.AvatarView
import kotlinx.coroutines.launch
import javax.inject.Inject
import kotlin.math.roundToInt
@ -64,36 +66,34 @@ class NoPartyFragmentFragment : BaseMainFragment<FragmentNoPartyBinding>() {
binding?.refreshLayout?.setOnRefreshListener { this.refresh() }
binding?.invitationsView?.acceptCall = {
socialRepository.joinGroup(it)
.flatMap { userRepository.retrieveUser(false) }
.subscribe(
{
parentFragmentManager.popBackStack()
MainNavigationController.navigate(
R.id.partyFragment,
bundleOf(Pair("partyID", userViewModel.partyID))
)
},
RxErrorHandler.handleEmptyError()
lifecycleScope.launch(ExceptionHandler.coroutine()) {
socialRepository.joinGroup(it)
userRepository.retrieveUser(false)
parentFragmentManager.popBackStack()
MainNavigationController.navigate(
R.id.partyFragment,
bundleOf(Pair("partyID", userViewModel.partyID))
)
}
}
binding?.invitationsView?.rejectCall = {
socialRepository.rejectGroupInvite(it).subscribe({ }, RxErrorHandler.handleEmptyError())
socialRepository.rejectGroupInvite(it).subscribe({ }, ExceptionHandler.rx())
binding?.invitationWrapper?.visibility = View.GONE
}
binding?.invitationsView?.setLeader = { leader ->
compositeSubscription.add(
socialRepository.getMember(leader)
.subscribe(
{
binding?.root?.findViewById<AvatarView>(R.id.groupleader_avatar_view)?.setAvatar(it)
binding?.root?.findViewById<TextView>(R.id.groupleader_text_view)?.text = getString(R.string.invitation_title, it.displayName, binding?.invitationsView?.groupName)
},
RxErrorHandler.handleEmptyError()
)
)
binding?.invitationsView?.setLeader = { leaderID ->
lifecycleScope.launch(ExceptionHandler.coroutine()) {
val leader = socialRepository.retrieveMember(leaderID) ?: return@launch
binding?.root?.findViewById<AvatarView>(R.id.groupleader_avatar_view)
?.setAvatar(leader)
binding?.root?.findViewById<TextView>(R.id.groupleader_text_view)?.text =
getString(
R.string.invitation_title,
leader.displayName,
binding?.invitationsView?.groupName
)
}
}
binding?.usernameTextview?.setOnClickListener {
@ -153,42 +153,38 @@ class NoPartyFragmentFragment : BaseMainFragment<FragmentNoPartyBinding>() {
if (it.resultCode == Activity.RESULT_OK) {
val bundle = it.data?.extras
if (bundle?.getString("groupType") == "party") {
socialRepository.createGroup(
bundle.getString("name"),
bundle.getString("description"),
bundle.getString("leader"),
"party",
bundle.getString("privacy"),
bundle.getBoolean("leaderCreateChallenge")
)
.flatMap {
lifecycleScope.launch(ExceptionHandler.coroutine()) {
val group = socialRepository.createGroup(
bundle.getString("name"),
bundle.getString("description"),
bundle.getString("leader"),
"party",
bundle.getString("privacy"),
bundle.getBoolean("leaderCreateChallenge")
)
userRepository.retrieveUser(false)
if (isAdded) {
parentFragmentManager.popBackStack()
}
MainNavigationController.navigate(
R.id.partyFragment,
bundleOf(Pair("partyID", userViewModel.partyID))
)
}
.subscribe(
{
if (isAdded) {
parentFragmentManager.popBackStack()
}
MainNavigationController.navigate(
R.id.partyFragment,
bundleOf(Pair("partyID", userViewModel.partyID))
)
},
RxErrorHandler.handleEmptyError()
)
}
}
}
private fun refresh() {
compositeSubscription.add(
userRepository.retrieveUser(false, forced = true)
.filter { it.hasParty }
.flatMap { socialRepository.retrieveGroup("party") }
.flatMap { group1 -> socialRepository.retrieveGroupMembers(group1.id, true) }
.doOnComplete { binding?.refreshLayout?.isRefreshing = false }
.subscribe({ }, RxErrorHandler.handleEmptyError())
)
lifecycleScope.launch(ExceptionHandler.coroutine()) {
val user = userRepository.retrieveUser(false, true)
if (user?.hasParty == true) {
lifecycleScope.launch(ExceptionHandler.coroutine()) {
val group = socialRepository.retrieveGroup("party")
socialRepository.retrieveGroupMembers(group?.id ?: "", true)
}
}
}
}
override fun onDestroy() {

View file

@ -17,32 +17,33 @@ import com.habitrpg.android.habitica.data.InventoryRepository
import com.habitrpg.android.habitica.data.SocialRepository
import com.habitrpg.android.habitica.data.UserRepository
import com.habitrpg.android.habitica.databinding.FragmentPartyDetailBinding
import com.habitrpg.common.habitica.extensions.dpToPx
import com.habitrpg.android.habitica.extensions.inflate
import com.habitrpg.android.habitica.helpers.ExceptionHandler
import com.habitrpg.android.habitica.helpers.HapticFeedbackManager
import com.habitrpg.android.habitica.helpers.MainNavigationController
import com.habitrpg.android.habitica.helpers.RxErrorHandler
import com.habitrpg.android.habitica.models.inventory.QuestContent
import com.habitrpg.android.habitica.models.members.Member
import com.habitrpg.android.habitica.models.social.Challenge
import com.habitrpg.android.habitica.models.social.Group
import com.habitrpg.android.habitica.models.user.User
import com.habitrpg.android.habitica.modules.AppModule
import com.habitrpg.common.habitica.views.AvatarView
import com.habitrpg.android.habitica.ui.activities.FullProfileActivity
import com.habitrpg.android.habitica.ui.activities.MainActivity
import com.habitrpg.android.habitica.ui.fragments.BaseFragment
import com.habitrpg.android.habitica.ui.fragments.inventory.items.ItemDialogFragment
import com.habitrpg.common.habitica.extensions.DataBindingUtils
import com.habitrpg.android.habitica.ui.helpers.dismissKeyboard
import com.habitrpg.common.habitica.extensions.loadImage
import com.habitrpg.common.habitica.helpers.setMarkdown
import com.habitrpg.android.habitica.ui.viewHolders.GroupMemberViewHolder
import com.habitrpg.android.habitica.ui.viewmodels.PartyViewModel
import com.habitrpg.android.habitica.ui.views.HabiticaSnackbar
import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog
import com.habitrpg.common.habitica.extensions.DataBindingUtils
import com.habitrpg.common.habitica.extensions.dpToPx
import com.habitrpg.common.habitica.extensions.loadImage
import com.habitrpg.common.habitica.helpers.setMarkdown
import com.habitrpg.common.habitica.views.AvatarView
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.launch
import javax.inject.Inject
import javax.inject.Named
@ -95,28 +96,29 @@ class PartyDetailFragment : BaseFragment<FragmentPartyDetailBinding>() {
binding?.invitationsView?.acceptCall = {
viewModel?.joinGroup(it) {
compositeSubscription.add(
userRepository.retrieveUser(false)
.subscribe { user ->
parentFragmentManager.popBackStack()
MainNavigationController.navigate(
R.id.partyFragment,
bundleOf(Pair("partyID", user.party?.id))
)
}
)
lifecycleScope.launch(ExceptionHandler.coroutine()) {
val user = userRepository.retrieveUser(false)
parentFragmentManager.popBackStack()
MainNavigationController.navigate(
R.id.partyFragment,
bundleOf(Pair("partyID", user?.party?.id))
)
}
}
}
binding?.invitationsView?.rejectCall = {
socialRepository.rejectGroupInvite(it)
.flatMap { userRepository.retrieveUser(false, true) }
.subscribe({ }, RxErrorHandler.handleEmptyError())
.subscribe({
lifecycleScope.launch(ExceptionHandler.coroutine()) {
userRepository.retrieveUser(false, true)
}
}, ExceptionHandler.rx())
}
viewModel?.getGroupData()?.observe(viewLifecycleOwner, { updateParty(it) })
viewModel?.user?.observe(viewLifecycleOwner, { updateUser(it) })
viewModel?.getMembersData()?.observe(viewLifecycleOwner, { updateMembersList(it) })
viewModel?.getGroupData()?.observe(viewLifecycleOwner) { updateParty(it) }
viewModel?.user?.observe(viewLifecycleOwner) { updateUser(it) }
viewModel?.getMembersData()?.observe(viewLifecycleOwner) { updateMembersList(it) }
}
private fun refreshParty() {
@ -141,8 +143,10 @@ class PartyDetailFragment : BaseFragment<FragmentPartyDetailBinding>() {
binding?.questImageWrapper?.visibility = View.VISIBLE
lifecycleScope.launch(Dispatchers.Main) {
delay(500)
inventoryRepository.getQuestContent(party.quest?.key ?: "")
.subscribe({ this@PartyDetailFragment.updateQuestContent(it) }, RxErrorHandler.handleEmptyError())
val content = inventoryRepository.getQuestContent(party.quest?.key ?: "").firstOrNull()
if (content != null) {
updateQuestContent(content)
}
}
} else {
binding?.newQuestButton?.visibility = View.VISIBLE
@ -184,16 +188,17 @@ class PartyDetailFragment : BaseFragment<FragmentPartyDetailBinding>() {
val groupName = invitation.name
leaderID.let { id ->
compositeSubscription.add(
socialRepository.getMember(id)
.subscribe(
{ member ->
binding?.root?.findViewById<AvatarView>(R.id.groupleader_avatar_view)?.setAvatar(member)
binding?.root?.findViewById<TextView>(R.id.groupleader_text_view)?.text = getString(R.string.invitation_title, member.displayName, groupName)
},
RxErrorHandler.handleEmptyError()
lifecycleScope.launch(ExceptionHandler.coroutine()) {
val member = socialRepository.retrieveMember(id) ?: return@launch
binding?.root?.findViewById<AvatarView>(R.id.groupleader_avatar_view)
?.setAvatar(member)
binding?.root?.findViewById<TextView>(R.id.groupleader_text_view)?.text =
getString(
R.string.invitation_title,
member.displayName,
groupName
)
)
}
}
view?.findViewById<Button>(R.id.accept_button)?.setOnClickListener {
@ -263,7 +268,7 @@ class PartyDetailFragment : BaseFragment<FragmentPartyDetailBinding>() {
val leaderID = viewModel?.leaderID
members?.forEachIndexed { index, member ->
val memberView = (
if (binding?.membersWrapper?.childCount ?: 0 > index) {
if ((binding?.membersWrapper?.childCount ?: 0) > index) {
binding?.membersWrapper?.getChildAt(index)
} else {
val view = binding?.membersWrapper?.inflate(R.layout.party_member, false)
@ -299,18 +304,15 @@ class PartyDetailFragment : BaseFragment<FragmentPartyDetailBinding>() {
val addMessageDialog = context?.let { HabiticaAlertDialog(it) }
addMessageDialog?.addButton(android.R.string.ok, true) { _, _ ->
socialRepository.postPrivateMessage(userID, emojiEditText.text.toString())
.subscribe(
{
(activity as? MainActivity)?.snackbarContainer?.let { it1 ->
HabiticaSnackbar.showSnackbar(
it1,
String.format(getString(R.string.profile_message_sent_to), username), HabiticaSnackbar.SnackbarDisplayType.NORMAL
)
}
},
RxErrorHandler.handleEmptyError()
)
lifecycleScope.launch(ExceptionHandler.coroutine()) {
socialRepository.postPrivateMessage(userID, emojiEditText.text.toString())
(activity as? MainActivity)?.snackbarContainer?.let { it1 ->
HabiticaSnackbar.showSnackbar(
it1,
String.format(getString(R.string.profile_message_sent_to), username), HabiticaSnackbar.SnackbarDisplayType.NORMAL
)
}
}
activity?.dismissKeyboard()
}
addMessageDialog?.addButton(android.R.string.cancel, false) { _, _ -> activity?.dismissKeyboard() }
@ -321,18 +323,15 @@ class PartyDetailFragment : BaseFragment<FragmentPartyDetailBinding>() {
private fun showTransferOwnerShipDialog(userID: String, displayName: String) {
val dialog = context?.let { HabiticaAlertDialog(it) }
dialog?.addButton(R.string.transfer, true) { _, _ ->
socialRepository.transferGroupOwnership(viewModel?.groupID ?: "", userID)
.subscribe(
{
(activity as? MainActivity)?.snackbarContainer?.let { it1 ->
HabiticaSnackbar.showSnackbar(
it1,
String.format(getString(R.string.transferred_ownership), displayName), HabiticaSnackbar.SnackbarDisplayType.NORMAL
)
}
},
RxErrorHandler.handleEmptyError()
)
lifecycleScope.launch(ExceptionHandler.coroutine()) {
socialRepository.transferGroupOwnership(viewModel?.groupID ?: "", userID)
(activity as? MainActivity)?.snackbarContainer?.let { it1 ->
HabiticaSnackbar.showSnackbar(
it1,
String.format(getString(R.string.transferred_ownership), displayName), HabiticaSnackbar.SnackbarDisplayType.NORMAL
)
}
}
activity?.dismissKeyboard()
}
dialog?.addButton(android.R.string.cancel, false) { _, _ -> activity?.dismissKeyboard() }
@ -344,18 +343,15 @@ class PartyDetailFragment : BaseFragment<FragmentPartyDetailBinding>() {
private fun showRemoveMemberDialog(userID: String, displayName: String) {
val dialog = context?.let { HabiticaAlertDialog(it) }
dialog?.addButton(R.string.remove, true) { _, _ ->
socialRepository.removeMemberFromGroup(viewModel?.groupID ?: "", userID)
.subscribe(
{
(activity as? MainActivity)?.snackbarContainer?.let { it1 ->
HabiticaSnackbar.showSnackbar(
it1,
String.format(getString(R.string.removed_member), displayName), HabiticaSnackbar.SnackbarDisplayType.NORMAL
)
}
},
RxErrorHandler.handleEmptyError()
)
lifecycleScope.launch(ExceptionHandler.coroutine()) {
socialRepository.removeMemberFromGroup(viewModel?.groupID ?: "", userID)
(activity as? MainActivity)?.snackbarContainer?.let { it1 ->
HabiticaSnackbar.showSnackbar(
it1,
String.format(getString(R.string.removed_member), displayName), HabiticaSnackbar.SnackbarDisplayType.NORMAL
)
}
}
activity?.dismissKeyboard()
}
dialog?.addButton(android.R.string.cancel, false) { _, _ -> activity?.dismissKeyboard() }
@ -373,7 +369,7 @@ class PartyDetailFragment : BaseFragment<FragmentPartyDetailBinding>() {
}
private fun getGroupChallenges(): List<Challenge> {
var groupChallenges = mutableListOf<Challenge>()
val groupChallenges = mutableListOf<Challenge>()
userRepository.getUserFlowable().forEach {
it.challenges?.forEach {
challengeRepository.getChallenge(it.challengeID).forEach {

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