mirror of
https://github.com/sudoxnym/habitica-android.git
synced 2026-05-19 12:18:59 +00:00
refactor for more coroutines
This commit is contained in:
parent
df993ec34d
commit
5f5729deca
27 changed files with 181 additions and 111 deletions
|
|
@ -12,6 +12,7 @@ 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.flow.Flow
|
||||
|
||||
interface SocialRepository : BaseRepository {
|
||||
fun getPublicGuilds(): Flowable<out List<Group>>
|
||||
|
|
@ -40,7 +41,8 @@ interface SocialRepository : BaseRepository {
|
|||
fun postGroupChat(groupId: String, message: String): Flowable<PostChatMessageResult>
|
||||
|
||||
fun retrieveGroup(id: String): Flowable<Group>
|
||||
fun getGroup(id: String?): Flowable<Group>
|
||||
fun getGroup(id: String?): Flow<Group?>
|
||||
fun getGroupFlowable(id: String?): Flowable<Group>
|
||||
|
||||
fun leaveGroup(id: String?, keepChallenges: Boolean): Flowable<Group>
|
||||
|
||||
|
|
@ -77,7 +79,7 @@ interface SocialRepository : BaseRepository {
|
|||
|
||||
fun postPrivateMessage(recipientId: String, message: String): Flowable<List<ChatMessage>>
|
||||
|
||||
fun getGroupMembers(id: String): Flowable<out List<Member>>
|
||||
fun getGroupMembers(id: String): Flow<List<Member>>
|
||||
fun retrieveGroupMembers(id: String, includeAllPublicFields: Boolean): Flowable<List<Member>>
|
||||
|
||||
fun inviteToGroup(id: String, inviteData: Map<String, Any>): Flowable<List<Void>>
|
||||
|
|
|
|||
|
|
@ -17,8 +17,12 @@ 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 java.util.UUID
|
||||
import kotlinx.coroutines.MainScope
|
||||
import kotlinx.coroutines.flow.emptyFlow
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.launch
|
||||
import retrofit2.HttpException
|
||||
import java.util.UUID
|
||||
|
||||
class SocialRepositoryImpl(
|
||||
localRepository: SocialLocalRepository,
|
||||
|
|
@ -26,7 +30,7 @@ class SocialRepositoryImpl(
|
|||
userID: String
|
||||
) : BaseRepositoryImpl<SocialLocalRepository>(localRepository, apiClient, userID), SocialRepository {
|
||||
override fun transferGroupOwnership(groupID: String, userID: String): Flowable<Group> {
|
||||
return localRepository.getGroup(groupID)
|
||||
return localRepository.getGroupFlowable(groupID)
|
||||
.map {
|
||||
val group = localRepository.getUnmanagedCopy(it)
|
||||
group.leaderID = userID
|
||||
|
|
@ -131,25 +135,23 @@ class SocialRepositoryImpl(
|
|||
return Flowable.zip(
|
||||
apiClient.getGroup(id).doOnNext { localRepository.saveGroup(it) },
|
||||
retrieveGroupChat(id)
|
||||
.toFlowable(),
|
||||
{ group, _ ->
|
||||
group
|
||||
}
|
||||
).doOnError {
|
||||
.toFlowable()
|
||||
) { group, _ ->
|
||||
group
|
||||
}.doOnError {
|
||||
if (it is HttpException && it.code() == 404) {
|
||||
localRepository.getGroup(id).firstElement().subscribe { group ->
|
||||
localRepository.delete(group)
|
||||
MainScope().launch {
|
||||
val group = localRepository.getGroup(id).first()
|
||||
if (group != null) {
|
||||
localRepository.delete(group)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun getGroup(id: String?): Flowable<Group> {
|
||||
if (id?.isNotBlank() != true) {
|
||||
return Flowable.empty()
|
||||
}
|
||||
return localRepository.getGroup(id)
|
||||
}
|
||||
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> {
|
||||
if (id?.isNotBlank() != true) {
|
||||
|
|
@ -157,7 +159,7 @@ class SocialRepositoryImpl(
|
|||
}
|
||||
return apiClient.leaveGroup(id, if (keepChallenges) "remain-in-challenges" else "leave-challenges")
|
||||
.doOnNext { localRepository.updateMembership(userID, id, false) }
|
||||
.flatMapMaybe { localRepository.getGroup(id).firstElement() }
|
||||
.flatMapMaybe { localRepository.getGroupFlowable(id).firstElement() }
|
||||
}
|
||||
|
||||
override fun joinGroup(id: String?): Flowable<Group> {
|
||||
|
|
@ -256,7 +258,7 @@ class SocialRepositoryImpl(
|
|||
return postPrivateMessage(recipientId, messageObject)
|
||||
}
|
||||
|
||||
override fun getGroupMembers(id: String): Flowable<out List<Member>> = localRepository.getGroupMembers(id)
|
||||
override fun getGroupMembers(id: String) = localRepository.getGroupMembers(id)
|
||||
|
||||
override fun retrieveGroupMembers(id: String, includeAllPublicFields: Boolean): Flowable<List<Member>> {
|
||||
return apiClient.getGroupMembers(id, includeAllPublicFields)
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import com.habitrpg.android.habitica.models.social.GroupMembership
|
|||
import com.habitrpg.android.habitica.models.social.InboxConversation
|
||||
import com.habitrpg.android.habitica.models.user.User
|
||||
import io.reactivex.rxjava3.core.Flowable
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface SocialLocalRepository : BaseLocalRepository {
|
||||
fun getPublicGuilds(): Flowable<out List<Group>>
|
||||
|
|
@ -14,14 +15,15 @@ interface SocialLocalRepository : BaseLocalRepository {
|
|||
fun getUserGroups(userID: String, type: String?): Flowable<out List<Group>>
|
||||
fun getGroups(type: String): Flowable<out List<Group>>
|
||||
|
||||
fun getGroup(id: String): Flowable<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>>
|
||||
|
||||
fun deleteMessage(id: String)
|
||||
|
||||
fun getGroupMembers(partyId: String): Flowable<out List<Member>>
|
||||
fun getGroupMembers(partyId: String): Flow<List<Member>>
|
||||
|
||||
fun updateRSVPNeeded(user: User?, newValue: Boolean)
|
||||
|
||||
|
|
|
|||
|
|
@ -13,6 +13,10 @@ import hu.akarnokd.rxjava3.bridge.RxJavaBridge
|
|||
import io.reactivex.rxjava3.core.Flowable
|
||||
import io.realm.Realm
|
||||
import io.realm.Sort
|
||||
import io.realm.kotlin.toFlow
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.filter
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
class RealmSocialLocalRepository(realm: Realm) : RealmBaseLocalRepository(realm), SocialLocalRepository {
|
||||
|
||||
|
|
@ -165,7 +169,7 @@ class RealmSocialLocalRepository(realm: Realm) : RealmBaseLocalRepository(realm)
|
|||
)
|
||||
}
|
||||
|
||||
override fun getGroup(id: String): Flowable<Group> {
|
||||
override fun getGroupFlowable(id: String): Flowable<Group> {
|
||||
return RxJavaBridge.toV3Flowable(
|
||||
realm.where(Group::class.java)
|
||||
.equalTo("id", id)
|
||||
|
|
@ -176,6 +180,15 @@ class RealmSocialLocalRepository(realm: Realm) : RealmBaseLocalRepository(realm)
|
|||
)
|
||||
}
|
||||
|
||||
override fun getGroup(id: String): Flow<Group?> {
|
||||
return realm.where(Group::class.java)
|
||||
.equalTo("id", id)
|
||||
.findAll()
|
||||
.toFlow()
|
||||
.filter { group -> group.isLoaded && group.isValid && !group.isEmpty() }
|
||||
.map { groups -> groups.first() }
|
||||
}
|
||||
|
||||
override fun getGroupChat(groupId: String): Flowable<out List<ChatMessage>> {
|
||||
return RxJavaBridge.toV3Flowable(
|
||||
realm.where(ChatMessage::class.java)
|
||||
|
|
@ -192,14 +205,12 @@ class RealmSocialLocalRepository(realm: Realm) : RealmBaseLocalRepository(realm)
|
|||
executeTransaction { chatMessage?.deleteFromRealm() }
|
||||
}
|
||||
|
||||
override fun getGroupMembers(partyId: String): Flowable<out List<Member>> {
|
||||
return RxJavaBridge.toV3Flowable(
|
||||
realm.where(Member::class.java)
|
||||
override fun getGroupMembers(partyId: String): Flow<List<Member>> {
|
||||
return realm.where(Member::class.java)
|
||||
.equalTo("party.id", partyId)
|
||||
.findAll()
|
||||
.asFlowable()
|
||||
.toFlow()
|
||||
.filter { it.isLoaded }
|
||||
)
|
||||
}
|
||||
|
||||
override fun updateRSVPNeeded(user: User?, newValue: Boolean) {
|
||||
|
|
|
|||
|
|
@ -5,9 +5,9 @@ import com.habitrpg.android.habitica.models.tasks.ChecklistItem
|
|||
import com.habitrpg.android.habitica.models.tasks.RemindersItem
|
||||
import com.habitrpg.android.habitica.models.tasks.Task
|
||||
import com.habitrpg.android.habitica.models.tasks.TaskList
|
||||
import com.habitrpg.android.habitica.models.user.User
|
||||
import com.habitrpg.common.habitica.models.tasks.TaskType
|
||||
import com.habitrpg.common.habitica.models.tasks.TasksOrder
|
||||
import com.habitrpg.android.habitica.models.user.User
|
||||
import hu.akarnokd.rxjava3.bridge.RxJavaBridge
|
||||
import io.reactivex.rxjava3.core.Flowable
|
||||
import io.reactivex.rxjava3.core.Maybe
|
||||
|
|
@ -169,8 +169,9 @@ class RealmTaskLocalRepository(realm: Realm) : RealmBaseLocalRepository(realm),
|
|||
return Flowable.empty()
|
||||
}
|
||||
return RxJavaBridge.toV3Flowable(
|
||||
realm.where(Task::class.java).equalTo("id", taskId).findFirstAsync().asFlowable<RealmObject>()
|
||||
.filter { realmObject -> realmObject.isLoaded }
|
||||
realm.where(Task::class.java).equalTo("id", taskId).findAll().asFlowable()
|
||||
.filter { realmObject -> realmObject.isLoaded && realmObject.isNotEmpty() }
|
||||
.map { it.first() }
|
||||
.cast(Task::class.java)
|
||||
)
|
||||
}
|
||||
|
|
@ -187,8 +188,8 @@ class RealmTaskLocalRepository(realm: Realm) : RealmBaseLocalRepository(realm),
|
|||
}
|
||||
|
||||
override fun markTaskCompleted(taskId: String, isCompleted: Boolean) {
|
||||
val task = realm.where(Task::class.java).equalTo("id", taskId).findFirstAsync()
|
||||
executeTransaction { task.completed = true }
|
||||
val task = realm.where(Task::class.java).equalTo("id", taskId).findFirst()
|
||||
executeTransaction { task?.completed = true }
|
||||
}
|
||||
|
||||
override fun swapTaskPosition(firstPosition: Int, secondPosition: Int) {
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ class RealmUserLocalRepository(realm: Realm) : RealmBaseLocalRepository(realm),
|
|||
.equalTo("id", userID)
|
||||
.findAll()
|
||||
.toFlow()
|
||||
.filter { it.isLoaded }
|
||||
.filter { it.isLoaded && it.size > 0 }
|
||||
.map { it.first()?.questAchievements ?: emptyList() }
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -46,6 +46,7 @@ class SoundFile(val theme: String, private val fileName: String) {
|
|||
player?.setVolume(100f, 100f)
|
||||
player?.isLooping = false
|
||||
player?.start()
|
||||
} catch (e: IllegalStateException) {
|
||||
} catch (e: Exception) {
|
||||
RxErrorHandler.reportError(e)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -297,6 +297,8 @@ open class Task : RealmObject, BaseMainObject, Parcelable, BaseTask {
|
|||
when {
|
||||
text != task.text -> return true
|
||||
notes != task.notes -> return true
|
||||
reminders?.size != task.reminders?.size -> return true
|
||||
checklist?.size != task.checklist?.size -> return true
|
||||
reminders?.mapIndexed { index, remindersItem -> task.reminders?.get(index) != remindersItem }?.contains(true) == true -> return true
|
||||
checklist?.mapIndexed { index, item -> task.checklist?.get(index) != item }?.contains(true) == true -> return true
|
||||
priority != task.priority -> return true
|
||||
|
|
|
|||
|
|
@ -26,17 +26,17 @@ 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.common.habitica.extensions.getThemeColor
|
||||
import com.habitrpg.common.habitica.extensions.isUsingNightModeResources
|
||||
import com.habitrpg.android.habitica.extensions.subscribeWithErrorHandler
|
||||
import com.habitrpg.android.habitica.extensions.updateStatusBarColor
|
||||
import com.habitrpg.common.habitica.helpers.LanguageHelper
|
||||
import com.habitrpg.android.habitica.helpers.NotificationsManager
|
||||
import com.habitrpg.android.habitica.helpers.RxErrorHandler
|
||||
import com.habitrpg.android.habitica.interactors.ShowNotificationInteractor
|
||||
import com.habitrpg.android.habitica.proxy.AnalyticsManager
|
||||
import com.habitrpg.android.habitica.ui.helpers.ToolbarColorHelper
|
||||
import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog
|
||||
import com.habitrpg.common.habitica.extensions.getThemeColor
|
||||
import com.habitrpg.common.habitica.extensions.isUsingNightModeResources
|
||||
import com.habitrpg.common.habitica.helpers.LanguageHelper
|
||||
import io.reactivex.rxjava3.disposables.CompositeDisposable
|
||||
import java.util.Date
|
||||
import java.util.Locale
|
||||
|
|
@ -245,8 +245,10 @@ abstract class BaseActivity : AppCompatActivity() {
|
|||
sharingIntent.putExtra(Intent.EXTRA_TEXT, message)
|
||||
if (image != null) {
|
||||
val path = MediaStore.Images.Media.insertImage(this.contentResolver, image, "${(Date())}", null)
|
||||
val uri = Uri.parse(path)
|
||||
sharingIntent.putExtra(Intent.EXTRA_STREAM, uri)
|
||||
if (path != null) {
|
||||
val uri = Uri.parse(path)
|
||||
sharingIntent.putExtra(Intent.EXTRA_STREAM, uri)
|
||||
}
|
||||
}
|
||||
startActivity(Intent.createChooser(sharingIntent, getString(R.string.share_using)))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -364,7 +364,8 @@ class FullProfileActivity : BaseActivity() {
|
|||
|
||||
binding.equipmentTableLayout.removeAllViews()
|
||||
for (index in 1 until binding.attributesTableLayout.childCount) {
|
||||
if (binding.attributesTableLayout.getChildAt(index).isAttachedToWindow) {
|
||||
val child = binding.attributesTableLayout.getChildAt(index) ?: continue
|
||||
if (child.isAttachedToWindow) {
|
||||
binding.attributesTableLayout.removeViewAt(index)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,7 +27,6 @@ import androidx.activity.result.contract.ActivityResultContracts
|
|||
import androidx.core.content.ContextCompat
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.preference.PreferenceManager
|
||||
import com.google.android.gms.common.api.ApiException
|
||||
import com.google.android.gms.tasks.Tasks
|
||||
import com.google.android.gms.wearable.CapabilityClient
|
||||
import com.google.android.gms.wearable.MessageClient
|
||||
|
|
@ -239,23 +238,27 @@ class LoginActivity : BaseActivity() {
|
|||
val messageClient: MessageClient = Wearable.getMessageClient(this)
|
||||
val capabilityClient: CapabilityClient = Wearable.getCapabilityClient(this)
|
||||
lifecycleScope.launch(Dispatchers.IO) {
|
||||
val info = Tasks.await(
|
||||
capabilityClient.getCapability(
|
||||
"receive_message",
|
||||
CapabilityClient.FILTER_REACHABLE
|
||||
)
|
||||
)
|
||||
info.nodes.forEach {
|
||||
Tasks.await(
|
||||
messageClient.sendMessage(
|
||||
it.id,
|
||||
"/auth",
|
||||
"${response.id}:${response.apiToken}".toByteArray()
|
||||
try {
|
||||
val info = Tasks.await(
|
||||
capabilityClient.getCapability(
|
||||
"receive_message",
|
||||
CapabilityClient.FILTER_REACHABLE
|
||||
)
|
||||
)
|
||||
info.nodes.forEach {
|
||||
Tasks.await(
|
||||
messageClient.sendMessage(
|
||||
it.id,
|
||||
"/auth",
|
||||
"${response.id}:${response.apiToken}".toByteArray()
|
||||
)
|
||||
)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
// Wearable API is not available on this device.
|
||||
}
|
||||
}
|
||||
} catch (e: ApiException) {
|
||||
} catch (e: Exception) {
|
||||
// Wearable API is not available on this device.
|
||||
}
|
||||
compositeSubscription.add(
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import android.app.Activity
|
|||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import com.habitrpg.android.habitica.R
|
||||
import com.habitrpg.android.habitica.components.UserComponent
|
||||
|
|
@ -12,6 +13,7 @@ import com.habitrpg.android.habitica.databinding.ActivitySkillMembersBinding
|
|||
import com.habitrpg.android.habitica.helpers.RxErrorHandler
|
||||
import com.habitrpg.android.habitica.ui.adapter.social.PartyMemberRecyclerViewAdapter
|
||||
import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
class SkillMemberActivity : BaseActivity() {
|
||||
|
|
@ -56,10 +58,12 @@ class SkillMemberActivity : BaseActivity() {
|
|||
)?.let { compositeSubscription.add(it) }
|
||||
binding.recyclerView.adapter = viewAdapter
|
||||
|
||||
compositeSubscription.add(
|
||||
userRepository.getUserFlowable()
|
||||
.flatMap { user -> socialRepository.getGroupMembers(user.party?.id ?: "") }
|
||||
.subscribe({ viewAdapter?.data = it }, RxErrorHandler.handleEmptyError())
|
||||
)
|
||||
val user = userViewModel.user.value
|
||||
lifecycleScope.launch {
|
||||
socialRepository.getGroupMembers(user?.party?.id ?: "")
|
||||
.collect {
|
||||
viewAdapter?.data = it
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -110,6 +110,7 @@ class AvatarCustomizationFragment :
|
|||
}
|
||||
adapter.customizationType = type
|
||||
binding?.refreshLayout?.setOnRefreshListener(this)
|
||||
layoutManager = FlexboxLayoutManager(activity, ROW)
|
||||
layoutManager.justifyContent = JustifyContent.CENTER
|
||||
layoutManager.alignItems = AlignItems.FLEX_START
|
||||
binding?.recyclerView?.layoutManager = layoutManager
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import android.view.LayoutInflater
|
|||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import com.habitrpg.android.habitica.R
|
||||
import com.habitrpg.android.habitica.components.UserComponent
|
||||
|
|
@ -21,11 +22,13 @@ import com.habitrpg.android.habitica.models.social.Group
|
|||
import com.habitrpg.android.habitica.models.user.User
|
||||
import com.habitrpg.android.habitica.ui.adapter.inventory.ShopRecyclerAdapter
|
||||
import com.habitrpg.android.habitica.ui.fragments.BaseMainFragment
|
||||
import com.habitrpg.common.habitica.helpers.RecyclerViewState
|
||||
import com.habitrpg.android.habitica.ui.helpers.SafeDefaultItemAnimator
|
||||
import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel
|
||||
import com.habitrpg.android.habitica.ui.views.CurrencyViews
|
||||
import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog
|
||||
import com.habitrpg.common.habitica.helpers.RecyclerViewState
|
||||
import kotlinx.coroutines.flow.filter
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
open class ShopFragment : BaseMainFragment<FragmentRefreshRecyclerviewBinding>() {
|
||||
|
|
@ -133,18 +136,20 @@ open class ShopFragment : BaseMainFragment<FragmentRefreshRecyclerviewBinding>()
|
|||
updateCurrencyView(it)
|
||||
}
|
||||
|
||||
compositeSubscription.add(
|
||||
lifecycleScope.launch {
|
||||
socialRepository.getGroup(Group.TAVERN_ID)
|
||||
.filter { it.hasActiveQuest }
|
||||
.filter { group -> group.quest?.rageStrikes?.any { it.key == shopIdentifier } ?: false }
|
||||
.filter { group -> group.quest?.rageStrikes?.filter { it.key == shopIdentifier }?.get(0)?.wasHit == true }
|
||||
.subscribe(
|
||||
{
|
||||
adapter?.shopSpriteSuffix = "_" + it.quest?.key
|
||||
},
|
||||
RxErrorHandler.handleEmptyError()
|
||||
)
|
||||
)
|
||||
.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
|
||||
}
|
||||
.collect {
|
||||
adapter?.shopSpriteSuffix = "_" + it?.quest?.key
|
||||
}
|
||||
}
|
||||
|
||||
view.post { setGridSpanCount(view.width) }
|
||||
|
||||
|
|
|
|||
|
|
@ -24,10 +24,10 @@ import com.habitrpg.android.habitica.models.members.Member
|
|||
import com.habitrpg.android.habitica.models.social.Group
|
||||
import com.habitrpg.android.habitica.modules.AppModule
|
||||
import com.habitrpg.android.habitica.ui.fragments.BaseMainFragment
|
||||
import com.habitrpg.common.habitica.helpers.MarkdownParser
|
||||
import com.habitrpg.common.habitica.extensions.loadImage
|
||||
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 javax.inject.Inject
|
||||
import javax.inject.Named
|
||||
|
||||
|
|
@ -79,7 +79,7 @@ class QuestDetailFragment : BaseMainFragment<FragmentQuestDetailBinding>() {
|
|||
}
|
||||
.skipWhile { it.isBlank() }
|
||||
.distinctUntilChanged()
|
||||
.flatMap { socialRepository.getGroup(it) }
|
||||
.flatMap { socialRepository.getGroupFlowable(it) }
|
||||
.doOnNext { updateParty(it) }
|
||||
.map {
|
||||
it.quest?.key ?: ""
|
||||
|
|
|
|||
|
|
@ -26,9 +26,9 @@ import com.habitrpg.android.habitica.models.social.Group
|
|||
import com.habitrpg.android.habitica.models.user.User
|
||||
import com.habitrpg.android.habitica.ui.fragments.BaseFragment
|
||||
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 com.habitrpg.android.habitica.ui.views.UsernameLabel
|
||||
import javax.inject.Inject
|
||||
|
||||
class TavernDetailFragment : BaseFragment<FragmentTavernDetailBinding>() {
|
||||
|
|
@ -74,7 +74,7 @@ class TavernDetailFragment : BaseFragment<FragmentTavernDetailBinding>() {
|
|||
bindButtons()
|
||||
|
||||
compositeSubscription.add(
|
||||
socialRepository.getGroup(Group.TAVERN_ID)
|
||||
socialRepository.getGroupFlowable(Group.TAVERN_ID)
|
||||
.doOnNext { if (!it.hasActiveQuest) binding?.worldBossSection?.visibility = View.GONE }
|
||||
.filter { it.hasActiveQuest }
|
||||
.doOnNext {
|
||||
|
|
|
|||
|
|
@ -18,8 +18,8 @@ import com.habitrpg.android.habitica.models.social.Group
|
|||
import com.habitrpg.android.habitica.modules.AppModule
|
||||
import com.habitrpg.android.habitica.ui.adapter.social.ChallengesListViewAdapter
|
||||
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.helpers.EmptyItem
|
||||
import io.reactivex.rxjava3.core.Flowable
|
||||
import io.reactivex.rxjava3.kotlin.Flowables
|
||||
import javax.inject.Inject
|
||||
|
|
@ -84,7 +84,7 @@ class ChallengeListFragment : BaseFragment<FragmentRefreshRecyclerviewBinding>()
|
|||
}
|
||||
|
||||
compositeSubscription.add(
|
||||
Flowables.combineLatest(socialRepository.getGroup(Group.TAVERN_ID), socialRepository.getUserGroups("guild")).subscribe(
|
||||
Flowables.combineLatest(socialRepository.getGroupFlowable(Group.TAVERN_ID), socialRepository.getUserGroups("guild")).subscribe(
|
||||
{
|
||||
this.filterGroups = mutableListOf()
|
||||
filterGroups?.add(it.first)
|
||||
|
|
|
|||
|
|
@ -20,21 +20,19 @@ import com.habitrpg.android.habitica.helpers.MainNavigationController
|
|||
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.modules.AppModule
|
||||
import com.habitrpg.android.habitica.ui.activities.GroupInviteActivity
|
||||
import com.habitrpg.android.habitica.ui.activities.MainActivity
|
||||
import com.habitrpg.android.habitica.ui.fragments.BaseFragment
|
||||
import com.habitrpg.common.habitica.helpers.setMarkdown
|
||||
import com.habitrpg.android.habitica.ui.viewmodels.GroupViewModel
|
||||
import com.habitrpg.android.habitica.ui.views.HabiticaIcons
|
||||
import com.habitrpg.android.habitica.ui.views.HabiticaIconsHelper
|
||||
import com.habitrpg.android.habitica.ui.views.SnackbarActivity
|
||||
import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog
|
||||
import com.habitrpg.common.habitica.helpers.setMarkdown
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Named
|
||||
|
||||
class GuildDetailFragment : BaseFragment<FragmentGuildDetailBinding>() {
|
||||
|
||||
|
|
@ -49,9 +47,6 @@ class GuildDetailFragment : BaseFragment<FragmentGuildDetailBinding>() {
|
|||
@Inject
|
||||
lateinit var userRepository: UserRepository
|
||||
|
||||
@field:[Inject Named(AppModule.NAMED_USER_ID)]
|
||||
lateinit var userId: String
|
||||
|
||||
override fun createBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentGuildDetailBinding {
|
||||
return FragmentGuildDetailBinding.inflate(inflater, container, false)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -57,8 +57,8 @@ class GuildFragment : BaseMainFragment<FragmentViewpagerBinding>() {
|
|||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
viewModel.groupViewType = GroupViewType.GUILD
|
||||
viewModel.getGroupData().observe(viewLifecycleOwner, { setGroup(it) })
|
||||
viewModel.getIsMemberData().observe(viewLifecycleOwner, { activity?.invalidateOptionsMenu() })
|
||||
viewModel.getGroupData().observe(viewLifecycleOwner) { setGroup(it) }
|
||||
viewModel.getIsMemberData().observe(viewLifecycleOwner) { activity?.invalidateOptionsMenu() }
|
||||
|
||||
arguments?.let {
|
||||
val args = GuildFragmentArgs.fromBundle(it)
|
||||
|
|
|
|||
|
|
@ -144,8 +144,8 @@ open class TaskRecyclerViewFragment : BaseFragment<FragmentRefreshRecyclerviewBi
|
|||
recyclerAdapter?.adventureGuideOpenEvents?.subscribeWithErrorHandler { MainNavigationController.navigate(R.id.adventureGuideActivity) }?.let { recyclerSubscription.add(it) }
|
||||
|
||||
viewModel.ownerID.observe(viewLifecycleOwner) {
|
||||
canEditTasks = viewModel.isPersonalBoard ?: true
|
||||
canScoreTaks = viewModel.isPersonalBoard ?: true
|
||||
canEditTasks = viewModel.isPersonalBoard
|
||||
canScoreTaks = viewModel.isPersonalBoard
|
||||
recyclerAdapter?.canScoreTasks = canScoreTaks
|
||||
updateTaskSubscription(it)
|
||||
}
|
||||
|
|
@ -270,8 +270,8 @@ open class TaskRecyclerViewFragment : BaseFragment<FragmentRefreshRecyclerviewBi
|
|||
) {
|
||||
if (validTaskId != null) {
|
||||
var newPosition = viewHolder.bindingAdapterPosition
|
||||
if ((viewModel.filterCount(taskType) ?: 0) > 0) {
|
||||
newPosition = if ((newPosition + 1) == recyclerAdapter?.data?.size) {
|
||||
if (viewModel.filterCount(taskType) > 0) {
|
||||
newPosition = if ((newPosition + 1) >= (recyclerAdapter?.data?.size ?: 0)) {
|
||||
recyclerAdapter?.data?.get(newPosition - 1)?.position ?: newPosition
|
||||
} else {
|
||||
(recyclerAdapter?.data?.get(newPosition + 1)?.position ?: newPosition) - 1
|
||||
|
|
@ -370,7 +370,7 @@ open class TaskRecyclerViewFragment : BaseFragment<FragmentRefreshRecyclerviewBi
|
|||
}
|
||||
|
||||
private fun setEmptyLabels() {
|
||||
binding?.recyclerView?.emptyItem = if ((viewModel.filterCount(taskType) ?: 0) > 0) {
|
||||
binding?.recyclerView?.emptyItem = if (viewModel.filterCount(taskType) > 0) {
|
||||
when (this.taskType) {
|
||||
TaskType.HABIT -> {
|
||||
EmptyItem(
|
||||
|
|
|
|||
|
|
@ -6,24 +6,24 @@ import androidx.lifecycle.MutableLiveData
|
|||
import com.habitrpg.android.habitica.components.UserComponent
|
||||
import com.habitrpg.android.habitica.data.ChallengeRepository
|
||||
import com.habitrpg.android.habitica.data.SocialRepository
|
||||
import com.habitrpg.common.habitica.extensions.Optional
|
||||
import com.habitrpg.common.habitica.extensions.asOptional
|
||||
import com.habitrpg.android.habitica.extensions.filterOptionalDoOnEmpty
|
||||
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.models.members.Member
|
||||
import com.habitrpg.common.habitica.models.notifications.NewChatMessageData
|
||||
import com.habitrpg.android.habitica.models.social.Challenge
|
||||
import com.habitrpg.android.habitica.models.social.ChatMessage
|
||||
import com.habitrpg.android.habitica.models.social.Group
|
||||
import com.habitrpg.common.habitica.extensions.Optional
|
||||
import com.habitrpg.common.habitica.extensions.asOptional
|
||||
import com.habitrpg.common.habitica.models.notifications.NewChatMessageData
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.rxjava3.core.BackpressureStrategy
|
||||
import io.reactivex.rxjava3.core.Flowable
|
||||
import io.reactivex.rxjava3.subjects.BehaviorSubject
|
||||
import retrofit2.HttpException
|
||||
import java.util.concurrent.TimeUnit
|
||||
import javax.inject.Inject
|
||||
import retrofit2.HttpException
|
||||
|
||||
enum class GroupViewType(internal val order: String) {
|
||||
PARTY("party"),
|
||||
|
|
@ -121,7 +121,7 @@ open class GroupViewModel(initializeComponent: Boolean) : BaseViewModel(initiali
|
|||
disposable.add(
|
||||
groupIDFlowable
|
||||
.filterOptionalDoOnEmpty { group.value = null }
|
||||
.flatMap { socialRepository.getGroup(it) }
|
||||
.flatMap { socialRepository.getGroupFlowable(it) }
|
||||
.map { socialRepository.getUnmanagedCopy(it) }
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe({ group.value = it }, RxErrorHandler.handleEmptyError())
|
||||
|
|
@ -132,7 +132,7 @@ open class GroupViewModel(initializeComponent: Boolean) : BaseViewModel(initiali
|
|||
disposable.add(
|
||||
groupIDFlowable
|
||||
.filterOptionalDoOnEmpty { leader.value = null }
|
||||
.flatMap { socialRepository.getGroup(it) }
|
||||
.flatMap { socialRepository.getGroupFlowable(it) }
|
||||
.distinctUntilChanged { group1, group2 -> group1.id == group2.id }
|
||||
.flatMap { socialRepository.getMember(it.leaderID) }
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
|
|
|
|||
|
|
@ -2,12 +2,14 @@ package com.habitrpg.android.habitica.ui.viewmodels
|
|||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.habitrpg.android.habitica.components.UserComponent
|
||||
import com.habitrpg.android.habitica.extensions.filterOptionalDoOnEmpty
|
||||
import com.habitrpg.android.habitica.helpers.RxErrorHandler
|
||||
import com.habitrpg.android.habitica.models.members.Member
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.rxjava3.core.BackpressureStrategy
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class PartyViewModel(initializeComponent: Boolean) : GroupViewModel(initializeComponent) {
|
||||
constructor() : this(true)
|
||||
|
|
@ -39,10 +41,16 @@ class PartyViewModel(initializeComponent: Boolean) : GroupViewModel(initializeCo
|
|||
private fun loadMembersFromLocal() {
|
||||
disposable.add(
|
||||
groupIDSubject.toFlowable(BackpressureStrategy.LATEST)
|
||||
.distinctUntilChanged()
|
||||
.filterOptionalDoOnEmpty { members.value = null }
|
||||
.flatMap { socialRepository.getGroupMembers(it) }
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe({ members.value = it }, RxErrorHandler.handleEmptyError())
|
||||
.subscribe({
|
||||
viewModelScope.launch {
|
||||
socialRepository.getGroupMembers(it)
|
||||
.collect {
|
||||
members.value = it
|
||||
}
|
||||
}}, RxErrorHandler.handleEmptyError())
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import com.google.gson.JsonObject
|
|||
import com.google.gson.JsonParseException
|
||||
import com.google.gson.JsonSerializationContext
|
||||
import com.google.gson.JsonSerializer
|
||||
import com.habitrpg.android.habitica.extensions.getAsString
|
||||
import com.habitrpg.android.habitica.models.social.Challenge
|
||||
import java.lang.reflect.Type
|
||||
import java.util.Date
|
||||
|
|
@ -22,10 +23,8 @@ class ChallengeDeserializer : JsonDeserializer<Challenge>, JsonSerializer<Challe
|
|||
|
||||
challenge.id = jsonObject.get("id").asString
|
||||
challenge.name = jsonObject.get("name").asString
|
||||
if (jsonObject.has("shortName")) {
|
||||
challenge.shortName = jsonObject.get("shortName").asString
|
||||
}
|
||||
challenge.description = jsonObject.get("description").asString
|
||||
challenge.shortName = jsonObject.getAsString("shortName")
|
||||
challenge.description = jsonObject.getAsString("description")
|
||||
challenge.memberCount = jsonObject.get("memberCount").asInt
|
||||
|
||||
val prizeElement = jsonObject.get("prize")
|
||||
|
|
|
|||
|
|
@ -50,6 +50,7 @@ object Animations {
|
|||
}
|
||||
|
||||
fun circularReveal(view: View, duration: Long = 300) {
|
||||
if (!view.isAttachedToWindow) return
|
||||
val cx = view.width / 2
|
||||
val cy = view.height / 2
|
||||
val finalRadius = Math.hypot(cx.toDouble(), cy.toDouble()).toFloat()
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ import kotlinx.coroutines.CoroutineScope
|
|||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.lang.NullPointerException
|
||||
|
||||
object MarkdownParser {
|
||||
private val cache = sortedMapOf<Int, Spanned>()
|
||||
|
|
@ -97,9 +98,13 @@ object MarkdownParser {
|
|||
// Adding this space here bc for some reason some markdown is not rendered correctly when the whole string is supposed to be formatted
|
||||
val result = markwon?.toMarkdown("$text ") ?: SpannableString(text)
|
||||
|
||||
cache[hashCode] = result
|
||||
if (cache.size > 100) {
|
||||
cache.remove(0)
|
||||
try {
|
||||
cache[hashCode] = result
|
||||
if (cache.size > 100) {
|
||||
cache.remove(cache.firstKey())
|
||||
}
|
||||
} catch (_: NullPointerException) {
|
||||
// for some reason hashCode seems to be null sometimes.
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
|
@ -114,7 +119,11 @@ object MarkdownParser {
|
|||
}
|
||||
|
||||
fun hasCached(input: String?): Boolean {
|
||||
return cache.containsKey(input?.hashCode())
|
||||
return try {
|
||||
cache.containsKey(input?.hashCode())
|
||||
} catch (_: NullPointerException) {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -1,3 +1,24 @@
|
|||
Treat your life like a game to stay motivated and organized! Habitica makes it simple to have fun while accomplishing goals.
|
||||
Input your Habits, your Daily goals, and your To-Do list, and then create a custom avatar. Check off tasks to level up your avatar and unlock features such as armor, pets, skills, and even quests! Fight monsters with friends to keep each other accountable, and use your gold on in-game rewards, like equipment, or custom awards, like watching an episode of your favorite TV show. Flexible, social, and fun, Habitica is the perfect way to motivate yourself to accomplish anything.
|
||||
If you have any questions, feel free to send feedback to mobile@habitica.com! And if you enjoy our app, we would really appreciate it if you would leave us a review.
|
||||
Habitica is a free habit-building and productivity app that uses retro RPG elements to gamify your tasks and goals.
|
||||
Use Habitica to help with ADHD, self care, New Year’s resolutions, household chores, work tasks, creative projects, fitness goals, back-to-school routines, and more!
|
||||
How it works:
|
||||
Create an avatar then add tasks, chores, or goals you’d like to work on. When you do something in real life, check it off in the app and receive gold, experience, and items that can be used in-game!
|
||||
Features:
|
||||
• Automatically repeating tasks scheduled for your daily, weekly, or monthly routines
|
||||
• Flexible habit tracker for tasks you want to do multiple times a day or only once in awhile
|
||||
• Traditional to do list for tasks that only need to be done once
|
||||
• Color coded tasks and streak counters help you see how you’re doing at a glance
|
||||
• Leveling system to visualize your overall progress
|
||||
• Tons of collectable gear and pets to suit your personal style
|
||||
• Inclusive avatar customizations: wheelchairs, hair styles, skin tones, and more
|
||||
• Regular content releases and seasonal events to keep things fresh
|
||||
• Parties let you team up with friends for extra accountability and battle fierce foes by completing tasks
|
||||
• Challenges offer shared task lists you can add to your personal tasks
|
||||
• Guilds let you connect with others that share your interests and goals
|
||||
• Reminders and widgets to help keep you on track
|
||||
• Customizable color themes with dark and light mode
|
||||
• Syncing across devices
|
||||
• Brand new WearOS watch app available in version 4.0!
|
||||
—
|
||||
Habitica is an open-source app run by a small team that’s made better by the work of volunteers who contribute pixel art, translations, bug fixes, and more. If you’d like to contribute, reach out!
|
||||
Community, privacy, and transparency are important to us. Your tasks are private and we don’t sell your personal data to third parties.
|
||||
If you have any questions, feel free to send feedback to admin@habitica.com! And if you enjoy our app, we would really appreciate it if you would leave us a review.
|
||||
|
|
@ -1,2 +1,2 @@
|
|||
NAME=4.0
|
||||
CODE=4310
|
||||
NAME=4.0.1
|
||||
CODE=4330
|
||||
Loading…
Reference in a new issue