From 5f5729decaa8a5d64d717706c665fad4b2917b7d Mon Sep 17 00:00:00 2001 From: Phillip Thelen Date: Tue, 9 Aug 2022 11:32:13 +0200 Subject: [PATCH] refactor for more coroutines --- .../android/habitica/data/SocialRepository.kt | 6 ++-- .../implementation/SocialRepositoryImpl.kt | 36 ++++++++++--------- .../data/local/SocialLocalRepository.kt | 6 ++-- .../RealmSocialLocalRepository.kt | 23 ++++++++---- .../RealmTaskLocalRepository.kt | 11 +++--- .../RealmUserLocalRepository.kt | 2 +- .../android/habitica/helpers/SoundFile.kt | 1 + .../android/habitica/models/tasks/Task.kt | 2 ++ .../habitica/ui/activities/BaseActivity.kt | 12 ++++--- .../ui/activities/FullProfileActivity.kt | 3 +- .../habitica/ui/activities/LoginActivity.kt | 31 ++++++++-------- .../ui/activities/SkillMemberActivity.kt | 14 +++++--- .../AvatarCustomizationFragment.kt | 1 + .../fragments/inventory/shops/ShopFragment.kt | 29 ++++++++------- .../fragments/social/QuestDetailFragment.kt | 6 ++-- .../fragments/social/TavernDetailFragment.kt | 4 +-- .../challenges/ChallengeListFragment.kt | 4 +-- .../social/guilds/GuildDetailFragment.kt | 7 +--- .../fragments/social/guilds/GuildFragment.kt | 4 +-- .../tasks/TaskRecyclerViewFragment.kt | 10 +++--- .../habitica/ui/viewmodels/GroupViewModel.kt | 12 +++---- .../habitica/ui/viewmodels/PartyViewModel.kt | 12 +++++-- .../habitica/utils/ChallengeDeserializer.kt | 7 ++-- .../common/habitica/helpers/Animations.kt | 1 + .../common/habitica/helpers/MarkdownParser.kt | 17 ++++++--- .../android/en-US/full_description.txt | 27 ++++++++++++-- version.properties | 4 +-- 27 files changed, 181 insertions(+), 111 deletions(-) diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/SocialRepository.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/SocialRepository.kt index 9985d2209..4d0732594 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/SocialRepository.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/SocialRepository.kt @@ -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> @@ -40,7 +41,8 @@ interface SocialRepository : BaseRepository { fun postGroupChat(groupId: String, message: String): Flowable fun retrieveGroup(id: String): Flowable - fun getGroup(id: String?): Flowable + fun getGroup(id: String?): Flow + fun getGroupFlowable(id: String?): Flowable fun leaveGroup(id: String?, keepChallenges: Boolean): Flowable @@ -77,7 +79,7 @@ interface SocialRepository : BaseRepository { fun postPrivateMessage(recipientId: String, message: String): Flowable> - fun getGroupMembers(id: String): Flowable> + fun getGroupMembers(id: String): Flow> fun retrieveGroupMembers(id: String, includeAllPublicFields: Boolean): Flowable> fun inviteToGroup(id: String, inviteData: Map): Flowable> diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/SocialRepositoryImpl.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/SocialRepositoryImpl.kt index 8844e41c7..30cbe18c0 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/SocialRepositoryImpl.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/SocialRepositoryImpl.kt @@ -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(localRepository, apiClient, userID), SocialRepository { override fun transferGroupOwnership(groupID: String, userID: String): Flowable { - 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 { - 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 = id?.let { localRepository.getGroupFlowable(it) } ?: Flowable.empty() override fun leaveGroup(id: String?, keepChallenges: Boolean): Flowable { 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 { @@ -256,7 +258,7 @@ class SocialRepositoryImpl( return postPrivateMessage(recipientId, messageObject) } - override fun getGroupMembers(id: String): Flowable> = localRepository.getGroupMembers(id) + override fun getGroupMembers(id: String) = localRepository.getGroupMembers(id) override fun retrieveGroupMembers(id: String, includeAllPublicFields: Boolean): Flowable> { return apiClient.getGroupMembers(id, includeAllPublicFields) diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/SocialLocalRepository.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/SocialLocalRepository.kt index 142f73dcd..bd0cc9518 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/SocialLocalRepository.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/SocialLocalRepository.kt @@ -7,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> @@ -14,14 +15,15 @@ interface SocialLocalRepository : BaseLocalRepository { fun getUserGroups(userID: String, type: String?): Flowable> fun getGroups(type: String): Flowable> - fun getGroup(id: String): Flowable + fun getGroup(id: String): Flow + fun getGroupFlowable(id: String): Flowable fun saveGroup(group: Group) fun getGroupChat(groupId: String): Flowable> fun deleteMessage(id: String) - fun getGroupMembers(partyId: String): Flowable> + fun getGroupMembers(partyId: String): Flow> fun updateRSVPNeeded(user: User?, newValue: Boolean) diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/implementation/RealmSocialLocalRepository.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/implementation/RealmSocialLocalRepository.kt index 452b924dd..cd9d86f98 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/implementation/RealmSocialLocalRepository.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/implementation/RealmSocialLocalRepository.kt @@ -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 { + override fun getGroupFlowable(id: String): Flowable { 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 { + 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> { 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> { - return RxJavaBridge.toV3Flowable( - realm.where(Member::class.java) + override fun getGroupMembers(partyId: String): Flow> { + return realm.where(Member::class.java) .equalTo("party.id", partyId) .findAll() - .asFlowable() + .toFlow() .filter { it.isLoaded } - ) } override fun updateRSVPNeeded(user: User?, newValue: Boolean) { diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/implementation/RealmTaskLocalRepository.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/implementation/RealmTaskLocalRepository.kt index af30ced38..a97d3bb8e 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/implementation/RealmTaskLocalRepository.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/implementation/RealmTaskLocalRepository.kt @@ -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() - .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) { diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/implementation/RealmUserLocalRepository.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/implementation/RealmUserLocalRepository.kt index 37336b9c6..5ff05e306 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/implementation/RealmUserLocalRepository.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/implementation/RealmUserLocalRepository.kt @@ -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() } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/SoundFile.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/SoundFile.kt index ba4073e66..0864cffe4 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/SoundFile.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/SoundFile.kt @@ -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) } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/models/tasks/Task.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/models/tasks/Task.kt index 5274bfb6b..49ac434a6 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/models/tasks/Task.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/models/tasks/Task.kt @@ -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 diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/BaseActivity.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/BaseActivity.kt index 880a9bb96..8eac5a2fc 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/BaseActivity.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/BaseActivity.kt @@ -26,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))) } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/FullProfileActivity.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/FullProfileActivity.kt index 9b786c578..5b7855516 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/FullProfileActivity.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/FullProfileActivity.kt @@ -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) } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/LoginActivity.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/LoginActivity.kt index 97103dbea..813f1ba2d 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/LoginActivity.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/LoginActivity.kt @@ -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( diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/SkillMemberActivity.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/SkillMemberActivity.kt index 2a5cc0372..83dc310d1 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/SkillMemberActivity.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/SkillMemberActivity.kt @@ -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 + } + } } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/customization/AvatarCustomizationFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/customization/AvatarCustomizationFragment.kt index e81079034..412176cc9 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/customization/AvatarCustomizationFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/customization/AvatarCustomizationFragment.kt @@ -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 diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/shops/ShopFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/shops/ShopFragment.kt index 1741c70dd..898acb9f9 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/shops/ShopFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/shops/ShopFragment.kt @@ -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() { @@ -133,18 +136,20 @@ open class ShopFragment : BaseMainFragment() 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) } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/QuestDetailFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/QuestDetailFragment.kt index feab88448..3043f06e3 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/QuestDetailFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/QuestDetailFragment.kt @@ -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() { } .skipWhile { it.isBlank() } .distinctUntilChanged() - .flatMap { socialRepository.getGroup(it) } + .flatMap { socialRepository.getGroupFlowable(it) } .doOnNext { updateParty(it) } .map { it.quest?.key ?: "" diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/TavernDetailFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/TavernDetailFragment.kt index a7f76692d..76697e5fe 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/TavernDetailFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/TavernDetailFragment.kt @@ -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() { @@ -74,7 +74,7 @@ class TavernDetailFragment : BaseFragment() { 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 { diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/challenges/ChallengeListFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/challenges/ChallengeListFragment.kt index a85a6e613..7816db106 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/challenges/ChallengeListFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/challenges/ChallengeListFragment.kt @@ -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() } 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) diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/guilds/GuildDetailFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/guilds/GuildDetailFragment.kt index eb4eb1c11..0439cf78e 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/guilds/GuildDetailFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/guilds/GuildDetailFragment.kt @@ -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() { @@ -49,9 +47,6 @@ class GuildDetailFragment : BaseFragment() { @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) } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/guilds/GuildFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/guilds/GuildFragment.kt index be680a448..b5055d99e 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/guilds/GuildFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/guilds/GuildFragment.kt @@ -57,8 +57,8 @@ class GuildFragment : BaseMainFragment() { 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) diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/tasks/TaskRecyclerViewFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/tasks/TaskRecyclerViewFragment.kt index ede210e6e..45fa41cc9 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/tasks/TaskRecyclerViewFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/tasks/TaskRecyclerViewFragment.kt @@ -144,8 +144,8 @@ open class TaskRecyclerViewFragment : BaseFragment 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 0) { + binding?.recyclerView?.emptyItem = if (viewModel.filterCount(taskType) > 0) { when (this.taskType) { TaskType.HABIT -> { EmptyItem( diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/GroupViewModel.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/GroupViewModel.kt index 31c38a2cd..08e69698c 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/GroupViewModel.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/GroupViewModel.kt @@ -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()) diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/PartyViewModel.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/PartyViewModel.kt index 1e87fc28c..0bca2c7c1 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/PartyViewModel.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/PartyViewModel.kt @@ -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()) ) } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/utils/ChallengeDeserializer.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/utils/ChallengeDeserializer.kt index c37b87b5c..5b567e219 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/utils/ChallengeDeserializer.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/utils/ChallengeDeserializer.kt @@ -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, JsonSerializer() @@ -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 + } } /** diff --git a/fastlane/metadata/android/en-US/full_description.txt b/fastlane/metadata/android/en-US/full_description.txt index 0693e020c..57ae3e75f 100644 --- a/fastlane/metadata/android/en-US/full_description.txt +++ b/fastlane/metadata/android/en-US/full_description.txt @@ -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. \ No newline at end of file +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. \ No newline at end of file diff --git a/version.properties b/version.properties index e6090a7fb..281ddb808 100644 --- a/version.properties +++ b/version.properties @@ -1,2 +1,2 @@ -NAME=4.0 -CODE=4310 \ No newline at end of file +NAME=4.0.1 +CODE=4330 \ No newline at end of file