From 241fbc762a7b72288e6d8354b23683513ce0a761 Mon Sep 17 00:00:00 2001 From: Sven Fulenchek <73049985+svenfulen@users.noreply.github.com> Date: Tue, 30 Aug 2022 22:36:50 -0500 Subject: [PATCH 01/36] Issue #1844 Set the AudioManager to use Music (media) volume instead of Notification volume Habitica Username - svenfulen --- .../java/com/habitrpg/android/habitica/helpers/SoundFile.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/SoundFile.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/SoundFile.kt index 0864cffe4..21b3bc4c9 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 @@ -37,7 +37,7 @@ class SoundFile(val theme: String, private val fileName: String) { player?.setDataSource(file?.path) val attributes = AudioAttributes.Builder() .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) - .setLegacyStreamType(AudioManager.STREAM_NOTIFICATION) + .setLegacyStreamType(AudioManager.STREAM_MUSIC) .build() player?.setAudioAttributes(attributes) player?.prepare() From e43b39aff1108aa7f1cf28c2c617abaf8218bad1 Mon Sep 17 00:00:00 2001 From: booker-lee Date: Sun, 11 Sep 2022 16:14:45 +0800 Subject: [PATCH 02/36] Fix hp display when value is under 1 #1842 The cause of this issue comes from the ValueAnimator. The under 1 value is truncated due to the cast to int in ValueAnimator. Add animation for floating point values (when value is between 0 ~ 1) --- .../java/com/habitrpg/common/habitica/views/ValueBar.kt | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/common/src/main/java/com/habitrpg/common/habitica/views/ValueBar.kt b/common/src/main/java/com/habitrpg/common/habitica/views/ValueBar.kt index 54d70ad78..2e3a4a87b 100644 --- a/common/src/main/java/com/habitrpg/common/habitica/views/ValueBar.kt +++ b/common/src/main/java/com/habitrpg/common/habitica/views/ValueBar.kt @@ -184,11 +184,16 @@ class ValueBar(context: Context, attrs: AttributeSet?) : FrameLayout(context, at if (animationDuration == 0L || binding.valueTextView.text.isEmpty()) { currentValue = value } else { - val animator = ValueAnimator.ofInt(currentValue.toInt(), value.toInt()) + val animator = if (0 < value && value < 1) { + // Show floating points in animation only if the value is between 0 to 1 + ValueAnimator.ofFloat(currentValue.toFloat(), value.toFloat()) + } else { + ValueAnimator.ofInt(currentValue.toInt(), value.toInt()) + } animator.duration = animationDuration animator.startDelay = animationDelay animator.addUpdateListener { - currentValue = (it.animatedValue as Int).toDouble() + currentValue = (it.animatedValue as? Int)?.toDouble() ?: (it.animatedValue as Float).toDouble() updateBar() } animator.start() From 2ee37064407c2fd0f1e9db7f0b8b4357e0faf3a3 Mon Sep 17 00:00:00 2001 From: Hafiz Date: Fri, 16 Sep 2022 01:46:02 -0400 Subject: [PATCH 03/36] Auto stash before checking out "origin/main" --- .../com/habitrpg/android/habitica/models/user/Preferences.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/models/user/Preferences.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/models/user/Preferences.kt index 070cff23e..4113e4ba5 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/models/user/Preferences.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/models/user/Preferences.kt @@ -30,7 +30,7 @@ open class Preferences : RealmObject(), AvatarPreferences, BaseObject { } else { "chair_" + field!! } - } else null + } else "chair_none" } var language: String? = null var sound: String? = null From 891de53a14149801d379e8a021e93d5ff416c86a Mon Sep 17 00:00:00 2001 From: Hafiz Date: Fri, 16 Sep 2022 03:07:47 -0400 Subject: [PATCH 04/36] Update user when changing class --- .../android/habitica/data/implementation/UserRepositoryImpl.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/UserRepositoryImpl.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/UserRepositoryImpl.kt index 66c5c0500..668e54b23 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/UserRepositoryImpl.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/UserRepositoryImpl.kt @@ -161,7 +161,7 @@ class UserRepositoryImpl( override fun disableClasses(): Flowable = apiClient.disableClasses().flatMap { retrieveUser(withTasks = false, forced = true) } override fun changeClass(selectedClass: String): Flowable = apiClient.changeClass(selectedClass) - .flatMap { retrieveUser(false) } + .flatMap { retrieveUser(false, forced = true) } override fun unlockPath(path: String, price: Int): Flowable { return zipWithLiveUser(apiClient.unlockPath(path)) { unlockResponse, copiedUser -> From e87342496e532331697a05b8c237c74f2697d37d Mon Sep 17 00:00:00 2001 From: kouki4416 Date: Sat, 17 Sep 2022 08:55:17 +0900 Subject: [PATCH 05/36] Fix deselecting daily item does not decrement count --- .../android/habitica/data/implementation/TaskRepositoryImpl.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/TaskRepositoryImpl.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/TaskRepositoryImpl.kt index b01a08b23..ac562212d 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/TaskRepositoryImpl.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/TaskRepositoryImpl.kt @@ -146,6 +146,8 @@ class TaskRepositoryImpl( bgTask.completed = up if (TaskType.DAILY == bgTask.type && up) { bgTask.streak = (bgTask.streak ?: 0) + 1 + } else if(TaskType.DAILY == bgTask.type && !up && (bgTask.streak != null && bgTask.streak!! >= 1)){ + bgTask.streak = bgTask.streak!! - 1 } } else if (TaskType.HABIT == bgTask.type) { if (up) { From c41c0b67d8fa623ac5ab72d3a06a2fe2a1d92881 Mon Sep 17 00:00:00 2001 From: Hafiz Date: Sun, 2 Oct 2022 13:22:23 -0400 Subject: [PATCH 06/36] Factor in if adventure guide is shown when dragging item If adventure guide is shown, update position value (decrease newPosition by 1) sent to API --- .../habitica/ui/fragments/tasks/TaskRecyclerViewFragment.kt | 4 ++++ 1 file changed, 4 insertions(+) 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 95c13db11..72bbe35ec 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 @@ -290,6 +290,10 @@ open class TaskRecyclerViewFragment : BaseFragment Date: Wed, 12 Oct 2022 13:08:47 -0400 Subject: [PATCH 07/36] Black screen when launching app first time fix hasTaskData was not being set although task list was set, create Observer to remove after retrieving tasks. --- .../habitica/ui/activities/RYAActivity.kt | 29 ++++++++++++------- .../habitica/ui/viewmodels/RYAViewModel.kt | 2 -- 2 files changed, 18 insertions(+), 13 deletions(-) diff --git a/wearos/src/main/java/com/habitrpg/wearos/habitica/ui/activities/RYAActivity.kt b/wearos/src/main/java/com/habitrpg/wearos/habitica/ui/activities/RYAActivity.kt index 6cef6d48a..1e995e122 100644 --- a/wearos/src/main/java/com/habitrpg/wearos/habitica/ui/activities/RYAActivity.kt +++ b/wearos/src/main/java/com/habitrpg/wearos/habitica/ui/activities/RYAActivity.kt @@ -5,6 +5,7 @@ import android.os.Bundle import android.widget.LinearLayout import androidx.activity.viewModels import androidx.core.view.isVisible +import androidx.lifecycle.Observer import androidx.lifecycle.lifecycleScope import com.habitrpg.android.habitica.R import com.habitrpg.android.habitica.databinding.ActivityRyaBinding @@ -26,15 +27,21 @@ class RYAActivity : BaseActivity() { binding = ActivityRyaBinding.inflate(layoutInflater) super.onCreate(savedInstanceState) - viewModel.tasks.observe(this) { - if (!viewModel.hasTaskData) return@observe - if (it.isEmpty()) { - runCron() - return@observe + viewModel.tasks.observe( + this, + object : Observer> { + override fun onChanged(list: List) { + if (list.isEmpty()) { + runCron() + viewModel.tasks.removeObserver(this) + } else { + binding.scrollView.isVisible = true + createTaskListViews(list) + viewModel.tasks.removeObserver(this) + } + } } - binding.scrollView.isVisible = true - createTaskListViews(it) - } + ) binding.ryaButton.setOnClickListener { binding.titleView.text = getString(R.string.check_off_yesterday) @@ -58,9 +65,9 @@ class RYAActivity : BaseActivity() { if (!viewModel.hasRunCron) { startActivity( Intent(this, RYAActivity::class.java) - .apply { - addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK) - }) + .apply { + addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK) + }) } super.onDestroy() } diff --git a/wearos/src/main/java/com/habitrpg/wearos/habitica/ui/viewmodels/RYAViewModel.kt b/wearos/src/main/java/com/habitrpg/wearos/habitica/ui/viewmodels/RYAViewModel.kt index 2a9e07572..6d4876865 100644 --- a/wearos/src/main/java/com/habitrpg/wearos/habitica/ui/viewmodels/RYAViewModel.kt +++ b/wearos/src/main/java/com/habitrpg/wearos/habitica/ui/viewmodels/RYAViewModel.kt @@ -26,7 +26,6 @@ class RYAViewModel @Inject constructor( ) : BaseViewModel(userRepository, taskRepository, exceptionBuilder, appStateManager) { var hasRunCron: Boolean = false val tasks = MutableLiveData>() - var hasTaskData = false private val tasksToComplete = mutableListOf() @@ -36,7 +35,6 @@ class RYAViewModel @Inject constructor( .map { it.filter { task -> task.isDue == true && !task.completed } } .first() tasks.value = taskList - hasTaskData = true } } From b44e459609d73cebe79301ce25a0438a6d16613d Mon Sep 17 00:00:00 2001 From: Hafiz Date: Fri, 14 Oct 2022 15:18:48 -0400 Subject: [PATCH 08/36] Display tasks if local repository does not initially populate After retrieving challenge tasks, check if tasks are showing (laid out earlier from local repository), and if not use the retrieved challenges to lay out tasks. This should handle any potential race conditions as well. --- .../challenges/ChallengeDetailFragment.kt | 41 +++++++++++-------- 1 file changed, 25 insertions(+), 16 deletions(-) diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/challenges/ChallengeDetailFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/challenges/ChallengeDetailFragment.kt index ca8aeef27..6f0e54624 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/challenges/ChallengeDetailFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/challenges/ChallengeDetailFragment.kt @@ -11,6 +11,7 @@ import android.view.MenuItem import android.view.View import android.view.ViewGroup import androidx.core.net.toUri +import androidx.core.view.isVisible import androidx.lifecycle.lifecycleScope import com.habitrpg.android.habitica.R import com.habitrpg.android.habitica.components.UserComponent @@ -93,6 +94,24 @@ class ChallengeDetailFragment : BaseMainFragment FullProfileActivity.open(leaderID) } + loadTasks() + + binding?.joinButton?.setOnClickListener { + challenge?.let { challenge -> + challengeRepository.joinChallenge(challenge) + .subscribe({ + lifecycleScope.launch(ExceptionHandler.coroutine()) { + userRepository.retrieveUser(true) + } + }, ExceptionHandler.rx()) + } + } + binding?.leaveButton?.setOnClickListener { showChallengeLeaveDialog() } + + refresh() + } + + private fun loadTasks() { challengeID?.let { id -> compositeSubscription.add( challengeRepository.getChallenge(id) @@ -107,7 +126,7 @@ class ChallengeDetailFragment : BaseMainFragment lifecycleScope.launch(ExceptionHandler.coroutine()) { set(socialRepository.retrieveMember(it)) } - }, ExceptionHandler.rx()) + }, ExceptionHandler.rx()) ) compositeSubscription.add( challengeRepository.getChallengeTasks(id).subscribe( @@ -158,20 +177,6 @@ class ChallengeDetailFragment : BaseMainFragment ) ) } - - binding?.joinButton?.setOnClickListener { - challenge?.let { challenge -> - challengeRepository.joinChallenge(challenge) - .subscribe({ - lifecycleScope.launch(ExceptionHandler.coroutine()) { - userRepository.retrieveUser(true) - } - }, ExceptionHandler.rx()) - } - } - binding?.leaveButton?.setOnClickListener { showChallengeLeaveDialog() } - - refresh() } override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { @@ -232,7 +237,11 @@ class ChallengeDetailFragment : BaseMainFragment challengeID?.let { id -> challengeRepository.retrieveChallenge(id) .flatMap { challengeRepository.retrieveChallengeTasks(id) } - .subscribe({ }, { + .subscribe({ taskList -> + if (binding?.taskGroupLayout?.childCount == 0 && taskList.tasks.isNotEmpty()) { + loadTasks() + } + }, { if (it is HttpException && it.code() == 404) { MainNavigationController.navigateBack() } From fbceaec49913da2317528dd33c758228f081ca6b Mon Sep 17 00:00:00 2001 From: kouki4416 Date: Sat, 15 Oct 2022 18:35:00 +0900 Subject: [PATCH 09/36] Changed if statement logic --- .../habitica/data/implementation/TaskRepositoryImpl.kt | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/TaskRepositoryImpl.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/TaskRepositoryImpl.kt index ac562212d..57ff01629 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/TaskRepositoryImpl.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/TaskRepositoryImpl.kt @@ -144,10 +144,12 @@ class TaskRepositoryImpl( bgTask.value = (bgTask.value - localDelta) + res.delta if (TaskType.DAILY == bgTask.type || TaskType.TODO == bgTask.type) { bgTask.completed = up - if (TaskType.DAILY == bgTask.type && up) { - bgTask.streak = (bgTask.streak ?: 0) + 1 - } else if(TaskType.DAILY == bgTask.type && !up && (bgTask.streak != null && bgTask.streak!! >= 1)){ - bgTask.streak = bgTask.streak!! - 1 + if(TaskType.DAILY == bgTask.type){ + if(up){ + bgTask.streak = (bgTask.streak ?: 0) + 1 + } else { + bgTask.streak = (bgTask.streak ?: 0) - 1 + } } } else if (TaskType.HABIT == bgTask.type) { if (up) { From 3ef4ea283e1996c8b406fec323b4bcbcb2032dbc Mon Sep 17 00:00:00 2001 From: kouki4416 Date: Sat, 15 Oct 2022 18:37:07 +0900 Subject: [PATCH 10/36] Code format --- .../habitica/data/implementation/TaskRepositoryImpl.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/TaskRepositoryImpl.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/TaskRepositoryImpl.kt index 57ff01629..ea832952e 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/TaskRepositoryImpl.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/TaskRepositoryImpl.kt @@ -144,8 +144,8 @@ class TaskRepositoryImpl( bgTask.value = (bgTask.value - localDelta) + res.delta if (TaskType.DAILY == bgTask.type || TaskType.TODO == bgTask.type) { bgTask.completed = up - if(TaskType.DAILY == bgTask.type){ - if(up){ + if (TaskType.DAILY == bgTask.type) { + if (up) { bgTask.streak = (bgTask.streak ?: 0) + 1 } else { bgTask.streak = (bgTask.streak ?: 0) - 1 From 3c3d1debc7acafe4da76ab7b3df13e0e084a825b Mon Sep 17 00:00:00 2001 From: Hafiz Date: Mon, 24 Oct 2022 01:40:46 -0400 Subject: [PATCH 11/36] Fix Task names appearing on incorrect task Removed extra unnecessary filter() onResume causing incorrect task placement --- .../habitica/ui/fragments/tasks/TaskRecyclerViewFragment.kt | 1 - 1 file changed, 1 deletion(-) 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 6cf2c14f1..42c17ed82 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 @@ -485,7 +485,6 @@ open class TaskRecyclerViewFragment : BaseFragment Date: Wed, 26 Oct 2022 16:25:31 -0400 Subject: [PATCH 12/36] Check customizationSet for month --- .../inventory/customization/AvatarCustomizationFragment.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 e0f464b6b..46aeedfca 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 @@ -227,7 +227,7 @@ class AvatarCustomizationFragment : ): Boolean { return if (filter.onlyPurchased && ownedCustomizations.find { it.key == customization.identifier } == null) { true - } else filter.months.isNotEmpty() && !filter.months.contains(customization.customizationSetName?.substringAfter('.')) + } else filter.months.isNotEmpty() && !filter.months.contains(customization.customizationSet?.substringAfter('.')) } fun updateUser(user: User?) { From 919ac41c299f14fa263886456a101553c6fa996f Mon Sep 17 00:00:00 2001 From: Phillip Thelen Date: Tue, 4 Oct 2022 11:46:27 +0200 Subject: [PATCH 13/36] Fix crash --- .../com/habitrpg/android/habitica/models/tasks/Task.kt | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) 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 a5e393167..c9788bd3a 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 @@ -92,8 +92,11 @@ open class Task : RealmObject, BaseMainObject, Parcelable, BaseTask { var nextDue: RealmList? = null var updatedAt: Date? = null val isUpdatedToday: Boolean - get() = ZonedDateTime.ofInstant(updatedAt?.toInstant(), ZoneId.systemDefault()).toLocalDate() - .equals(ZonedDateTime.now().withZoneSameLocal(ZoneId.systemDefault()).toLocalDate()) + get() { + val updatedAt = updatedAt ?: return false + return ZonedDateTime.ofInstant(updatedAt.toInstant(), ZoneId.systemDefault()).toLocalDate() + .equals(ZonedDateTime.now().withZoneSameLocal(ZoneId.systemDefault()).toLocalDate()) + } // Needed for offline creating/updating From b9dff19cdb27d143334634265fc636b4d188bd92 Mon Sep 17 00:00:00 2001 From: Phillip Thelen Date: Tue, 4 Oct 2022 11:46:34 +0200 Subject: [PATCH 14/36] Fix issues wih notifications --- .../helpers/notifications/HabiticaLocalNotification.kt | 2 ++ .../notifications/HabiticaLocalNotificationFactory.kt | 3 --- .../android/habitica/receivers/NotificationPublisher.kt | 5 +---- .../com/habitrpg/android/habitica/receivers/TaskReceiver.kt | 4 +++- 4 files changed, 6 insertions(+), 8 deletions(-) diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/notifications/HabiticaLocalNotification.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/notifications/HabiticaLocalNotification.kt index ec2a1c035..91cb2c279 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/notifications/HabiticaLocalNotification.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/notifications/HabiticaLocalNotification.kt @@ -7,6 +7,7 @@ import android.media.RingtoneManager import androidx.annotation.CallSuper import androidx.core.app.NotificationCompat import androidx.core.app.NotificationManagerCompat +import androidx.core.content.ContextCompat import com.habitrpg.android.habitica.R import com.habitrpg.android.habitica.extensions.withImmutableFlag import com.habitrpg.android.habitica.ui.activities.MainActivity @@ -32,6 +33,7 @@ abstract class HabiticaLocalNotification( val path = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION) return notificationBuilder .setSound(path) + .setColor(ContextCompat.getColor(context, R.color.brand_300)) } @CallSuper diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/notifications/HabiticaLocalNotificationFactory.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/notifications/HabiticaLocalNotificationFactory.kt index a71cfb522..f45e3e122 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/notifications/HabiticaLocalNotificationFactory.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/notifications/HabiticaLocalNotificationFactory.kt @@ -2,9 +2,6 @@ package com.habitrpg.android.habitica.helpers.notifications import android.content.Context -/** - * Created by keithholliday on 6/28/16. - */ class HabiticaLocalNotificationFactory { // use getShape method to get object of type shape fun build(notificationType: String?, context: Context?): HabiticaLocalNotification { diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/receivers/NotificationPublisher.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/receivers/NotificationPublisher.kt index d7071d703..dd0dac59a 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/receivers/NotificationPublisher.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/receivers/NotificationPublisher.kt @@ -27,7 +27,6 @@ import java.util.Date import java.util.Random import javax.inject.Inject -@Suppress("DEPRECATION") // https://gist.github.com/BrandonSmith/6679223 class NotificationPublisher : BroadcastReceiver() { @@ -145,8 +144,7 @@ class NotificationPublisher : BroadcastReceiver() { private fun getRandomDailyTip(): String { val thisContext = context ?: return "" - val index = Random().nextInt(4) - return when (index) { + return when (Random().nextInt(10)) { 0 -> thisContext.getString(R.string.daily_tip_0) 1 -> thisContext.getString(R.string.daily_tip_1) 2 -> thisContext.getString(R.string.daily_tip_2) @@ -162,7 +160,6 @@ class NotificationPublisher : BroadcastReceiver() { } companion object { - var NOTIFICATION_ID = "notification-id" var CHECK_DAILIES = "check-dailies" } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/receivers/TaskReceiver.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/receivers/TaskReceiver.kt index 1cb1c9589..839925764 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/receivers/TaskReceiver.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/receivers/TaskReceiver.kt @@ -9,6 +9,7 @@ import android.media.RingtoneManager import android.os.Build import androidx.core.app.NotificationCompat import androidx.core.app.NotificationManagerCompat +import androidx.core.content.ContextCompat import com.habitrpg.android.habitica.HabiticaBaseApplication import com.habitrpg.android.habitica.R import com.habitrpg.android.habitica.data.TaskRepository @@ -16,10 +17,10 @@ import com.habitrpg.android.habitica.extensions.withImmutableFlag import com.habitrpg.android.habitica.helpers.ExceptionHandler import com.habitrpg.android.habitica.helpers.TaskAlarmManager import com.habitrpg.android.habitica.models.tasks.Task -import com.habitrpg.shared.habitica.models.tasks.TaskType import com.habitrpg.android.habitica.ui.activities.MainActivity import com.habitrpg.shared.habitica.HLogger import com.habitrpg.shared.habitica.LogLevel +import com.habitrpg.shared.habitica.models.tasks.TaskType import io.reactivex.rxjava3.functions.Consumer import javax.inject.Inject @@ -67,6 +68,7 @@ class TaskReceiver : BroadcastReceiver() { var notificationBuilder = NotificationCompat.Builder(context, "default") .setSmallIcon(R.drawable.ic_gryphon_white) + .setColor(ContextCompat.getColor(context, R.color.brand_300)) .setContentTitle(task.text) .setStyle( NotificationCompat.BigTextStyle() From af964643aae2804fafea0614cec4a54f737eebf3 Mon Sep 17 00:00:00 2001 From: Phillip Thelen Date: Tue, 4 Oct 2022 18:43:16 +0200 Subject: [PATCH 15/36] notification improvements # Conflicts: # Habitica/src/main/java/com/habitrpg/android/habitica/helpers/notifications/HabiticaFirebaseMessagingService.kt # version.properties --- Habitica/AndroidManifest.xml | 6 +++++ .../notifications/PushNotificationManager.kt | 25 ++++++++----------- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/Habitica/AndroidManifest.xml b/Habitica/AndroidManifest.xml index a7bbc8380..447f79bdd 100644 --- a/Habitica/AndroidManifest.xml +++ b/Habitica/AndroidManifest.xml @@ -257,6 +257,12 @@ + + key = "preference_push_invited_to_party" - type.contains(RECEIVED_PRIVATE_MESSAGE_PUSH_NOTIFICATION_KEY) -> key = "preference_push_received_a_private_message" - type.contains(RECEIVED_GEMS_PUSH_NOTIFICATION_KEY) -> key = "preference_push_gifted_gems" - type.contains(RECEIVED_SUBSCRIPTION_GIFT_PUSH_NOTIFICATION_KEY) -> key = "preference_push_gifted_subscription" - type.contains(GUILD_INVITE_PUSH_NOTIFICATION_KEY) -> key = "preference_push_invited_to_guild" - type.contains(QUEST_INVITE_PUSH_NOTIFICATION_KEY) -> key = "preference_push_invited_to_quest" - type.contains(QUEST_BEGUN_PUSH_NOTIFICATION_KEY) -> key = "preference_push_your_quest_has_begun" - type.contains(WON_CHALLENGE_PUSH_NOTIFICATION_KEY) -> key = "preference_push_you_won_challenge" + val key = when { + type == PARTY_INVITE_PUSH_NOTIFICATION_KEY -> "preference_push_invited_to_party" + type?.contains(RECEIVED_PRIVATE_MESSAGE_PUSH_NOTIFICATION_KEY) == true -> "preference_push_received_a_private_message" + type?.contains(RECEIVED_GEMS_PUSH_NOTIFICATION_KEY) == true -> "preference_push_gifted_gems" + type?.contains(RECEIVED_SUBSCRIPTION_GIFT_PUSH_NOTIFICATION_KEY) == true -> "preference_push_gifted_subscription" + type?.contains(GUILD_INVITE_PUSH_NOTIFICATION_KEY) == true -> "preference_push_invited_to_guild" + type?.contains(QUEST_INVITE_PUSH_NOTIFICATION_KEY) == true -> "preference_push_invited_to_quest" + type?.contains(QUEST_BEGUN_PUSH_NOTIFICATION_KEY) == true -> "preference_push_your_quest_has_begun" + type?.contains(WON_CHALLENGE_PUSH_NOTIFICATION_KEY) == true -> "preference_push_you_won_challenge" + else -> return true } return sharedPreferences.getBoolean(key, true) From adfe3ac2805553fdea18b66e5b573b99779afb5c Mon Sep 17 00:00:00 2001 From: Phillip Thelen Date: Wed, 5 Oct 2022 09:21:21 +0200 Subject: [PATCH 16/36] fix setting emails and push notifications # Conflicts: # version.properties --- .../fragments/preferences/PreferencesFragment.kt | 16 ++++++++-------- build.gradle | 1 - 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/PreferencesFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/PreferencesFragment.kt index f63bf8362..035b054f5 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/PreferencesFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/PreferencesFragment.kt @@ -205,19 +205,19 @@ class PreferencesFragment : BasePreferencesFragment(), SharedPreferences.OnShare TaskAlarmManager.scheduleDailyReminder(context) } "usePushNotifications" -> { - val userPushNotifications = sharedPreferences.getBoolean(key, false) - pushNotificationsPreference?.isEnabled = userPushNotifications - userRepository.updateUser("preferences.pushNotifications.unsubscribeFromAll", userPushNotifications).subscribe() - if (userPushNotifications) { + val usePushNotifications = sharedPreferences.getBoolean(key, true) + pushNotificationsPreference?.isEnabled = usePushNotifications + userRepository.updateUser("preferences.pushNotifications.unsubscribeFromAll", !usePushNotifications).subscribe() + if (usePushNotifications) { pushNotificationManager.addPushDeviceUsingStoredToken() } else { pushNotificationManager.removePushDeviceUsingStoredToken() } } "useEmails" -> { - val useEmailNotifications = sharedPreferences.getBoolean(key, false) + val useEmailNotifications = sharedPreferences.getBoolean(key, true) emailNotificationsPreference?.isEnabled = useEmailNotifications - userRepository.updateUser("preferences.emailNotifications.unsubscribeFromAll", useEmailNotifications).subscribe() + userRepository.updateUser("preferences.emailNotifications.unsubscribeFromAll", !useEmailNotifications).subscribe() } "cds_time" -> { val timeval = sharedPreferences.getString("cds_time", "0") ?: "0" @@ -356,13 +356,13 @@ class PreferencesFragment : BasePreferencesFragment(), SharedPreferences.OnShare val usePushPreference = findPreference("usePushNotifications") as? CheckBoxPreference pushNotificationsPreference = findPreference("pushNotifications") as? PreferenceScreen - val usePushNotifications = user?.preferences?.pushNotifications?.unsubscribeFromAll ?: false + val usePushNotifications = !(user?.preferences?.pushNotifications?.unsubscribeFromAll ?: false) pushNotificationsPreference?.isEnabled = usePushNotifications usePushPreference?.isChecked = usePushNotifications val useEmailPreference = findPreference("useEmails") as? CheckBoxPreference emailNotificationsPreference = findPreference("emailNotifications") as? PreferenceScreen - val useEmailNotifications = user?.preferences?.emailNotifications?.unsubscribeFromAll ?: false + val useEmailNotifications = !(user?.preferences?.emailNotifications?.unsubscribeFromAll ?: false) emailNotificationsPreference?.isEnabled = useEmailNotifications useEmailPreference?.isChecked = useEmailNotifications diff --git a/build.gradle b/build.gradle index 9e0ea00e0..41ced8bc4 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,6 @@ buildscript { app_version_name = '' app_version_code = 0 - amplitude_version = '3.35.1' appcompat_version = '1.5.1' coil_version = '2.1.0' From 8fe9de12232e91745821b7a4e25fceb2c2148e30 Mon Sep 17 00:00:00 2001 From: Phillip Thelen Date: Thu, 6 Oct 2022 10:55:13 +0200 Subject: [PATCH 17/36] Improve push notification handling # Conflicts: # Habitica/src/main/java/com/habitrpg/android/habitica/helpers/notifications/HabiticaFirebaseMessagingService.kt # Habitica/src/main/java/com/habitrpg/android/habitica/helpers/notifications/PushNotificationManager.kt # version.properties --- .../HabiticaFirebaseMessagingService.kt | 13 ++-- .../notifications/PushNotificationManager.kt | 78 ++++++++++--------- 2 files changed, 48 insertions(+), 43 deletions(-) diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/notifications/HabiticaFirebaseMessagingService.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/notifications/HabiticaFirebaseMessagingService.kt index 99ea9d490..32cc85184 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/notifications/HabiticaFirebaseMessagingService.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/notifications/HabiticaFirebaseMessagingService.kt @@ -16,12 +16,15 @@ class HabiticaFirebaseMessagingService : FirebaseMessagingService() { internal lateinit var pushNotificationManager: PushNotificationManager override fun onMessageReceived(remoteMessage: RemoteMessage) { - userComponent?.inject(this) - if (this::pushNotificationManager.isInitialized) { - pushNotificationManager.displayNotification(remoteMessage) + try { + userComponent?.inject(this) + } catch (_: java.lang.IllegalStateException) { + } + PushNotificationManager.displayNotification(remoteMessage, applicationContext) - if (remoteMessage.data["identifier"]?.contains(PushNotificationManager.WON_CHALLENGE_PUSH_NOTIFICATION_KEY) == true) { - // userRepository.retrieveUser(true).subscribe({}, ExceptionHandler.rx()) + if (remoteMessage.data["identifier"]?.contains(PushNotificationManager.WON_CHALLENGE_PUSH_NOTIFICATION_KEY) == true) { + if (this::userRepository.isInitialized) { + // userRepository.retrieveUser(true).subscribe({}, RxErrorHandler.handleEmptyError()) } } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/notifications/PushNotificationManager.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/notifications/PushNotificationManager.kt index 05d8a5c94..8fac75a74 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/notifications/PushNotificationManager.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/notifications/PushNotificationManager.kt @@ -34,24 +34,28 @@ class PushNotificationManager( } fun addPushDeviceUsingStoredToken() { - FirebaseMessaging.getInstance().token.addOnCompleteListener { - if (!it.isSuccessful) { - return@addOnCompleteListener + if (refreshedToken.isNotBlank()) { + addRefreshToken() + } else { + FirebaseMessaging.getInstance().token.addOnCompleteListener { + refreshedToken = it.result + addRefreshToken() } - this.refreshedToken = it.result - if (this.refreshedToken.isEmpty() || this.user == null || this.userHasPushDevice() || !this.userIsSubscribedToNotifications()) { - return@addOnCompleteListener - } - - val pushDeviceData = HashMap() - pushDeviceData["regId"] = this.refreshedToken - pushDeviceData["type"] = "android" - apiClient.addPushDevice(pushDeviceData).subscribe({ }, ExceptionHandler.rx()) } } + private fun addRefreshToken() { + if (this.refreshedToken.isEmpty() || this.user == null || this.userHasPushDevice()) { + return + } + val pushDeviceData = HashMap() + pushDeviceData["regId"] = this.refreshedToken + pushDeviceData["type"] = "android" + apiClient.addPushDevice(pushDeviceData).subscribe({ }, ExceptionHandler.rx()) + } + fun removePushDeviceUsingStoredToken() { - if (this.refreshedToken.isEmpty()) { + if (this.refreshedToken.isEmpty() || !userHasPushDevice()) { return } apiClient.deletePushDevice(this.refreshedToken).subscribe({ }, ExceptionHandler.rx()) @@ -66,31 +70,6 @@ class PushNotificationManager( return this.user?.pushDevices == null } - fun displayNotification(remoteMessage: RemoteMessage) { - val remoteMessageIdentifier = remoteMessage.data["identifier"] - - val notificationFactory = HabiticaLocalNotificationFactory() - val notification = notificationFactory.build(remoteMessageIdentifier, context) - if (userIsSubscribedToNotificationType(remoteMessageIdentifier)) { - if (remoteMessage.data.containsKey("sendAnalytics")) { - val additionalData = HashMap() - additionalData["identifier"] = remoteMessageIdentifier ?: "" - AmplitudeManager.sendEvent( - "receive notification", - AmplitudeManager.EVENT_CATEGORY_BEHAVIOUR, - AmplitudeManager.EVENT_HITTYPE_EVENT, - additionalData - ) - } - notification.setExtras(remoteMessage.data) - notification.notifyLocally(remoteMessage.data["title"], remoteMessage.data["body"], remoteMessage.data) - } - } - - private fun userIsSubscribedToNotifications(): Boolean { - return sharedPreferences.getBoolean("pushNotifications", true) - } - private fun userIsSubscribedToNotificationType(type: String?): Boolean { val key = when { type == PARTY_INVITE_PUSH_NOTIFICATION_KEY -> "preference_push_invited_to_party" @@ -122,5 +101,28 @@ class PushNotificationManager( const val GROUP_ACTIVITY_NOTIFICATION_KEY = "groupActivity" const val G1G1_PROMO_KEY = "g1g1Promo" private const val DEVICE_TOKEN_PREFERENCE_KEY = "device-token-preference" + + fun displayNotification(remoteMessage: RemoteMessage, context: Context, pushNotificationManager: PushNotificationManager? = null) { + val remoteMessageIdentifier = remoteMessage.data["identifier"] + + val notificationFactory = HabiticaLocalNotificationFactory() + val notification = notificationFactory.build(remoteMessageIdentifier, + context + ) + if (pushNotificationManager?.userIsSubscribedToNotificationType(remoteMessageIdentifier) != false) { + if (remoteMessage.data.containsKey("sendAnalytics")) { + val additionalData = HashMap() + additionalData["identifier"] = remoteMessageIdentifier ?: "" + AmplitudeManager.sendEvent( + "receive notification", + AmplitudeManager.EVENT_CATEGORY_BEHAVIOUR, + AmplitudeManager.EVENT_HITTYPE_EVENT, + additionalData + ) + } + notification.setExtras(remoteMessage.data) + notification.notifyLocally(remoteMessage.data["title"], remoteMessage.data["body"], remoteMessage.data) + } + } } } From 5ae7f9bbb7faf96033be97695a612c4a0d28c4cc Mon Sep 17 00:00:00 2001 From: Phillip Thelen Date: Fri, 7 Oct 2022 13:22:56 +0200 Subject: [PATCH 18/36] Merge avatar and equipment screens --- Gemfile.lock | 2 +- Habitica/build.gradle | 12 +- Habitica/res/layout/fragment_compose.xml | 4 + .../res/layout/fragment_compose_scrolling.xml | 10 + Habitica/res/values/strings.sidebar.xml | 1 + .../HabiticaFirebaseMessagingService.kt | 4 +- .../habitica/ui/activities/MainActivity.kt | 3 +- .../ui/fragments/NavigationDrawerFragment.kt | 6 +- .../customization/AvatarEquipmentFragment.kt | 158 ------------- .../customization/AvatarOverviewFragment.kt | 187 +++++++++------ .../equipment/EquipmentOverviewFragment.kt | 81 ------- .../habitica/ui/theme/HabiticaTheme.kt | 111 +++++++++ .../ui/views/EquipmentOverviewView.kt | 217 ++++++++++++++++++ .../android/habitica/ui/views/PixelArtView.kt | 28 +++ build.gradle | 2 +- 15 files changed, 506 insertions(+), 320 deletions(-) create mode 100644 Habitica/res/layout/fragment_compose.xml create mode 100644 Habitica/res/layout/fragment_compose_scrolling.xml delete mode 100644 Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/customization/AvatarEquipmentFragment.kt delete mode 100644 Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/equipment/EquipmentOverviewFragment.kt create mode 100644 Habitica/src/main/java/com/habitrpg/android/habitica/ui/theme/HabiticaTheme.kt create mode 100644 Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/EquipmentOverviewView.kt create mode 100644 Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/PixelArtView.kt diff --git a/Gemfile.lock b/Gemfile.lock index 7ad7fbb7d..cbf714ee3 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -83,7 +83,7 @@ GEM xcpretty-travis-formatter (>= 0.0.3) fastlane-plugin-properties (1.1.2) java-properties - fastlane-plugin-semantic_release (1.14.1) + fastlane-plugin-semantic_release (1.18.0) fastlane-plugin-versioning_android (0.1.0) gh_inspector (1.1.3) google-api-client (0.38.0) diff --git a/Habitica/build.gradle b/Habitica/build.gradle index 5ce8fdf5a..c9e3bc90e 100644 --- a/Habitica/build.gradle +++ b/Habitica/build.gradle @@ -79,7 +79,7 @@ dependencies { androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' androidTestImplementation 'androidx.test:runner:1.4.0' androidTestImplementation 'androidx.test:rules:1.4.0' - debugImplementation 'androidx.fragment:fragment-testing:1.5.2' + debugImplementation 'androidx.fragment:fragment-testing:1.5.3' androidTestImplementation 'androidx.test.ext:junit:1.1.3' androidTestImplementation 'androidx.test:core-ktx:1.4.0' androidTestImplementation 'androidx.test.ext:junit-ktx:1.1.3' @@ -109,18 +109,18 @@ dependencies { implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version" implementation "androidx.navigation:navigation-fragment-ktx:$navigation_version" implementation "androidx.navigation:navigation-ui-ktx:$navigation_version" - implementation "androidx.fragment:fragment-ktx:1.5.2" + implementation "androidx.fragment:fragment-ktx:1.5.3" implementation "androidx.paging:paging-runtime-ktx:3.1.1" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version" - implementation "com.google.android.material:compose-theme-adapter:1.1.18" + implementation "com.google.android.material:compose-theme-adapter:1.1.19" - implementation 'androidx.activity:activity-compose:1.5.1' + implementation 'androidx.activity:activity-compose:1.6.0' implementation "androidx.compose.runtime:runtime-livedata:$compose_version" implementation "androidx.compose.material:material:$compose_version" implementation "androidx.compose.animation:animation:$compose_version" implementation "androidx.compose.ui:ui-tooling:$compose_version" - implementation 'androidx.lifecycle:lifecycle-viewmodel-compose:2.5.1' + implementation "androidx.lifecycle:lifecycle-viewmodel-compose:$lifecycle_version" implementation 'com.willowtreeapps:signinwithapplebutton:0.3' @@ -172,7 +172,7 @@ android { } composeOptions { - kotlinCompilerExtensionVersion = "1.3.1" + kotlinCompilerExtensionVersion = "1.3.2" } signingConfigs { diff --git a/Habitica/res/layout/fragment_compose.xml b/Habitica/res/layout/fragment_compose.xml new file mode 100644 index 000000000..3896c2c2f --- /dev/null +++ b/Habitica/res/layout/fragment_compose.xml @@ -0,0 +1,4 @@ + + \ No newline at end of file diff --git a/Habitica/res/layout/fragment_compose_scrolling.xml b/Habitica/res/layout/fragment_compose_scrolling.xml new file mode 100644 index 000000000..2418f8be3 --- /dev/null +++ b/Habitica/res/layout/fragment_compose_scrolling.xml @@ -0,0 +1,10 @@ + + + + diff --git a/Habitica/res/values/strings.sidebar.xml b/Habitica/res/values/strings.sidebar.xml index e6265a982..0f4224a3d 100644 --- a/Habitica/res/values/strings.sidebar.xml +++ b/Habitica/res/values/strings.sidebar.xml @@ -12,6 +12,7 @@ Challenges Inventory Avatar Customization + Avatar & Equipment Equipment Pets & Mounts News diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/notifications/HabiticaFirebaseMessagingService.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/notifications/HabiticaFirebaseMessagingService.kt index 32cc85184..93709ac1d 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/notifications/HabiticaFirebaseMessagingService.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/notifications/HabiticaFirebaseMessagingService.kt @@ -23,9 +23,9 @@ class HabiticaFirebaseMessagingService : FirebaseMessagingService() { PushNotificationManager.displayNotification(remoteMessage, applicationContext) if (remoteMessage.data["identifier"]?.contains(PushNotificationManager.WON_CHALLENGE_PUSH_NOTIFICATION_KEY) == true) { - if (this::userRepository.isInitialized) { + // if (this::userRepository.isInitialized) { // userRepository.retrieveUser(true).subscribe({}, RxErrorHandler.handleEmptyError()) - } + // } } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/MainActivity.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/MainActivity.kt index 3b48f29cf..b39b10e16 100755 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/MainActivity.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/MainActivity.kt @@ -49,6 +49,7 @@ import com.habitrpg.android.habitica.models.user.User import com.habitrpg.android.habitica.models.user.UserQuestStatus import com.habitrpg.android.habitica.ui.TutorialView import com.habitrpg.android.habitica.ui.fragments.NavigationDrawerFragment +import com.habitrpg.android.habitica.ui.theme.HabiticaTheme import com.habitrpg.android.habitica.ui.viewmodels.MainActivityViewModel import com.habitrpg.android.habitica.ui.viewmodels.NotificationsViewModel import com.habitrpg.android.habitica.ui.views.AppHeaderView @@ -212,7 +213,7 @@ open class MainActivity : BaseActivity(), SnackbarActivity { setupBottomnavigationLayoutListener() binding.content.headerView.setContent { - MdcTheme(setTextColors = true) { + HabiticaTheme { AppHeaderView(viewModel.userViewModel) } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/NavigationDrawerFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/NavigationDrawerFragment.kt index c8b3792a7..c370de647 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/NavigationDrawerFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/NavigationDrawerFragment.kt @@ -29,11 +29,10 @@ import com.habitrpg.android.habitica.databinding.DrawerMainBinding import com.habitrpg.android.habitica.extensions.getMinuteOrSeconds import com.habitrpg.android.habitica.extensions.getRemainingString import com.habitrpg.android.habitica.extensions.getShortRemainingString -import com.habitrpg.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.ExceptionHandler +import com.habitrpg.android.habitica.helpers.MainNavigationController import com.habitrpg.android.habitica.models.WorldStateEvent import com.habitrpg.android.habitica.models.inventory.Item import com.habitrpg.android.habitica.models.promotions.HabiticaPromotion @@ -368,10 +367,9 @@ class NavigationDrawerFragment : DialogFragment() { items.add(HabiticaDrawerItem(R.id.timeTravelersShopFragment, SIDEBAR_SHOPS_TIMETRAVEL, context.getString(R.string.timeTravelers))) items.add(HabiticaDrawerItem(0, SIDEBAR_INVENTORY, context.getString(R.string.sidebar_section_inventory), true)) + items.add(HabiticaDrawerItem(R.id.avatarOverviewFragment, SIDEBAR_AVATAR, context.getString(R.string.sidebar_avatar_equipment))) items.add(HabiticaDrawerItem(R.id.itemsFragment, SIDEBAR_ITEMS, context.getString(R.string.sidebar_items))) - items.add(HabiticaDrawerItem(R.id.equipmentOverviewFragment, SIDEBAR_EQUIPMENT, context.getString(R.string.sidebar_equipment))) items.add(HabiticaDrawerItem(R.id.stableFragment, SIDEBAR_STABLE, context.getString(R.string.sidebar_stable))) - items.add(HabiticaDrawerItem(R.id.avatarOverviewFragment, SIDEBAR_AVATAR, context.getString(R.string.sidebar_avatar))) items.add(HabiticaDrawerItem(R.id.gemPurchaseActivity, SIDEBAR_GEMS, context.getString(R.string.sidebar_gems))) items.add(HabiticaDrawerItem(R.id.subscriptionPurchaseActivity, SIDEBAR_SUBSCRIPTION, context.getString(R.string.sidebar_subscription))) diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/customization/AvatarEquipmentFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/customization/AvatarEquipmentFragment.kt deleted file mode 100644 index 21438f469..000000000 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/customization/AvatarEquipmentFragment.kt +++ /dev/null @@ -1,158 +0,0 @@ -package com.habitrpg.android.habitica.ui.fragments.inventory.customization - -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.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 -import com.habitrpg.android.habitica.ui.fragments.BaseMainFragment -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 : - BaseMainFragment(), - SwipeRefreshLayout.OnRefreshListener { - - @Inject - lateinit var inventoryRepository: InventoryRepository - @Inject - lateinit var userViewModel: MainUserViewModel - - override var binding: FragmentRefreshRecyclerviewBinding? = null - - override fun createBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentRefreshRecyclerviewBinding { - return FragmentRefreshRecyclerviewBinding.inflate(inflater, container, false) - } - - var type: String? = null - var category: String? = null - private var activeEquipment: String? = null - - internal var adapter: CustomizationEquipmentRecyclerViewAdapter = CustomizationEquipmentRecyclerViewAdapter() - internal var layoutManager: GridLayoutManager = GridLayoutManager(activity, 2) - - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ): View? { - showsBackButton = true - compositeSubscription.add( - adapter.getSelectCustomizationEvents() - .flatMap { equipment -> - 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({ }, ExceptionHandler.rx()) - ) - compositeSubscription.add( - adapter.getUnlockCustomizationEvents() - .flatMap { - Flowable.empty() - } - .subscribe({ }, ExceptionHandler.rx()) - ) - return super.onCreateView(inflater, container, savedInstanceState) - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - showsBackButton = true - super.onViewCreated(view, savedInstanceState) - arguments?.let { - val args = AvatarEquipmentFragmentArgs.fromBundle(it) - type = args.type - if (args.category.isNotEmpty()) { - category = args.category - } - } - binding?.refreshLayout?.setOnRefreshListener(this) - setGridSpanCount(view.width) - val layoutManager = GridLayoutManager(activity, 4) - layoutManager.spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() { - override fun getSpanSize(position: Int): Int { - return if (adapter.getItemViewType(position) == 0) { - layoutManager.spanCount - } else { - 1 - } - } - } - binding?.recyclerView?.layoutManager = layoutManager - binding?.recyclerView?.addItemDecoration(MarginDecoration(context)) - - binding?.recyclerView?.adapter = adapter - binding?.recyclerView?.itemAnimator = SafeDefaultItemAnimator() - this.loadEquipment() - - userViewModel.user.observe(viewLifecycleOwner) { updateUser(it) } - } - - override fun injectFragment(component: UserComponent) { - component.inject(this) - } - - private fun loadEquipment() { - val type = this.type ?: return - compositeSubscription.add( - inventoryRepository.getEquipmentType(type, category ?: "").subscribe( - { - adapter.setEquipment(it) - }, - ExceptionHandler.rx() - ) - ) - } - - private fun setGridSpanCount(width: Int) { - val itemWidth = context?.resources?.getDimension(R.dimen.customization_width) ?: 0F - var spanCount = (width / itemWidth).toInt() - if (spanCount == 0) { - spanCount = 1 - } - layoutManager.spanCount = spanCount - } - - fun updateUser(user: User?) { - this.updateActiveCustomization(user) - this.adapter.gemBalance = user?.gemCount ?: 0 - adapter.notifyDataSetChanged() - } - - private fun updateActiveCustomization(user: User?) { - if (this.type == null || user?.preferences == null) { - return - } - val outfit = if (user.preferences?.costume == true) user.items?.gear?.costume else user.items?.gear?.equipped - val activeEquipment = when (this.type) { - "headAccessory" -> outfit?.headAccessory - "back" -> outfit?.back - "eyewear" -> outfit?.eyeWear - else -> "" - } - if (activeEquipment != null) { - this.activeEquipment = activeEquipment - this.adapter.activeEquipment = activeEquipment - } - } - - override fun onRefresh() { - lifecycleScope.launch(ExceptionHandler.coroutine()) { - userRepository.retrieveUser(true, true) - binding?.refreshLayout?.isRefreshing = false - } - } -} diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/customization/AvatarOverviewFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/customization/AvatarOverviewFragment.kt index f40390f42..a5b74ab6e 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/customization/AvatarOverviewFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/customization/AvatarOverviewFragment.kt @@ -5,46 +5,68 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.AdapterView +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.padding +import androidx.compose.material.Switch +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.livedata.observeAsState +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.ViewCompositionStrategy +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import com.habitrpg.android.habitica.R 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.databinding.FragmentComposeScrollingBinding import com.habitrpg.android.habitica.helpers.ExceptionHandler -import com.habitrpg.android.habitica.models.user.User +import com.habitrpg.android.habitica.helpers.MainNavigationController import com.habitrpg.android.habitica.ui.fragments.BaseMainFragment +import com.habitrpg.android.habitica.ui.fragments.inventory.equipment.EquipmentOverviewFragmentDirections +import com.habitrpg.android.habitica.ui.theme.HabiticaTheme import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel +import com.habitrpg.android.habitica.ui.views.AvatarCustomizationOverviewView +import com.habitrpg.android.habitica.ui.views.EquipmentOverviewView import javax.inject.Inject -class AvatarOverviewFragment : BaseMainFragment(), AdapterView.OnItemSelectedListener { +class AvatarOverviewFragment : BaseMainFragment(), + AdapterView.OnItemSelectedListener { @Inject lateinit var userViewModel: MainUserViewModel - override var binding: FragmentAvatarOverviewBinding? = null + override var binding: FragmentComposeScrollingBinding? = null - override fun createBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentAvatarOverviewBinding { - return FragmentAvatarOverviewBinding.inflate(inflater, container, false) + override fun createBinding( + inflater: LayoutInflater, + container: ViewGroup? + ): FragmentComposeScrollingBinding { + return FragmentComposeScrollingBinding.inflate(inflater, container, false) } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - binding?.avatarSizeSpinner?.onItemSelectedListener = this - - binding?.avatarShirtView?.setOnClickListener { displayCustomizationFragment("shirt", null) } - binding?.avatarSkinView?.setOnClickListener { displayCustomizationFragment("skin", null) } - binding?.avatarChairView?.setOnClickListener { displayCustomizationFragment("chair", null) } - binding?.avatarGlassesView?.setOnClickListener { displayEquipmentFragment("eyewear", "glasses") } - binding?.avatarAnimalEarsView?.setOnClickListener { displayEquipmentFragment("headAccessory", "animal") } - binding?.avatarAnimalTailView?.setOnClickListener { displayEquipmentFragment("back", "animal") } - binding?.avatarHeadbandView?.setOnClickListener { displayEquipmentFragment("headAccessory", "headband") } - binding?.avatarHairColorView?.setOnClickListener { displayCustomizationFragment("hair", "color") } - binding?.avatarHairBangsView?.setOnClickListener { displayCustomizationFragment("hair", "bangs") } - binding?.avatarHairBaseView?.setOnClickListener { displayCustomizationFragment("hair", "base") } - binding?.avatarAccentView?.setOnClickListener { displayCustomizationFragment("hair", "flower") } - binding?.avatarHairBeardView?.setOnClickListener { displayCustomizationFragment("hair", "beard") } - binding?.avatarHairMustacheView?.setOnClickListener { displayCustomizationFragment("hair", "mustache") } - binding?.avatarBackgroundView?.setOnClickListener { displayCustomizationFragment("background", null) } - - userViewModel.user.observe(viewLifecycleOwner) { updateUser(it) } + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + val view = super.onCreateView(inflater, container, savedInstanceState) + binding?.composeView?.apply { + setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) + setContent { + HabiticaTheme { + AvatarOverviewView(userViewModel, { type, category -> + displayCustomizationFragment(type, category) + }, { type, equipped, isCostume -> + displayEquipmentFragment(type, equipped, isCostume) + }) + } + } + } + return view } override fun injectFragment(component: UserComponent) { @@ -52,46 +74,16 @@ class AvatarOverviewFragment : BaseMainFragment() } private fun displayCustomizationFragment(type: String, category: String?) { - MainNavigationController.navigate(AvatarOverviewFragmentDirections.openAvatarDetail(type, category ?: "")) + MainNavigationController.navigate( + AvatarOverviewFragmentDirections.openAvatarDetail( + type, + category ?: "" + ) + ) } - private fun displayEquipmentFragment(type: String, category: String?) { - MainNavigationController.navigate(AvatarOverviewFragmentDirections.openAvatarEquipment(type, category ?: "")) - } - - fun updateUser(user: User?) { - this.setSize(user?.preferences?.size) - setCustomizations(user) - } - - private fun setCustomizations(user: User?) { - if (user == null) return - binding?.avatarShirtView?.customizationIdentifier = user.preferences?.size + "_shirt_" + user.preferences?.shirt - binding?.avatarSkinView?.customizationIdentifier = "skin_" + user.preferences?.skin - val chair = user.preferences?.chair - binding?.avatarChairView?.customizationIdentifier = if (chair?.startsWith("handleless") == true) "chair_$chair" else chair - binding?.avatarGlassesView?.equipmentIdentifier = user.equipped?.eyeWear - binding?.avatarAnimalEarsView?.equipmentIdentifier = user.equipped?.headAccessory - binding?.avatarHeadbandView?.equipmentIdentifier = user.equipped?.headAccessory - binding?.avatarAnimalTailView?.equipmentIdentifier = user.equipped?.back - binding?.avatarHairColorView?.customizationIdentifier = if (user.preferences?.hair?.color != null && user.preferences?.hair?.color != "") "hair_bangs_1_" + user.preferences?.hair?.color else "" - binding?.avatarHairBangsView?.customizationIdentifier = if (user.preferences?.hair?.bangs != null && user.preferences?.hair?.bangs != 0) "hair_bangs_" + user.preferences?.hair?.bangs + "_" + user.preferences?.hair?.color else "" - binding?.avatarHairBaseView?.customizationIdentifier = if (user.preferences?.hair?.base != null && user.preferences?.hair?.base != 0) "hair_base_" + user.preferences?.hair?.base + "_" + user.preferences?.hair?.color else "" - binding?.avatarAccentView?.customizationIdentifier = if (user.preferences?.hair?.flower != null && user.preferences?.hair?.flower != 0) "hair_flower_" + user.preferences?.hair?.flower else "" - binding?.avatarHairBeardView?.customizationIdentifier = if (user.preferences?.hair?.beard != null && user.preferences?.hair?.beard != 0) "hair_beard_" + user.preferences?.hair?.beard + "_" + user.preferences?.hair?.color else "" - binding?.avatarHairMustacheView?.customizationIdentifier = if (user.preferences?.hair?.mustache != null && user.preferences?.hair?.mustache != 0) "hair_mustache_" + user.preferences?.hair?.mustache + "_" + user.preferences?.hair?.color else "" - binding?.avatarBackgroundView?.customizationIdentifier = "background_" + user.preferences?.background - } - - private fun setSize(size: String?) { - if (size == null) { - return - } - if (size == "slim") { - binding?.avatarSizeSpinner?.setSelection(0, false) - } else { - binding?.avatarSizeSpinner?.setSelection(1, false) - } + private fun displayEquipmentFragment(type: String, equipped: String?, isCostume: Boolean = false) { + MainNavigationController.navigate(EquipmentOverviewFragmentDirections.openEquipmentDetail(type, isCostume, equipped ?: "")) } override fun onItemSelected(parent: AdapterView<*>, view: View?, position: Int, id: Long) { @@ -103,5 +95,68 @@ class AvatarOverviewFragment : BaseMainFragment() ) } - override fun onNothingSelected(parent: AdapterView<*>) { /* no-on */ } + override fun onNothingSelected(parent: AdapterView<*>) { /* no-on */ + } } + +@Composable +fun AvatarOverviewView(userViewModel: MainUserViewModel, + onCustomizationTap: (String, String?) -> Unit, + onEquipmentTap: (String, String?, Boolean) -> Unit + ) { + val user by userViewModel.user.observeAsState() + Column( + Modifier + .padding(horizontal = 8.dp) + .padding(bottom = 16.dp)) { + Row(Modifier.padding(horizontal = 12.dp, vertical = 15.dp)) { + Text( + stringResource(R.string.avatar_size), + style = HabiticaTheme.typography.subtitle2 + ) + } + AvatarCustomizationOverviewView(user?.preferences, onCustomizationTap) + Row( + Modifier + .padding(horizontal = 12.dp) + .padding(top = 15.dp), + verticalAlignment = Alignment.CenterVertically) { + Text(stringResource(R.string.equipped), style = HabiticaTheme.typography.subtitle2) + Spacer(modifier = Modifier.weight(1f)) + Text( + stringResource(R.string.equip_automatically), + style = HabiticaTheme.typography.body2 + ) + Switch(checked = user?.preferences?.autoEquip == true, onCheckedChange = { + userViewModel.updateUser("preferences.autoEquip", it) + }) + } + EquipmentOverviewView(user?.items?.gear?.equipped, { type, equipped -> + onEquipmentTap(type, equipped, false) + }) + Row( + Modifier + .padding(horizontal = 12.dp) + .padding(top = 15.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Text( + stringResource(R.string.costume), + style = HabiticaTheme.typography.subtitle2 + ) + Spacer(modifier = Modifier.weight(1f)) + Text( + stringResource(R.string.wear_costume), + style = HabiticaTheme.typography.body2 + ) + Switch(checked = user?.preferences?.costume == true, onCheckedChange = { + userViewModel.updateUser("preferences.costume", it) + }) + } + AnimatedVisibility(visible = user?.preferences?.costume == true) { + EquipmentOverviewView(user?.items?.gear?.costume, { type, equipped -> + onEquipmentTap(type, equipped, true) + }) + } + } +} \ No newline at end of file diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/equipment/EquipmentOverviewFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/equipment/EquipmentOverviewFragment.kt deleted file mode 100644 index 7925ca84e..000000000 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/equipment/EquipmentOverviewFragment.kt +++ /dev/null @@ -1,81 +0,0 @@ -package com.habitrpg.android.habitica.ui.fragments.inventory.equipment - -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.fragment.app.viewModels -import com.habitrpg.android.habitica.components.UserComponent -import com.habitrpg.android.habitica.databinding.FragmentEquipmentOverviewBinding -import com.habitrpg.android.habitica.helpers.MainNavigationController -import com.habitrpg.android.habitica.models.user.Gear -import com.habitrpg.android.habitica.models.user.Outfit -import com.habitrpg.android.habitica.ui.fragments.BaseMainFragment -import com.habitrpg.android.habitica.ui.viewmodels.inventory.equipment.EquipmentOverviewViewModel -import com.habitrpg.android.habitica.ui.views.equipment.EquipmentOverviewView - -class EquipmentOverviewFragment : BaseMainFragment() { - - private val viewModel: EquipmentOverviewViewModel by viewModels() - - override var binding: FragmentEquipmentOverviewBinding? = null - - override fun createBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentEquipmentOverviewBinding { - return FragmentEquipmentOverviewBinding.inflate(inflater, container, false) - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - binding?.battlegearView?.onNavigate = { type, equipped -> - displayEquipmentDetailList(type, equipped, false) - } - binding?.costumeView?.onNavigate = { type, equipped -> - displayEquipmentDetailList(type, equipped, true) - } - - binding?.autoEquipSwitch?.setOnCheckedChangeListener { _, isChecked -> - if (isChecked == viewModel.user.value?.preferences?.autoEquip) return@setOnCheckedChangeListener - viewModel.updateUser("preferences.autoEquip", isChecked) - } - binding?.costumeSwitch?.setOnCheckedChangeListener { _, isChecked -> - if (isChecked == viewModel.user.value?.preferences?.costume) return@setOnCheckedChangeListener - viewModel.updateUser("preferences.costume", isChecked) - } - - viewModel.user.observe(viewLifecycleOwner) { - it?.items?.gear?.let { - updateGearData(it) - } - binding?.autoEquipSwitch?.isChecked = viewModel.usesAutoEquip - binding?.costumeSwitch?.isChecked = viewModel.usesCostume - - binding?.costumeView?.isEnabled = viewModel.usesCostume - } - } - - override fun injectFragment(component: UserComponent) { - component.inject(this) - } - - private fun displayEquipmentDetailList(type: String, equipped: String?, isCostume: Boolean?) { - MainNavigationController.navigate(EquipmentOverviewFragmentDirections.openEquipmentDetail(type, isCostume ?: false, equipped ?: "")) - } - - private fun updateGearData(gear: Gear) { - updateOutfit(binding?.battlegearView, gear.equipped) - updateOutfit(binding?.costumeView, gear.costume) - } - - private fun updateOutfit(view: EquipmentOverviewView?, outfit: Outfit?) { - if (outfit?.weapon?.isNotEmpty() == true) { - viewModel.getGear(outfit.weapon) { - if (it.isValid) { - view?.updateData(outfit, it.twoHanded) - } - } - } else { - view?.updateData(outfit) - } - } -} diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/theme/HabiticaTheme.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/theme/HabiticaTheme.kt new file mode 100644 index 000000000..a82ff67d1 --- /dev/null +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/theme/HabiticaTheme.kt @@ -0,0 +1,111 @@ +package com.habitrpg.android.habitica.ui.theme + +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Typography +import androidx.compose.runtime.Composable +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalLayoutDirection +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.sp +import com.google.android.material.composethemeadapter.createMdcTheme + +@Composable +fun HabiticaTheme( + content: @Composable () -> Unit +) { + val context = LocalContext.current + val layoutDirection = LocalLayoutDirection.current + val (colors, typography, shapes) = createMdcTheme( + context = context, + layoutDirection = layoutDirection, + setTextColors = true + ) + MaterialTheme( + colors = colors ?: MaterialTheme.colors, + typography = Typography( + defaultFontFamily = FontFamily.Default, + h1 = TextStyle( + fontWeight = FontWeight.Medium, + fontSize = 20.sp, + letterSpacing = (0.05).sp + ), + h2 = TextStyle( + fontWeight = FontWeight.Normal, + fontSize = 28.sp, + letterSpacing = (0.05).sp + ), + subtitle1 = TextStyle( + fontWeight = FontWeight.Medium, + fontSize = 16.sp, + ), + subtitle2 = TextStyle( + fontWeight = FontWeight.Normal, + fontSize = 16.sp, + letterSpacing = 0.1.sp + ), + body1 = TextStyle( + fontWeight = FontWeight.Medium, + fontSize = 14.sp, + letterSpacing = 0.35.sp, + lineHeight = 16.sp + ), + body2 = TextStyle( + fontWeight = FontWeight.Normal, + fontSize = 14.sp, + letterSpacing = 0.2.sp, + lineHeight = 16.sp + ), + button = TextStyle( + fontWeight = FontWeight.Medium, + fontSize = 14.sp, + letterSpacing = 1.25.sp + ), + caption = TextStyle( + fontWeight = FontWeight.Bold, + fontSize = 12.sp + ), + overline = TextStyle( + fontWeight = FontWeight.Medium, + fontSize = 10.sp, + letterSpacing = 1.5.sp + ) + ), + shapes = shapes ?: MaterialTheme.shapes, + content = content + ) +} + +val Typography.caption1 + get() = caption +val Typography.caption2 +get() = TextStyle( + fontWeight = FontWeight.Medium, + fontSize = 12.sp, + letterSpacing = 0.4.sp +) +val Typography.caption3 + get() = TextStyle( + fontWeight = FontWeight.Medium, + fontSize = 12.sp, + letterSpacing = 0.3.sp, + lineHeight = 14.sp + ) +val Typography.caption4 + get() = TextStyle( + fontWeight = FontWeight.Normal, + fontSize = 12.sp, + letterSpacing = 0.35.sp + ) +val Typography.subtitle3 + get() = TextStyle( + fontWeight = FontWeight.Normal, + fontSize = 16.sp, + letterSpacing = 0.15.sp + ) +object HabiticaTheme { + val typography: Typography + @Composable + get() = MaterialTheme.typography +} \ No newline at end of file diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/EquipmentOverviewView.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/EquipmentOverviewView.kt new file mode 100644 index 000000000..ce37fef3b --- /dev/null +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/EquipmentOverviewView.kt @@ -0,0 +1,217 @@ +package com.habitrpg.android.habitica.ui.views + +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.res.colorResource +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.habitrpg.android.habitica.R +import com.habitrpg.android.habitica.models.user.Outfit +import com.habitrpg.android.habitica.models.user.Preferences +import com.habitrpg.android.habitica.ui.theme.HabiticaTheme +import com.habitrpg.android.habitica.ui.theme.caption2 + +@Composable +fun OverviewItem( + text: String, + iconName: String?, + modifier: Modifier = Modifier, + isTwoHanded: Boolean = false +) { + val hasIcon = iconName?.isNotBlank() == true + Column( + horizontalAlignment = Alignment.CenterHorizontally, modifier = modifier + .width(70.dp) + ) { + Box( + Modifier + .size(70.dp) + .clip(RoundedCornerShape(4.dp)) + .background(colorResource(if (hasIcon) R.color.gray_700 else R.color.gray_10)), + contentAlignment = Alignment.Center + ) { + if (isTwoHanded) { + Image(painterResource(R.drawable.equipment_two_handed), null) + } else if (hasIcon) { + PixelArtView( + imageName = iconName, Modifier + .size(70.dp) + ) + } else { + Image(painterResource(R.drawable.equipment_nothing_equipped), null) + } + } + Text( + text, + style = HabiticaTheme.typography.caption2, + color = colorResource(R.color.gray_400), + textAlign = TextAlign.Center, + modifier = Modifier.padding(top = 4.dp) + ) + } +} + +@Composable +fun EquipmentOverviewView( + outfit: Outfit?, + onEquipmentTap: (String, String?) -> Unit, + modifier: Modifier = Modifier +) { + Column( + verticalArrangement = Arrangement.spacedBy(18.dp), + modifier = modifier + .fillMaxWidth() + .clip(RoundedCornerShape(6.dp)) + .background(colorResource(R.color.gray_50)) + .padding(12.dp) + ) { + Row(horizontalArrangement = Arrangement.SpaceBetween, modifier = Modifier.fillMaxWidth()) { + OverviewItem(stringResource(R.string.outfit_weapon), outfit?.weapon.let { "shop_$it" }, Modifier.clickable { + onEquipmentTap("weapon", null) + }) + OverviewItem(stringResource(R.string.outfit_shield), outfit?.shield.let { "shop_$it" }, Modifier.clickable { + onEquipmentTap("shield", null) + }) + OverviewItem(stringResource(R.string.outfit_head), outfit?.head.let { "shop_$it" }, Modifier.clickable { + onEquipmentTap("head", null) + }) + OverviewItem(stringResource(R.string.outfit_armor), outfit?.armor.let { "shop_$it" }, Modifier.clickable { + onEquipmentTap("armor", null) + }) + } + Row(horizontalArrangement = Arrangement.SpaceBetween, modifier = Modifier.fillMaxWidth()) { + OverviewItem( + stringResource(R.string.outfit_headAccessory), + outfit?.headAccessory.let { "shop_$it" }, Modifier.clickable { + onEquipmentTap("headAccessory", null) + }) + OverviewItem(stringResource(R.string.outfit_body), outfit?.body.let { "shop_$it" }, Modifier.clickable { + onEquipmentTap("body", null) + }) + OverviewItem(stringResource(R.string.outfit_back), outfit?.back.let { "shop_$it" }, Modifier.clickable { + onEquipmentTap("back", null) + }) + OverviewItem( + stringResource(R.string.outfit_eyewear), + outfit?.eyeWear.let { "shop_$it" }, Modifier.clickable { + onEquipmentTap("eyewear", null) + }) + } + } +} + +@Composable +fun AvatarCustomizationOverviewView( + preferences: Preferences?, + onCustomizationTap: (String, String?) -> Unit, + modifier: Modifier = Modifier +) { + Column( + verticalArrangement = Arrangement.spacedBy(18.dp), + modifier = modifier + .fillMaxWidth() + .clip(RoundedCornerShape(6.dp)) + .background(colorResource(R.color.gray_50)) + .padding(12.dp) + ) { + Row(horizontalArrangement = Arrangement.SpaceBetween, modifier = Modifier.fillMaxWidth()) { + OverviewItem( + stringResource(R.string.avatar_shirt), + preferences?.shirt.let { "${preferences?.size}_shirt$it" }, Modifier.clickable { + onCustomizationTap("shirt", null) + }) + OverviewItem( + stringResource(R.string.avatar_skin), + preferences?.skin.let { "skin_$it" }, + Modifier.clickable { + onCustomizationTap("skin", null) + }) + OverviewItem( + stringResource(R.string.avatar_hair_color), + if (preferences?.hair?.color != null && preferences.hair?.color != "") "hair_bangs_1_" + preferences.hair?.color else "", + Modifier.clickable { + onCustomizationTap("hair", "color") + } + ) + OverviewItem( + stringResource(R.string.avatar_hair_bangs), + if (preferences?.hair?.bangs != null && preferences.hair?.bangs != 0) "hair_bangs_" + preferences.hair?.bangs + "_" + preferences.hair?.color else "", + Modifier.clickable { + onCustomizationTap("hair", "bangs") + } + ) + } + Row(horizontalArrangement = Arrangement.SpaceBetween, modifier = Modifier.fillMaxWidth()) { + OverviewItem( + stringResource(R.string.avatar_style), + if (preferences?.hair?.base != null && preferences.hair?.base != 0) "hair_base_" + preferences.hair?.base + "_" + preferences.hair?.color else "", + Modifier.clickable { + onCustomizationTap("hair", "base") + } + ) + OverviewItem( + stringResource(R.string.avatar_mustache), + if (preferences?.hair?.mustache != null && preferences.hair?.mustache != 0) "hair_mustache_" + preferences.hair?.mustache + "_" + preferences.hair?.color else "", + Modifier.clickable { + onCustomizationTap("hair", "mustache") + } + ) + OverviewItem( + stringResource(R.string.avatar_beard), + if (preferences?.hair?.beard != null && preferences.hair?.beard != 0) "hair_beard_" + preferences.hair?.beard + "_" + preferences.hair?.color else "", + Modifier.clickable { + onCustomizationTap("hair", "beard") + } + ) + OverviewItem( + stringResource(R.string.avatar_flower), + if (preferences?.hair?.flower != null && preferences.hair?.flower != 0) "hair_flower_" + preferences.hair?.flower else "", + Modifier.clickable { + onCustomizationTap("hair", "flower") + } + ) + } + Row(horizontalArrangement = Arrangement.SpaceBetween, modifier = Modifier.fillMaxWidth()) { + OverviewItem( + stringResource(R.string.avatar_wheelchair), + preferences?.chair?.let { if (it.startsWith("handleless")) "chair_$it" else it }) + OverviewItem( + stringResource(R.string.avatar_background), + preferences?.background.let { "background_$it" }) + Box(Modifier.size(70.dp)) + Box(Modifier.size(70.dp)) + } + } +} + +@Preview +@Composable +fun EquipmentOverviewItemPreview() { + Column(Modifier.width(320.dp)) { + Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { + OverviewItem("Main-Hand", "shop_weapon_warrior_1") + OverviewItem("Off-Hand", null, isTwoHanded = true) + OverviewItem("Armor", null) + } + EquipmentOverviewView(null, { _, _ -> }) + AvatarCustomizationOverviewView(null, { _, _ -> }) + } +} \ No newline at end of file diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/PixelArtView.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/PixelArtView.kt new file mode 100644 index 000000000..d9f95f89c --- /dev/null +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/PixelArtView.kt @@ -0,0 +1,28 @@ +package com.habitrpg.android.habitica.ui.views + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.viewinterop.AndroidView +import com.habitrpg.common.habitica.extensions.loadImage +import com.habitrpg.common.habitica.views.PixelArtView + +@Composable +fun PixelArtView( + imageName: String?, + modifier: Modifier = Modifier, + imageFormat: String? = null +) { + AndroidView( + modifier = modifier, // Occupy the max size in the Compose UI tree + factory = { context -> + PixelArtView(context) + }, + update = { view -> + if (imageName != null) { + view.loadImage(imageName, imageFormat) + } else { + view.bitmap = null + } + } + ) +} \ No newline at end of file diff --git a/build.gradle b/build.gradle index 41ced8bc4..0518c1c63 100644 --- a/build.gradle +++ b/build.gradle @@ -14,7 +14,7 @@ buildscript { coroutines_version = '1.6.4' daggerhilt_version = '2.42' firebase_bom = '30.2.0' - kotlin_version = '1.7.10' + kotlin_version = '1.7.20' lifecycle_version = '2.5.1' markwon_version = '4.6.2' moshi_version = '1.13.0' From e3f66e57df8d9efb1e2b3cd7b0f534629af78746 Mon Sep 17 00:00:00 2001 From: Phillip Thelen Date: Wed, 12 Oct 2022 17:50:02 +0200 Subject: [PATCH 19/36] Prepare for new menu --- Habitica/build.gradle | 2 +- .../res/layout/drawer_main_section_header.xml | 13 +---- Habitica/res/navigation/navigation.xml | 25 +-------- .../habitica/components/UserComponent.java | 6 --- .../ui/adapter/NavigationDrawerAdapter.kt | 53 +------------------ .../ui/fragments/NavigationDrawerFragment.kt | 25 ++------- .../customization/AvatarOverviewFragment.kt | 3 +- .../habitica/ui/menu/HabiticaDrawerItem.kt | 1 + build.gradle | 1 + common/build.gradle | 6 +-- shared/build.gradle.kts | 4 +- 11 files changed, 18 insertions(+), 121 deletions(-) diff --git a/Habitica/build.gradle b/Habitica/build.gradle index c9e3bc90e..74c19431c 100644 --- a/Habitica/build.gradle +++ b/Habitica/build.gradle @@ -147,7 +147,7 @@ android { } defaultConfig { - minSdkVersion 21 + minSdkVersion min_sdk applicationId "com.habitrpg.android.habitica" vectorDrawables.useSupportLibrary = true buildConfigField "String", "STORE", "\"google\"" diff --git a/Habitica/res/layout/drawer_main_section_header.xml b/Habitica/res/layout/drawer_main_section_header.xml index da8bbd038..9bae55d9a 100644 --- a/Habitica/res/layout/drawer_main_section_header.xml +++ b/Habitica/res/layout/drawer_main_section_header.xml @@ -1,13 +1,4 @@ - \ No newline at end of file + android:layout_height="30dp" /> \ No newline at end of file diff --git a/Habitica/res/navigation/navigation.xml b/Habitica/res/navigation/navigation.xml index d192e2c80..e11fc3bac 100644 --- a/Habitica/res/navigation/navigation.xml +++ b/Habitica/res/navigation/navigation.xml @@ -4,16 +4,6 @@ xmlns:tools="http://schemas.android.com/tools" android:id="@+id/main_nav" app:startDestination="@id/tasksFragment"> - - - - - + android:id="@+id/openEquipmentDetail" + app:destination="@id/equipmentDetailFragment" /> - - - - = 0) { - notifyItemChanged(visiblePosition) - } - } - } - } - internal val items: MutableList = ArrayList() var selectedItem: Int? = null set(value) { @@ -77,32 +61,6 @@ class NavigationDrawerAdapter(tintColor: Int, backgroundTintColor: Int) : Recycl notifyDataSetChanged() } - fun setTeams(teams: List) { - var teamHeaderIndex = -1 - var nextHeaderIndex = -1 - for ((index, item) in items.withIndex()) { - if (teamHeaderIndex != -1 && item.isHeader) { - nextHeaderIndex = index - break - } else if (item.identifier == NavigationDrawerFragment.SIDEBAR_TEAMS) { - teamHeaderIndex = index - } - } - if (teamHeaderIndex != -1 && nextHeaderIndex != -1) { - for (x in nextHeaderIndex - 1 downTo teamHeaderIndex + 1) { - items.removeAt(x) - notifyItemRemoved(x) - } - for ((index, team) in teams.withIndex()) { - val item = HabiticaDrawerItem(R.id.tasksFragment, team.id, team.summary) - item.bundle = bundleOf(Pair("ownerID", team.id)) - val newIndex = teamHeaderIndex + index + 1 - items.add(newIndex, item) - notifyItemInserted(newIndex) - } - } - } - override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { val drawerItem = getItem(position) when { @@ -112,10 +70,6 @@ class NavigationDrawerAdapter(tintColor: Int, backgroundTintColor: Int) : Recycl itemHolder?.bind(drawerItem, drawerItem.transitionId == selectedItem) itemHolder?.itemView?.setOnClickListener { itemSelectedEvents.onNext(drawerItem) } } - getItemViewType(position) == 1 -> { - (holder as? SectionHeaderViewHolder)?.backgroundTintColor = backgroundTintColor - (holder as? SectionHeaderViewHolder)?.bind(drawerItem) - } getItemViewType(position) == 5 -> { activePromo?.let { promo -> (holder as? PromoMenuViewHolder)?.bind(promo) @@ -216,10 +170,5 @@ class NavigationDrawerAdapter(tintColor: Int, backgroundTintColor: Int) : Recycl class SectionHeaderViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { var backgroundTintColor: Int = 0 - - fun bind(drawerItem: HabiticaDrawerItem) { - (itemView as? TextView)?.text = drawerItem.text - itemView.setBackgroundColor(backgroundTintColor) - } } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/NavigationDrawerFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/NavigationDrawerFragment.kt index c370de647..8759207e8 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/NavigationDrawerFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/NavigationDrawerFragment.kt @@ -52,7 +52,6 @@ import io.reactivex.rxjava3.disposables.CompositeDisposable import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.delay -import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.launch import java.util.Calendar import java.util.Date @@ -176,19 +175,6 @@ class NavigationDrawerFragment : DialogFragment() { ) ) - if (configManager.enableTeamBoards()) { - lifecycleScope.launch(ExceptionHandler.coroutine()) { - userRepository.getTeamPlans() - .distinctUntilChanged() - .collect { - getItemWithIdentifier(SIDEBAR_TEAMS)?.isVisible = it.isNotEmpty() - adapter.setTeams(it) - } - } - } else { - getItemWithIdentifier(SIDEBAR_TEAMS)?.isVisible = false - } - userViewModel.user.observe(viewLifecycleOwner) { if (it != null) { updateUser(it) @@ -356,9 +342,7 @@ class NavigationDrawerFragment : DialogFragment() { items.add(HabiticaDrawerItem(R.id.statsFragment, SIDEBAR_STATS, context.getString(R.string.sidebar_stats))) items.add(HabiticaDrawerItem(R.id.achievementsFragment, SIDEBAR_ACHIEVEMENTS, context.getString(R.string.sidebar_achievements))) - items.add(HabiticaDrawerItem(0, SIDEBAR_TEAMS, context.getString(R.string.sidebar_teams), true)) - - items.add(HabiticaDrawerItem(0, SIDEBAR_INVENTORY, context.getString(R.string.sidebar_shops), true)) + items.add(HabiticaDrawerItem(0, SIDEBAR_INVENTORY, context.getString(R.string.sidebar_shops), isHeader = true)) items.add(HabiticaDrawerItem(R.id.marketFragment, SIDEBAR_SHOPS_MARKET, context.getString(R.string.market))) items.add(HabiticaDrawerItem(R.id.questShopFragment, SIDEBAR_SHOPS_QUEST, context.getString(R.string.questShop))) val seasonalShopEntry = HabiticaDrawerItem(R.id.seasonalShopFragment, SIDEBAR_SHOPS_SEASONAL, context.getString(R.string.seasonalShop)) @@ -366,20 +350,20 @@ class NavigationDrawerFragment : DialogFragment() { items.add(seasonalShopEntry) items.add(HabiticaDrawerItem(R.id.timeTravelersShopFragment, SIDEBAR_SHOPS_TIMETRAVEL, context.getString(R.string.timeTravelers))) - items.add(HabiticaDrawerItem(0, SIDEBAR_INVENTORY, context.getString(R.string.sidebar_section_inventory), true)) + items.add(HabiticaDrawerItem(0, SIDEBAR_INVENTORY, context.getString(R.string.sidebar_section_inventory), isHeader = true)) items.add(HabiticaDrawerItem(R.id.avatarOverviewFragment, SIDEBAR_AVATAR, context.getString(R.string.sidebar_avatar_equipment))) items.add(HabiticaDrawerItem(R.id.itemsFragment, SIDEBAR_ITEMS, context.getString(R.string.sidebar_items))) items.add(HabiticaDrawerItem(R.id.stableFragment, SIDEBAR_STABLE, context.getString(R.string.sidebar_stable))) items.add(HabiticaDrawerItem(R.id.gemPurchaseActivity, SIDEBAR_GEMS, context.getString(R.string.sidebar_gems))) items.add(HabiticaDrawerItem(R.id.subscriptionPurchaseActivity, SIDEBAR_SUBSCRIPTION, context.getString(R.string.sidebar_subscription))) - items.add(HabiticaDrawerItem(0, SIDEBAR_SOCIAL, context.getString(R.string.sidebar_section_social), true)) + items.add(HabiticaDrawerItem(0, SIDEBAR_SOCIAL, context.getString(R.string.sidebar_section_social), isHeader = true)) items.add(HabiticaDrawerItem(R.id.partyFragment, SIDEBAR_PARTY, context.getString(R.string.sidebar_party))) items.add(HabiticaDrawerItem(R.id.tavernFragment, SIDEBAR_TAVERN, context.getString(R.string.sidebar_tavern))) items.add(HabiticaDrawerItem(R.id.guildOverviewFragment, SIDEBAR_GUILDS, context.getString(R.string.sidebar_guilds))) items.add(HabiticaDrawerItem(R.id.challengesOverviewFragment, SIDEBAR_CHALLENGES, context.getString(R.string.sidebar_challenges))) - items.add(HabiticaDrawerItem(0, SIDEBAR_ABOUT_HEADER, context.getString(R.string.sidebar_about), true)) + items.add(HabiticaDrawerItem(0, SIDEBAR_ABOUT_HEADER, context.getString(R.string.sidebar_about), isHeader = true)) items.add(HabiticaDrawerItem(R.id.newsFragment, SIDEBAR_NEWS, context.getString(R.string.sidebar_news))) items.add(HabiticaDrawerItem(R.id.supportMainFragment, SIDEBAR_HELP, context.getString(R.string.sidebar_help))) items.add(HabiticaDrawerItem(R.id.aboutFragment, SIDEBAR_ABOUT, context.getString(R.string.sidebar_about))) @@ -634,7 +618,6 @@ class NavigationDrawerFragment : DialogFragment() { const val SIDEBAR_SKILLS = "skills" const val SIDEBAR_STATS = "stats" const val SIDEBAR_ACHIEVEMENTS = "achievements" - const val SIDEBAR_TEAMS = "teams" const val SIDEBAR_SOCIAL = "social" const val SIDEBAR_TAVERN = "tavern" const val SIDEBAR_PARTY = "party" diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/customization/AvatarOverviewFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/customization/AvatarOverviewFragment.kt index a5b74ab6e..98d2a4619 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/customization/AvatarOverviewFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/customization/AvatarOverviewFragment.kt @@ -26,7 +26,6 @@ import com.habitrpg.android.habitica.databinding.FragmentComposeScrollingBinding import com.habitrpg.android.habitica.helpers.ExceptionHandler import com.habitrpg.android.habitica.helpers.MainNavigationController import com.habitrpg.android.habitica.ui.fragments.BaseMainFragment -import com.habitrpg.android.habitica.ui.fragments.inventory.equipment.EquipmentOverviewFragmentDirections import com.habitrpg.android.habitica.ui.theme.HabiticaTheme import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel import com.habitrpg.android.habitica.ui.views.AvatarCustomizationOverviewView @@ -83,7 +82,7 @@ class AvatarOverviewFragment : BaseMainFragment } private fun displayEquipmentFragment(type: String, equipped: String?, isCostume: Boolean = false) { - MainNavigationController.navigate(EquipmentOverviewFragmentDirections.openEquipmentDetail(type, isCostume, equipped ?: "")) + MainNavigationController.navigate(AvatarOverviewFragmentDirections.openEquipmentDetail(type, isCostume, equipped ?: "")) } override fun onItemSelected(parent: AdapterView<*>, view: View?, position: Int, id: Long) { diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/menu/HabiticaDrawerItem.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/menu/HabiticaDrawerItem.kt index 2586929be..42b2cd6f5 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/menu/HabiticaDrawerItem.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/menu/HabiticaDrawerItem.kt @@ -8,6 +8,7 @@ data class HabiticaDrawerItem( var transitionId: Int, val identifier: String, val text: String, + val icon: Drawable? = null, val isHeader: Boolean = false ) { constructor(transitionId: Int, identifier: String) : this(transitionId, identifier, "") diff --git a/build.gradle b/build.gradle index 0518c1c63..75d747444 100644 --- a/build.gradle +++ b/build.gradle @@ -2,6 +2,7 @@ buildscript { ext { + min_sdk = 21 target_sdk = 33 app_version_name = '' app_version_code = 0 diff --git a/common/build.gradle b/common/build.gradle index 71188b0a7..ad3a8d9bb 100644 --- a/common/build.gradle +++ b/common/build.gradle @@ -4,11 +4,11 @@ plugins { } android { - compileSdk 32 + compileSdk target_sdk defaultConfig { - minSdk 21 - targetSdk 32 + minSdk min_sdk + targetSdkVersion target_sdk testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" consumerProguardFiles "consumer-rules.pro" diff --git a/shared/build.gradle.kts b/shared/build.gradle.kts index a58de7160..f06786270 100644 --- a/shared/build.gradle.kts +++ b/shared/build.gradle.kts @@ -54,11 +54,11 @@ kotlin { } android { - compileSdk = 32 + compileSdk = 33 sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml") defaultConfig { minSdk = 21 - targetSdk = 32 + targetSdk = 33 } namespace = "com.habitrpg.shared.habitica" } \ No newline at end of file From bdfb475a998d12a2abd397f7d9a35ddf225c891a Mon Sep 17 00:00:00 2001 From: Phillip Thelen Date: Mon, 31 Oct 2022 12:16:09 +0100 Subject: [PATCH 20/36] correctly display task completion for group tasks # Conflicts: # Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/tasks/RealmBaseTasksRecyclerViewAdapter.kt # Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/tasks/ChecklistedViewHolder.kt # Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/tasks/DailyViewHolder.kt # Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/tasks/TodoViewHolder.kt --- .../habitica/api/GSonFactoryCreator.java | 5 +++ .../android/habitica/models/tasks/Task.kt | 13 ++++++++ .../habitica/models/tasks/TaskGroupPlan.kt | 10 ++++++ .../RealmBaseTasksRecyclerViewAdapter.kt | 1 + .../viewHolders/tasks/BaseTaskViewHolder.kt | 1 + .../tasks/ChecklistedViewHolder.kt | 18 +++++------ .../ui/viewHolders/tasks/DailyViewHolder.kt | 5 ++- .../ui/viewHolders/tasks/TodoViewHolder.kt | 4 +-- .../utils/AssignedDetailsDeserializer.kt | 31 +++++++++++++++++++ 9 files changed, 74 insertions(+), 14 deletions(-) create mode 100644 Habitica/src/main/java/com/habitrpg/android/habitica/utils/AssignedDetailsDeserializer.kt diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/api/GSonFactoryCreator.java b/Habitica/src/main/java/com/habitrpg/android/habitica/api/GSonFactoryCreator.java index 553d68f81..860c8de38 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/api/GSonFactoryCreator.java +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/api/GSonFactoryCreator.java @@ -6,6 +6,8 @@ import com.google.gson.reflect.TypeToken; import com.habitrpg.android.habitica.models.Achievement; import com.habitrpg.android.habitica.models.ContentResult; import com.habitrpg.android.habitica.models.FAQArticle; +import com.habitrpg.android.habitica.models.tasks.GroupAssignedDetails; +import com.habitrpg.android.habitica.utils.AssignedDetailsDeserializer; import com.habitrpg.common.habitica.models.Notification; import com.habitrpg.android.habitica.models.Skill; import com.habitrpg.android.habitica.models.Tag; @@ -101,6 +103,8 @@ public class GSonFactoryCreator { }.getType(); Type achievementsListType = new TypeToken>() { }.getType(); + Type assignedDetailsListType = new TypeToken>() { + }.getType(); return new GsonBuilder() .registerTypeAdapter(taskTagClassListType, new TaskTagDeserializer()) @@ -129,6 +133,7 @@ public class GSonFactoryCreator { .registerTypeAdapter(ownedPetListType, new OwnedPetListDeserializer()) .registerTypeAdapter(ownedMountListType, new OwnedMountListDeserializer()) .registerTypeAdapter(achievementsListType, new AchievementListDeserializer()) + .registerTypeAdapter(assignedDetailsListType, new AssignedDetailsDeserializer()) .registerTypeAdapter(Quest.class, new QuestDeserializer()) .registerTypeAdapter(Member.class, new MemberSerialization()) .registerTypeAdapter(WorldState.class, new WorldStateSerialization()) 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 c9788bd3a..325f16dba 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 @@ -117,6 +117,19 @@ open class Task : RealmObject, BaseMainObject, Parcelable, BaseTask { val completedChecklistCount: Int get() = checklist?.count { it.completed } ?: 0 + fun completed(byUserID: String?): Boolean { + return if (isGroupTask) { + group?.assignedUsersDetail?.firstOrNull { it.assignedUserID == byUserID }?.completed ?: false + } else { + completed + } + } + + fun isDisplayedActiveForUser(userID: String?): Boolean { + val isActive = ((isDue == true && type == TaskType.DAILY) || type == TaskType.TODO) + return isActive && completed(userID) + } + val streakString: String? get() { return if (counterUp != null && (counterUp ?: 0) > 0 && counterDown != null && (counterDown ?: 0) > 0) { diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/models/tasks/TaskGroupPlan.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/models/tasks/TaskGroupPlan.kt index 49e2191da..2e936526c 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/models/tasks/TaskGroupPlan.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/models/tasks/TaskGroupPlan.kt @@ -7,6 +7,15 @@ import io.realm.RealmObject import io.realm.annotations.RealmClass import java.util.Date +@RealmClass(embedded = true) +open class GroupAssignedDetails: RealmObject(), BaseObject { + var assignedDate: String? = null + var assignedUsername: String? = null + var assignedUserID: String? = null + var assigningUsername: String? = null + var completed: Boolean = false +} + @RealmClass(embedded = true) open class TaskGroupPlan : RealmObject(), BaseObject { @@ -17,6 +26,7 @@ open class TaskGroupPlan : RealmObject(), BaseObject { var assignedDate: Date? = null var assigningUsername: String? = null var assignedUsers: RealmList = RealmList() + var assignedUsersDetail: RealmList = RealmList() var approvalRequested: Boolean? = false var approvalApproved: Boolean? = false diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/tasks/RealmBaseTasksRecyclerViewAdapter.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/tasks/RealmBaseTasksRecyclerViewAdapter.kt index e39b19af2..801e10a1c 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/tasks/RealmBaseTasksRecyclerViewAdapter.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/tasks/RealmBaseTasksRecyclerViewAdapter.kt @@ -79,6 +79,7 @@ abstract class RealmBaseTasksRecyclerViewAdapter( override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { val item = getItem(position) if (item != null && holder is BaseTaskViewHolder) { + holder.userID = user?.id holder.isLocked = !viewModel.canScoreTask(item) holder.bind(item, position, taskDisplayMode, viewModel.ownerID.value) holder.errorButtonClicked = Action { diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/tasks/BaseTaskViewHolder.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/tasks/BaseTaskViewHolder.kt index 42ef8c45a..1af36d06d 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/tasks/BaseTaskViewHolder.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/tasks/BaseTaskViewHolder.kt @@ -43,6 +43,7 @@ abstract class BaseTaskViewHolder constructor( var task: Task? = null var movingFromPosition: Int? = null var errorButtonClicked: Action? = null + var userID: String? = null var isLocked = false protected var context: Context private val mainTaskWrapper: ViewGroup = itemView.findViewById(R.id.main_task_wrapper) diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/tasks/ChecklistedViewHolder.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/tasks/ChecklistedViewHolder.kt index 58b3a27ad..499445b24 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/tasks/ChecklistedViewHolder.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/tasks/ChecklistedViewHolder.kt @@ -56,7 +56,7 @@ abstract class ChecklistedViewHolder( displayMode: String, ownerID: String? ) { - var completed = data.completed + var completed = data.completed(userID) if (data.isPendingApproval) { completed = false } @@ -80,11 +80,11 @@ abstract class ChecklistedViewHolder( val regularBoxBackground = if (task?.type == TaskType.DAILY) R.drawable.daily_unchecked else R.drawable.todo_unchecked val completedBoxBackground = if (task?.type == TaskType.DAILY) R.drawable.daily_checked else R.drawable.todo_checked val inactiveBoxBackground = R.drawable.daily_inactive - if (this.shouldDisplayAsActive(data) && !data.isPendingApproval) { + if (this.shouldDisplayAsActive(data, userID) && !data.isPendingApproval) { this.checkboxHolder.setBackgroundResource(data.lightTaskColor) checkboxBackground.setBackgroundResource(regularBoxBackground) } else { - if (data.completed) { + if (completed) { titleTextView.setTextColor(ContextCompat.getColor(context, R.color.text_quad)) notesTextView?.setTextColor(ContextCompat.getColor(context, R.color.text_quad)) this.checkboxHolder.setBackgroundColor(context.getThemeColor(R.attr.colorWindowBackground)) @@ -98,7 +98,7 @@ abstract class ChecklistedViewHolder( } } - abstract fun shouldDisplayAsActive(newTask: Task?): Boolean + abstract fun shouldDisplayAsActive(task: Task?, userID: String?): Boolean private fun updateChecklistDisplay() { // This needs to be a LinearLayout, as ListViews can not be inside other ListViews. @@ -116,7 +116,7 @@ abstract class ChecklistedViewHolder( context, ( if (context.isUsingNightModeResources()) { - if (task?.completed == true || (task?.type == TaskType.DAILY && task?.isDue == false)) { + if (task?.completed(userID) == true || (task?.type == TaskType.DAILY && task?.isDue == false)) { R.color.checkbox_fill } else { task?.lightTaskColor @@ -151,7 +151,7 @@ abstract class ChecklistedViewHolder( } val color = ContextCompat.getColor( context, - if (task?.completed == true || (task?.type == TaskType.DAILY && task?.isDue == false)) { + if (task?.completed(userID) == true || (task?.type == TaskType.DAILY && task?.isDue == false)) { checkmark?.drawable?.setTint(ContextCompat.getColor(context, R.color.text_dimmed)) R.color.offset_background } else { @@ -207,7 +207,7 @@ abstract class ChecklistedViewHolder( override fun onLeftActionTouched() { super.onLeftActionTouched() if (task?.isValid == true) { - onCheckedChanged(!(task?.completed ?: false)) + onCheckedChanged(!(task?.completed(userID) ?: false)) } } @@ -224,8 +224,8 @@ abstract class ChecklistedViewHolder( if (task?.isValid != true) { return } - if (isChecked != task?.completed) { - task?.let { scoreTaskFunc(it, if (task?.completed == false) TaskDirection.UP else TaskDirection.DOWN) } + if (isChecked != task?.completed(userID)) { + task?.let { scoreTaskFunc(it, if (task?.completed(userID) == false) TaskDirection.UP else TaskDirection.DOWN) } } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/tasks/DailyViewHolder.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/tasks/DailyViewHolder.kt index 04a2147aa..3ff7784bf 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/tasks/DailyViewHolder.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/tasks/DailyViewHolder.kt @@ -1,7 +1,6 @@ package com.habitrpg.android.habitica.ui.viewHolders.tasks import android.view.View -import com.habitrpg.android.habitica.helpers.GroupPlanInfoProvider import com.habitrpg.android.habitica.models.tasks.ChecklistItem import com.habitrpg.android.habitica.models.tasks.Task import com.habitrpg.shared.habitica.models.responses.TaskDirection @@ -69,8 +68,8 @@ class DailyViewHolder( super.bind(data, position, displayMode, ownerID) } - override fun shouldDisplayAsActive(newTask: Task?): Boolean { - return newTask?.isDisplayedActive ?: false + override fun shouldDisplayAsActive(task: Task?, userID: String?): Boolean { + return task?.isDisplayedActive ?: false } override fun configureSpecialTaskTextView(task: Task) { diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/tasks/TodoViewHolder.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/tasks/TodoViewHolder.kt index 2370a4ad4..8ac863f50 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/tasks/TodoViewHolder.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/tasks/TodoViewHolder.kt @@ -42,7 +42,7 @@ class TodoViewHolder( } } - override fun shouldDisplayAsActive(newTask: Task?): Boolean { - return newTask?.completed != true + override fun shouldDisplayAsActive(task: Task?, userID: String?): Boolean { + return task?.completed(userID) != true } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/utils/AssignedDetailsDeserializer.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/utils/AssignedDetailsDeserializer.kt new file mode 100644 index 000000000..9adef8c45 --- /dev/null +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/utils/AssignedDetailsDeserializer.kt @@ -0,0 +1,31 @@ +package com.habitrpg.android.habitica.utils + +import com.google.gson.JsonDeserializationContext +import com.google.gson.JsonDeserializer +import com.google.gson.JsonElement +import com.habitrpg.android.habitica.models.tasks.GroupAssignedDetails +import io.realm.RealmList +import java.lang.reflect.Type + +class AssignedDetailsDeserializer: JsonDeserializer> { + override fun deserialize( + json: JsonElement?, + typeOfT: Type?, + context: JsonDeserializationContext? + ): RealmList { + val list = RealmList() + if (json == null) { + return list + } else if (json.isJsonArray) { + json.asJsonArray.forEach { list.add(context?.deserialize(it, GroupAssignedDetails::class.java)) } + } else { + val jsonObject = json.asJsonObject + jsonObject.keySet().forEach { + val details= context?.deserialize(jsonObject[it], GroupAssignedDetails::class.java) + details?.assignedUserID = it + list.add(details) + } + } + return list + } +} \ No newline at end of file From 07e6fc313396904af07d40d3be168100457e7429 Mon Sep 17 00:00:00 2001 From: Phillip Thelen Date: Mon, 31 Oct 2022 14:30:57 +0100 Subject: [PATCH 21/36] Revert "Prepare for new menu" This reverts commit e3f66e57df8d9efb1e2b3cd7b0f534629af78746. --- .../ui/adapter/NavigationDrawerAdapter.kt | 53 ++++++++++++++++++- 1 file changed, 52 insertions(+), 1 deletion(-) diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/NavigationDrawerAdapter.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/NavigationDrawerAdapter.kt index 933e17d3f..f961d9f07 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/NavigationDrawerAdapter.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/NavigationDrawerAdapter.kt @@ -4,16 +4,19 @@ import android.view.View import android.view.ViewGroup import android.widget.TextView import androidx.core.content.ContextCompat +import androidx.core.os.bundleOf import androidx.recyclerview.widget.RecyclerView import com.habitrpg.android.habitica.R +import com.habitrpg.common.habitica.extensions.dpToPx import com.habitrpg.android.habitica.extensions.inflate +import com.habitrpg.android.habitica.models.TeamPlan import com.habitrpg.android.habitica.models.promotions.HabiticaPromotion +import com.habitrpg.android.habitica.ui.fragments.NavigationDrawerFragment import com.habitrpg.android.habitica.ui.menu.HabiticaDrawerItem import com.habitrpg.android.habitica.ui.views.promo.PromoMenuView import com.habitrpg.android.habitica.ui.views.promo.PromoMenuViewHolder import com.habitrpg.android.habitica.ui.views.promo.SubscriptionBuyGemsPromoView import com.habitrpg.android.habitica.ui.views.promo.SubscriptionBuyGemsPromoViewHolder -import com.habitrpg.common.habitica.extensions.dpToPx import io.reactivex.rxjava3.core.BackpressureStrategy import io.reactivex.rxjava3.core.Flowable import io.reactivex.rxjava3.subjects.PublishSubject @@ -26,6 +29,19 @@ class NavigationDrawerAdapter(tintColor: Int, backgroundTintColor: Int) : Recycl notifyDataSetChanged() } + var backgroundTintColor: Int = backgroundTintColor + set(value) { + field = value + for (item in items) { + if (item.isHeader) { + val visiblePosition = getVisibleItemPosition(item.identifier) + if (visiblePosition >= 0) { + notifyItemChanged(visiblePosition) + } + } + } + } + internal val items: MutableList = ArrayList() var selectedItem: Int? = null set(value) { @@ -61,6 +77,32 @@ class NavigationDrawerAdapter(tintColor: Int, backgroundTintColor: Int) : Recycl notifyDataSetChanged() } + fun setTeams(teams: List) { + var teamHeaderIndex = -1 + var nextHeaderIndex = -1 + for ((index, item) in items.withIndex()) { + if (teamHeaderIndex != -1 && item.isHeader) { + nextHeaderIndex = index + break + } else if (item.identifier == NavigationDrawerFragment.SIDEBAR_TEAMS) { + teamHeaderIndex = index + } + } + if (teamHeaderIndex != -1 && nextHeaderIndex != -1) { + for (x in nextHeaderIndex - 1 downTo teamHeaderIndex + 1) { + items.removeAt(x) + notifyItemRemoved(x) + } + for ((index, team) in teams.withIndex()) { + val item = HabiticaDrawerItem(R.id.tasksFragment, team.id, team.summary) + item.bundle = bundleOf(Pair("ownerID", team.id)) + val newIndex = teamHeaderIndex + index + 1 + items.add(newIndex, item) + notifyItemInserted(newIndex) + } + } + } + override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { val drawerItem = getItem(position) when { @@ -70,6 +112,10 @@ class NavigationDrawerAdapter(tintColor: Int, backgroundTintColor: Int) : Recycl itemHolder?.bind(drawerItem, drawerItem.transitionId == selectedItem) itemHolder?.itemView?.setOnClickListener { itemSelectedEvents.onNext(drawerItem) } } + getItemViewType(position) == 1 -> { + (holder as? SectionHeaderViewHolder)?.backgroundTintColor = backgroundTintColor + (holder as? SectionHeaderViewHolder)?.bind(drawerItem) + } getItemViewType(position) == 5 -> { activePromo?.let { promo -> (holder as? PromoMenuViewHolder)?.bind(promo) @@ -170,5 +216,10 @@ class NavigationDrawerAdapter(tintColor: Int, backgroundTintColor: Int) : Recycl class SectionHeaderViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { var backgroundTintColor: Int = 0 + + fun bind(drawerItem: HabiticaDrawerItem) { + (itemView as? TextView)?.text = drawerItem.text + itemView.setBackgroundColor(backgroundTintColor) + } } } From f6a77bc77fd4a9960980af9752af8ab4e31b217f Mon Sep 17 00:00:00 2001 From: Phillip Thelen Date: Mon, 31 Oct 2022 14:31:03 +0100 Subject: [PATCH 22/36] fix import --- .../android/habitica/ui/viewHolders/tasks/DailyViewHolder.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/tasks/DailyViewHolder.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/tasks/DailyViewHolder.kt index 3ff7784bf..cb4a1a6fa 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/tasks/DailyViewHolder.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/tasks/DailyViewHolder.kt @@ -1,6 +1,7 @@ package com.habitrpg.android.habitica.ui.viewHolders.tasks import android.view.View +import com.habitrpg.android.habitica.helpers.GroupPlanInfoProvider import com.habitrpg.android.habitica.models.tasks.ChecklistItem import com.habitrpg.android.habitica.models.tasks.Task import com.habitrpg.shared.habitica.models.responses.TaskDirection From e2fdeab1945760fc235640cbc8cc02cd6092b9d3 Mon Sep 17 00:00:00 2001 From: Phillip Thelen Date: Mon, 31 Oct 2022 14:33:03 +0100 Subject: [PATCH 23/36] Revert "Prepare for new menu" This reverts commit e3f66e57df8d9efb1e2b3cd7b0f534629af78746. --- Habitica/res/layout/drawer_main_section_header.xml | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/Habitica/res/layout/drawer_main_section_header.xml b/Habitica/res/layout/drawer_main_section_header.xml index 9bae55d9a..da8bbd038 100644 --- a/Habitica/res/layout/drawer_main_section_header.xml +++ b/Habitica/res/layout/drawer_main_section_header.xml @@ -1,4 +1,13 @@ - \ No newline at end of file + android:layout_height="22dp" + android:gravity="center_vertical" + android:paddingStart="@dimen/drawer_padding" + android:paddingEnd="@dimen/drawer_padding" + android:textAllCaps="true" + android:textColor="@color/white" + android:textSize="12sp" + tools:text="Social" /> \ No newline at end of file From f93f45e69261c5e0e7a10b7b48a44a7b507c5af4 Mon Sep 17 00:00:00 2001 From: Phillip Thelen Date: Mon, 31 Oct 2022 15:12:55 +0100 Subject: [PATCH 24/36] fix colrs --- Habitica/build.gradle | 12 +- Habitica/res/values/styles.xml | 1 + .../ui/adapter/NavigationDrawerAdapter.kt | 31 +--- .../customization/AvatarOverviewFragment.kt | 9 +- .../habitica/ui/views/AppHeaderView.kt | 7 +- .../habitica/ui/views/SegmentedControl.kt | 139 ++++++++++++++++++ build.gradle | 8 +- 7 files changed, 164 insertions(+), 43 deletions(-) create mode 100644 Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/SegmentedControl.kt diff --git a/Habitica/build.gradle b/Habitica/build.gradle index 74c19431c..34fa27a2b 100644 --- a/Habitica/build.gradle +++ b/Habitica/build.gradle @@ -43,7 +43,7 @@ dependencies { compileOnly 'javax.annotation:javax.annotation-api:1.3.2' //App Compatibility and Material Design implementation "androidx.appcompat:appcompat:$appcompat_version" - implementation 'com.google.android.material:material:1.6.1' + implementation 'com.google.android.material:material:1.7.0' implementation "androidx.recyclerview:recyclerview:$recyclerview_version" implementation "androidx.preference:preference-ktx:$preferences_version" implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0" @@ -79,7 +79,7 @@ dependencies { androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' androidTestImplementation 'androidx.test:runner:1.4.0' androidTestImplementation 'androidx.test:rules:1.4.0' - debugImplementation 'androidx.fragment:fragment-testing:1.5.3' + debugImplementation 'androidx.fragment:fragment-testing:1.5.4' androidTestImplementation 'androidx.test.ext:junit:1.1.3' androidTestImplementation 'androidx.test:core-ktx:1.4.0' androidTestImplementation 'androidx.test.ext:junit-ktx:1.1.3' @@ -98,7 +98,7 @@ dependencies { implementation 'com.google.firebase:firebase-messaging-ktx' implementation 'com.google.firebase:firebase-config-ktx' implementation 'com.google.firebase:firebase-perf-ktx' - implementation 'com.google.android.gms:play-services-ads:21.2.0' + implementation 'com.google.android.gms:play-services-ads:21.3.0' implementation "com.google.android.gms:play-services-auth:$play_auth_version" implementation 'com.google.android.flexbox:flexbox:3.0.0' implementation "com.google.android.gms:play-services-wearable:$play_wearables_version" @@ -109,13 +109,13 @@ dependencies { implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version" implementation "androidx.navigation:navigation-fragment-ktx:$navigation_version" implementation "androidx.navigation:navigation-ui-ktx:$navigation_version" - implementation "androidx.fragment:fragment-ktx:1.5.3" + implementation "androidx.fragment:fragment-ktx:1.5.4" implementation "androidx.paging:paging-runtime-ktx:3.1.1" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version" - implementation "com.google.android.material:compose-theme-adapter:1.1.19" + implementation "com.google.android.material:compose-theme-adapter:1.1.21" - implementation 'androidx.activity:activity-compose:1.6.0' + implementation 'androidx.activity:activity-compose:1.6.1' implementation "androidx.compose.runtime:runtime-livedata:$compose_version" implementation "androidx.compose.material:material:$compose_version" implementation "androidx.compose.animation:animation:$compose_version" diff --git a/Habitica/res/values/styles.xml b/Habitica/res/values/styles.xml index 48e23d006..72e59c4da 100644 --- a/Habitica/res/values/styles.xml +++ b/Habitica/res/values/styles.xml @@ -26,6 +26,7 @@ @color/brand_500 @color/brand_400 @color/brand_300 + @color/brand @color/text_primary @color/text_secondary diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/NavigationDrawerAdapter.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/NavigationDrawerAdapter.kt index f961d9f07..23e19efff 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/NavigationDrawerAdapter.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/NavigationDrawerAdapter.kt @@ -4,19 +4,16 @@ import android.view.View import android.view.ViewGroup import android.widget.TextView import androidx.core.content.ContextCompat -import androidx.core.os.bundleOf import androidx.recyclerview.widget.RecyclerView import com.habitrpg.android.habitica.R -import com.habitrpg.common.habitica.extensions.dpToPx import com.habitrpg.android.habitica.extensions.inflate -import com.habitrpg.android.habitica.models.TeamPlan import com.habitrpg.android.habitica.models.promotions.HabiticaPromotion -import com.habitrpg.android.habitica.ui.fragments.NavigationDrawerFragment import com.habitrpg.android.habitica.ui.menu.HabiticaDrawerItem import com.habitrpg.android.habitica.ui.views.promo.PromoMenuView import com.habitrpg.android.habitica.ui.views.promo.PromoMenuViewHolder import com.habitrpg.android.habitica.ui.views.promo.SubscriptionBuyGemsPromoView import com.habitrpg.android.habitica.ui.views.promo.SubscriptionBuyGemsPromoViewHolder +import com.habitrpg.common.habitica.extensions.dpToPx import io.reactivex.rxjava3.core.BackpressureStrategy import io.reactivex.rxjava3.core.Flowable import io.reactivex.rxjava3.subjects.PublishSubject @@ -77,32 +74,6 @@ class NavigationDrawerAdapter(tintColor: Int, backgroundTintColor: Int) : Recycl notifyDataSetChanged() } - fun setTeams(teams: List) { - var teamHeaderIndex = -1 - var nextHeaderIndex = -1 - for ((index, item) in items.withIndex()) { - if (teamHeaderIndex != -1 && item.isHeader) { - nextHeaderIndex = index - break - } else if (item.identifier == NavigationDrawerFragment.SIDEBAR_TEAMS) { - teamHeaderIndex = index - } - } - if (teamHeaderIndex != -1 && nextHeaderIndex != -1) { - for (x in nextHeaderIndex - 1 downTo teamHeaderIndex + 1) { - items.removeAt(x) - notifyItemRemoved(x) - } - for ((index, team) in teams.withIndex()) { - val item = HabiticaDrawerItem(R.id.tasksFragment, team.id, team.summary) - item.bundle = bundleOf(Pair("ownerID", team.id)) - val newIndex = teamHeaderIndex + index + 1 - items.add(newIndex, item) - notifyItemInserted(newIndex) - } - } - } - override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { val drawerItem = getItem(position) when { diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/customization/AvatarOverviewFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/customization/AvatarOverviewFragment.kt index 98d2a4619..0b0e62358 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/customization/AvatarOverviewFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/customization/AvatarOverviewFragment.kt @@ -30,6 +30,7 @@ import com.habitrpg.android.habitica.ui.theme.HabiticaTheme import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel import com.habitrpg.android.habitica.ui.views.AvatarCustomizationOverviewView import com.habitrpg.android.habitica.ui.views.EquipmentOverviewView +import com.habitrpg.android.habitica.ui.views.SegmentedControl import javax.inject.Inject class AvatarOverviewFragment : BaseMainFragment(), @@ -108,11 +109,17 @@ fun AvatarOverviewView(userViewModel: MainUserViewModel, Modifier .padding(horizontal = 8.dp) .padding(bottom = 16.dp)) { - Row(Modifier.padding(horizontal = 12.dp, vertical = 15.dp)) { + Row(Modifier.padding(horizontal = 12.dp, vertical = 15.dp), + verticalAlignment = Alignment.CenterVertically) { Text( stringResource(R.string.avatar_size), style = HabiticaTheme.typography.subtitle2 ) + Spacer(modifier = Modifier.weight(1f)) + SegmentedControl(items = listOf(stringResource(R.string.avatar_size_slim), stringResource(R.string.avatar_size_broad + )), defaultSelectedItemIndex = if (user?.preferences?.size == "slim") 0 else 1, onItemSelection = { + userViewModel.updateUser("preferences.size", if (it == 0) "slim" else "broad") + }) } AvatarCustomizationOverviewView(user?.preferences, onCustomizationTap) Row( diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/AppHeaderView.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/AppHeaderView.kt index 6d7a86ca8..a604a4bf2 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/AppHeaderView.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/AppHeaderView.kt @@ -52,6 +52,7 @@ import com.habitrpg.android.habitica.helpers.MainNavigationController import com.habitrpg.android.habitica.models.user.User import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel import com.habitrpg.common.habitica.helpers.NumberAbbreviator +import java.lang.Double.max import java.math.RoundingMode import java.text.NumberFormat @@ -261,6 +262,8 @@ fun LabeledBar( formatter.roundingMode = RoundingMode.UP formatter.isGroupingUsed = true + val cleanedMaxVlaue = max(1.0, maxValue) + val animatedValue = animateFloatAsState( targetValue = value.toFloat(), animationSpec = ProgressIndicatorDefaults.ProgressAnimationSpec, @@ -274,7 +277,7 @@ fun LabeledBar( } Column(modifier = Modifier.weight(1f)) { LinearProgressIndicator( - progress = (animatedValue / maxValue).toFloat(), + progress = (animatedValue / cleanedMaxVlaue).toFloat(), Modifier .fillMaxWidth() .clip(CircleShape) @@ -288,7 +291,7 @@ fun LabeledBar( modifier = Modifier.padding(top = 2.dp) ) { Text( - "${formatter.format(animatedValue)} / ${formatter.format(maxValue)}", + "${formatter.format(animatedValue)} / ${formatter.format(cleanedMaxVlaue)}", fontSize = 12.sp, color = colorResource(R.color.text_ternary) ) diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/SegmentedControl.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/SegmentedControl.kt new file mode 100644 index 000000000..c13f53773 --- /dev/null +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/SegmentedControl.kt @@ -0,0 +1,139 @@ +package com.habitrpg.android.habitica.ui.views + +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.offset +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.wrapContentSize +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.ButtonDefaults +import androidx.compose.material.MaterialTheme +import androidx.compose.material.OutlinedButton +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import androidx.compose.ui.zIndex + +/** + * items : list of items to be render + * defaultSelectedItemIndex : to highlight item by default (Optional) + * useFixedWidth : set true if you want to set fix width to item (Optional) + * itemWidth : Provide item width if useFixedWidth is set to true (Optional) + * cornerRadius : To make control as rounded (Optional) + * color : Set color to control (Optional) + * onItemSelection : Get selected item index + */ +@Composable +fun SegmentedControl( + items: List, + defaultSelectedItemIndex: Int = 0, + useFixedWidth: Boolean = false, + itemWidth: Dp = 120.dp, + cornerRadius : Int = 10, + onItemSelection: (selectedItemIndex: Int) -> Unit +) { + val selectedIndex = remember { mutableStateOf(defaultSelectedItemIndex) } + val color = MaterialTheme.colors.primary + Row( + modifier = Modifier + ) { + items.forEachIndexed { index, item -> + OutlinedButton( + modifier = when (index) { + 0 -> { + if (useFixedWidth) { + Modifier + .width(itemWidth) + .offset(0.dp, 0.dp) + .zIndex(if (selectedIndex.value == 0) 1f else 0f) + } else { + Modifier + .wrapContentSize() + .offset(0.dp, 0.dp) + .zIndex(if (selectedIndex.value == 0) 1f else 0f) + } + } else -> { + if (useFixedWidth) + Modifier + .width(itemWidth) + .offset((-1 * index).dp, 0.dp) + .zIndex(if (selectedIndex.value == index) 1f else 0f) + else Modifier + .wrapContentSize() + .offset((-1 * index).dp, 0.dp) + .zIndex(if (selectedIndex.value == index) 1f else 0f) + } + }, + onClick = { + selectedIndex.value = index + onItemSelection(selectedIndex.value) + }, + shape = when (index) { + /** + * left outer button + */ + 0 -> RoundedCornerShape( + topStartPercent = cornerRadius, + topEndPercent = 0, + bottomStartPercent = cornerRadius, + bottomEndPercent = 0 + ) + /** + * right outer button + */ + items.size - 1 -> RoundedCornerShape( + topStartPercent = 0, + topEndPercent = cornerRadius, + bottomStartPercent = 0, + bottomEndPercent = cornerRadius + ) + /** + * middle button + */ + else -> RoundedCornerShape( + topStartPercent = 0, + topEndPercent = 0, + bottomStartPercent = 0, + bottomEndPercent = 0 + ) + }, + border = BorderStroke( + 1.dp, if (selectedIndex.value == index) { + color + } else { + color.copy(alpha = 0.75f) + } + ), + colors = if (selectedIndex.value == index) { + /** + * selected colors + */ + ButtonDefaults.outlinedButtonColors( + backgroundColor = color + ) + } else { + /** + * not selected colors + */ + ButtonDefaults.outlinedButtonColors(backgroundColor = Color.Transparent) + }, + ) { + Text( + text = item, + fontWeight = FontWeight.Normal, + color = if (selectedIndex.value == index) { + Color.White + } else { + color.copy(alpha = 0.9f) + }, + ) + } + } + } +} \ No newline at end of file diff --git a/build.gradle b/build.gradle index 75d747444..2df4a4e19 100644 --- a/build.gradle +++ b/build.gradle @@ -10,7 +10,7 @@ buildscript { amplitude_version = '3.35.1' appcompat_version = '1.5.1' coil_version = '2.1.0' - compose_version = '1.2.1' + compose_version = '1.3.0' core_ktx_version = '1.9.0' coroutines_version = '1.6.4' daggerhilt_version = '2.42' @@ -19,7 +19,7 @@ buildscript { lifecycle_version = '2.5.1' markwon_version = '4.6.2' moshi_version = '1.13.0' - navigation_version = '2.5.2' + navigation_version = '2.5.3' okhttp_version = '4.9.3' play_wearables_version = '18.0.0' play_auth_version = '20.3.0' @@ -35,7 +35,7 @@ buildscript { mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:7.3.0' + classpath 'com.android.tools.build:gradle:7.3.1' classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8' classpath 'com.google.gms:google-services:4.3.14' classpath 'com.google.firebase:firebase-crashlytics-gradle:2.9.2' @@ -44,7 +44,7 @@ buildscript { classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "io.gitlab.arturbosch.detekt:detekt-gradle-plugin:1.19.0" classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$navigation_version" - classpath 'com.google.firebase:perf-plugin:1.4.1' + classpath 'com.google.firebase:perf-plugin:1.4.2' classpath "com.google.dagger:hilt-android-gradle-plugin:$daggerhilt_version" } } From 576d81c868aa84ff1a13bf19a79dfd5378eae9ea Mon Sep 17 00:00:00 2001 From: Phillip Thelen Date: Wed, 2 Nov 2022 10:17:47 +0100 Subject: [PATCH 25/36] group task logic fixes # Conflicts: # Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/TaskRepositoryImpl.kt # version.properties --- .../data/implementation/TaskRepositoryImpl.kt | 12 ++++++------ .../android/habitica/models/tasks/Task.kt | 15 +++++++++++++-- .../ui/viewHolders/tasks/DailyViewHolder.kt | 2 +- 3 files changed, 20 insertions(+), 9 deletions(-) diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/TaskRepositoryImpl.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/TaskRepositoryImpl.kt index 3bb9717f1..565adaa79 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/TaskRepositoryImpl.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/TaskRepositoryImpl.kt @@ -9,17 +9,17 @@ 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 -import com.habitrpg.shared.habitica.models.responses.TaskDirection -import com.habitrpg.shared.habitica.models.responses.TaskDirectionData -import com.habitrpg.shared.habitica.models.responses.TaskScoringResult import com.habitrpg.android.habitica.models.tasks.ChecklistItem import com.habitrpg.android.habitica.models.tasks.Task import com.habitrpg.android.habitica.models.tasks.TaskList -import com.habitrpg.shared.habitica.models.tasks.TaskType -import com.habitrpg.shared.habitica.models.tasks.TasksOrder import com.habitrpg.android.habitica.models.user.OwnedItem import com.habitrpg.android.habitica.models.user.User import com.habitrpg.android.habitica.proxy.AnalyticsManager +import com.habitrpg.shared.habitica.models.responses.TaskDirection +import com.habitrpg.shared.habitica.models.responses.TaskDirectionData +import com.habitrpg.shared.habitica.models.responses.TaskScoringResult +import com.habitrpg.shared.habitica.models.tasks.TaskType +import com.habitrpg.shared.habitica.models.tasks.TasksOrder import io.reactivex.rxjava3.core.Flowable import io.reactivex.rxjava3.core.Maybe import io.reactivex.rxjava3.core.Single @@ -144,7 +144,7 @@ class TaskRepositoryImpl( if (bgTask.type != TaskType.REWARD && (bgTask.value - localDelta) + res.delta != bgTask.value) { bgTask.value = (bgTask.value - localDelta) + res.delta if (TaskType.DAILY == bgTask.type || TaskType.TODO == bgTask.type) { - bgTask.completed = up + bgTask.completeForUser(userID, up) if (TaskType.DAILY == bgTask.type) { if (up) { bgTask.streak = (bgTask.streak ?: 0) + 1 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 325f16dba..19fed52b9 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 @@ -119,15 +119,26 @@ open class Task : RealmObject, BaseMainObject, Parcelable, BaseTask { fun completed(byUserID: String?): Boolean { return if (isGroupTask) { - group?.assignedUsersDetail?.firstOrNull { it.assignedUserID == byUserID }?.completed ?: false + group?.assignedUsersDetail?.firstOrNull { it.assignedUserID == byUserID }?.completed ?: completed } else { completed } } + fun completeForUser(userID: String?, completed: Boolean) { + if (isGroupTask && group?.assignedUsersDetail?.isNotEmpty() == true) { + group?.assignedUsersDetail?.firstOrNull { it.assignedUserID == userID }?.completed = completed + if (group?.assignedUsersDetail?.filter { it.completed != completed }?.isEmpty() == true) { + this.completed = completed + } + } else { + this.completed = completed + } + } + fun isDisplayedActiveForUser(userID: String?): Boolean { val isActive = ((isDue == true && type == TaskType.DAILY) || type == TaskType.TODO) - return isActive && completed(userID) + return isActive && !completed(userID) } val streakString: String? diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/tasks/DailyViewHolder.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/tasks/DailyViewHolder.kt index cb4a1a6fa..d9cf2886d 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/tasks/DailyViewHolder.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/tasks/DailyViewHolder.kt @@ -70,7 +70,7 @@ class DailyViewHolder( } override fun shouldDisplayAsActive(task: Task?, userID: String?): Boolean { - return task?.isDisplayedActive ?: false + return task?.isDisplayedActiveForUser(userID) ?: false } override fun configureSpecialTaskTextView(task: Task) { From 4520248ed81660964713e056a58e549511dbb7e9 Mon Sep 17 00:00:00 2001 From: Phillip Thelen Date: Mon, 7 Nov 2022 13:37:43 +0100 Subject: [PATCH 26/36] More coroutine work --- Habitica/res/navigation/navigation.xml | 10 +++ Habitica/res/values/strings.xml | 1 + .../android/habitica/api/ApiService.kt | 4 +- .../habitica/components/UserComponent.java | 6 ++ .../android/habitica/data/ApiClient.kt | 4 +- .../android/habitica/data/TaskRepository.kt | 16 ++-- .../data/implementation/ApiClientImpl.kt | 8 +- .../data/implementation/TaskRepositoryImpl.kt | 90 +++++++++---------- .../data/local/TaskLocalRepository.kt | 7 +- .../RealmTaskLocalRepository.kt | 30 ++++--- .../habitica/helpers/TaskAlarmManager.kt | 14 ++- .../habitica/interactors/BuyRewardUseCase.kt | 17 ++-- .../android/habitica/interactors/UseCase.java | 26 ------ .../android/habitica/interactors/UseCase.kt | 28 ++++++ .../LocalNotificationActionReceiver.kt | 25 +++--- .../habitica/receivers/TaskReceiver.kt | 23 +++-- .../ui/activities/AdventureGuideActivity.kt | 4 +- .../habitica/ui/activities/ArmoireActivity.kt | 2 +- .../habitica/ui/activities/BaseActivity.kt | 14 +-- .../ui/activities/ChallengeFormActivity.kt | 2 +- .../ui/activities/ClassSelectionActivity.kt | 2 +- .../habitica/ui/activities/DeathActivity.kt | 2 +- .../activities/FixCharacterValuesActivity.kt | 2 +- .../ui/activities/FullProfileActivity.kt | 2 +- .../ui/activities/GiftGemsActivity.kt | 2 +- .../ui/activities/GiftSubscriptionActivity.kt | 2 +- .../ui/activities/GroupFormActivity.kt | 2 +- .../ui/activities/GroupInviteActivity.kt | 2 +- .../activities/HabitButtonWidgetActivity.kt | 2 +- .../habitica/ui/activities/IntroActivity.kt | 2 +- .../habitica/ui/activities/LoginActivity.kt | 2 +- .../habitica/ui/activities/MainActivity.kt | 2 +- .../ui/activities/MaintenanceActivity.kt | 2 +- .../ui/activities/NotificationsActivity.kt | 2 +- .../ui/activities/ReportMessageActivity.kt | 2 +- .../habitica/ui/activities/SetupActivity.kt | 2 +- .../ui/activities/SkillMemberActivity.kt | 2 +- .../ui/activities/SkillTasksActivity.kt | 2 +- .../ui/activities/TaskFormActivity.kt | 65 ++++++-------- .../ui/activities/TaskSummaryActivity.kt | 58 ++++++++++++ .../ui/activities/VerifyUsernameActivity.kt | 2 +- .../tasks/TaskRecyclerViewFragment.kt | 25 +++--- .../habitica/ui/viewmodels/TasksViewModel.kt | 6 +- .../habitica/ui/views/NPCBannerView.kt | 16 ++-- .../views/yesterdailies/YesterdailyDialog.kt | 20 +++-- .../widget/HabitButtonWidgetProvider.kt | 11 ++- .../widget/HabitButtonWidgetService.kt | 11 ++- .../habitica/widget/TaskListWidgetProvider.kt | 17 ++-- .../implementation/TaskRepositoryImplTest.kt | 4 +- 49 files changed, 349 insertions(+), 253 deletions(-) delete mode 100644 Habitica/src/main/java/com/habitrpg/android/habitica/interactors/UseCase.java create mode 100644 Habitica/src/main/java/com/habitrpg/android/habitica/interactors/UseCase.kt create mode 100644 Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/TaskSummaryActivity.kt diff --git a/Habitica/res/navigation/navigation.xml b/Habitica/res/navigation/navigation.xml index e11fc3bac..1379c226f 100644 --- a/Habitica/res/navigation/navigation.xml +++ b/Habitica/res/navigation/navigation.xml @@ -34,6 +34,16 @@ + + + + + + Show assigned and open tasks on your personal task lists Copy shared tasks Group Plan Settings + Task Summary You You, %d other diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/api/ApiService.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/api/ApiService.kt index 59160edf4..ab3fe3f3c 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/api/ApiService.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/api/ApiService.kt @@ -130,7 +130,7 @@ interface ApiService { fun getTask(@Path("id") id: String): Flowable> @POST("tasks/{id}/score/{direction}") - fun postTaskDirection(@Path("id") id: String, @Path("direction") direction: String): Flowable> + suspend fun postTaskDirection(@Path("id") id: String, @Path("direction") direction: String): HabitResponse @POST("tasks/bulk-score") fun bulkScoreTasks(@Body data: List>): Flowable> @@ -138,7 +138,7 @@ interface ApiService { fun postTaskNewPosition(@Path("id") id: String, @Path("position") position: Int): Flowable>> @POST("tasks/{taskId}/checklist/{itemId}/score") - fun scoreChecklistItem(@Path("taskId") taskId: String, @Path("itemId") itemId: String): Flowable> + suspend fun scoreChecklistItem(@Path("taskId") taskId: String, @Path("itemId") itemId: String): HabitResponse @POST("tasks/user") fun createTask(@Body item: Task): Flowable> diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/components/UserComponent.java b/Habitica/src/main/java/com/habitrpg/android/habitica/components/UserComponent.java index c3a3fef75..6cbab653f 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/components/UserComponent.java +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/components/UserComponent.java @@ -38,6 +38,8 @@ import com.habitrpg.android.habitica.ui.activities.SetupActivity; import com.habitrpg.android.habitica.ui.activities.SkillMemberActivity; import com.habitrpg.android.habitica.ui.activities.SkillTasksActivity; import com.habitrpg.android.habitica.ui.activities.TaskFormActivity; +import com.habitrpg.android.habitica.ui.activities.TaskSummaryActivity; +import com.habitrpg.android.habitica.ui.activities.TaskSummaryViewModel; import com.habitrpg.android.habitica.ui.activities.VerifyUsernameActivity; import com.habitrpg.android.habitica.ui.adapter.social.challenges.ChallengeTasksRecyclerViewAdapter; import com.habitrpg.android.habitica.ui.adapter.tasks.DailiesRecyclerViewHolder; @@ -364,4 +366,8 @@ public interface UserComponent { void inject(@NotNull DeathActivity deathActivity); void inject(@NotNull DeviceCommunicationService deviceCommunicationService); + + void inject(@NotNull TaskSummaryActivity taskSummaryActivity); + + void inject(@NotNull TaskSummaryViewModel taskSummaryViewModel); } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/ApiClient.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/ApiClient.kt index a4184fc9e..26dbacaac 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/ApiClient.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/ApiClient.kt @@ -93,12 +93,12 @@ interface ApiClient { fun getTask(id: String): Flowable - fun postTaskDirection(id: String, direction: String): Flowable + suspend fun postTaskDirection(id: String, direction: String): TaskDirectionData? fun bulkScoreTasks(data: List>): Flowable fun postTaskNewPosition(id: String, position: Int): Flowable> - fun scoreChecklistItem(taskId: String, itemId: String): Flowable + suspend fun scoreChecklistItem(taskId: String, itemId: String): Task? fun createTask(item: Task): Flowable diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/TaskRepository.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/TaskRepository.kt index a8e011813..9548b54b3 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/TaskRepository.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/TaskRepository.kt @@ -22,24 +22,24 @@ interface TaskRepository : BaseRepository { suspend fun retrieveTasks(userId: String, tasksOrder: TasksOrder): TaskList? fun retrieveTasks(userId: String, tasksOrder: TasksOrder, dueDate: Date): Flowable - fun taskChecked( + suspend fun taskChecked( user: User?, task: Task, up: Boolean, force: Boolean, notifyFunc: ((TaskScoringResult) -> Unit)? - ): Flowable - fun taskChecked( + ): TaskScoringResult? + suspend fun taskChecked( user: User?, taskId: String, up: Boolean, force: Boolean, notifyFunc: ((TaskScoringResult) -> Unit)? - ): Maybe - fun scoreChecklistItem(taskId: String, itemId: String): Flowable + ): TaskScoringResult? + suspend fun scoreChecklistItem(taskId: String, itemId: String): Task? - fun getTask(taskId: String): Flowable - fun getTaskCopy(taskId: String): Flowable + fun getTask(taskId: String): Flow + fun getTaskCopy(taskId: String): Flow fun createTask(task: Task, force: Boolean = false): Flowable fun updateTask(task: Task, force: Boolean = false): Maybe? fun deleteTask(taskId: String): Flowable @@ -55,7 +55,7 @@ interface TaskRepository : BaseRepository { fun updateTaskPosition(taskType: TaskType, taskID: String, newPosition: Int): Maybe> - fun getUnmanagedTask(taskid: String): Flowable + fun getUnmanagedTask(taskid: String): Flow fun updateTaskInBackground(task: Task) diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/ApiClientImpl.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/ApiClientImpl.kt index fb8226076..ceecbb0c4 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/ApiClientImpl.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/ApiClientImpl.kt @@ -451,8 +451,8 @@ class ApiClientImpl( return apiService.getTask(id).compose(configureApiCallObserver()) } - override fun postTaskDirection(id: String, direction: String): Flowable { - return apiService.postTaskDirection(id, direction).compose(configureApiCallObserver()) + override suspend fun postTaskDirection(id: String, direction: String): TaskDirectionData? { + return handleSuspendCall { apiService.postTaskDirection(id, direction) } } override fun bulkScoreTasks(data: List>): Flowable { @@ -463,8 +463,8 @@ class ApiClientImpl( return apiService.postTaskNewPosition(id, position).compose(configureApiCallObserver()) } - override fun scoreChecklistItem(taskId: String, itemId: String): Flowable { - return apiService.scoreChecklistItem(taskId, itemId).compose(configureApiCallObserver()) + override suspend fun scoreChecklistItem(taskId: String, itemId: String): Task? { + return handleSuspendCall { apiService.scoreChecklistItem(taskId, itemId) } } override fun createTask(item: Task): Flowable { diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/TaskRepositoryImpl.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/TaskRepositoryImpl.kt index 565adaa79..a6e0a8196 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/TaskRepositoryImpl.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/TaskRepositoryImpl.kt @@ -24,6 +24,7 @@ import io.reactivex.rxjava3.core.Flowable import io.reactivex.rxjava3.core.Maybe import io.reactivex.rxjava3.core.Single import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.map import java.text.SimpleDateFormat import java.util.Date @@ -70,13 +71,13 @@ class TaskRepositoryImpl( } @Suppress("ReturnCount") - override fun taskChecked( + override suspend fun taskChecked( user: User?, task: Task, up: Boolean, force: Boolean, notifyFunc: ((TaskScoringResult) -> Unit)? - ): Flowable { + ): TaskScoringResult? { val localData = if (user != null && appConfigManager.enableLocalTaskScoring()) { ScoreTaskLocallyInteractor.score(user, task, if (up) TaskDirection.UP else TaskDirection.DOWN) } else null @@ -90,41 +91,33 @@ class TaskRepositoryImpl( val now = Date().time val id = task.id if (lastTaskAction > now - 500 && !force || id == null) { - return Flowable.empty() + return null } lastTaskAction = now - return this.apiClient.postTaskDirection(id, (if (up) TaskDirection.UP else TaskDirection.DOWN).text) - .flatMapMaybe { - // There are cases where the user object is not set correctly. So the app refetches it as a fallback - if (user == null) { - localRepository.getUser(userID).firstElement() - } else { - Maybe.just(user) - }.map { user -> Pair(it, user) } - } - .map { (res, user): Pair -> - // save local task changes + val res = this.apiClient.postTaskDirection(id, (if (up) TaskDirection.UP else TaskDirection.DOWN).text) ?: return null + // There are cases where the user object is not set correctly. So the app refetches it as a fallback + val thisUser = user ?: localRepository.getUser(userID).firstOrNull() ?: return null + // save local task changes - analyticsManager.logEvent( - "task_scored", - bundleOf( - Pair("type", task.type), - Pair("scored_up", up), - Pair("value", task.value) - ) - ) - if (res.lvl == 0) { - // Team tasks that require approval have weird data that we should just ignore. - return@map TaskScoringResult() - } - val result = TaskScoringResult(res, user.stats) - if (localData == null) { - notifyFunc?.invoke(result) - } - handleTaskResponse(user, res, task, up, localData?.delta ?: 0f) - result - } + analyticsManager.logEvent( + "task_scored", + bundleOf( + Pair("type", task.type), + Pair("scored_up", up), + Pair("value", task.value) + ) + ) + if (res.lvl == 0) { + // Team tasks that require approval have weird data that we should just ignore. + return TaskScoringResult() + } + val result = TaskScoringResult(res, thisUser.stats) + if (localData == null) { + notifyFunc?.invoke(result) + } + handleTaskResponse(thisUser, res, task, up, localData?.delta ?: 0f) + return result } override fun bulkScoreTasks(data: List>): Flowable { @@ -195,31 +188,29 @@ class TaskRepositoryImpl( } } - override fun taskChecked( + override suspend fun taskChecked( user: User?, taskId: String, up: Boolean, force: Boolean, notifyFunc: ((TaskScoringResult) -> Unit)? - ): Maybe { - return localRepository.getTask(taskId).firstElement() - .flatMap { task -> taskChecked(user, task, up, force, notifyFunc).singleElement() } + ): TaskScoringResult? { + val task = localRepository.getTask(taskId).firstOrNull() ?: return null + return taskChecked(user, task, up, force, notifyFunc) } - override fun scoreChecklistItem(taskId: String, itemId: String): Flowable { - return apiClient.scoreChecklistItem(taskId, itemId) - .flatMapMaybe { localRepository.getTask(taskId).firstElement() } - .doOnNext { task -> - val updatedItem: ChecklistItem? = task.checklist?.lastOrNull { itemId == it.id } - if (updatedItem != null) { - localRepository.modify(updatedItem) { liveItem -> liveItem.completed = !liveItem.completed } - } - } + override suspend fun scoreChecklistItem(taskId: String, itemId: String): Task? { + val task = apiClient.scoreChecklistItem(taskId, itemId) + val updatedItem: ChecklistItem? = task?.checklist?.lastOrNull { itemId == it.id } + if (updatedItem != null) { + localRepository.modify(updatedItem) { liveItem -> liveItem.completed = !liveItem.completed } + } + return task } - override fun getTask(taskId: String): Flowable = localRepository.getTask(taskId) + override fun getTask(taskId: String) = localRepository.getTask(taskId) - override fun getTaskCopy(taskId: String): Flowable = localRepository.getTaskCopy(taskId) + override fun getTaskCopy(taskId: String) = localRepository.getTaskCopy(taskId) override fun createTask(task: Task, force: Boolean): Flowable { val now = Date().time @@ -310,8 +301,7 @@ class TaskRepositoryImpl( .doOnSuccess { localRepository.updateTaskPositions(it) } } - override fun getUnmanagedTask(taskid: String): Flowable = - getTask(taskid).map { localRepository.getUnmanagedCopy(it) } + override fun getUnmanagedTask(taskid: String) = getTask(taskid).map { localRepository.getUnmanagedCopy(it) } override fun updateTaskInBackground(task: Task) { updateTask(task).subscribe({ }, ExceptionHandler.rx()) diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/TaskLocalRepository.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/TaskLocalRepository.kt index 4697c2ead..6f8b80a60 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/TaskLocalRepository.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/TaskLocalRepository.kt @@ -19,8 +19,8 @@ interface TaskLocalRepository : BaseLocalRepository { fun deleteTask(taskID: String) - fun getTask(taskId: String): Flowable - fun getTaskCopy(taskId: String): Flowable + fun getTask(taskId: String): Flow + fun getTaskCopy(taskId: String): Flow fun markTaskCompleted(taskId: String, isCompleted: Boolean) @@ -33,6 +33,7 @@ interface TaskLocalRepository : BaseLocalRepository { fun updateTaskPositions(taskOrder: List) fun saveCompletedTodos(userId: String, tasks: MutableCollection) fun getErroredTasks(userID: String): Flowable> - fun getUser(userID: String): Flowable + fun getUserFlowable(userID: String): Flowable + fun getUser(userID: String): Flow fun getTasksForChallenge(challengeID: String?, userID: String?): Flowable> } 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 d7b66a56c..6d97a4e61 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 @@ -18,6 +18,8 @@ import io.realm.kotlin.toFlow import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.filterNotNull +import kotlinx.coroutines.flow.map class RealmTaskLocalRepository(realm: Realm) : RealmBaseLocalRepository(realm), TaskLocalRepository { @@ -187,19 +189,17 @@ class RealmTaskLocalRepository(realm: Realm) : RealmBaseLocalRepository(realm), } } - override fun getTask(taskId: String): Flowable { + override fun getTask(taskId: String): Flow { if (realm.isClosed) { - return Flowable.empty() + return emptyFlow() } - return RxJavaBridge.toV3Flowable( - realm.where(Task::class.java).equalTo("id", taskId).findAll().asFlowable() - .filter { realmObject -> realmObject.isLoaded && realmObject.isNotEmpty() } - .map { it.first() } - .cast(Task::class.java) - ) + return realm.where(Task::class.java).equalTo("id", taskId).findAll().toFlow() + .filter { realmObject -> realmObject.isLoaded && realmObject.isNotEmpty() } + .map { it.first() } + .filterNotNull() } - override fun getTaskCopy(taskId: String): Flowable { + override fun getTaskCopy(taskId: String): Flow { return getTask(taskId) .map { task -> return@map if (task.isManaged && task.isValid) { @@ -270,7 +270,7 @@ class RealmTaskLocalRepository(realm: Realm) : RealmBaseLocalRepository(realm), ).retry(1) } - override fun getUser(userID: String): Flowable { + override fun getUserFlowable(userID: String): Flowable { return RxJavaBridge.toV3Flowable( realm.where(User::class.java) .equalTo("id", userID) @@ -281,6 +281,16 @@ class RealmTaskLocalRepository(realm: Realm) : RealmBaseLocalRepository(realm), ) } + override fun getUser(userID: String): Flow { + return realm.where(User::class.java) + .equalTo("id", userID) + .findAll() + .toFlow() + .filter { realmObject -> realmObject.isLoaded && realmObject.isValid && !realmObject.isEmpty() } + .map { users -> users.first() } + .filterNotNull() + } + override fun getTasksForChallenge(challengeID: String?, userID: String?): Flowable> { return RxJavaBridge.toV3Flowable( realm.where(Task::class.java) diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/TaskAlarmManager.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/TaskAlarmManager.kt index 44791f639..060f5ef1a 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/TaskAlarmManager.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/TaskAlarmManager.kt @@ -16,7 +16,11 @@ import com.habitrpg.android.habitica.receivers.TaskReceiver import com.habitrpg.shared.habitica.HLogger import com.habitrpg.shared.habitica.LogLevel import com.habitrpg.shared.habitica.models.tasks.TaskType +import kotlinx.coroutines.MainScope +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.firstOrNull +import kotlinx.coroutines.launch import java.time.ZoneId import java.time.ZonedDateTime import java.time.format.DateTimeFormatter @@ -55,10 +59,12 @@ class TaskAlarmManager( // We currently only use this function to schedule the next reminder for dailies // We may be able to use repeating alarms instead of this in the future fun addAlarmForTaskId(taskId: String) { - taskRepository.getTaskCopy(taskId) - .filter { task -> task.isValid && task.isManaged && TaskType.DAILY == task.type } - .firstElement() - .subscribe({ this.setAlarmsForTask(it) }, ExceptionHandler.rx()) + MainScope().launch(ExceptionHandler.coroutine()) { + val task = taskRepository.getTaskCopy(taskId) + .filter { task -> task.isValid && task.isManaged && TaskType.DAILY == task.type } + .first() + setAlarmsForTask(task) + } } suspend fun scheduleAllSavedAlarms(preventDailyReminder: Boolean) { diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/interactors/BuyRewardUseCase.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/interactors/BuyRewardUseCase.kt index 8f0c30ac5..617c50a8a 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/interactors/BuyRewardUseCase.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/interactors/BuyRewardUseCase.kt @@ -1,30 +1,27 @@ package com.habitrpg.android.habitica.interactors import com.habitrpg.android.habitica.data.TaskRepository -import com.habitrpg.android.habitica.executors.PostExecutionThread import com.habitrpg.android.habitica.helpers.SoundManager -import com.habitrpg.shared.habitica.models.responses.TaskScoringResult import com.habitrpg.android.habitica.models.tasks.Task import com.habitrpg.android.habitica.models.user.User -import io.reactivex.rxjava3.core.Flowable +import com.habitrpg.shared.habitica.models.responses.TaskScoringResult import javax.inject.Inject class BuyRewardUseCase @Inject constructor( private val taskRepository: TaskRepository, private val soundManager: SoundManager, - postExecutionThread: PostExecutionThread -) : UseCase(postExecutionThread) { +) : FlowUseCase() { - override fun buildUseCaseObservable(requestValues: RequestValues): Flowable { - return taskRepository - .taskChecked(requestValues.user, requestValues.task, false, false, requestValues.notifyFunc) - .doOnNext { soundManager.loadAndPlayAudio(SoundManager.SoundReward) } + override suspend fun run(requestValues: RequestValues): TaskScoringResult? { + val response = taskRepository.taskChecked(requestValues.user, requestValues.task, false, false, requestValues.notifyFunc) + soundManager.loadAndPlayAudio(SoundManager.SoundReward) + return response } class RequestValues( internal val user: User?, val task: Task, val notifyFunc: (TaskScoringResult) -> Unit - ) : UseCase.RequestValues + ) : FlowUseCase.RequestValues } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/interactors/UseCase.java b/Habitica/src/main/java/com/habitrpg/android/habitica/interactors/UseCase.java deleted file mode 100644 index 7f4088282..000000000 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/interactors/UseCase.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.habitrpg.android.habitica.interactors; - -import com.habitrpg.android.habitica.executors.PostExecutionThread; - -import io.reactivex.rxjava3.core.Flowable; - -public abstract class UseCase { - - private final PostExecutionThread postExecutionThread; - - protected UseCase(PostExecutionThread postExecutionThread) { - this.postExecutionThread = postExecutionThread; - } - - protected abstract Flowable buildUseCaseObservable(Q requestValues); - - public Flowable observable(Q requestValues) { - return this.buildUseCaseObservable(requestValues) - .subscribeOn(postExecutionThread.getScheduler()) - .observeOn(postExecutionThread.getScheduler()); - } - - public interface RequestValues { - - } -} diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/interactors/UseCase.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/interactors/UseCase.kt new file mode 100644 index 000000000..d60e1ed32 --- /dev/null +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/interactors/UseCase.kt @@ -0,0 +1,28 @@ +package com.habitrpg.android.habitica.interactors + +import com.habitrpg.android.habitica.executors.PostExecutionThread +import io.reactivex.rxjava3.core.Flowable +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext + +abstract class UseCase protected constructor(private val postExecutionThread: PostExecutionThread) { + protected abstract fun buildUseCaseObservable(requestValues: Q): Flowable + fun observable(requestValues: Q): Flowable { + return buildUseCaseObservable(requestValues) + .subscribeOn(postExecutionThread.scheduler) + .observeOn(postExecutionThread.scheduler) + } + + interface RequestValues +} + +abstract class FlowUseCase { + protected abstract suspend fun run(requestValues: Q): T + suspend fun observable(requestValues: Q): T { + return withContext(Dispatchers.IO) { + run(requestValues) + } + } + + interface RequestValues +} \ No newline at end of file diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/receivers/LocalNotificationActionReceiver.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/receivers/LocalNotificationActionReceiver.kt index a20769fa4..4ecc891e7 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/receivers/LocalNotificationActionReceiver.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/receivers/LocalNotificationActionReceiver.kt @@ -113,16 +113,17 @@ class LocalNotificationActionReceiver : BroadcastReceiver() { } context?.getString(R.string.complete_task_action) -> { taskID?.let { - taskRepository.taskChecked(null, it, up = true, force = false) { - }.subscribe({ - val pair = NotifyUserUseCase.getNotificationAndAddStatsToUserAsText( - it.experienceDelta, - it.healthDelta, - it.goldDelta, - it.manaDelta - ) - showToast(pair.first) - }, ExceptionHandler.rx()) + MainScope().launch(ExceptionHandler.coroutine()) { + taskRepository.taskChecked(null, it, up = true, force = false) { + val pair = NotifyUserUseCase.getNotificationAndAddStatsToUserAsText( + it.experienceDelta, + it.healthDelta, + it.goldDelta, + it.manaDelta + ) + showToast(pair.first) + } + } } } } @@ -134,6 +135,8 @@ class LocalNotificationActionReceiver : BroadcastReceiver() { } private fun getMessageText(key: String?): String? { - return intent?.let { RemoteInput.getResultsFromIntent(it)?.getCharSequence(key)?.toString() } + return intent?.let { + RemoteInput.getResultsFromIntent(it)?.getCharSequence(key)?.toString() + } } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/receivers/TaskReceiver.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/receivers/TaskReceiver.kt index 839925764..b5dfbebb6 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/receivers/TaskReceiver.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/receivers/TaskReceiver.kt @@ -21,7 +21,9 @@ import com.habitrpg.android.habitica.ui.activities.MainActivity import com.habitrpg.shared.habitica.HLogger import com.habitrpg.shared.habitica.LogLevel import com.habitrpg.shared.habitica.models.tasks.TaskType -import io.reactivex.rxjava3.functions.Consumer +import kotlinx.coroutines.MainScope +import kotlinx.coroutines.flow.firstOrNull +import kotlinx.coroutines.launch import javax.inject.Inject class TaskReceiver : BroadcastReceiver() { @@ -43,18 +45,13 @@ class TaskReceiver : BroadcastReceiver() { taskAlarmManager.addAlarmForTaskId(taskId) } - taskRepository.getTask(taskId ?: "") - .firstElement() - .subscribe( - Consumer { - if (it.isUpdatedToday && it.completed) { - return@Consumer - } - - createNotification(context, it) - }, - ExceptionHandler.rx() - ) + MainScope().launch(ExceptionHandler.coroutine()) { + val task = taskRepository.getTask(taskId ?: "").firstOrNull() ?: return@launch + if (task.isUpdatedToday && task.completed) { + return@launch + } + createNotification(context, task) + } } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/AdventureGuideActivity.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/AdventureGuideActivity.kt index 15b6b6907..55564e5c6 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/AdventureGuideActivity.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/AdventureGuideActivity.kt @@ -16,8 +16,8 @@ import com.habitrpg.android.habitica.databinding.AdventureGuideItemBinding import com.habitrpg.android.habitica.extensions.fromHtml import com.habitrpg.android.habitica.helpers.AmplitudeManager import com.habitrpg.android.habitica.models.user.User -import com.habitrpg.common.habitica.extensions.loadImage import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel +import com.habitrpg.common.habitica.extensions.loadImage import javax.inject.Inject class AdventureGuideActivity : BaseActivity() { @@ -33,7 +33,7 @@ class AdventureGuideActivity : BaseActivity() { return R.layout.activity_main } - override fun getContentView(): View { + override fun getContentView(layoutResId: Int?): View { binding = ActivityAdventureGuideBinding.inflate(layoutInflater) return binding.root } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/ArmoireActivity.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/ArmoireActivity.kt index 63d6d453f..c09c999f1 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/ArmoireActivity.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/ArmoireActivity.kt @@ -49,7 +49,7 @@ class ArmoireActivity : BaseActivity() { component?.inject(this) } - override fun getContentView(): View { + override fun getContentView(layoutResId: Int?): View { binding = ActivityArmoireBinding.inflate(layoutInflater) return binding.root } 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 95bbb7b9f..0e63b948f 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,11 +26,9 @@ 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.updateStatusBarColor -import com.habitrpg.android.habitica.helpers.NotificationsManager import com.habitrpg.android.habitica.helpers.ExceptionHandler +import com.habitrpg.android.habitica.helpers.NotificationsManager import com.habitrpg.android.habitica.interactors.ShowNotificationInteractor import com.habitrpg.android.habitica.proxy.AnalyticsManager import com.habitrpg.android.habitica.ui.helpers.ToolbarColorHelper @@ -62,10 +60,10 @@ abstract class BaseActivity : AppCompatActivity() { internal var toolbar: Toolbar? = null - protected abstract fun getLayoutResId(): Int + protected abstract fun getLayoutResId(): Int? - open fun getContentView(): View { - return (getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater).inflate(getLayoutResId(), null) + open fun getContentView(layoutResId: Int? = getLayoutResId()): View { + return (getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater).inflate(layoutResId ?: 0, null) } var compositeSubscription = CompositeDisposable() @@ -94,7 +92,9 @@ abstract class BaseActivity : AppCompatActivity() { super.onCreate(savedInstanceState) habiticaApplication injectActivity(HabiticaBaseApplication.userComponent) - setContentView(getContentView()) + getLayoutResId()?.let { + setContentView(getContentView(it)) + } compositeSubscription = CompositeDisposable() compositeSubscription.add(notificationsManager.displayNotificationEvents.subscribe( { diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/ChallengeFormActivity.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/ChallengeFormActivity.kt index 4889bfcb8..9e56e29c7 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/ChallengeFormActivity.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/ChallengeFormActivity.kt @@ -78,7 +78,7 @@ class ChallengeFormActivity : BaseActivity() { private var savingInProgress = false - override fun getContentView(): View { + override fun getContentView(layoutResId: Int?): View { binding = ActivityCreateChallengeBinding.inflate(layoutInflater) return binding.root } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/ClassSelectionActivity.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/ClassSelectionActivity.kt index 492cce57c..5011641e4 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/ClassSelectionActivity.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/ClassSelectionActivity.kt @@ -60,7 +60,7 @@ class ClassSelectionActivity : BaseActivity() { return R.layout.activity_class_selection } - override fun getContentView(): View { + override fun getContentView(layoutResId: Int?): View { binding = ActivityClassSelectionBinding.inflate(layoutInflater) return binding.root } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/DeathActivity.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/DeathActivity.kt index 70c204ba7..4e035a944 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/DeathActivity.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/DeathActivity.kt @@ -40,7 +40,7 @@ class DeathActivity: BaseActivity() { component?.inject(this) } - override fun getContentView(): View { + override fun getContentView(layoutResId: Int?): View { binding = ActivityDeathBinding.inflate(layoutInflater) return binding.root } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/FixCharacterValuesActivity.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/FixCharacterValuesActivity.kt index ba36246ca..7a1286c8c 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/FixCharacterValuesActivity.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/FixCharacterValuesActivity.kt @@ -27,7 +27,7 @@ class FixCharacterValuesActivity : BaseActivity() { override fun getLayoutResId(): Int = R.layout.activity_fixcharacter - override fun getContentView(): View { + override fun getContentView(layoutResId: Int?): View { binding = ActivityFixcharacterBinding.inflate(layoutInflater) return binding.root } 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 1fd8b47ce..4b598252a 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 @@ -496,7 +496,7 @@ class FullProfileActivity : BaseActivity() { return R.layout.activity_full_profile } - override fun getContentView(): View { + override fun getContentView(layoutResId: Int?): View { binding = ActivityFullProfileBinding.inflate(layoutInflater) return binding.root } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/GiftGemsActivity.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/GiftGemsActivity.kt index 8917adaa1..a894a2d8c 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/GiftGemsActivity.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/GiftGemsActivity.kt @@ -49,7 +49,7 @@ class GiftGemsActivity : PurchaseActivity() { return R.layout.activity_gift_gems } - override fun getContentView(): View { + override fun getContentView(layoutResId: Int?): View { binding = ActivityGiftGemsBinding.inflate(layoutInflater) return binding.root } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/GiftSubscriptionActivity.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/GiftSubscriptionActivity.kt index a429ade2c..fc755c822 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/GiftSubscriptionActivity.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/GiftSubscriptionActivity.kt @@ -46,7 +46,7 @@ class GiftSubscriptionActivity : PurchaseActivity() { component?.inject(this) } - override fun getContentView(): View { + override fun getContentView(layoutResId: Int?): View { binding = ActivityGiftSubscriptionBinding.inflate(layoutInflater) return binding.root } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/GroupFormActivity.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/GroupFormActivity.kt index 726cc1a50..3ebc9adc9 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/GroupFormActivity.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/GroupFormActivity.kt @@ -28,7 +28,7 @@ class GroupFormActivity : BaseActivity() { return R.layout.activity_group_form } - override fun getContentView(): View { + override fun getContentView(layoutResId: Int?): View { binding = ActivityGroupFormBinding.inflate(layoutInflater) return binding.root } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/GroupInviteActivity.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/GroupInviteActivity.kt index b75dedd99..8ad8fbf1e 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/GroupInviteActivity.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/GroupInviteActivity.kt @@ -38,7 +38,7 @@ class GroupInviteActivity : BaseActivity() { return R.layout.activity_party_invite } - override fun getContentView(): View { + override fun getContentView(layoutResId: Int?): View { binding = ActivityPartyInviteBinding.inflate(layoutInflater) return binding.root } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/HabitButtonWidgetActivity.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/HabitButtonWidgetActivity.kt index 392a71189..1b7c805a8 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/HabitButtonWidgetActivity.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/HabitButtonWidgetActivity.kt @@ -42,7 +42,7 @@ class HabitButtonWidgetActivity : BaseActivity() { return R.layout.widget_configure_habit_button } - override fun getContentView(): View { + override fun getContentView(layoutResId: Int?): View { binding = WidgetConfigureHabitButtonBinding.inflate(layoutInflater) return binding.root } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/IntroActivity.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/IntroActivity.kt index 6610a3f62..1d1786216 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/IntroActivity.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/IntroActivity.kt @@ -32,7 +32,7 @@ class IntroActivity : BaseActivity(), View.OnClickListener, ViewPager.OnPageChan return R.layout.activity_intro } - override fun getContentView(): View { + override fun getContentView(layoutResId: Int?): View { binding = ActivityIntroBinding.inflate(layoutInflater) return binding.root } 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 6ea4c7f23..e2ec696a9 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 @@ -113,7 +113,7 @@ class LoginActivity : BaseActivity() { return R.layout.activity_login } - override fun getContentView(): View { + override fun getContentView(layoutResId: Int?): View { binding = ActivityLoginBinding.inflate(layoutInflater) return binding.root } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/MainActivity.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/MainActivity.kt index b39b10e16..64dcf69f2 100755 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/MainActivity.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/MainActivity.kt @@ -119,7 +119,7 @@ open class MainActivity : BaseActivity(), SnackbarActivity { return R.layout.activity_main } - override fun getContentView(): View { + override fun getContentView(layoutResId: Int?): View { binding = ActivityMainBinding.inflate(layoutInflater) return binding.root } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/MaintenanceActivity.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/MaintenanceActivity.kt index cc972c6cc..386d8cd5f 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/MaintenanceActivity.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/MaintenanceActivity.kt @@ -32,7 +32,7 @@ class MaintenanceActivity : BaseActivity() { return R.layout.activity_maintenance } - override fun getContentView(): View { + override fun getContentView(layoutResId: Int?): View { binding = ActivityMaintenanceBinding.inflate(layoutInflater) return binding.root } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/NotificationsActivity.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/NotificationsActivity.kt index 819453959..6ccfc37ea 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/NotificationsActivity.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/NotificationsActivity.kt @@ -51,7 +51,7 @@ class NotificationsActivity : BaseActivity(), androidx.swiperefreshlayout.widget override fun getLayoutResId(): Int = R.layout.activity_notifications - override fun getContentView(): View { + override fun getContentView(layoutResId: Int?): View { binding = ActivityNotificationsBinding.inflate(layoutInflater) return binding.root } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/ReportMessageActivity.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/ReportMessageActivity.kt index 3d70206cb..b08d214da 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/ReportMessageActivity.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/ReportMessageActivity.kt @@ -41,7 +41,7 @@ class ReportMessageActivity : BaseActivity() { component?.inject(this) } - override fun getContentView(): View { + override fun getContentView(layoutResId: Int?): View { binding = ActivityReportMessageBinding.inflate(layoutInflater) return binding.root } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/SetupActivity.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/SetupActivity.kt index 99b23dbc3..7f078676c 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/SetupActivity.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/SetupActivity.kt @@ -64,7 +64,7 @@ class SetupActivity : BaseActivity(), ViewPager.OnPageChangeListener { return R.layout.activity_setup } - override fun getContentView(): View { + override fun getContentView(layoutResId: Int?): View { binding = ActivitySetupBinding.inflate(layoutInflater) return binding.root } 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 a041329ec..dc4a75519 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 @@ -36,7 +36,7 @@ class SkillMemberActivity : BaseActivity() { component?.inject(this) } - override fun getContentView(): View { + override fun getContentView(layoutResId: Int?): View { binding = ActivitySkillMembersBinding.inflate(layoutInflater) return binding.root } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/SkillTasksActivity.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/SkillTasksActivity.kt index 176fe5364..10c8a9062 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/SkillTasksActivity.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/SkillTasksActivity.kt @@ -41,7 +41,7 @@ class SkillTasksActivity : BaseActivity() { loadTaskLists() } - override fun getContentView(): View { + override fun getContentView(layoutResId: Int?): View { binding = ActivitySkillTasksBinding.inflate(layoutInflater) return binding.root } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/TaskFormActivity.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/TaskFormActivity.kt index 70fe0fc0a..a00a30eeb 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/TaskFormActivity.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/TaskFormActivity.kt @@ -31,8 +31,6 @@ 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.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 @@ -48,9 +46,9 @@ import com.habitrpg.shared.habitica.models.tasks.Frequency 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.flow.firstOrNull import kotlinx.coroutines.launch -import java.util.* +import java.util.Date import javax.inject.Inject class TaskFormActivity : BaseActivity() { @@ -105,7 +103,7 @@ class TaskFormActivity : BaseActivity() { return R.layout.activity_task_form } - override fun getContentView(): View { + override fun getContentView(layoutResId: Int?): View { binding = ActivityTaskFormBinding.inflate(layoutInflater) return binding.root } @@ -203,36 +201,32 @@ class TaskFormActivity : BaseActivity() { when { taskId != null -> { isCreating = false - compositeSubscription.add( - taskRepository.getUnmanagedTask(taskId).firstElement().subscribe( - { - if (!it.isValid) return@subscribe - task = it - initialTaskInstance = it - // tintColor = ContextCompat.getColor(this, it.mediumTaskColor) - fillForm(it) - it.challengeID?.let { challengeID -> - compositeSubscription.add( - challengeRepository.retrieveChallenge(challengeID) - .subscribe( - { challenge -> - this.challenge = challenge - binding.challengeNameView.text = getString(R.string.challenge_task_name, challenge.name) - binding.challengeNameView.visibility = View.VISIBLE - disableEditingForUneditableFieldsInChallengeTask() - }, - ExceptionHandler.rx() - ) + lifecycleScope.launch(ExceptionHandler.coroutine()) { + val task = taskRepository.getUnmanagedTask(taskId).firstOrNull() ?: return@launch + if (!task.isValid) return@launch + this@TaskFormActivity.task = task + initialTaskInstance = task + // tintColor = ContextCompat.getColor(this, it.mediumTaskColor) + fillForm(task) + task.challengeID?.let { challengeID -> + compositeSubscription.add( + challengeRepository.retrieveChallenge(challengeID) + .subscribe( + { challenge -> + this@TaskFormActivity.challenge = challenge + binding.challengeNameView.text = getString(R.string.challenge_task_name, challenge.name) + binding.challengeNameView.visibility = View.VISIBLE + disableEditingForUneditableFieldsInChallengeTask() + }, + ExceptionHandler.rx() ) - } - }, - ExceptionHandler.rx() - ) - ) + ) + } + } } bundle.containsKey(PARCELABLE_TASK) -> { isCreating = false - task = bundle.getParcelable(PARCELABLE_TASK) + task = bundle.getParcelable(PARCELABLE_TASK, Task::class.java) task?.let { fillForm(it) } } else -> { @@ -547,16 +541,13 @@ class TaskFormActivity : BaseActivity() { resultIntent.putExtra(TASK_TYPE_KEY, taskType.value) if (!isChallengeTask) { if (isCreating) { - if (isDiscardCancelled) { - analyticsManager.logEvent("back_to_task", bundleOf(Pair("is_creating", isCreating))) - } taskRepository.createTaskInBackground(thisTask) } else { - if (isDiscardCancelled) { - analyticsManager.logEvent("back_to_task", bundleOf(Pair("is_creating", isCreating))) - } taskRepository.updateTaskInBackground(thisTask) } + if (isDiscardCancelled) { + analyticsManager.logEvent("back_to_task", bundleOf(Pair("is_creating", isCreating))) + } if (thisTask.type == TaskType.DAILY || thisTask.type == TaskType.TODO) { taskAlarmManager.scheduleAlarmsForTask(thisTask) diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/TaskSummaryActivity.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/TaskSummaryActivity.kt new file mode 100644 index 000000000..0e53ac81f --- /dev/null +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/TaskSummaryActivity.kt @@ -0,0 +1,58 @@ +package com.habitrpg.android.habitica.ui.activities + +import android.os.Bundle +import androidx.activity.compose.setContent +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Column +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.livedata.observeAsState +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.lifecycle.asLiveData +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.ui.viewmodels.BaseViewModel +import javax.inject.Inject + +class TaskSummaryViewModel(val taskId: String) : BaseViewModel() { + @Inject + lateinit var taskRespository: TaskRepository + val task = taskRespository.getTask(taskId).asLiveData() + + override fun inject(component: UserComponent) { + component.inject(this) + } +} + +class TaskSummaryActivity: BaseActivity() { + override fun getLayoutResId(): Int? = null + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContent { + + } + } + + override fun injectActivity(component: UserComponent?) { + component?.inject(this) + } +} + +@Composable +fun TaskSummaryView(viewModel: TaskSummaryViewModel) { + val task by viewModel.task.observeAsState() + Column { + Text(stringResource(R.string.task_summary), Modifier) + } +} + +@Preview +@Composable +private fun TaskSummaryViewPreview() { + TaskSummaryView(TaskSummaryViewModel("")) +} \ No newline at end of file diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/VerifyUsernameActivity.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/VerifyUsernameActivity.kt index 83d7c26f4..fde4b03c7 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/VerifyUsernameActivity.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/VerifyUsernameActivity.kt @@ -39,7 +39,7 @@ class VerifyUsernameActivity : BaseActivity() { return R.layout.activity_verify_username } - override fun getContentView(): View { + override fun getContentView(layoutResId: Int?): View { binding = ActivityVerifyUsernameBinding.inflate(layoutInflater) return binding.root } 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 42c17ed82..23f0e279f 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 @@ -1,7 +1,6 @@ package com.habitrpg.android.habitica.ui.fragments.tasks import android.content.Context -import android.content.Intent import android.content.SharedPreferences import android.graphics.drawable.BitmapDrawable import android.os.Bundle @@ -30,6 +29,7 @@ import com.habitrpg.android.habitica.helpers.HapticFeedbackManager import com.habitrpg.android.habitica.helpers.MainNavigationController import com.habitrpg.android.habitica.helpers.NotificationsManager import com.habitrpg.android.habitica.helpers.SoundManager +import com.habitrpg.android.habitica.models.tasks.ChecklistItem import com.habitrpg.android.habitica.models.tasks.Task import com.habitrpg.android.habitica.ui.activities.MainActivity import com.habitrpg.android.habitica.ui.activities.TaskFormActivity @@ -141,10 +141,9 @@ open class TaskRecyclerViewFragment : BaseFragment notificationsManager.dismissTaskNotification(it1, it.first) } }?.subscribeWithErrorHandler { scoreTask(it.first, it.second) }?.let { recyclerSubscription.add(it) } - recyclerAdapter?.checklistItemScoreEvents - ?.flatMap { - taskRepository.scoreChecklistItem(it.first.id ?: "", it.second.id ?: "") - }?.subscribeWithErrorHandler {}?.let { recyclerSubscription.add(it) } + recyclerAdapter?.checklistItemScoreEvents?.subscribeWithErrorHandler { + scoreChecklistItem(it.first, it.second) + }?.let { recyclerSubscription.add(it) } recyclerAdapter?.brokenTaskEvents?.subscribeWithErrorHandler { showBrokenChallengeDialog(it) }?.let { recyclerSubscription.add(it) } recyclerAdapter?.adventureGuideOpenEvents?.subscribeWithErrorHandler { MainNavigationController.navigate(R.id.adventureGuideActivity) }?.let { recyclerSubscription.add(it) } @@ -163,6 +162,12 @@ open class TaskRecyclerViewFragment : BaseFragment @@ -530,12 +535,12 @@ open class TaskRecyclerViewFragment : BaseFragment Unit ) { - compositeSubscription.add( + viewModelScope.launch(ExceptionHandler.coroutine()) { taskRepository.taskChecked( null, task.id ?: "", @@ -134,8 +134,8 @@ class TasksViewModel : BaseViewModel(), GroupPlanInfoProvider { putLong("last_task_reporting", Date().time) } } - }.subscribe({}, ExceptionHandler.rx()) - ) + } + } } private val filterSets: HashMap>>> = diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/NPCBannerView.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/NPCBannerView.kt index 2c894bdb0..ce2f650c4 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/NPCBannerView.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/NPCBannerView.kt @@ -10,11 +10,10 @@ import android.widget.ImageView import androidx.core.graphics.drawable.toBitmap import com.habitrpg.android.habitica.R import com.habitrpg.android.habitica.databinding.NpcBannerBinding -import com.habitrpg.android.habitica.helpers.ExceptionHandler import com.habitrpg.common.habitica.extensions.DataBindingUtils import com.habitrpg.common.habitica.extensions.layoutInflater -import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers -import io.reactivex.rxjava3.core.Observable +import kotlinx.coroutines.MainScope +import kotlinx.coroutines.launch import kotlin.math.roundToInt class NPCBannerView(context: Context, attrs: AttributeSet?) : FrameLayout(context, attrs) { @@ -50,14 +49,9 @@ class NPCBannerView(context: Context, attrs: AttributeSet?) : FrameLayout(contex val width = (height * aspectRatio).roundToInt() val drawable = BitmapDrawable(context.resources, Bitmap.createScaledBitmap(it.toBitmap(), width, height, false)) drawable.tileModeX = Shader.TileMode.REPEAT - Observable.just(drawable) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe( - { - binding.backgroundView.background = it - }, - ExceptionHandler.rx() - ) + MainScope().launch { + binding.backgroundView.background = it + } } } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/yesterdailies/YesterdailyDialog.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/yesterdailies/YesterdailyDialog.kt index 7ccb35cd3..155833d03 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/yesterdailies/YesterdailyDialog.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/yesterdailies/YesterdailyDialog.kt @@ -9,6 +9,7 @@ import android.widget.ImageView import android.widget.LinearLayout import android.widget.TextView import androidx.core.content.ContextCompat +import androidx.lifecycle.lifecycleScope import com.habitrpg.android.habitica.R import com.habitrpg.android.habitica.data.TaskRepository import com.habitrpg.android.habitica.data.UserRepository @@ -22,7 +23,7 @@ import com.habitrpg.common.habitica.extensions.isUsingNightModeResources import com.habitrpg.shared.habitica.models.tasks.TaskType import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers import io.reactivex.rxjava3.core.Observable -import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.MainScope import kotlinx.coroutines.launch import java.lang.ref.WeakReference import java.util.Calendar @@ -83,7 +84,7 @@ class YesterdailyDialog private constructor( } } lastCronRun = Date() - GlobalScope.launch(ExceptionHandler.coroutine()) { + MainScope().launch(ExceptionHandler.coroutine()) { userRepository.runCron(completedTasks) } displayedDialog = null @@ -141,12 +142,12 @@ class YesterdailyDialog private constructor( val checkboxHolder = checklistView.findViewById(R.id.checkBoxHolder) as? ViewGroup checkboxHolder?.setOnClickListener { _ -> item.completed = !item.completed - taskRepository.scoreChecklistItem(task.id ?: "", item.id ?: "").subscribe({ }, ExceptionHandler.rx()) + scoreChecklistItem(task, item) configureChecklistView(checklistView, task, item) } checklistView.setOnClickListener { item.completed = !item.completed - taskRepository.scoreChecklistItem(task.id ?: "", item.id ?: "").subscribe({ }, ExceptionHandler.rx()) + scoreChecklistItem(task, item) configureChecklistView(checklistView, task, item) } checkboxHolder?.setBackgroundResource( @@ -175,6 +176,15 @@ class YesterdailyDialog private constructor( ) } + private fun scoreChecklistItem( + task: Task, + item: ChecklistItem + ) { + lifecycleScope.launch(ExceptionHandler.coroutine()) { + taskRepository.scoreChecklistItem(task.id ?: "", item.id ?: "") + } + } + private fun configureTaskView(taskView: View, task: Task) { val completed = !task.isDisplayedActive val checkmark = taskView.findViewById(R.id.checkmark) @@ -274,7 +284,7 @@ class YesterdailyDialog private constructor( ) } else { lastCronRun = Date() - GlobalScope.launch(ExceptionHandler.coroutine()) { + MainScope().launch(ExceptionHandler.coroutine()) { userRepository.runCron() } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/widget/HabitButtonWidgetProvider.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/widget/HabitButtonWidgetProvider.kt index cd14793f1..2684fd174 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/widget/HabitButtonWidgetProvider.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/widget/HabitButtonWidgetProvider.kt @@ -10,6 +10,9 @@ import com.habitrpg.android.habitica.R import com.habitrpg.android.habitica.data.TaskRepository import com.habitrpg.android.habitica.helpers.ExceptionHandler import com.habitrpg.shared.habitica.models.responses.TaskDirection +import kotlinx.coroutines.MainScope +import kotlinx.coroutines.flow.firstOrNull +import kotlinx.coroutines.launch import javax.inject.Inject class HabitButtonWidgetProvider : BaseWidgetProvider() { @@ -72,8 +75,12 @@ class HabitButtonWidgetProvider : BaseWidgetProvider() { val ids = intArrayOf(appWidgetId) if (taskId != null) { - userRepository.getUserFlowable().firstElement().flatMap { user -> taskRepository.taskChecked(user, taskId, TaskDirection.UP.text == direction, false, null) } - .subscribe({ taskDirectionData -> showToastForTaskDirection(context, taskDirectionData) }, ExceptionHandler.rx(), { this.onUpdate(context, mgr, ids) }) + MainScope().launch(ExceptionHandler.coroutine()) { + val user = userRepository.getUser().firstOrNull() + val response = taskRepository.taskChecked(user, taskId, TaskDirection.UP.text == direction, false, null) + showToastForTaskDirection(context, response) + this@HabitButtonWidgetProvider.onUpdate(context, mgr, ids) + } } } super.onReceive(context, intent) diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/widget/HabitButtonWidgetService.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/widget/HabitButtonWidgetService.kt index 21786a1d5..52c469901 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/widget/HabitButtonWidgetService.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/widget/HabitButtonWidgetService.kt @@ -20,6 +20,9 @@ import com.habitrpg.android.habitica.helpers.ExceptionHandler import com.habitrpg.android.habitica.models.tasks.Task import com.habitrpg.common.habitica.helpers.MarkdownParser import com.habitrpg.shared.habitica.models.responses.TaskDirection +import kotlinx.coroutines.MainScope +import kotlinx.coroutines.flow.firstOrNull +import kotlinx.coroutines.launch import javax.inject.Inject import kotlin.math.min @@ -42,10 +45,14 @@ class HabitButtonWidgetService : Service() { allWidgetIds = appWidgetManager?.getAppWidgetIds(thisWidget) makeTaskMapping() - for (taskid in this.taskMapping.keys) { - taskRepository.getUnmanagedTask(taskid).firstElement().subscribe({ this.updateData(it) }, ExceptionHandler.rx()) + MainScope().launch(ExceptionHandler.coroutine()) { + for (taskid in taskMapping.keys) { + val task = taskRepository.getUnmanagedTask(taskid).firstOrNull() + updateData(task) + } } + stopSelf() return START_STICKY diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/widget/TaskListWidgetProvider.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/widget/TaskListWidgetProvider.kt index 342b5894c..72bda08f9 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/widget/TaskListWidgetProvider.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/widget/TaskListWidgetProvider.kt @@ -14,6 +14,9 @@ import com.habitrpg.android.habitica.extensions.withImmutableFlag import com.habitrpg.android.habitica.extensions.withMutableFlag import com.habitrpg.android.habitica.helpers.ExceptionHandler import com.habitrpg.android.habitica.ui.activities.MainActivity +import kotlinx.coroutines.MainScope +import kotlinx.coroutines.flow.firstOrNull +import kotlinx.coroutines.launch import javax.inject.Inject abstract class TaskListWidgetProvider : BaseWidgetProvider() { @@ -44,14 +47,12 @@ abstract class TaskListWidgetProvider : BaseWidgetProvider() { val taskId = intent.getStringExtra(TASK_ID_ITEM) if (taskId != null) { - userRepository.getUserFlowable().firstElement().flatMap { user -> taskRepository.taskChecked(user, taskId, up = true, force = false, notifyFunc = null) } - .subscribe( - { taskDirectionData -> - showToastForTaskDirection(context, taskDirectionData) - AppWidgetManager.getInstance(context).notifyAppWidgetViewDataChanged(appWidgetId, R.id.list_view) - }, - ExceptionHandler.rx() - ) + MainScope().launch(ExceptionHandler.coroutine()) { + val user = userRepository.getUser().firstOrNull() + val response = taskRepository.taskChecked(user, taskId, up = true, force = false, notifyFunc = null) + showToastForTaskDirection(context, response) + AppWidgetManager.getInstance(context).notifyAppWidgetViewDataChanged(appWidgetId, R.id.list_view) + } } } super.onReceive(context, intent) diff --git a/Habitica/src/test/java/com/habitrpg/android/habitica/data/implementation/TaskRepositoryImplTest.kt b/Habitica/src/test/java/com/habitrpg/android/habitica/data/implementation/TaskRepositoryImplTest.kt index 02905f207..bfa0ada14 100644 --- a/Habitica/src/test/java/com/habitrpg/android/habitica/data/implementation/TaskRepositoryImplTest.kt +++ b/Habitica/src/test/java/com/habitrpg/android/habitica/data/implementation/TaskRepositoryImplTest.kt @@ -81,10 +81,10 @@ class TaskRepositoryImplTest : WordSpec({ every { apiClient.postTaskDirection(any(), "up") } returns Flowable.just( TaskDirectionData() ) - every { localRepository.getUser("") } returns Flowable.just(user) + every { localRepository.getUserFlowable("") } returns Flowable.just(user) repository.taskChecked(null, task, true, false, null) eventually(5000) { - localRepository.getUser("") + localRepository.getUserFlowable("") } } "does not update user for team tasks" { From 7e8366a3ce1d606c53f5e175a6f718056c58e3c8 Mon Sep 17 00:00:00 2001 From: Phillip Thelen Date: Wed, 2 Nov 2022 10:17:47 +0100 Subject: [PATCH 27/36] group task logic fixes # Conflicts: # Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/TaskRepositoryImpl.kt # version.properties From 4dfc7ec1a1c025ec7106169575d22dfedaff9b8c Mon Sep 17 00:00:00 2001 From: Phillip Thelen Date: Tue, 15 Nov 2022 13:59:49 +0100 Subject: [PATCH 28/36] fix crash # Conflicts: # Habitica/src/main/java/com/habitrpg/android/habitica/helpers/AppConfigManager.kt --- Habitica/AndroidManifest.xml | 1 - .../habitica/helpers/AppConfigManager.kt | 16 ++++++---- .../habitica/helpers/TaskAlarmManager.kt | 6 +++- .../habitica/ui/fragments/NewsFragment.kt | 32 +++++++++++++++++-- 4 files changed, 45 insertions(+), 10 deletions(-) diff --git a/Habitica/AndroidManifest.xml b/Habitica/AndroidManifest.xml index 447f79bdd..39e9edf19 100644 --- a/Habitica/AndroidManifest.xml +++ b/Habitica/AndroidManifest.xml @@ -30,7 +30,6 @@ android:value="@string/application_ad_id"/> () { @@ -29,13 +32,38 @@ class NewsFragment : BaseMainFragment() { return super.onCreateView(inflater, container, savedInstanceState) } + private val webviewClient = object : WebViewClient() { + + override fun shouldOverrideUrlLoading(view: WebView?, url: String?): Boolean { + if (url?.contains("/static/new-stuff") == true) { + view?.loadUrl(url) + } else if (url != null) { + MainNavigationController.navigate(url) + } + return true + } + + override fun shouldOverrideUrlLoading( + view: WebView?, + request: WebResourceRequest? + ): Boolean { + if (request?.url?.path == "/static/new-stuff") { + view?.loadUrl(request.url.toString()) + } else { + request?.url?.let { MainNavigationController.navigate(it) } + } + return true + } + } + @SuppressLint("SetJavaScriptEnabled") override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - val address = if (BuildConfig.DEBUG) BuildConfig.BASE_URL else context?.getString(R.string.base_url) + val address = context?.getString(R.string.base_url) val webSettings = binding?.newsWebview?.settings webSettings?.javaScriptEnabled = true webSettings?.domStorageEnabled = true + binding?.newsWebview?.webViewClient = webviewClient binding?.newsWebview?.webChromeClient = object : WebChromeClient() { } binding?.newsWebview?.loadUrl("$address/static/new-stuff") From cedc06f1e938ea592f02d8f06159e898aede3d60 Mon Sep 17 00:00:00 2001 From: Phillip Thelen Date: Tue, 15 Nov 2022 16:06:43 +0100 Subject: [PATCH 29/36] port more code to coroutines --- .../android/habitica/api/ApiService.kt | 33 ++-- .../android/habitica/data/ApiClient.kt | 29 ++-- .../habitica/data/InventoryRepository.kt | 16 +- .../android/habitica/data/UserRepository.kt | 27 ++-- .../data/implementation/ApiClientImpl.kt | 90 +++++------ .../implementation/InventoryRepositoryImpl.kt | 83 +++++----- .../data/implementation/UserRepositoryImpl.kt | 147 +++++++++--------- .../data/local/UserLocalRepository.kt | 3 +- .../RealmUserLocalRepository.kt | 52 ++++--- .../habitica/helpers/ExceptionHandler.kt | 6 + .../android/habitica/models/tasks/Task.kt | 4 + .../habitica/ui/activities/ArmoireActivity.kt | 30 ++-- .../habitica/ui/activities/DeathActivity.kt | 9 +- .../habitica/ui/activities/SetupActivity.kt | 22 +-- .../ui/activities/VerifyUsernameActivity.kt | 11 +- .../CustomizationRecyclerViewAdapter.kt | 15 +- .../setup/CustomizationSetupAdapter.kt | 5 +- .../habitica/ui/fragments/NewsFragment.kt | 8 +- .../habitica/ui/fragments/StatsFragment.kt | 17 +- .../AvatarCustomizationFragment.kt | 17 +- .../customization/AvatarOverviewFragment.kt | 8 +- .../inventory/items/ItemRecyclerFragment.kt | 17 +- .../preferences/AccountPreferenceFragment.kt | 18 ++- .../EmailNotificationsPreferencesFragment.kt | 10 +- .../preferences/PreferencesFragment.kt | 42 +++-- .../PushNotificationsPreferencesFragment.kt | 7 +- .../ui/fragments/setup/AvatarSetupFragment.kt | 14 +- .../ui/fragments/skills/SkillsFragment.kt | 20 +-- .../fragments/support/SupportMainFragment.kt | 10 +- .../tasks/RewardsRecyclerviewFragment.kt | 40 +++-- .../habitica/ui/viewmodels/BaseViewModel.kt | 7 +- .../ui/viewmodels/MainUserViewModel.kt | 12 +- .../ui/views/dialogs/PetSuggestHatchDialog.kt | 26 ++-- .../habitica/ui/views/shops/PurchaseDialog.kt | 90 +++++------ shared/build.gradle.kts | 2 +- .../shared/habitica/PlatformLogger.kt | 4 + .../com/habitrpg/shared/habitica/Logger.kt | 4 +- .../shared/habitica/PlatformLogger.kt | 12 +- 38 files changed, 495 insertions(+), 472 deletions(-) diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/api/ApiService.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/api/ApiService.kt index ab3fe3f3c..8fb909988 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/api/ApiService.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/api/ApiService.kt @@ -72,41 +72,38 @@ interface ApiService { suspend fun getContent(@Query("language") language: String?): HabitResponse @PUT("user/") - fun updateUser(@Body updateDictionary: Map): Flowable> + suspend fun updateUser(@Body updateDictionary: Map): HabitResponse @PUT("user/") fun registrationLanguage(@Header("Accept-Language") registrationLanguage: String): Flowable> @GET("user/in-app-rewards") - fun retrieveInAppRewards(): Flowable>> - - @GET("user/inventory/buy") - fun retrieveOldGearRewards(): Flowable>> + suspend fun retrieveInAppRewards(): HabitResponse> @POST("user/equip/{type}/{key}") fun equipItem(@Path("type") type: String, @Path("key") itemKey: String): Flowable> @POST("user/buy/{key}") - fun buyItem(@Path("key") itemKey: String, @Body quantity: Map): Flowable> + suspend fun buyItem(@Path("key") itemKey: String, @Body quantity: Map): HabitResponse @POST("user/purchase/{type}/{key}") - fun purchaseItem( + suspend fun purchaseItem( @Path("type") type: String, @Path("key") itemKey: String, @Body quantity: Map - ): Flowable> + ): HabitResponse @POST("user/purchase-hourglass/{type}/{key}") - fun purchaseHourglassItem(@Path("type") type: String, @Path("key") itemKey: String): Flowable> + suspend fun purchaseHourglassItem(@Path("type") type: String, @Path("key") itemKey: String): HabitResponse @POST("user/buy-mystery-set/{key}") - fun purchaseMysterySet(@Path("key") itemKey: String): Flowable> + suspend fun purchaseMysterySet(@Path("key") itemKey: String): HabitResponse @POST("user/buy-quest/{key}") - fun purchaseQuest(@Path("key") key: String): Flowable> + suspend fun purchaseQuest(@Path("key") key: String): HabitResponse @POST("user/buy-special-spell/{key}") - fun purchaseSpecialSpell(@Path("key") key: String): Flowable> + suspend fun purchaseSpecialSpell(@Path("key") key: String): HabitResponse @POST("user/sell/{type}/{key}") fun sellItem(@Path("type") itemType: String, @Path("key") itemKey: String): Flowable> @@ -124,7 +121,7 @@ interface ApiService { fun getTasks(@Query("type") type: String, @Query("dueDate") dueDate: String): Flowable> @POST("user/unlock") - fun unlockPath(@Query("path") path: String): Flowable> + suspend fun unlockPath(@Query("path") path: String): HabitResponse @GET("tasks/{id}") fun getTask(@Path("id") id: String): Flowable> @@ -183,14 +180,14 @@ interface ApiService { suspend fun revive(): HabitResponse @POST("user/class/cast/{skill}") - fun useSkill( + suspend fun useSkill( @Path("skill") skillName: String, @Query("targetType") targetType: String, @Query("targetId") targetId: String - ): Flowable> + ): HabitResponse @POST("user/class/cast/{skill}") - fun useSkill(@Path("skill") skillName: String, @Query("targetType") targetType: String): Flowable> + suspend fun useSkill(@Path("skill") skillName: String, @Query("targetType") targetType: String): HabitResponse @POST("user/change-class") suspend fun changeClass(): HabitResponse @@ -407,13 +404,13 @@ interface ApiService { fun deleteAccount(@Body body: Map): Flowable> @GET("user/toggle-pinned-item/{pinType}/{path}") - fun togglePinnedItem(@Path("pinType") pinType: String, @Path("path") path: String): Flowable> + suspend fun togglePinnedItem(@Path("pinType") pinType: String, @Path("path") path: String): HabitResponse @POST("user/reset-password") fun sendPasswordResetEmail(@Body data: Map): Flowable> @PUT("user/auth/update-username") - fun updateLoginName(@Body data: Map): Flowable> + suspend fun updateLoginName(@Body data: Map): HabitResponse @POST("user/auth/verify-username") fun verifyUsername(@Body data: Map): Flowable> diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/ApiClient.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/ApiClient.kt index 26dbacaac..907486b5e 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/ApiClient.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/ApiClient.kt @@ -58,25 +58,24 @@ interface ApiClient { fun setLanguageCode(languageCode: String) suspend fun getContent(language: String? = null): ContentResult? - fun updateUser(updateDictionary: Map): Flowable + suspend fun updateUser(updateDictionary: Map): User? fun registrationLanguage(registrationLanguage: String): Flowable - fun retrieveInAppRewards(): Flowable> - fun retrieveOldGear(): Flowable> + suspend fun retrieveInAppRewards(): List? fun equipItem(type: String, itemKey: String): Flowable - fun buyItem(itemKey: String, purchaseQuantity: Int): Flowable + suspend fun buyItem(itemKey: String, purchaseQuantity: Int): BuyResponse? - fun purchaseItem(type: String, itemKey: String, purchaseQuantity: Int): Flowable + suspend fun purchaseItem(type: String, itemKey: String, purchaseQuantity: Int): Void? - fun purchaseHourglassItem(type: String, itemKey: String): Flowable + suspend fun purchaseHourglassItem(type: String, itemKey: String): Void? - fun purchaseMysterySet(itemKey: String): Flowable + suspend fun purchaseMysterySet(itemKey: String): Void? - fun purchaseQuest(key: String): Flowable - fun purchaseSpecialSpell(key: String): Flowable + suspend fun purchaseQuest(key: String): Void? + suspend fun purchaseSpecialSpell(key: String): Void? fun validateSubscription(request: PurchaseValidationRequest): Flowable fun validateNoRenewSubscription(request: PurchaseValidationRequest): Flowable suspend fun cancelSubscription(): Void? @@ -89,7 +88,7 @@ interface ApiClient { fun getTasks(type: String): Flowable fun getTasks(type: String, dueDate: String): Flowable - fun unlockPath(path: String): Flowable + suspend fun unlockPath(path: String): UnlockResponse? fun getTask(id: String): Flowable @@ -126,9 +125,9 @@ interface ApiClient { suspend fun sleep(): Boolean? suspend fun revive(): User? - fun useSkill(skillName: String, targetType: String, targetId: String): Flowable + suspend fun useSkill(skillName: String, targetType: String, targetId: String): SkillResponse? - fun useSkill(skillName: String, targetType: String): Flowable + suspend fun useSkill(skillName: String, targetType: String): SkillResponse? suspend fun changeClass(className: String?): User? @@ -251,12 +250,12 @@ interface ApiClient { fun resetAccount(): Flowable fun deleteAccount(password: String): Flowable - fun togglePinnedItem(pinType: String, path: String): Flowable + suspend fun togglePinnedItem(pinType: String, path: String): Void? fun sendPasswordResetEmail(email: String): Flowable - fun updateLoginName(newLoginName: String, password: String): Flowable - fun updateUsername(newLoginName: String): Flowable + suspend fun updateLoginName(newLoginName: String, password: String): Void? + suspend fun updateUsername(newLoginName: String): Void? fun updateEmail(newEmail: String, password: String): Flowable diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/InventoryRepository.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/InventoryRepository.kt index 8fa5eea2c..7cacbe430 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/InventoryRepository.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/InventoryRepository.kt @@ -39,7 +39,7 @@ interface InventoryRepository : BaseRepository { fun getQuestContent(keys: List): Flow> fun getEquipment(searchedKeys: List): Flowable> - fun retrieveInAppRewards(): Flowable> + suspend fun retrieveInAppRewards(): List? fun getOwnedEquipment(type: String): Flowable> fun getEquipmentType(type: String, set: String): Flowable> @@ -71,21 +71,21 @@ interface InventoryRepository : BaseRepository { fun inviteToQuest(quest: QuestContent): Flowable - fun buyItem(user: User?, id: String, value: Double, purchaseQuantity: Int): Flowable + suspend fun buyItem(user: User?, id: String, value: Double, purchaseQuantity: Int): BuyResponse? fun retrieveShopInventory(identifier: String): Flowable fun retrieveMarketGear(): Flowable - fun purchaseMysterySet(categoryIdentifier: String): Flowable + suspend fun purchaseMysterySet(categoryIdentifier: String): Void? - fun purchaseHourglassItem(purchaseType: String, key: String): Flowable + suspend fun purchaseHourglassItem(purchaseType: String, key: String): Void? - fun purchaseQuest(key: String): Flowable - fun purchaseSpecialSpell(key: String): Flowable + suspend fun purchaseQuest(key: String): Void? + suspend fun purchaseSpecialSpell(key: String): Void? - fun purchaseItem(purchaseType: String, key: String, purchaseQuantity: Int): Flowable + suspend fun purchaseItem(purchaseType: String, key: String, purchaseQuantity: Int): Void? - fun togglePinnedItem(item: ShopItem): Flowable> + suspend fun togglePinnedItem(item: ShopItem): List? fun getItems(itemClass: Class, keys: Array): Flow> fun getItemsFlowable(itemClass: Class): Flowable> fun getItems(itemClass: Class): Flow> diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/UserRepository.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/UserRepository.kt index 2c957f717..220c675a0 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/UserRepository.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/UserRepository.kt @@ -1,6 +1,5 @@ package com.habitrpg.android.habitica.data -import com.habitrpg.android.habitica.models.user.UserQuestStatus import com.habitrpg.android.habitica.models.Achievement import com.habitrpg.android.habitica.models.QuestAchievement import com.habitrpg.android.habitica.models.Skill @@ -8,14 +7,14 @@ import com.habitrpg.android.habitica.models.TeamPlan import com.habitrpg.android.habitica.models.inventory.Customization import com.habitrpg.android.habitica.models.responses.SkillResponse import com.habitrpg.android.habitica.models.responses.UnlockResponse -import com.habitrpg.shared.habitica.models.responses.VerifyUsernameResponse import com.habitrpg.android.habitica.models.social.Group -import com.habitrpg.shared.habitica.models.tasks.Attribute import com.habitrpg.android.habitica.models.tasks.Task import com.habitrpg.android.habitica.models.user.Stats import com.habitrpg.android.habitica.models.user.User +import com.habitrpg.android.habitica.models.user.UserQuestStatus +import com.habitrpg.shared.habitica.models.responses.VerifyUsernameResponse +import com.habitrpg.shared.habitica.models.tasks.Attribute import io.reactivex.rxjava3.core.Flowable -import io.reactivex.rxjava3.core.Maybe import kotlinx.coroutines.flow.Flow interface UserRepository : BaseRepository { @@ -23,14 +22,14 @@ interface UserRepository : BaseRepository { fun getUserFlowable(): Flowable fun getUser(userID: String): Flow - fun updateUser(updateData: Map): Flowable - fun updateUser(key: String, value: Any): Flowable + suspend fun updateUser(updateData: Map): User? + suspend fun updateUser(key: String, value: Any): User? suspend fun retrieveUser(withTasks: Boolean = false, forced: Boolean = false, overrideExisting: Boolean = false): User? suspend fun revive(): User? - fun resetTutorial(): Maybe + suspend fun resetTutorial(): User? suspend fun sleep(user: User): User? @@ -38,14 +37,14 @@ interface UserRepository : BaseRepository { fun getSpecialItems(user: User): Flowable> - fun useSkill(key: String, target: String?, taskId: String): Flowable - fun useSkill(key: String, target: String?): Flowable + suspend fun useSkill(key: String, target: String?, taskId: String): SkillResponse? + suspend fun useSkill(key: String, target: String?): SkillResponse? suspend fun disableClasses(): User? suspend fun changeClass(selectedClass: String? = null): User? - fun unlockPath(path: String, price: Int): Flowable - fun unlockPath(customization: Customization): Flowable + suspend fun unlockPath(path: String, price: Int): UnlockResponse? + suspend fun unlockPath(customization: Customization): UnlockResponse? suspend fun runCron(tasks: MutableList) suspend fun runCron() @@ -56,14 +55,14 @@ interface UserRepository : BaseRepository { fun changeCustomDayStart(dayStartTime: Int): Flowable - fun updateLanguage(languageCode: String): Flowable + suspend fun updateLanguage(languageCode: String): User? suspend fun resetAccount(): User? fun deleteAccount(password: String): Flowable fun sendPasswordResetEmail(email: String): Flowable - fun updateLoginName(newLoginName: String, password: String? = null): Maybe + suspend fun updateLoginName(newLoginName: String, password: String? = null): User? fun updateEmail(newEmail: String, password: String): Flowable fun updatePassword(oldPassword: String, newPassword: String, newPasswordConfirmation: String): Flowable fun verifyUsername(username: String): Flowable @@ -71,7 +70,7 @@ interface UserRepository : BaseRepository { fun allocatePoint(stat: Attribute): Flowable fun bulkAllocatePoints(strength: Int, intelligence: Int, constitution: Int, perception: Int): Flowable - fun useCustomization(type: String, category: String?, identifier: String): Flowable + suspend fun useCustomization(type: String, category: String?, identifier: String): User? fun retrieveAchievements(): Flowable> fun getAchievements(): Flow> fun getQuestAchievements(): Flow> diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/ApiClientImpl.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/ApiClientImpl.kt index ceecbb0c4..c42556b06 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/ApiClientImpl.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/ApiClientImpl.kt @@ -105,7 +105,7 @@ class ApiClientImpl( return habitResponse.data } - suspend fun handleSuspendCall(apiCall: suspend () -> HabitResponse): T? { + private suspend fun process(apiCall: suspend () -> HabitResponse): T? { try { return processResponse(apiCall()) } catch (throwable: Throwable) { @@ -280,14 +280,14 @@ class ApiClientImpl( } override suspend fun retrieveUser(withTasks: Boolean): User? { - val user = handleSuspendCall { apiService.getUser() } + val user = process { apiService.getUser() } val tasks = getTasks() user?.tasks = tasks return user } override suspend fun retrieveInboxMessages(uuid: String, page: Int): List? { - return handleSuspendCall { apiService.getInboxMessages(uuid, page) } + return process { apiService.getInboxMessages(uuid, page) } } override fun retrieveInboxConversations(): Flowable> { @@ -346,34 +346,30 @@ class ApiClientImpl( this.languageCode = languageCode } - override suspend fun getStatus(): Status? = handleSuspendCall { apiService.getStatus() } + override suspend fun getStatus(): Status? = process { apiService.getStatus() } override suspend fun getContent(language: String?): ContentResult? { - return handleSuspendCall { apiService.getContent(language) } + return process { apiService.getContent(language) } } - override fun updateUser(updateDictionary: Map): Flowable { - return apiService.updateUser(updateDictionary).compose(configureApiCallObserver()) + override suspend fun updateUser(updateDictionary: Map): User? { + return process { apiService.updateUser(updateDictionary) } } override fun registrationLanguage(registrationLanguage: String): Flowable { return apiService.registrationLanguage(registrationLanguage).compose(configureApiCallObserver()) } - override fun retrieveInAppRewards(): Flowable> { - return apiService.retrieveInAppRewards().compose(configureApiCallObserver()) - } - - override fun retrieveOldGear(): Flowable> { - return apiService.retrieveOldGearRewards().compose(configureApiCallObserver()) + override suspend fun retrieveInAppRewards(): List? { + return process { apiService.retrieveInAppRewards() } } override fun equipItem(type: String, itemKey: String): Flowable { return apiService.equipItem(type, itemKey).compose(configureApiCallObserver()) } - override fun buyItem(itemKey: String, purchaseQuantity: Int): Flowable { - return apiService.buyItem(itemKey, mapOf(Pair("quantity", purchaseQuantity))).compose(configureApiCallObserver()) + override suspend fun buyItem(itemKey: String, purchaseQuantity: Int): BuyResponse? { + return process { apiService.buyItem(itemKey, mapOf(Pair("quantity", purchaseQuantity))) } } override fun unlinkAllTasks(challengeID: String?, keepOption: String): Flowable { @@ -384,8 +380,8 @@ class ApiClientImpl( return apiService.blockMember(userID).compose(configureApiCallObserver()) } - override fun purchaseItem(type: String, itemKey: String, purchaseQuantity: Int): Flowable { - return apiService.purchaseItem(type, itemKey, mapOf(Pair("quantity", purchaseQuantity))).compose(configureApiCallObserver()) + override suspend fun purchaseItem(type: String, itemKey: String, purchaseQuantity: Int): Void? { + return process { apiService.purchaseItem(type, itemKey, mapOf(Pair("quantity", purchaseQuantity))) } } override fun validateSubscription(request: PurchaseValidationRequest): Flowable { @@ -400,20 +396,20 @@ class ApiClientImpl( return processResponse(apiService.cancelSubscription()) } - override fun purchaseHourglassItem(type: String, itemKey: String): Flowable { - return apiService.purchaseHourglassItem(type, itemKey).compose(configureApiCallObserver()) + override suspend fun purchaseHourglassItem(type: String, itemKey: String): Void? { + return process { apiService.purchaseHourglassItem(type, itemKey) } } - override fun purchaseMysterySet(itemKey: String): Flowable { - return apiService.purchaseMysterySet(itemKey).compose(configureApiCallObserver()) + override suspend fun purchaseMysterySet(itemKey: String): Void? { + return process { apiService.purchaseMysterySet(itemKey) } } - override fun purchaseQuest(key: String): Flowable { - return apiService.purchaseQuest(key).compose(configureApiCallObserver()) + override suspend fun purchaseQuest(key: String): Void? { + return process { apiService.purchaseQuest(key) } } - override fun purchaseSpecialSpell(key: String): Flowable { - return apiService.purchaseSpecialSpell(key).compose(configureApiCallObserver()) + override suspend fun purchaseSpecialSpell(key: String): Void? { + return process { apiService.purchaseSpecialSpell(key) } } override fun sellItem(itemType: String, itemKey: String): Flowable { @@ -433,7 +429,7 @@ class ApiClientImpl( return apiService.hatchPet(eggKey, hatchingPotionKey).compose(configureApiCallObserver()) } - override suspend fun getTasks(): TaskList? = handleSuspendCall { apiService.getTasks() } + override suspend fun getTasks(): TaskList? = process { apiService.getTasks() } override fun getTasks(type: String): Flowable { return apiService.getTasks(type).compose(configureApiCallObserver()) @@ -443,8 +439,8 @@ class ApiClientImpl( return apiService.getTasks(type, dueDate).compose(configureApiCallObserver()) } - override fun unlockPath(path: String): Flowable { - return apiService.unlockPath(path).compose(configureApiCallObserver()) + override suspend fun unlockPath(path: String): UnlockResponse? { + return process { apiService.unlockPath(path) } } override fun getTask(id: String): Flowable { @@ -452,7 +448,7 @@ class ApiClientImpl( } override suspend fun postTaskDirection(id: String, direction: String): TaskDirectionData? { - return handleSuspendCall { apiService.postTaskDirection(id, direction) } + return process { apiService.postTaskDirection(id, direction) } } override fun bulkScoreTasks(data: List>): Flowable { @@ -464,7 +460,7 @@ class ApiClientImpl( } override suspend fun scoreChecklistItem(taskId: String, itemId: String): Task? { - return handleSuspendCall { apiService.scoreChecklistItem(taskId, itemId) } + return process { apiService.scoreChecklistItem(taskId, itemId) } } override fun createTask(item: Task): Flowable { @@ -495,20 +491,20 @@ class ApiClientImpl( return apiService.deleteTag(id).compose(configureApiCallObserver()) } - override suspend fun sleep(): Boolean? = handleSuspendCall { apiService.sleep() } + override suspend fun sleep(): Boolean? = process { apiService.sleep() } - override suspend fun revive(): User? = handleSuspendCall { apiService.revive() } + override suspend fun revive(): User? = process { apiService.revive() } - override fun useSkill(skillName: String, targetType: String, targetId: String): Flowable { - return apiService.useSkill(skillName, targetType, targetId).compose(configureApiCallObserver()) + suspend override fun useSkill(skillName: String, targetType: String, targetId: String): SkillResponse? { + return process { apiService.useSkill(skillName, targetType, targetId) } } - override fun useSkill(skillName: String, targetType: String): Flowable { - return apiService.useSkill(skillName, targetType).compose(configureApiCallObserver()) + suspend override fun useSkill(skillName: String, targetType: String): SkillResponse? { + return process { apiService.useSkill(skillName, targetType) } } override suspend fun changeClass(className: String?): User? { - return handleSuspendCall { + return process { if (className != null) { apiService.changeClass(className) } else { @@ -517,7 +513,7 @@ class ApiClientImpl( } } - override suspend fun disableClasses(): User? = handleSuspendCall { apiService.disableClasses() } + override suspend fun disableClasses(): User? = process { apiService.disableClasses() } override fun markPrivateMessagesRead(): Flowable { // This is necessary, because the API call returns weird data. @@ -650,7 +646,7 @@ class ApiClientImpl( } override suspend fun postPrivateMessage(messageDetails: Map): PostChatMessageResult? { - return handleSuspendCall { apiService.postPrivateMessage(messageDetails) } + return process { apiService.postPrivateMessage(messageDetails) } } override fun retrieveShopIventory(identifier: String): Flowable { @@ -733,7 +729,7 @@ class ApiClientImpl( return apiService.runCron().compose(configureApiCallObserver()) } - override suspend fun reroll(): User? = handleSuspendCall { apiService.reroll() } + override suspend fun reroll(): User? = process { apiService.reroll() } override fun resetAccount(): Flowable { return apiService.resetAccount().compose(configureApiCallObserver()) @@ -745,8 +741,8 @@ class ApiClientImpl( return apiService.deleteAccount(updateObject).compose(configureApiCallObserver()) } - override fun togglePinnedItem(pinType: String, path: String): Flowable { - return apiService.togglePinnedItem(pinType, path).compose(configureApiCallObserver()) + override suspend fun togglePinnedItem(pinType: String, path: String): Void? { + return process { apiService.togglePinnedItem(pinType, path) } } override fun sendPasswordResetEmail(email: String): Flowable { @@ -755,17 +751,17 @@ class ApiClientImpl( return apiService.sendPasswordResetEmail(data).compose(configureApiCallObserver()) } - override fun updateLoginName(newLoginName: String, password: String): Flowable { + override suspend fun updateLoginName(newLoginName: String, password: String): Void? { val updateObject = HashMap() updateObject["username"] = newLoginName updateObject["password"] = password - return apiService.updateLoginName(updateObject).compose(configureApiCallObserver()) + return process { apiService.updateLoginName(updateObject) } } - override fun updateUsername(newLoginName: String): Flowable { + override suspend fun updateUsername(newLoginName: String): Void? { val updateObject = HashMap() updateObject["username"] = newLoginName - return apiService.updateLoginName(updateObject).compose(configureApiCallObserver()) + return process { apiService.updateLoginName(updateObject) } } override fun verifyUsername(username: String): Flowable { @@ -831,7 +827,7 @@ class ApiClientImpl( return apiService.retrieveMarketGear(languageCode).compose(configureApiCallObserver()) } - override suspend fun getWorldState(): WorldState? = handleSuspendCall { apiService.worldState() } + override suspend fun getWorldState(): WorldState? = process { apiService.worldState() } companion object { fun createGsonFactory(): GsonConverterFactory { diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/InventoryRepositoryImpl.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/InventoryRepositoryImpl.kt index 3f11e468a..e8a165d56 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/InventoryRepositoryImpl.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/InventoryRepositoryImpl.kt @@ -47,8 +47,12 @@ class InventoryRepositoryImpl( return localRepository.getInAppRewards() } - override fun retrieveInAppRewards(): Flowable> { - return apiClient.retrieveInAppRewards().doOnNext { localRepository.saveInAppRewards(it) } + override suspend fun retrieveInAppRewards(): List? { + val rewards = apiClient.retrieveInAppRewards() + if (rewards != null) { + localRepository.saveInAppRewards(rewards) + } + return rewards } override fun getOwnedEquipment(type: String): Flowable> { @@ -238,35 +242,32 @@ class InventoryRepositoryImpl( .doOnNext { localRepository.changeOwnedCount("quests", quest.key, userID, -1) } } - override fun buyItem(user: User?, id: String, value: Double, purchaseQuantity: Int): Flowable { - return apiClient.buyItem(id, purchaseQuantity) - .doOnNext { buyResponse -> - if (user == null) { - return@doOnNext - } - val copiedUser = localRepository.getUnmanagedCopy(user) - if (buyResponse.items != null) { - copiedUser.items = buyResponse.items - } - if (buyResponse.hp != null) { - copiedUser.stats?.hp = buyResponse.hp - } - if (buyResponse.exp != null) { - copiedUser.stats?.exp = buyResponse.exp - } - if (buyResponse.mp != null) { - copiedUser.stats?.mp = buyResponse.mp - } - if (buyResponse.gp != null) { - copiedUser.stats?.gp = buyResponse.gp - } else { - copiedUser.stats?.gp = copiedUser.stats?.gp ?: 0 - (value * purchaseQuantity) - } - if (buyResponse.lvl != null) { - copiedUser.stats?.lvl = buyResponse.lvl - } - localRepository.save(copiedUser) - } + override suspend fun buyItem(user: User?, id: String, value: Double, purchaseQuantity: Int): BuyResponse? { + val buyResponse = apiClient.buyItem(id, purchaseQuantity) ?: return null + val foundUser = user ?: localRepository.getLiveUser(userID) ?: return buyResponse + val copiedUser = localRepository.getUnmanagedCopy(foundUser) + if (buyResponse.items != null) { + copiedUser.items = buyResponse.items + } + if (buyResponse.hp != null) { + copiedUser.stats?.hp = buyResponse.hp + } + if (buyResponse.exp != null) { + copiedUser.stats?.exp = buyResponse.exp + } + if (buyResponse.mp != null) { + copiedUser.stats?.mp = buyResponse.mp + } + if (buyResponse.gp != null) { + copiedUser.stats?.gp = buyResponse.gp + } else { + copiedUser.stats?.gp = (copiedUser.stats?.gp ?: 0.0) - (value * purchaseQuantity) + } + if (buyResponse.lvl != null) { + copiedUser.stats?.lvl = buyResponse.lvl + } + localRepository.save(copiedUser) + return buyResponse } override fun getAvailableLimitedItems(): Flowable> { @@ -281,30 +282,30 @@ class InventoryRepositoryImpl( return apiClient.retrieveMarketGear() } - override fun purchaseMysterySet(categoryIdentifier: String): Flowable { + override suspend fun purchaseMysterySet(categoryIdentifier: String): Void? { return apiClient.purchaseMysterySet(categoryIdentifier) } - override fun purchaseHourglassItem(purchaseType: String, key: String): Flowable { + override suspend fun purchaseHourglassItem(purchaseType: String, key: String): Void? { return apiClient.purchaseHourglassItem(purchaseType, key) } - override fun purchaseQuest(key: String): Flowable { + override suspend fun purchaseQuest(key: String): Void? { return apiClient.purchaseQuest(key) } - override fun purchaseSpecialSpell(key: String): Flowable { + override suspend fun purchaseSpecialSpell(key: String): Void? { return apiClient.purchaseSpecialSpell(key) } - override fun purchaseItem(purchaseType: String, key: String, purchaseQuantity: Int): Flowable { + override suspend fun purchaseItem(purchaseType: String, key: String, purchaseQuantity: Int): Void? { return apiClient.purchaseItem(purchaseType, key, purchaseQuantity) } - override fun togglePinnedItem(item: ShopItem): Flowable> { - return if (!item.isValid) { - Flowable.empty() - } else apiClient.togglePinnedItem(item.pinType ?: "", item.path ?: "") - .flatMap { retrieveInAppRewards() } + override suspend fun togglePinnedItem(item: ShopItem): List? { + if (item.isValid) { + apiClient.togglePinnedItem(item.pinType ?: "", item.path ?: "") + } + return retrieveInAppRewards() } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/UserRepositoryImpl.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/UserRepositoryImpl.kt index 5dd94d2d4..44a081976 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/UserRepositoryImpl.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/UserRepositoryImpl.kt @@ -30,6 +30,7 @@ import io.reactivex.rxjava3.core.Maybe import io.reactivex.rxjava3.functions.BiFunction import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.withContext import java.util.Date import java.util.GregorianCalendar @@ -51,24 +52,23 @@ class UserRepositoryImpl( override fun getUser(userID: String): Flow = localRepository.getUser(userID) - private fun updateUser(userID: String, updateData: Map): Flowable { - return Flowable.zip( - apiClient.updateUser(updateData), - localRepository.getUserFlowable(userID).firstElement().toFlowable() - ) { newUser, user -> mergeUser(user, newUser) } + private suspend fun updateUser(userID: String, updateData: Map): User? { + val networkUser = apiClient.updateUser(updateData) ?: return null + val oldUser = localRepository.getUser(userID).firstOrNull() + return mergeUser(oldUser, networkUser) } - private fun updateUser(userID: String, key: String, value: Any): Flowable { + private suspend fun updateUser(userID: String, key: String, value: Any): User? { val updateData = HashMap() updateData[key] = value return updateUser(userID, updateData) } - override fun updateUser(updateData: Map): Flowable { + override suspend fun updateUser(updateData: Map): User? { return updateUser(userID, updateData) } - override fun updateUser(key: String, value: Any): Flowable { + override suspend fun updateUser(key: String, value: Any): User? { return updateUser(userID, key, value) } @@ -90,12 +90,11 @@ class UserRepositoryImpl( 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()) + return if (offset.toInt() != (user.preferences?.timezoneOffset ?: 0)) { + updateUser(user.id ?: "", "preferences.timezoneOffset", offset.toString()) } else { - return@flatMap Flowable.just(user) - }*/ - return user + user + } } else { return null } @@ -106,17 +105,13 @@ class UserRepositoryImpl( return retrieveUser(false, true) } - override fun resetTutorial(): Maybe { - return localRepository.getTutorialSteps() - .firstElement() - .map> { tutorialSteps -> - val updateData = HashMap() - for (step in tutorialSteps) { - updateData[step.flagPath] = false - } - updateData - } - .flatMap { updateData -> updateUser(updateData).firstElement() } + override suspend fun resetTutorial(): User? { + val tutorialSteps = localRepository.getTutorialSteps().firstOrNull() ?: return null + val updateData = HashMap() + for (step in tutorialSteps) { + updateData[step.flagPath] = false + } + return updateUser(updateData) } override suspend fun sleep(user: User): User { @@ -134,26 +129,26 @@ class UserRepositoryImpl( override fun getSpecialItems(user: User): Flowable> = localRepository.getSpecialItems(user) - override fun useSkill(key: String, target: String?, taskId: String): Flowable { - return zipWithLiveUser(apiClient.useSkill(key, target ?: "", taskId)) { response, user -> - response.hpDiff = (response.user?.stats?.hp ?: 0.0) - (user.stats?.hp ?: 0.0) - response.expDiff =(response.user?.stats?.exp ?: 0.0) - (user.stats?.exp ?: 0.0) - response.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) } - response - } + override suspend fun useSkill(key: String, target: String?, taskId: String): SkillResponse? { + val response = apiClient.useSkill(key, target ?: "", taskId) ?: return null + val user = getLiveUser() ?: return response + 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.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) } + return response } - override fun useSkill(key: String, target: String?): Flowable { - return zipWithLiveUser(apiClient.useSkill(key, target ?: "")) { response, user -> - response.hpDiff = (response.user?.stats?.hp ?: 0.0) - (user.stats?.hp ?: 0.0) - response.expDiff =(response.user?.stats?.exp ?: 0.0) - (user.stats?.exp ?: 0.0) - response.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) } - response - } + override suspend fun useSkill(key: String, target: String?): SkillResponse? { + val response = apiClient.useSkill(key, target ?: "") ?: return null + val user = getLiveUser() ?: return response + 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.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) } + return response } override suspend fun disableClasses(): User? = apiClient.disableClasses() @@ -163,20 +158,19 @@ class UserRepositoryImpl( return retrieveUser(false, forced = true) } - override fun unlockPath(customization: Customization): Flowable { + override suspend fun unlockPath(customization: Customization): UnlockResponse? { return unlockPath(customization.path, customization.price ?: 0) } - override fun unlockPath(path: String, price: Int): Flowable { - return zipWithLiveUser(apiClient.unlockPath(path)) { unlockResponse, copiedUser -> - val user = localRepository.getUnmanagedCopy(copiedUser) - user.preferences = unlockResponse.preferences - user.purchased = unlockResponse.purchased - user.items = unlockResponse.items - user.balance = copiedUser.balance - (price / 4.0) - localRepository.saveUser(copiedUser, false) - unlockResponse - } + override suspend fun unlockPath(path: String, price: Int): UnlockResponse? { + val unlockResponse = apiClient.unlockPath(path) ?: return null + val user = localRepository.getUser(userID).firstOrNull() ?: return unlockResponse + user.preferences = unlockResponse.preferences + user.purchased = unlockResponse.purchased + user.items = unlockResponse.items + user.balance = user.balance - (price / 4.0) + localRepository.saveUser(user, false) + return unlockResponse } override suspend fun runCron() { @@ -204,9 +198,10 @@ class UserRepositoryImpl( return apiClient.changeCustomDayStart(updateObject) } - override fun updateLanguage(languageCode: String): Flowable { - return updateUser("preferences.language", languageCode) - .doOnNext { apiClient.setLanguageCode(languageCode) } + override suspend fun updateLanguage(languageCode: String): User? { + val user = updateUser("preferences.language", languageCode) + apiClient.setLanguageCode(languageCode) + return user } override suspend fun resetAccount(): User? { @@ -220,21 +215,18 @@ class UserRepositoryImpl( override fun sendPasswordResetEmail(email: String): Flowable = apiClient.sendPasswordResetEmail(email) - override fun updateLoginName(newLoginName: String, password: String?): Maybe { - return ( - if (password != null && password.isNotEmpty()) { - apiClient.updateLoginName(newLoginName.trim(), password.trim()) - } else { - apiClient.updateUsername(newLoginName.trim()) - } - ).flatMapMaybe { localRepository.getUserFlowable(userID).firstElement() } - .doOnNext { user -> - localRepository.modify(user) { liveUser -> - liveUser.authentication?.localAuthentication?.username = newLoginName - liveUser.flags?.verifiedUsername = true - } - } - .firstElement() + override suspend fun updateLoginName(newLoginName: String, password: String?): User? { + if (password != null && password.isNotEmpty()) { + apiClient.updateLoginName(newLoginName.trim(), password.trim()) + } else { + apiClient.updateUsername(newLoginName.trim()) + } + val user = localRepository.getUser(userID).firstOrNull() ?: return null + localRepository.modify(user) { liveUser -> + liveUser.authentication?.localAuthentication?.username = newLoginName + liveUser.flags?.verifiedUsername = true + } + return user } override fun verifyUsername(username: String): Flowable = apiClient.verifyUsername(username.trim()) @@ -250,7 +242,7 @@ class UserRepositoryImpl( apiClient.updatePassword(oldPassword.trim(), newPassword.trim(), newPasswordConfirmation.trim()) override fun allocatePoint(stat: Attribute): Flowable { - getLiveUser().firstElement().subscribe( + getLiveUserFlowable().firstElement().subscribe( { liveUser -> localRepository.executeTransaction { when (stat) { @@ -324,7 +316,7 @@ class UserRepositoryImpl( } } - override fun useCustomization(type: String, category: String?, identifier: String): Flowable { + override suspend fun useCustomization(type: String, category: String?, identifier: String): User? { if (appConfigManager.enableLocalChanges()) { localRepository.getUserFlowable(userID).firstElement().subscribe( { liveUser -> @@ -401,14 +393,19 @@ class UserRepositoryImpl( return localRepository.getTeamPlan(teamID) } - private fun getLiveUser(): Flowable { + private fun getLiveUserFlowable(): Flowable { return localRepository.getUserFlowable(userID) .map { Optional(localRepository.getLiveObject(it)) } .filterMapEmpty() } + private suspend fun getLiveUser(): User? { + val user = localRepository.getUser(userID).firstOrNull() ?: return null + return localRepository.getLiveObject(user) + } + private fun zipWithLiveUser(flowable: Flowable, mergeFunc: BiFunction): Flowable { - return Flowable.zip(flowable, getLiveUser().firstElement().toFlowable(), mergeFunc) + return Flowable.zip(flowable, getLiveUserFlowable().firstElement().toFlowable(), mergeFunc) } private fun mergeUser(oldUser: User?, newUser: User): User { diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/UserLocalRepository.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/UserLocalRepository.kt index 3d37970c9..04dbb8e90 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/UserLocalRepository.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/UserLocalRepository.kt @@ -10,11 +10,12 @@ import com.habitrpg.android.habitica.models.social.Group import com.habitrpg.android.habitica.models.user.User import com.habitrpg.android.habitica.models.user.UserQuestStatus import io.reactivex.rxjava3.core.Flowable +import io.realm.RealmResults import kotlinx.coroutines.flow.Flow interface UserLocalRepository : BaseLocalRepository { - fun getTutorialSteps(): Flowable> + suspend fun getTutorialSteps(): Flow> fun getUser(userID: String): Flow fun getUserFlowable(userID: String): Flowable 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 af919b312..501efe7b8 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 @@ -1,7 +1,6 @@ package com.habitrpg.android.habitica.data.local.implementation import com.habitrpg.android.habitica.data.local.UserLocalRepository -import com.habitrpg.android.habitica.models.user.UserQuestStatus import com.habitrpg.android.habitica.extensions.filterMap import com.habitrpg.android.habitica.models.Achievement import com.habitrpg.android.habitica.models.QuestAchievement @@ -12,6 +11,7 @@ import com.habitrpg.android.habitica.models.TutorialStep import com.habitrpg.android.habitica.models.social.ChatMessage import com.habitrpg.android.habitica.models.social.Group import com.habitrpg.android.habitica.models.user.User +import com.habitrpg.android.habitica.models.user.UserQuestStatus import hu.akarnokd.rxjava3.bridge.RxJavaBridge import io.reactivex.rxjava3.core.Flowable import io.realm.Realm @@ -21,7 +21,8 @@ import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.map -class RealmUserLocalRepository(realm: Realm) : RealmBaseLocalRepository(realm), UserLocalRepository { +class RealmUserLocalRepository(realm: Realm) : RealmBaseLocalRepository(realm), + UserLocalRepository { override fun getUserQuestStatus(userID: String): Flowable { return getUserFlowable(userID) .map { it.party?.id ?: "" } @@ -39,7 +40,8 @@ class RealmUserLocalRepository(realm: Realm) : RealmBaseLocalRepository(realm), .map { when { it.quest?.members?.find { questMember -> questMember.key == userID } === null -> UserQuestStatus.NO_QUEST - it.quest?.progress?.collect?.isNotEmpty() ?: false -> UserQuestStatus.QUEST_COLLECT + it.quest?.progress?.collect?.isNotEmpty() + ?: false -> UserQuestStatus.QUEST_COLLECT it.quest?.progress?.hp ?: 0.0 > 0.0 -> UserQuestStatus.QUEST_BOSS else -> UserQuestStatus.QUEST_UNKNOWN } @@ -48,25 +50,24 @@ class RealmUserLocalRepository(realm: Realm) : RealmBaseLocalRepository(realm), override fun getAchievements(): Flow> { return realm.where(Achievement::class.java) - .sort("index") - .findAll() - .toFlow() - .filter { it.isLoaded } + .sort("index") + .findAll() + .toFlow() + .filter { it.isLoaded } } override fun getQuestAchievements(userID: String): Flow> { return realm.where(User::class.java) - .equalTo("id", userID) - .findAll() - .toFlow() - .filter { it.isLoaded && it.size > 0 } - .map { it.first()?.questAchievements ?: emptyList() } + .equalTo("id", userID) + .findAll() + .toFlow() + .filter { it.isLoaded && it.size > 0 } + .map { it.first()?.questAchievements ?: emptyList() } } - override fun getTutorialSteps(): Flowable> = RxJavaBridge.toV3Flowable( - realm.where(TutorialStep::class.java).findAll().asFlowable() + override suspend fun getTutorialSteps() = + realm.where(TutorialStep::class.java).findAll().toFlow() .filter { it.isLoaded }.map { it } - ) override fun getUser(userID: String): Flow { if (realm.isClosed) return emptyFlow() @@ -82,11 +83,11 @@ class RealmUserLocalRepository(realm: Realm) : RealmBaseLocalRepository(realm), if (realm.isClosed) return Flowable.empty() return RxJavaBridge.toV3Flowable( realm.where(User::class.java) - .equalTo("id", userID) - .findAll() - .asFlowable() - .filter { realmObject -> realmObject.isLoaded && realmObject.isValid && !realmObject.isEmpty() } - .map { users -> users.first() }) + .equalTo("id", userID) + .findAll() + .asFlowable() + .filter { realmObject -> realmObject.isLoaded && realmObject.isValid && !realmObject.isEmpty() } + .map { users -> users.first() }) } override fun saveUser(user: User, overrideExisting: Boolean) { @@ -127,10 +128,10 @@ class RealmUserLocalRepository(realm: Realm) : RealmBaseLocalRepository(realm), override fun getTeamPlans(userID: String): Flow> { return realm.where(TeamPlan::class.java) - .equalTo("userID", userID) - .findAll() - .toFlow() - .filter { it.isLoaded } + .equalTo("userID", userID) + .findAll() + .toFlow() + .filter { it.isLoaded } } override fun getTeamPlan(teamID: String): Flowable { @@ -146,7 +147,8 @@ class RealmUserLocalRepository(realm: Realm) : RealmBaseLocalRepository(realm), } override fun getSkills(user: User): Flowable> { - val habitClass = if (user.preferences?.disableClasses == true) "none" else user.stats?.habitClass + val habitClass = + if (user.preferences?.disableClasses == true) "none" else user.stats?.habitClass return RxJavaBridge.toV3Flowable( realm.where(Skill::class.java) .equalTo("habitClass", habitClass) diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/ExceptionHandler.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/ExceptionHandler.kt index 2e1f573aa..91f3821f4 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/ExceptionHandler.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/ExceptionHandler.kt @@ -5,6 +5,8 @@ import com.habitrpg.android.habitica.BuildConfig import com.habitrpg.android.habitica.proxy.AnalyticsManager import io.reactivex.rxjava3.functions.Consumer import kotlinx.coroutines.CoroutineExceptionHandler +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch import okhttp3.internal.http2.ConnectionShutdownException import retrofit2.HttpException import java.io.EOFException @@ -53,3 +55,7 @@ class ExceptionHandler { } } } + +fun CoroutineScope.launchCatching(function: suspend CoroutineScope.() -> Unit) { + launch((ExceptionHandler.coroutine()), block = function) +} \ No newline at end of file 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 19fed52b9..2cd447a1e 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 @@ -501,6 +501,10 @@ open class Task : RealmObject, BaseMainObject, Parcelable, BaseTask { return daysOfMonth } + fun canEdit(userID: String): Boolean { + return true + } + companion object CREATOR : Parcelable.Creator { override fun createFromParcel(source: Parcel): Task = Task(source) diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/ArmoireActivity.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/ArmoireActivity.kt index c09c999f1..f18b327f0 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/ArmoireActivity.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/ArmoireActivity.kt @@ -18,7 +18,6 @@ import com.habitrpg.android.habitica.helpers.AdHandler import com.habitrpg.android.habitica.helpers.AdType import com.habitrpg.android.habitica.helpers.AppConfigManager import com.habitrpg.android.habitica.helpers.ExceptionHandler -import com.habitrpg.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 @@ -26,6 +25,7 @@ import com.habitrpg.common.habitica.extensions.dpToPx import com.habitrpg.common.habitica.extensions.loadImage import com.habitrpg.common.habitica.helpers.Animations import com.plattysoft.leonids.ParticleSystem +import kotlinx.coroutines.launch import java.util.Locale import javax.inject.Inject @@ -80,22 +80,20 @@ class ArmoireActivity : BaseActivity() { Log.d("AdHandler", "Giving Armoire") val user = userViewModel.user.value ?: return@AdHandler val currentGold = user.stats?.gp ?: return@AdHandler - compositeSubscription.add( + lifecycleScope.launch(ExceptionHandler.coroutine()) { userRepository.updateUser("stats.gp", currentGold + 100) - .flatMap { inventoryRepository.buyItem(user, "armoire", 100.0, 1) } - .subscribe({ - configure( - it.armoire["type"] ?: "", - it.armoire["dropKey"] ?: "", - it.armoire["dropText"] ?: "", - it.armoire["value"] ?: "" - ) - binding.adButton.state = AdButton.State.UNAVAILABLE - binding.adButton.visibility = View.INVISIBLE - hasAnimatedChanges = false - gold = null - }, ExceptionHandler.rx()) - ) + val buyResponse = inventoryRepository.buyItem(user, "armoire", 100.0, 1) ?: return@launch + configure( + buyResponse.armoire["type"] ?: "", + buyResponse.armoire["dropKey"] ?: "", + buyResponse.armoire["dropText"] ?: "", + buyResponse.armoire["value"] ?: "" + ) + binding.adButton.state = AdButton.State.UNAVAILABLE + binding.adButton.visibility = View.INVISIBLE + hasAnimatedChanges = false + gold = null + } } handler.prepare { if (it && binding.adButton.state == AdButton.State.LOADING) { diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/DeathActivity.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/DeathActivity.kt index 4e035a944..64b90d66b 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/DeathActivity.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/DeathActivity.kt @@ -57,11 +57,10 @@ class DeathActivity: BaseActivity() { return@AdHandler } Log.d("AdHandler", "Reviving user") - compositeSubscription.add( - userRepository.updateUser("stats.hp", 1).subscribe({ - finish() - }, ExceptionHandler.rx()) - ) + lifecycleScope.launch(ExceptionHandler.coroutine()) { + userRepository.updateUser("stats.hp", 1) + finish() + } } handler.prepare { if (it && binding.adButton.state == AdButton.State.LOADING) { diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/SetupActivity.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/SetupActivity.kt index 7f078676c..ec94f2fc8 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/SetupActivity.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/SetupActivity.kt @@ -209,17 +209,10 @@ class SetupActivity : BaseActivity(), ViewPager.OnPageChangeListener { additionalData["status"] = "completed" AmplitudeManager.sendEvent("setup", AmplitudeManager.EVENT_CATEGORY_BEHAVIOUR, AmplitudeManager.EVENT_HITTYPE_EVENT, additionalData) - compositeSubscription.add( - userRepository.updateUser("flags.welcomed", true).subscribe( - { - if (!compositeSubscription.isDisposed) { - compositeSubscription.dispose() - } - startMainActivity() - }, - ExceptionHandler.rx() - ) - ) + lifecycleScope.launch(ExceptionHandler.coroutine()) { + userRepository.updateUser("flags.welcomed", true) + startMainActivity() + } return } this.user = user @@ -238,11 +231,10 @@ class SetupActivity : BaseActivity(), ViewPager.OnPageChangeListener { } private fun confirmNames(displayName: String, username: String) { - compositeSubscription.add( + lifecycleScope.launch(ExceptionHandler.coroutine()) { userRepository.updateUser("profile.name", displayName) - .flatMap { userRepository.updateLoginName(username).toFlowable() } - .subscribe({ }, ExceptionHandler.rx()) - ) + userRepository.updateLoginName(username) + } } private inner class ViewPageAdapter(fm: FragmentManager) : FragmentPagerAdapter(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT), IconPagerAdapter { diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/VerifyUsernameActivity.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/VerifyUsernameActivity.kt index fde4b03c7..31ac9393d 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/VerifyUsernameActivity.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/VerifyUsernameActivity.kt @@ -114,13 +114,12 @@ class VerifyUsernameActivity : BaseActivity() { private fun confirmNames() { binding.confirmUsernameButton.isClickable = false - compositeSubscription.add( + lifecycleScope.launch(ExceptionHandler.coroutine()) { userRepository.updateUser("profile.name", binding.displayNameEditText.text.toString()) - .flatMap { userRepository.updateLoginName(binding.usernameEditText.text.toString()).toFlowable() } - .doOnComplete { showConfirmationAndFinish() } - .doOnEach { binding.confirmUsernameButton.isClickable = true } - .subscribe({ }, ExceptionHandler.rx()) - ) + userRepository.updateLoginName(binding.usernameEditText.text.toString()) + showConfirmationAndFinish() + binding.confirmUsernameButton.isClickable = true + } } private fun showConfirmationAndFinish() { diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/CustomizationRecyclerViewAdapter.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/CustomizationRecyclerViewAdapter.kt index 3238e6245..9b44cfe63 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/CustomizationRecyclerViewAdapter.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/CustomizationRecyclerViewAdapter.kt @@ -18,11 +18,8 @@ import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog import com.habitrpg.android.habitica.ui.views.shops.PurchaseDialog import com.habitrpg.common.habitica.extensions.dpToPx import com.habitrpg.common.habitica.extensions.loadImage -import com.habitrpg.shared.habitica.models.Avatar import com.habitrpg.common.habitica.views.AvatarView -import io.reactivex.rxjava3.core.BackpressureStrategy -import io.reactivex.rxjava3.core.Flowable -import io.reactivex.rxjava3.subjects.PublishSubject +import com.habitrpg.shared.habitica.models.Avatar import java.util.Date import java.util.EnumMap import kotlin.math.min @@ -46,7 +43,7 @@ class CustomizationRecyclerViewAdapter() : androidx.recyclerview.widget.Recycler var ownedCustomizations: List = listOf() private var pinnedItemKeys: List = ArrayList() - private val selectCustomizationEvents = PublishSubject.create() + var onCustomizationSelected: ((Customization) -> Unit)? = null override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): androidx.recyclerview.widget.RecyclerView.ViewHolder { return when (viewType) { @@ -152,10 +149,6 @@ class CustomizationRecyclerViewAdapter() : androidx.recyclerview.widget.Recycler this.notifyDataSetChanged() } - fun getSelectCustomizationEvents(): Flowable { - return selectCustomizationEvents.toFlowable(BackpressureStrategy.DROP) - } - fun setPinnedItemKeys(pinnedItemKeys: List) { this.pinnedItemKeys = pinnedItemKeys if (customizationList.size > 0) this.notifyDataSetChanged() @@ -234,7 +227,7 @@ class CustomizationRecyclerViewAdapter() : androidx.recyclerview.widget.Recycler alert.setMessage(customization?.notes) alert.addButton(R.string.equip, true) { _, _ -> customization?.let { - selectCustomizationEvents.onNext(it) + onCustomizationSelected?.invoke(it) } } alert.addButton(R.string.close, false) { _, _ -> @@ -243,7 +236,7 @@ class CustomizationRecyclerViewAdapter() : androidx.recyclerview.widget.Recycler alert.show() } else { customization?.let { - selectCustomizationEvents.onNext(it) + onCustomizationSelected?.invoke(it) } } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/setup/CustomizationSetupAdapter.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/setup/CustomizationSetupAdapter.kt index afcd17483..2eea727e3 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/setup/CustomizationSetupAdapter.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/setup/CustomizationSetupAdapter.kt @@ -24,8 +24,7 @@ internal class CustomizationSetupAdapter : RecyclerView.Adapter() val equipGearEvents: Flowable = equipGearEventSubject.toFlowable(BackpressureStrategy.DROP) - private val updateUserEventsSubject = PublishSubject.create>() - val updateUserEvents: Flowable> = updateUserEventsSubject.toFlowable(BackpressureStrategy.DROP) + var onUpdateUser: ((Map) -> Unit)? = null fun setCustomizationList(newCustomizationList: List) { this.customizationList = newCustomizationList @@ -128,7 +127,7 @@ internal class CustomizationSetupAdapter : RecyclerView.Adapter() val updatePath = "preferences." + selectedCustomization.path updateData[updatePath] = selectedCustomization.key - updateUserEventsSubject.onNext(updateData) + onUpdateUser?.invoke(updateData) } } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/NewsFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/NewsFragment.kt index 748d9c160..ff54e804f 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/NewsFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/NewsFragment.kt @@ -9,11 +9,13 @@ import android.webkit.WebChromeClient import android.webkit.WebResourceRequest import android.webkit.WebView import android.webkit.WebViewClient +import androidx.lifecycle.lifecycleScope import com.habitrpg.android.habitica.R import com.habitrpg.android.habitica.components.UserComponent import com.habitrpg.android.habitica.databinding.FragmentNewsBinding -import com.habitrpg.android.habitica.extensions.subscribeWithErrorHandler +import com.habitrpg.android.habitica.helpers.ExceptionHandler import com.habitrpg.android.habitica.helpers.MainNavigationController +import kotlinx.coroutines.launch class NewsFragment : BaseMainFragment() { @@ -75,6 +77,8 @@ class NewsFragment : BaseMainFragment() { override fun onResume() { super.onResume() - compositeSubscription.add(userRepository.updateUser("flags.newStuff", false).subscribeWithErrorHandler({})) + lifecycleScope.launch(ExceptionHandler.coroutine()) { + userRepository.updateUser("flags.newStuff", false) + } } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/StatsFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/StatsFragment.kt index 237fbc1b8..dc955b258 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/StatsFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/StatsFragment.kt @@ -5,23 +5,25 @@ 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.HabiticaBaseApplication 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.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.ExceptionHandler import com.habitrpg.android.habitica.helpers.UserStatComputer -import com.habitrpg.shared.habitica.models.tasks.Attribute +import com.habitrpg.android.habitica.helpers.launchCatching import com.habitrpg.android.habitica.models.user.Stats import com.habitrpg.android.habitica.models.user.User import com.habitrpg.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.stats.BulkAllocateStatsDialog +import com.habitrpg.common.habitica.extensions.getThemeColor +import com.habitrpg.shared.habitica.models.tasks.Attribute import javax.inject.Inject import kotlin.math.min @@ -131,8 +133,9 @@ class StatsFragment : BaseMainFragment() { } binding?.automaticAllocationSwitch?.setOnCheckedChangeListener { _, isChecked -> - userRepository.updateUser("preferences.automaticAllocation", isChecked) - .subscribe({}, ExceptionHandler.rx()) + lifecycleScope.launchCatching { + userRepository.updateUser("preferences.automaticAllocation", isChecked) + } } binding?.strengthStatsView?.allocateAction = { allocatePoint(Attribute.STRENGTH) } @@ -159,12 +162,12 @@ class StatsFragment : BaseMainFragment() { } private fun changeAutoAllocationMode(allocationMode: String) { - compositeSubscription.add( + lifecycleScope.launchCatching { userRepository.updateUser( "preferences.allocationMode", allocationMode - ).subscribe({}, ExceptionHandler.rx()) - ) + ) + } binding?.distributeEvenlyButton?.isChecked = allocationMode == Stats.AUTO_ALLOCATE_FLAT binding?.distributeClassButton?.isChecked = allocationMode == Stats.AUTO_ALLOCATE_CLASSBASED binding?.distributeTaskButton?.isChecked = allocationMode == Stats.AUTO_ALLOCATE_TASKBASED 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 46aeedfca..f09793524 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 @@ -26,6 +26,7 @@ import com.habitrpg.android.habitica.databinding.BottomSheetBackgroundsFilterBin import com.habitrpg.android.habitica.databinding.FragmentRefreshRecyclerviewBinding import com.habitrpg.android.habitica.extensions.setTintWith import com.habitrpg.android.habitica.helpers.ExceptionHandler +import com.habitrpg.android.habitica.helpers.launchCatching import com.habitrpg.android.habitica.models.CustomizationFilter import com.habitrpg.android.habitica.models.inventory.Customization import com.habitrpg.android.habitica.models.user.OwnedCustomization @@ -79,18 +80,20 @@ class AvatarCustomizationFragment : savedInstanceState: Bundle? ): View? { showsBackButton = true - compositeSubscription.add( - adapter.getSelectCustomizationEvents() - .flatMap { customization -> + adapter.onCustomizationSelected = { customization -> + lifecycleScope.launchCatching { if (customization.type == "background") { userRepository.unlockPath(customization) - //TODO: .flatMap { userRepository.retrieveUser(false, true, true) } + userRepository.retrieveUser(false, true, true) } else { - userRepository.useCustomization(customization.type ?: "", customization.category, customization.identifier ?: "") + userRepository.useCustomization( + customization.type ?: "", + customization.category, + customization.identifier ?: "" + ) } } - .subscribe({ }, ExceptionHandler.rx()) - ) + } compositeSubscription.add( this.inventoryRepository.getInAppRewards() diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/customization/AvatarOverviewFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/customization/AvatarOverviewFragment.kt index 0b0e62358..11ae5b17d 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/customization/AvatarOverviewFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/customization/AvatarOverviewFragment.kt @@ -20,11 +20,12 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.platform.ViewCompositionStrategy import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp +import androidx.lifecycle.lifecycleScope import com.habitrpg.android.habitica.R import com.habitrpg.android.habitica.components.UserComponent import com.habitrpg.android.habitica.databinding.FragmentComposeScrollingBinding -import com.habitrpg.android.habitica.helpers.ExceptionHandler import com.habitrpg.android.habitica.helpers.MainNavigationController +import com.habitrpg.android.habitica.helpers.launchCatching import com.habitrpg.android.habitica.ui.fragments.BaseMainFragment import com.habitrpg.android.habitica.ui.theme.HabiticaTheme import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel @@ -89,10 +90,9 @@ class AvatarOverviewFragment : BaseMainFragment override fun onItemSelected(parent: AdapterView<*>, view: View?, position: Int, id: Long) { val newSize: String = if (position == 0) "slim" else "broad" - compositeSubscription.add( + lifecycleScope.launchCatching { userRepository.updateUser("preferences.size", newSize) - .subscribe({ }, ExceptionHandler.rx()) - ) + } } override fun onNothingSelected(parent: AdapterView<*>) { /* no-on */ diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/items/ItemRecyclerFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/items/ItemRecyclerFragment.kt index fca4bbc7d..345df8dda 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/items/ItemRecyclerFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/items/ItemRecyclerFragment.kt @@ -19,8 +19,9 @@ import com.habitrpg.android.habitica.databinding.FragmentItemsBinding import com.habitrpg.android.habitica.extensions.addCloseButton import com.habitrpg.android.habitica.extensions.observeOnce import com.habitrpg.android.habitica.extensions.subscribeWithErrorHandler -import com.habitrpg.android.habitica.helpers.MainNavigationController import com.habitrpg.android.habitica.helpers.ExceptionHandler +import com.habitrpg.android.habitica.helpers.MainNavigationController +import com.habitrpg.android.habitica.helpers.launchCatching import com.habitrpg.android.habitica.interactors.HatchPetUseCase import com.habitrpg.android.habitica.models.inventory.Egg import com.habitrpg.android.habitica.models.inventory.Food @@ -28,7 +29,6 @@ import com.habitrpg.android.habitica.models.inventory.HatchingPotion import com.habitrpg.android.habitica.models.inventory.Item import com.habitrpg.android.habitica.models.inventory.QuestContent import com.habitrpg.android.habitica.models.inventory.SpecialItem -import com.habitrpg.android.habitica.models.responses.SkillResponse import com.habitrpg.android.habitica.models.user.OwnedItem import com.habitrpg.android.habitica.models.user.OwnedPet import com.habitrpg.android.habitica.models.user.User @@ -44,7 +44,6 @@ 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 import kotlinx.coroutines.flow.onEach @@ -322,16 +321,10 @@ class ItemRecyclerFragment : BaseFragment(), SwipeRefreshL if (specialItem == null || memberID == null) { return } - - val observable: Flowable = + lifecycleScope.launchCatching { userRepository.useSkill(specialItem.key, specialItem.target, memberID) - - compositeSubscription.add( - observable.subscribe( - { this.displaySpecialItemResult(specialItem) }, - ExceptionHandler.rx() - ) - ) + displaySpecialItemResult(specialItem) + } } private fun displaySpecialItemResult(specialItem: SpecialItem?) { diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/AccountPreferenceFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/AccountPreferenceFragment.kt index d76d6d85f..09e002eae 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/AccountPreferenceFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/AccountPreferenceFragment.kt @@ -27,6 +27,7 @@ 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.launchCatching 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 @@ -256,8 +257,9 @@ class AccountPreferenceFragment : private fun updateUser(path: String, value: String?, title: String) { showSingleEntryDialog(value, title) { if (value != it) { - userRepository.updateUser(path, it ?: "") - .subscribe({}, ExceptionHandler.rx()) + lifecycleScope.launchCatching { + userRepository.updateUser(path, it ?: "") + } } } } @@ -388,8 +390,9 @@ class AccountPreferenceFragment : private fun showLoginNameDialog() { showSingleEntryDialog(user?.username, getString(R.string.username)) { - userRepository.updateLoginName(it ?: "") - .subscribe({}, ExceptionHandler.rx()) + lifecycleScope.launchCatching { + userRepository.updateLoginName(it ?: "") + } } } @@ -478,8 +481,11 @@ class AccountPreferenceFragment : dialog.setTitle(R.string.confirm_username_title) dialog.setMessage(R.string.confirm_username_description) dialog.addButton(R.string.confirm, true) { _, _ -> - userRepository.updateLoginName(user?.authentication?.localAuthentication?.username ?: "") - .subscribe({ }, ExceptionHandler.rx()) + lifecycleScope.launchCatching { + userRepository.updateLoginName( + user?.authentication?.localAuthentication?.username ?: "" + ) + } } dialog.addCancelButton() dialog.show() diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/EmailNotificationsPreferencesFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/EmailNotificationsPreferencesFragment.kt index 8a8ed8b59..a1e6f0603 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/EmailNotificationsPreferencesFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/EmailNotificationsPreferencesFragment.kt @@ -2,9 +2,10 @@ package com.habitrpg.android.habitica.ui.fragments.preferences import android.content.SharedPreferences import android.os.Bundle +import androidx.lifecycle.lifecycleScope import androidx.preference.CheckBoxPreference import com.habitrpg.android.habitica.HabiticaBaseApplication -import com.habitrpg.android.habitica.helpers.ExceptionHandler +import com.habitrpg.android.habitica.helpers.launchCatching import com.habitrpg.android.habitica.models.user.User class EmailNotificationsPreferencesFragment : BasePreferencesFragment(), SharedPreferences.OnSharedPreferenceChangeListener { @@ -73,7 +74,12 @@ class EmailNotificationsPreferencesFragment : BasePreferencesFragment(), SharedP else -> null } if (pathKey != null) { - compositeSubscription.add(userRepository.updateUser("preferences.emailNotifications.$pathKey", sharedPreferences.getBoolean(key, false)).subscribe({ }, ExceptionHandler.rx())) + lifecycleScope.launchCatching { + userRepository.updateUser( + "preferences.emailNotifications.$pathKey", + sharedPreferences.getBoolean(key, false) + ) + } } } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/PreferencesFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/PreferencesFragment.kt index 035b054f5..8191c4983 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/PreferencesFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/PreferencesFragment.kt @@ -23,6 +23,7 @@ import com.habitrpg.android.habitica.helpers.AppConfigManager 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.launchCatching import com.habitrpg.android.habitica.helpers.notifications.PushNotificationManager import com.habitrpg.android.habitica.models.user.User import com.habitrpg.android.habitica.prefs.TimePreference @@ -207,7 +208,9 @@ class PreferencesFragment : BasePreferencesFragment(), SharedPreferences.OnShare "usePushNotifications" -> { val usePushNotifications = sharedPreferences.getBoolean(key, true) pushNotificationsPreference?.isEnabled = usePushNotifications - userRepository.updateUser("preferences.pushNotifications.unsubscribeFromAll", !usePushNotifications).subscribe() + lifecycleScope.launchCatching { + userRepository.updateUser("preferences.pushNotifications.unsubscribeFromAll", !usePushNotifications) + } if (usePushNotifications) { pushNotificationManager.addPushDeviceUsingStoredToken() } else { @@ -217,7 +220,9 @@ class PreferencesFragment : BasePreferencesFragment(), SharedPreferences.OnShare "useEmails" -> { val useEmailNotifications = sharedPreferences.getBoolean(key, true) emailNotificationsPreference?.isEnabled = useEmailNotifications - userRepository.updateUser("preferences.emailNotifications.unsubscribeFromAll", !useEmailNotifications).subscribe() + lifecycleScope.launchCatching { + userRepository.updateUser("preferences.emailNotifications.unsubscribeFromAll", !useEmailNotifications) + } } "cds_time" -> { val timeval = sharedPreferences.getString("cds_time", "0") ?: "0" @@ -238,10 +243,10 @@ class PreferencesFragment : BasePreferencesFragment(), SharedPreferences.OnShare if (user?.preferences?.language == languageHelper.languageCode) { return } - - userRepository.updateLanguage(languageHelper.languageCode ?: "en") - .subscribe({ reloadContent(false) }, ExceptionHandler.rx()) - + lifecycleScope.launchCatching { + userRepository.updateLanguage(languageHelper.languageCode ?: "en") + reloadContent(false) + } val intent = Intent(activity, MainActivity::class.java) this.startActivity(intent) activity?.finishAffinity() @@ -249,10 +254,9 @@ class PreferencesFragment : BasePreferencesFragment(), SharedPreferences.OnShare "audioTheme" -> { val newAudioTheme = sharedPreferences.getString(key, "off") if (newAudioTheme != null) { - compositeSubscription.add( + lifecycleScope.launchCatching { userRepository.updateUser("preferences.sound", newAudioTheme) - .subscribe({ }, ExceptionHandler.rx()) - ) + } soundManager.soundTheme = newAudioTheme soundManager.preloadAllFiles() } @@ -265,8 +269,12 @@ class PreferencesFragment : BasePreferencesFragment(), SharedPreferences.OnShare val activity = activity as? PrefsActivity ?: return activity.reload() } - "dailyDueDefaultView" -> userRepository.updateUser("preferences.dailyDueDefaultView", sharedPreferences.getBoolean(key, false)) - .subscribe({ }, ExceptionHandler.rx()) + "dailyDueDefaultView" -> lifecycleScope.launchCatching { + userRepository.updateUser( + "preferences.dailyDueDefaultView", + sharedPreferences.getBoolean(key, false) + ) + } "server_url" -> { apiClient.updateServerUrl(sharedPreferences.getString(key, "")) findPreference(key)?.summary = sharedPreferences.getString(key, "") @@ -282,10 +290,9 @@ class PreferencesFragment : BasePreferencesFragment(), SharedPreferences.OnShare "disablePMs" -> { val isDisabled = sharedPreferences.getBoolean("disablePMs", false) if (user?.inbox?.optOut != isDisabled) { - compositeSubscription.add( + lifecycleScope.launchCatching { userRepository.updateUser("inbox.optOut", isDisabled) - .subscribe({ }, ExceptionHandler.rx()) - ) + } } } "launch_screen" -> { @@ -390,7 +397,12 @@ class PreferencesFragment : BasePreferencesFragment(), SharedPreferences.OnShare } else if (newValue == false && currentIds.contains(team.id)) { currentIds.remove(team.id) } - userRepository.updateUser("preferences.tasks.mirrorGroupTasks", currentIds).subscribe({}, ExceptionHandler.rx()) + lifecycleScope.launchCatching { + userRepository.updateUser( + "preferences.tasks.mirrorGroupTasks", + currentIds + ) + } true } groupCategory?.addPreference(newPreference) diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/PushNotificationsPreferencesFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/PushNotificationsPreferencesFragment.kt index 07a654ab3..702fc6270 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/PushNotificationsPreferencesFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/PushNotificationsPreferencesFragment.kt @@ -2,9 +2,10 @@ package com.habitrpg.android.habitica.ui.fragments.preferences import android.content.SharedPreferences import android.os.Bundle +import androidx.lifecycle.lifecycleScope import androidx.preference.CheckBoxPreference import com.habitrpg.android.habitica.HabiticaBaseApplication -import com.habitrpg.android.habitica.helpers.ExceptionHandler +import com.habitrpg.android.habitica.helpers.launchCatching import com.habitrpg.android.habitica.models.user.User class PushNotificationsPreferencesFragment : BasePreferencesFragment(), SharedPreferences.OnSharedPreferenceChangeListener { @@ -75,7 +76,9 @@ class PushNotificationsPreferencesFragment : BasePreferencesFragment(), SharedPr else -> null } if (pathKey != null) { - compositeSubscription.add(userRepository.updateUser("preferences.pushNotifications.$pathKey", sharedPreferences.getBoolean(key, false)).subscribe({ }, ExceptionHandler.rx())) + lifecycleScope.launchCatching { + userRepository.updateUser("preferences.pushNotifications.$pathKey", sharedPreferences.getBoolean(key, false)) + } } } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/setup/AvatarSetupFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/setup/AvatarSetupFragment.kt index b6c57d7e6..31dc320b3 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/setup/AvatarSetupFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/setup/AvatarSetupFragment.kt @@ -6,6 +6,7 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.RelativeLayout +import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.LinearLayoutManager import com.google.android.material.tabs.TabLayout import com.habitrpg.android.habitica.R @@ -15,6 +16,7 @@ import com.habitrpg.android.habitica.data.SetupCustomizationRepository import com.habitrpg.android.habitica.data.UserRepository import com.habitrpg.android.habitica.databinding.FragmentSetupAvatarBinding import com.habitrpg.android.habitica.extensions.subscribeWithErrorHandler +import com.habitrpg.android.habitica.helpers.launchCatching import com.habitrpg.android.habitica.models.SetupCustomization import com.habitrpg.android.habitica.models.user.User import com.habitrpg.android.habitica.ui.activities.SetupActivity @@ -56,7 +58,11 @@ class AvatarSetupFragment : BaseFragment() { this.adapter = CustomizationSetupAdapter() this.adapter?.userSize = this.user?.preferences?.size ?: "slim" - adapter?.updateUserEvents?.flatMap { userRepository.updateUser(it) }?.subscribeWithErrorHandler {}?.let { compositeSubscription.add(it) } + adapter?.onUpdateUser = { + lifecycleScope.launchCatching { + userRepository.updateUser(it) + } + } adapter?.equipGearEvents?.flatMap { inventoryRepository.equip("equipped", it) }?.subscribeWithErrorHandler {}?.let { compositeSubscription.add(it) } this.adapter?.user = this.user @@ -185,7 +191,9 @@ class AvatarSetupFragment : BaseFragment() { updateData["preferences.hair.bangs"] = chooseRandomKey(customizationRepository.getCustomizations(SetupCustomizationRepository.CATEGORY_HAIR, SetupCustomizationRepository.SUBCATEGORY_BANGS, user), false) updateData["preferences.hair.flower"] = chooseRandomKey(customizationRepository.getCustomizations(SetupCustomizationRepository.CATEGORY_EXTRAS, SetupCustomizationRepository.SUBCATEGORY_FLOWER, user), true) updateData["preferences.chair"] = chooseRandomKey(customizationRepository.getCustomizations(SetupCustomizationRepository.CATEGORY_EXTRAS, SetupCustomizationRepository.SUBCATEGORY_WHEELCHAIR, user), true) - compositeSubscription.add(userRepository.updateUser(updateData).subscribeWithErrorHandler({})) + lifecycleScope.launchCatching { + userRepository.updateUser(updateData) + } } @Suppress("ReturnCount") @@ -215,4 +223,4 @@ class AvatarSetupFragment : BaseFragment() { params?.marginStart = location[0] + px binding?.customizationDrawer?.binding?.caretView?.layoutParams = params } -} +} \ No newline at end of file diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/skills/SkillsFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/skills/SkillsFragment.kt index 428f6ef7c..8b6322401 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/skills/SkillsFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/skills/SkillsFragment.kt @@ -15,6 +15,7 @@ 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.ExceptionHandler +import com.habitrpg.android.habitica.helpers.launchCatching import com.habitrpg.android.habitica.models.Skill import com.habitrpg.android.habitica.models.responses.SkillResponse import com.habitrpg.android.habitica.models.user.User @@ -161,16 +162,15 @@ class SkillsFragment : BaseMainFragment() { if (skill == null) { return } - val observable: Flowable = if (taskId != null) { - userRepository.useSkill(skill.key, skill.target, taskId) - } else { - userRepository.useSkill(skill.key, skill.target) + lifecycleScope.launchCatching { + val skillResponse = if (taskId != null) { + userRepository.useSkill(skill.key, skill.target, taskId) + } else { + userRepository.useSkill(skill.key, skill.target) + } + if (skillResponse != null) { + displaySkillResult(skill, skillResponse) + } } - compositeSubscription.add( - observable.subscribe( - { skillResponse -> this.displaySkillResult(skill, skillResponse) }, - ExceptionHandler.rx() - ) - ) } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/support/SupportMainFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/support/SupportMainFragment.kt index 402109bdd..1b1d8c084 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/support/SupportMainFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/support/SupportMainFragment.kt @@ -6,16 +6,18 @@ import android.view.LayoutInflater 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.FAQRepository import com.habitrpg.android.habitica.databinding.FragmentSupportMainBinding import com.habitrpg.android.habitica.helpers.AppConfigManager -import com.habitrpg.android.habitica.helpers.MainNavigationController import com.habitrpg.android.habitica.helpers.ExceptionHandler +import com.habitrpg.android.habitica.helpers.MainNavigationController import com.habitrpg.android.habitica.modules.AppModule import com.habitrpg.android.habitica.ui.fragments.BaseMainFragment import com.habitrpg.android.habitica.ui.views.HabiticaSnackbar +import kotlinx.coroutines.launch import javax.inject.Inject import javax.inject.Named @@ -59,9 +61,11 @@ class SupportMainFragment : BaseMainFragment() { } binding?.resetTutorialButton?.setOnClickListener { - userRepository.resetTutorial().subscribe({ + lifecycleScope.launch(ExceptionHandler.coroutine()) { + userRepository.resetTutorial() activity?.showSnackbar(null, null, getString(R.string.tutorial_reset_confirmation), displayType = HabiticaSnackbar.SnackbarDisplayType.SUCCESS) - }, ExceptionHandler.rx()) + + } } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/tasks/RewardsRecyclerviewFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/tasks/RewardsRecyclerviewFragment.kt index 82854c32a..d646de22e 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/tasks/RewardsRecyclerviewFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/tasks/RewardsRecyclerviewFragment.kt @@ -15,6 +15,7 @@ import androidx.recyclerview.widget.LinearLayoutManager import com.habitrpg.android.habitica.R import com.habitrpg.android.habitica.extensions.subscribeWithErrorHandler import com.habitrpg.android.habitica.helpers.ExceptionHandler +import com.habitrpg.android.habitica.helpers.launchCatching import com.habitrpg.android.habitica.models.shops.ShopItem import com.habitrpg.android.habitica.ui.activities.MainActivity import com.habitrpg.android.habitica.ui.activities.SkillMemberActivity @@ -22,7 +23,6 @@ import com.habitrpg.android.habitica.ui.adapter.tasks.RewardsRecyclerViewAdapter import com.habitrpg.android.habitica.ui.helpers.SafeDefaultItemAnimator import com.habitrpg.android.habitica.ui.views.HabiticaSnackbar import com.habitrpg.shared.habitica.models.tasks.TaskType -import io.reactivex.rxjava3.functions.Consumer import kotlinx.coroutines.launch class RewardsRecyclerviewFragment : TaskRecyclerViewFragment() { @@ -35,7 +35,9 @@ class RewardsRecyclerviewFragment : TaskRecyclerViewFragment() { container: ViewGroup?, savedInstanceState: Bundle? ): View? { - compositeSubscription.add(inventoryRepository.retrieveInAppRewards().subscribe({ }, ExceptionHandler.rx())) + lifecycleScope.launchCatching { + inventoryRepository.retrieveInAppRewards() + } return super.onCreateView(inflater, container, savedInstanceState) } @@ -97,13 +99,9 @@ class RewardsRecyclerviewFragment : TaskRecyclerViewFragment() { binding?.refreshLayout?.isRefreshing = true lifecycleScope.launch(ExceptionHandler.coroutine()) { userRepository.retrieveUser(withTasks = true, forced = true) - } - compositeSubscription.add( inventoryRepository.retrieveInAppRewards() - .doOnTerminate { - binding?.refreshLayout?.isRefreshing = false - }.subscribe({ }, ExceptionHandler.rx()) - ) + binding?.refreshLayout?.isRefreshing = false + } } private fun setGridSpanCount(width: Int) { @@ -122,21 +120,19 @@ class RewardsRecyclerviewFragment : TaskRecyclerViewFragment() { private val cardSelectedResult = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { if (it.resultCode == Activity.RESULT_OK) { - userRepository.useSkill( - selectedCard?.key ?: "", - "member", - it.data?.getStringExtra("member_id") ?: "" - ) - .subscribeWithErrorHandler( - Consumer { - val activity = (activity as? MainActivity) ?: return@Consumer - HabiticaSnackbar.showSnackbar( - activity.snackbarContainer, - context?.getString(R.string.sent_card, selectedCard?.text), - HabiticaSnackbar.SnackbarDisplayType.BLUE - ) - } + lifecycleScope.launchCatching { + userRepository.useSkill( + selectedCard?.key ?: "", + "member", + it.data?.getStringExtra("member_id") ?: "" ) + val activity = (activity as? MainActivity) ?: return@launchCatching + HabiticaSnackbar.showSnackbar( + activity.snackbarContainer, + context?.getString(R.string.sent_card, selectedCard?.text), + HabiticaSnackbar.SnackbarDisplayType.BLUE + ) + } } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/BaseViewModel.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/BaseViewModel.kt index 50078eceb..e4656357d 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/BaseViewModel.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/BaseViewModel.kt @@ -2,12 +2,14 @@ package com.habitrpg.android.habitica.ui.viewmodels import androidx.lifecycle.LiveData import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope import com.habitrpg.android.habitica.HabiticaBaseApplication import com.habitrpg.android.habitica.components.UserComponent import com.habitrpg.android.habitica.data.UserRepository import com.habitrpg.android.habitica.helpers.ExceptionHandler import com.habitrpg.android.habitica.models.user.User import io.reactivex.rxjava3.disposables.CompositeDisposable +import kotlinx.coroutines.launch import javax.inject.Inject abstract class BaseViewModel(initializeComponent: Boolean = true) : ViewModel() { @@ -38,9 +40,8 @@ abstract class BaseViewModel(initializeComponent: Boolean = true) : ViewModel() internal val disposable = CompositeDisposable() fun updateUser(path: String, value: Any) { - disposable.add( + viewModelScope.launch(ExceptionHandler.coroutine()) { userRepository.updateUser(path, value) - .subscribe({ }, ExceptionHandler.rx()) - ) + } } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/MainUserViewModel.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/MainUserViewModel.kt index c86726f30..a141c608d 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/MainUserViewModel.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/MainUserViewModel.kt @@ -11,10 +11,12 @@ import com.habitrpg.android.habitica.models.members.Member import com.habitrpg.android.habitica.models.user.User import io.reactivex.rxjava3.disposables.CompositeDisposable import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.MainScope import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.launch class MainUserViewModel(private val providedUserID: String, val userRepository: UserRepository, val socialRepository: SocialRepository) { @@ -59,16 +61,14 @@ class MainUserViewModel(private val providedUserID: String, val userRepository: internal val disposable = CompositeDisposable() fun updateUser(path: String, value: Any) { - disposable.add( + MainScope().launch(ExceptionHandler.coroutine()) { userRepository.updateUser(path, value) - .subscribe({ }, ExceptionHandler.rx()) - ) + } } fun updateUser(data: Map) { - disposable.add( + MainScope().launch(ExceptionHandler.coroutine()) { userRepository.updateUser(data) - .subscribe({ }, ExceptionHandler.rx()) - ) + } } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/dialogs/PetSuggestHatchDialog.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/dialogs/PetSuggestHatchDialog.kt index 1652ba27f..0cd43b1da 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/dialogs/PetSuggestHatchDialog.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/dialogs/PetSuggestHatchDialog.kt @@ -6,12 +6,14 @@ import android.view.LayoutInflater import android.view.View import androidx.core.content.ContextCompat import androidx.core.graphics.drawable.toBitmap +import androidx.lifecycle.lifecycleScope import com.habitrpg.android.habitica.HabiticaBaseApplication import com.habitrpg.android.habitica.R import com.habitrpg.android.habitica.databinding.DialogHatchPetButtonBinding import com.habitrpg.android.habitica.databinding.DialogPetSuggestHatchBinding import com.habitrpg.android.habitica.extensions.subscribeWithErrorHandler import com.habitrpg.android.habitica.helpers.ExceptionHandler +import com.habitrpg.android.habitica.helpers.launchCatching import com.habitrpg.android.habitica.interactors.HatchPetUseCase import com.habitrpg.android.habitica.models.inventory.Animal import com.habitrpg.android.habitica.models.inventory.Egg @@ -22,7 +24,6 @@ import com.habitrpg.android.habitica.ui.activities.MainActivity import com.habitrpg.common.habitica.extensions.DataBindingUtils import com.habitrpg.common.habitica.extensions.loadImage import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers -import io.reactivex.rxjava3.core.Flowable import io.reactivex.rxjava3.core.Observable import java.util.Locale import javax.inject.Inject @@ -140,21 +141,16 @@ class PetSuggestHatchDialog(context: Context) : HabiticaAlertDialog(context) { val activity = (getActivity() as? MainActivity) ?: return@addButton val thisPotion = potion ?: return@addButton val thisEgg = egg ?: return@addButton - var observable: Flowable = Flowable.just("") - if (!hasEgg) { - observable = observable.flatMap { activity.inventoryRepository.purchaseItem("eggs", thisEgg.key, 1) } + lifecycleScope.launchCatching { + if (!hasEgg) { + activity.inventoryRepository.purchaseItem("eggs", thisEgg.key, 1) + } + if (!hasPotion) { + activity.inventoryRepository.purchaseItem("hatchingPotions", thisPotion.key, 1) + } + activity.userRepository.retrieveUser(true, forced = true) + hatchPet(thisPotion, thisEgg) } - if (!hasPotion) { - observable = observable.flatMap { activity.inventoryRepository.purchaseItem("hatchingPotions", thisPotion.key, 1) } - } - observable - .subscribe( - { - // TODO: activity.userRepository.retrieveUser(true, forced = true) - hatchPet(thisPotion, thisEgg) - }, - ExceptionHandler.rx() - ) } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/shops/PurchaseDialog.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/shops/PurchaseDialog.kt index 953bb8be7..b17cbb98d 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/shops/PurchaseDialog.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/shops/PurchaseDialog.kt @@ -10,6 +10,7 @@ import android.widget.TextView import androidx.core.content.ContextCompat import androidx.core.graphics.drawable.toDrawable import androidx.core.os.bundleOf +import androidx.lifecycle.lifecycleScope import com.google.firebase.analytics.FirebaseAnalytics import com.habitrpg.android.habitica.HabiticaBaseApplication import com.habitrpg.android.habitica.R @@ -23,6 +24,7 @@ import com.habitrpg.android.habitica.helpers.AppConfigManager 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.launchCatching import com.habitrpg.android.habitica.models.shops.Shop import com.habitrpg.android.habitica.models.shops.ShopItem import com.habitrpg.android.habitica.models.user.OwnedItem @@ -38,7 +40,6 @@ import com.habitrpg.android.habitica.ui.views.insufficientCurrency.InsufficientG import com.habitrpg.android.habitica.ui.views.insufficientCurrency.InsufficientHourglassesDialog import com.habitrpg.android.habitica.ui.views.insufficientCurrency.InsufficientSubscriberGemsDialog import com.habitrpg.android.habitica.ui.views.tasks.form.StepperValueFormView -import io.reactivex.rxjava3.core.Flowable import io.reactivex.rxjava3.disposables.CompositeDisposable import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.Dispatchers @@ -254,7 +255,12 @@ class PurchaseDialog(context: Context, component: UserComponent?, val item: Shop priceLabel = buyButton.findViewById(R.id.priceLabel) priceLabel.animationDuration = 0L buyLabel = buyButton.findViewById(R.id.buy_label) - pinButton.setOnClickListener { inventoryRepository.togglePinnedItem(shopItem).subscribe({ isPinned = !this.isPinned }, ExceptionHandler.rx()) } + pinButton.setOnClickListener { + lifecycleScope.launchCatching { + inventoryRepository.togglePinnedItem(shopItem) + isPinned = !isPinned + } + } shopItem = item @@ -357,30 +363,31 @@ class PurchaseDialog(context: Context, component: UserComponent?, val item: Shop ) HapticFeedbackManager.tap(contentView) val snackbarText = arrayOf("") - val observable: Flowable + val observable: (suspend () -> Unit) if (shopIdentifier != null && shopIdentifier == Shop.TIME_TRAVELERS_SHOP || "mystery_set" == shopItem.purchaseType || shopItem.currency == "hourglasses") { observable = if (shopItem.purchaseType == "gear") { - inventoryRepository.purchaseMysterySet(shopItem.key).cast(Any::class.java) + { inventoryRepository.purchaseMysterySet(shopItem.key) } } else { - inventoryRepository.purchaseHourglassItem(shopItem.purchaseType, shopItem.key).cast(Any::class.java) + { inventoryRepository.purchaseHourglassItem(shopItem.purchaseType, shopItem.key) } } - // TODO: } else if (shopItem.purchaseType == "fortify") { - // observable = userRepository.reroll().cast(Any::class.java) + } else if (shopItem.purchaseType == "fortify") { + observable = { userRepository.reroll() } } else if (shopItem.purchaseType == "quests" && shopItem.currency == "gold") { - observable = inventoryRepository.purchaseQuest(shopItem.key).cast(Any::class.java) + observable = { inventoryRepository.purchaseQuest(shopItem.key) } } else if (shopItem.purchaseType == "debuffPotion") { - observable = userRepository.useSkill(shopItem.key, null).cast(Any::class.java) + observable = { userRepository.useSkill(shopItem.key, null) } } else if (shopItem.purchaseType == "customization" || shopItem.purchaseType == "background" || shopItem.purchaseType == "backgrounds" || shopItem.purchaseType == "customizationSet") { - observable = userRepository.unlockPath(item.unlockPath ?: "${item.pinType}.${item.key}" ?: "", item.value).cast(Any::class.java) + observable = { userRepository.unlockPath(item.unlockPath ?: "${item.pinType}.${item.key}" ?: "", item.value) } } else if (shopItem.purchaseType == "debuffPotion") { - observable = userRepository.useSkill(shopItem.key, null).cast(Any::class.java) + observable = { userRepository.useSkill(shopItem.key, null) } } else if (shopItem.purchaseType == "card") { purchaseCardAction?.invoke(shopItem) dismiss() return } else if ("gold" == shopItem.currency && "gem" != shopItem.key) { - observable = inventoryRepository.buyItem(user, shopItem.key, shopItem.value.toDouble(), quantity).map { buyResponse -> - if (shopItem.key == "armoire" && configManager.enableNewArmoire()) { + observable = { + val buyResponse = inventoryRepository.buyItem(user, shopItem.key, shopItem.value.toDouble(), quantity) + if (shopItem.key == "armoire" && configManager.enableNewArmoire() && buyResponse != null) { MainNavigationController.navigate( R.id.armoireActivity, ArmoireActivityDirections.openArmoireActivity( @@ -391,45 +398,34 @@ class PurchaseDialog(context: Context, component: UserComponent?, val item: Shop ).arguments ) } - buyResponse } } else { - observable = inventoryRepository.purchaseItem(shopItem.purchaseType, shopItem.key, quantity).cast(Any::class.java) + observable = { inventoryRepository.purchaseItem(shopItem.purchaseType, shopItem.key, quantity) } } - val subscription = observable - .doOnNext { - val text = if (snackbarText[0].isNotEmpty()) { - snackbarText[0] - } else { - context.getString(R.string.successful_purchase, shopItem.text) - } - val rightTextColor = when (item.currency) { - "gold" -> ContextCompat.getColor(context, R.color.text_yellow) - "gems" -> ContextCompat.getColor(context, R.color.text_green) - "hourglasses" -> ContextCompat.getColor(context, R.color.text_brand) - else -> 0 - } - ((application?.currentActivity?.get() ?: getActivity() ?: ownerActivity) as? SnackbarActivity)?.showSnackbar( - content = text, - rightIcon = priceLabel.compoundDrawables[0], - rightTextColor = rightTextColor, - rightText = "-" + priceLabel.text - ) + lifecycleScope.launchCatching { + observable() + val text = if (snackbarText[0].isNotEmpty()) { + snackbarText[0] + } else { + context.getString(R.string.successful_purchase, shopItem.text) } - // TODO: .flatMap { userRepository.retrieveUser(withTasks = false, forced = true) } - .flatMap { inventoryRepository.retrieveInAppRewards() } - .subscribe({ - if (item.isTypeGear || item.currency == "hourglasses") { - onGearPurchased?.invoke(item) - } - }) { throwable -> - if (throwable.javaClass.isAssignableFrom(retrofit2.HttpException::class.java)) { - val error = throwable as retrofit2.HttpException - if (error.code() == 401 && shopItem.currency == "gems") { - MainNavigationController.navigate(R.id.gemPurchaseActivity, bundleOf(Pair("openSubscription", false))) - } - } + val rightTextColor = when (item.currency) { + "gold" -> ContextCompat.getColor(context, R.color.text_yellow) + "gems" -> ContextCompat.getColor(context, R.color.text_green) + "hourglasses" -> ContextCompat.getColor(context, R.color.text_brand) + else -> 0 } + ((application?.currentActivity?.get() ?: getActivity() ?: ownerActivity) as? SnackbarActivity)?.showSnackbar( + content = text, + rightIcon = priceLabel.compoundDrawables[0], + rightTextColor = rightTextColor, + rightText = "-" + priceLabel.text + ) + inventoryRepository.retrieveInAppRewards() + if (item.isTypeGear || item.currency == "hourglasses") { + onGearPurchased?.invoke(item) + } + } } private fun displayPurchaseConfirmationDialog(quantity: Int) { diff --git a/shared/build.gradle.kts b/shared/build.gradle.kts index f06786270..5ac5eb01e 100644 --- a/shared/build.gradle.kts +++ b/shared/build.gradle.kts @@ -21,7 +21,7 @@ kotlin { sourceSets { val commonMain by getting { dependencies { - implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.0-native-mt") + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4") implementation("io.realm.kotlin:library-base:1.0.2") } } diff --git a/shared/src/androidMain/kotlin/com/habitrpg/shared/habitica/PlatformLogger.kt b/shared/src/androidMain/kotlin/com/habitrpg/shared/habitica/PlatformLogger.kt index eaaa0c50a..fa2589ee1 100644 --- a/shared/src/androidMain/kotlin/com/habitrpg/shared/habitica/PlatformLogger.kt +++ b/shared/src/androidMain/kotlin/com/habitrpg/shared/habitica/PlatformLogger.kt @@ -14,6 +14,10 @@ actual class PlatformLogger actual constructor() { Log.i(tag, message) } + actual fun logWarning(tag: String, message: String) { + Log.w(tag, message) + } + actual fun logError(tag: String, message: String) { Log.e(tag, message) } diff --git a/shared/src/commonMain/kotlin/com/habitrpg/shared/habitica/Logger.kt b/shared/src/commonMain/kotlin/com/habitrpg/shared/habitica/Logger.kt index b79f07723..da6de266c 100644 --- a/shared/src/commonMain/kotlin/com/habitrpg/shared/habitica/Logger.kt +++ b/shared/src/commonMain/kotlin/com/habitrpg/shared/habitica/Logger.kt @@ -5,12 +5,13 @@ expect class PlatformLogger() { fun logDebug(tag: String, message: String) fun logInfo(tag: String, message: String) + fun logWarning(tag: String, message: String) fun logError(tag: String, message: String) fun logError(tag: String, message: String, exception: Throwable) } enum class LogLevel { - ERROR, INFO, DEBUG + ERROR, INFO, WARNING, DEBUG } class HLogger { @@ -26,6 +27,7 @@ class HLogger { when (level) { LogLevel.ERROR -> platformLogger.logError(tag, message) LogLevel.INFO -> platformLogger.logInfo(tag, message) + LogLevel.WARNING -> platformLogger.logWarning(tag, message) LogLevel.DEBUG -> platformLogger.logDebug(tag, message) } } diff --git a/shared/src/iosMain/kotlin/com/habitrpg/shared/habitica/PlatformLogger.kt b/shared/src/iosMain/kotlin/com/habitrpg/shared/habitica/PlatformLogger.kt index 84871b48d..e30cc6c03 100644 --- a/shared/src/iosMain/kotlin/com/habitrpg/shared/habitica/PlatformLogger.kt +++ b/shared/src/iosMain/kotlin/com/habitrpg/shared/habitica/PlatformLogger.kt @@ -5,18 +5,22 @@ actual class PlatformLogger { get() = true actual fun logDebug(tag: String, message: String) { - println("[DEBUG] $tag: $message") + println("[🟢] $tag: $message") } actual fun logInfo(tag: String, message: String) { - println("[INFO] $tag: $message") + println("[🟡] $tag: $message") + } + + actual fun logWarning(tag: String, message: String) { + println("[🟠] $tag: $message") } actual fun logError(tag: String, message: String) { - println("[ERROR] $tag: $message") + println("[🔴] $tag: $message") } actual fun logError(tag: String, message: String, exception: Throwable) { - println("[ERROR] $tag: $message\n${exception.getStackTrace().joinToString("\n")}") + println("[🔴] $tag: $message\n${exception.getStackTrace().joinToString("\n")}") } } \ No newline at end of file From c4a482eac82eb6f2f8f1645987b156359693c0a2 Mon Sep 17 00:00:00 2001 From: Phillip Thelen Date: Wed, 16 Nov 2022 14:02:58 +0100 Subject: [PATCH 30/36] Remove RxJava from all repositories --- Habitica/AndroidManifest.xml | 3 - .../items/ItemRecyclerFragmentTest.kt | 8 +- .../inventory/stable/PetDetailFragmentTest.kt | 8 +- .../android/habitica/api/ApiService.kt | 165 +++--- .../habitica/api/MaintenanceApiService.java | 16 - .../habitica/api/MaintenanceApiService.kt | 12 + .../habitica/components/UserComponent.java | 3 - .../android/habitica/data/ApiClient.kt | 165 +++--- .../habitica/data/ChallengeRepository.kt | 34 +- .../habitica/data/ContentRepository.kt | 4 +- .../habitica/data/CustomizationRepository.kt | 4 +- .../android/habitica/data/FAQRepository.kt | 6 +- .../habitica/data/InventoryRepository.kt | 44 +- .../android/habitica/data/SocialRepository.kt | 57 +- .../android/habitica/data/TagRepository.kt | 19 +- .../android/habitica/data/TaskRepository.kt | 28 +- .../habitica/data/TutorialRepository.kt | 6 +- .../android/habitica/data/UserRepository.kt | 36 +- .../data/implementation/ApiClientImpl.kt | 359 ++++++------ .../implementation/ChallengeRepositoryImpl.kt | 100 ++-- .../implementation/ContentRepositoryImpl.kt | 4 +- .../CustomizationRepositoryImpl.kt | 4 +- .../data/implementation/FAQRepositoryImpl.kt | 6 +- .../implementation/InventoryRepositoryImpl.kt | 137 +++-- .../implementation/SocialRepositoryImpl.kt | 145 +++-- .../data/implementation/TagRepositoryImpl.kt | 63 +-- .../data/implementation/TaskRepositoryImpl.kt | 141 +++-- .../implementation/TutorialRepositoryImpl.kt | 6 +- .../data/implementation/UserRepositoryImpl.kt | 205 +++---- .../data/local/ChallengeLocalRepository.kt | 16 +- .../data/local/ContentLocalRepository.kt | 3 +- .../local/CustomizationLocalRepository.kt | 4 +- .../habitica/data/local/FAQLocalRepository.kt | 6 +- .../data/local/InventoryLocalRepository.kt | 29 +- .../data/local/SocialLocalRepository.kt | 9 +- .../habitica/data/local/TagLocalRepository.kt | 3 +- .../data/local/TaskLocalRepository.kt | 9 +- .../data/local/TutorialLocalRepository.kt | 6 +- .../data/local/UserLocalRepository.kt | 11 +- .../RealmChallengeLocalRepository.kt | 69 ++- .../RealmContentLocalRepository.kt | 16 +- .../RealmCustomizationLocalRepository.kt | 14 +- .../implementation/RealmFAQLocalRepository.kt | 26 +- .../RealmInventoryLocalRepository.kt | 361 ++++++------ .../RealmSocialLocalRepository.kt | 30 +- .../implementation/RealmTagLocalRepository.kt | 10 +- .../RealmTaskLocalRepository.kt | 47 +- .../RealmTutorialLocalRepository.kt | 29 +- .../RealmUserLocalRepository.kt | 64 +-- .../habitica/helpers/AppConfigManager.kt | 12 +- .../habitica/helpers/ExceptionHandler.kt | 6 +- .../habitica/helpers/NotificationsManager.kt | 41 +- .../habitica/helpers/PurchaseHandler.kt | 97 ++-- .../habitica/helpers/UserStatComputer.kt | 4 +- .../notifications/PushNotificationManager.kt | 11 +- .../interactors/CheckClassSelectionUseCase.kt | 30 +- .../interactors/DisplayItemDropUseCase.kt | 53 +- .../habitica/interactors/FeedPetUseCase.kt | 110 ++-- .../habitica/interactors/HatchPetUseCase.kt | 20 +- .../habitica/interactors/LevelUpUseCase.kt | 135 +++-- .../habitica/interactors/NotifyUserUseCase.kt | 42 +- .../android/habitica/interactors/UseCase.kt | 4 +- .../LocalNotificationActionReceiver.kt | 33 +- .../receivers/NotificationPublisher.kt | 39 +- .../habitica/ui/activities/ArmoireActivity.kt | 6 +- .../habitica/ui/activities/BaseActivity.kt | 24 +- .../ui/activities/ChallengeFormActivity.kt | 160 +++--- .../ui/activities/FullProfileActivity.kt | 184 ++++-- .../activities/HabitButtonWidgetActivity.kt | 7 +- .../habitica/ui/activities/LoginActivity.kt | 38 +- .../habitica/ui/activities/MainActivity.kt | 49 +- .../ui/activities/MaintenanceActivity.kt | 24 +- .../ui/activities/NotificationsActivity.kt | 16 +- .../ui/activities/ReportMessageActivity.kt | 15 +- .../habitica/ui/activities/SetupActivity.kt | 10 +- .../ui/activities/SkillMemberActivity.kt | 12 +- .../ui/activities/SkillTasksActivity.kt | 7 +- .../ui/activities/TaskFormActivity.kt | 274 +++++---- .../ui/activities/VerifyUsernameActivity.kt | 135 ----- .../adapter/SkillTasksRecyclerViewAdapter.kt | 11 +- .../inventory/EquipmentRecyclerViewAdapter.kt | 7 +- .../adapter/inventory/ItemRecyclerAdapter.kt | 56 +- .../inventory/MountDetailRecyclerAdapter.kt | 9 +- .../inventory/PetDetailRecyclerAdapter.kt | 16 +- .../inventory/StableRecyclerAdapter.kt | 14 +- .../setup/CustomizationSetupAdapter.kt | 8 +- .../social/PartyMemberRecyclerViewAdapter.kt | 11 +- .../ChallengeTasksRecyclerViewAdapter.kt | 29 +- .../ui/fragments/AchievementsFragment.kt | 11 +- .../ui/fragments/BaseDialogFragment.kt | 36 +- .../habitica/ui/fragments/BaseFragment.kt | 34 +- .../ui/fragments/NavigationDrawerFragment.kt | 314 ++++++++--- .../habitica/ui/fragments/StatsFragment.kt | 126 ++--- .../AvatarCustomizationFragment.kt | 56 +- .../equipment/EquipmentDetailFragment.kt | 20 +- .../inventory/items/ItemDialogFragment.kt | 94 ++-- .../inventory/items/ItemRecyclerFragment.kt | 83 ++- .../fragments/inventory/shops/ShopFragment.kt | 120 ++-- .../stable/MountDetailRecyclerFragment.kt | 14 +- .../stable/PetDetailRecyclerFragment.kt | 40 +- .../stable/StableRecyclerFragment.kt | 11 +- .../preferences/AccountPreferenceFragment.kt | 98 ++-- .../preferences/PreferencesFragment.kt | 8 +- .../purchases/GiftBalanceGemsFragment.kt | 21 +- .../purchases/SubscriptionFragment.kt | 25 +- .../ui/fragments/setup/AvatarSetupFragment.kt | 7 +- .../ui/fragments/setup/WelcomeFragment.kt | 94 +++- .../skills/SkillTasksRecyclerViewFragment.kt | 19 +- .../ui/fragments/skills/SkillsFragment.kt | 28 +- .../social/InboxMessageListFragment.kt | 7 +- .../fragments/social/InboxOverviewFragment.kt | 18 +- .../fragments/social/QuestDetailFragment.kt | 90 +-- .../challenges/ChallengeDetailFragment.kt | 108 ++-- .../challenges/ChallengeListFragment.kt | 87 +-- .../social/guilds/GuildDetailFragment.kt | 13 +- .../social/guilds/GuildListFragment.kt | 40 +- .../social/party/NoPartyFragmentFragment.kt | 5 +- .../social/party/PartyDetailFragment.kt | 22 +- .../ui/fragments/support/FAQDetailFragment.kt | 18 +- .../fragments/support/FAQOverviewFragment.kt | 41 +- .../tasks/RewardsRecyclerviewFragment.kt | 13 +- .../tasks/TaskRecyclerViewFragment.kt | 529 ++++++++++-------- .../ui/fragments/tasks/TasksFragment.kt | 18 +- .../ui/helpers/AutocompleteAdapter.kt | 4 +- .../ui/viewHolders/MountViewHolder.kt | 7 +- .../habitica/ui/viewHolders/PetViewHolder.kt | 13 +- .../ui/viewmodels/AuthenticationViewModel.kt | 63 +-- .../habitica/ui/viewmodels/BaseViewModel.kt | 4 - .../habitica/ui/viewmodels/GroupViewModel.kt | 112 ++-- .../ui/viewmodels/MainActivityViewModel.kt | 56 +- .../ui/viewmodels/NotificationsViewModel.kt | 96 ++-- .../habitica/ui/viewmodels/PartyViewModel.kt | 29 +- .../habitica/ui/viewmodels/StableViewModel.kt | 7 +- .../habitica/ui/viewmodels/TasksViewModel.kt | 7 +- .../equipment/EquipmentOverviewViewModel.kt | 11 +- .../ui/views/dialogs/PetSuggestHatchDialog.kt | 13 +- .../habitica/ui/views/shops/PurchaseDialog.kt | 11 +- .../ui/views/stats/BulkAllocateStatsDialog.kt | 24 +- .../ui/views/tasks/TaskFilterDialog.kt | 25 +- .../views/yesterdailies/YesterdailyDialog.kt | 119 ++-- .../widget/AvatarStatsWidgetProvider.kt | 22 +- 141 files changed, 3548 insertions(+), 3652 deletions(-) delete mode 100644 Habitica/src/main/java/com/habitrpg/android/habitica/api/MaintenanceApiService.java create mode 100644 Habitica/src/main/java/com/habitrpg/android/habitica/api/MaintenanceApiService.kt delete mode 100644 Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/VerifyUsernameActivity.kt diff --git a/Habitica/AndroidManifest.xml b/Habitica/AndroidManifest.xml index 39e9edf19..ed0a0b018 100644 --- a/Habitica/AndroidManifest.xml +++ b/Habitica/AndroidManifest.xml @@ -204,9 +204,6 @@ android:theme="@style/Theme.AppCompat.Light.NoActionBar" /> - diff --git a/Habitica/src/androidTest/java/com/habitrpg/android/habitica/ui/fragments/inventory/items/ItemRecyclerFragmentTest.kt b/Habitica/src/androidTest/java/com/habitrpg/android/habitica/ui/fragments/inventory/items/ItemRecyclerFragmentTest.kt index d4806812c..ca01795b6 100644 --- a/Habitica/src/androidTest/java/com/habitrpg/android/habitica/ui/fragments/inventory/items/ItemRecyclerFragmentTest.kt +++ b/Habitica/src/androidTest/java/com/habitrpg/android/habitica/ui/fragments/inventory/items/ItemRecyclerFragmentTest.kt @@ -134,7 +134,7 @@ internal class ItemRecyclerFragmentTest : FragmentTestCase() - every { hatchPetUseCase.observable(capture(slot)) } returns mockk(relaxed = true) + every { hatchPetUseCase.callInteractor(capture(slot)) } returns mockk(relaxed = true) fragment.itemType = "eggs" launchFragment() screen { @@ -142,7 +142,7 @@ internal class ItemRecyclerFragmentTest : FragmentTestCase { withDescendant { withText("Wolf") } }.click() KView { withText(R.string.hatch_with_potion) }.click() KView { withText("Shade") }.click() - verify { hatchPetUseCase.observable(any()) } + verify { hatchPetUseCase.callInteractor(any()) } slot.captured.egg.key shouldBe "Wolf" slot.captured.potion.key shouldBe "Shade" } @@ -152,7 +152,7 @@ internal class ItemRecyclerFragmentTest : FragmentTestCase() - every { hatchPetUseCase.observable(capture(slot)) } returns mockk(relaxed = true) + every { hatchPetUseCase.callInteractor(capture(slot)) } returns mockk(relaxed = true) fragment.itemType = "hatchingPotions" launchFragment() screen { @@ -160,7 +160,7 @@ internal class ItemRecyclerFragmentTest : FragmentTestCase { withDescendant { withText("Shade") } }.click() KView { withText(R.string.hatch_egg) }.click() KView { withText("Wolf") }.click() - verify { hatchPetUseCase.observable(any()) } + verify { hatchPetUseCase.callInteractor(any()) } slot.captured.egg.key shouldBe "Wolf" slot.captured.potion.key shouldBe "Shade" } diff --git a/Habitica/src/androidTest/java/com/habitrpg/android/habitica/ui/fragments/inventory/stable/PetDetailFragmentTest.kt b/Habitica/src/androidTest/java/com/habitrpg/android/habitica/ui/fragments/inventory/stable/PetDetailFragmentTest.kt index 78509ba7e..b44295720 100644 --- a/Habitica/src/androidTest/java/com/habitrpg/android/habitica/ui/fragments/inventory/stable/PetDetailFragmentTest.kt +++ b/Habitica/src/androidTest/java/com/habitrpg/android/habitica/ui/fragments/inventory/stable/PetDetailFragmentTest.kt @@ -69,7 +69,7 @@ internal class PetDetailRecyclerFragmentTest : @Test fun canFeedPet() { val slot = CapturingSlot() - every { feedPetUseCase.observable(capture(slot)) } returns mockk(relaxed = true) + every { feedPetUseCase.callInteractor(capture(slot)) } returns mockk(relaxed = true) every { inventoryRepository.getPets( any(), @@ -92,7 +92,7 @@ internal class PetDetailRecyclerFragmentTest : childWith { withContentDescription("Skeleton Cactus") }.click() KView { withText(R.string.feed) }.click() KView { withText("Meat") }.click() - verify { feedPetUseCase.observable(any()) } + verify { feedPetUseCase.callInteractor(any()) } slot.captured.pet.key shouldBe "Cactus-Skeleton" slot.captured.food.key shouldBe "Meat" } @@ -102,7 +102,7 @@ internal class PetDetailRecyclerFragmentTest : @Test fun canUseSaddle() { val slot = CapturingSlot() - every { feedPetUseCase.observable(capture(slot)) } returns mockk(relaxed = true) + every { feedPetUseCase.callInteractor(capture(slot)) } returns mockk(relaxed = true) every { inventoryRepository.getPets( any(), @@ -122,7 +122,7 @@ internal class PetDetailRecyclerFragmentTest : recycler { childWith { withContentDescription("Shade Fox") }.click() KView { withText(R.string.use_saddle) }.click() - verify { feedPetUseCase.observable(any()) } + verify { feedPetUseCase.callInteractor(any()) } slot.captured.pet.key shouldBe "Fox-Shade" slot.captured.food.key shouldBe "Saddle" } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/api/ApiService.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/api/ApiService.kt index 8fb909988..e56a215b0 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/api/ApiService.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/api/ApiService.kt @@ -36,7 +36,6 @@ import com.habitrpg.shared.habitica.models.responses.FeedResponse import com.habitrpg.shared.habitica.models.responses.Status import com.habitrpg.shared.habitica.models.responses.TaskDirectionData import com.habitrpg.shared.habitica.models.responses.VerifyUsernameResponse -import io.reactivex.rxjava3.core.Flowable import retrofit2.http.Body import retrofit2.http.DELETE import retrofit2.http.GET @@ -60,7 +59,7 @@ interface ApiService { @GET("inbox/messages") suspend fun getInboxMessages(@Query("conversation") uuid: String, @Query("page") page: Int): HabitResponse> @GET("inbox/conversations") - fun getInboxConversations(): Flowable>> + suspend fun getInboxConversations(): HabitResponse> @GET("tasks/user") suspend fun getTasks(): HabitResponse @@ -75,13 +74,13 @@ interface ApiService { suspend fun updateUser(@Body updateDictionary: Map): HabitResponse @PUT("user/") - fun registrationLanguage(@Header("Accept-Language") registrationLanguage: String): Flowable> + suspend fun registrationLanguage(@Header("Accept-Language") registrationLanguage: String): HabitResponse @GET("user/in-app-rewards") suspend fun retrieveInAppRewards(): HabitResponse> @POST("user/equip/{type}/{key}") - fun equipItem(@Path("type") type: String, @Path("key") itemKey: String): Flowable> + suspend fun equipItem(@Path("type") type: String, @Path("key") itemKey: String): HabitResponse @POST("user/buy/{key}") suspend fun buyItem(@Path("key") itemKey: String, @Body quantity: Map): HabitResponse @@ -106,72 +105,72 @@ interface ApiService { suspend fun purchaseSpecialSpell(@Path("key") key: String): HabitResponse @POST("user/sell/{type}/{key}") - fun sellItem(@Path("type") itemType: String, @Path("key") itemKey: String): Flowable> + suspend fun sellItem(@Path("type") itemType: String, @Path("key") itemKey: String): HabitResponse @POST("user/feed/{pet}/{food}") - fun feedPet(@Path("pet") petKey: String, @Path("food") foodKey: String): Flowable> + suspend fun feedPet(@Path("pet") petKey: String, @Path("food") foodKey: String): HabitResponse @POST("user/hatch/{egg}/{hatchingPotion}") - fun hatchPet(@Path("egg") eggKey: String, @Path("hatchingPotion") hatchingPotionKey: String): Flowable> + suspend fun hatchPet(@Path("egg") eggKey: String, @Path("hatchingPotion") hatchingPotionKey: String): HabitResponse @GET("tasks/user") - fun getTasks(@Query("type") type: String): Flowable> + suspend fun getTasks(@Query("type") type: String): HabitResponse @GET("tasks/user") - fun getTasks(@Query("type") type: String, @Query("dueDate") dueDate: String): Flowable> + suspend fun getTasks(@Query("type") type: String, @Query("dueDate") dueDate: String): HabitResponse @POST("user/unlock") suspend fun unlockPath(@Query("path") path: String): HabitResponse @GET("tasks/{id}") - fun getTask(@Path("id") id: String): Flowable> + suspend fun getTask(@Path("id") id: String): HabitResponse @POST("tasks/{id}/score/{direction}") suspend fun postTaskDirection(@Path("id") id: String, @Path("direction") direction: String): HabitResponse @POST("tasks/bulk-score") - fun bulkScoreTasks(@Body data: List>): Flowable> + suspend fun bulkScoreTasks(@Body data: List>): HabitResponse @POST("tasks/{id}/move/to/{position}") - fun postTaskNewPosition(@Path("id") id: String, @Path("position") position: Int): Flowable>> + suspend fun postTaskNewPosition(@Path("id") id: String, @Path("position") position: Int): HabitResponse> @POST("tasks/{taskId}/checklist/{itemId}/score") suspend fun scoreChecklistItem(@Path("taskId") taskId: String, @Path("itemId") itemId: String): HabitResponse @POST("tasks/user") - fun createTask(@Body item: Task): Flowable> + suspend fun createTask(@Body item: Task): HabitResponse @POST("tasks/user") - fun createTasks(@Body tasks: List): Flowable>> + suspend fun createTasks(@Body tasks: List): HabitResponse> @PUT("tasks/{id}") - fun updateTask(@Path("id") id: String, @Body item: Task): Flowable> + suspend fun updateTask(@Path("id") id: String, @Body item: Task): HabitResponse @DELETE("tasks/{id}") - fun deleteTask(@Path("id") id: String): Flowable> + suspend fun deleteTask(@Path("id") id: String): HabitResponse @POST("tags") - fun createTag(@Body tag: Tag): Flowable> + suspend fun createTag(@Body tag: Tag): HabitResponse @PUT("tags/{id}") - fun updateTag(@Path("id") id: String, @Body tag: Tag): Flowable> + suspend fun updateTag(@Path("id") id: String, @Body tag: Tag): HabitResponse @DELETE("tags/{id}") - fun deleteTag(@Path("id") id: String): Flowable> + suspend fun deleteTag(@Path("id") id: String): HabitResponse @POST("user/auth/local/register") - fun registerUser(@Body auth: UserAuth): Flowable> + suspend fun registerUser(@Body auth: UserAuth): HabitResponse @POST("user/auth/local/login") - fun connectLocal(@Body auth: UserAuth): Flowable> + suspend fun connectLocal(@Body auth: UserAuth): HabitResponse @POST("user/auth/social") - fun connectSocial(@Body auth: UserAuthSocial): Flowable> + suspend fun connectSocial(@Body auth: UserAuthSocial): HabitResponse @DELETE("user/auth/social/{network}") - fun disconnectSocial(@Path("network") network: String): Flowable> + suspend fun disconnectSocial(@Path("network") network: String): HabitResponse @POST("user/auth/apple") - fun loginApple(@Body auth: Map): Flowable> + suspend fun loginApple(@Body auth: Map): HabitResponse @POST("user/sleep") suspend fun sleep(): HabitResponse @@ -199,12 +198,12 @@ interface ApiService { suspend fun disableClasses(): HabitResponse @POST("user/mark-pms-read") - fun markPrivateMessagesRead(): Flowable + suspend fun markPrivateMessagesRead(): Void /* Group API */ @GET("groups") - fun listGroups(@Query("type") type: String): Flowable>> + suspend fun listGroups(@Query("type") type: String): HabitResponse> @GET("groups/{gid}") suspend fun getGroup(@Path("gid") groupId: String): HabitResponse @@ -228,13 +227,13 @@ interface ApiService { suspend fun leaveGroup(@Path("gid") groupId: String, @Query("keepChallenges") keepChallenges: String): HabitResponse @POST("groups/{gid}/chat") - fun postGroupChat(@Path("gid") groupId: String, @Body message: Map): Flowable> + suspend fun postGroupChat(@Path("gid") groupId: String, @Body message: Map): HabitResponse @DELETE("groups/{gid}/chat/{messageId}") - fun deleteMessage(@Path("gid") groupId: String, @Path("messageId") messageId: String): Flowable> + suspend fun deleteMessage(@Path("gid") groupId: String, @Path("messageId") messageId: String): HabitResponse @DELETE("inbox/messages/{messageId}") - fun deleteInboxMessage(@Path("messageId") messageId: String): Flowable> + suspend fun deleteInboxMessage(@Path("messageId") messageId: String): HabitResponse @GET("groups/{gid}/members") suspend fun getGroupMembers( @@ -251,59 +250,59 @@ interface ApiService { // Like returns the full chat list @POST("groups/{gid}/chat/{mid}/like") - fun likeMessage(@Path("gid") groupId: String, @Path("mid") mid: String): Flowable> + suspend fun likeMessage(@Path("gid") groupId: String, @Path("mid") mid: String): HabitResponse @POST("groups/{gid}/chat/{mid}/flag") - fun flagMessage( + suspend fun flagMessage( @Path("gid") groupId: String, @Path("mid") mid: String, @Body data: Map - ): Flowable> + ): HabitResponse @POST("groups/{gid}/chat/seen") - fun seenMessages(@Path("gid") groupId: String): Flowable> + suspend fun seenMessages(@Path("gid") groupId: String): HabitResponse @POST("groups/{gid}/invite") - fun inviteToGroup(@Path("gid") groupId: String, @Body inviteData: Map): Flowable>> + suspend fun inviteToGroup(@Path("gid") groupId: String, @Body inviteData: Map): HabitResponse> @POST("groups/{gid}/reject-invite") - fun rejectGroupInvite(@Path("gid") groupId: String): Flowable> + suspend fun rejectGroupInvite(@Path("gid") groupId: String): HabitResponse @POST("groups/{gid}/quests/accept") - fun acceptQuest(@Path("gid") groupId: String): Flowable> + suspend fun acceptQuest(@Path("gid") groupId: String): HabitResponse @POST("groups/{gid}/quests/reject") - fun rejectQuest(@Path("gid") groupId: String): Flowable> + suspend fun rejectQuest(@Path("gid") groupId: String): HabitResponse @POST("groups/{gid}/quests/cancel") - fun cancelQuest(@Path("gid") groupId: String): Flowable> + suspend fun cancelQuest(@Path("gid") groupId: String): HabitResponse @POST("groups/{gid}/quests/force-start") - fun forceStartQuest(@Path("gid") groupId: String, @Body group: Group): Flowable> + suspend fun forceStartQuest(@Path("gid") groupId: String, @Body group: Group): HabitResponse @POST("groups/{gid}/quests/invite/{questKey}") - fun inviteToQuest(@Path("gid") groupId: String, @Path("questKey") questKey: String): Flowable> + suspend fun inviteToQuest(@Path("gid") groupId: String, @Path("questKey") questKey: String): HabitResponse @POST("groups/{gid}/quests/abort") - fun abortQuest(@Path("gid") groupId: String): Flowable> + suspend fun abortQuest(@Path("gid") groupId: String): HabitResponse @POST("groups/{gid}/quests/leave") - fun leaveQuest(@Path("gid") groupId: String): Flowable> + suspend fun leaveQuest(@Path("gid") groupId: String): HabitResponse @POST("/iap/android/verify") - fun validatePurchase(@Body request: PurchaseValidationRequest): Flowable> + suspend fun validatePurchase(@Body request: PurchaseValidationRequest): HabitResponse @POST("/iap/android/subscribe") - fun validateSubscription(@Body request: PurchaseValidationRequest): Flowable> + suspend fun validateSubscription(@Body request: PurchaseValidationRequest): HabitResponse @GET("/iap/android/subscribe/cancel") suspend fun cancelSubscription(): HabitResponse @POST("/iap/android/norenew-subscribe") - fun validateNoRenewSubscription(@Body request: PurchaseValidationRequest): Flowable> + suspend fun validateNoRenewSubscription(@Body request: PurchaseValidationRequest): HabitResponse @POST("user/custom-day-start") - fun changeCustomDayStart(@Body updateObject: Map): Flowable> + suspend fun changeCustomDayStart(@Body updateObject: Map): HabitResponse // Members URL @GET("members/{mid}") @@ -313,128 +312,128 @@ interface ApiService { suspend fun getMemberWithUsername(@Path("username") username: String): HabitResponse @GET("members/{mid}/achievements") - fun getMemberAchievements(@Path("mid") memberId: String, @Query("lang") language: String?): Flowable>> + suspend fun getMemberAchievements(@Path("mid") memberId: String, @Query("lang") language: String?): HabitResponse> @POST("members/send-private-message") suspend fun postPrivateMessage(@Body messageDetails: Map): HabitResponse @GET("members/find/{username}") - fun findUsernames( + suspend fun findUsernames( @Path("username") username: String, @Query("context") context: String?, @Query("id") id: String? - ): Flowable>> + ): HabitResponse> @POST("members/flag-private-message/{mid}") - fun flagInboxMessage(@Path("mid") mid: String, @Body data: Map): Flowable> + suspend fun flagInboxMessage(@Path("mid") mid: String, @Body data: Map): HabitResponse @GET("shops/{identifier}") - fun retrieveShopInventory(@Path("identifier") identifier: String, @Query("lang") language: String?): Flowable> + suspend fun retrieveShopInventory(@Path("identifier") identifier: String, @Query("lang") language: String?): HabitResponse @GET("shops/market-gear") - fun retrieveMarketGear(@Query("lang") language: String?): Flowable> + suspend fun retrieveMarketGear(@Query("lang") language: String?): HabitResponse // Push notifications @POST("user/push-devices") - fun addPushDevice(@Body pushDeviceData: Map): Flowable>> + suspend fun addPushDevice(@Body pushDeviceData: Map): HabitResponse> @DELETE("user/push-devices/{regId}") - fun deletePushDevice(@Path("regId") regId: String): Flowable>> + suspend fun deletePushDevice(@Path("regId") regId: String): HabitResponse> /* challenges api */ @GET("challenges/user") - fun getUserChallenges(@Query("page") page: Int?, @Query("member") memberOnly: Boolean): Flowable>> + suspend fun getUserChallenges(@Query("page") page: Int?, @Query("member") memberOnly: Boolean): HabitResponse> @GET("challenges/user") - fun getUserChallenges(@Query("page") page: Int?): Flowable>> + suspend fun getUserChallenges(@Query("page") page: Int?): HabitResponse> @GET("tasks/challenge/{challengeId}") - fun getChallengeTasks(@Path("challengeId") challengeId: String): Flowable> + suspend fun getChallengeTasks(@Path("challengeId") challengeId: String): HabitResponse @GET("challenges/{challengeId}") - fun getChallenge(@Path("challengeId") challengeId: String): Flowable> + suspend fun getChallenge(@Path("challengeId") challengeId: String): HabitResponse @POST("challenges/{challengeId}/join") - fun joinChallenge(@Path("challengeId") challengeId: String): Flowable> + suspend fun joinChallenge(@Path("challengeId") challengeId: String): HabitResponse @POST("challenges/{challengeId}/leave") - fun leaveChallenge(@Path("challengeId") challengeId: String, @Body body: LeaveChallengeBody): Flowable> + suspend fun leaveChallenge(@Path("challengeId") challengeId: String, @Body body: LeaveChallengeBody): HabitResponse @POST("challenges") - fun createChallenge(@Body challenge: Challenge): Flowable> + suspend fun createChallenge(@Body challenge: Challenge): HabitResponse @POST("tasks/challenge/{challengeId}") - fun createChallengeTasks(@Path("challengeId") challengeId: String, @Body tasks: List): Flowable>> + suspend fun createChallengeTasks(@Path("challengeId") challengeId: String, @Body tasks: List): HabitResponse> @POST("tasks/challenge/{challengeId}") - fun createChallengeTask(@Path("challengeId") challengeId: String, @Body task: Task): Flowable> + suspend fun createChallengeTask(@Path("challengeId") challengeId: String, @Body task: Task): HabitResponse @PUT("challenges/{challengeId}") - fun updateChallenge(@Path("challengeId") challengeId: String, @Body challenge: Challenge): Flowable> + suspend fun updateChallenge(@Path("challengeId") challengeId: String, @Body challenge: Challenge): HabitResponse @DELETE("challenges/{challengeId}") - fun deleteChallenge(@Path("challengeId") challengeId: String): Flowable> + suspend fun deleteChallenge(@Path("challengeId") challengeId: String): HabitResponse // DEBUG: These calls only work on a local development server @POST("debug/add-ten-gems") - fun debugAddTenGems(): Flowable> + suspend fun debugAddTenGems(): HabitResponse // Notifications @POST("notifications/{notificationId}/read") - fun readNotification(@Path("notificationId") notificationId: String): Flowable>> + suspend fun readNotification(@Path("notificationId") notificationId: String): HabitResponse> @POST("notifications/read") - fun readNotifications(@Body notificationIds: Map>): Flowable>> + suspend fun readNotifications(@Body notificationIds: Map>): HabitResponse> @POST("notifications/see") - fun seeNotifications(@Body notificationIds: Map>): Flowable>> + suspend fun seeNotifications(@Body notificationIds: Map>): HabitResponse> @POST("user/open-mystery-item") - fun openMysteryItem(): Flowable> + suspend fun openMysteryItem(): HabitResponse @POST("cron") - fun runCron(): Flowable> + suspend fun runCron(): HabitResponse @POST("user/reset") - fun resetAccount(): Flowable> + suspend fun resetAccount(): HabitResponse @HTTP(method = "DELETE", path = "user", hasBody = true) - fun deleteAccount(@Body body: Map): Flowable> + suspend fun deleteAccount(@Body body: Map): HabitResponse @GET("user/toggle-pinned-item/{pinType}/{path}") suspend fun togglePinnedItem(@Path("pinType") pinType: String, @Path("path") path: String): HabitResponse @POST("user/reset-password") - fun sendPasswordResetEmail(@Body data: Map): Flowable> + suspend fun sendPasswordResetEmail(@Body data: Map): HabitResponse @PUT("user/auth/update-username") suspend fun updateLoginName(@Body data: Map): HabitResponse @POST("user/auth/verify-username") - fun verifyUsername(@Body data: Map): Flowable> + suspend fun verifyUsername(@Body data: Map): HabitResponse @PUT("user/auth/update-email") - fun updateEmail(@Body data: Map): Flowable> + suspend fun updateEmail(@Body data: Map): HabitResponse @PUT("user/auth/update-password") - fun updatePassword(@Body data: Map): Flowable> + suspend fun updatePassword(@Body data: Map): HabitResponse @POST("user/allocate") - fun allocatePoint(@Query("stat") stat: String): Flowable> + suspend fun allocatePoint(@Query("stat") stat: String): HabitResponse @POST("user/allocate-bulk") - fun bulkAllocatePoints(@Body stats: Map>): Flowable> + suspend fun bulkAllocatePoints(@Body stats: Map>): HabitResponse @POST("members/transfer-gems") - fun transferGems(@Body data: Map): Flowable> + suspend fun transferGems(@Body data: Map): HabitResponse @POST("tasks/unlink-all/{challengeID}") - fun unlinkAllTasks(@Path("challengeID") challengeID: String?, @Query("keep") keepOption: String): Flowable> + suspend fun unlinkAllTasks(@Path("challengeID") challengeID: String?, @Query("keep") keepOption: String): HabitResponse @POST("user/block/{userID}") - fun blockMember(@Path("userID") userID: String): Flowable>> + suspend fun blockMember(@Path("userID") userID: String): HabitResponse> @POST("user/reroll") suspend fun reroll(): HabitResponse @@ -442,7 +441,7 @@ interface ApiService { // Team Plans @GET("group-plans") - fun getTeamPlans(): Flowable>> + suspend fun getTeamPlans(): HabitResponse> @GET("tasks/group/{groupID}") suspend fun getTeamPlanTasks(@Path("groupID") groupId: String): HabitResponse diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/api/MaintenanceApiService.java b/Habitica/src/main/java/com/habitrpg/android/habitica/api/MaintenanceApiService.java deleted file mode 100644 index d90a4cbfe..000000000 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/api/MaintenanceApiService.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.habitrpg.android.habitica.api; - -import com.habitrpg.shared.habitica.models.responses.MaintenanceResponse; - -import io.reactivex.rxjava3.core.Flowable; -import retrofit2.http.GET; - -public interface MaintenanceApiService { - - @GET("maintenance-android.json") - Flowable getMaintenanceStatus(); - - @GET("deprecation-android.json") - Flowable getDepricationStatus(); - -} diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/api/MaintenanceApiService.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/api/MaintenanceApiService.kt new file mode 100644 index 000000000..25e6e5208 --- /dev/null +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/api/MaintenanceApiService.kt @@ -0,0 +1,12 @@ +package com.habitrpg.android.habitica.api + +import com.habitrpg.shared.habitica.models.responses.MaintenanceResponse +import retrofit2.http.GET + +interface MaintenanceApiService { + @GET("maintenance-android.json") + suspend fun getMaintenanceStatus(): MaintenanceResponse? + + @GET("deprecation-android.json") + suspend fun getDepricationStatus(): MaintenanceResponse? +} \ No newline at end of file diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/components/UserComponent.java b/Habitica/src/main/java/com/habitrpg/android/habitica/components/UserComponent.java index 6cbab653f..9da7461ba 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/components/UserComponent.java +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/components/UserComponent.java @@ -40,7 +40,6 @@ import com.habitrpg.android.habitica.ui.activities.SkillTasksActivity; import com.habitrpg.android.habitica.ui.activities.TaskFormActivity; import com.habitrpg.android.habitica.ui.activities.TaskSummaryActivity; import com.habitrpg.android.habitica.ui.activities.TaskSummaryViewModel; -import com.habitrpg.android.habitica.ui.activities.VerifyUsernameActivity; import com.habitrpg.android.habitica.ui.adapter.social.challenges.ChallengeTasksRecyclerViewAdapter; import com.habitrpg.android.habitica.ui.adapter.tasks.DailiesRecyclerViewHolder; import com.habitrpg.android.habitica.ui.adapter.tasks.HabitsRecyclerViewAdapter; @@ -293,8 +292,6 @@ public interface UserComponent { void inject(ChallengeDetailFragment challengeDetailFragment); - void inject(VerifyUsernameActivity verifyUsernameActivity); - void inject(GroupViewModel viewModel); void inject(NotificationsViewModel viewModel); diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/ApiClient.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/ApiClient.kt index 907486b5e..bd41d9e15 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/ApiClient.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/ApiClient.kt @@ -27,7 +27,6 @@ import com.habitrpg.android.habitica.models.user.Items import com.habitrpg.android.habitica.models.user.Stats import com.habitrpg.android.habitica.models.user.User import com.habitrpg.common.habitica.api.HostConfig -import com.habitrpg.common.habitica.models.HabitResponse import com.habitrpg.common.habitica.models.PurchaseValidationRequest import com.habitrpg.common.habitica.models.PurchaseValidationResult import com.habitrpg.common.habitica.models.auth.UserAuthResponse @@ -36,8 +35,6 @@ import com.habitrpg.shared.habitica.models.responses.FeedResponse import com.habitrpg.shared.habitica.models.responses.Status import com.habitrpg.shared.habitica.models.responses.TaskDirectionData import com.habitrpg.shared.habitica.models.responses.VerifyUsernameResponse -import io.reactivex.rxjava3.core.Flowable -import io.reactivex.rxjava3.core.FlowableTransformer import retrofit2.HttpException interface ApiClient { @@ -52,19 +49,19 @@ interface ApiClient { /* challenges api */ - fun getUserChallenges(page: Int, memberOnly: Boolean): Flowable> + suspend fun getUserChallenges(page: Int, memberOnly: Boolean): List? suspend fun getWorldState(): WorldState? - fun setLanguageCode(languageCode: String) + var languageCode: String? suspend fun getContent(language: String? = null): ContentResult? suspend fun updateUser(updateDictionary: Map): User? - fun registrationLanguage(registrationLanguage: String): Flowable + suspend fun registrationLanguage(registrationLanguage: String): User? suspend fun retrieveInAppRewards(): List? - fun equipItem(type: String, itemKey: String): Flowable + suspend fun equipItem(type: String, itemKey: String): Items? suspend fun buyItem(itemKey: String, purchaseQuantity: Int): BuyResponse? @@ -76,51 +73,51 @@ interface ApiClient { suspend fun purchaseQuest(key: String): Void? suspend fun purchaseSpecialSpell(key: String): Void? - fun validateSubscription(request: PurchaseValidationRequest): Flowable - fun validateNoRenewSubscription(request: PurchaseValidationRequest): Flowable + suspend fun validateSubscription(request: PurchaseValidationRequest): Any? + suspend fun validateNoRenewSubscription(request: PurchaseValidationRequest): Any? suspend fun cancelSubscription(): Void? - fun sellItem(itemType: String, itemKey: String): Flowable + suspend fun sellItem(itemType: String, itemKey: String): User? - fun feedPet(petKey: String, foodKey: String): Flowable + suspend fun feedPet(petKey: String, foodKey: String): FeedResponse? - fun hatchPet(eggKey: String, hatchingPotionKey: String): Flowable - fun getTasks(type: String): Flowable - fun getTasks(type: String, dueDate: String): Flowable + suspend fun hatchPet(eggKey: String, hatchingPotionKey: String): Items? + suspend fun getTasks(type: String): TaskList? + suspend fun getTasks(type: String, dueDate: String): TaskList? suspend fun unlockPath(path: String): UnlockResponse? - fun getTask(id: String): Flowable + suspend fun getTask(id: String): Task? suspend fun postTaskDirection(id: String, direction: String): TaskDirectionData? - fun bulkScoreTasks(data: List>): Flowable + suspend fun bulkScoreTasks(data: List>): BulkTaskScoringData? - fun postTaskNewPosition(id: String, position: Int): Flowable> + suspend fun postTaskNewPosition(id: String, position: Int): List? suspend fun scoreChecklistItem(taskId: String, itemId: String): Task? - fun createTask(item: Task): Flowable + suspend fun createTask(item: Task): Task? - fun createTasks(tasks: List): Flowable> + suspend fun createTasks(tasks: List): List? - fun updateTask(id: String, item: Task): Flowable + suspend fun updateTask(id: String, item: Task): Task? - fun deleteTask(id: String): Flowable + suspend fun deleteTask(id: String): Void? - fun createTag(tag: Tag): Flowable + suspend fun createTag(tag: Tag): Tag? - fun updateTag(id: String, tag: Tag): Flowable + suspend fun updateTag(id: String, tag: Tag): Tag? - fun deleteTag(id: String): Flowable + suspend fun deleteTag(id: String): Void? - fun registerUser(username: String, email: String, password: String, confirmPassword: String): Flowable + suspend fun registerUser(username: String, email: String, password: String, confirmPassword: String): UserAuthResponse? - fun connectUser(username: String, password: String): Flowable + suspend fun connectUser(username: String, password: String): UserAuthResponse? - fun connectSocial(network: String, userId: String, accessToken: String): Flowable - fun disconnectSocial(network: String): Flowable + suspend fun connectSocial(network: String, userId: String, accessToken: String): UserAuthResponse? + suspend fun disconnectSocial(network: String): Void? - fun loginApple(authToken: String): Flowable + suspend fun loginApple(authToken: String): UserAuthResponse? suspend fun sleep(): Boolean? suspend fun revive(): User? @@ -133,11 +130,11 @@ interface ApiClient { suspend fun disableClasses(): User? - fun markPrivateMessagesRead(): Flowable + suspend fun markPrivateMessagesRead(): Void? /* Group API */ - fun listGroups(type: String): Flowable> + suspend fun listGroups(type: String): List? suspend fun getGroup(groupId: String): Group? @@ -151,83 +148,83 @@ interface ApiClient { suspend fun leaveGroup(groupId: String, keepChallenges: String): Void? - fun postGroupChat(groupId: String, message: Map): Flowable + suspend fun postGroupChat(groupId: String, message: Map): PostChatMessageResult? - fun deleteMessage(groupId: String, messageId: String): Flowable - fun deleteInboxMessage(id: String): Flowable + suspend fun deleteMessage(groupId: String, messageId: String): Void? + suspend fun deleteInboxMessage(id: String): Void? suspend fun getGroupMembers(groupId: String, includeAllPublicFields: Boolean?): List? suspend fun getGroupMembers(groupId: String, includeAllPublicFields: Boolean?, lastId: String): List? // Like returns the full chat list - fun likeMessage(groupId: String, mid: String): Flowable + suspend fun likeMessage(groupId: String, mid: String): ChatMessage? - fun flagMessage(groupId: String, mid: String, data: MutableMap): Flowable - fun flagInboxMessage(mid: String, data: MutableMap): Flowable + suspend fun flagMessage(groupId: String, mid: String, data: MutableMap): Void? + suspend fun flagInboxMessage(mid: String, data: MutableMap): Void? - fun seenMessages(groupId: String): Flowable + suspend fun seenMessages(groupId: String): Void? - fun inviteToGroup(groupId: String, inviteData: Map): Flowable> + suspend fun inviteToGroup(groupId: String, inviteData: Map): List? - fun rejectGroupInvite(groupId: String): Flowable + suspend fun rejectGroupInvite(groupId: String): Void? - fun acceptQuest(groupId: String): Flowable + suspend fun acceptQuest(groupId: String): Void? - fun rejectQuest(groupId: String): Flowable + suspend fun rejectQuest(groupId: String): Void? - fun cancelQuest(groupId: String): Flowable + suspend fun cancelQuest(groupId: String): Void? - fun forceStartQuest(groupId: String, group: Group): Flowable + suspend fun forceStartQuest(groupId: String, group: Group): Quest? - fun inviteToQuest(groupId: String, questKey: String): Flowable + suspend fun inviteToQuest(groupId: String, questKey: String): Quest? - fun abortQuest(groupId: String): Flowable + suspend fun abortQuest(groupId: String): Quest? - fun leaveQuest(groupId: String): Flowable + suspend fun leaveQuest(groupId: String): Void? - fun validatePurchase(request: PurchaseValidationRequest): Flowable + suspend fun validatePurchase(request: PurchaseValidationRequest): PurchaseValidationResult? - fun changeCustomDayStart(updateObject: Map): Flowable + suspend fun changeCustomDayStart(updateObject: Map): User? // Members URL suspend fun getMember(memberId: String): Member? suspend fun getMemberWithUsername(username: String): Member? - fun getMemberAchievements(memberId: String): Flowable> + suspend fun getMemberAchievements(memberId: String): List? suspend fun postPrivateMessage(messageDetails: Map): PostChatMessageResult? - fun retrieveShopIventory(identifier: String): Flowable + suspend fun retrieveShopIventory(identifier: String): Shop? // Push notifications - fun addPushDevice(pushDeviceData: Map): Flowable> + suspend fun addPushDevice(pushDeviceData: Map): List? - fun deletePushDevice(regId: String): Flowable> + suspend fun deletePushDevice(regId: String): List? - fun getChallengeTasks(challengeId: String): Flowable + suspend fun getChallengeTasks(challengeId: String): TaskList? - fun getChallenge(challengeId: String): Flowable + suspend fun getChallenge(challengeId: String): Challenge? - fun joinChallenge(challengeId: String): Flowable + suspend fun joinChallenge(challengeId: String): Challenge? - fun leaveChallenge(challengeId: String, body: LeaveChallengeBody): Flowable + suspend fun leaveChallenge(challengeId: String, body: LeaveChallengeBody): Void? - fun createChallenge(challenge: Challenge): Flowable + suspend fun createChallenge(challenge: Challenge): Challenge? - fun createChallengeTasks(challengeId: String, tasks: List): Flowable> - fun createChallengeTask(challengeId: String, task: Task): Flowable - fun updateChallenge(challenge: Challenge): Flowable - fun deleteChallenge(challengeId: String): Flowable + suspend fun createChallengeTasks(challengeId: String, tasks: List): List? + suspend fun createChallengeTask(challengeId: String, task: Task): Task? + suspend fun updateChallenge(challenge: Challenge): Challenge? + suspend fun deleteChallenge(challengeId: String): Void? // DEBUG: These calls only work on a local development server - fun debugAddTenGems(): Flowable + suspend fun debugAddTenGems(): Void? // Notifications - fun readNotification(notificationId: String): Flowable> - fun readNotifications(notificationIds: Map>): Flowable> - fun seeNotifications(notificationIds: Map>): Flowable> + suspend fun readNotification(notificationId: String): List? + suspend fun readNotifications(notificationIds: Map>): List? + suspend fun seeNotifications(notificationIds: Map>): List? fun getErrorResponse(throwable: HttpException): ErrorResponse @@ -237,42 +234,40 @@ interface ApiClient { suspend fun retrieveUser(withTasks: Boolean = false): User? suspend fun retrieveInboxMessages(uuid: String, page: Int): List? - fun retrieveInboxConversations(): Flowable> + suspend fun retrieveInboxConversations(): List? - fun configureApiCallObserver(): FlowableTransformer, T> + suspend fun openMysteryItem(): Equipment? - fun openMysteryItem(): Flowable - - fun runCron(): Flowable + suspend fun runCron(): Void? suspend fun reroll(): User? - fun resetAccount(): Flowable - fun deleteAccount(password: String): Flowable + suspend fun resetAccount(): Void? + suspend fun deleteAccount(password: String): Void? suspend fun togglePinnedItem(pinType: String, path: String): Void? - fun sendPasswordResetEmail(email: String): Flowable + suspend fun sendPasswordResetEmail(email: String): Void? suspend fun updateLoginName(newLoginName: String, password: String): Void? suspend fun updateUsername(newLoginName: String): Void? - fun updateEmail(newEmail: String, password: String): Flowable + suspend fun updateEmail(newEmail: String, password: String): Void? - fun updatePassword(oldPassword: String, newPassword: String, newPasswordConfirmation: String): Flowable + suspend fun updatePassword(oldPassword: String, newPassword: String, newPasswordConfirmation: String): Void? - fun allocatePoint(stat: String): Flowable + suspend fun allocatePoint(stat: String): Stats? - fun bulkAllocatePoints(strength: Int, intelligence: Int, constitution: Int, perception: Int): Flowable + suspend fun bulkAllocatePoints(strength: Int, intelligence: Int, constitution: Int, perception: Int): Stats? - fun retrieveMarketGear(): Flowable - fun verifyUsername(username: String): Flowable + suspend fun retrieveMarketGear(): Shop? + suspend fun verifyUsername(username: String): VerifyUsernameResponse? fun updateServerUrl(newAddress: String?) - fun findUsernames(username: String, context: String?, id: String?): Flowable> + suspend fun findUsernames(username: String, context: String?, id: String?): List? - fun transferGems(giftedID: String, amount: Int): Flowable - fun unlinkAllTasks(challengeID: String?, keepOption: String): Flowable - fun blockMember(userID: String): Flowable> - fun getTeamPlans(): Flowable> + suspend fun transferGems(giftedID: String, amount: Int): Void? + suspend fun unlinkAllTasks(challengeID: String?, keepOption: String): Void? + suspend fun blockMember(userID: String): List? + suspend fun getTeamPlans(): List? suspend fun getTeamPlanTasks(teamID: String): TaskList? } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/ChallengeRepository.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/ChallengeRepository.kt index 6f5758b2e..626216fb5 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/ChallengeRepository.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/ChallengeRepository.kt @@ -4,18 +4,18 @@ import com.habitrpg.android.habitica.models.social.Challenge import com.habitrpg.android.habitica.models.social.ChallengeMembership import com.habitrpg.android.habitica.models.tasks.Task import com.habitrpg.android.habitica.models.tasks.TaskList -import io.reactivex.rxjava3.core.Flowable +import kotlinx.coroutines.flow.Flow interface ChallengeRepository : BaseRepository { - fun retrieveChallenges(page: Int = 0, memberOnly: Boolean): Flowable> - fun getChallenges(): Flowable> - fun getChallenge(challengeId: String): Flowable - fun getChallengeTasks(challengeId: String): Flowable> + suspend fun retrieveChallenges(page: Int = 0, memberOnly: Boolean): List? + fun getChallenges(): Flow> + fun getChallenge(challengeId: String): Flow + fun getChallengeTasks(challengeId: String): Flow> - fun retrieveChallenge(challengeID: String): Flowable - fun retrieveChallengeTasks(challengeID: String): Flowable - fun createChallenge(challenge: Challenge, taskList: List): Flowable + suspend fun retrieveChallenge(challengeID: String): Challenge? + suspend fun retrieveChallengeTasks(challengeID: String): TaskList? + suspend fun createChallenge(challenge: Challenge, taskList: List): Challenge? /** * @@ -26,22 +26,22 @@ interface ChallengeRepository : BaseRepository { * @param removedTaskList tasks that has be to be removed * @return Observable with the updated challenge */ - fun updateChallenge( + suspend fun updateChallenge( challenge: Challenge, fullTaskList: List, addedTaskList: List, updatedTaskList: List, removedTaskList: List - ): Flowable + ): Challenge? - fun deleteChallenge(challengeId: String): Flowable - fun getUserChallenges(userId: String? = null): Flowable> + suspend fun deleteChallenge(challengeId: String): Void? + fun getUserChallenges(userId: String? = null): Flow> - fun leaveChallenge(challenge: Challenge, keepTasks: String): Flowable + suspend fun leaveChallenge(challenge: Challenge, keepTasks: String): Void? - fun joinChallenge(challenge: Challenge): Flowable + suspend fun joinChallenge(challenge: Challenge): Challenge? - fun getChallengepMembership(id: String): Flowable - fun getChallengeMemberships(): Flowable> - fun isChallengeMember(challengeID: String): Flowable + fun getChallengepMembership(id: String): Flow + fun getChallengeMemberships(): Flow> + fun isChallengeMember(challengeID: String): Flow } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/ContentRepository.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/ContentRepository.kt index 1a71ef514..141c5cacc 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/ContentRepository.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/ContentRepository.kt @@ -2,11 +2,11 @@ package com.habitrpg.android.habitica.data import com.habitrpg.android.habitica.models.ContentResult import com.habitrpg.android.habitica.models.WorldState -import io.reactivex.rxjava3.core.Flowable +import kotlinx.coroutines.flow.Flow interface ContentRepository: BaseRepository { suspend fun retrieveContent(forced: Boolean = false): ContentResult? suspend fun retrieveWorldState(): WorldState? - fun getWorldState(): Flowable + fun getWorldState(): Flow } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/CustomizationRepository.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/CustomizationRepository.kt index f65af96df..8c657e98a 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/CustomizationRepository.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/CustomizationRepository.kt @@ -1,8 +1,8 @@ package com.habitrpg.android.habitica.data import com.habitrpg.android.habitica.models.inventory.Customization -import io.reactivex.rxjava3.core.Flowable +import kotlinx.coroutines.flow.Flow interface CustomizationRepository : BaseRepository { - fun getCustomizations(type: String, category: String?, onlyAvailable: Boolean): Flowable> + fun getCustomizations(type: String, category: String?, onlyAvailable: Boolean): Flow> } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/FAQRepository.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/FAQRepository.kt index 9ead63c61..d4b61d443 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/FAQRepository.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/FAQRepository.kt @@ -1,9 +1,9 @@ package com.habitrpg.android.habitica.data import com.habitrpg.android.habitica.models.FAQArticle -import io.reactivex.rxjava3.core.Flowable +import kotlinx.coroutines.flow.Flow interface FAQRepository : BaseRepository { - fun getArticles(): Flowable> - fun getArticle(position: Int): Flowable + fun getArticles(): Flow> + fun getArticle(position: Int): Flow } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/InventoryRepository.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/InventoryRepository.kt index 7cacbe430..16263bfb4 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/InventoryRepository.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/InventoryRepository.kt @@ -18,15 +18,14 @@ import com.habitrpg.android.habitica.models.user.OwnedMount import com.habitrpg.android.habitica.models.user.OwnedPet import com.habitrpg.android.habitica.models.user.User import com.habitrpg.shared.habitica.models.responses.FeedResponse -import io.reactivex.rxjava3.core.Flowable import kotlinx.coroutines.flow.Flow interface InventoryRepository : BaseRepository { fun getArmoireRemainingCount(): Long - fun getInAppRewards(): Flowable> - fun getOwnedEquipment(): Flowable> + fun getInAppRewards(): Flow> + fun getOwnedEquipment(): Flow> fun getMounts(): Flow> @@ -38,18 +37,18 @@ interface InventoryRepository : BaseRepository { fun getQuestContent(key: String): Flow fun getQuestContent(keys: List): Flow> - fun getEquipment(searchedKeys: List): Flowable> + fun getEquipment(searchedKeys: List): Flow> suspend fun retrieveInAppRewards(): List? - fun getOwnedEquipment(type: String): Flowable> - fun getEquipmentType(type: String, set: String): Flowable> + fun getOwnedEquipment(type: String): Flow> + fun getEquipmentType(type: String, set: String): Flow> fun getOwnedItems(itemType: String, includeZero: Boolean = false): Flow> - fun getOwnedItems(includeZero: Boolean = false): Flowable> + fun getOwnedItems(includeZero: Boolean = false): Flow> - fun getEquipment(key: String): Flowable + fun getEquipment(key: String): Flow - fun openMysteryItem(user: User?): Flowable + suspend fun openMysteryItem(user: User?): Equipment? fun saveEquipment(equipment: Equipment) fun getMounts(type: String?, group: String?, color: String?): Flow> @@ -57,24 +56,24 @@ interface InventoryRepository : BaseRepository { fun updateOwnedEquipment(user: User) - fun changeOwnedCount(type: String, key: String, amountToAdd: Int) + suspend fun changeOwnedCount(type: String, key: String, amountToAdd: Int) - fun sellItem(type: String, key: String): Flowable - fun sellItem(item: OwnedItem): Flowable + suspend fun sellItem(type: String, key: String): User? + suspend fun sellItem(item: OwnedItem): User? - fun equipGear(equipment: String, asCostume: Boolean): Flowable - fun equip(type: String, key: String): Flowable + suspend fun equipGear(equipment: String, asCostume: Boolean): Items? + suspend fun equip(type: String, key: String): Items? - fun feedPet(pet: Pet, food: Food): Flowable + suspend fun feedPet(pet: Pet, food: Food): FeedResponse? - fun hatchPet(egg: Egg, hatchingPotion: HatchingPotion, successFunction: () -> Unit): Flowable + suspend fun hatchPet(egg: Egg, hatchingPotion: HatchingPotion, successFunction: () -> Unit): Items? - fun inviteToQuest(quest: QuestContent): Flowable + suspend fun inviteToQuest(quest: QuestContent): Quest? suspend fun buyItem(user: User?, id: String, value: Double, purchaseQuantity: Int): BuyResponse? - fun retrieveShopInventory(identifier: String): Flowable - fun retrieveMarketGear(): Flowable + suspend fun retrieveShopInventory(identifier: String): Shop? + suspend fun retrieveMarketGear(): Shop? suspend fun purchaseMysterySet(categoryIdentifier: String): Void? @@ -87,9 +86,8 @@ interface InventoryRepository : BaseRepository { suspend fun togglePinnedItem(item: ShopItem): List? fun getItems(itemClass: Class, keys: Array): Flow> - fun getItemsFlowable(itemClass: Class): Flowable> fun getItems(itemClass: Class): Flow> - fun getLatestMysteryItem(): Flowable - fun getItem(type: String, key: String): Flowable - fun getAvailableLimitedItems(): Flowable> + fun getLatestMysteryItem(): Flow + fun getItem(type: String, key: String): Flow + fun getAvailableLimitedItems(): Flow> } 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 dfb6713f7..5ccb1674d 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 @@ -10,35 +10,34 @@ import com.habitrpg.android.habitica.models.social.Group import com.habitrpg.android.habitica.models.social.GroupMembership import com.habitrpg.android.habitica.models.social.InboxConversation import com.habitrpg.android.habitica.models.user.User -import io.reactivex.rxjava3.core.Flowable import io.realm.RealmResults import kotlinx.coroutines.flow.Flow interface SocialRepository : BaseRepository { - fun getPublicGuilds(): Flowable> + fun getPublicGuilds(): Flow> fun getUserGroups(type: String?): Flow> suspend fun retrieveGroupChat(groupId: String): List? - fun getGroupChat(groupId: String): Flowable> + fun getGroupChat(groupId: String): Flow> - fun markMessagesSeen(seenGroupId: String) + suspend fun markMessagesSeen(seenGroupId: String) - fun flagMessage( + suspend fun flagMessage( chatMessageID: String, additionalInfo: String, groupID: String? = null - ): Flowable + ): Void? - fun likeMessage(chatMessage: ChatMessage): Flowable + suspend fun likeMessage(chatMessage: ChatMessage): ChatMessage? - fun deleteMessage(chatMessage: ChatMessage): Flowable + suspend fun deleteMessage(chatMessage: ChatMessage): Void? - fun postGroupChat( + suspend fun postGroupChat( groupId: String, messageObject: HashMap - ): Flowable + ): PostChatMessageResult? - fun postGroupChat(groupId: String, message: String): Flowable + suspend fun postGroupChat(groupId: String, message: String): PostChatMessageResult? suspend fun retrieveGroup(id: String): Group? fun getGroup(id: String?): Flow @@ -64,12 +63,12 @@ interface SocialRepository : BaseRepository { leaderCreateChallenge: Boolean? ): Group? - fun retrieveGroups(type: String): Flowable> - fun getGroups(type: String): Flowable> + suspend fun retrieveGroups(type: String): List? + fun getGroups(type: String): Flow> fun getInboxMessages(replyToUserID: String?): Flow> suspend fun retrieveInboxMessages(uuid: String, page: Int): List? - fun retrieveInboxConversations(): Flowable> + suspend fun retrieveInboxConversations(): List? fun getInboxConversations(): Flow> suspend fun postPrivateMessage( recipientId: String, @@ -82,42 +81,42 @@ interface SocialRepository : BaseRepository { suspend fun getGroupMembers(id: String): Flow> suspend fun retrievePartyMembers(id: String, includeAllPublicFields: Boolean): List? - fun inviteToGroup(id: String, inviteData: Map): Flowable> + suspend fun inviteToGroup(id: String, inviteData: Map): List? suspend fun retrieveMember(userId: String?): Member? suspend fun retrieveMemberWithUsername(username: String?): Member? - fun findUsernames( + suspend fun findUsernames( username: String, context: String? = null, id: String? = null - ): Flowable> + ): List? - fun markPrivateMessagesRead(user: User?): Flowable + suspend fun markPrivateMessagesRead(user: User?): Void? fun markSomePrivateMessagesAsRead(user: User?, messages: List) suspend fun transferGroupOwnership(groupID: String, userID: String): Group? suspend fun removeMemberFromGroup(groupID: String, userID: String): List? - fun acceptQuest(user: User?, partyId: String = "party"): Flowable - fun rejectQuest(user: User?, partyId: String = "party"): Flowable + suspend fun acceptQuest(user: User?, partyId: String = "party"): Void? + suspend fun rejectQuest(user: User?, partyId: String = "party"): Void? - fun leaveQuest(partyId: String): Flowable + suspend fun leaveQuest(partyId: String): Void? - fun cancelQuest(partyId: String): Flowable + suspend fun cancelQuest(partyId: String): Void? - fun abortQuest(partyId: String): Flowable + suspend fun abortQuest(partyId: String): Quest? - fun rejectGroupInvite(groupId: String): Flowable + suspend fun rejectGroupInvite(groupId: String): Void? - fun forceStartQuest(party: Group): Flowable + suspend fun forceStartQuest(party: Group): Quest? - fun getMemberAchievements(userId: String?): Flowable> + suspend fun getMemberAchievements(userId: String?): List? - fun transferGems(giftedID: String, amount: Int): Flowable + suspend fun transferGems(giftedID: String, amount: Int): Void? fun getGroupMembership(id: String): Flow - fun getGroupMemberships(): Flowable> - fun blockMember(userID: String): Flowable> + fun getGroupMemberships(): Flow> + suspend fun blockMember(userID: String): List? } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/TagRepository.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/TagRepository.kt index 8369c33cd..b6a04dcc2 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/TagRepository.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/TagRepository.kt @@ -1,19 +1,18 @@ package com.habitrpg.android.habitica.data import com.habitrpg.android.habitica.models.Tag -import io.reactivex.rxjava3.core.Flowable -import io.reactivex.rxjava3.core.Single +import kotlinx.coroutines.flow.Flow interface TagRepository : BaseRepository { - fun getTags(): Flowable> - fun getTags(userId: String): Flowable> + fun getTags(): Flow> + fun getTags(userId: String): Flow> - fun createTag(tag: Tag): Flowable - fun updateTag(tag: Tag): Flowable - fun deleteTag(id: String): Flowable + suspend fun createTag(tag: Tag): Tag? + suspend fun updateTag(tag: Tag): Tag? + suspend fun deleteTag(id: String): Void? - fun createTags(tags: Collection): Single> - fun updateTags(tags: Collection): Single> - fun deleteTags(tagIds: Collection): Single> + suspend fun createTags(tags: Collection): List + suspend fun updateTags(tags: Collection): List + suspend fun deleteTags(tagIds: Collection): List } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/TaskRepository.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/TaskRepository.kt index 9548b54b3..efa4f3507 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/TaskRepository.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/TaskRepository.kt @@ -8,19 +8,15 @@ import com.habitrpg.android.habitica.models.user.User import com.habitrpg.shared.habitica.models.responses.TaskScoringResult import com.habitrpg.shared.habitica.models.tasks.TaskType import com.habitrpg.shared.habitica.models.tasks.TasksOrder -import io.reactivex.rxjava3.core.Flowable -import io.reactivex.rxjava3.core.Maybe -import io.reactivex.rxjava3.core.Single import kotlinx.coroutines.flow.Flow import java.util.Date interface TaskRepository : BaseRepository { fun getTasks(taskType: TaskType, userID: String? = null, includedGroupIDs: Array): Flow> - fun getTasksFlowable(taskType: TaskType, userID: String? = null, includedGroupIDs: Array): Flowable> fun saveTasks(userId: String, order: TasksOrder, tasks: TaskList) suspend fun retrieveTasks(userId: String, tasksOrder: TasksOrder): TaskList? - fun retrieveTasks(userId: String, tasksOrder: TasksOrder, dueDate: Date): Flowable + suspend fun retrieveTasks(userId: String, tasksOrder: TasksOrder, dueDate: Date): TaskList? suspend fun taskChecked( user: User?, @@ -40,12 +36,12 @@ interface TaskRepository : BaseRepository { fun getTask(taskId: String): Flow fun getTaskCopy(taskId: String): Flow - fun createTask(task: Task, force: Boolean = false): Flowable - fun updateTask(task: Task, force: Boolean = false): Maybe? - fun deleteTask(taskId: String): Flowable + suspend fun createTask(task: Task, force: Boolean = false): Task? + suspend fun updateTask(task: Task, force: Boolean = false): Task? + suspend fun deleteTask(taskId: String): Void? fun saveTask(task: Task) - fun createTasks(newTasks: List): Flowable> + suspend fun createTasks(newTasks: List): List? fun markTaskCompleted(taskId: String, isCompleted: Boolean) @@ -53,7 +49,7 @@ interface TaskRepository : BaseRepository { fun swapTaskPosition(firstPosition: Int, secondPosition: Int) - fun updateTaskPosition(taskType: TaskType, taskID: String, newPosition: Int): Maybe> + suspend fun updateTaskPosition(taskType: TaskType, taskID: String, newPosition: Int): List? fun getUnmanagedTask(taskid: String): Flow @@ -65,10 +61,10 @@ interface TaskRepository : BaseRepository { fun getTaskCopies(tasks: List): List - fun retrieveDailiesFromDate(date: Date): Flowable - fun retrieveCompletedTodos(userId: String? = null): Flowable - fun syncErroredTasks(): Single> - fun unlinkAllTasks(challengeID: String?, keepOption: String): Flowable - fun getTasksForChallenge(challengeID: String?): Flowable> - fun bulkScoreTasks(data: List>): Flowable + suspend fun retrieveDailiesFromDate(date: Date): TaskList? + suspend fun retrieveCompletedTodos(userId: String? = null): TaskList? + suspend fun syncErroredTasks(): List? + suspend fun unlinkAllTasks(challengeID: String?, keepOption: String): Void? + fun getTasksForChallenge(challengeID: String?): Flow> + suspend fun bulkScoreTasks(data: List>): BulkTaskScoringData? } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/TutorialRepository.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/TutorialRepository.kt index f982b7f3e..5940aa690 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/TutorialRepository.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/TutorialRepository.kt @@ -1,10 +1,10 @@ package com.habitrpg.android.habitica.data import com.habitrpg.android.habitica.models.TutorialStep -import io.reactivex.rxjava3.core.Flowable +import kotlinx.coroutines.flow.Flow interface TutorialRepository : BaseRepository { - fun getTutorialStep(key: String): Flowable - fun getTutorialSteps(keys: List): Flowable> + fun getTutorialStep(key: String): Flow + fun getTutorialSteps(keys: List): Flow> } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/UserRepository.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/UserRepository.kt index 220c675a0..5390821a9 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/UserRepository.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/UserRepository.kt @@ -14,12 +14,10 @@ import com.habitrpg.android.habitica.models.user.User import com.habitrpg.android.habitica.models.user.UserQuestStatus import com.habitrpg.shared.habitica.models.responses.VerifyUsernameResponse import com.habitrpg.shared.habitica.models.tasks.Attribute -import io.reactivex.rxjava3.core.Flowable import kotlinx.coroutines.flow.Flow interface UserRepository : BaseRepository { fun getUser(): Flow - fun getUserFlowable(): Flowable fun getUser(userID: String): Flow suspend fun updateUser(updateData: Map): User? @@ -33,9 +31,9 @@ interface UserRepository : BaseRepository { suspend fun sleep(user: User): User? - fun getSkills(user: User): Flowable> + fun getSkills(user: User): Flow> - fun getSpecialItems(user: User): Flowable> + fun getSpecialItems(user: User): Flow> suspend fun useSkill(key: String, target: String?, taskId: String): SkillResponse? suspend fun useSkill(key: String, target: String?): SkillResponse? @@ -49,37 +47,37 @@ interface UserRepository : BaseRepository { suspend fun runCron(tasks: MutableList) suspend fun runCron() - fun readNotification(id: String): Flowable> - fun readNotifications(notificationIds: Map>): Flowable> - fun seeNotifications(notificationIds: Map>): Flowable> + suspend fun readNotification(id: String): List? + suspend fun readNotifications(notificationIds: Map>): List? + suspend fun seeNotifications(notificationIds: Map>): List? - fun changeCustomDayStart(dayStartTime: Int): Flowable + suspend fun changeCustomDayStart(dayStartTime: Int): User? suspend fun updateLanguage(languageCode: String): User? suspend fun resetAccount(): User? - fun deleteAccount(password: String): Flowable + suspend fun deleteAccount(password: String): Void? - fun sendPasswordResetEmail(email: String): Flowable + suspend fun sendPasswordResetEmail(email: String): Void? suspend fun updateLoginName(newLoginName: String, password: String? = null): User? - fun updateEmail(newEmail: String, password: String): Flowable - fun updatePassword(oldPassword: String, newPassword: String, newPasswordConfirmation: String): Flowable - fun verifyUsername(username: String): Flowable + suspend fun updateEmail(newEmail: String, password: String): Void? + suspend fun updatePassword(oldPassword: String, newPassword: String, newPasswordConfirmation: String): Void? + suspend fun verifyUsername(username: String): VerifyUsernameResponse? - fun allocatePoint(stat: Attribute): Flowable - fun bulkAllocatePoints(strength: Int, intelligence: Int, constitution: Int, perception: Int): Flowable + suspend fun allocatePoint(stat: Attribute): Stats? + suspend fun bulkAllocatePoints(strength: Int, intelligence: Int, constitution: Int, perception: Int): Stats? suspend fun useCustomization(type: String, category: String?, identifier: String): User? - fun retrieveAchievements(): Flowable> + suspend fun retrieveAchievements(): List? fun getAchievements(): Flow> fun getQuestAchievements(): Flow> - fun getUserQuestStatus(): Flowable + fun getUserQuestStatus(): Flow suspend fun reroll(): User? - fun retrieveTeamPlans(): Flowable> + suspend fun retrieveTeamPlans(): List? fun getTeamPlans(): Flow> suspend fun retrieveTeamPlan(teamID: String): Group? - fun getTeamPlan(teamID: String): Flowable + fun getTeamPlan(teamID: String): Flow } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/ApiClientImpl.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/ApiClientImpl.kt index c42556b06..3b56c2116 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/ApiClientImpl.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/ApiClientImpl.kt @@ -51,11 +51,7 @@ import com.habitrpg.shared.habitica.models.responses.FeedResponse import com.habitrpg.shared.habitica.models.responses.Status import com.habitrpg.shared.habitica.models.responses.TaskDirectionData import com.habitrpg.shared.habitica.models.responses.VerifyUsernameResponse -import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers -import io.reactivex.rxjava3.core.Flowable -import io.reactivex.rxjava3.core.FlowableTransformer import io.reactivex.rxjava3.functions.Consumer -import io.reactivex.rxjava3.schedulers.Schedulers import okhttp3.Cache import okhttp3.OkHttpClient import okhttp3.Request @@ -87,17 +83,6 @@ class ApiClientImpl( // I think we don't need the ApiClientImpl anymore we could just use ApiService private lateinit var apiService: ApiService - private val apiCallTransformer = FlowableTransformer, Any> { observable -> - observable - .filter { it.data != null } - .map { habitResponse -> - processResponse(habitResponse) - } - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .doOnError(this) - } - private fun processResponse(habitResponse: HabitResponse): T? { habitResponse.notifications?.let { notificationsManager.setNotifications(it) @@ -114,7 +99,7 @@ class ApiClientImpl( return null } - private var languageCode: String? = null + override var languageCode: String? = null private var lastAPICallURL: String? = null private var hadError = false @@ -186,28 +171,28 @@ class ApiClientImpl( } } - override fun registerUser( + override suspend fun registerUser( username: String, email: String, password: String, confirmPassword: String - ): Flowable { + ): UserAuthResponse? { val auth = UserAuth() auth.username = username auth.password = password auth.confirmPassword = confirmPassword auth.email = email - return this.apiService.registerUser(auth).compose(configureApiCallObserver()) + return process { this.apiService.registerUser(auth) } } - override fun connectUser(username: String, password: String): Flowable { + override suspend fun connectUser(username: String, password: String): UserAuthResponse? { val auth = UserAuth() auth.username = username auth.password = password - return this.apiService.connectLocal(auth).compose(configureApiCallObserver()) + return process { this.apiService.connectLocal(auth) } } - override fun connectSocial(network: String, userId: String, accessToken: String): Flowable { + override suspend fun connectSocial(network: String, userId: String, accessToken: String): UserAuthResponse? { val auth = UserAuthSocial() auth.network = network val authResponse = UserAuthSocialTokens() @@ -215,15 +200,15 @@ class ApiClientImpl( authResponse.access_token = accessToken auth.authResponse = authResponse - return this.apiService.connectSocial(auth).compose(configureApiCallObserver()) + return process { this.apiService.connectSocial(auth) } } - override fun disconnectSocial(network: String): Flowable { - return this.apiService.disconnectSocial(network).compose(configureApiCallObserver()) + override suspend fun disconnectSocial(network: String): Void? { + return process { this.apiService.disconnectSocial(network) } } - override fun loginApple(authToken: String): Flowable { - return apiService.loginApple(mapOf(Pair("code", authToken))).compose(configureApiCallObserver()) + override suspend fun loginApple(authToken: String): UserAuthResponse? { + return process { apiService.loginApple(mapOf(Pair("code", authToken))) } } override fun accept(throwable: Throwable) { @@ -290,8 +275,8 @@ class ApiClientImpl( return process { apiService.getInboxMessages(uuid, page) } } - override fun retrieveInboxConversations(): Flowable> { - return apiService.getInboxConversations().compose(configureApiCallObserver()) + override suspend fun retrieveInboxConversations(): List? { + return process { apiService.getInboxConversations() } } override fun hasAuthenticationKeys(): Boolean { @@ -330,11 +315,6 @@ class ApiClientImpl( See here for more info: http://blog.danlew.net/2015/03/02/dont-break-the-chain/ */ - override fun configureApiCallObserver(): FlowableTransformer, T> { - @Suppress("UNCHECKED_CAST") - return apiCallTransformer as FlowableTransformer, T> - } - override fun updateAuthenticationCredentials(userID: String?, apiToken: String?) { this.hostConfig.userID = userID ?: "" this.hostConfig.apiKey = apiToken ?: "" @@ -342,10 +322,6 @@ class ApiClientImpl( Amplitude.getInstance().userId = this.hostConfig.userID } - override fun setLanguageCode(languageCode: String) { - this.languageCode = languageCode - } - override suspend fun getStatus(): Status? = process { apiService.getStatus() } override suspend fun getContent(language: String?): ContentResult? { @@ -356,40 +332,40 @@ class ApiClientImpl( return process { apiService.updateUser(updateDictionary) } } - override fun registrationLanguage(registrationLanguage: String): Flowable { - return apiService.registrationLanguage(registrationLanguage).compose(configureApiCallObserver()) + override suspend fun registrationLanguage(registrationLanguage: String): User? { + return process { apiService.registrationLanguage(registrationLanguage) } } override suspend fun retrieveInAppRewards(): List? { return process { apiService.retrieveInAppRewards() } } - override fun equipItem(type: String, itemKey: String): Flowable { - return apiService.equipItem(type, itemKey).compose(configureApiCallObserver()) + override suspend fun equipItem(type: String, itemKey: String): Items? { + return process { apiService.equipItem(type, itemKey) } } override suspend fun buyItem(itemKey: String, purchaseQuantity: Int): BuyResponse? { return process { apiService.buyItem(itemKey, mapOf(Pair("quantity", purchaseQuantity))) } } - override fun unlinkAllTasks(challengeID: String?, keepOption: String): Flowable { - return apiService.unlinkAllTasks(challengeID, keepOption).compose(configureApiCallObserver()) + override suspend fun unlinkAllTasks(challengeID: String?, keepOption: String): Void? { + return process { apiService.unlinkAllTasks(challengeID, keepOption) } } - override fun blockMember(userID: String): Flowable> { - return apiService.blockMember(userID).compose(configureApiCallObserver()) + override suspend fun blockMember(userID: String): List? { + return process { apiService.blockMember(userID) } } override suspend fun purchaseItem(type: String, itemKey: String, purchaseQuantity: Int): Void? { return process { apiService.purchaseItem(type, itemKey, mapOf(Pair("quantity", purchaseQuantity))) } } - override fun validateSubscription(request: PurchaseValidationRequest): Flowable { - return apiService.validateSubscription(request).compose(configureApiCallObserver()) + override suspend fun validateSubscription(request: PurchaseValidationRequest): Any? { + return process { apiService.validateSubscription(request) } } - override fun validateNoRenewSubscription(request: PurchaseValidationRequest): Flowable { - return apiService.validateNoRenewSubscription(request).compose(configureApiCallObserver()) + override suspend fun validateNoRenewSubscription(request: PurchaseValidationRequest): Any? { + return process { apiService.validateNoRenewSubscription(request) } } override suspend fun cancelSubscription(): Void? { @@ -412,83 +388,80 @@ class ApiClientImpl( return process { apiService.purchaseSpecialSpell(key) } } - override fun sellItem(itemType: String, itemKey: String): Flowable { - return apiService.sellItem(itemType, itemKey).compose(configureApiCallObserver()) + override suspend fun sellItem(itemType: String, itemKey: String): User? { + return process { apiService.sellItem(itemType, itemKey) } } - override fun feedPet(petKey: String, foodKey: String): Flowable { - return apiService.feedPet(petKey, foodKey) - .map { - it.data?.message = it.message - it - } - .compose(configureApiCallObserver()) + override suspend fun feedPet(petKey: String, foodKey: String): FeedResponse? { + val response = apiService.feedPet(petKey, foodKey) + response.data?.message = response.message + return process { response } } - override fun hatchPet(eggKey: String, hatchingPotionKey: String): Flowable { - return apiService.hatchPet(eggKey, hatchingPotionKey).compose(configureApiCallObserver()) + override suspend fun hatchPet(eggKey: String, hatchingPotionKey: String): Items? { + return process { apiService.hatchPet(eggKey, hatchingPotionKey) } } override suspend fun getTasks(): TaskList? = process { apiService.getTasks() } - override fun getTasks(type: String): Flowable { - return apiService.getTasks(type).compose(configureApiCallObserver()) + override suspend fun getTasks(type: String): TaskList? { + return process { apiService.getTasks(type) } } - override fun getTasks(type: String, dueDate: String): Flowable { - return apiService.getTasks(type, dueDate).compose(configureApiCallObserver()) + override suspend fun getTasks(type: String, dueDate: String): TaskList? { + return process { apiService.getTasks(type, dueDate) } } override suspend fun unlockPath(path: String): UnlockResponse? { return process { apiService.unlockPath(path) } } - override fun getTask(id: String): Flowable { - return apiService.getTask(id).compose(configureApiCallObserver()) + override suspend fun getTask(id: String): Task? { + return process { apiService.getTask(id) } } override suspend fun postTaskDirection(id: String, direction: String): TaskDirectionData? { return process { apiService.postTaskDirection(id, direction) } } - override fun bulkScoreTasks(data: List>): Flowable { - return apiService.bulkScoreTasks(data).compose(configureApiCallObserver()) + override suspend fun bulkScoreTasks(data: List>): BulkTaskScoringData? { + return process { apiService.bulkScoreTasks(data) } } - override fun postTaskNewPosition(id: String, position: Int): Flowable> { - return apiService.postTaskNewPosition(id, position).compose(configureApiCallObserver()) + override suspend fun postTaskNewPosition(id: String, position: Int): List? { + return process { apiService.postTaskNewPosition(id, position) } } override suspend fun scoreChecklistItem(taskId: String, itemId: String): Task? { return process { apiService.scoreChecklistItem(taskId, itemId) } } - override fun createTask(item: Task): Flowable { - return apiService.createTask(item).compose(configureApiCallObserver()) + override suspend fun createTask(item: Task): Task? { + return process { apiService.createTask(item) } } - override fun createTasks(tasks: List): Flowable> { - return apiService.createTasks(tasks).compose(configureApiCallObserver()) + override suspend fun createTasks(tasks: List): List? { + return process { apiService.createTasks(tasks) } } - override fun updateTask(id: String, item: Task): Flowable { - return apiService.updateTask(id, item).compose(configureApiCallObserver()) + override suspend fun updateTask(id: String, item: Task): Task? { + return process { apiService.updateTask(id, item) } } - override fun deleteTask(id: String): Flowable { - return apiService.deleteTask(id).compose(configureApiCallObserver()) + override suspend fun deleteTask(id: String): Void? { + return process { apiService.deleteTask(id) } } - override fun createTag(tag: Tag): Flowable { - return apiService.createTag(tag).compose(configureApiCallObserver()) + override suspend fun createTag(tag: Tag): Tag? { + return process { apiService.createTag(tag) } } - override fun updateTag(id: String, tag: Tag): Flowable { - return apiService.updateTag(id, tag).compose(configureApiCallObserver()) + override suspend fun updateTag(id: String, tag: Tag): Tag? { + return process { apiService.updateTag(id, tag) } } - override fun deleteTag(id: String): Flowable { - return apiService.deleteTag(id).compose(configureApiCallObserver()) + override suspend fun deleteTag(id: String): Void? { + return process { apiService.deleteTag(id) } } override suspend fun sleep(): Boolean? = process { apiService.sleep() } @@ -515,16 +488,12 @@ class ApiClientImpl( override suspend fun disableClasses(): User? = process { apiService.disableClasses() } - override fun markPrivateMessagesRead(): Flowable { - // This is necessary, because the API call returns weird data. + override suspend fun markPrivateMessagesRead(): Void { return apiService.markPrivateMessagesRead() - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .doOnError(this) } - override fun listGroups(type: String): Flowable> { - return apiService.listGroups(type).compose(configureApiCallObserver()) + override suspend fun listGroups(type: String): List? { + return process { apiService.listGroups(type) } } override suspend fun getGroup(groupId: String): Group? { @@ -555,15 +524,15 @@ class ApiClientImpl( return processResponse(apiService.leaveGroup(groupId, keepChallenges)) } - override fun postGroupChat(groupId: String, message: Map): Flowable { - return apiService.postGroupChat(groupId, message).compose(configureApiCallObserver()) + override suspend fun postGroupChat(groupId: String, message: Map): PostChatMessageResult? { + return process { apiService.postGroupChat(groupId, message) } } - override fun deleteMessage(groupId: String, messageId: String): Flowable { - return apiService.deleteMessage(groupId, messageId).compose(configureApiCallObserver()) + override suspend fun deleteMessage(groupId: String, messageId: String): Void? { + return process { apiService.deleteMessage(groupId, messageId) } } - override fun deleteInboxMessage(id: String): Flowable { - return apiService.deleteInboxMessage(id).compose(configureApiCallObserver()) + override suspend fun deleteInboxMessage(id: String): Void? { + return process { apiService.deleteInboxMessage(id) } } override suspend fun getGroupMembers(groupId: String, includeAllPublicFields: Boolean?): List? { @@ -574,181 +543,181 @@ class ApiClientImpl( return processResponse(apiService.getGroupMembers(groupId, includeAllPublicFields, lastId)) } - override fun likeMessage(groupId: String, mid: String): Flowable { - return apiService.likeMessage(groupId, mid).compose(configureApiCallObserver()) + override suspend fun likeMessage(groupId: String, mid: String): ChatMessage? { + return process { apiService.likeMessage(groupId, mid) } } - override fun flagMessage(groupId: String, mid: String, data: MutableMap): Flowable { - return apiService.flagMessage(groupId, mid, data).compose(configureApiCallObserver()) + override suspend fun flagMessage(groupId: String, mid: String, data: MutableMap): Void? { + return process { apiService.flagMessage(groupId, mid, data) } } - override fun flagInboxMessage(mid: String, data: MutableMap): Flowable { - return apiService.flagInboxMessage(mid, data).compose(configureApiCallObserver()) + override suspend fun flagInboxMessage(mid: String, data: MutableMap): Void? { + return process { apiService.flagInboxMessage(mid, data) } } - override fun seenMessages(groupId: String): Flowable { - return apiService.seenMessages(groupId).compose(configureApiCallObserver()) + override suspend fun seenMessages(groupId: String): Void? { + return process { apiService.seenMessages(groupId) } } - override fun inviteToGroup(groupId: String, inviteData: Map): Flowable> { - return apiService.inviteToGroup(groupId, inviteData).compose(configureApiCallObserver()) + override suspend fun inviteToGroup(groupId: String, inviteData: Map): List? { + return process { apiService.inviteToGroup(groupId, inviteData) } } - override fun rejectGroupInvite(groupId: String): Flowable { - return apiService.rejectGroupInvite(groupId).compose(configureApiCallObserver()) + override suspend fun rejectGroupInvite(groupId: String): Void? { + return process { apiService.rejectGroupInvite(groupId) } } - override fun acceptQuest(groupId: String): Flowable { - return apiService.acceptQuest(groupId).compose(configureApiCallObserver()) + override suspend fun acceptQuest(groupId: String): Void? { + return process { apiService.acceptQuest(groupId) } } - override fun rejectQuest(groupId: String): Flowable { - return apiService.rejectQuest(groupId).compose(configureApiCallObserver()) + override suspend fun rejectQuest(groupId: String): Void? { + return process { apiService.rejectQuest(groupId) } } - override fun cancelQuest(groupId: String): Flowable { - return apiService.cancelQuest(groupId).compose(configureApiCallObserver()) + override suspend fun cancelQuest(groupId: String): Void? { + return process { apiService.cancelQuest(groupId) } } - override fun forceStartQuest(groupId: String, group: Group): Flowable { - return apiService.forceStartQuest(groupId, group).compose(configureApiCallObserver()) + override suspend fun forceStartQuest(groupId: String, group: Group): Quest? { + return process { apiService.forceStartQuest(groupId, group) } } - override fun inviteToQuest(groupId: String, questKey: String): Flowable { - return apiService.inviteToQuest(groupId, questKey).compose(configureApiCallObserver()) + override suspend fun inviteToQuest(groupId: String, questKey: String): Quest? { + return process { apiService.inviteToQuest(groupId, questKey) } } - override fun abortQuest(groupId: String): Flowable { - return apiService.abortQuest(groupId).compose(configureApiCallObserver()) + override suspend fun abortQuest(groupId: String): Quest? { + return process { apiService.abortQuest(groupId) } } - override fun leaveQuest(groupId: String): Flowable { - return apiService.leaveQuest(groupId).compose(configureApiCallObserver()) + override suspend fun leaveQuest(groupId: String): Void? { + return process { apiService.leaveQuest(groupId) } } - override fun validatePurchase(request: PurchaseValidationRequest): Flowable { - return apiService.validatePurchase(request).compose(configureApiCallObserver()) + override suspend fun validatePurchase(request: PurchaseValidationRequest): PurchaseValidationResult? { + return process { apiService.validatePurchase(request) } } - override fun changeCustomDayStart(updateObject: Map): Flowable { - return apiService.changeCustomDayStart(updateObject).compose(configureApiCallObserver()) + override suspend fun changeCustomDayStart(updateObject: Map): User? { + return process { apiService.changeCustomDayStart(updateObject) } } override suspend fun getMember(memberId: String) = processResponse(apiService.getMember(memberId)) override suspend fun getMemberWithUsername(username: String) = processResponse(apiService.getMemberWithUsername(username)) - override fun getMemberAchievements(memberId: String): Flowable> { - return apiService.getMemberAchievements(memberId, languageCode).compose(configureApiCallObserver()) + override suspend fun getMemberAchievements(memberId: String): List? { + return process { apiService.getMemberAchievements(memberId, languageCode) } } - override fun findUsernames(username: String, context: String?, id: String?): Flowable> { - return apiService.findUsernames(username, context, id).compose(configureApiCallObserver()) + override suspend fun findUsernames(username: String, context: String?, id: String?): List? { + return process { apiService.findUsernames(username, context, id) } } override suspend fun postPrivateMessage(messageDetails: Map): PostChatMessageResult? { return process { apiService.postPrivateMessage(messageDetails) } } - override fun retrieveShopIventory(identifier: String): Flowable { - return apiService.retrieveShopInventory(identifier, languageCode).compose(configureApiCallObserver()) + override suspend fun retrieveShopIventory(identifier: String): Shop? { + return process { apiService.retrieveShopInventory(identifier, languageCode) } } - override fun addPushDevice(pushDeviceData: Map): Flowable> { - return apiService.addPushDevice(pushDeviceData).compose(configureApiCallObserver()) + override suspend fun addPushDevice(pushDeviceData: Map): List? { + return process { apiService.addPushDevice(pushDeviceData) } } - override fun deletePushDevice(regId: String): Flowable> { - return apiService.deletePushDevice(regId).compose(configureApiCallObserver()) + override suspend fun deletePushDevice(regId: String): List? { + return process { apiService.deletePushDevice(regId) } } - override fun getUserChallenges(page: Int, memberOnly: Boolean): Flowable> { + override suspend fun getUserChallenges(page: Int, memberOnly: Boolean): List? { return if (memberOnly) { - apiService.getUserChallenges(page, memberOnly).compose(configureApiCallObserver()) + process { apiService.getUserChallenges(page, memberOnly) } } else { - apiService.getUserChallenges(page).compose(configureApiCallObserver()) + process { apiService.getUserChallenges(page) } } } - override fun getChallengeTasks(challengeId: String): Flowable { - return apiService.getChallengeTasks(challengeId).compose(configureApiCallObserver()) + override suspend fun getChallengeTasks(challengeId: String): TaskList? { + return process { apiService.getChallengeTasks(challengeId) } } - override fun getChallenge(challengeId: String): Flowable { - return apiService.getChallenge(challengeId).compose(configureApiCallObserver()) + override suspend fun getChallenge(challengeId: String): Challenge? { + return process { apiService.getChallenge(challengeId) } } - override fun joinChallenge(challengeId: String): Flowable { - return apiService.joinChallenge(challengeId).compose(configureApiCallObserver()) + override suspend fun joinChallenge(challengeId: String): Challenge? { + return process { apiService.joinChallenge(challengeId) } } - override fun leaveChallenge(challengeId: String, body: LeaveChallengeBody): Flowable { - return apiService.leaveChallenge(challengeId, body).compose(configureApiCallObserver()) + override suspend fun leaveChallenge(challengeId: String, body: LeaveChallengeBody): Void? { + return process { apiService.leaveChallenge(challengeId, body) } } - override fun createChallenge(challenge: Challenge): Flowable { - return apiService.createChallenge(challenge).compose(configureApiCallObserver()) + override suspend fun createChallenge(challenge: Challenge): Challenge? { + return process { apiService.createChallenge(challenge) } } - override fun createChallengeTasks(challengeId: String, tasks: List): Flowable> { - return apiService.createChallengeTasks(challengeId, tasks).compose(configureApiCallObserver()) + override suspend fun createChallengeTasks(challengeId: String, tasks: List): List? { + return process { apiService.createChallengeTasks(challengeId, tasks) } } - override fun createChallengeTask(challengeId: String, task: Task): Flowable { - return apiService.createChallengeTask(challengeId, task).compose(configureApiCallObserver()) + override suspend fun createChallengeTask(challengeId: String, task: Task): Task? { + return process { apiService.createChallengeTask(challengeId, task) } } - override fun updateChallenge(challenge: Challenge): Flowable { - return apiService.updateChallenge(challenge.id ?: "", challenge).compose(configureApiCallObserver()) + override suspend fun updateChallenge(challenge: Challenge): Challenge? { + return process { apiService.updateChallenge(challenge.id ?: "", challenge) } } - override fun deleteChallenge(challengeId: String): Flowable { - return apiService.deleteChallenge(challengeId).compose(configureApiCallObserver()) + override suspend fun deleteChallenge(challengeId: String): Void? { + return process { apiService.deleteChallenge(challengeId) } } - override fun debugAddTenGems(): Flowable { - return apiService.debugAddTenGems().compose(configureApiCallObserver()) + override suspend fun debugAddTenGems(): Void? { + return process { apiService.debugAddTenGems() } } - override fun readNotification(notificationId: String): Flowable> { - return apiService.readNotification(notificationId).compose(configureApiCallObserver()) + override suspend fun readNotification(notificationId: String): List? { + return process { apiService.readNotification(notificationId) } } - override fun readNotifications(notificationIds: Map>): Flowable> { - return apiService.readNotifications(notificationIds).compose(configureApiCallObserver()) + override suspend fun readNotifications(notificationIds: Map>): List? { + return process { apiService.readNotifications(notificationIds) } } - override fun seeNotifications(notificationIds: Map>): Flowable> { - return apiService.seeNotifications(notificationIds).compose(configureApiCallObserver()) + override suspend fun seeNotifications(notificationIds: Map>): List? { + return process { apiService.seeNotifications(notificationIds) } } - override fun openMysteryItem(): Flowable { - return apiService.openMysteryItem().compose(configureApiCallObserver()) + override suspend fun openMysteryItem(): Equipment? { + return process { apiService.openMysteryItem() } } - override fun runCron(): Flowable { - return apiService.runCron().compose(configureApiCallObserver()) + override suspend fun runCron(): Void? { + return process { apiService.runCron() } } override suspend fun reroll(): User? = process { apiService.reroll() } - override fun resetAccount(): Flowable { - return apiService.resetAccount().compose(configureApiCallObserver()) + override suspend fun resetAccount(): Void? { + return process { apiService.resetAccount() } } - override fun deleteAccount(password: String): Flowable { + override suspend fun deleteAccount(password: String): Void? { val updateObject = HashMap() updateObject["password"] = password - return apiService.deleteAccount(updateObject).compose(configureApiCallObserver()) + return process { apiService.deleteAccount(updateObject) } } override suspend fun togglePinnedItem(pinType: String, path: String): Void? { return process { apiService.togglePinnedItem(pinType, path) } } - override fun sendPasswordResetEmail(email: String): Flowable { + override suspend fun sendPasswordResetEmail(email: String): Void? { val data = HashMap() data["email"] = email - return apiService.sendPasswordResetEmail(data).compose(configureApiCallObserver()) + return process { apiService.sendPasswordResetEmail(data) } } override suspend fun updateLoginName(newLoginName: String, password: String): Void? { @@ -764,55 +733,55 @@ class ApiClientImpl( return process { apiService.updateLoginName(updateObject) } } - override fun verifyUsername(username: String): Flowable { + override suspend fun verifyUsername(username: String): VerifyUsernameResponse? { val updateObject = HashMap() updateObject["username"] = username - return this.apiService.verifyUsername(updateObject).compose(configureApiCallObserver()) + return process { this.apiService.verifyUsername(updateObject) } } - override fun updateEmail(newEmail: String, password: String): Flowable { + override suspend fun updateEmail(newEmail: String, password: String): Void? { val updateObject = HashMap() updateObject["newEmail"] = newEmail if (password.isNotBlank()) { updateObject["password"] = password } - return apiService.updateEmail(updateObject).compose(configureApiCallObserver()) + return process { apiService.updateEmail(updateObject) } } - override fun updatePassword( + override suspend fun updatePassword( oldPassword: String, newPassword: String, newPasswordConfirmation: String - ): Flowable { + ): Void? { val updateObject = HashMap() updateObject["password"] = oldPassword updateObject["newPassword"] = newPassword updateObject["confirmPassword"] = newPasswordConfirmation - return apiService.updatePassword(updateObject).compose(configureApiCallObserver()) + return process { apiService.updatePassword(updateObject) } } - override fun allocatePoint(stat: String): Flowable { - return apiService.allocatePoint(stat).compose(configureApiCallObserver()) + override suspend fun allocatePoint(stat: String): Stats? { + return process { apiService.allocatePoint(stat) } } - override fun transferGems(giftedID: String, amount: Int): Flowable { - return apiService.transferGems(mapOf(Pair("toUserId", giftedID), Pair("gemAmount", amount))).compose(configureApiCallObserver()) + override suspend fun transferGems(giftedID: String, amount: Int): Void? { + return process { apiService.transferGems(mapOf(Pair("toUserId", giftedID), Pair("gemAmount", amount))) } } - override fun getTeamPlans(): Flowable> { - return apiService.getTeamPlans().compose(configureApiCallObserver()) + override suspend fun getTeamPlans(): List? { + return process { apiService.getTeamPlans() } } override suspend fun getTeamPlanTasks(teamID: String): TaskList? { return processResponse(apiService.getTeamPlanTasks(teamID)) } - override fun bulkAllocatePoints( + override suspend fun bulkAllocatePoints( strength: Int, intelligence: Int, constitution: Int, perception: Int - ): Flowable { + ): Stats? { val body = HashMap>() val stats = HashMap() stats["str"] = strength @@ -820,11 +789,11 @@ class ApiClientImpl( stats["con"] = constitution stats["per"] = perception body["stats"] = stats - return apiService.bulkAllocatePoints(body).compose(configureApiCallObserver()) + return process { apiService.bulkAllocatePoints(body) } } - override fun retrieveMarketGear(): Flowable { - return apiService.retrieveMarketGear(languageCode).compose(configureApiCallObserver()) + override suspend fun retrieveMarketGear(): Shop? { + return process { apiService.retrieveMarketGear(languageCode) } } override suspend fun getWorldState(): WorldState? = process { apiService.worldState() } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/ChallengeRepositoryImpl.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/ChallengeRepositoryImpl.kt index 747992885..8621aa273 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/ChallengeRepositoryImpl.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/ChallengeRepositoryImpl.kt @@ -10,8 +10,7 @@ import com.habitrpg.android.habitica.models.tasks.Task import com.habitrpg.android.habitica.models.tasks.TaskList import com.habitrpg.shared.habitica.models.tasks.TaskType import com.habitrpg.shared.habitica.models.tasks.TasksOrder -import io.reactivex.rxjava3.core.Flowable -import retrofit2.HttpException +import kotlinx.coroutines.flow.Flow class ChallengeRepositoryImpl( localRepository: ChallengeLocalRepository, @@ -19,47 +18,42 @@ class ChallengeRepositoryImpl( userID: String ) : BaseRepositoryImpl(localRepository, apiClient, userID), ChallengeRepository { - override fun isChallengeMember(challengeID: String): Flowable { + override fun isChallengeMember(challengeID: String): Flow { return localRepository.isChallengeMember(userID, challengeID) } - override fun getChallengepMembership(id: String): Flowable { + override fun getChallengepMembership(id: String): Flow { return localRepository.getChallengeMembership(userID, id) } - override fun getChallengeMemberships(): Flowable> { + override fun getChallengeMemberships(): Flow> { return localRepository.getChallengeMemberships(userID) } - override fun getChallenge(challengeId: String): Flowable { + override fun getChallenge(challengeId: String): Flow { return localRepository.getChallenge(challengeId) } - override fun getChallengeTasks(challengeId: String): Flowable> { + override fun getChallengeTasks(challengeId: String): Flow> { return localRepository.getTasks(challengeId) } - override fun retrieveChallenge(challengeID: String): Flowable { - return apiClient.getChallenge(challengeID).doOnNext { - localRepository.save(it) - } - .doOnError { - if (it is HttpException && it.code() == 404) { - localRepository.getChallenge(challengeID).firstElement().subscribe { challenge -> - localRepository.delete(challenge) - } - } - } + override suspend fun retrieveChallenge(challengeID: String): Challenge? { + val challenge = apiClient.getChallenge(challengeID) ?: return null + localRepository.save(challenge) + return challenge } - override fun retrieveChallengeTasks(challengeID: String): Flowable { - return apiClient.getChallengeTasks(challengeID).doOnNext { tasks -> + override suspend fun retrieveChallengeTasks(challengeID: String): TaskList? { + val tasks = apiClient.getChallengeTasks(challengeID) + if (tasks != null) { val taskList = tasks.tasks.values.toList() taskList.forEach { it.userId = challengeID } localRepository.save(taskList) } + return tasks } private fun getTaskOrders(taskList: List): TasksOrder { @@ -81,75 +75,81 @@ class ChallengeRepositoryImpl( return tasksOrder } - private fun addChallengeTasks(challenge: Challenge, addedTaskList: List): Flowable { - return when { - addedTaskList.count() == 1 -> apiClient.createChallengeTask(challenge.id ?: "", addedTaskList[0]).map { challenge } - addedTaskList.count() > 1 -> apiClient.createChallengeTasks(challenge.id ?: "", addedTaskList).map { challenge } - else -> Flowable.just(challenge) + private suspend fun addChallengeTasks(challenge: Challenge, addedTaskList: List) { + when { + addedTaskList.count() == 1 -> apiClient.createChallengeTask(challenge.id ?: "", addedTaskList[0]) + addedTaskList.count() > 1 -> apiClient.createChallengeTasks(challenge.id ?: "", addedTaskList) } } - override fun createChallenge(challenge: Challenge, taskList: List): Flowable { + override suspend fun createChallenge(challenge: Challenge, taskList: List): Challenge? { challenge.tasksOrder = getTaskOrders(taskList) - return apiClient.createChallenge(challenge).flatMap { - addChallengeTasks(it, taskList) + val createdChallenge = apiClient.createChallenge(challenge) + if (createdChallenge != null) { + addChallengeTasks(createdChallenge, taskList) } + return createdChallenge } - override fun updateChallenge( + override suspend fun updateChallenge( challenge: Challenge, fullTaskList: List, addedTaskList: List, updatedTaskList: List, removedTaskList: List - ): Flowable { - - var flowable: Flowable<*> = Flowable.just("") - + ): Challenge? { updatedTaskList .map { localRepository.getUnmanagedCopy(it) } .forEach { task -> - flowable = flowable.flatMap { apiClient.updateTask(task.id ?: "", task) } + apiClient.updateTask(task.id ?: "", task) } removedTaskList.forEach { task -> - flowable = flowable.flatMap { apiClient.deleteTask(task) } + apiClient.deleteTask(task) } if (addedTaskList.isNotEmpty()) { - flowable = flowable.flatMap { addChallengeTasks(challenge, addedTaskList) } + addChallengeTasks(challenge, addedTaskList) } challenge.tasksOrder = getTaskOrders(fullTaskList) - return flowable.flatMap { apiClient.updateChallenge(challenge) } - .doOnNext { localRepository.save(challenge) } + val updatedChallenges = apiClient.updateChallenge(challenge) + if (updatedChallenges != null) { + localRepository.save(updatedChallenges) + } + return updatedChallenges } - override fun deleteChallenge(challengeId: String): Flowable { + override suspend fun deleteChallenge(challengeId: String): Void? { return apiClient.deleteChallenge(challengeId) } - override fun getChallenges(): Flowable> { + override fun getChallenges(): Flow> { return localRepository.challenges } - override fun getUserChallenges(userId: String?): Flowable> { + override fun getUserChallenges(userId: String?): Flow> { return localRepository.getUserChallenges(userId ?: userID) } - override fun retrieveChallenges(page: Int, memberOnly: Boolean): Flowable> { - return apiClient.getUserChallenges(page, memberOnly) - .doOnNext { localRepository.saveChallenges(it, page == 0, memberOnly, userID) } + override suspend fun retrieveChallenges(page: Int, memberOnly: Boolean): List? { + val challenges = apiClient.getUserChallenges(page, memberOnly) + if (challenges != null) { + localRepository.saveChallenges(challenges, page == 0, memberOnly, userID) + } + return challenges } - override fun leaveChallenge(challenge: Challenge, keepTasks: String): Flowable { - return apiClient.leaveChallenge(challenge.id ?: "", LeaveChallengeBody(keepTasks)) - .doOnNext { localRepository.setParticipating(userID, challenge.id ?: "", false) } + override suspend fun leaveChallenge(challenge: Challenge, keepTasks: String): Void? { + apiClient.leaveChallenge(challenge.id ?: "", LeaveChallengeBody(keepTasks)) + localRepository.setParticipating(userID, challenge.id ?: "", false) + return null } - override fun joinChallenge(challenge: Challenge): Flowable { - return apiClient.joinChallenge(challenge.id ?: "") - .doOnNext { localRepository.setParticipating(userID, challenge.id ?: "", true) } + override suspend fun joinChallenge(challenge: Challenge): Challenge? { + val returnedChallenge = apiClient.joinChallenge(challenge.id ?: "") ?: return null + localRepository.setParticipating(userID, returnedChallenge.id ?: "", true) + return returnedChallenge } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/ContentRepositoryImpl.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/ContentRepositoryImpl.kt index 5028049df..ba6ed961b 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/ContentRepositoryImpl.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/ContentRepositoryImpl.kt @@ -8,8 +8,8 @@ import com.habitrpg.android.habitica.helpers.AprilFoolsHandler import com.habitrpg.android.habitica.models.ContentResult import com.habitrpg.android.habitica.models.WorldState import com.habitrpg.android.habitica.models.inventory.SpecialItem -import io.reactivex.rxjava3.core.Flowable import io.realm.RealmList +import kotlinx.coroutines.flow.Flow import java.util.Date class ContentRepositoryImpl( @@ -52,7 +52,7 @@ class ContentRepositoryImpl( return null } - override fun getWorldState(): Flowable { + override fun getWorldState(): Flow { return localRepository.getWorldState() } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/CustomizationRepositoryImpl.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/CustomizationRepositoryImpl.kt index 502e9cd64..ee17bff76 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/CustomizationRepositoryImpl.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/CustomizationRepositoryImpl.kt @@ -4,7 +4,7 @@ import com.habitrpg.android.habitica.data.ApiClient import com.habitrpg.android.habitica.data.CustomizationRepository import com.habitrpg.android.habitica.data.local.CustomizationLocalRepository import com.habitrpg.android.habitica.models.inventory.Customization -import io.reactivex.rxjava3.core.Flowable +import kotlinx.coroutines.flow.Flow class CustomizationRepositoryImpl( localRepository: CustomizationLocalRepository, @@ -12,7 +12,7 @@ class CustomizationRepositoryImpl( userID: String ) : BaseRepositoryImpl(localRepository, apiClient, userID), CustomizationRepository { - override fun getCustomizations(type: String, category: String?, onlyAvailable: Boolean): Flowable> { + override fun getCustomizations(type: String, category: String?, onlyAvailable: Boolean): Flow> { return localRepository.getCustomizations(type, category, onlyAvailable) } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/FAQRepositoryImpl.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/FAQRepositoryImpl.kt index eb7ef1dc9..8c74b2722 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/FAQRepositoryImpl.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/FAQRepositoryImpl.kt @@ -4,14 +4,14 @@ import com.habitrpg.android.habitica.data.ApiClient import com.habitrpg.android.habitica.data.FAQRepository import com.habitrpg.android.habitica.data.local.FAQLocalRepository import com.habitrpg.android.habitica.models.FAQArticle -import io.reactivex.rxjava3.core.Flowable +import kotlinx.coroutines.flow.Flow class FAQRepositoryImpl(localRepository: FAQLocalRepository, apiClient: ApiClient, userID: String) : BaseRepositoryImpl(localRepository, apiClient, userID), FAQRepository { - override fun getArticle(position: Int): Flowable { + override fun getArticle(position: Int): Flow { return localRepository.getArticle(position) } - override fun getArticles(): Flowable> { + override fun getArticles(): Flow> { return localRepository.articles } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/InventoryRepositoryImpl.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/InventoryRepositoryImpl.kt index e8a165d56..712c970ae 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/InventoryRepositoryImpl.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/InventoryRepositoryImpl.kt @@ -22,8 +22,8 @@ import com.habitrpg.android.habitica.models.user.OwnedMount import com.habitrpg.android.habitica.models.user.OwnedPet import com.habitrpg.android.habitica.models.user.User import com.habitrpg.shared.habitica.models.responses.FeedResponse -import io.reactivex.rxjava3.core.Flowable import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.firstOrNull class InventoryRepositoryImpl( localRepository: InventoryLocalRepository, @@ -35,7 +35,7 @@ class InventoryRepositoryImpl( override fun getQuestContent(key: String) = localRepository.getQuestContent(key) - override fun getEquipment(searchedKeys: List): Flowable> { + override fun getEquipment(searchedKeys: List): Flow> { return localRepository.getEquipment(searchedKeys) } @@ -43,7 +43,7 @@ class InventoryRepositoryImpl( return localRepository.getArmoireRemainingCount() } - override fun getInAppRewards(): Flowable> { + override fun getInAppRewards(): Flow> { return localRepository.getInAppRewards() } @@ -55,15 +55,15 @@ class InventoryRepositoryImpl( return rewards } - override fun getOwnedEquipment(type: String): Flowable> { + override fun getOwnedEquipment(type: String): Flow> { return localRepository.getOwnedEquipment(type) } - override fun getOwnedEquipment(): Flowable> { + override fun getOwnedEquipment(): Flow> { return localRepository.getOwnedEquipment() } - override fun getEquipmentType(type: String, set: String): Flowable> { + override fun getEquipmentType(type: String, set: String): Flow> { return localRepository.getEquipmentType(type, set) } @@ -71,36 +71,31 @@ class InventoryRepositoryImpl( return localRepository.getOwnedItems(itemType, userID, includeZero) } - override fun getOwnedItems(includeZero: Boolean): Flowable> { + override fun getOwnedItems(includeZero: Boolean): Flow> { return localRepository.getOwnedItems(userID, includeZero) } override fun getItems(itemClass: Class, keys: Array): Flow> { - return localRepository.getItemsFlowable(itemClass, keys) - } - - override fun getItemsFlowable(itemClass: Class): Flowable> { - return localRepository.getItemsFlowable(itemClass) + return localRepository.getItems(itemClass, keys) } override fun getItems(itemClass: Class): Flow> { return localRepository.getItems(itemClass) } - override fun getEquipment(key: String): Flowable { + override fun getEquipment(key: String): Flow { return localRepository.getEquipment(key) } - override fun openMysteryItem(user: User?): Flowable { - return apiClient.openMysteryItem() - .flatMap { localRepository.getEquipment(it.key ?: "").firstElement().toFlowable() } - .doOnNext { itemData -> - val liveEquipment = localRepository.getLiveObject(itemData) - localRepository.executeTransaction { - liveEquipment?.owned = true - } - localRepository.decrementMysteryItemCount(user) - } + override suspend fun openMysteryItem(user: User?): Equipment? { + val item = apiClient.openMysteryItem() + val equipment = localRepository.getEquipment(item?.key ?: "").firstOrNull() ?: return null + val liveEquipment = localRepository.getLiveObject(equipment) + localRepository.executeTransaction { + liveEquipment?.owned = true + } + localRepository.decrementMysteryItemCount(user) + return equipment } override fun saveEquipment(equipment: Equipment) { @@ -135,44 +130,42 @@ class InventoryRepositoryImpl( localRepository.updateOwnedEquipment(user) } - override fun changeOwnedCount(type: String, key: String, amountToAdd: Int) { + override suspend fun changeOwnedCount(type: String, key: String, amountToAdd: Int) { localRepository.changeOwnedCount(type, key, userID, amountToAdd) } - override fun sellItem(type: String, key: String): Flowable { - return localRepository.getOwnedItem(userID, type, key, true) - .flatMap { item -> sellItem(item) } + override suspend fun sellItem(type: String, key: String): User? { + val item = localRepository.getOwnedItem(userID, type, key, true).firstOrNull() ?: return null + return sellItem(item) } - override fun sellItem(item: OwnedItem): Flowable { - return localRepository.getItem(item.itemType ?: "", item.key ?: "") - .flatMap { newItem -> sellItem(newItem, item) } + override suspend fun sellItem(ownedItem: OwnedItem): User? { + val item = localRepository.getItem(ownedItem.itemType ?: "", ownedItem.key ?: "").firstOrNull() ?: return null + return sellItem(item, ownedItem) } - override fun getLatestMysteryItem(): Flowable { + override fun getLatestMysteryItem(): Flow { return localRepository.getLatestMysteryItem() } - override fun getItem(type: String, key: String): Flowable { + override fun getItem(type: String, key: String): Flow { return localRepository.getItem(type, key) } - private fun sellItem(item: Item, ownedItem: OwnedItem): Flowable { + private suspend fun sellItem(item: Item, ownedItem: OwnedItem): User? { localRepository.executeTransaction { val liveItem = localRepository.getLiveObject(ownedItem) liveItem?.numberOwned = (liveItem?.numberOwned ?: 0) - 1 } - return apiClient.sellItem(item.type, item.key) - .map { user -> - localRepository.soldItem(userID, user) - } + val user = apiClient.sellItem(item.type, item.key) ?: return null + return localRepository.soldItem(userID, user) } - override fun equipGear(equipment: String, asCostume: Boolean): Flowable { + override suspend fun equipGear(equipment: String, asCostume: Boolean): Items? { return equip(if (asCostume) "costume" else "equipped", equipment) } - override fun equip(type: String, key: String): Flowable { + override suspend fun equip(type: String, key: String): Items? { val liveUser = localRepository.getLiveUser(userID) if (liveUser != null) { @@ -199,47 +192,45 @@ class InventoryRepositoryImpl( } } } - return apiClient.equipItem(type, key) - .doOnNext { items -> - if (liveUser == null) return@doOnNext - localRepository.modify(liveUser) { liveUser -> - val newEquipped = items.gear?.equipped - val oldEquipped = liveUser.items?.gear?.equipped - val newCostume = items.gear?.costume - val oldCostume = liveUser.items?.gear?.costume - newEquipped?.let { equipped -> oldEquipped?.updateWith(equipped) } - newCostume?.let { costume -> oldCostume?.updateWith(costume) } - liveUser.items?.currentMount = items.currentMount - liveUser.items?.currentPet = items.currentPet - liveUser.balance = liveUser.balance - } - } + val items = apiClient.equipItem(type, key) ?: return null + if (liveUser == null) return null + localRepository.modify(liveUser) { liveUser -> + val newEquipped = items.gear?.equipped + val oldEquipped = liveUser.items?.gear?.equipped + val newCostume = items.gear?.costume + val oldCostume = liveUser.items?.gear?.costume + newEquipped?.let { equipped -> oldEquipped?.updateWith(equipped) } + newCostume?.let { costume -> oldCostume?.updateWith(costume) } + liveUser.items?.currentMount = items.currentMount + liveUser.items?.currentPet = items.currentPet + liveUser.balance = liveUser.balance + } + return items } - override fun feedPet(pet: Pet, food: Food): Flowable { - return apiClient.feedPet(pet.key ?: "", food.key) - .doOnNext { feedResponse -> - localRepository.feedPet(food.key, pet.key ?: "", feedResponse.value ?: 0, userID) - } + override suspend fun feedPet(pet: Pet, food: Food): FeedResponse? { + val feedResponse = apiClient.feedPet(pet.key ?: "", food.key) ?: return null + localRepository.feedPet(food.key, pet.key ?: "", feedResponse.value ?: 0, userID) + return feedResponse } - override fun hatchPet(egg: Egg, hatchingPotion: HatchingPotion, successFunction: () -> Unit): Flowable { + override suspend fun hatchPet(egg: Egg, hatchingPotion: HatchingPotion, successFunction: () -> Unit): Items? { if (appConfigManager.enableLocalChanges()) { localRepository.hatchPet(egg.key, hatchingPotion.key, userID) successFunction() } - return apiClient.hatchPet(egg.key, hatchingPotion.key) - .doOnNext { - localRepository.save(it, userID) - if (!appConfigManager.enableLocalChanges()) { - successFunction() - } - } + val items = apiClient.hatchPet(egg.key, hatchingPotion.key) ?: return null + localRepository.save(items, userID) + if (!appConfigManager.enableLocalChanges()) { + successFunction() + } + return items } - override fun inviteToQuest(quest: QuestContent): Flowable { - return apiClient.inviteToQuest("party", quest.key) - .doOnNext { localRepository.changeOwnedCount("quests", quest.key, userID, -1) } + override suspend fun inviteToQuest(quest: QuestContent): Quest? { + val newQuest = apiClient.inviteToQuest("party", quest.key) + localRepository.changeOwnedCount("quests", quest.key, userID, -1) + return newQuest } override suspend fun buyItem(user: User?, id: String, value: Double, purchaseQuantity: Int): BuyResponse? { @@ -270,15 +261,15 @@ class InventoryRepositoryImpl( return buyResponse } - override fun getAvailableLimitedItems(): Flowable> { + override fun getAvailableLimitedItems(): Flow> { return localRepository.getAvailableLimitedItems() } - override fun retrieveShopInventory(identifier: String): Flowable { + override suspend fun retrieveShopInventory(identifier: String): Shop? { return apiClient.retrieveShopIventory(identifier) } - override fun retrieveMarketGear(): Flowable { + override suspend fun retrieveMarketGear(): Shop? { return apiClient.retrieveMarketGear() } 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 20de3196e..a99a5fcae 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/SocialRepositoryImpl.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/SocialRepositoryImpl.kt @@ -4,7 +4,6 @@ import com.habitrpg.android.habitica.BuildConfig import com.habitrpg.android.habitica.data.ApiClient import com.habitrpg.android.habitica.data.SocialRepository import com.habitrpg.android.habitica.data.local.SocialLocalRepository -import com.habitrpg.android.habitica.helpers.ExceptionHandler import com.habitrpg.android.habitica.models.Achievement import com.habitrpg.android.habitica.models.inventory.Quest import com.habitrpg.android.habitica.models.members.Member @@ -15,7 +14,6 @@ import com.habitrpg.android.habitica.models.social.Group import com.habitrpg.android.habitica.models.social.GroupMembership import com.habitrpg.android.habitica.models.social.InboxConversation import com.habitrpg.android.habitica.models.user.User -import io.reactivex.rxjava3.core.Flowable import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.flow.first @@ -38,13 +36,13 @@ class SocialRepositoryImpl( return retrievePartyMembers(groupID, true) } - override fun blockMember(userID: String): Flowable> { + override suspend fun blockMember(userID: String): List? { return apiClient.blockMember(userID) } override fun getGroupMembership(id: String) = localRepository.getGroupMembership(userID, id) - override fun getGroupMemberships(): Flowable> { + override fun getGroupMemberships(): Flow> { return localRepository.getGroupMemberships(userID) } @@ -54,18 +52,18 @@ class SocialRepositoryImpl( return messages } - override fun getGroupChat(groupId: String): Flowable> { + override fun getGroupChat(groupId: String): Flow> { return localRepository.getGroupChat(groupId) } - override fun markMessagesSeen(seenGroupId: String) { - apiClient.seenMessages(seenGroupId).subscribe({ }, ExceptionHandler.rx()) + override suspend fun markMessagesSeen(seenGroupId: String) { + apiClient.seenMessages(seenGroupId) } - override fun flagMessage(chatMessageID: String, additionalInfo: String, groupID: String?): Flowable { + override suspend fun flagMessage(chatMessageID: String, additionalInfo: String, groupID: String?): Void? { return when { - chatMessageID.isBlank() -> Flowable.empty() - userID == BuildConfig.ANDROID_TESTING_UUID -> Flowable.empty() + chatMessageID.isBlank() -> return null + userID == BuildConfig.ANDROID_TESTING_UUID -> return null else -> { val data = mutableMapOf() data["comment"] = additionalInfo @@ -78,38 +76,36 @@ class SocialRepositoryImpl( } } - override fun likeMessage(chatMessage: ChatMessage): Flowable { + override suspend fun likeMessage(chatMessage: ChatMessage): ChatMessage? { if (chatMessage.id.isBlank()) { - return Flowable.empty() + return null } val liked = chatMessage.userLikesMessage(userID) if (chatMessage.isManaged) { localRepository.likeMessage(chatMessage, userID, !liked) } - return apiClient.likeMessage(chatMessage.groupId ?: "", chatMessage.id) - .map { - it.groupId = chatMessage.groupId - it - } + val message = apiClient.likeMessage(chatMessage.groupId ?: "", chatMessage.id) + message?.groupId = chatMessage.groupId + return null } - override fun deleteMessage(chatMessage: ChatMessage): Flowable { - return if (chatMessage.isInboxMessage) { + override suspend fun deleteMessage(chatMessage: ChatMessage): Void? { + if (chatMessage.isInboxMessage) { apiClient.deleteInboxMessage(chatMessage.id) } else { apiClient.deleteMessage(chatMessage.groupId ?: "", chatMessage.id) - }.doOnNext { localRepository.deleteMessage(chatMessage.id) } + } + localRepository.deleteMessage(chatMessage.id) + return null } - override fun postGroupChat(groupId: String, messageObject: HashMap): Flowable { - return apiClient.postGroupChat(groupId, messageObject) - .map { postChatMessageResult -> - postChatMessageResult.message.groupId = groupId - postChatMessageResult - } + override suspend fun postGroupChat(groupId: String, messageObject: HashMap): PostChatMessageResult? { + val result = apiClient.postGroupChat(groupId, messageObject) + result?.message?.groupId = groupId + return result } - override fun postGroupChat(groupId: String, message: String): Flowable { + override suspend fun postGroupChat(groupId: String, message: String): PostChatMessageResult? { val messageObject = HashMap() messageObject["message"] = message return postGroupChat(groupId, messageObject) @@ -189,17 +185,16 @@ class SocialRepositoryImpl( return apiClient.updateGroup(copiedGroup.id, copiedGroup) } - override fun retrieveGroups(type: String): Flowable> { - return apiClient.listGroups(type) - .doOnNext { groups -> - if ("guilds" == type) { - val memberships = groups.map { - GroupMembership(userID, it.id) - } - localRepository.saveGroupMemberships(userID, memberships) - } - localRepository.save(groups) + override suspend fun retrieveGroups(type: String): List? { + val groups = apiClient.listGroups(type) ?: return null + if ("guilds" == type) { + val memberships = groups.map { + GroupMembership(userID, it.id) } + localRepository.saveGroupMemberships(userID, memberships) + } + localRepository.save(groups) + return groups } override fun getGroups(type: String) = localRepository.getGroups(type) @@ -219,14 +214,14 @@ class SocialRepositoryImpl( return messages } - override fun retrieveInboxConversations(): Flowable> { - return apiClient.retrieveInboxConversations().doOnNext { conversations -> - localRepository.saveInboxConversations(userID, conversations) - } + override suspend fun retrieveInboxConversations(): List? { + val conversations = apiClient.retrieveInboxConversations() ?: return null + localRepository.saveInboxConversations(userID, conversations) + return conversations } override suspend fun postPrivateMessage(recipientId: String, messageObject: HashMap): List? { - val message = apiClient.postPrivateMessage(messageObject) + apiClient.postPrivateMessage(messageObject) return retrieveInboxMessages(recipientId, 0) } @@ -246,7 +241,7 @@ class SocialRepositoryImpl( return members } - override fun inviteToGroup(id: String, inviteData: Map): Flowable> = apiClient.inviteToGroup(id, inviteData) + override suspend fun inviteToGroup(id: String, inviteData: Map) = apiClient.inviteToGroup(id, inviteData) override suspend fun retrieveMember(userId: String?): Member? { return if (userId == null) { @@ -264,11 +259,11 @@ class SocialRepositoryImpl( return retrieveMember(username) } - override fun findUsernames(username: String, context: String?, id: String?): Flowable> { + override suspend fun findUsernames(username: String, context: String?, id: String?): List? { return apiClient.findUsernames(username, context, id) } - override fun markPrivateMessagesRead(user: User?): Flowable { + override suspend fun markPrivateMessagesRead(user: User?): Void? { if (user?.isManaged == true) { localRepository.modify(user) { it.inbox?.hasUserSeenInbox = true @@ -298,57 +293,57 @@ class SocialRepositoryImpl( override fun getUserGroups(type: String?) = localRepository.getUserGroups(userID, type) - override fun acceptQuest(user: User?, partyId: String): Flowable { - return apiClient.acceptQuest(partyId) - .doOnNext { - user?.let { - localRepository.updateRSVPNeeded(it, false) - } + override suspend fun acceptQuest(user: User?, partyId: String): Void? { + apiClient.acceptQuest(partyId) + user?.let { + localRepository.updateRSVPNeeded(it, false) } + return null } - override fun rejectQuest(user: User?, partyId: String): Flowable { - return apiClient.rejectQuest(partyId) - .doOnNext { _ -> - user?.let { - localRepository.updateRSVPNeeded(it, false) - } + override suspend fun rejectQuest(user: User?, partyId: String): Void? { + apiClient.rejectQuest(partyId) + user?.let { + localRepository.updateRSVPNeeded(it, false) } + return null } - override fun leaveQuest(partyId: String): Flowable { + override suspend fun leaveQuest(partyId: String): Void? { return apiClient.leaveQuest(partyId) } - override fun cancelQuest(partyId: String): Flowable { - return apiClient.cancelQuest(partyId) - .doOnNext { localRepository.removeQuest(partyId) } + override suspend fun cancelQuest(partyId: String): Void? { + apiClient.cancelQuest(partyId) + localRepository.removeQuest(partyId) + return null } - override fun abortQuest(partyId: String): Flowable { - return apiClient.abortQuest(partyId) - .doOnNext { localRepository.removeQuest(partyId) } + override suspend fun abortQuest(partyId: String): Quest? { + val quest = apiClient.abortQuest(partyId) + localRepository.removeQuest(partyId) + return quest } - override fun rejectGroupInvite(groupId: String): Flowable { - return apiClient.rejectGroupInvite(groupId) - .doOnNext { - localRepository.rejectGroupInvitation(userID, groupId) - } + override suspend fun rejectGroupInvite(groupId: String): Void? { + apiClient.rejectGroupInvite(groupId) + localRepository.rejectGroupInvitation(userID, groupId) + return null } - override fun forceStartQuest(party: Group): Flowable { - return apiClient.forceStartQuest(party.id, localRepository.getUnmanagedCopy(party)) - .doOnNext { localRepository.setQuestActivity(party, true) } + override suspend fun forceStartQuest(party: Group): Quest? { + val quest = apiClient.forceStartQuest(party.id, localRepository.getUnmanagedCopy(party)) + localRepository.setQuestActivity(party, true) + return quest } - override fun getMemberAchievements(userId: String?): Flowable> { + override suspend fun getMemberAchievements(userId: String?): List? { return if (userId == null) { - Flowable.empty() + null } else apiClient.getMemberAchievements(userId) } - override fun transferGems(giftedID: String, amount: Int): Flowable { + override suspend fun transferGems(giftedID: String, amount: Int): Void? { return apiClient.transferGems(giftedID, amount) } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/TagRepositoryImpl.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/TagRepositoryImpl.kt index e70a7f83c..ff56233b0 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/TagRepositoryImpl.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/TagRepositoryImpl.kt @@ -4,58 +4,53 @@ import com.habitrpg.android.habitica.data.ApiClient import com.habitrpg.android.habitica.data.TagRepository import com.habitrpg.android.habitica.data.local.TagLocalRepository import com.habitrpg.android.habitica.models.Tag -import io.reactivex.rxjava3.core.Flowable -import io.reactivex.rxjava3.core.Single +import kotlinx.coroutines.flow.Flow class TagRepositoryImpl(localRepository: TagLocalRepository, apiClient: ApiClient, userID: String) : BaseRepositoryImpl(localRepository, apiClient, userID), TagRepository { - override fun getTags(): Flowable> { + override fun getTags(): Flow> { return getTags(userID) } - override fun getTags(userId: String): Flowable> { + override fun getTags(userId: String): Flow> { return localRepository.getTags(userId) } - override fun createTag(tag: Tag): Flowable { - return apiClient.createTag(tag) - .doOnNext { - it.userId = userID - localRepository.save(it) - } + override suspend fun createTag(tag: Tag): Tag? { + val savedTag = apiClient.createTag(tag) ?: return null + savedTag.userId = userID + localRepository.save(savedTag) + return savedTag } - override fun updateTag(tag: Tag): Flowable { - return apiClient.updateTag(tag.id, tag) - .doOnNext { - it.userId = userID - localRepository.save(it) - } + override suspend fun updateTag(tag: Tag): Tag? { + val savedTag = apiClient.updateTag(tag.id, tag) ?: return null + savedTag.userId = userID + localRepository.save(savedTag) + return savedTag } - override fun deleteTag(id: String): Flowable { - return apiClient.deleteTag(id) - .doOnNext { - localRepository.deleteTag(id) - } + override suspend fun deleteTag(id: String): Void? { + apiClient.deleteTag(id) + localRepository.deleteTag(id) + return null } - override fun createTags(tags: Collection): Single> { - return Flowable.defer { Flowable.fromIterable(tags) } - .filter { tag -> tag.name.isNotEmpty() } - .flatMap { this.createTag(it) } - .toList() + override suspend fun createTags(tags: Collection): List { + return tags.mapNotNull { + createTag(it) + } } - override fun updateTags(tags: Collection): Single> { - return Flowable.defer { Flowable.fromIterable(tags) } - .flatMap { this.updateTag(it) } - .toList() + override suspend fun updateTags(tags: Collection): List { + return tags.mapNotNull { + updateTag(it) + } } - override fun deleteTags(tagIds: Collection): Single> { - return Flowable.defer { Flowable.fromIterable(tagIds) } - .flatMap { this.deleteTag(it) } - .toList() + override suspend fun deleteTags(tagIds: Collection): List { + return tagIds.mapNotNull { + deleteTag(it) + } } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/TaskRepositoryImpl.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/TaskRepositoryImpl.kt index a6e0a8196..3a89070db 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/TaskRepositoryImpl.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/TaskRepositoryImpl.kt @@ -5,7 +5,7 @@ import com.habitrpg.android.habitica.data.ApiClient import com.habitrpg.android.habitica.data.TaskRepository import com.habitrpg.android.habitica.data.local.TaskLocalRepository import com.habitrpg.android.habitica.helpers.AppConfigManager -import com.habitrpg.android.habitica.helpers.ExceptionHandler +import com.habitrpg.android.habitica.helpers.launchCatching import com.habitrpg.android.habitica.interactors.ScoreTaskLocallyInteractor import com.habitrpg.android.habitica.models.BaseMainObject import com.habitrpg.android.habitica.models.responses.BulkTaskScoringData @@ -20,9 +20,7 @@ import com.habitrpg.shared.habitica.models.responses.TaskDirectionData import com.habitrpg.shared.habitica.models.responses.TaskScoringResult import com.habitrpg.shared.habitica.models.tasks.TaskType import com.habitrpg.shared.habitica.models.tasks.TasksOrder -import io.reactivex.rxjava3.core.Flowable -import io.reactivex.rxjava3.core.Maybe -import io.reactivex.rxjava3.core.Single +import kotlinx.coroutines.MainScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.map @@ -43,9 +41,6 @@ class TaskRepositoryImpl( override fun getTasks(taskType: TaskType, userID: String?, includedGroupIDs: Array): Flow> = this.localRepository.getTasks(taskType, userID ?: this.userID, includedGroupIDs) - override fun getTasksFlowable(taskType: TaskType, userID: String?, includedGroupIDs: Array): Flowable> = - this.localRepository.getTasksFlowable(taskType, userID ?: this.userID, includedGroupIDs) - override fun saveTasks(userId: String, order: TasksOrder, tasks: TaskList) { localRepository.saveTasks(userId, order, tasks) } @@ -56,18 +51,18 @@ class TaskRepositoryImpl( return tasks } - override fun retrieveCompletedTodos(userId: String?): Flowable { - return this.apiClient.getTasks("completedTodos") - .doOnNext { taskList -> - val tasks = taskList.tasks - this.localRepository.saveCompletedTodos(userId ?: this.userID, tasks.values) - } + override suspend fun retrieveCompletedTodos(userId: String?): TaskList? { + val taskList = this.apiClient.getTasks("completedTodos") ?: return null + val tasks = taskList.tasks + this.localRepository.saveCompletedTodos(userId ?: this.userID, tasks.values) + return taskList } - override fun retrieveTasks(userId: String, tasksOrder: TasksOrder, dueDate: Date): Flowable { + override suspend fun retrieveTasks(userId: String, tasksOrder: TasksOrder, dueDate: Date): TaskList? { val formatter = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZZZZZ", Locale.US) - return this.apiClient.getTasks("dailys", formatter.format(dueDate)) - .doOnNext { res -> this.localRepository.saveTasks(userId, tasksOrder, res) } + val taskList = this.apiClient.getTasks("dailys", formatter.format(dueDate)) ?: return null + this.localRepository.saveTasks(userId, tasksOrder, taskList) + return taskList } @Suppress("ReturnCount") @@ -120,7 +115,7 @@ class TaskRepositoryImpl( return result } - override fun bulkScoreTasks(data: List>): Flowable { + override suspend fun bulkScoreTasks(data: List>): BulkTaskScoringData? { return apiClient.bulkScoreTasks(data) } @@ -212,10 +207,10 @@ class TaskRepositoryImpl( override fun getTaskCopy(taskId: String) = localRepository.getTaskCopy(taskId) - override fun createTask(task: Task, force: Boolean): Flowable { + override suspend fun createTask(task: Task, force: Boolean): Task? { val now = Date().time if (lastTaskAction > now - 500 && !force) { - return Flowable.empty() + return null } lastTaskAction = now @@ -228,61 +223,56 @@ class TaskRepositoryImpl( } localRepository.saveSyncronous(task) - return apiClient.createTask(task) - .map { task1 -> - task1.dateCreated = Date() - task1 - } - .doOnNext { - it.tags = task.tags - localRepository.save(it) - } - .doOnError { - task.hasErrored = true - task.isSaving = false - localRepository.saveSyncronous(task) - } + val savedTask = apiClient.createTask(task) + savedTask?.dateCreated = Date() + if (savedTask != null) { + savedTask.tags = task.tags + localRepository.save(savedTask) + } else { + task.hasErrored = true + task.isSaving = false + localRepository.saveSyncronous(task) + } + return savedTask } @Suppress("ReturnCount") - override fun updateTask(task: Task, force: Boolean): Maybe { + override suspend fun updateTask(task: Task, force: Boolean): Task? { val now = Date().time if ((lastTaskAction > now - 500 && !force) || !task.isValid) { - return Maybe.just(task) + return task } lastTaskAction = now - val id = task.id ?: return Maybe.just(task) + val id = task.id ?: return task val unmanagedTask = localRepository.getUnmanagedCopy(task) unmanagedTask.isSaving = true unmanagedTask.hasErrored = false localRepository.saveSyncronous(unmanagedTask) - return apiClient.updateTask(id, unmanagedTask).singleElement() - .map { task1 -> - task1.position = task.position - task1.id = task.id - task1 - } - .doOnSuccess { - it.tags = task.tags - localRepository.save(it) - } - .doOnError { - unmanagedTask.hasErrored = true - unmanagedTask.isSaving = false - localRepository.saveSyncronous(unmanagedTask) - } + val savedTask = apiClient.updateTask(id, unmanagedTask) + savedTask?.position = task.position + savedTask?.id = task.id + if (savedTask != null) { + savedTask.tags = task.tags + localRepository.save(savedTask) + } else { + unmanagedTask.hasErrored = true + unmanagedTask.isSaving = false + localRepository.saveSyncronous(unmanagedTask) + } + return savedTask } - override fun deleteTask(taskId: String): Flowable { - return apiClient.deleteTask(taskId) - .doOnNext { localRepository.deleteTask(taskId) } + override suspend fun deleteTask(taskId: String): Void? { + apiClient.deleteTask(taskId) ?: return null + localRepository.deleteTask(taskId) + return null } override fun saveTask(task: Task) { localRepository.save(task) } - override fun createTasks(newTasks: List): Flowable> = apiClient.createTasks(newTasks) + override suspend fun createTasks(newTasks: List) = apiClient.createTasks(newTasks) override fun markTaskCompleted(taskId: String, isCompleted: Boolean) { localRepository.markTaskCompleted(taskId, isCompleted) @@ -296,19 +286,24 @@ class TaskRepositoryImpl( localRepository.swapTaskPosition(firstPosition, secondPosition) } - override fun updateTaskPosition(taskType: TaskType, taskID: String, newPosition: Int): Maybe> { - return apiClient.postTaskNewPosition(taskID, newPosition).firstElement() - .doOnSuccess { localRepository.updateTaskPositions(it) } + override suspend fun updateTaskPosition(taskType: TaskType, taskID: String, newPosition: Int): List? { + val positions = apiClient.postTaskNewPosition(taskID, newPosition) ?: return null + localRepository.updateTaskPositions(positions) + return positions } override fun getUnmanagedTask(taskid: String) = getTask(taskid).map { localRepository.getUnmanagedCopy(it) } override fun updateTaskInBackground(task: Task) { - updateTask(task).subscribe({ }, ExceptionHandler.rx()) + MainScope().launchCatching { + updateTask(task) + } } override fun createTaskInBackground(task: Task) { - createTask(task).subscribe({ }, ExceptionHandler.rx()) + MainScope().launchCatching { + createTask(task) + } } override fun getTaskCopies(userId: String): Flow> = @@ -316,29 +311,27 @@ class TaskRepositoryImpl( override fun getTaskCopies(tasks: List): List = localRepository.getUnmanagedCopy(tasks) - override fun retrieveDailiesFromDate(date: Date): Flowable { + override suspend fun retrieveDailiesFromDate(date: Date): TaskList? { val formatter = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZZZZZ", Locale.US) return apiClient.getTasks("dailys", formatter.format(date)) } - override fun syncErroredTasks(): Single> { - return localRepository.getErroredTasks(userID).firstElement() - .flatMapPublisher { Flowable.fromIterable(it) } - .map { localRepository.getUnmanagedCopy(it) } - .flatMap { - return@flatMap if (it.isCreating) { - createTask(it, true) - } else { - updateTask(it, true).toFlowable() - } - }.toList() + override suspend fun syncErroredTasks(): List? { + val tasks = localRepository.getErroredTasks(userID).firstOrNull() + return tasks?.map { localRepository.getUnmanagedCopy(it) }?.mapNotNull { + if (it.isCreating) { + createTask(it, true) + } else { + updateTask(it, true) + } + } } - override fun unlinkAllTasks(challengeID: String?, keepOption: String): Flowable { + override suspend fun unlinkAllTasks(challengeID: String?, keepOption: String): Void? { return apiClient.unlinkAllTasks(challengeID, keepOption) } - override fun getTasksForChallenge(challengeID: String?): Flowable> { + override fun getTasksForChallenge(challengeID: String?): Flow> { return localRepository.getTasksForChallenge(challengeID, userID) } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/TutorialRepositoryImpl.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/TutorialRepositoryImpl.kt index faa1a0455..b6bd7e63f 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/TutorialRepositoryImpl.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/TutorialRepositoryImpl.kt @@ -4,7 +4,7 @@ import com.habitrpg.android.habitica.data.ApiClient import com.habitrpg.android.habitica.data.TutorialRepository import com.habitrpg.android.habitica.data.local.TutorialLocalRepository import com.habitrpg.android.habitica.models.TutorialStep -import io.reactivex.rxjava3.core.Flowable +import kotlinx.coroutines.flow.Flow class TutorialRepositoryImpl( localRepository: TutorialLocalRepository, @@ -12,9 +12,9 @@ class TutorialRepositoryImpl( userID: String ) : BaseRepositoryImpl(localRepository, apiClient, userID), TutorialRepository { - override fun getTutorialStep(key: String): Flowable = + override fun getTutorialStep(key: String): Flow = localRepository.getTutorialStep(key) - override fun getTutorialSteps(keys: List): Flowable> = + override fun getTutorialSteps(keys: List): Flow> = localRepository.getTutorialSteps(keys) } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/UserRepositoryImpl.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/UserRepositoryImpl.kt index 44a081976..dbc733a99 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/UserRepositoryImpl.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/UserRepositoryImpl.kt @@ -4,12 +4,9 @@ import com.habitrpg.android.habitica.data.ApiClient import com.habitrpg.android.habitica.data.TaskRepository import com.habitrpg.android.habitica.data.UserRepository import com.habitrpg.android.habitica.data.local.UserLocalRepository -import com.habitrpg.android.habitica.extensions.filterMapEmpty import com.habitrpg.android.habitica.helpers.AppConfigManager -import com.habitrpg.android.habitica.helpers.ExceptionHandler import com.habitrpg.android.habitica.models.Achievement import com.habitrpg.android.habitica.models.QuestAchievement -import com.habitrpg.android.habitica.models.Skill import com.habitrpg.android.habitica.models.TeamPlan import com.habitrpg.android.habitica.models.inventory.Customization import com.habitrpg.android.habitica.models.responses.SkillResponse @@ -21,17 +18,10 @@ import com.habitrpg.android.habitica.models.user.Stats import com.habitrpg.android.habitica.models.user.User import com.habitrpg.android.habitica.models.user.UserQuestStatus import com.habitrpg.android.habitica.proxy.AnalyticsManager -import com.habitrpg.common.habitica.extensions.Optional import com.habitrpg.shared.habitica.models.responses.TaskDirection -import com.habitrpg.shared.habitica.models.responses.VerifyUsernameResponse import com.habitrpg.shared.habitica.models.tasks.Attribute -import io.reactivex.rxjava3.core.Flowable -import io.reactivex.rxjava3.core.Maybe -import io.reactivex.rxjava3.functions.BiFunction -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.firstOrNull -import kotlinx.coroutines.withContext import java.util.Date import java.util.GregorianCalendar import java.util.concurrent.TimeUnit @@ -48,8 +38,6 @@ class UserRepositoryImpl( private var lastSync: Date? = null override fun getUser(): Flow = getUser(userID) - override fun getUserFlowable(): Flowable = localRepository.getUserFlowable(userID) - override fun getUser(userID: String): Flow = localRepository.getUser(userID) private suspend fun updateUser(userID: String, updateData: Map): User? { @@ -123,11 +111,9 @@ class UserRepositoryImpl( return user } - override fun getSkills(user: User): Flowable> = - localRepository.getSkills(user) + override fun getSkills(user: User) = localRepository.getSkills(user) - override fun getSpecialItems(user: User): Flowable> = - localRepository.getSpecialItems(user) + override fun getSpecialItems(user: User) = localRepository.getSpecialItems(user) override suspend fun useSkill(key: String, target: String?, taskId: String): SkillResponse? { val response = apiClient.useSkill(key, target ?: "", taskId) ?: return null @@ -177,8 +163,8 @@ class UserRepositoryImpl( runCron(ArrayList()) } - override fun readNotification(id: String): Flowable> = apiClient.readNotification(id) - override fun getUserQuestStatus(): Flowable { + override suspend fun readNotification(id: String) = apiClient.readNotification(id) + override fun getUserQuestStatus(): Flow { return localRepository.getUserQuestStatus(userID) } @@ -186,13 +172,11 @@ class UserRepositoryImpl( return apiClient.reroll() } - override fun readNotifications(notificationIds: Map>): Flowable> = - apiClient.readNotifications(notificationIds) + override suspend fun readNotifications(notificationIds: Map>) = apiClient.readNotifications(notificationIds) - override fun seeNotifications(notificationIds: Map>): Flowable> = - apiClient.seeNotifications(notificationIds) + override suspend fun seeNotifications(notificationIds: Map>) = apiClient.seeNotifications(notificationIds) - override fun changeCustomDayStart(dayStartTime: Int): Flowable { + override suspend fun changeCustomDayStart(dayStartTime: Int): User? { val updateObject = HashMap() updateObject["dayStart"] = dayStartTime return apiClient.changeCustomDayStart(updateObject) @@ -200,7 +184,7 @@ class UserRepositoryImpl( override suspend fun updateLanguage(languageCode: String): User? { val user = updateUser("preferences.language", languageCode) - apiClient.setLanguageCode(languageCode) + apiClient.languageCode = languageCode return user } @@ -209,11 +193,9 @@ class UserRepositoryImpl( return retrieveUser(withTasks = true, forced = true) } - override fun deleteAccount(password: String): Flowable = - apiClient.deleteAccount(password) + override suspend fun deleteAccount(password: String) = apiClient.deleteAccount(password) - override fun sendPasswordResetEmail(email: String): Flowable = - apiClient.sendPasswordResetEmail(email) + override suspend fun sendPasswordResetEmail(email: String) = apiClient.sendPasswordResetEmail(email) override suspend fun updateLoginName(newLoginName: String, password: String?): User? { if (password != null && password.isNotEmpty()) { @@ -229,35 +211,32 @@ class UserRepositoryImpl( return user } - override fun verifyUsername(username: String): Flowable = apiClient.verifyUsername(username.trim()) + override suspend fun verifyUsername(username: String) = apiClient.verifyUsername(username.trim()) - override fun updateEmail(newEmail: String, password: String): Flowable = - apiClient.updateEmail(newEmail.trim(), password) + override suspend fun updateEmail(newEmail: String, password: String) = apiClient.updateEmail(newEmail.trim(), password) - override fun updatePassword( + override suspend fun updatePassword( oldPassword: String, newPassword: String, newPasswordConfirmation: String - ): Flowable = - apiClient.updatePassword(oldPassword.trim(), newPassword.trim(), newPasswordConfirmation.trim()) + ) = apiClient.updatePassword(oldPassword.trim(), newPassword.trim(), newPasswordConfirmation.trim()) - override fun allocatePoint(stat: Attribute): Flowable { - getLiveUserFlowable().firstElement().subscribe( - { liveUser -> - localRepository.executeTransaction { - when (stat) { - Attribute.STRENGTH -> liveUser.stats?.strength = liveUser.stats?.strength?.inc() - Attribute.INTELLIGENCE -> liveUser.stats?.intelligence = liveUser.stats?.intelligence?.inc() - Attribute.CONSTITUTION -> liveUser.stats?.constitution = liveUser.stats?.constitution?.inc() - Attribute.PERCEPTION -> liveUser.stats?.per = liveUser.stats?.per?.inc() - } - liveUser.stats?.points = liveUser.stats?.points?.dec() + override suspend fun allocatePoint(stat: Attribute): Stats? { + val liveUser = getLiveUser() + if (liveUser != null) { + localRepository.executeTransaction { + when (stat) { + Attribute.STRENGTH -> liveUser.stats?.strength = liveUser.stats?.strength?.inc() + Attribute.INTELLIGENCE -> liveUser.stats?.intelligence = liveUser.stats?.intelligence?.inc() + Attribute.CONSTITUTION -> liveUser.stats?.constitution = liveUser.stats?.constitution?.inc() + Attribute.PERCEPTION -> liveUser.stats?.per = liveUser.stats?.per?.inc() } - }, - ExceptionHandler.rx() - ) - return zipWithLiveUser(apiClient.allocatePoint(stat.value)) { stats, user -> - localRepository.modify(user) { liveUser -> + liveUser.stats?.points = liveUser.stats?.points?.dec() + } + } + val stats = apiClient.allocatePoint(stat.value) ?: return null + if (liveUser != null) { + localRepository.executeTransaction { liveUser.stats?.strength = stats.strength liveUser.stats?.constitution = stats.constitution liveUser.stats?.per = stats.per @@ -265,17 +244,24 @@ class UserRepositoryImpl( liveUser.stats?.points = stats.points liveUser.stats?.mp = stats.mp } - stats } + return stats } - override fun bulkAllocatePoints( + override suspend fun bulkAllocatePoints( strength: Int, intelligence: Int, constitution: Int, perception: Int - ): Flowable = - zipWithLiveUser(apiClient.bulkAllocatePoints(strength, intelligence, constitution, perception)) { stats, user -> + ): Stats? { + val stats = apiClient.bulkAllocatePoints( + strength, + intelligence, + constitution, + perception + ) ?: return null + val user = getLiveUser() + if (user != null) { localRepository.modify(user) { liveUser -> liveUser.stats?.strength = stats.strength liveUser.stats?.constitution = stats.constitution @@ -284,63 +270,54 @@ class UserRepositoryImpl( liveUser.stats?.points = stats.points liveUser.stats?.mp = stats.mp } - stats } + return stats + } override suspend fun runCron(tasks: MutableList) { - withContext(Dispatchers.Main) { - var observable: Maybe = localRepository.getUserFlowable(userID).firstElement() - .filter { it.needsCron } - .map { user -> - localRepository.modify(user) { liveUser -> - liveUser.needsCron = false - liveUser.lastCron = Date() - } - user - } - if (tasks.isNotEmpty()) { - val scoringList = mutableListOf>() - for (task in tasks) { - val map = mutableMapOf() - map["id"] = task.id ?: "" - map["direction"] = TaskDirection.UP.text - scoringList.add(map) - } - observable = observable.flatMap { taskRepository.bulkScoreTasks(scoringList).firstElement() } + val user = getLiveUser() + if (user != null) { + localRepository.modify(user) { liveUser -> + liveUser.needsCron = false + liveUser.lastCron = Date() } - observable.flatMap { apiClient.runCron().firstElement() } - // .flatMap { - // this.retrieveUser(withTasks = true, forced = true) - // } - .subscribe({ }, ExceptionHandler.rx()) } + if (tasks.isNotEmpty()) { + val scoringList = mutableListOf>() + for (task in tasks) { + val map = mutableMapOf() + map["id"] = task.id ?: "" + map["direction"] = TaskDirection.UP.text + scoringList.add(map) + } + taskRepository.bulkScoreTasks(scoringList) + } + apiClient.runCron() } override suspend fun useCustomization(type: String, category: String?, identifier: String): User? { if (appConfigManager.enableLocalChanges()) { - localRepository.getUserFlowable(userID).firstElement().subscribe( - { liveUser -> - localRepository.modify(liveUser) { user -> - when (type) { - "skin" -> user.preferences?.skin = identifier - "shirt" -> user.preferences?.shirt = identifier - "hair" -> { - when (category) { - "color" -> user.preferences?.hair?.color = identifier - "flower" -> user.preferences?.hair?.flower = identifier.toInt() - "mustache" -> user.preferences?.hair?.mustache = identifier.toInt() - "beard" -> user.preferences?.hair?.beard = identifier.toInt() - "bangs" -> user.preferences?.hair?.bangs = identifier.toInt() - "base" -> user.preferences?.hair?.base = identifier.toInt() - } + val liveUser = getLiveUser() + if (liveUser != null) { + localRepository.modify(liveUser) { user -> + when (type) { + "skin" -> user.preferences?.skin = identifier + "shirt" -> user.preferences?.shirt = identifier + "hair" -> { + when (category) { + "color" -> user.preferences?.hair?.color = identifier + "flower" -> user.preferences?.hair?.flower = identifier.toInt() + "mustache" -> user.preferences?.hair?.mustache = identifier.toInt() + "beard" -> user.preferences?.hair?.beard = identifier.toInt() + "bangs" -> user.preferences?.hair?.bangs = identifier.toInt() + "base" -> user.preferences?.hair?.base = identifier.toInt() } - "background" -> user.preferences?.background = identifier - "chair" -> user.preferences?.chair = identifier } + "background" -> user.preferences?.background = identifier + "chair" -> user.preferences?.chair = identifier } - }, - ExceptionHandler.rx() - ) + } + } } var updatePath = "preferences.$type" if (category != null) { @@ -349,10 +326,10 @@ class UserRepositoryImpl( return updateUser(updatePath, identifier) } - override fun retrieveAchievements(): Flowable> { - return apiClient.getMemberAchievements(userID).doOnNext { - localRepository.save(it) - } + override suspend fun retrieveAchievements(): List? { + val achievements = apiClient.getMemberAchievements(userID) ?: return null + localRepository.save(achievements) + return achievements } override fun getAchievements(): Flow> { @@ -363,11 +340,11 @@ class UserRepositoryImpl( return localRepository.getQuestAchievements(userID) } - override fun retrieveTeamPlans(): Flowable> { - return apiClient.getTeamPlans().doOnNext { teams -> - teams.forEach { it.userID = userID } - localRepository.save(teams) - } + override suspend fun retrieveTeamPlans(): List? { + val teams = apiClient.getTeamPlans() ?: return null + teams.forEach { it.userID = userID } + localRepository.save(teams) + return teams } override fun getTeamPlans(): Flow> { @@ -389,25 +366,15 @@ class UserRepositoryImpl( return team } - override fun getTeamPlan(teamID: String): Flowable { + override fun getTeamPlan(teamID: String): Flow { return localRepository.getTeamPlan(teamID) } - private fun getLiveUserFlowable(): Flowable { - return localRepository.getUserFlowable(userID) - .map { Optional(localRepository.getLiveObject(it)) } - .filterMapEmpty() - } - private suspend fun getLiveUser(): User? { val user = localRepository.getUser(userID).firstOrNull() ?: return null return localRepository.getLiveObject(user) } - private fun zipWithLiveUser(flowable: Flowable, mergeFunc: BiFunction): Flowable { - return Flowable.zip(flowable, getLiveUserFlowable().firstElement().toFlowable(), mergeFunc) - } - private fun mergeUser(oldUser: User?, newUser: User): User { if (oldUser == null || !oldUser.isValid) { return oldUser ?: newUser diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/ChallengeLocalRepository.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/ChallengeLocalRepository.kt index a549cec18..c72ce4084 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/ChallengeLocalRepository.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/ChallengeLocalRepository.kt @@ -3,15 +3,15 @@ package com.habitrpg.android.habitica.data.local import com.habitrpg.android.habitica.models.social.Challenge import com.habitrpg.android.habitica.models.social.ChallengeMembership import com.habitrpg.android.habitica.models.tasks.Task -import io.reactivex.rxjava3.core.Flowable +import kotlinx.coroutines.flow.Flow interface ChallengeLocalRepository : BaseLocalRepository { - val challenges: Flowable> - fun getChallenge(id: String): Flowable - fun getTasks(challengeID: String): Flowable> + val challenges: Flow> + fun getChallenge(id: String): Flow + fun getTasks(challengeID: String): Flow> - fun getUserChallenges(userId: String): Flowable> + fun getUserChallenges(userId: String): Flow> fun setParticipating(userID: String, challengeID: String, isParticipating: Boolean) @@ -21,7 +21,7 @@ interface ChallengeLocalRepository : BaseLocalRepository { memberOnly: Boolean, userID: String ) - fun getChallengeMembership(userId: String, id: String): Flowable - fun getChallengeMemberships(userId: String): Flowable> - fun isChallengeMember(userID: String, challengeID: String): Flowable + fun getChallengeMembership(userId: String, id: String): Flow + fun getChallengeMemberships(userId: String): Flow> + fun isChallengeMember(userID: String, challengeID: String): Flow } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/ContentLocalRepository.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/ContentLocalRepository.kt index a3f601ab9..670a0dbce 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/ContentLocalRepository.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/ContentLocalRepository.kt @@ -3,9 +3,10 @@ package com.habitrpg.android.habitica.data.local import com.habitrpg.android.habitica.models.ContentResult import com.habitrpg.android.habitica.models.WorldState import io.reactivex.rxjava3.core.Flowable +import kotlinx.coroutines.flow.Flow interface ContentLocalRepository : BaseLocalRepository { fun saveContent(contentResult: ContentResult) fun saveWorldState(worldState: WorldState) - fun getWorldState(): Flowable + fun getWorldState(): Flow } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/CustomizationLocalRepository.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/CustomizationLocalRepository.kt index 2b21bae96..122aa7399 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/CustomizationLocalRepository.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/CustomizationLocalRepository.kt @@ -1,8 +1,8 @@ package com.habitrpg.android.habitica.data.local import com.habitrpg.android.habitica.models.inventory.Customization -import io.reactivex.rxjava3.core.Flowable +import kotlinx.coroutines.flow.Flow interface CustomizationLocalRepository : ContentLocalRepository { - fun getCustomizations(type: String, category: String?, onlyAvailable: Boolean): Flowable> + fun getCustomizations(type: String, category: String?, onlyAvailable: Boolean): Flow> } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/FAQLocalRepository.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/FAQLocalRepository.kt index 43bb04245..f7c2b04fb 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/FAQLocalRepository.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/FAQLocalRepository.kt @@ -1,10 +1,10 @@ package com.habitrpg.android.habitica.data.local import com.habitrpg.android.habitica.models.FAQArticle -import io.reactivex.rxjava3.core.Flowable +import kotlinx.coroutines.flow.Flow interface FAQLocalRepository : ContentLocalRepository { - fun getArticle(position: Int): Flowable + fun getArticle(position: Int): Flow - val articles: Flowable> + val articles: Flow> } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/InventoryLocalRepository.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/InventoryLocalRepository.kt index 895c57fe3..77cecd4d6 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/InventoryLocalRepository.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/InventoryLocalRepository.kt @@ -11,13 +11,12 @@ import com.habitrpg.android.habitica.models.user.OwnedItem import com.habitrpg.android.habitica.models.user.OwnedMount import com.habitrpg.android.habitica.models.user.OwnedPet import com.habitrpg.android.habitica.models.user.User -import io.reactivex.rxjava3.core.Flowable import kotlinx.coroutines.flow.Flow interface InventoryLocalRepository : ContentLocalRepository { fun getArmoireRemainingCount(): Long - fun getOwnedEquipment(): Flowable> + fun getOwnedEquipment(): Flow> fun getMounts(): Flow> @@ -27,31 +26,29 @@ interface InventoryLocalRepository : ContentLocalRepository { fun getOwnedPets(userID: String): Flow> - fun getInAppRewards(): Flowable> + fun getInAppRewards(): Flow> fun getQuestContent(key: String): Flow fun getQuestContent(keys: List): Flow> - fun getEquipment(searchedKeys: List): Flowable> + fun getEquipment(searchedKeys: List): Flow> - fun getOwnedEquipment(type: String): Flowable> + fun getOwnedEquipment(type: String): Flow> - fun getItemsFlowable(itemClass: Class, keys: Array): Flow> - fun getItemsFlowable(itemClass: Class): Flowable> fun getOwnedItems(itemType: String, userID: String, includeZero: Boolean): Flow> - fun getOwnedItems(userID: String, includeZero: Boolean): Flowable> - fun getEquipmentType(type: String, set: String): Flowable> + fun getOwnedItems(userID: String, includeZero: Boolean): Flow> + fun getEquipmentType(type: String, set: String): Flow> - fun getEquipment(key: String): Flowable + fun getEquipment(key: String): Flow fun getMounts(type: String?, group: String?, color: String?): Flow> fun getPets(type: String?, group: String?, color: String?): Flow> fun updateOwnedEquipment(user: User) - fun changeOwnedCount(type: String, key: String, userID: String, amountToAdd: Int) + suspend fun changeOwnedCount(type: String, key: String, userID: String, amountToAdd: Int) fun changeOwnedCount(item: OwnedItem, amountToAdd: Int?) - fun getItem(type: String, key: String): Flowable - fun getOwnedItem(userID: String, type: String, key: String, includeZero: Boolean): Flowable + fun getItem(type: String, key: String): Flow + fun getOwnedItem(userID: String, type: String, key: String, includeZero: Boolean): Flow fun decrementMysteryItemCount(user: User?) fun saveInAppRewards(onlineItems: List) @@ -59,12 +56,14 @@ interface InventoryLocalRepository : ContentLocalRepository { fun hatchPet(eggKey: String, potionKey: String, userID: String) fun unhatchPet(eggKey: String, potionKey: String, userID: String) fun feedPet(foodKey: String, petKey: String, feedValue: Int, userID: String) - fun getLatestMysteryItem(): Flowable + fun getLatestMysteryItem(): Flow fun soldItem(userID: String, updatedUser: User): User - fun getAvailableLimitedItems(): Flowable> + fun getAvailableLimitedItems(): Flow> fun save(items: Items, userID: String) fun getLiveObject(obj: OwnedItem): OwnedItem? fun getItems(itemClass: Class): Flow> + fun getItems(itemClass: Class, keys: Array): Flow> + } 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 b1f8dbdd5..c40981364 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 @@ -6,20 +6,19 @@ import com.habitrpg.android.habitica.models.social.Group import com.habitrpg.android.habitica.models.social.GroupMembership import com.habitrpg.android.habitica.models.social.InboxConversation import com.habitrpg.android.habitica.models.user.User -import io.reactivex.rxjava3.core.Flowable import io.realm.RealmResults import kotlinx.coroutines.flow.Flow interface SocialLocalRepository : BaseLocalRepository { - fun getPublicGuilds(): Flowable> + fun getPublicGuilds(): Flow> fun getUserGroups(userID: String, type: String?): Flow> - fun getGroups(type: String): Flowable> + fun getGroups(type: String): Flow> fun getGroup(id: String): Flow fun saveGroup(group: Group) - fun getGroupChat(groupId: String): Flowable> + fun getGroupChat(groupId: String): Flow> fun deleteMessage(id: String) @@ -41,7 +40,7 @@ interface SocialLocalRepository : BaseLocalRepository { fun doesGroupExist(id: String): Boolean fun updateMembership(userId: String, id: String, isMember: Boolean) fun getGroupMembership(userId: String, id: String): Flow - fun getGroupMemberships(userId: String): Flowable> + fun getGroupMemberships(userId: String): Flow> fun rejectGroupInvitation(userID: String, groupID: String) fun getInboxMessages(userId: String, replyToUserID: String?): Flow> diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/TagLocalRepository.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/TagLocalRepository.kt index fff81f926..b4a2cf921 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/TagLocalRepository.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/TagLocalRepository.kt @@ -2,9 +2,10 @@ package com.habitrpg.android.habitica.data.local import com.habitrpg.android.habitica.models.Tag import io.reactivex.rxjava3.core.Flowable +import kotlinx.coroutines.flow.Flow interface TagLocalRepository : BaseLocalRepository { - fun getTags(userId: String): Flowable> + fun getTags(userId: String): Flow> fun deleteTag(tagID: String) } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/TaskLocalRepository.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/TaskLocalRepository.kt index 6f8b80a60..e8a3a0f24 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/TaskLocalRepository.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/TaskLocalRepository.kt @@ -5,14 +5,12 @@ import com.habitrpg.android.habitica.models.tasks.TaskList import com.habitrpg.android.habitica.models.user.User import com.habitrpg.shared.habitica.models.tasks.TaskType import com.habitrpg.shared.habitica.models.tasks.TasksOrder -import io.reactivex.rxjava3.core.Flowable import io.reactivex.rxjava3.core.Maybe import kotlinx.coroutines.flow.Flow interface TaskLocalRepository : BaseLocalRepository { fun getTasks(taskType: TaskType, userID: String, includedGroupIDs: Array): Flow> - fun getTasksFlowable(taskType: TaskType, userID: String, includedGroupIDs: Array): Flowable> fun getTasks(userId: String): Flow> fun saveTasks(ownerID: String, tasksOrder: TasksOrder, tasks: TaskList) @@ -26,14 +24,13 @@ interface TaskLocalRepository : BaseLocalRepository { fun swapTaskPosition(firstPosition: Int, secondPosition: Int) - fun getTaskAtPosition(taskType: String, position: Int): Flowable + fun getTaskAtPosition(taskType: String, position: Int): Flow fun updateIsdue(daily: TaskList): Maybe fun updateTaskPositions(taskOrder: List) fun saveCompletedTodos(userId: String, tasks: MutableCollection) - fun getErroredTasks(userID: String): Flowable> - fun getUserFlowable(userID: String): Flowable + fun getErroredTasks(userID: String): Flow> fun getUser(userID: String): Flow - fun getTasksForChallenge(challengeID: String?, userID: String?): Flowable> + fun getTasksForChallenge(challengeID: String?, userID: String?): Flow> } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/TutorialLocalRepository.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/TutorialLocalRepository.kt index 455dc5d75..951986edc 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/TutorialLocalRepository.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/TutorialLocalRepository.kt @@ -1,10 +1,10 @@ package com.habitrpg.android.habitica.data.local import com.habitrpg.android.habitica.models.TutorialStep -import io.reactivex.rxjava3.core.Flowable +import kotlinx.coroutines.flow.Flow interface TutorialLocalRepository : BaseLocalRepository { - fun getTutorialStep(key: String): Flowable - fun getTutorialSteps(keys: List): Flowable> + fun getTutorialStep(key: String): Flow + fun getTutorialSteps(keys: List): Flow> } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/UserLocalRepository.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/UserLocalRepository.kt index 04dbb8e90..49ef8bf35 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/UserLocalRepository.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/UserLocalRepository.kt @@ -9,7 +9,6 @@ import com.habitrpg.android.habitica.models.social.ChatMessage import com.habitrpg.android.habitica.models.social.Group import com.habitrpg.android.habitica.models.user.User import com.habitrpg.android.habitica.models.user.UserQuestStatus -import io.reactivex.rxjava3.core.Flowable import io.realm.RealmResults import kotlinx.coroutines.flow.Flow @@ -18,18 +17,16 @@ interface UserLocalRepository : BaseLocalRepository { suspend fun getTutorialSteps(): Flow> fun getUser(userID: String): Flow - fun getUserFlowable(userID: String): Flowable - fun saveUser(user: User, overrideExisting: Boolean = true) fun saveMessages(messages: List) - fun getSkills(user: User): Flowable> + fun getSkills(user: User): Flow> - fun getSpecialItems(user: User): Flowable> + fun getSpecialItems(user: User): Flow> fun getAchievements(): Flow> fun getQuestAchievements(userID: String): Flow> - fun getUserQuestStatus(userID: String): Flowable + fun getUserQuestStatus(userID: String): Flow fun getTeamPlans(userID: String): Flow> - fun getTeamPlan(teamID: String): Flowable + fun getTeamPlan(teamID: String): Flow } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/implementation/RealmChallengeLocalRepository.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/implementation/RealmChallengeLocalRepository.kt index 9dfb48d1b..393ab5d4b 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/implementation/RealmChallengeLocalRepository.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/implementation/RealmChallengeLocalRepository.kt @@ -1,84 +1,79 @@ package com.habitrpg.android.habitica.data.local.implementation import com.habitrpg.android.habitica.data.local.ChallengeLocalRepository -import com.habitrpg.android.habitica.extensions.filterMap import com.habitrpg.android.habitica.models.social.Challenge import com.habitrpg.android.habitica.models.social.ChallengeMembership import com.habitrpg.android.habitica.models.tasks.Task import com.habitrpg.android.habitica.models.user.User -import hu.akarnokd.rxjava3.bridge.RxJavaBridge -import io.reactivex.rxjava3.core.Flowable import io.realm.Realm import io.realm.Sort +import io.realm.kotlin.toFlow +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.filterNotNull +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.map class RealmChallengeLocalRepository(realm: Realm) : RealmBaseLocalRepository(realm), ChallengeLocalRepository { - override fun isChallengeMember(userID: String, challengeID: String): Flowable = RxJavaBridge.toV3Flowable( - realm.where(ChallengeMembership::class.java) + override fun isChallengeMember(userID: String, challengeID: String): Flow = realm.where(ChallengeMembership::class.java) .equalTo("userID", userID) .equalTo("challengeID", challengeID) .findAll() - .asFlowable() + .toFlow() .filter { it.isLoaded } - ).map { it.count() > 0 } + .map { it.count() > 0 } - override fun getChallengeMembership(userId: String, id: String): Flowable = RxJavaBridge.toV3Flowable( - realm.where(ChallengeMembership::class.java) + override fun getChallengeMembership(userId: String, id: String) = realm.where(ChallengeMembership::class.java) .equalTo("userID", userId) .equalTo("challengeID", id) .findAll() - .asFlowable() + .toFlow() .filter { it.isLoaded } - ).filterMap { it.first() } + .map { it.first() } + .filterNotNull() - override fun getChallengeMemberships(userId: String): Flowable> = RxJavaBridge.toV3Flowable( - realm.where(ChallengeMembership::class.java) + override fun getChallengeMemberships(userId: String) = realm.where(ChallengeMembership::class.java) .equalTo("userID", userId) .findAll() - .asFlowable() + .toFlow() .filter { it.isLoaded } - ) - override fun getChallenge(id: String): Flowable { - return RxJavaBridge.toV3Flowable( - realm.where(Challenge::class.java) + override fun getChallenge(id: String): Flow { + return realm.where(Challenge::class.java) .equalTo("id", id) .findAll() - .asFlowable() + .toFlow() .filter { realmObject -> realmObject.isLoaded && realmObject.isNotEmpty() } .map { it.first() } - ) + .filterNotNull() } - override fun getTasks(challengeID: String): Flowable> { - return RxJavaBridge.toV3Flowable( - realm.where(Task::class.java) + override fun getTasks(challengeID: String): Flow> { + return realm.where(Task::class.java) .equalTo("userId", challengeID) .findAll() - .asFlowable() + .toFlow() .filter { realmObject -> realmObject.isLoaded } - ) } - override val challenges: Flowable> - get() = RxJavaBridge.toV3Flowable( - realm.where(Challenge::class.java) + override val challenges: Flow> + get() = realm.where(Challenge::class.java) .isNotNull("name") .sort("official", Sort.DESCENDING, "createdAt", Sort.DESCENDING) .findAll() - .asFlowable() + .toFlow() .filter { it.isLoaded } - ) - override fun getUserChallenges(userId: String): Flowable> { - return RxJavaBridge.toV3Flowable( - realm.where(ChallengeMembership::class.java) + @OptIn(ExperimentalCoroutinesApi::class) + override fun getUserChallenges(userId: String): Flow> { + return realm.where(ChallengeMembership::class.java) .equalTo("userID", userId) .findAll() - .asFlowable() + .toFlow() .filter { it.isLoaded } - ) - .flatMap { it -> + .flatMapLatest { it -> val ids = it.map { return@map it.challengeID }.toTypedArray() @@ -91,7 +86,7 @@ class RealmChallengeLocalRepository(realm: Realm) : RealmBaseLocalRepository(rea .endGroup() .sort("official", Sort.DESCENDING, "createdAt", Sort.DESCENDING) .findAll() - .asFlowable() + .toFlow() .filter { it.isLoaded } } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/implementation/RealmContentLocalRepository.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/implementation/RealmContentLocalRepository.kt index 9445ba11f..eddc37ca9 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/implementation/RealmContentLocalRepository.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/implementation/RealmContentLocalRepository.kt @@ -5,9 +5,12 @@ import com.habitrpg.android.habitica.models.ContentResult import com.habitrpg.android.habitica.models.WorldState import com.habitrpg.android.habitica.models.inventory.Quest import com.habitrpg.android.habitica.models.social.Group -import hu.akarnokd.rxjava3.bridge.RxJavaBridge -import io.reactivex.rxjava3.core.Flowable import io.realm.Realm +import io.realm.kotlin.toFlow +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.filterNotNull +import kotlinx.coroutines.flow.map open class RealmContentLocalRepository(realm: Realm) : RealmBaseLocalRepository(realm), ContentLocalRepository { @@ -34,14 +37,13 @@ open class RealmContentLocalRepository(realm: Realm) : RealmBaseLocalRepository( } } - override fun getWorldState(): Flowable { - return RxJavaBridge.toV3Flowable( - realm.where(WorldState::class.java) + override fun getWorldState(): Flow { + return realm.where(WorldState::class.java) .findAll() - .asFlowable() + .toFlow() .filter { it.isLoaded && it.size > 0 } .map { it.first() } - ) + .filterNotNull() } override fun saveWorldState(worldState: WorldState) { diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/implementation/RealmCustomizationLocalRepository.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/implementation/RealmCustomizationLocalRepository.kt index c67d3d5a7..f7e564370 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/implementation/RealmCustomizationLocalRepository.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/implementation/RealmCustomizationLocalRepository.kt @@ -2,14 +2,15 @@ package com.habitrpg.android.habitica.data.local.implementation import com.habitrpg.android.habitica.data.local.CustomizationLocalRepository import com.habitrpg.android.habitica.models.inventory.Customization -import hu.akarnokd.rxjava3.bridge.RxJavaBridge -import io.reactivex.rxjava3.core.Flowable import io.realm.Realm +import io.realm.kotlin.toFlow +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.filter import java.util.Date class RealmCustomizationLocalRepository(realm: Realm) : RealmContentLocalRepository(realm), CustomizationLocalRepository { - override fun getCustomizations(type: String, category: String?, onlyAvailable: Boolean): Flowable> { + override fun getCustomizations(type: String, category: String?, onlyAvailable: Boolean): Flow> { var query = realm.where(Customization::class.java) .equalTo("type", type) .equalTo("category", category) @@ -28,13 +29,10 @@ class RealmCustomizationLocalRepository(realm: Realm) : RealmContentLocalReposit .endGroup() .endGroup() } - return RxJavaBridge.toV3Flowable( - query + return query .sort("customizationSet") .findAll() - .asFlowable() + .toFlow() .filter { it.isLoaded } - .map { it } - ) } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/implementation/RealmFAQLocalRepository.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/implementation/RealmFAQLocalRepository.kt index 5a704138f..84be05ed2 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/implementation/RealmFAQLocalRepository.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/implementation/RealmFAQLocalRepository.kt @@ -2,27 +2,27 @@ package com.habitrpg.android.habitica.data.local.implementation import com.habitrpg.android.habitica.data.local.FAQLocalRepository import com.habitrpg.android.habitica.models.FAQArticle -import hu.akarnokd.rxjava3.bridge.RxJavaBridge -import io.reactivex.rxjava3.core.Flowable import io.realm.Realm +import io.realm.kotlin.toFlow +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.filterNotNull +import kotlinx.coroutines.flow.map class RealmFAQLocalRepository(realm: Realm) : RealmContentLocalRepository(realm), FAQLocalRepository { - override fun getArticle(position: Int): Flowable { - return RxJavaBridge.toV3Flowable( - realm.where(FAQArticle::class.java) + override fun getArticle(position: Int): Flow { + return realm.where(FAQArticle::class.java) .equalTo("position", position) .findAll() - .asFlowable() + .toFlow() .filter { it.isLoaded && it.count() > 0 } - .map { it.first() } - ) + .map { it.firstOrNull() } + .filterNotNull() } - override val articles: Flowable> - get() = RxJavaBridge.toV3Flowable( - realm.where(FAQArticle::class.java) + override val articles: Flow> + get() = realm.where(FAQArticle::class.java) .findAll() - .asFlowable() + .toFlow() .filter { it.isLoaded } - ) } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/implementation/RealmInventoryLocalRepository.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/implementation/RealmInventoryLocalRepository.kt index 7d1159c6f..c3ebc3af8 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/implementation/RealmInventoryLocalRepository.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/implementation/RealmInventoryLocalRepository.kt @@ -1,7 +1,6 @@ package com.habitrpg.android.habitica.data.local.implementation import com.habitrpg.android.habitica.data.local.InventoryLocalRepository -import com.habitrpg.android.habitica.helpers.ExceptionHandler import com.habitrpg.android.habitica.models.inventory.Egg import com.habitrpg.android.habitica.models.inventory.Equipment import com.habitrpg.android.habitica.models.inventory.Food @@ -17,44 +16,44 @@ import com.habitrpg.android.habitica.models.user.OwnedItem import com.habitrpg.android.habitica.models.user.OwnedMount import com.habitrpg.android.habitica.models.user.OwnedPet import com.habitrpg.android.habitica.models.user.User -import hu.akarnokd.rxjava3.bridge.RxJavaBridge -import io.reactivex.rxjava3.core.Flowable import io.realm.Realm import io.realm.RealmObject import io.realm.Sort import io.realm.kotlin.toFlow import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.filterNotNull +import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.map import java.text.SimpleDateFormat import java.util.Date import java.util.Locale -class RealmInventoryLocalRepository(realm: Realm) : RealmContentLocalRepository(realm), InventoryLocalRepository { +class RealmInventoryLocalRepository(realm: Realm) : RealmContentLocalRepository(realm), + InventoryLocalRepository { override fun getQuestContent(keys: List): Flow> { return realm.where(QuestContent::class.java) - .`in`("key", keys.toTypedArray()) - .findAll() - .toFlow() - .filter { it.isLoaded } + .`in`("key", keys.toTypedArray()) + .findAll() + .toFlow() + .filter { it.isLoaded } } override fun getQuestContent(key: String): Flow { return realm.where(QuestContent::class.java).equalTo("key", key) - .findAll() - .toFlow() - .filter { content -> content.isLoaded && content.isValid && !content.isEmpty() } - .map { content -> content.first() } + .findAll() + .toFlow() + .filter { content -> content.isLoaded && content.isValid && !content.isEmpty() } + .map { content -> content.first() } } - override fun getEquipment(searchedKeys: List): Flowable> { - return RxJavaBridge.toV3Flowable( - realm.where(Equipment::class.java) - .`in`("key", searchedKeys.toTypedArray()) - .findAll() - .asFlowable() - .filter { it.isLoaded } - ) + override fun getEquipment(searchedKeys: List): Flow> { + return realm.where(Equipment::class.java) + .`in`("key", searchedKeys.toTypedArray()) + .findAll() + .toFlow() + .filter { it.isLoaded } } override fun getArmoireRemainingCount(): Long { @@ -68,39 +67,37 @@ class RealmInventoryLocalRepository(realm: Realm) : RealmContentLocalRepository( .count() } - override fun getOwnedEquipment(type: String): Flowable> { - return RxJavaBridge.toV3Flowable( - realm.where(Equipment::class.java) - .equalTo("type", type) - .equalTo("owned", true) - .findAll() - .asFlowable() - .filter { it.isLoaded } - ) + override fun getOwnedEquipment(type: String): Flow> { + return realm.where(Equipment::class.java) + .equalTo("type", type) + .equalTo("owned", true) + .findAll() + .toFlow() + .filter { it.isLoaded } } - override fun getOwnedEquipment(): Flowable> { - return RxJavaBridge.toV3Flowable( - realm.where(Equipment::class.java) - .equalTo("owned", true) - .findAll() - .asFlowable() - .filter { it.isLoaded } - ) + override fun getOwnedEquipment(): Flow> { + return realm.where(Equipment::class.java) + .equalTo("owned", true) + .findAll() + .toFlow() + .filter { it.isLoaded } } - override fun getEquipmentType(type: String, set: String): Flowable> { - return RxJavaBridge.toV3Flowable( - realm.where(Equipment::class.java) - .equalTo("type", type) - .equalTo("gearSet", set) - .findAll() - .asFlowable() - .filter { it.isLoaded } - ) + override fun getEquipmentType(type: String, set: String): Flow> { + return realm.where(Equipment::class.java) + .equalTo("type", type) + .equalTo("gearSet", set) + .findAll() + .toFlow() + .filter { it.isLoaded } } - override fun getOwnedItems(itemType: String, userID: String, includeZero: Boolean): Flow> { + override fun getOwnedItems( + itemType: String, + userID: String, + includeZero: Boolean + ): Flow> { return queryUser(userID).map { val items = when (itemType) { "eggs" -> it?.items?.eggs @@ -118,56 +115,49 @@ class RealmInventoryLocalRepository(realm: Realm) : RealmContentLocalRepository( } } - override fun getItemsFlowable(itemClass: Class, keys: Array): Flow> { - return realm.where(itemClass).`in`("key", keys).findAll().toFlow() - .filter { it.isLoaded } - } - - override fun getItemsFlowable(itemClass: Class): Flowable> { - return RxJavaBridge.toV3Flowable( - realm.where(itemClass).findAll().asFlowable() - .filter { it.isLoaded } - ) - } - override fun getItems(itemClass: Class): Flow> { return realm.where(itemClass).findAll().toFlow() .filter { it.isLoaded } } - override fun getOwnedItems(userID: String, includeZero: Boolean): Flowable> { - return queryUserFlowable(userID).map { - val items = HashMap() - it.items?.eggs?.forEach { items[it.key + "-" + it.itemType] = it } - it.items?.food?.forEach { items[it.key + "-" + it.itemType] = it } - it.items?.hatchingPotions?.forEach { items[it.key + "-" + it.itemType] = it } - it.items?.quests?.forEach { items[it.key + "-" + it.itemType] = it } - if (includeZero) { - items - } else { - items.filter { it.value.numberOwned > 0 } - } - } + override fun getItems(itemClass: Class, keys: Array): Flow> { + return realm.where(itemClass).`in`("key", keys).findAll().toFlow() + .filter { it.isLoaded } } - override fun getEquipment(key: String): Flowable { - return RxJavaBridge.toV3Flowable( - realm.where(Equipment::class.java) - .equalTo("key", key) - .findAll() - .asFlowable() - .filter { realmObject -> realmObject.isLoaded && realmObject.isNotEmpty() } - .map { it.first() } - .cast(Equipment::class.java) - ) + override fun getOwnedItems(userID: String, includeZero: Boolean): Flow> { + return queryUser(userID) + .filterNotNull() + .map { + val items = HashMap() + it.items?.eggs?.forEach { items[it.key + "-" + it.itemType] = it } + it.items?.food?.forEach { items[it.key + "-" + it.itemType] = it } + it.items?.hatchingPotions?.forEach { items[it.key + "-" + it.itemType] = it } + it.items?.quests?.forEach { items[it.key + "-" + it.itemType] = it } + if (includeZero) { + items + } else { + items.filter { it.value.numberOwned > 0 } + } + } + } + + override fun getEquipment(key: String): Flow { + return realm.where(Equipment::class.java) + .equalTo("key", key) + .findAll() + .toFlow() + .filter { realmObject -> realmObject.isLoaded && realmObject.isNotEmpty() } + .map { it.first() } + .filterNotNull() } override fun getMounts(): Flow> { return realm.where(Mount::class.java) - .sort("type", Sort.ASCENDING, "animal", Sort.ASCENDING) - .findAll() - .toFlow() - .filter { it.isLoaded } + .sort("type", Sort.ASCENDING, "animal", Sort.ASCENDING) + .findAll() + .toFlow() + .filter { it.isLoaded } } override fun getMounts(type: String?, group: String?, color: String?): Flow> { @@ -183,8 +173,8 @@ class RealmInventoryLocalRepository(realm: Realm) : RealmContentLocalRepository( query = query.equalTo("color", color) } return query.findAll() - .toFlow() - .filter { it.isLoaded } + .toFlow() + .filter { it.isLoaded } } override fun getOwnedMounts(userID: String): Flow> { @@ -198,10 +188,10 @@ class RealmInventoryLocalRepository(realm: Realm) : RealmContentLocalRepository( override fun getPets(): Flow> { return realm.where(Pet::class.java) - .sort("type", Sort.ASCENDING, "animal", Sort.ASCENDING) - .findAll() - .toFlow() - .filter { it.isLoaded } + .sort("type", Sort.ASCENDING, "animal", Sort.ASCENDING) + .findAll() + .toFlow() + .filter { it.isLoaded } } override fun getPets(type: String?, group: String?, color: String?): Flow> { @@ -217,15 +207,15 @@ class RealmInventoryLocalRepository(realm: Realm) : RealmContentLocalRepository( query = query.equalTo("color", color) } return query.findAll() - .toFlow() - .filter { it.isLoaded } + .toFlow() + .filter { it.isLoaded } } override fun getOwnedPets(userID: String): Flow> { return realm.where(User::class.java) - .equalTo("id", userID) - .findAll() - .toFlow() + .equalTo("id", userID) + .findAll() + .toFlow() .filter { it.isLoaded && it.isValid && !it.isEmpty() } .map { it.first()?.items?.pets?.filter { @@ -237,8 +227,16 @@ class RealmInventoryLocalRepository(realm: Realm) : RealmContentLocalRepository( override fun updateOwnedEquipment(user: User) { } - override fun changeOwnedCount(type: String, key: String, userID: String, amountToAdd: Int) { - getOwnedItem(userID, type, key, true).firstElement().subscribe({ changeOwnedCount(it, amountToAdd) }, ExceptionHandler.rx()) + override suspend fun changeOwnedCount( + type: String, + key: String, + userID: String, + amountToAdd: Int + ) { + val item = getOwnedItem(userID, type, key, true).firstOrNull() + if (item != null) { + changeOwnedCount(item, amountToAdd) + } } override fun changeOwnedCount(item: OwnedItem, amountToAdd: Int?) { @@ -248,29 +246,36 @@ class RealmInventoryLocalRepository(realm: Realm) : RealmContentLocalRepository( } } - override fun getOwnedItem(userID: String, type: String, key: String, includeZero: Boolean): Flowable { - return queryUserFlowable(userID).map { - var items = ( - when (type) { - "eggs" -> it.items?.eggs - "hatchingPotions" -> it.items?.hatchingPotions - "food" -> it.items?.food - "quests" -> it.items?.quests - else -> emptyList() - } ?: emptyList() - ) - items = items.filter { it.key == key } - if (includeZero) { - items - } else { - items.filter { it.numberOwned > 0 } + override fun getOwnedItem( + userID: String, + type: String, + key: String, + includeZero: Boolean + ): Flow { + return queryUser(userID) + .filterNotNull() + .map { + var items = ( + when (type) { + "eggs" -> it.items?.eggs + "hatchingPotions" -> it.items?.hatchingPotions + "food" -> it.items?.food + "quests" -> it.items?.quests + else -> emptyList() + } ?: emptyList() + ) + items = items.filter { it.key == key } + if (includeZero) { + items + } else { + items.filter { it.numberOwned > 0 } + } } - } .filter { it.isNotEmpty() } .map { it.first() } } - override fun getItem(type: String, key: String): Flowable { + override fun getItem(type: String, key: String): Flow { val itemClass: Class = when (type) { "eggs" -> Egg::class.java "hatchingPotions" -> HatchingPotion::class.java @@ -279,14 +284,12 @@ class RealmInventoryLocalRepository(realm: Realm) : RealmContentLocalRepository( "special" -> SpecialItem::class.java else -> Egg::class.java } - return RxJavaBridge.toV3Flowable( - realm.where(itemClass).equalTo("key", key) - .findAll() - .asFlowable() - .filter { realmObject -> realmObject.isLoaded && realmObject.isNotEmpty() } - .map { it.first() } - .cast(Item::class.java) - ) + return realm.where(itemClass).equalTo("key", key) + .findAll() + .toFlow() + .filter { realmObject -> realmObject.isLoaded && realmObject.isNotEmpty() } + .map { it.firstOrNull() as? Item } + .filterNotNull() } override fun decrementMysteryItemCount(user: User?) { @@ -301,18 +304,17 @@ class RealmInventoryLocalRepository(realm: Realm) : RealmContentLocalRepository( item.numberOwned = item.numberOwned - 1 } if (liveUser?.isValid == true) { - liveUser.purchased?.plan?.mysteryItemCount = (user.purchased?.plan?.mysteryItemCount ?: 0) - 1 + liveUser.purchased?.plan?.mysteryItemCount = + (user.purchased?.plan?.mysteryItemCount ?: 0) - 1 } } } - override fun getInAppRewards(): Flowable> { - return RxJavaBridge.toV3Flowable( - realm.where(ShopItem::class.java) - .findAll() - .asFlowable() - .filter { it.isLoaded } - ) + override fun getInAppRewards(): Flow> { + return realm.where(ShopItem::class.java) + .findAll() + .toFlow() + .filter { it.isLoaded } } override fun saveInAppRewards(onlineItems: List) { @@ -333,7 +335,8 @@ class RealmInventoryLocalRepository(realm: Realm) : RealmContentLocalRepository( newPet.trained = 5 val user = realm.where(User::class.java).equalTo("id", userID).findFirst() ?: return val egg = user.items?.eggs?.firstOrNull { it.key == eggKey } ?: return - val hatchingPotion = user.items?.hatchingPotions?.firstOrNull { it.key == potionKey } ?: return + val hatchingPotion = + user.items?.hatchingPotions?.firstOrNull { it.key == potionKey } ?: return executeTransaction { egg.numberOwned -= 1 hatchingPotion.numberOwned -= 1 @@ -344,7 +347,8 @@ class RealmInventoryLocalRepository(realm: Realm) : RealmContentLocalRepository( override fun getLiveObject(obj: OwnedItem): OwnedItem? { if (isClosed) return null if (!obj.isManaged) return obj - return realm.where(OwnedItem::class.java).equalTo("key", obj.key).equalTo("itemType", obj.itemType).findFirst() + return realm.where(OwnedItem::class.java).equalTo("key", obj.key) + .equalTo("itemType", obj.itemType).findFirst() } override fun save(items: Items, userID: String) { @@ -359,7 +363,8 @@ class RealmInventoryLocalRepository(realm: Realm) : RealmContentLocalRepository( val pet = realm.where(OwnedPet::class.java).equalTo("key", "$eggKey-$potionKey").findFirst() val user = realm.where(User::class.java).equalTo("id", userID).findFirst() ?: return val egg = user.items?.eggs?.firstOrNull { it.key == eggKey } ?: return - val hatchingPotion = user.items?.hatchingPotions?.firstOrNull { it.key == potionKey } ?: return + val hatchingPotion = + user.items?.hatchingPotions?.firstOrNull { it.key == potionKey } ?: return executeTransaction { egg.numberOwned += 1 hatchingPotion.numberOwned += 1 @@ -384,21 +389,19 @@ class RealmInventoryLocalRepository(realm: Realm) : RealmContentLocalRepository( } } - override fun getLatestMysteryItem(): Flowable { - return RxJavaBridge.toV3Flowable( - realm.where(Equipment::class.java) - .contains("key", "mystery_2") - .sort("mystery", Sort.DESCENDING) - .findAll() - .asFlowable() - .filter { it.isLoaded && it.size > 0 } - .map { - val format = SimpleDateFormat("yyyyMM", Locale.US) - it.first { - it.key?.contains(format.format(Date())) == true - } + override fun getLatestMysteryItem(): Flow { + return realm.where(Equipment::class.java) + .contains("key", "mystery_2") + .sort("mystery", Sort.DESCENDING) + .findAll() + .toFlow() + .filter { it.isLoaded && it.size > 0 } + .map { + val format = SimpleDateFormat("yyyyMM", Locale.US) + it.first { + it.key?.contains(format.format(Date())) == true } - ) + } } override fun soldItem(userID: String, updatedUser: User): User { @@ -418,32 +421,42 @@ class RealmInventoryLocalRepository(realm: Realm) : RealmContentLocalRepository( return user } - override fun getAvailableLimitedItems(): Flowable> { - return Flowable.combineLatest( - realm.where(Egg::class.java) - .lessThan("event.start", Date()) - .greaterThan("event.end", Date()) - .findAll().asFlowable(), - realm.where(Food::class.java) - .lessThan("event.start", Date()) - .greaterThan("event.end", Date()) - .findAll().asFlowable(), - realm.where(HatchingPotion::class.java) - .lessThan("event.start", Date()) - .greaterThan("event.end", Date()) - .findAll().asFlowable(), - realm.where(QuestContent::class.java) - .lessThan("event.start", Date()) - .greaterThan("event.end", Date()) - .findAll().asFlowable(), - { eggs, food, potions, quests -> + override fun getAvailableLimitedItems(): Flow> { + return realm.where(Egg::class.java) + .lessThan("event.start", Date()) + .greaterThan("event.end", Date()) + .findAll().toFlow() + .map { val items = mutableListOf() - items.addAll(eggs) - items.addAll(food) - items.addAll(potions) - items.addAll(quests) + items.addAll(it) + items + } + .combine( + realm.where(Food::class.java) + .lessThan("event.start", Date()) + .greaterThan("event.end", Date()) + .findAll().toFlow() + ) { items, food -> + items.addAll(food) + items + } + .combine( + realm.where(HatchingPotion::class.java) + .lessThan("event.start", Date()) + .greaterThan("event.end", Date()) + .findAll().toFlow() + ) { items, food -> + items.addAll(food) + items + } + .combine( + realm.where(QuestContent::class.java) + .lessThan("event.start", Date()) + .greaterThan("event.end", Date()) + .findAll().toFlow() + ) { items, food -> + items.addAll(food) items } - ) } } 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 eb1f5dec9..c63e9094a 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 @@ -9,8 +9,6 @@ import com.habitrpg.android.habitica.models.social.Group import com.habitrpg.android.habitica.models.social.GroupMembership import com.habitrpg.android.habitica.models.social.InboxConversation import com.habitrpg.android.habitica.models.user.User -import hu.akarnokd.rxjava3.bridge.RxJavaBridge -import io.reactivex.rxjava3.core.Flowable import io.realm.Realm import io.realm.Sort import io.realm.kotlin.toFlow @@ -30,13 +28,11 @@ class RealmSocialLocalRepository(realm: Realm) : RealmBaseLocalRepository(realm) .filter { it.isLoaded && it.isNotEmpty() } .map { it.first() } - override fun getGroupMemberships(userId: String): Flowable> = RxJavaBridge.toV3Flowable( - realm.where(GroupMembership::class.java) + override fun getGroupMemberships(userId: String): Flow> = realm.where(GroupMembership::class.java) .equalTo("userID", userId) .findAll() - .asFlowable() + .toFlow() .filter { it.isLoaded } - ) override fun updateMembership(userId: String, id: String, isMember: Boolean) { if (isMember) { @@ -123,16 +119,14 @@ class RealmSocialLocalRepository(realm: Realm) : RealmBaseLocalRepository(realm) } } - override fun getPublicGuilds(): Flowable> = RxJavaBridge.toV3Flowable( - realm.where(Group::class.java) + override fun getPublicGuilds() = realm.where(Group::class.java) .equalTo("type", "guild") .equalTo("privacy", "public") .notEqualTo("id", Group.TAVERN_ID) .sort("memberCount", Sort.DESCENDING) .findAll() - .asFlowable() + .toFlow() .filter { it.isLoaded } - ) @OptIn(ExperimentalCoroutinesApi::class) override fun getUserGroups(userID: String, type: String?) = realm.where(GroupMembership::class.java) @@ -155,14 +149,12 @@ class RealmSocialLocalRepository(realm: Realm) : RealmBaseLocalRepository(realm) .toFlow() } - override fun getGroups(type: String): Flowable> { - return RxJavaBridge.toV3Flowable( - realm.where(Group::class.java) + override fun getGroups(type: String): Flow> { + return realm.where(Group::class.java) .equalTo("type", type) .findAll() - .asFlowable() + .toFlow() .filter { it.isLoaded } - ) } override fun getGroup(id: String): Flow { @@ -174,15 +166,13 @@ class RealmSocialLocalRepository(realm: Realm) : RealmBaseLocalRepository(realm) .map { groups -> groups.first() } } - override fun getGroupChat(groupId: String): Flowable> { - return RxJavaBridge.toV3Flowable( - realm.where(ChatMessage::class.java) + override fun getGroupChat(groupId: String): Flow> { + return realm.where(ChatMessage::class.java) .equalTo("groupId", groupId) .sort("timestamp", Sort.DESCENDING) .findAll() - .asFlowable() + .toFlow() .filter { it.isLoaded } - ) } override fun deleteMessage(id: String) { diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/implementation/RealmTagLocalRepository.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/implementation/RealmTagLocalRepository.kt index 8f1e93c8c..99d26a19c 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/implementation/RealmTagLocalRepository.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/implementation/RealmTagLocalRepository.kt @@ -2,9 +2,9 @@ package com.habitrpg.android.habitica.data.local.implementation import com.habitrpg.android.habitica.data.local.TagLocalRepository import com.habitrpg.android.habitica.models.Tag -import hu.akarnokd.rxjava3.bridge.RxJavaBridge -import io.reactivex.rxjava3.core.Flowable import io.realm.Realm +import io.realm.kotlin.toFlow +import kotlinx.coroutines.flow.Flow class RealmTagLocalRepository(realm: Realm) : RealmBaseLocalRepository(realm), TagLocalRepository { override fun deleteTag(tagID: String) { @@ -12,9 +12,7 @@ class RealmTagLocalRepository(realm: Realm) : RealmBaseLocalRepository(realm), T executeTransaction { tags.deleteAllFromRealm() } } - override fun getTags(userId: String): Flowable> { - return RxJavaBridge.toV3Flowable( - realm.where(Tag::class.java).equalTo("userId", userId).findAll().asFlowable() - ) + override fun getTags(userId: String): Flow> { + return realm.where(Tag::class.java).equalTo("userId", userId).findAll().toFlow() } } 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 6d97a4e61..51ff44e96 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 @@ -8,7 +8,6 @@ import com.habitrpg.android.habitica.models.tasks.TaskList import com.habitrpg.android.habitica.models.user.User import com.habitrpg.shared.habitica.models.tasks.TaskType import com.habitrpg.shared.habitica.models.tasks.TasksOrder -import hu.akarnokd.rxjava3.bridge.RxJavaBridge import io.reactivex.rxjava3.core.Flowable import io.reactivex.rxjava3.core.Maybe import io.realm.Realm @@ -30,13 +29,6 @@ class RealmTaskLocalRepository(realm: Realm) : RealmBaseLocalRepository(realm), .filter { it.isLoaded } } - override fun getTasksFlowable(taskType: TaskType, userID: String, includedGroupIDs: Array): Flowable> { - if (realm.isClosed) return Flowable.empty() - return RxJavaBridge.toV3Flowable(findTasks(taskType, userID, includedGroupIDs) - .asFlowable() - .filter { it.isLoaded }) - } - private fun findTasks( taskType: TaskType, ownerID: String, @@ -226,16 +218,13 @@ class RealmTaskLocalRepository(realm: Realm) : RealmBaseLocalRepository(realm), } } - override fun getTaskAtPosition(taskType: String, position: Int): Flowable { - return RxJavaBridge.toV3Flowable( - realm.where(Task::class.java).equalTo("typeValue", taskType).equalTo("position", position) + override fun getTaskAtPosition(taskType: String, position: Int): Flow { + return realm.where(Task::class.java).equalTo("typeValue", taskType).equalTo("position", position) .findAll() - .asFlowable() + .toFlow() .filter { realmObject -> realmObject.isLoaded && realmObject.isNotEmpty() } .map { it.first() } - .filter { realmObject -> realmObject.isLoaded } - .cast(Task::class.java) - ) + .filterNotNull() } override fun updateIsdue(daily: TaskList): Maybe { @@ -258,27 +247,14 @@ class RealmTaskLocalRepository(realm: Realm) : RealmBaseLocalRepository(realm), } } - override fun getErroredTasks(userID: String): Flowable> { - return RxJavaBridge.toV3Flowable( - realm.where(Task::class.java) + override fun getErroredTasks(userID: String): Flow> { + return realm.where(Task::class.java) .equalTo("userId", userID) .equalTo("hasErrored", true) .sort("position") .findAll() - .asFlowable() + .toFlow() .filter { it.isLoaded } - ).retry(1) - } - - override fun getUserFlowable(userID: String): Flowable { - return RxJavaBridge.toV3Flowable( - realm.where(User::class.java) - .equalTo("id", userID) - .findAll() - .asFlowable() - .filter { realmObject -> realmObject.isLoaded && realmObject.isValid && !realmObject.isEmpty() } - .map { users -> users.first() } - ) } override fun getUser(userID: String): Flow { @@ -291,15 +267,12 @@ class RealmTaskLocalRepository(realm: Realm) : RealmBaseLocalRepository(realm), .filterNotNull() } - override fun getTasksForChallenge(challengeID: String?, userID: String?): Flowable> { - return RxJavaBridge.toV3Flowable( - realm.where(Task::class.java) + override fun getTasksForChallenge(challengeID: String?, userID: String?): Flow> { + return realm.where(Task::class.java) .equalTo("challengeID", challengeID) .equalTo("userId", userID) .findAll() - .asFlowable() + .toFlow() .filter { it.isLoaded } - ) - .retry(1) } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/implementation/RealmTutorialLocalRepository.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/implementation/RealmTutorialLocalRepository.kt index 8f7577680..0e7c81185 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/implementation/RealmTutorialLocalRepository.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/implementation/RealmTutorialLocalRepository.kt @@ -2,31 +2,32 @@ package com.habitrpg.android.habitica.data.local.implementation import com.habitrpg.android.habitica.data.local.TutorialLocalRepository import com.habitrpg.android.habitica.models.TutorialStep -import hu.akarnokd.rxjava3.bridge.RxJavaBridge -import io.reactivex.rxjava3.core.Flowable import io.realm.Realm +import io.realm.kotlin.toFlow +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.emptyFlow +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.filterNotNull +import kotlinx.coroutines.flow.map class RealmTutorialLocalRepository(realm: Realm) : RealmBaseLocalRepository(realm), TutorialLocalRepository { - override fun getTutorialStep(key: String): Flowable { - if (realm.isClosed) return Flowable.empty() - return RxJavaBridge.toV3Flowable( - realm.where(TutorialStep::class.java).equalTo("identifier", key) + override fun getTutorialStep(key: String): Flow { + if (realm.isClosed) return emptyFlow() + return realm.where(TutorialStep::class.java).equalTo("identifier", key) .findAll() - .asFlowable() + .toFlow() .filter { realmObject -> realmObject.isLoaded && realmObject.isValid && realmObject.isNotEmpty() } .map { steps -> steps.first() } - ) + .filterNotNull() } - override fun getTutorialSteps(keys: List): Flowable> { - if (realm.isClosed) return Flowable.empty() - return RxJavaBridge.toV3Flowable( - realm.where(TutorialStep::class.java) + override fun getTutorialSteps(keys: List): Flow> { + if (realm.isClosed) return emptyFlow() + return realm.where(TutorialStep::class.java) .`in`("identifier", keys.toTypedArray()) .findAll() - .asFlowable() + .toFlow() .filter { it.isLoaded } - ) } } 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 501efe7b8..f835f4017 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 @@ -13,36 +13,40 @@ import com.habitrpg.android.habitica.models.social.Group import com.habitrpg.android.habitica.models.user.User import com.habitrpg.android.habitica.models.user.UserQuestStatus import hu.akarnokd.rxjava3.bridge.RxJavaBridge -import io.reactivex.rxjava3.core.Flowable import io.realm.Realm import io.realm.kotlin.toFlow +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.filterNotNull +import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.map class RealmUserLocalRepository(realm: Realm) : RealmBaseLocalRepository(realm), UserLocalRepository { - override fun getUserQuestStatus(userID: String): Flowable { - return getUserFlowable(userID) + @OptIn(ExperimentalCoroutinesApi::class) + override fun getUserQuestStatus(userID: String): Flow { + return getUser(userID) + .filterNotNull() .map { it.party?.id ?: "" } - .distinctUntilChanged() .filter { it.isNotBlank() } - .flatMap { - RxJavaBridge.toV3Flowable( + .flatMapLatest { realm.where(Group::class.java) .equalTo("id", it) .findAll() - .asFlowable() + .toFlow() .filter { groups -> groups.size > 0 } - ).filterMap { it.first() } + .map { it.firstOrNull() } + .filterNotNull() } .map { when { it.quest?.members?.find { questMember -> questMember.key == userID } === null -> UserQuestStatus.NO_QUEST it.quest?.progress?.collect?.isNotEmpty() ?: false -> UserQuestStatus.QUEST_COLLECT - it.quest?.progress?.hp ?: 0.0 > 0.0 -> UserQuestStatus.QUEST_BOSS + (it.quest?.progress?.hp ?: 0.0) > 0.0 -> UserQuestStatus.QUEST_BOSS else -> UserQuestStatus.QUEST_UNKNOWN } } @@ -79,17 +83,6 @@ class RealmUserLocalRepository(realm: Realm) : RealmBaseLocalRepository(realm), .map { users -> users.first() } } - override fun getUserFlowable(userID: String): Flowable { - if (realm.isClosed) return Flowable.empty() - return RxJavaBridge.toV3Flowable( - realm.where(User::class.java) - .equalTo("id", userID) - .findAll() - .asFlowable() - .filter { realmObject -> realmObject.isLoaded && realmObject.isValid && !realmObject.isEmpty() } - .map { users -> users.first() }) - } - override fun saveUser(user: User, overrideExisting: Boolean) { if (realm.isClosed) return val oldUser = realm.where(User::class.java) @@ -134,45 +127,40 @@ class RealmUserLocalRepository(realm: Realm) : RealmBaseLocalRepository(realm), .filter { it.isLoaded } } - override fun getTeamPlan(teamID: String): Flowable { - if (realm.isClosed) return Flowable.empty() - return RxJavaBridge.toV3Flowable( - realm.where(Group::class.java) + override fun getTeamPlan(teamID: String): Flow { + if (realm.isClosed) return emptyFlow() + return realm.where(Group::class.java) .equalTo("id", teamID) .findAll() - .asFlowable() + .toFlow() .filter { realmObject -> realmObject.isLoaded && realmObject.isValid && !realmObject.isEmpty() } - .map { teams -> teams.first() } - ) + .map { teams -> teams.firstOrNull() } + .filterNotNull() } - override fun getSkills(user: User): Flowable> { + override fun getSkills(user: User): Flow> { val habitClass = if (user.preferences?.disableClasses == true) "none" else user.stats?.habitClass - return RxJavaBridge.toV3Flowable( - realm.where(Skill::class.java) + return realm.where(Skill::class.java) .equalTo("habitClass", habitClass) .sort("lvl") .findAll() - .asFlowable() + .toFlow() .filter { it.isLoaded } - ) } - override fun getSpecialItems(user: User): Flowable> { + override fun getSpecialItems(user: User): Flow> { val specialItems = user.items?.special val ownedItems = ArrayList() for (key in listOf("snowball", "shinySeed", "seafoam", "spookySparkles")) { - if (specialItems?.firstOrNull() { it.key == key }?.numberOwned ?: 0 > 0) { + if ((specialItems?.firstOrNull { it.key == key }?.numberOwned ?: 0) > 0) { ownedItems.add(key) } } - return RxJavaBridge.toV3Flowable( - realm.where(Skill::class.java) + return realm.where(Skill::class.java) .`in`("key", ownedItems.toTypedArray()) .findAll() - .asFlowable() + .toFlow() .filter { it.isLoaded } - ) } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/AppConfigManager.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/AppConfigManager.kt index ff3e5cc5b..95828ea89 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/AppConfigManager.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/AppConfigManager.kt @@ -12,6 +12,7 @@ import com.habitrpg.android.habitica.models.promotions.HabiticaPromotion import com.habitrpg.android.habitica.models.promotions.HabiticaWebPromotion import com.habitrpg.android.habitica.models.promotions.getHabiticaPromotionFromKey import com.habitrpg.common.habitica.helpers.AppTestingLevel +import kotlinx.coroutines.MainScope class AppConfigManager(contentRepository: ContentRepository?): com.habitrpg.common.habitica.helpers.AppConfigManager() { @@ -19,12 +20,11 @@ class AppConfigManager(contentRepository: ContentRepository?): com.habitrpg.comm init { try { - contentRepository?.getWorldState()?.subscribe( - { - worldState = it - }, - ExceptionHandler.rx() - ) + MainScope().launchCatching { + contentRepository?.getWorldState()?.collect { + worldState = it + } + } } catch (_: java.lang.IllegalStateException) { // pass } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/ExceptionHandler.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/ExceptionHandler.kt index 91f3821f4..2e52bb4ec 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/ExceptionHandler.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/ExceptionHandler.kt @@ -56,6 +56,8 @@ class ExceptionHandler { } } -fun CoroutineScope.launchCatching(function: suspend CoroutineScope.() -> Unit) { - launch((ExceptionHandler.coroutine()), block = function) +fun CoroutineScope.launchCatching(errorHandler: ((Throwable) -> Unit)? = null, function: suspend CoroutineScope.() -> Unit) { + launch((ExceptionHandler.coroutine { + errorHandler?.invoke(it) + }), block = function) } \ No newline at end of file diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/NotificationsManager.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/NotificationsManager.kt index 96eaeb26b..803829294 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/NotificationsManager.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/NotificationsManager.kt @@ -3,56 +3,50 @@ package com.habitrpg.android.habitica.helpers import android.content.Context import androidx.core.app.NotificationManagerCompat import com.habitrpg.android.habitica.data.ApiClient -import com.habitrpg.common.habitica.models.Notification import com.habitrpg.android.habitica.models.tasks.Task -import io.reactivex.rxjava3.core.BackpressureStrategy -import io.reactivex.rxjava3.core.Flowable -import io.reactivex.rxjava3.subjects.BehaviorSubject -import io.reactivex.rxjava3.subjects.PublishSubject +import com.habitrpg.common.habitica.models.Notification +import kotlinx.coroutines.MainScope +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.filterNotNull import java.lang.ref.WeakReference import java.util.Date interface NotificationsManager { - val displayNotificationEvents: Flowable + val displayNotificationEvents: Flow var apiClient: WeakReference? fun setNotifications(current: List) - fun getNotifications(): Flowable> + fun getNotifications(): Flow> fun getNotification(id: String): Notification? fun dismissTaskNotification(context: Context, task: Task) } class MainNotificationsManager: NotificationsManager { - private val displayNotificationSubject = PublishSubject.create() private val seenNotifications: MutableMap override var apiClient: WeakReference? = null - private val notifications: BehaviorSubject> private var lastNotificationHandling: Date? = null - - override val displayNotificationEvents: Flowable - get() { - return displayNotificationSubject.toFlowable(BackpressureStrategy.DROP) - } + val _notifications = MutableStateFlow?>(null) + val _displaynotificationEvents = MutableStateFlow(null) + override val displayNotificationEvents: Flow = _displaynotificationEvents.filterNotNull() init { this.seenNotifications = HashMap() - this.notifications = BehaviorSubject.create() } override fun setNotifications(current: List) { - this.notifications.onNext(current) + _notifications.value = current this.handlePopupNotifications(current) } - override fun getNotifications(): Flowable> { - return this.notifications.startWithArray(emptyList()) - .toFlowable(BackpressureStrategy.LATEST) + override fun getNotifications(): Flow> { + return _notifications.filterNotNull() } override fun getNotification(id: String): Notification? { - return this.notifications.value?.find { it.id == id } + return _notifications.value?.find { it.id == id } } override fun dismissTaskNotification(context: Context, task: Task) { @@ -109,7 +103,7 @@ class MainNotificationsManager: NotificationsManager { } if (notificationDisplayed) { - displayNotificationSubject.onNext(it) + _displaynotificationEvents.value = it this.seenNotifications[it.id] = true readNotification(it) } @@ -119,7 +113,8 @@ class MainNotificationsManager: NotificationsManager { } private fun readNotification(notification: Notification) { - apiClient?.get()?.readNotification(notification.id) - ?.subscribe({ }, ExceptionHandler.rx()) + MainScope().launchCatching { + apiClient?.get()?.readNotification(notification.id) + } } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/PurchaseHandler.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/PurchaseHandler.kt index 6acb1c644..7d497f91a 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/PurchaseHandler.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/PurchaseHandler.kt @@ -72,10 +72,13 @@ class PurchaseHandler( val mostRecentSub = findMostRecentSubscription(purchases) val plan = userViewModel.user.value?.purchased?.plan for (purchase in purchases) { - if (plan?.isActive == true && PurchaseTypes.allSubscriptionTypes.contains(purchase.skus.firstOrNull())) { + if (plan?.isActive == true && PurchaseTypes.allSubscriptionTypes.contains( + purchase.skus.firstOrNull() + ) + ) { if ((plan.additionalData?.data?.orderId == purchase.orderId && - ((plan.dateTerminated != null) == purchase.isAutoRenewing)) || - mostRecentSub?.orderId != purchase.orderId + ((plan.dateTerminated != null) == purchase.isAutoRenewing)) || + mostRecentSub?.orderId != purchase.orderId ) { return } @@ -138,8 +141,8 @@ class PurchaseHandler( billingClient.endConnection() } - fun queryPurchases(){ - if (billingClientState == BillingClientState.READY){ + fun queryPurchases() { + if (billingClientState == BillingClientState.READY) { billingClient.queryPurchasesAsync( BillingClient.SkuType.SUBS, this@PurchaseHandler @@ -185,7 +188,12 @@ class PurchaseHandler( return skuDetailsResult.skuDetailsList } - fun purchase(activity: Activity, skuDetails: SkuDetails, recipient: String? = null, isSaleGemPurchase: Boolean = false) { + fun purchase( + activity: Activity, + skuDetails: SkuDetails, + recipient: String? = null, + isSaleGemPurchase: Boolean = false + ) { this.isSaleGemPurchase = isSaleGemPurchase recipient?.let { addGift(skuDetails.sku, it) @@ -216,41 +224,50 @@ class PurchaseHandler( when { PurchaseTypes.allGemTypes.contains(sku) -> { val validationRequest = buildValidationRequest(purchase) - apiClient.validatePurchase(validationRequest).subscribe({ - processedPurchase(purchase) - val gift = removeGift(sku) - CoroutineScope(Dispatchers.IO).launch(ExceptionHandler.coroutine()) { - consume(purchase) + MainScope().launchCatching { + try { + apiClient.validatePurchase(validationRequest) + processedPurchase(purchase) + val gift = removeGift(sku) + CoroutineScope(Dispatchers.IO).launch(ExceptionHandler.coroutine()) { + consume(purchase) + } + displayConfirmationDialog(purchase, gift?.second) + } catch (throwable: Throwable) { + handleError(throwable, purchase) } - displayConfirmationDialog(purchase, gift?.second) - }) { throwable: Throwable -> - handleError(throwable, purchase) } } PurchaseTypes.allSubscriptionNoRenewTypes.contains(sku) -> { val validationRequest = buildValidationRequest(purchase) - apiClient.validateNoRenewSubscription(validationRequest).subscribe({ - processedPurchase(purchase) - val gift = removeGift(sku) - CoroutineScope(Dispatchers.IO).launch(ExceptionHandler.coroutine()) { - consume(purchase) + MainScope().launchCatching { + try { + apiClient.validateNoRenewSubscription(validationRequest) + processedPurchase(purchase) + val gift = removeGift(sku) + CoroutineScope(Dispatchers.IO).launch(ExceptionHandler.coroutine()) { + consume(purchase) + } + displayConfirmationDialog(purchase, gift?.second) + } catch (throwable: Throwable) { + handleError(throwable, purchase) } - displayConfirmationDialog(purchase, gift?.second) - }) { throwable: Throwable -> - handleError(throwable, purchase) } } PurchaseTypes.allSubscriptionTypes.contains(sku) -> { val validationRequest = buildValidationRequest(purchase) - apiClient.validateSubscription(validationRequest).subscribe({ - processedPurchase(purchase) - analyticsManager.logEvent("user_subscribed", bundleOf(Pair("sku", sku))) - CoroutineScope(Dispatchers.IO).launch(ExceptionHandler.coroutine()) { - acknowledgePurchase(purchase) + MainScope().launchCatching { + try { + apiClient.validateSubscription(validationRequest) + processedPurchase(purchase) + analyticsManager.logEvent("user_subscribed", bundleOf(Pair("sku", sku))) + CoroutineScope(Dispatchers.IO).launch(ExceptionHandler.coroutine()) { + acknowledgePurchase(purchase) + } + displayConfirmationDialog(purchase) + } catch (throwable: Throwable) { + handleError(throwable, purchase) } - displayConfirmationDialog(purchase) - }) { throwable: Throwable -> - handleError(throwable, purchase) } } } @@ -320,7 +337,8 @@ class PurchaseHandler( } private fun findMostRecentSubscription(purchasesList: List): Purchase? { - val purchases = purchasesList.filter { it.isAcknowledged }.sortedByDescending { it.purchaseTime } + val purchases = + purchasesList.filter { it.isAcknowledged }.sortedByDescending { it.purchaseTime } var fallback: Purchase? = null // If there is a subscription that is still active, prioritise that. Otherwise return the most recent one. for (purchase in purchases) { @@ -380,18 +398,29 @@ class PurchaseHandler( val message = when { PurchaseTypes.allSubscriptionNoRenewTypes.contains(sku) -> { title = context.getString(R.string.gift_confirmation_title) - context.getString(R.string.gift_confirmation_text_sub, giftedTo, durationString(sku)) + context.getString( + R.string.gift_confirmation_text_sub, + giftedTo, + durationString(sku) + ) } PurchaseTypes.allSubscriptionTypes.contains(sku) -> { if (sku == PurchaseTypes.Subscription1Month) { context.getString(R.string.subscription_confirmation) } else { - context.getString(R.string.subscription_confirmation_multiple, durationString(sku)) + context.getString( + R.string.subscription_confirmation_multiple, + durationString(sku) + ) } } PurchaseTypes.allGemTypes.contains(sku) && giftedTo != null -> { title = context.getString(R.string.gift_confirmation_title) - context.getString(R.string.gift_confirmation_text_gems_new, giftedTo, gemAmountString(sku)) + context.getString( + R.string.gift_confirmation_text_gems_new, + giftedTo, + gemAmountString(sku) + ) } PurchaseTypes.allGemTypes.contains(sku) && giftedTo == null -> { context.getString(R.string.gem_purchase_confirmation, gemAmountString(sku)) diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/UserStatComputer.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/UserStatComputer.kt index de8b61a14..f75d18a68 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/UserStatComputer.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/UserStatComputer.kt @@ -25,7 +25,7 @@ class UserStatComputer { var stats: String? = null } - fun computeClassBonus(equipmentList: List, user: Avatar): List { + fun computeClassBonus(equipmentList: List?, user: Avatar): List { val skillRows = ArrayList() var strAttributes = 0f @@ -39,7 +39,7 @@ class UserStatComputer { var perClassBonus = 0f // Summarize stats and fill equipment table - for (i in equipmentList) { + for (i in equipmentList ?: emptyList()) { val strength = i.str val intelligence = i._int val constitution = i.con diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/notifications/PushNotificationManager.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/notifications/PushNotificationManager.kt index 8fac75a74..89f2a7ea1 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/notifications/PushNotificationManager.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/notifications/PushNotificationManager.kt @@ -7,8 +7,9 @@ import com.google.firebase.messaging.FirebaseMessaging import com.google.firebase.messaging.RemoteMessage import com.habitrpg.android.habitica.data.ApiClient import com.habitrpg.android.habitica.helpers.AmplitudeManager -import com.habitrpg.android.habitica.helpers.ExceptionHandler +import com.habitrpg.android.habitica.helpers.launchCatching import com.habitrpg.android.habitica.models.user.User +import kotlinx.coroutines.MainScope class PushNotificationManager( var apiClient: ApiClient, @@ -51,14 +52,18 @@ class PushNotificationManager( val pushDeviceData = HashMap() pushDeviceData["regId"] = this.refreshedToken pushDeviceData["type"] = "android" - apiClient.addPushDevice(pushDeviceData).subscribe({ }, ExceptionHandler.rx()) + MainScope().launchCatching { + apiClient.addPushDevice(pushDeviceData) + } } fun removePushDeviceUsingStoredToken() { if (this.refreshedToken.isEmpty() || !userHasPushDevice()) { return } - apiClient.deletePushDevice(this.refreshedToken).subscribe({ }, ExceptionHandler.rx()) + MainScope().launchCatching { + apiClient.deletePushDevice(refreshedToken) + } } private fun userHasPushDevice(): Boolean { diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/interactors/CheckClassSelectionUseCase.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/interactors/CheckClassSelectionUseCase.kt index d0cd898ff..ecbace013 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/interactors/CheckClassSelectionUseCase.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/interactors/CheckClassSelectionUseCase.kt @@ -6,27 +6,21 @@ import android.os.Bundle import com.habitrpg.android.habitica.executors.PostExecutionThread import com.habitrpg.android.habitica.models.user.User import com.habitrpg.android.habitica.ui.activities.ClassSelectionActivity -import io.reactivex.rxjava3.core.Flowable import javax.inject.Inject -class CheckClassSelectionUseCase @Inject constructor(postExecutionThread: PostExecutionThread) : UseCase(postExecutionThread) { +class CheckClassSelectionUseCase @Inject constructor(postExecutionThread: PostExecutionThread) : FlowUseCase() { - override fun buildUseCaseObservable(requestValues: RequestValues): Flowable { - return Flowable.defer { - val user = requestValues.user - - if (requestValues.currentClass == null) { - if (user?.stats?.lvl ?: 0 >= 9 && - user?.preferences?.disableClasses != true && - user?.flags?.classSelected != true - ) { - displayClassSelectionActivity(true, null, requestValues.activity) - } - } else { - displayClassSelectionActivity(requestValues.isInitialSelection, requestValues.currentClass, requestValues.activity) + override suspend fun run(requestValues: RequestValues) { + val user = requestValues.user + if (requestValues.currentClass == null) { + if ((user?.stats?.lvl ?: 0) >= 9 && + user?.preferences?.disableClasses != true && + user?.flags?.classSelected != true + ) { + displayClassSelectionActivity(true, null, requestValues.activity) } - - Flowable.empty() + } else { + displayClassSelectionActivity(requestValues.isInitialSelection, requestValues.currentClass, requestValues.activity) } } @@ -49,5 +43,5 @@ class CheckClassSelectionUseCase @Inject constructor(postExecutionThread: PostEx val isInitialSelection: Boolean, val currentClass: String?, val activity: Activity - ) : UseCase.RequestValues + ) : FlowUseCase.RequestValues } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/interactors/DisplayItemDropUseCase.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/interactors/DisplayItemDropUseCase.kt index c5e841032..f108a63bc 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/interactors/DisplayItemDropUseCase.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/interactors/DisplayItemDropUseCase.kt @@ -3,11 +3,9 @@ package com.habitrpg.android.habitica.interactors import android.view.ViewGroup import androidx.appcompat.app.AppCompatActivity import com.habitrpg.android.habitica.R -import com.habitrpg.android.habitica.executors.PostExecutionThread import com.habitrpg.android.habitica.helpers.SoundManager import com.habitrpg.android.habitica.ui.views.HabiticaSnackbar import com.habitrpg.shared.habitica.models.responses.TaskScoringResult -import io.reactivex.rxjava3.core.Flowable import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.MainScope import kotlinx.coroutines.delay @@ -15,32 +13,35 @@ import kotlinx.coroutines.launch import javax.inject.Inject class DisplayItemDropUseCase @Inject -constructor(private val soundManager: SoundManager, postExecutionThread: PostExecutionThread) : UseCase(postExecutionThread) { +constructor(private val soundManager: SoundManager): + FlowUseCase() { - override fun buildUseCaseObservable(requestValues: RequestValues): Flowable { - return Flowable.defer { - val data = requestValues.data - val snackbarText = StringBuilder(data?.drop?.dialog ?: "") + override suspend fun run(requestValues: RequestValues) { + val data = requestValues.data + val snackbarText = StringBuilder(data?.drop?.dialog ?: "") - if ((data?.questItemsFound ?: 0) > 0 && requestValues.showQuestItems) { - if (snackbarText.isNotEmpty()) - snackbarText.append('\n') - snackbarText.append(requestValues.context.getString(R.string.quest_items_found, data!!.questItemsFound)) - } - - if (snackbarText.isNotEmpty()) { - MainScope().launch(context = Dispatchers.Main) { - delay(3000L) - HabiticaSnackbar.showSnackbar( - requestValues.snackbarTargetView, - snackbarText, HabiticaSnackbar.SnackbarDisplayType.DROP, true - ) - soundManager.loadAndPlayAudio(SoundManager.SoundItemDrop) - } - } - - Flowable.empty() + if ((data?.questItemsFound ?: 0) > 0 && requestValues.showQuestItems) { + if (snackbarText.isNotEmpty()) + snackbarText.append('\n') + snackbarText.append( + requestValues.context.getString( + R.string.quest_items_found, + data!!.questItemsFound + ) + ) } + + if (snackbarText.isNotEmpty()) { + MainScope().launch(context = Dispatchers.Main) { + delay(3000L) + HabiticaSnackbar.showSnackbar( + requestValues.snackbarTargetView, + snackbarText, HabiticaSnackbar.SnackbarDisplayType.DROP, true + ) + soundManager.loadAndPlayAudio(SoundManager.SoundItemDrop) + } + } + return } class RequestValues( @@ -48,5 +49,5 @@ constructor(private val soundManager: SoundManager, postExecutionThread: PostExe val context: AppCompatActivity, val snackbarTargetView: ViewGroup, val showQuestItems: Boolean - ) : UseCase.RequestValues + ) : FlowUseCase.RequestValues } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/interactors/FeedPetUseCase.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/interactors/FeedPetUseCase.kt index eba34e677..5ae6bf77b 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/interactors/FeedPetUseCase.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/interactors/FeedPetUseCase.kt @@ -7,75 +7,75 @@ import android.view.View import android.widget.FrameLayout import com.habitrpg.android.habitica.R import com.habitrpg.android.habitica.data.InventoryRepository -import com.habitrpg.android.habitica.executors.PostExecutionThread import com.habitrpg.android.habitica.models.inventory.Food import com.habitrpg.android.habitica.models.inventory.Pet -import com.habitrpg.shared.habitica.models.responses.FeedResponse import com.habitrpg.android.habitica.ui.activities.BaseActivity -import com.habitrpg.common.habitica.extensions.loadImage -import com.habitrpg.common.habitica.views.PixelArtView import com.habitrpg.android.habitica.ui.views.SnackbarActivity import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog -import io.reactivex.rxjava3.core.Flowable +import com.habitrpg.common.habitica.extensions.loadImage +import com.habitrpg.common.habitica.views.PixelArtView +import com.habitrpg.shared.habitica.models.responses.FeedResponse import javax.inject.Inject class FeedPetUseCase @Inject constructor( private val inventoryRepository: InventoryRepository, - postExecutionThread: PostExecutionThread -) : UseCase(postExecutionThread) { - override fun buildUseCaseObservable(requestValues: FeedPetUseCase.RequestValues): Flowable { - return inventoryRepository.feedPet(requestValues.pet, requestValues.food) - .doOnNext { feedResponse -> - (requestValues.context as? SnackbarActivity)?.showSnackbar(content = feedResponse.message) - if (feedResponse.value == -1) { - val mountWrapper = - View.inflate( - requestValues.context, - R.layout.pet_imageview, - null - ) as? FrameLayout - val mountImageView = - mountWrapper?.findViewById(R.id.pet_imageview) as? PixelArtView +) : FlowUseCase() { + override suspend fun run(requestValues: FeedPetUseCase.RequestValues): FeedResponse? { + val feedResponse = inventoryRepository.feedPet(requestValues.pet, requestValues.food) + (requestValues.context as? SnackbarActivity)?.showSnackbar(content = feedResponse?.message) + if (feedResponse?.value == -1) { + val mountWrapper = + View.inflate( + requestValues.context, + R.layout.pet_imageview, + null + ) as? FrameLayout + val mountImageView = + mountWrapper?.findViewById(R.id.pet_imageview) as? PixelArtView - mountImageView?.loadImage("Mount_Icon_" + requestValues.pet.key) - val dialog = HabiticaAlertDialog(requestValues.context) - dialog.setTitle( - requestValues.context.getString( - R.string.evolved_pet_title, - requestValues.pet.text - ) + mountImageView?.loadImage("Mount_Icon_" + requestValues.pet.key) + val dialog = HabiticaAlertDialog(requestValues.context) + dialog.setTitle( + requestValues.context.getString( + R.string.evolved_pet_title, + requestValues.pet.text + ) + ) + dialog.setAdditionalContentView(mountWrapper) + dialog.addButton(R.string.onwards, true) + dialog.addButton(R.string.share, false) { hatchingDialog, _ -> + val message = + requestValues.context.getString( + R.string.share_raised, + requestValues.pet.text ) - dialog.setAdditionalContentView(mountWrapper) - dialog.addButton(R.string.onwards, true) - dialog.addButton(R.string.share, false) { hatchingDialog, _ -> - val message = - requestValues.context.getString( - R.string.share_raised, - requestValues.pet.text - ) - val mountImageSideLength = 99 - val sharedImage = Bitmap.createBitmap( - mountImageSideLength, - mountImageSideLength, - Bitmap.Config.ARGB_8888 - ) - val canvas = Canvas(sharedImage) - mountImageView?.drawable?.setBounds( - 0, - 0, - mountImageSideLength, - mountImageSideLength - ) - mountImageView?.drawable?.draw(canvas) - (requestValues.context as? BaseActivity)?.shareContent("raisedPet", message, sharedImage) - hatchingDialog.dismiss() - } - dialog.enqueue() - } + val mountImageSideLength = 99 + val sharedImage = Bitmap.createBitmap( + mountImageSideLength, + mountImageSideLength, + Bitmap.Config.ARGB_8888 + ) + val canvas = Canvas(sharedImage) + mountImageView?.drawable?.setBounds( + 0, + 0, + mountImageSideLength, + mountImageSideLength + ) + mountImageView?.drawable?.draw(canvas) + (requestValues.context as? BaseActivity)?.shareContent( + "raisedPet", + message, + sharedImage + ) + hatchingDialog.dismiss() } + dialog.enqueue() + } + return feedResponse } class RequestValues(val pet: Pet, val food: Food, val context: Context) : - UseCase.RequestValues + FlowUseCase.RequestValues } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/interactors/HatchPetUseCase.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/interactors/HatchPetUseCase.kt index 4125c9194..b2cbdbb50 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/interactors/HatchPetUseCase.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/interactors/HatchPetUseCase.kt @@ -7,24 +7,21 @@ import android.view.View import android.widget.FrameLayout import com.habitrpg.android.habitica.R import com.habitrpg.android.habitica.data.InventoryRepository -import com.habitrpg.android.habitica.executors.PostExecutionThread -import com.habitrpg.android.habitica.helpers.ExceptionHandler +import com.habitrpg.android.habitica.helpers.launchCatching import com.habitrpg.android.habitica.models.inventory.Egg import com.habitrpg.android.habitica.models.inventory.HatchingPotion import com.habitrpg.android.habitica.models.user.Items import com.habitrpg.android.habitica.ui.activities.BaseActivity +import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog import com.habitrpg.common.habitica.extensions.loadImage import com.habitrpg.common.habitica.views.PixelArtView -import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog -import io.reactivex.rxjava3.core.Flowable +import kotlinx.coroutines.MainScope import javax.inject.Inject class HatchPetUseCase @Inject constructor( - private val inventoryRepository: InventoryRepository, - postExecutionThread: PostExecutionThread -) : UseCase(postExecutionThread) { - override fun buildUseCaseObservable(requestValues: RequestValues): Flowable { + private val inventoryRepository: InventoryRepository) : FlowUseCase() { + override suspend fun run(requestValues: RequestValues): Items? { return inventoryRepository.hatchPet(requestValues.egg, requestValues.potion) { val petWrapper = View.inflate(requestValues.context, R.layout.pet_imageview, null) as? FrameLayout val petImageView = petWrapper?.findViewById(R.id.pet_imageview) as? PixelArtView @@ -36,8 +33,9 @@ constructor( dialog.setTitle(requestValues.context.getString(R.string.hatched_pet_title, potionName, eggName)) dialog.setAdditionalContentView(petWrapper) dialog.addButton(R.string.equip, true) { _, _ -> - inventoryRepository.equip("pet", requestValues.egg.key + "-" + requestValues.potion.key) - .subscribe({}, ExceptionHandler.rx()) + MainScope().launchCatching { + inventoryRepository.equip("pet", requestValues.egg.key + "-" + requestValues.potion.key) + } } dialog.addButton(R.string.share, false) { hatchingDialog, _ -> val message = requestValues.context.getString(R.string.share_hatched, potionName, eggName) @@ -54,5 +52,5 @@ constructor( } } - class RequestValues(val potion: HatchingPotion, val egg: Egg, val context: Context) : UseCase.RequestValues + class RequestValues(val potion: HatchingPotion, val egg: Egg, val context: Context) : FlowUseCase.RequestValues } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/interactors/LevelUpUseCase.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/interactors/LevelUpUseCase.kt index 669a660ab..2f0e7b2c6 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/interactors/LevelUpUseCase.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/interactors/LevelUpUseCase.kt @@ -4,97 +4,94 @@ import android.graphics.Bitmap import android.view.ViewGroup import com.habitrpg.android.habitica.R import com.habitrpg.android.habitica.databinding.DialogLevelup10Binding -import com.habitrpg.android.habitica.executors.PostExecutionThread -import com.habitrpg.android.habitica.helpers.ExceptionHandler import com.habitrpg.android.habitica.helpers.SoundManager +import com.habitrpg.android.habitica.helpers.launchCatching import com.habitrpg.android.habitica.models.user.Stats import com.habitrpg.android.habitica.models.user.User -import com.habitrpg.common.habitica.views.AvatarView import com.habitrpg.android.habitica.ui.activities.BaseActivity import com.habitrpg.android.habitica.ui.views.HabiticaIconsHelper import com.habitrpg.android.habitica.ui.views.HabiticaSnackbar import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog -import io.reactivex.rxjava3.core.Flowable +import com.habitrpg.common.habitica.views.AvatarView +import kotlinx.coroutines.MainScope import javax.inject.Inject class LevelUpUseCase @Inject constructor( private val soundManager: SoundManager, - postExecutionThread: PostExecutionThread, private val checkClassSelectionUseCase: CheckClassSelectionUseCase -) : UseCase(postExecutionThread) { +) : FlowUseCase() { - override fun buildUseCaseObservable(requestValues: RequestValues): Flowable { - return Flowable.defer { - soundManager.loadAndPlayAudio(SoundManager.SoundLevelUp) + override suspend fun run(requestValues: RequestValues): Stats? { + soundManager.loadAndPlayAudio(SoundManager.SoundLevelUp) + val suppressedModals = requestValues.user.preferences?.suppressModals - val suppressedModals = requestValues.user.preferences?.suppressModals + if (requestValues.newLevel == 10) { + val binding = DialogLevelup10Binding.inflate(requestValues.activity.layoutInflater) + binding.healerIconView.setImageBitmap(HabiticaIconsHelper.imageOfHealerLightBg()) + binding.mageIconView.setImageBitmap(HabiticaIconsHelper.imageOfMageLightBg()) + binding.rogueIconView.setImageBitmap(HabiticaIconsHelper.imageOfRogueLightBg()) + binding.warriorIconView.setImageBitmap(HabiticaIconsHelper.imageOfWarriorLightBg()) - if (requestValues.newLevel == 10) { - val binding = DialogLevelup10Binding.inflate(requestValues.activity.layoutInflater) - binding.healerIconView.setImageBitmap(HabiticaIconsHelper.imageOfHealerLightBg()) - binding.mageIconView.setImageBitmap(HabiticaIconsHelper.imageOfMageLightBg()) - binding.rogueIconView.setImageBitmap(HabiticaIconsHelper.imageOfRogueLightBg()) - binding.warriorIconView.setImageBitmap(HabiticaIconsHelper.imageOfWarriorLightBg()) - - val alert = HabiticaAlertDialog(requestValues.activity) - alert.setTitle(requestValues.activity.getString(R.string.levelup_header, requestValues.newLevel)) - alert.setAdditionalContentView(binding.root) - alert.addButton(R.string.select_class, true) { _, _ -> + val alert = HabiticaAlertDialog(requestValues.activity) + alert.setTitle(requestValues.activity.getString(R.string.levelup_header, requestValues.newLevel)) + alert.setAdditionalContentView(binding.root) + alert.addButton(R.string.select_class, true) { _, _ -> + MainScope().launchCatching { showClassSelection(requestValues) } - alert.addButton(R.string.not_now, false) - alert.isCelebratory = true - - if (!requestValues.activity.isFinishing) { - alert.enqueue() - } - } else { - if (suppressedModals?.levelUp == true) { - HabiticaSnackbar.showSnackbar( - requestValues.snackbarTargetView, - requestValues.activity.getString(R.string.levelup_header, requestValues.newLevel), - HabiticaSnackbar.SnackbarDisplayType.SUCCESS, true - ) - return@defer Flowable.just(requestValues.user.stats) - } - val customView = requestValues.activity.layoutInflater.inflate(R.layout.dialog_levelup, null) - if (customView != null) { - val dialogAvatarView = customView.findViewById(R.id.avatarView) - dialogAvatarView.setAvatar(requestValues.user) - } - - val message = requestValues.activity.getString(R.string.share_levelup, requestValues.newLevel) - val avatarView = AvatarView(requestValues.activity, showBackground = true, showMount = true, showPet = true) - avatarView.setAvatar(requestValues.user) - var sharedImage: Bitmap? = null - avatarView.onAvatarImageReady { image -> - sharedImage = image - } - - val alert = HabiticaAlertDialog(requestValues.activity) - alert.setTitle(requestValues.activity.getString(R.string.levelup_header, requestValues.newLevel)) - alert.setAdditionalContentView(customView) - alert.addButton(R.string.onwards, true) { _, _ -> - showClassSelection(requestValues) - } - alert.addButton(R.string.share, false) { _, _ -> - requestValues.activity.shareContent("levelup", message, sharedImage) - } - alert.isCelebratory = true - - if (!requestValues.activity.isFinishing) { - alert.enqueue() - } } + alert.addButton(R.string.not_now, false) + alert.isCelebratory = true - Flowable.just(requestValues.user.stats!!) + if (!requestValues.activity.isFinishing) { + alert.enqueue() + } + } else { + if (suppressedModals?.levelUp == true) { + HabiticaSnackbar.showSnackbar( + requestValues.snackbarTargetView, + requestValues.activity.getString(R.string.levelup_header, requestValues.newLevel), + HabiticaSnackbar.SnackbarDisplayType.SUCCESS, true + ) + return requestValues.user.stats + } + val customView = requestValues.activity.layoutInflater.inflate(R.layout.dialog_levelup, null) + if (customView != null) { + val dialogAvatarView = customView.findViewById(R.id.avatarView) + dialogAvatarView.setAvatar(requestValues.user) + } + + val message = requestValues.activity.getString(R.string.share_levelup, requestValues.newLevel) + val avatarView = AvatarView(requestValues.activity, showBackground = true, showMount = true, showPet = true) + avatarView.setAvatar(requestValues.user) + var sharedImage: Bitmap? = null + avatarView.onAvatarImageReady { image -> + sharedImage = image + } + + val alert = HabiticaAlertDialog(requestValues.activity) + alert.setTitle(requestValues.activity.getString(R.string.levelup_header, requestValues.newLevel)) + alert.setAdditionalContentView(customView) + alert.addButton(R.string.onwards, true) { _, _ -> + MainScope().launchCatching { + showClassSelection(requestValues) + } + } + alert.addButton(R.string.share, false) { _, _ -> + requestValues.activity.shareContent("levelup", message, sharedImage) + } + alert.isCelebratory = true + + if (!requestValues.activity.isFinishing) { + alert.enqueue() + } } + return requestValues.user.stats } - private fun showClassSelection(requestValues: RequestValues) { - checkClassSelectionUseCase.observable(CheckClassSelectionUseCase.RequestValues(requestValues.user, true, null, requestValues.activity)) - .subscribe({ }, ExceptionHandler.rx()) + private suspend fun showClassSelection(requestValues: RequestValues) { + checkClassSelectionUseCase.callInteractor(CheckClassSelectionUseCase.RequestValues(requestValues.user, true, null, requestValues.activity)) } class RequestValues( @@ -102,7 +99,7 @@ constructor( val level: Int?, val activity: BaseActivity, val snackbarTargetView: ViewGroup - ) : UseCase.RequestValues { + ) : FlowUseCase.RequestValues { val newLevel: Int = level ?: 0 } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/interactors/NotifyUserUseCase.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/interactors/NotifyUserUseCase.kt index 19b3f55b2..a70070cd0 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/interactors/NotifyUserUseCase.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/interactors/NotifyUserUseCase.kt @@ -13,7 +13,6 @@ import androidx.core.content.ContextCompat import androidx.core.util.Pair import com.habitrpg.android.habitica.R import com.habitrpg.android.habitica.data.UserRepository -import com.habitrpg.android.habitica.executors.PostExecutionThread import com.habitrpg.android.habitica.models.user.Stats import com.habitrpg.android.habitica.models.user.User import com.habitrpg.android.habitica.ui.activities.BaseActivity @@ -21,39 +20,30 @@ import com.habitrpg.android.habitica.ui.views.HabiticaIconsHelper import com.habitrpg.android.habitica.ui.views.HabiticaSnackbar import com.habitrpg.android.habitica.ui.views.HabiticaSnackbar.SnackbarDisplayType import com.habitrpg.shared.habitica.extensions.round -import io.reactivex.rxjava3.core.Flowable import javax.inject.Inject import kotlin.math.abs class NotifyUserUseCase @Inject constructor( - postExecutionThread: PostExecutionThread, private val levelUpUseCase: LevelUpUseCase, private val userRepository: UserRepository -) : UseCase(postExecutionThread) { +) : FlowUseCase() { - override fun buildUseCaseObservable(requestValues: RequestValues): Flowable { - return Flowable.defer { - if (requestValues.user == null) { - return@defer Flowable.empty() - } - val stats = requestValues.user.stats - - val pair = getNotificationAndAddStatsToUser(requestValues.context, requestValues.xp, requestValues.hp, requestValues.gold, requestValues.mp, requestValues.questDamage, requestValues.user) - val view = pair.first - val type = pair.second - if (view != null && type != null) { - HabiticaSnackbar.showSnackbar(requestValues.snackbarTargetView, null, null, view, type) - } - if (requestValues.hasLeveledUp == true) { - return@defer levelUpUseCase.observable(LevelUpUseCase.RequestValues(requestValues.user, requestValues.level, requestValues.context, requestValues.snackbarTargetView)) - // TODO: .flatMap { userRepository.retrieveUser(true) } - .flatMap { userRepository.getUserFlowable().firstElement().toFlowable() } - .map { it.stats } - } else { - return@defer Flowable.just(stats) - } + override suspend fun run(requestValues: RequestValues): Stats? { + if (requestValues.user == null) { + return null } + val pair = getNotificationAndAddStatsToUser(requestValues.context, requestValues.xp, requestValues.hp, requestValues.gold, requestValues.mp, requestValues.questDamage, requestValues.user) + val view = pair.first + val type = pair.second + if (view != null && type != null) { + HabiticaSnackbar.showSnackbar(requestValues.snackbarTargetView, null, null, view, type) + } + if (requestValues.hasLeveledUp == true) { + levelUpUseCase.callInteractor(LevelUpUseCase.RequestValues(requestValues.user, requestValues.level, requestValues.context, requestValues.snackbarTargetView)) + userRepository.retrieveUser(true) + } + return requestValues.user.stats } class RequestValues( @@ -67,7 +57,7 @@ constructor( val questDamage: Double?, val hasLeveledUp: Boolean?, val level: Int? - ) : UseCase.RequestValues + ) : FlowUseCase.RequestValues companion object { diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/interactors/UseCase.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/interactors/UseCase.kt index d60e1ed32..45e028064 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/interactors/UseCase.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/interactors/UseCase.kt @@ -18,8 +18,8 @@ abstract class UseCase protected constructor abstract class FlowUseCase { protected abstract suspend fun run(requestValues: Q): T - suspend fun observable(requestValues: Q): T { - return withContext(Dispatchers.IO) { + suspend fun callInteractor(requestValues: Q): T { + return withContext(Dispatchers.Main) { run(requestValues) } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/receivers/LocalNotificationActionReceiver.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/receivers/LocalNotificationActionReceiver.kt index 4ecc891e7..4e8662a2d 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/receivers/LocalNotificationActionReceiver.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/receivers/LocalNotificationActionReceiver.kt @@ -15,6 +15,7 @@ import com.habitrpg.android.habitica.data.SocialRepository import com.habitrpg.android.habitica.data.TaskRepository import com.habitrpg.android.habitica.data.UserRepository import com.habitrpg.android.habitica.helpers.ExceptionHandler +import com.habitrpg.android.habitica.helpers.launchCatching import com.habitrpg.android.habitica.interactors.NotifyUserUseCase import com.habitrpg.android.habitica.models.user.User import kotlinx.coroutines.MainScope @@ -65,15 +66,20 @@ class LocalNotificationActionReceiver : BroadcastReceiver() { } context?.getString(R.string.reject_party_invite) -> { groupID?.let { - socialRepository.rejectGroupInvite(it) - .subscribe({ }, ExceptionHandler.rx()) + MainScope().launchCatching { + socialRepository.rejectGroupInvite(it) + } } } context?.getString(R.string.accept_quest_invite) -> { - socialRepository.acceptQuest(user).subscribe({ }, ExceptionHandler.rx()) + MainScope().launchCatching { + socialRepository.acceptQuest(user) + } } context?.getString(R.string.reject_quest_invite) -> { - socialRepository.rejectQuest(user).subscribe({ }, ExceptionHandler.rx()) + MainScope().launchCatching { + socialRepository.rejectQuest(user) + } } context?.getString(R.string.accept_guild_invite) -> { groupID?.let { @@ -84,21 +90,20 @@ class LocalNotificationActionReceiver : BroadcastReceiver() { } context?.getString(R.string.reject_guild_invite) -> { groupID?.let { - socialRepository.rejectGroupInvite(it) - .subscribe({ }, ExceptionHandler.rx()) + MainScope().launchCatching { + socialRepository.rejectGroupInvite(it) + } } } context?.getString(R.string.group_message_reply) -> { groupID?.let { getMessageText(context?.getString(R.string.group_message_reply))?.let { message -> - socialRepository.postGroupChat(it, message).subscribe( - { - context?.let { c -> - NotificationManagerCompat.from(c).cancel(it.hashCode()) - } - }, - ExceptionHandler.rx() - ) + MainScope().launchCatching { + socialRepository.postGroupChat(it, message) + context?.let { c -> + NotificationManagerCompat.from(c).cancel(it.hashCode()) + } + } } } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/receivers/NotificationPublisher.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/receivers/NotificationPublisher.kt index dd0dac59a..979a5323e 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/receivers/NotificationPublisher.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/receivers/NotificationPublisher.kt @@ -15,13 +15,12 @@ import com.habitrpg.android.habitica.R import com.habitrpg.android.habitica.data.TaskRepository import com.habitrpg.android.habitica.data.UserRepository import com.habitrpg.android.habitica.extensions.withImmutableFlag -import com.habitrpg.android.habitica.helpers.ExceptionHandler import com.habitrpg.android.habitica.helpers.TaskAlarmManager -import com.habitrpg.android.habitica.models.tasks.Task -import com.habitrpg.android.habitica.models.user.User +import com.habitrpg.android.habitica.helpers.launchCatching import com.habitrpg.android.habitica.ui.activities.MainActivity import com.habitrpg.shared.habitica.models.tasks.TaskType -import io.reactivex.rxjava3.functions.BiFunction +import kotlinx.coroutines.MainScope +import kotlinx.coroutines.flow.firstOrNull import java.util.Calendar import java.util.Date import java.util.Random @@ -57,26 +56,20 @@ class NotificationPublisher : BroadcastReceiver() { } val checkDailies = intent.getBooleanExtra(CHECK_DAILIES, false) if (checkDailies) { - taskRepository.getTasksFlowable(TaskType.DAILY, null, emptyArray()).firstElement().zipWith( - userRepository.getUserFlowable().firstElement(), - BiFunction, User, Pair, User>> { tasks, user -> - return@BiFunction Pair(tasks, user) + MainScope().launchCatching { + val tasks = taskRepository.getTasks(TaskType.DAILY, null, emptyArray()).firstOrNull() + val user = userRepository.getUser().firstOrNull() + var showNotifications = false + for (task in tasks ?: emptyList()) { + if (task.checkIfDue()) { + showNotifications = true + break + } } - ).subscribe( - { pair -> - var showNotifications = false - for (task in pair.first) { - if (task.checkIfDue()) { - showNotifications = true - break - } - } - if (showNotifications) { - notify(intent, buildNotification(wasInactive, pair.second.authentication?.timestamps?.createdAt)) - } - }, - ExceptionHandler.rx() - ) + if (showNotifications) { + notify(intent, buildNotification(wasInactive, user?.authentication?.timestamps?.createdAt)) + } + } } else { notify(intent, buildNotification(wasInactive)) } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/ArmoireActivity.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/ArmoireActivity.kt index f18b327f0..b1a973642 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/ArmoireActivity.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/ArmoireActivity.kt @@ -18,6 +18,7 @@ import com.habitrpg.android.habitica.helpers.AdHandler import com.habitrpg.android.habitica.helpers.AdType import com.habitrpg.android.habitica.helpers.AppConfigManager import com.habitrpg.android.habitica.helpers.ExceptionHandler +import com.habitrpg.android.habitica.helpers.launchCatching import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel import com.habitrpg.android.habitica.ui.views.ads.AdButton import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaBottomSheetDialog @@ -25,6 +26,7 @@ import com.habitrpg.common.habitica.extensions.dpToPx import com.habitrpg.common.habitica.extensions.loadImage import com.habitrpg.common.habitica.helpers.Animations import com.plattysoft.leonids.ParticleSystem +import kotlinx.coroutines.MainScope import kotlinx.coroutines.launch import java.util.Locale import javax.inject.Inject @@ -115,7 +117,9 @@ class ArmoireActivity : BaseActivity() { finish() } binding.equipButton.setOnClickListener { - equipmentKey?.let { it1 -> inventoryRepository.equip("equipped", it1).subscribe({}, ExceptionHandler.rx()) } + equipmentKey?.let { it1 -> + MainScope().launchCatching { inventoryRepository.equip("equipped", it1) } + } finish() } binding.dropRateButton.setOnClickListener { 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 0e63b948f..8f112448c 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 @@ -29,6 +29,7 @@ import com.habitrpg.android.habitica.data.UserRepository import com.habitrpg.android.habitica.extensions.updateStatusBarColor import com.habitrpg.android.habitica.helpers.ExceptionHandler import com.habitrpg.android.habitica.helpers.NotificationsManager +import com.habitrpg.android.habitica.helpers.launchCatching import com.habitrpg.android.habitica.interactors.ShowNotificationInteractor import com.habitrpg.android.habitica.proxy.AnalyticsManager import com.habitrpg.android.habitica.ui.helpers.ToolbarColorHelper @@ -36,7 +37,6 @@ import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog import com.habitrpg.common.habitica.extensions.getThemeColor import com.habitrpg.common.habitica.extensions.isUsingNightModeResources import com.habitrpg.common.habitica.helpers.LanguageHelper -import io.reactivex.rxjava3.disposables.CompositeDisposable import kotlinx.coroutines.launch import java.util.Date import java.util.Locale @@ -66,8 +66,6 @@ abstract class BaseActivity : AppCompatActivity() { return (getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater).inflate(layoutResId ?: 0, null) } - var compositeSubscription = CompositeDisposable() - private val habiticaApplication: HabiticaApplication get() = application as HabiticaApplication @@ -95,17 +93,15 @@ abstract class BaseActivity : AppCompatActivity() { getLayoutResId()?.let { setContentView(getContentView(it)) } - compositeSubscription = CompositeDisposable() - compositeSubscription.add(notificationsManager.displayNotificationEvents.subscribe( - { - if (ShowNotificationInteractor(this, lifecycleScope).handleNotification(it)) { - lifecycleScope.launch(ExceptionHandler.coroutine()) { - userRepository.retrieveUser(false, true) + lifecycleScope.launchCatching { + notificationsManager.displayNotificationEvents.collect { + if (ShowNotificationInteractor(this@BaseActivity, lifecycleScope).handleNotification(it)) { + lifecycleScope.launch(ExceptionHandler.coroutine()) { + userRepository.retrieveUser(false, true) + } } } - }, - ExceptionHandler.rx() - )) + } } override fun onRestart() { @@ -212,10 +208,6 @@ abstract class BaseActivity : AppCompatActivity() { override fun onDestroy() { destroyed = true - - if (!compositeSubscription.isDisposed) { - compositeSubscription.dispose() - } super.onDestroy() } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/ChallengeFormActivity.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/ChallengeFormActivity.kt index 9e56e29c7..64e6fecf1 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/ChallengeFormActivity.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/ChallengeFormActivity.kt @@ -25,6 +25,7 @@ import com.habitrpg.android.habitica.databinding.ActivityCreateChallengeBinding import com.habitrpg.android.habitica.extensions.addCloseButton import com.habitrpg.android.habitica.helpers.ExceptionHandler import com.habitrpg.android.habitica.helpers.MainNavigationController +import com.habitrpg.android.habitica.helpers.launchCatching import com.habitrpg.android.habitica.models.social.Challenge import com.habitrpg.android.habitica.models.social.Group import com.habitrpg.android.habitica.models.tasks.Task @@ -38,7 +39,6 @@ import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaProgressDialog import com.habitrpg.common.habitica.extensions.getThemeColor import com.habitrpg.shared.habitica.models.tasks.TaskType -import io.reactivex.rxjava3.core.Flowable import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.flow.firstOrNull @@ -52,8 +52,10 @@ class ChallengeFormActivity : BaseActivity() { @Inject internal lateinit var challengeRepository: ChallengeRepository + @Inject internal lateinit var socialRepository: SocialRepository + @Inject internal lateinit var userViewModel: MainUserViewModel @@ -69,6 +71,7 @@ class ChallengeFormActivity : BaseActivity() { private val removedTasks = HashMap() override var overrideModernHeader: Boolean? = true + // Add {*} Items private lateinit var addHabit: Task private lateinit var addDaily: Task @@ -130,37 +133,34 @@ class ChallengeFormActivity : BaseActivity() { savingInProgress = true val dialog = HabiticaProgressDialog.show(this, R.string.saving) - val observable: Flowable = if (editMode) { - updateChallenge() - } else { - createChallenge() - } + lifecycleScope.launchCatching({ + dialog?.dismiss() + savingInProgress = false + ExceptionHandler.reportError(it) + }) { + val challenge = if (editMode) { + updateChallenge() + } else { + createChallenge() + } - compositeSubscription.add( - observable - .flatMap { - challengeId = it.id - challengeRepository.retrieveChallenges(0, true) + challengeId = challenge?.id + challengeRepository.retrieveChallenges(0, true) + + dialog?.dismiss() + savingInProgress = false + finish() + if (!editMode) { + lifecycleScope.launch(context = Dispatchers.Main) { + delay(500L) + MainNavigationController.navigate( + ChallengesOverviewFragmentDirections.openChallengeDetail( + challengeId ?: "" + ) + ) } - .subscribe( - { - dialog?.dismiss() - savingInProgress = false - finish() - if (!editMode) { - lifecycleScope.launch(context = Dispatchers.Main) { - delay(500L) - MainNavigationController.navigate(ChallengesOverviewFragmentDirections.openChallengeDetail(challengeId ?: "")) - } - } - }, - { throwable -> - dialog?.dismiss() - savingInProgress = false - ExceptionHandler.reportError(throwable) - } - ) - ) + } + } } else if (item.itemId == android.R.id.home) { finish() return true @@ -229,13 +229,11 @@ class ChallengeFormActivity : BaseActivity() { openTaskDisabled = false, taskActionsDisabled = true ).also { challengeTasks = it } - compositeSubscription.add( - challengeTasks.taskOpenEvents.subscribe { - if (it.isValid) { - openNewTaskActivity(it.type, it) - } + challengeTasks.onTaskOpen = { + if (it.isValid) { + openNewTaskActivity(it.type, it) } - ) + } locationAdapter = GroupArrayAdapter(this) if (bundle != null) { @@ -339,7 +337,8 @@ class ChallengeFormActivity : BaseActivity() { locationAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item) lifecycleScope.launch(ExceptionHandler.coroutine()) { - val groups = socialRepository.getUserGroups("guild").firstOrNull()?.toMutableList() ?: return@launch + val groups = socialRepository.getUserGroups("guild").firstOrNull()?.toMutableList() + ?: return@launch val partyID = userRepository.getUser().firstOrNull()?.party?.id val party = if (partyID?.isNotBlank() == true) { socialRepository.retrieveGroup(partyID) @@ -360,13 +359,20 @@ class ChallengeFormActivity : BaseActivity() { } binding.challengeLocationSpinner.adapter = locationAdapter - binding.challengeLocationSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener { - override fun onItemSelected(adapterView: AdapterView<*>, view: View?, i: Int, l: Long) { - checkPrizeAndMinimumForTavern() - } + binding.challengeLocationSpinner.onItemSelectedListener = + object : AdapterView.OnItemSelectedListener { + override fun onItemSelected( + adapterView: AdapterView<*>, + view: View?, + i: Int, + l: Long + ) { + checkPrizeAndMinimumForTavern() + } - override fun onNothingSelected(adapterView: AdapterView<*>) { /* no-on */ } - } + override fun onNothingSelected(adapterView: AdapterView<*>) { /* no-on */ + } + } binding.createChallengePrize.setOnKeyListener { _, _, _ -> checkPrizeAndMinimumForTavern() @@ -380,21 +386,17 @@ class ChallengeFormActivity : BaseActivity() { taskList.add(addReward) challengeTasks.setTasks(taskList) - compositeSubscription.add( - challengeTasks.addItemObservable().subscribe( - { t -> - when (t.text) { - addHabit.text -> openNewTaskActivity(TaskType.HABIT, null) - addDaily.text -> openNewTaskActivity(TaskType.DAILY, null) - addTodo.text -> openNewTaskActivity(TaskType.TODO, null) - addReward.text -> openNewTaskActivity(TaskType.REWARD, null) - } - }, - ExceptionHandler.rx() - ) - ) + challengeTasks.onTaskOpen = { t -> + when (t.text) { + addHabit.text -> openNewTaskActivity(TaskType.HABIT, null) + addDaily.text -> openNewTaskActivity(TaskType.DAILY, null) + addTodo.text -> openNewTaskActivity(TaskType.TODO, null) + addReward.text -> openNewTaskActivity(TaskType.REWARD, null) + } + } - binding.createChallengeTaskList.addOnItemTouchListener(object : androidx.recyclerview.widget.RecyclerView.SimpleOnItemTouchListener() { + binding.createChallengeTaskList.addOnItemTouchListener(object : + androidx.recyclerview.widget.RecyclerView.SimpleOnItemTouchListener() { override fun onInterceptTouchEvent( rv: androidx.recyclerview.widget.RecyclerView, e: MotionEvent @@ -404,13 +406,14 @@ class ChallengeFormActivity : BaseActivity() { } }) binding.createChallengeTaskList.adapter = challengeTasks - binding.createChallengeTaskList.layoutManager = androidx.recyclerview.widget.LinearLayoutManager(this) + binding.createChallengeTaskList.layoutManager = + androidx.recyclerview.widget.LinearLayoutManager(this) } private fun fillControlsByChallenge() { challengeId?.let { - challengeRepository.getChallenge(it).subscribe( - { challenge -> + lifecycleScope.launchCatching { + challengeRepository.getChallenge(it).collect { challenge -> groupID = challenge.groupId editMode = true binding.createChallengeTitle.setText(challenge.name) @@ -428,17 +431,15 @@ class ChallengeFormActivity : BaseActivity() { } } checkPrizeAndMinimumForTavern() - }, - ExceptionHandler.rx() - ) - challengeRepository.getChallengeTasks(it).subscribe( - { tasks -> + } + } + lifecycleScope.launchCatching { + challengeRepository.getChallengeTasks(it).collect { tasks -> tasks.forEach { task -> addOrUpdateTaskInList(task, true) } - }, - ExceptionHandler.rx() - ) + } + } } } @@ -460,16 +461,17 @@ class ChallengeFormActivity : BaseActivity() { newTaskResult.launch(intent) } - private val newTaskResult = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { - if (it.resultCode == Activity.RESULT_OK) { - val task = it.data?.getParcelableExtra(TaskFormActivity.PARCELABLE_TASK) - if (task != null) { - addOrUpdateTaskInList(task) + private val newTaskResult = + registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { + if (it.resultCode == Activity.RESULT_OK) { + val task = it.data?.getParcelableExtra(TaskFormActivity.PARCELABLE_TASK) + if (task != null) { + addOrUpdateTaskInList(task) + } } } - } - private fun createChallenge(): Flowable { + private suspend fun createChallenge(): Challenge? { val c = challengeData val taskList = challengeTasks.taskList @@ -481,7 +483,7 @@ class ChallengeFormActivity : BaseActivity() { return challengeRepository.createChallenge(c, taskList) } - private fun updateChallenge(): Flowable { + private suspend fun updateChallenge(): Challenge? { val c = challengeData val taskList = challengeTasks.taskList @@ -528,7 +530,8 @@ class ChallengeFormActivity : BaseActivity() { return editText.text.toString() } - private class GroupArrayAdapter(context: Context) : ArrayAdapter(context, android.R.layout.simple_spinner_item) { + private class GroupArrayAdapter(context: Context) : + ArrayAdapter(context, android.R.layout.simple_spinner_item) { override fun getView(position: Int, convertView: View?, parent: ViewGroup): View { val checkedTextView = super.getView(position, convertView, parent) as? TextView @@ -537,7 +540,8 @@ class ChallengeFormActivity : BaseActivity() { } override fun getDropDownView(position: Int, convertView: View?, parent: ViewGroup): View { - val checkedTextView = super.getDropDownView(position, convertView, parent) as? AppCompatCheckedTextView + val checkedTextView = + super.getDropDownView(position, convertView, parent) as? AppCompatCheckedTextView checkedTextView?.text = getItem(position)?.name return checkedTextView ?: View(context) } 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 4b598252a..7b1adc430 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 @@ -24,6 +24,7 @@ import com.habitrpg.android.habitica.extensions.addCancelButton import com.habitrpg.android.habitica.helpers.ExceptionHandler import com.habitrpg.android.habitica.helpers.MainNavigationController import com.habitrpg.android.habitica.helpers.UserStatComputer +import com.habitrpg.android.habitica.helpers.launchCatching import com.habitrpg.android.habitica.models.Achievement import com.habitrpg.android.habitica.models.inventory.Equipment import com.habitrpg.android.habitica.models.members.Member @@ -39,10 +40,10 @@ import com.habitrpg.common.habitica.extensions.loadImage import com.habitrpg.common.habitica.helpers.RecyclerViewState import com.habitrpg.common.habitica.helpers.setMarkdown import com.habitrpg.common.habitica.views.PixelArtView -import io.reactivex.rxjava3.core.Flowable import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.MainScope import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.launch import java.text.SimpleDateFormat import javax.inject.Inject @@ -54,8 +55,10 @@ class FullProfileActivity : BaseActivity() { @Inject lateinit var inventoryRepository: InventoryRepository + @Inject lateinit var apiClient: ApiClient + @Inject lateinit var socialRepository: SocialRepository @@ -91,7 +94,12 @@ class FullProfileActivity : BaseActivity() { } avatarWithBars = AvatarWithBarsViewModel(this, binding.avatarWithBars) - binding.avatarWithBars.root.setBackgroundColor(ContextCompat.getColor(this, R.color.transparent)) + binding.avatarWithBars.root.setBackgroundColor( + ContextCompat.getColor( + this, + R.color.transparent + ) + ) binding.avatarWithBars.hpBar.barBackgroundColor = getThemeColor(R.color.window_background) binding.avatarWithBars.xpBar.barBackgroundColor = getThemeColor(R.color.window_background) binding.avatarWithBars.mpBar.barBackgroundColor = getThemeColor(R.color.window_background) @@ -100,13 +108,24 @@ class FullProfileActivity : BaseActivity() { binding.attributesCardView.setOnClickListener { toggleAttributeDetails() } binding.sendMessageButton.setOnClickListener { showSendMessageToUserDialog() } - binding.giftGemsButton.setOnClickListener { MainNavigationController.navigate(R.id.giftGemsActivity, bundleOf(Pair("userID", userID), Pair("username", null))) } - binding.giftSubscriptionButton.setOnClickListener { MainNavigationController.navigate(R.id.giftSubscriptionActivity, bundleOf(Pair("userID", userID), Pair("username", null))) } + binding.giftGemsButton.setOnClickListener { + MainNavigationController.navigate( + R.id.giftGemsActivity, + bundleOf(Pair("userID", userID), Pair("username", null)) + ) + } + binding.giftSubscriptionButton.setOnClickListener { + MainNavigationController.navigate( + R.id.giftSubscriptionActivity, + bundleOf(Pair("userID", userID), Pair("username", null)) + ) + } lifecycleScope.launch(ExceptionHandler.coroutine()) { userRepository.getUser() .collect { blocks = it?.inbox?.blocks ?: listOf() - binding.blockedDisclaimerView.visibility = if (isUserBlocked()) View.VISIBLE else View.GONE + binding.blockedDisclaimerView.visibility = + if (isUserBlocked()) View.VISIBLE else View.GONE } } } @@ -139,22 +158,26 @@ class FullProfileActivity : BaseActivity() { true } R.id.copy_username -> { - val clipboard = this.getSystemService(Context.CLIPBOARD_SERVICE) as? android.content.ClipboardManager + val clipboard = + this.getSystemService(Context.CLIPBOARD_SERVICE) as? android.content.ClipboardManager val clip = android.content.ClipData.newPlainText(username, username) clipboard?.setPrimaryClip(clip) HabiticaSnackbar.showSnackbar( this@FullProfileActivity.binding.scrollView.getChildAt(0) as ViewGroup, - String.format(getString(R.string.username_copied), userDisplayName), SnackbarDisplayType.NORMAL + String.format(getString(R.string.username_copied), userDisplayName), + SnackbarDisplayType.NORMAL ) true } R.id.copy_userid -> { - val clipboard = this.getSystemService(Context.CLIPBOARD_SERVICE) as? android.content.ClipboardManager + val clipboard = + this.getSystemService(Context.CLIPBOARD_SERVICE) as? android.content.ClipboardManager val clip = android.content.ClipData.newPlainText(userID, userID) clipboard?.setPrimaryClip(clip) HabiticaSnackbar.showSnackbar( this@FullProfileActivity.binding.scrollView.getChildAt(0) as ViewGroup, - String.format(getString(R.string.id_copied), userDisplayName), SnackbarDisplayType.NORMAL + String.format(getString(R.string.id_copied), userDisplayName), + SnackbarDisplayType.NORMAL ) true } @@ -171,17 +194,11 @@ class FullProfileActivity : BaseActivity() { } private fun useBlock() { - compositeSubscription.add( - socialRepository.blockMember(userID).subscribe( - { - lifecycleScope.launch(ExceptionHandler.coroutine()) { - userRepository.retrieveUser() - invalidateOptionsMenu() - } - }, - ExceptionHandler.rx() - ) - ) + lifecycleScope.launchCatching { + socialRepository.blockMember(userID) + userRepository.retrieveUser() + invalidateOptionsMenu() + } } private fun showBlockDialog() { @@ -199,7 +216,10 @@ class FullProfileActivity : BaseActivity() { finish() MainScope().launch(context = Dispatchers.Main) { delay(500L) - MainNavigationController.navigate(R.id.inboxMessageListFragment, bundleOf(Pair("username", username), Pair("userID", userID))) + MainNavigationController.navigate( + R.id.inboxMessageListFragment, + bundleOf(Pair("username", username), Pair("userID", userID)) + ) } } @@ -226,22 +246,33 @@ class FullProfileActivity : BaseActivity() { binding.blurbTextView.movementMethod = LinkMovementMethod.getInstance() } - user.authentication?.timestamps?.createdAt?.let { binding.joinedView.text = dateFormatter.format(it) } - user.authentication?.timestamps?.lastLoggedIn?.let { binding.lastLoginView.text = dateFormatter.format(it) } + user.authentication?.timestamps?.createdAt?.let { + binding.joinedView.text = dateFormatter.format(it) + } + user.authentication?.timestamps?.lastLoggedIn?.let { + binding.lastLoginView.text = dateFormatter.format(it) + } binding.totalCheckinsView.text = user.loginIncentives.toString() avatarWithBars?.updateData(user) - compositeSubscription.add(loadItemDataByOutfit(user.equipped).subscribe({ gear -> this.gotGear(gear, user) }, ExceptionHandler.rx())) + lifecycleScope.launchCatching { + loadItemDataByOutfit(user.equipped).collect { gear -> gotGear(gear, user) } + } if (user.preferences?.costume == true) { - compositeSubscription.add(loadItemDataByOutfit(user.costume).subscribe({ this.gotCostume(it) }, ExceptionHandler.rx())) + lifecycleScope.launchCatching { + loadItemDataByOutfit(user.costume).collect { gotCostume(it) } + } } else { binding.costumeCard.visibility = View.GONE } // Load the members achievements now - compositeSubscription.add(socialRepository.getMemberAchievements(this.userID).subscribe({ this.fillAchievements(it) }, ExceptionHandler.rx())) + lifecycleScope.launchCatching { + val achievements = socialRepository.getMemberAchievements(userID) + fillAchievements(achievements) + } } private fun updatePetsMountsView(user: Member) { @@ -252,9 +283,9 @@ class FullProfileActivity : BaseActivity() { if (user.currentMount?.isNotBlank() == true) binding.currentMountDrawee.loadImage("Mount_Icon_" + user.currentMount) } - // endregion +// endregion - // region Attributes +// region Attributes private fun fillAchievements(achievements: List?) { if (achievements == null) { @@ -262,23 +293,36 @@ class FullProfileActivity : BaseActivity() { } val items = ArrayList() - fillAchievements(R.string.basic_achievements, achievements.filter { it.category == "basic" }, items) - fillAchievements(R.string.seasonal_achievements, achievements.filter { it.category == "seasonal" }, items) - fillAchievements(R.string.special_achievements, achievements.filter { it.category == "special" }, items) + fillAchievements( + R.string.basic_achievements, + achievements.filter { it.category == "basic" }, + items + ) + fillAchievements( + R.string.seasonal_achievements, + achievements.filter { it.category == "seasonal" }, + items + ) + fillAchievements( + R.string.special_achievements, + achievements.filter { it.category == "special" }, + items + ) val adapter = AchievementProfileAdapter() adapter.setItemList(items) val layoutManager = androidx.recyclerview.widget.GridLayoutManager(this, 3) - layoutManager.spanSizeLookup = object : androidx.recyclerview.widget.GridLayoutManager.SpanSizeLookup() { - override fun getSpanSize(position: Int): Int { - return if (adapter.getItemViewType(position) == 0) { - layoutManager.spanCount - } else { - 1 + layoutManager.spanSizeLookup = + object : androidx.recyclerview.widget.GridLayoutManager.SpanSizeLookup() { + override fun getSpanSize(position: Int): Int { + return if (adapter.getItemViewType(position) == 0) { + layoutManager.spanCount + } else { + 1 + } } } - } binding.achievementGroupList.layoutManager = layoutManager binding.achievementGroupList.adapter = adapter @@ -292,7 +336,9 @@ class FullProfileActivity : BaseActivity() { ) { // Order by ID first val achievementList = ArrayList(achievements) - achievementList.sortWith { achievement, t1 -> achievement.index.toDouble().compareTo(t1.index.toDouble()) } + achievementList.sortWith { achievement, t1 -> + achievement.index.toDouble().compareTo(t1.index.toDouble()) + } targetList.add(getString(labelID)) targetList.addAll(achievementList) @@ -318,8 +364,14 @@ class FullProfileActivity : BaseActivity() { } } - private fun addEquipmentRow(table: TableLayout, gearKey: String?, text: String?, stats: String?) { - val gearRow = layoutInflater.inflate(R.layout.profile_gear_tablerow, table, false) as? TableRow + private fun addEquipmentRow( + table: TableLayout, + gearKey: String?, + text: String?, + stats: String? + ) { + val gearRow = + layoutInflater.inflate(R.layout.profile_gear_tablerow, table, false) as? TableRow val draweeView = gearRow?.findViewById(R.id.gear_drawee) @@ -348,7 +400,7 @@ class FullProfileActivity : BaseActivity() { ) } - private fun loadItemDataByOutfit(outfit: Outfit?): Flowable> { + private fun loadItemDataByOutfit(outfit: Outfit?): Flow> { val outfitList = ArrayList() if (outfit != null) { outfitList.add(outfit.armor) @@ -381,7 +433,15 @@ class FullProfileActivity : BaseActivity() { if (row is UserStatComputer.EquipmentRow) { addEquipmentRow(binding.equipmentTableLayout, row.gearKey, row.text, row.stats) } else if (row is UserStatComputer.AttributeRow) { - addAttributeRow(getString(row.labelId), row.strVal, row.intVal, row.conVal, row.perVal, row.roundDown, row.summary) + addAttributeRow( + getString(row.labelId), + row.strVal, + row.intVal, + row.conVal, + row.perVal, + row.roundDown, + row.summary + ) } } @@ -400,7 +460,11 @@ class FullProfileActivity : BaseActivity() { val buffs = stats.buffs addAttributeRow( - getString(R.string.profile_allocated), stats.strength?.toFloat() ?: 0f, stats.intelligence?.toFloat() ?: 0f, stats.constitution?.toFloat() ?: 0f, stats.per?.toFloat() ?: 0f, + getString(R.string.profile_allocated), + stats.strength?.toFloat() ?: 0f, + stats.intelligence?.toFloat() ?: 0f, + stats.constitution?.toFloat() ?: 0f, + stats.per?.toFloat() ?: 0f, roundDown = true, isSummary = false ) @@ -408,11 +472,23 @@ class FullProfileActivity : BaseActivity() { getString(R.string.buffs), buffs?.str ?: 0f, - buffs?._int ?: 0f, buffs?.con ?: 0f, buffs?.per ?: 0f, roundDown = true, isSummary = false + buffs?._int ?: 0f, + buffs?.con ?: 0f, + buffs?.per ?: 0f, + roundDown = true, + isSummary = false ) // Summary row - addAttributeRow("", attributeStrSum, attributeIntSum, attributeConSum, attributePerSum, roundDown = false, isSummary = true) + addAttributeRow( + "", + attributeStrSum, + attributeIntSum, + attributeConSum, + attributePerSum, + roundDown = false, + isSummary = true + ) } private fun addAttributeRow( @@ -424,7 +500,11 @@ class FullProfileActivity : BaseActivity() { roundDown: Boolean, isSummary: Boolean ) { - val tableRow = layoutInflater.inflate(R.layout.profile_attributetablerow, binding.attributesTableLayout, false) as? TableRow ?: return + val tableRow = layoutInflater.inflate( + R.layout.profile_attributetablerow, + binding.attributesTableLayout, + false + ) as? TableRow ?: return val keyTextView = tableRow.findViewById(R.id.tv_attribute_type) keyTextView?.text = label @@ -475,9 +555,9 @@ class FullProfileActivity : BaseActivity() { } } - // endregion +// endregion - // region Navigation +// region Navigation override fun onSupportNavigateUp(): Boolean { finish() @@ -488,9 +568,9 @@ class FullProfileActivity : BaseActivity() { finish() } - // endregion +// endregion - // region BaseActivity-Overrides +// region BaseActivity-Overrides override fun getLayoutResId(): Int { return R.layout.activity_full_profile @@ -517,5 +597,5 @@ class FullProfileActivity : BaseActivity() { } } - // endregion +// endregion } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/HabitButtonWidgetActivity.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/HabitButtonWidgetActivity.kt index 1b7c805a8..10f111e5f 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/HabitButtonWidgetActivity.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/HabitButtonWidgetActivity.kt @@ -75,10 +75,9 @@ class HabitButtonWidgetActivity : BaseActivity() { } adapter = SkillTasksRecyclerViewAdapter() - adapter?.getTaskSelectionEvents()?.subscribe( - { task -> taskSelected(task.id) }, - ExceptionHandler.rx() - )?.let { compositeSubscription.add(it) } + adapter?.onTaskSelection = { + taskSelected(it.id) + } binding.recyclerView.adapter = adapter CoroutineScope(Dispatchers.Main + job).launch(ExceptionHandler.coroutine()) { 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 e2ec696a9..5d4e59bd3 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 @@ -42,6 +42,7 @@ import com.habitrpg.android.habitica.extensions.updateStatusBarColor import com.habitrpg.android.habitica.helpers.AmplitudeManager import com.habitrpg.android.habitica.helpers.AppConfigManager import com.habitrpg.android.habitica.helpers.ExceptionHandler +import com.habitrpg.android.habitica.helpers.launchCatching import com.habitrpg.android.habitica.models.user.User import com.habitrpg.android.habitica.ui.helpers.dismissKeyboard import com.habitrpg.android.habitica.ui.viewmodels.AuthenticationViewModel @@ -81,14 +82,15 @@ class LoginActivity : BaseActivity() { showValidationError(getString(R.string.password_too_short, configManager.minimumPasswordLength())) return@OnClickListener } - apiClient.registerUser(username, email, password, confirmPassword) - .subscribe( - { handleAuthResponse(it) }, - { - hideProgress() - ExceptionHandler.reportError(it) - } - ) + lifecycleScope.launch(ExceptionHandler.coroutine { + hideProgress() + ExceptionHandler.reportError(it) + }) { + val response = apiClient.registerUser(username, email, password, confirmPassword) + if (response != null) { + handleAuthResponse(response) + } + } } else { val username: String = binding.username.text.toString().trim { it <= ' ' } val password: String = binding.password.text.toString() @@ -97,14 +99,15 @@ class LoginActivity : BaseActivity() { return@OnClickListener } Log.d("LoginActivity", ": $username, $password") - apiClient.connectUser(username, password).subscribe( - { handleAuthResponse(it) }, - { - hideProgress() - ExceptionHandler.reportError(it) - Log.d("LoginActivity", ": ${it.message}", it) + lifecycleScope.launch(ExceptionHandler.coroutine { + hideProgress() + ExceptionHandler.reportError(it) + }) { + val response = apiClient.connectUser(username, password) + if (response != null) { + handleAuthResponse(response) } - ) + } } } @@ -450,7 +453,10 @@ class LoginActivity : BaseActivity() { alertDialog.setMessage(R.string.forgot_password_description) alertDialog.setAdditionalContentView(input) alertDialog.addButton(R.string.send, true) { _, _ -> - userRepository.sendPasswordResetEmail(input.text.toString()).subscribe({ showPasswordEmailConfirmation() }, ExceptionHandler.rx()) + lifecycleScope.launchCatching { + userRepository.sendPasswordResetEmail(input.text.toString()) + showPasswordEmailConfirmation() + } } alertDialog.addCancelButton() alertDialog.show() diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/MainActivity.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/MainActivity.kt index 64dcf69f2..96c564248 100755 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/MainActivity.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/MainActivity.kt @@ -22,7 +22,6 @@ import androidx.navigation.NavDestination import androidx.navigation.findNavController import androidx.navigation.fragment.NavHostFragment import com.google.android.gms.wearable.Wearable -import com.google.android.material.composethemeadapter.MdcTheme import com.google.firebase.perf.FirebasePerformance import com.habitrpg.android.habitica.BuildConfig import com.habitrpg.android.habitica.R @@ -33,7 +32,6 @@ import com.habitrpg.android.habitica.data.TaskRepository import com.habitrpg.android.habitica.databinding.ActivityMainBinding import com.habitrpg.android.habitica.extensions.hideKeyboard import com.habitrpg.android.habitica.extensions.observeOnce -import com.habitrpg.android.habitica.extensions.subscribeWithErrorHandler import com.habitrpg.android.habitica.extensions.updateStatusBarColor import com.habitrpg.android.habitica.helpers.AmplitudeManager import com.habitrpg.android.habitica.helpers.AppConfigManager @@ -41,6 +39,7 @@ import com.habitrpg.android.habitica.helpers.ExceptionHandler import com.habitrpg.android.habitica.helpers.MainNavigationController import com.habitrpg.android.habitica.helpers.NotificationOpenHandler import com.habitrpg.android.habitica.helpers.SoundManager +import com.habitrpg.android.habitica.helpers.launchCatching import com.habitrpg.android.habitica.interactors.CheckClassSelectionUseCase import com.habitrpg.android.habitica.interactors.DisplayItemDropUseCase import com.habitrpg.android.habitica.interactors.NotifyUserUseCase @@ -154,11 +153,11 @@ open class MainActivity : BaseActivity(), SnackbarActivity { viewModel.user.observe(this) { setUserData(it) } - compositeSubscription.add( - userRepository.getUserQuestStatus().subscribeWithErrorHandler { + lifecycleScope.launchCatching { + userRepository.getUserQuestStatus().collect { userQuestStatus = it } - ) + } val drawerLayout = findViewById(R.id.drawer_layout) drawerFragment = supportFragmentManager.findFragmentById(R.id.navigation_drawer) as? NavigationDrawerFragment @@ -383,7 +382,7 @@ open class MainActivity : BaseActivity(), SnackbarActivity { if (user != null) { val preferences = user.preferences - preferences?.language?.let { apiClient.setLanguageCode(it) } + preferences?.language?.let { apiClient.languageCode = it } if (preferences?.language != viewModel.preferenceLanguage) { viewModel.preferenceLanguage = preferences?.language } @@ -392,11 +391,6 @@ open class MainActivity : BaseActivity(), SnackbarActivity { displayDeathDialogIfNeeded() YesterdailyDialog.showDialogIfNeeded(this, user.id, userRepository, taskRepository) - if (user.flags?.verifiedUsername == false && isActivityVisible) { - val intent = Intent(this, VerifyUsernameActivity::class.java) - startActivity(intent) - } - val quest = user.party?.quest if (quest?.completed?.isNotBlank() == true) { lifecycleScope.launch(ExceptionHandler.coroutine()) { @@ -447,22 +441,35 @@ open class MainActivity : BaseActivity(), SnackbarActivity { UserQuestStatus.QUEST_BOSS -> data.questDamage else -> 0.0 } - compositeSubscription.add( - notifyUserUseCase.observable( + lifecycleScope.launchCatching { + notifyUserUseCase.callInteractor( NotifyUserUseCase.RequestValues( - this, snackbarContainer, - viewModel.user.value, data.experienceDelta, data.healthDelta, data.goldDelta, data.manaDelta, damageValue, data.hasLeveledUp, data.level + this@MainActivity, + snackbarContainer, + viewModel.user.value, + data.experienceDelta, + data.healthDelta, + data.goldDelta, + data.manaDelta, + damageValue, + data.hasLeveledUp, + data.level ) ) - .subscribe({ }, ExceptionHandler.rx()) - ) + } } val showItemsFound = userQuestStatus == UserQuestStatus.QUEST_COLLECT - compositeSubscription.add( - displayItemDropUseCase.observable(DisplayItemDropUseCase.RequestValues(data, this, snackbarContainer, showItemsFound)) - .subscribe({ }, ExceptionHandler.rx()) - ) + lifecycleScope.launchCatching { + displayItemDropUseCase.callInteractor( + DisplayItemDropUseCase.RequestValues( + data, + this@MainActivity, + snackbarContainer, + showItemsFound + ) + ) + } } private var lastDeathDialogDisplay = 0L diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/MaintenanceActivity.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/MaintenanceActivity.kt index 386d8cd5f..4dea75377 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/MaintenanceActivity.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/MaintenanceActivity.kt @@ -5,15 +5,14 @@ import android.os.Bundle import android.text.method.LinkMovementMethod import android.view.View import androidx.core.net.toUri +import androidx.lifecycle.lifecycleScope import com.habitrpg.android.habitica.R import com.habitrpg.android.habitica.api.MaintenanceApiService import com.habitrpg.android.habitica.components.UserComponent import com.habitrpg.android.habitica.data.ApiClient import com.habitrpg.android.habitica.databinding.ActivityMaintenanceBinding -import com.habitrpg.android.habitica.helpers.ExceptionHandler +import com.habitrpg.android.habitica.helpers.launchCatching import com.habitrpg.common.habitica.helpers.setMarkdown -import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers -import io.reactivex.rxjava3.schedulers.Schedulers import javax.inject.Inject class MaintenanceActivity : BaseActivity() { @@ -66,19 +65,12 @@ class MaintenanceActivity : BaseActivity() { override fun onResume() { super.onResume() if (!isDeprecationNotice) { - compositeSubscription.add( - this.maintenanceService.maintenanceStatus - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe( - { maintenanceResponse -> - if (maintenanceResponse.activeMaintenance == false) { - finish() - } - }, - ExceptionHandler.rx() - ) - ) + lifecycleScope.launchCatching { + val maintenanceResponse = maintenanceService.getMaintenanceStatus() + if (maintenanceResponse?.activeMaintenance == false) { + finish() + } + } } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/NotificationsActivity.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/NotificationsActivity.kt index 6ccfc37ea..a8fbfa936 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/NotificationsActivity.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/NotificationsActivity.kt @@ -21,6 +21,7 @@ import com.habitrpg.android.habitica.data.SocialRepository import com.habitrpg.android.habitica.databinding.ActivityNotificationsBinding import com.habitrpg.android.habitica.extensions.fromHtml import com.habitrpg.android.habitica.helpers.ExceptionHandler +import com.habitrpg.android.habitica.helpers.launchCatching import com.habitrpg.android.habitica.models.inventory.QuestContent import com.habitrpg.android.habitica.ui.viewmodels.NotificationsViewModel import com.habitrpg.common.habitica.models.Notification @@ -65,15 +66,12 @@ class NotificationsActivity : BaseActivity(), androidx.swiperefreshlayout.widget inflater = getSystemService(Context.LAYOUT_INFLATER_SERVICE) as? LayoutInflater - compositeSubscription.add( - viewModel.getNotifications().subscribe( - { - this.setNotifications(it) - viewModel.markNotificationsAsSeen(it) - }, - ExceptionHandler.rx() - ) - ) + lifecycleScope.launchCatching { + viewModel.getNotifications().collect { + setNotifications(it) + viewModel.markNotificationsAsSeen(it) + } + } binding.notificationsRefreshLayout.setOnRefreshListener(this) } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/ReportMessageActivity.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/ReportMessageActivity.kt index b08d214da..fbebe5d71 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/ReportMessageActivity.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/ReportMessageActivity.kt @@ -8,16 +8,18 @@ import android.os.Bundle import android.view.View import androidx.annotation.RequiresApi import androidx.core.content.ContextCompat +import androidx.lifecycle.lifecycleScope import androidx.navigation.navArgs import com.google.android.material.bottomsheet.BottomSheetBehavior import com.habitrpg.android.habitica.R import com.habitrpg.android.habitica.components.UserComponent import com.habitrpg.android.habitica.data.SocialRepository import com.habitrpg.android.habitica.databinding.ActivityReportMessageBinding -import com.habitrpg.common.habitica.extensions.getThemeColor import com.habitrpg.android.habitica.helpers.ExceptionHandler import com.habitrpg.android.habitica.ui.helpers.dismissKeyboard +import com.habitrpg.common.habitica.extensions.getThemeColor import com.habitrpg.common.habitica.helpers.setMarkdown +import kotlinx.coroutines.launch import javax.inject.Inject class ReportMessageActivity : BaseActivity() { @@ -94,12 +96,11 @@ class ReportMessageActivity : BaseActivity() { } isReporting = true messageID?.let { - socialRepository.flagMessage(it, binding.additionalInfoEdittext.text.toString(), groupID) - .doOnError { isReporting = false } - .subscribe( - { finish() }, - ExceptionHandler.rx() - ) + lifecycleScope.launch(ExceptionHandler.coroutine { + isReporting = false + }) { + finish() + } } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/SetupActivity.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/SetupActivity.kt index ec94f2fc8..54e51ff75 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/SetupActivity.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/SetupActivity.kt @@ -25,6 +25,7 @@ import com.habitrpg.android.habitica.data.TaskRepository import com.habitrpg.android.habitica.databinding.ActivitySetupBinding import com.habitrpg.android.habitica.helpers.AmplitudeManager import com.habitrpg.android.habitica.helpers.ExceptionHandler +import com.habitrpg.android.habitica.helpers.launchCatching import com.habitrpg.android.habitica.models.user.User import com.habitrpg.android.habitica.ui.fragments.setup.AvatarSetupFragment import com.habitrpg.android.habitica.ui.fragments.setup.TaskSetupFragment @@ -84,10 +85,9 @@ class SetupActivity : BaseActivity(), ViewPager.OnPageChangeListener { val currentDeviceLanguage = Locale.getDefault().language for (language in resources.getStringArray(R.array.LanguageValues)) { if (language == currentDeviceLanguage) { - compositeSubscription.add( + lifecycleScope.launchCatching { apiClient.registrationLanguage(currentDeviceLanguage) - .subscribe({ }, ExceptionHandler.rx()) - ) + } } } @@ -141,7 +141,9 @@ class SetupActivity : BaseActivity(), ViewPager.OnPageChangeListener { this.completedSetup = true createdTasks = true newTasks?.let { - this.taskRepository.createTasks(it).subscribe({ onUserReceived(user) }, ExceptionHandler.rx()) + lifecycleScope.launchCatching { + taskRepository.createTasks(it) + } } } else if (binding.viewPager.currentItem == 0) { 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 dc4a75519..1fe0effb4 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 @@ -11,6 +11,7 @@ import com.habitrpg.android.habitica.components.UserComponent import com.habitrpg.android.habitica.data.SocialRepository import com.habitrpg.android.habitica.databinding.ActivitySkillMembersBinding import com.habitrpg.android.habitica.helpers.ExceptionHandler +import com.habitrpg.android.habitica.helpers.launchCatching import com.habitrpg.android.habitica.ui.adapter.social.PartyMemberRecyclerViewAdapter import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel import kotlinx.coroutines.flow.filterNotNull @@ -50,15 +51,14 @@ class SkillMemberActivity : BaseActivity() { private fun loadMemberList() { binding.recyclerView.layoutManager = LinearLayoutManager(this) viewAdapter = PartyMemberRecyclerViewAdapter() - viewAdapter?.getUserClickedEvents()?.subscribe( - { userId -> + viewAdapter?.onUserClicked = { + lifecycleScope.launchCatching { val resultIntent = Intent() - resultIntent.putExtra("member_id", userId) + resultIntent.putExtra("member_id", it) setResult(Activity.RESULT_OK, resultIntent) finish() - }, - ExceptionHandler.rx() - )?.let { compositeSubscription.add(it) } + } + } binding.recyclerView.adapter = viewAdapter lifecycleScope.launch(ExceptionHandler.coroutine()) { diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/SkillTasksActivity.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/SkillTasksActivity.kt index 10c8a9062..caa7afae3 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/SkillTasksActivity.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/SkillTasksActivity.kt @@ -13,11 +13,10 @@ import com.habitrpg.android.habitica.R import com.habitrpg.android.habitica.components.UserComponent import com.habitrpg.android.habitica.data.TaskRepository import com.habitrpg.android.habitica.databinding.ActivitySkillTasksBinding -import com.habitrpg.android.habitica.helpers.ExceptionHandler import com.habitrpg.android.habitica.models.tasks.Task -import com.habitrpg.shared.habitica.models.tasks.TaskType import com.habitrpg.android.habitica.modules.AppModule import com.habitrpg.android.habitica.ui.fragments.skills.SkillTasksRecyclerViewFragment +import com.habitrpg.shared.habitica.models.tasks.TaskType import javax.inject.Inject import javax.inject.Named @@ -60,7 +59,9 @@ class SkillTasksActivity : BaseActivity() { 1 -> TaskType.DAILY else -> TaskType.TODO } - compositeSubscription.add(fragment.getTaskSelectionEvents().subscribe({ task -> taskSelected(task) }, ExceptionHandler.rx())) + fragment.onTaskSelection = { + taskSelected(it) + } viewFragmentsDictionary.put(position, fragment) return fragment } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/TaskFormActivity.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/TaskFormActivity.kt index a00a30eeb..44f672935 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/TaskFormActivity.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/TaskFormActivity.kt @@ -6,6 +6,7 @@ import android.content.SharedPreferences import android.content.res.ColorStateList import android.graphics.Typeface import android.graphics.drawable.ColorDrawable +import android.os.Build import android.os.Bundle import android.os.Handler import android.view.Menu @@ -33,6 +34,7 @@ import com.habitrpg.android.habitica.extensions.OnChangeTextWatcher import com.habitrpg.android.habitica.extensions.addCancelButton import com.habitrpg.android.habitica.helpers.ExceptionHandler import com.habitrpg.android.habitica.helpers.TaskAlarmManager +import com.habitrpg.android.habitica.helpers.launchCatching import com.habitrpg.android.habitica.models.Tag import com.habitrpg.android.habitica.models.social.Challenge import com.habitrpg.android.habitica.models.tasks.Task @@ -47,6 +49,7 @@ import com.habitrpg.shared.habitica.models.tasks.HabitResetOption import com.habitrpg.shared.habitica.models.tasks.TaskType import io.realm.RealmList import kotlinx.coroutines.flow.firstOrNull +import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch import java.util.Date import javax.inject.Inject @@ -56,16 +59,22 @@ class TaskFormActivity : BaseActivity() { private lateinit var binding: ActivityTaskFormBinding private var userScrolled: Boolean = false private var isSaving: Boolean = false + @Inject lateinit var taskRepository: TaskRepository + @Inject lateinit var tagRepository: TagRepository + @Inject lateinit var taskAlarmManager: TaskAlarmManager + @Inject lateinit var challengeRepository: ChallengeRepository + @Inject lateinit var sharedPreferences: SharedPreferences + @Inject lateinit var userViewModel: MainUserViewModel @@ -135,18 +144,23 @@ class TaskFormActivity : BaseActivity() { super.onCreate(savedInstanceState) if (forcedTheme == "yellow") { - binding.taskDifficultyButtons.textTintColor = ContextCompat.getColor(this, R.color.text_yellow) - binding.habitScoringButtons.textTintColor = ContextCompat.getColor(this, R.color.text_yellow) + binding.taskDifficultyButtons.textTintColor = + ContextCompat.getColor(this, R.color.text_yellow) + binding.habitScoringButtons.textTintColor = + ContextCompat.getColor(this, R.color.text_yellow) } else if (forcedTheme == "taskform") { - binding.taskDifficultyButtons.textTintColor = ContextCompat.getColor(this, R.color.text_brand_neon) - binding.habitScoringButtons.textTintColor = ContextCompat.getColor(this, R.color.text_brand_neon) + binding.taskDifficultyButtons.textTintColor = + ContextCompat.getColor(this, R.color.text_brand_neon) + binding.habitScoringButtons.textTintColor = + ContextCompat.getColor(this, R.color.text_brand_neon) } setSupportActionBar(binding.toolbar) supportActionBar?.setDisplayHomeAsUpEnabled(true) supportActionBar?.setDisplayShowHomeEnabled(true) tintColor = getThemeColor(R.attr.taskFormTint) - val upperTintColor = if (forcedTheme == "taskform") getThemeColor(R.attr.taskFormTint) else getThemeColor(R.attr.colorAccent) + val upperTintColor = + if (forcedTheme == "taskform") getThemeColor(R.attr.taskFormTint) else getThemeColor(R.attr.colorAccent) supportActionBar?.setBackgroundDrawable(ColorDrawable(upperTintColor)) binding.upperTextWrapper.setBackgroundColor(upperTintColor) @@ -155,19 +169,17 @@ class TaskFormActivity : BaseActivity() { taskType = TaskType.from(bundle.getString(TASK_TYPE_KEY)) ?: TaskType.HABIT preselectedTags = bundle.getStringArrayList(SELECTED_TAGS_KEY) - compositeSubscription.add( + lifecycleScope.launchCatching { tagRepository.getTags() .map { tagRepository.getUnmanagedCopy(it) } - .subscribe( - { - tags = it - setTagViews() - }, - ExceptionHandler.rx() - ) - ) + .collect { + tags = it + setTagViews() + } + } userViewModel.user.observe(this) { - usesTaskAttributeStats = it?.preferences?.allocationMode == "taskbased" && it.preferences?.automaticAllocation == true + usesTaskAttributeStats = + it?.preferences?.allocationMode == "taskbased" && it.preferences?.automaticAllocation == true configureForm() } @@ -188,7 +200,8 @@ class TaskFormActivity : BaseActivity() { binding.statConstitutionButton.setOnClickListener { selectedStat = Attribute.CONSTITUTION } binding.statPerceptionButton.setOnClickListener { selectedStat = Attribute.PERCEPTION } binding.scrollView.setOnTouchListener { view, event -> - userScrolled = view == binding.scrollView && (event.action == MotionEvent.ACTION_SCROLL || event.action == MotionEvent.ACTION_MOVE) + userScrolled = + view == binding.scrollView && (event.action == MotionEvent.ACTION_SCROLL || event.action == MotionEvent.ACTION_MOVE) return@setOnTouchListener false } binding.scrollView.setOnScrollChangeListener { _: NestedScrollView?, _: Int, _: Int, _: Int, _: Int -> @@ -202,31 +215,33 @@ class TaskFormActivity : BaseActivity() { taskId != null -> { isCreating = false lifecycleScope.launch(ExceptionHandler.coroutine()) { - val task = taskRepository.getUnmanagedTask(taskId).firstOrNull() ?: return@launch + val task = + taskRepository.getUnmanagedTask(taskId).firstOrNull() ?: return@launch if (!task.isValid) return@launch this@TaskFormActivity.task = task initialTaskInstance = task // tintColor = ContextCompat.getColor(this, it.mediumTaskColor) fillForm(task) task.challengeID?.let { challengeID -> - compositeSubscription.add( - challengeRepository.retrieveChallenge(challengeID) - .subscribe( - { challenge -> - this@TaskFormActivity.challenge = challenge - binding.challengeNameView.text = getString(R.string.challenge_task_name, challenge.name) - binding.challengeNameView.visibility = View.VISIBLE - disableEditingForUneditableFieldsInChallengeTask() - }, - ExceptionHandler.rx() - ) - ) + lifecycleScope.launchCatching { + val challenge = challengeRepository.retrieveChallenge(challengeID) + ?: return@launchCatching + this@TaskFormActivity.challenge = challenge + binding.challengeNameView.text = + getString(R.string.challenge_task_name, challenge.name) + binding.challengeNameView.visibility = View.VISIBLE + disableEditingForUneditableFieldsInChallengeTask() + } } } } bundle.containsKey(PARCELABLE_TASK) -> { isCreating = false - task = bundle.getParcelable(PARCELABLE_TASK, Task::class.java) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + task = bundle.getParcelable(PARCELABLE_TASK, Task::class.java) + } else { + task = bundle.getParcelable(PARCELABLE_TASK) + } task?.let { fillForm(it) } } else -> { @@ -249,7 +264,8 @@ class TaskFormActivity : BaseActivity() { override fun loadTheme(sharedPreferences: SharedPreferences, forced: Boolean) { super.loadTheme(sharedPreferences, forced) - val upperTintColor = if (forcedTheme == "taskform") getThemeColor(R.attr.taskFormTint) else getThemeColor(R.attr.colorAccent) + val upperTintColor = + if (forcedTheme == "taskform") getThemeColor(R.attr.taskFormTint) else getThemeColor(R.attr.colorAccent) window.statusBarColor = upperTintColor } @@ -275,8 +291,8 @@ class TaskFormActivity : BaseActivity() { alert.dismiss() } alert.setOnDismissListener { - isDiscardCancelled = true - } + isDiscardCancelled = true + } alert.show() } else { super.onBackPressed() @@ -315,13 +331,15 @@ class TaskFormActivity : BaseActivity() { binding.habitScoringButtons.visibility = habitViewsVisibility binding.habitResetStreakTitleView.visibility = habitViewsVisibility binding.habitResetStreakButtons.visibility = habitViewsVisibility - (binding.habitAdjustNegativeStreakView.parent as ViewGroup).visibility = habitViewsVisibility + (binding.habitAdjustNegativeStreakView.parent as ViewGroup).visibility = + habitViewsVisibility if (taskType == TaskType.HABIT) { binding.habitScoringButtons.isPositive = true binding.habitScoringButtons.isNegative = false } - val habitDailyVisibility = if (taskType == TaskType.DAILY || taskType == TaskType.HABIT) View.VISIBLE else View.GONE + val habitDailyVisibility = + if (taskType == TaskType.DAILY || taskType == TaskType.HABIT) View.VISIBLE else View.GONE binding.adjustStreakTitleView.visibility = habitDailyVisibility binding.adjustStreakWrapper.visibility = habitDailyVisibility if (taskType == TaskType.HABIT) { @@ -332,13 +350,18 @@ class TaskFormActivity : BaseActivity() { binding.adjustStreakTitleView.text = getString(R.string.adjust_streak) } - val todoDailyViewsVisibility = if (taskType == TaskType.DAILY || taskType == TaskType.TODO) View.VISIBLE else View.GONE + val todoDailyViewsVisibility = + if (taskType == TaskType.DAILY || taskType == TaskType.TODO) View.VISIBLE else View.GONE - binding.checklistTitleView.visibility = if (isChallengeTask) View.GONE else todoDailyViewsVisibility - binding.checklistContainer.visibility = if (isChallengeTask) View.GONE else todoDailyViewsVisibility + binding.checklistTitleView.visibility = + if (isChallengeTask) View.GONE else todoDailyViewsVisibility + binding.checklistContainer.visibility = + if (isChallengeTask) View.GONE else todoDailyViewsVisibility - binding.remindersTitleView.visibility = if (isChallengeTask) View.GONE else todoDailyViewsVisibility - binding.remindersContainer.visibility = if (isChallengeTask) View.GONE else todoDailyViewsVisibility + binding.remindersTitleView.visibility = + if (isChallengeTask) View.GONE else todoDailyViewsVisibility + binding.remindersContainer.visibility = + if (isChallengeTask) View.GONE else todoDailyViewsVisibility binding.remindersContainer.taskType = taskType binding.remindersContainer.firstDayOfWeek = firstDayOfWeek @@ -405,12 +428,15 @@ class TaskFormActivity : BaseActivity() { binding.habitScoringButtons.isPositive = task.up ?: false binding.habitScoringButtons.isNegative = task.down ?: false task.frequency?.let { - binding.habitResetStreakButtons.selectedResetOption = HabitResetOption.from(it) ?: HabitResetOption.DAILY + binding.habitResetStreakButtons.selectedResetOption = + HabitResetOption.from(it) ?: HabitResetOption.DAILY } binding.habitAdjustPositiveStreakView.setText((task.counterUp ?: 0).toString()) binding.habitAdjustNegativeStreakView.setText((task.counterDown ?: 0).toString()) - (binding.habitAdjustPositiveStreakView.parent as ViewGroup).visibility = if (task.up == true) View.VISIBLE else View.GONE - (binding.habitAdjustNegativeStreakView.parent as ViewGroup).visibility = if (task.down == true) View.VISIBLE else View.GONE + (binding.habitAdjustPositiveStreakView.parent as ViewGroup).visibility = + if (task.up == true) View.VISIBLE else View.GONE + (binding.habitAdjustNegativeStreakView.parent as ViewGroup).visibility = + if (task.down == true) View.VISIBLE else View.GONE if (task.up != true && task.down != true) { binding.adjustStreakTitleView.visibility = View.GONE binding.adjustStreakWrapper.visibility = View.GONE @@ -440,13 +466,24 @@ class TaskFormActivity : BaseActivity() { private fun setSelectedAttribute(attributeName: Attribute) { if (!usesTaskAttributeStats) return configureStatsButton(binding.statStrengthButton, attributeName == Attribute.STRENGTH) - configureStatsButton(binding.statIntelligenceButton, attributeName == Attribute.INTELLIGENCE) - configureStatsButton(binding.statConstitutionButton, attributeName == Attribute.CONSTITUTION) + configureStatsButton( + binding.statIntelligenceButton, + attributeName == Attribute.INTELLIGENCE + ) + configureStatsButton( + binding.statConstitutionButton, + attributeName == Attribute.CONSTITUTION + ) configureStatsButton(binding.statPerceptionButton, attributeName == Attribute.PERCEPTION) } private fun configureStatsButton(button: TextView, isSelected: Boolean) { - button.background.setTint(if (isSelected) tintColor else ContextCompat.getColor(this, R.color.taskform_gray)) + button.background.setTint( + if (isSelected) tintColor else ContextCompat.getColor( + this, + R.color.taskform_gray + ) + ) val textColorID = if (isSelected) R.color.window_background else R.color.text_secondary button.setTextColor(ContextCompat.getColor(this, textColorID)) if (isSelected) { @@ -578,7 +615,11 @@ class TaskFormActivity : BaseActivity() { alert.setTitle(R.string.are_you_sure) alert.addButton(R.string.delete_task, true) { _, _ -> if (task?.isValid != true) return@addButton - task?.id?.let { taskRepository.deleteTask(it).subscribe({ }, ExceptionHandler.rx()) } + task?.id?.let { + lifecycleScope.launchCatching { + taskRepository.deleteTask(it) + } + } finish() } alert.addCancelButton() @@ -586,52 +627,46 @@ class TaskFormActivity : BaseActivity() { } private fun showChallengeDeleteTask() { - compositeSubscription.add( - taskRepository.getTasksForChallenge(task?.challengeID).firstElement().subscribe( - { tasks -> - val taskCount = tasks.size - val alert = HabiticaAlertDialog(this) - alert.setTitle(getString(R.string.delete_challenge_task_title)) - alert.setMessage(getString(R.string.delete_challenge_task_description, taskCount, challenge?.name ?: "")) - alert.addButton(R.string.leave_delete_task, isPrimary = true, isDestructive = true) { _, _ -> - challenge?.let { - compositeSubscription.add( - challengeRepository.leaveChallenge(it, "keep-all") - .flatMap { taskRepository.deleteTask(task?.id ?: "") } - .subscribe( - { - lifecycleScope.launch(ExceptionHandler.coroutine()) { - userRepository.retrieveUser(true, true) - } - finish() - }, - ExceptionHandler.rx() - ) - ) - } - } - alert.addButton(getString(R.string.leave_delete_x_tasks, taskCount), isPrimary = false, isDestructive = true) { _, _ -> - challenge?.let { - compositeSubscription.add( - challengeRepository.leaveChallenge(it, "remove-all") - .subscribe( - { - lifecycleScope.launch(ExceptionHandler.coroutine()) { - userRepository.retrieveUser(true, true) - } - finish() - }, - ExceptionHandler.rx() - ) - ) - } - } - alert.setExtraCloseButtonVisibility(View.VISIBLE) - alert.show() - }, - ExceptionHandler.rx() + lifecycleScope.launchCatching { + val tasks = taskRepository.getTasksForChallenge(task?.challengeID).firstOrNull() ?: return@launchCatching + val taskCount = tasks.size + val alert = HabiticaAlertDialog(this@TaskFormActivity) + alert.setTitle(getString(R.string.delete_challenge_task_title)) + alert.setMessage( + getString( + R.string.delete_challenge_task_description, + taskCount, + challenge?.name ?: "" + ) ) - ) + alert.addButton( + R.string.leave_delete_task, + isPrimary = true, + isDestructive = true + ) { _, _ -> + challenge?.let { + lifecycleScope.launchCatching { + challengeRepository.leaveChallenge(it, "keep-all") + taskRepository.deleteTask(task?.id ?: "") + userRepository.retrieveUser(true, true) + } + } + } + alert.addButton( + getString(R.string.leave_delete_x_tasks, taskCount), + isPrimary = false, + isDestructive = true + ) { _, _ -> + challenge?.let { + lifecycleScope.launchCatching { + challengeRepository.leaveChallenge(it, "remove-all") + userRepository.retrieveUser(true, true) + } + } + } + alert.setExtraCloseButtonVisibility(View.VISIBLE) + alert.show() + } } private fun showBrokenChallengeDialog() { @@ -639,43 +674,40 @@ class TaskFormActivity : BaseActivity() { if (!task.isValid) { return } - compositeSubscription.add( - taskRepository.getTasksForChallenge(task.challengeID).subscribe( - { tasks -> + lifecycleScope.launchCatching { + val tasks = taskRepository.getTasksForChallenge(task.challengeID).firstOrNull() ?: return@launchCatching val taskCount = tasks.size - val dialog = HabiticaAlertDialog(this) + val dialog = HabiticaAlertDialog(this@TaskFormActivity) dialog.setTitle(R.string.broken_challenge) - dialog.setMessage(this.getString(R.string.broken_challenge_description, taskCount)) - dialog.addButton(this.getString(R.string.keep_x_tasks, taskCount), true) { _, _ -> - taskRepository.unlinkAllTasks(task.challengeID, "keep-all") - .subscribe( - { - lifecycleScope.launch(ExceptionHandler.coroutine()) { - userRepository.retrieveUser(true, true) - } - finish() - }, - ExceptionHandler.rx() + dialog.setMessage( + getString( + R.string.broken_challenge_description, + taskCount ) + ) + dialog.addButton( + getString(R.string.keep_x_tasks, taskCount), + true + ) { _, _ -> + lifecycleScope.launchCatching { + taskRepository.unlinkAllTasks(task.challengeID, "keep-all") + userRepository.retrieveUser(true, true) + } } - dialog.addButton(this.getString(R.string.delete_x_tasks, taskCount), false, true) { _, _ -> + dialog.addButton( + getString(R.string.delete_x_tasks, taskCount), + false, + true + ) { _, _ -> + lifecycleScope.launchCatching { + taskRepository.unlinkAllTasks(task.challengeID, "remove-all") - .subscribe( - { - lifecycleScope.launch(ExceptionHandler.coroutine()) { - userRepository.retrieveUser(true, true) - } - finish() - }, - ExceptionHandler.rx() - ) + userRepository.retrieveUser(true, true) + } } dialog.setExtraCloseButtonVisibility(View.VISIBLE) dialog.show() - }, - ExceptionHandler.rx() - ) - ) + } } private fun disableEditingForUneditableFieldsInChallengeTask() { diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/VerifyUsernameActivity.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/VerifyUsernameActivity.kt deleted file mode 100644 index 31ac9393d..000000000 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/VerifyUsernameActivity.kt +++ /dev/null @@ -1,135 +0,0 @@ -package com.habitrpg.android.habitica.ui.activities - -import android.graphics.drawable.BitmapDrawable -import android.graphics.drawable.Drawable -import android.os.Bundle -import android.text.method.LinkMovementMethod -import android.view.View -import androidx.core.content.ContextCompat -import androidx.lifecycle.lifecycleScope -import com.habitrpg.android.habitica.R -import com.habitrpg.android.habitica.components.UserComponent -import com.habitrpg.android.habitica.databinding.ActivityVerifyUsernameBinding -import com.habitrpg.android.habitica.extensions.OnChangeTextWatcher -import com.habitrpg.android.habitica.extensions.runDelayed -import com.habitrpg.android.habitica.helpers.ExceptionHandler -import com.habitrpg.android.habitica.ui.views.HabiticaIconsHelper -import com.habitrpg.android.habitica.ui.views.HabiticaSnackbar -import io.reactivex.rxjava3.core.BackpressureStrategy -import io.reactivex.rxjava3.core.Flowable -import io.reactivex.rxjava3.subjects.PublishSubject -import kotlinx.coroutines.flow.firstOrNull -import kotlinx.coroutines.launch -import java.util.concurrent.TimeUnit - -class VerifyUsernameActivity : BaseActivity() { - private lateinit var binding: ActivityVerifyUsernameBinding - - private val displayNameVerificationEvents = PublishSubject.create() - private val usernameVerificationEvents = PublishSubject.create() - - private val checkmarkIcon: Drawable by lazy { - BitmapDrawable(resources, HabiticaIconsHelper.imageOfCheckmark(ContextCompat.getColor(this, R.color.text_green), 1f)) - } - private val alertIcon: Drawable by lazy { - BitmapDrawable(resources, HabiticaIconsHelper.imageOfAlertIcon()) - } - - override fun getLayoutResId(): Int { - return R.layout.activity_verify_username - } - - override fun getContentView(layoutResId: Int?): View { - binding = ActivityVerifyUsernameBinding.inflate(layoutInflater) - return binding.root - } - - override fun injectActivity(component: UserComponent?) { - component?.inject(this) - } - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - - binding.wikiTextView.movementMethod = LinkMovementMethod.getInstance() - binding.footerTextView.movementMethod = LinkMovementMethod.getInstance() - - binding.confirmUsernameButton.setOnClickListener { confirmNames() } - - binding.displayNameEditText.addTextChangedListener( - OnChangeTextWatcher { p0, _, _, _ -> - displayNameVerificationEvents.onNext(p0.toString()) - } - ) - binding.usernameEditText.addTextChangedListener( - OnChangeTextWatcher { p0, _, _, _ -> - usernameVerificationEvents.onNext(p0.toString()) - } - ) - - compositeSubscription.add( - Flowable.combineLatest( - displayNameVerificationEvents.toFlowable(BackpressureStrategy.DROP) - .map { it.length in 1..30 } - .doOnNext { - if (it) { - binding.displayNameEditText.setCompoundDrawablesWithIntrinsicBounds(null, null, checkmarkIcon, null) - binding.issuesTextView.visibility = View.GONE - } else { - binding.displayNameEditText.setCompoundDrawablesWithIntrinsicBounds(null, null, alertIcon, null) - binding.issuesTextView.visibility = View.VISIBLE - binding.issuesTextView.text = getString(R.string.display_name_length_error) - } - }, - usernameVerificationEvents.toFlowable(BackpressureStrategy.DROP) - .throttleLast(1, TimeUnit.SECONDS) - .flatMap { userRepository.verifyUsername(binding.usernameEditText.text.toString()) } - .doOnNext { - if (it.isUsable) { - binding.usernameEditText.setCompoundDrawablesWithIntrinsicBounds(null, null, checkmarkIcon, null) - binding.issuesTextView.visibility = View.GONE - } else { - binding.usernameEditText.setCompoundDrawablesWithIntrinsicBounds(null, null, alertIcon, null) - binding.issuesTextView.visibility = View.VISIBLE - binding.issuesTextView.text = it.issues.joinToString("\n") - } - } - ) { displayNameUsable, usernameUsable -> displayNameUsable && usernameUsable.isUsable } - .subscribe( - { - binding.confirmUsernameButton.isEnabled = it - }, - ExceptionHandler.rx() - ) - ) - - lifecycleScope.launch(ExceptionHandler.coroutine()) { - val user = userRepository.getUser().firstOrNull() - binding.displayNameEditText.setText(user?.profile?.name) - displayNameVerificationEvents.onNext(user?.profile?.name ?: "") - binding.usernameEditText.setText(user?.authentication?.localAuthentication?.username) - usernameVerificationEvents.onNext(user?.username ?: "") - } - } - - private fun confirmNames() { - binding.confirmUsernameButton.isClickable = false - lifecycleScope.launch(ExceptionHandler.coroutine()) { - userRepository.updateUser("profile.name", binding.displayNameEditText.text.toString()) - userRepository.updateLoginName(binding.usernameEditText.text.toString()) - showConfirmationAndFinish() - binding.confirmUsernameButton.isClickable = true - } - } - - private fun showConfirmationAndFinish() { - HabiticaSnackbar.showSnackbar(binding.snackbarView, getString(R.string.username_confirmed), HabiticaSnackbar.SnackbarDisplayType.SUCCESS) - runDelayed(3, TimeUnit.SECONDS) { - finish() - } - } - - override fun onBackPressed() { - moveTaskToBack(true) - } -} diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/SkillTasksRecyclerViewAdapter.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/SkillTasksRecyclerViewAdapter.kt index e519a18fd..2496b7fdf 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/SkillTasksRecyclerViewAdapter.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/SkillTasksRecyclerViewAdapter.kt @@ -8,14 +8,11 @@ import androidx.recyclerview.widget.RecyclerView import com.habitrpg.android.habitica.R import com.habitrpg.android.habitica.databinding.SkillTaskItemCardBinding import com.habitrpg.android.habitica.models.tasks.Task -import io.reactivex.rxjava3.core.BackpressureStrategy -import io.reactivex.rxjava3.core.Flowable -import io.reactivex.rxjava3.subjects.PublishSubject import java.util.UUID class SkillTasksRecyclerViewAdapter : BaseRecyclerViewAdapter() { - private val taskSelectionEvents = PublishSubject.create() + var onTaskSelection: ((Task) -> Unit)? = null override fun getItemId(position: Int): Long { val task = getItem(position) @@ -35,10 +32,6 @@ class SkillTasksRecyclerViewAdapter : BaseRecyclerViewAdapter { - return taskSelectionEvents.toFlowable(BackpressureStrategy.DROP) - } - inner class TaskViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView), View.OnClickListener { private val binding = SkillTaskItemCardBinding.bind(itemView) var task: Task? = null @@ -63,7 +56,7 @@ class SkillTasksRecyclerViewAdapter : BaseRecyclerViewAdapter() { @@ -22,7 +21,7 @@ class EquipmentRecyclerViewAdapter : BaseRecyclerViewAdapter = PublishSubject.create() + var onEquip: ((String) -> Unit)? = null override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): GearViewHolder { val view = LayoutInflater.from(parent.context).inflate(R.layout.gear_list_item, parent, false) @@ -45,7 +44,7 @@ class EquipmentRecyclerViewAdapter : BaseRecyclerViewAdapter() - private val questInvitationEvents = PublishSubject.create() - private val openMysteryItemEvents = PublishSubject.create() - private val startHatchingSubject = PublishSubject.create() - private val hatchPetSubject = PublishSubject.create>() - private val feedPetSubject = PublishSubject.create() - private val createNewPartySubject = PublishSubject.create() - private val useSpecialSubject = PublishSubject.create() - - fun getSellItemFlowable(): Flowable { - return sellItemEvents.toFlowable(BackpressureStrategy.DROP) - } - - fun getQuestInvitationFlowable(): Flowable { - return questInvitationEvents.toFlowable(BackpressureStrategy.DROP) - } - fun getOpenMysteryItemFlowable(): Flowable { - return openMysteryItemEvents.toFlowable(BackpressureStrategy.DROP) - } - - val startHatchingEvents: Flowable = startHatchingSubject.toFlowable(BackpressureStrategy.DROP) - val hatchPetEvents: Flowable> = hatchPetSubject.toFlowable(BackpressureStrategy.DROP) - val feedPetEvents: Flowable = feedPetSubject.toFlowable(BackpressureStrategy.DROP) - val startNewPartyEvents: Flowable = createNewPartySubject.toFlowable(BackpressureStrategy.DROP) - val useSpecialEvents: Flowable = useSpecialSubject.toFlowable(BackpressureStrategy.DROP) + var onSellItem: ((OwnedItem) -> Unit)? = null + var onQuestInvitation: ((QuestContent) -> Unit)? = null + var onOpenMysteryItem: ((Item) -> Unit)? = null + var onStartHatching: ((Item) -> Unit)? = null + var onHatchPet: ((HatchingPotion, Egg) -> Unit)? = null + var onFeedPet: ((Food) -> Unit)? = null + var onCreateNewParty: (() -> Unit)? = null + var onUseSpecialItem: ((SpecialItem) -> Unit)? = null override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemViewHolder { return ItemViewHolder(ItemItemBinding.inflate(context.layoutInflater, parent, false)) @@ -188,12 +168,12 @@ class ItemRecyclerAdapter(val context: Context) : BaseRecyclerViewAdapter item?.let { selectedItem -> if (!(selectedItem is QuestContent || selectedItem is SpecialItem) && index == 0) { - ownedItem?.let { selectedOwnedItem -> sellItemEvents.onNext(selectedOwnedItem) } + ownedItem?.let { selectedOwnedItem -> onSellItem?.invoke(selectedOwnedItem) } return@let } when (selectedItem) { - is Egg -> item?.let { startHatchingSubject.onNext(it) } - is HatchingPotion -> startHatchingSubject.onNext(selectedItem) + is Egg -> item?.let { onStartHatching?.invoke(it) } + is HatchingPotion -> onStartHatching?.invoke(selectedItem) is QuestContent -> { if (index == 0) { val dialog = DetailDialog(context) @@ -201,17 +181,17 @@ class ItemRecyclerAdapter(val context: Context) : BaseRecyclerViewAdapter if (item?.key != "inventory_present") { - useSpecialSubject.onNext(selectedItem) + onUseSpecialItem?.invoke(selectedItem) } else { - openMysteryItemEvents.onNext(selectedItem) + onOpenMysteryItem?.invoke(selectedItem) } } } @@ -224,17 +204,17 @@ class ItemRecyclerAdapter(val context: Context) : BaseRecyclerViewAdapter if (firstItem is Egg) { (hatchingItem as? HatchingPotion)?.let { potion -> - hatchPetSubject.onNext(Pair(potion, firstItem)) + onHatchPet?.invoke(potion, firstItem) } } else if (firstItem is HatchingPotion) { (hatchingItem as? Egg)?.let { egg -> - hatchPetSubject.onNext(Pair(firstItem, egg)) + onHatchPet?.invoke(firstItem, egg) } } return@let } } else if (isFeeding) { - feedPetSubject.onNext(item as Food?) + (item as Food?)?.let { onFeedPet?.invoke(it) } fragment?.dismiss() } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/inventory/MountDetailRecyclerAdapter.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/inventory/MountDetailRecyclerAdapter.kt index 865550a3c..1fc7f5a3e 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/inventory/MountDetailRecyclerAdapter.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/inventory/MountDetailRecyclerAdapter.kt @@ -6,11 +6,10 @@ import com.habitrpg.android.habitica.models.inventory.StableSection import com.habitrpg.android.habitica.models.user.OwnedMount import com.habitrpg.android.habitica.ui.viewHolders.MountViewHolder import com.habitrpg.android.habitica.ui.viewHolders.SectionViewHolder -import io.reactivex.rxjava3.core.BackpressureStrategy -import io.reactivex.rxjava3.core.Flowable import io.reactivex.rxjava3.subjects.PublishSubject class MountDetailRecyclerAdapter : androidx.recyclerview.widget.RecyclerView.Adapter() { + var onEquip: ((String) -> Unit)? = null private var ownedMounts: Map? = null private val equipEvents = PublishSubject.create() @@ -27,14 +26,10 @@ class MountDetailRecyclerAdapter : androidx.recyclerview.widget.RecyclerView.Ada this.notifyDataSetChanged() } - fun getEquipFlowable(): Flowable { - return equipEvents.toFlowable(BackpressureStrategy.DROP) - } - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): androidx.recyclerview.widget.RecyclerView.ViewHolder = when (viewType) { 1 -> SectionViewHolder(parent) - else -> MountViewHolder(parent, equipEvents) + else -> MountViewHolder(parent, onEquip) } override fun onBindViewHolder( diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/inventory/PetDetailRecyclerAdapter.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/inventory/PetDetailRecyclerAdapter.kt index f06af2b9f..f8452d3d0 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/inventory/PetDetailRecyclerAdapter.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/inventory/PetDetailRecyclerAdapter.kt @@ -5,7 +5,6 @@ import android.view.ViewGroup import com.habitrpg.android.habitica.R import com.habitrpg.android.habitica.databinding.CanHatchItemBinding import com.habitrpg.android.habitica.extensions.inflate -import com.habitrpg.common.habitica.helpers.Animations import com.habitrpg.android.habitica.models.inventory.Animal import com.habitrpg.android.habitica.models.inventory.Egg import com.habitrpg.android.habitica.models.inventory.Food @@ -16,15 +15,16 @@ import com.habitrpg.android.habitica.models.inventory.StableSection import com.habitrpg.android.habitica.models.user.OwnedItem import com.habitrpg.android.habitica.models.user.OwnedMount import com.habitrpg.android.habitica.models.user.OwnedPet -import com.habitrpg.common.habitica.extensions.loadImage import com.habitrpg.android.habitica.ui.viewHolders.PetViewHolder import com.habitrpg.android.habitica.ui.viewHolders.SectionViewHolder import com.habitrpg.android.habitica.ui.views.dialogs.PetSuggestHatchDialog -import io.reactivex.rxjava3.core.BackpressureStrategy -import io.reactivex.rxjava3.core.Flowable +import com.habitrpg.common.habitica.extensions.loadImage +import com.habitrpg.common.habitica.helpers.Animations import io.reactivex.rxjava3.subjects.PublishSubject class PetDetailRecyclerAdapter : androidx.recyclerview.widget.RecyclerView.Adapter() { + var onFeed: ((Pet, Food?) -> Unit)? = null + var onEquip: ((String) -> Unit)? = null private var existingMounts: List? = null private var ownedPets: Map? = null private var ownedMounts: Map? = null @@ -45,12 +45,6 @@ class PetDetailRecyclerAdapter : androidx.recyclerview.widget.RecyclerView.Adapt this.notifyDataSetChanged() } - fun getEquipFlowable(): Flowable { - return equipEvents.toFlowable(BackpressureStrategy.DROP) - } - - var feedFlowable: Flowable> = feedEvents.toFlowable(BackpressureStrategy.DROP) - var animalIngredientsRetriever: ((Animal, ((Pair) -> Unit)) -> Unit)? = null private fun canRaiseToMount(pet: Pet): Boolean { @@ -74,7 +68,7 @@ class PetDetailRecyclerAdapter : androidx.recyclerview.widget.RecyclerView.Adapt when (viewType) { 1 -> SectionViewHolder(parent) 2 -> CanHatchViewHolder(parent, animalIngredientsRetriever) - else -> PetViewHolder(parent, equipEvents, feedEvents, animalIngredientsRetriever) + else -> PetViewHolder(parent, onEquip, onFeed, animalIngredientsRetriever) } override fun onBindViewHolder( diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/inventory/StableRecyclerAdapter.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/inventory/StableRecyclerAdapter.kt index c9c4c06e0..d16b06f27 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/inventory/StableRecyclerAdapter.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/inventory/StableRecyclerAdapter.kt @@ -25,16 +25,13 @@ import com.habitrpg.android.habitica.ui.viewHolders.PetViewHolder import com.habitrpg.android.habitica.ui.viewHolders.SectionViewHolder import com.habitrpg.common.habitica.extensions.loadImage import com.habitrpg.common.habitica.views.PixelArtView -import io.reactivex.rxjava3.core.BackpressureStrategy -import io.reactivex.rxjava3.core.Flowable -import io.reactivex.rxjava3.subjects.PublishSubject class StableRecyclerAdapter : RecyclerView.Adapter() { var shopSpriteSuffix: String? = null private var eggs: Map = mapOf() var animalIngredientsRetriever: ((Animal, ((Pair) -> Unit)) -> Unit)? = null - private val feedEvents = PublishSubject.create>() + var onFeed: ((Pet, Food?) -> Unit)? = null var itemType: String? = null var currentPet: String? = null set(value) { @@ -46,16 +43,13 @@ class StableRecyclerAdapter : RecyclerView.Adapter() { field = value notifyDataSetChanged() } - private val equipEvents = PublishSubject.create() + var onEquip: ((String) -> Unit)? = null private var existingMounts: List? = null private var ownedMounts: Map? = null private var ownedItems: Map? = null private var ownsSaddles: Boolean = false private var itemList: List = ArrayList() - fun getEquipFlowable(): Flowable { - return equipEvents.toFlowable(BackpressureStrategy.DROP) - } private fun canRaiseToMount(pet: Pet): Boolean { if (pet.type == "special") return false @@ -101,8 +95,8 @@ class StableRecyclerAdapter : RecyclerView.Adapter() { 1 -> SectionViewHolder(parent) 4 -> StableViewHolder(parent.inflate(R.layout.pet_overview_item)) 5 -> StableViewHolder(parent.inflate(R.layout.mount_overview_item)) - 2 -> PetViewHolder(parent, equipEvents, feedEvents, animalIngredientsRetriever) - 3 -> MountViewHolder(parent, equipEvents) + 2 -> PetViewHolder(parent, onEquip, onFeed, animalIngredientsRetriever) + 3 -> MountViewHolder(parent, onEquip) else -> StableHeaderViewHolder(parent) } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/setup/CustomizationSetupAdapter.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/setup/CustomizationSetupAdapter.kt index 2eea727e3..dbe59c6da 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/setup/CustomizationSetupAdapter.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/setup/CustomizationSetupAdapter.kt @@ -12,9 +12,6 @@ import com.habitrpg.android.habitica.extensions.inflate import com.habitrpg.android.habitica.extensions.setTintWith import com.habitrpg.android.habitica.models.SetupCustomization import com.habitrpg.android.habitica.models.user.User -import io.reactivex.rxjava3.core.BackpressureStrategy -import io.reactivex.rxjava3.core.Flowable -import io.reactivex.rxjava3.subjects.PublishSubject internal class CustomizationSetupAdapter : RecyclerView.Adapter() { @@ -22,8 +19,7 @@ internal class CustomizationSetupAdapter : RecyclerView.Adapter = emptyList() - private val equipGearEventSubject = PublishSubject.create() - val equipGearEvents: Flowable = equipGearEventSubject.toFlowable(BackpressureStrategy.DROP) + var onEquipGear: ((String) -> Unit)? = null var onUpdateUser: ((Map) -> Unit)? = null fun setCustomizationList(newCustomizationList: List) { @@ -122,7 +118,7 @@ internal class CustomizationSetupAdapter : RecyclerView.Adapter() val updatePath = "preferences." + selectedCustomization.path diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/social/PartyMemberRecyclerViewAdapter.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/social/PartyMemberRecyclerViewAdapter.kt index b1127ecc7..7e8b4bf36 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/social/PartyMemberRecyclerViewAdapter.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/social/PartyMemberRecyclerViewAdapter.kt @@ -6,15 +6,12 @@ import com.habitrpg.android.habitica.extensions.inflate import com.habitrpg.android.habitica.models.members.Member import com.habitrpg.android.habitica.ui.adapter.BaseRecyclerViewAdapter import com.habitrpg.android.habitica.ui.viewHolders.GroupMemberViewHolder -import io.reactivex.rxjava3.core.BackpressureStrategy -import io.reactivex.rxjava3.core.Flowable -import io.reactivex.rxjava3.subjects.PublishSubject class PartyMemberRecyclerViewAdapter : BaseRecyclerViewAdapter() { var leaderID: String? = null - private val userClickedEvents = PublishSubject.create() + var onUserClicked: ((String) -> Unit)? = null override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): GroupMemberViewHolder { return GroupMemberViewHolder(parent.inflate(R.layout.party_member)) @@ -23,11 +20,7 @@ class PartyMemberRecyclerViewAdapter : BaseRecyclerViewAdapter { - return userClickedEvents.toFlowable(BackpressureStrategy.DROP) - } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/social/challenges/ChallengeTasksRecyclerViewAdapter.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/social/challenges/ChallengeTasksRecyclerViewAdapter.kt index b399fd74e..d700317cb 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/social/challenges/ChallengeTasksRecyclerViewAdapter.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/social/challenges/ChallengeTasksRecyclerViewAdapter.kt @@ -17,9 +17,6 @@ import com.habitrpg.android.habitica.ui.viewHolders.tasks.RewardViewHolder import com.habitrpg.android.habitica.ui.viewHolders.tasks.TodoViewHolder import com.habitrpg.android.habitica.ui.viewmodels.TasksViewModel import com.habitrpg.shared.habitica.models.tasks.TaskType -import io.reactivex.rxjava3.core.BackpressureStrategy -import io.reactivex.rxjava3.core.Flowable -import io.reactivex.rxjava3.subjects.PublishSubject class ChallengeTasksRecyclerViewAdapter( viewModel: TasksViewModel, @@ -30,13 +27,11 @@ class ChallengeTasksRecyclerViewAdapter( private val taskActionsDisabled: Boolean ) : BaseTasksRecyclerViewAdapter>(TaskType.HABIT, viewModel, layoutResource, newContext, userID) { - private val addItemSubject = PublishSubject.create() - val taskList: MutableList get() = content?.map { t -> t }?.toMutableList() ?: mutableListOf() - private var taskOpenEventsSubject = PublishSubject.create() - val taskOpenEvents: Flowable = taskOpenEventsSubject.toFlowable(BackpressureStrategy.LATEST) + var onAddItem: ((Task) -> Unit)? = null + var onTaskOpen: ((Task) -> Unit)? = null override fun injectThis(component: UserComponent) { component.inject(this) @@ -50,14 +45,10 @@ class ChallengeTasksRecyclerViewAdapter( TaskType.DAILY -> TYPE_DAILY TaskType.TODO -> TYPE_TODO TaskType.REWARD -> TYPE_REWARD - else -> if (addItemSubject.hasObservers() && task?.id == "addtask") TYPE_ADD_ITEM else TYPE_HEADER + else -> if (task?.id == "addtask") TYPE_ADD_ITEM else TYPE_HEADER } } - fun addItemObservable(): Flowable { - return addItemSubject.toFlowable(BackpressureStrategy.BUFFER) - } - fun addTaskUnder(taskToAdd: Task, taskAbove: Task?): Int { val position = content?.indexOfFirst { t -> t.id == taskAbove?.id } ?: 0 @@ -70,18 +61,18 @@ class ChallengeTasksRecyclerViewAdapter( override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BindableViewHolder { val viewHolder: BindableViewHolder = when (viewType) { TYPE_HABIT -> HabitViewHolder(getContentView(parent, R.layout.habit_item_card), { _, _ -> }, { }, { task -> - taskOpenEventsSubject.onNext(task) + onTaskOpen?.invoke(task) }, null) TYPE_DAILY -> DailyViewHolder(getContentView(parent, R.layout.daily_item_card), { _, _ -> }, { _, _ -> }, { }, { task -> - taskOpenEventsSubject.onNext(task) + onTaskOpen?.invoke(task) }, null) TYPE_TODO -> TodoViewHolder(getContentView(parent, R.layout.todo_item_card), { _, _ -> }, { _, _ -> }, { }, { task -> - taskOpenEventsSubject.onNext(task) + onTaskOpen?.invoke(task) }, null) TYPE_REWARD -> RewardViewHolder(getContentView(parent, R.layout.reward_item_card), { _, _ -> }, { }, { task -> - taskOpenEventsSubject.onNext(task) + onTaskOpen?.invoke(task) }, null) - TYPE_ADD_ITEM -> AddItemViewHolder(getContentView(parent, R.layout.challenge_add_task_item), addItemSubject) + TYPE_ADD_ITEM -> AddItemViewHolder(getContentView(parent, R.layout.challenge_add_task_item), onAddItem) else -> DividerViewHolder(getContentView(parent, R.layout.challenge_task_divider)) } @@ -113,7 +104,7 @@ class ChallengeTasksRecyclerViewAdapter( inner class AddItemViewHolder internal constructor( itemView: View, - private val callback: PublishSubject + private val callback: ((Task) -> Unit)? ) : BindableViewHolder(itemView) { private val addBtn: Button = itemView.findViewById(R.id.btn_add_task) @@ -121,7 +112,7 @@ class ChallengeTasksRecyclerViewAdapter( init { addBtn.isClickable = true - addBtn.setOnClickListener { newTask?.let { callback.onNext(it) } } + addBtn.setOnClickListener { newTask?.let { callback?.invoke(it) } } } override fun bind( diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/AchievementsFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/AchievementsFragment.kt index 215f9ebfe..e4c8b93d6 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/AchievementsFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/AchievementsFragment.kt @@ -17,6 +17,7 @@ import com.habitrpg.android.habitica.components.UserComponent import com.habitrpg.android.habitica.data.InventoryRepository import com.habitrpg.android.habitica.databinding.FragmentRefreshRecyclerviewBinding import com.habitrpg.android.habitica.helpers.ExceptionHandler +import com.habitrpg.android.habitica.helpers.launchCatching import com.habitrpg.android.habitica.ui.adapter.AchievementsAdapter import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel import kotlinx.coroutines.flow.combine @@ -167,12 +168,8 @@ class AchievementsFragment : BaseMainFragment : BottomSheetDialogFragment() { @@ -68,21 +70,19 @@ abstract class BaseDialogFragment : BottomSheetDialogFragment( private fun showTutorialIfNeeded() { if (view != null) { - if (this.tutorialStepIdentifier != null) { - compositeSubscription.add( - tutorialRepository.getTutorialStep(this.tutorialStepIdentifier ?: "").firstElement() - .delay(1, TimeUnit.SECONDS) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe( - Consumer { step -> - if (step.isValid && step.isManaged && step.shouldDisplay) { - val mainActivity = activity as? MainActivity ?: return@Consumer - mainActivity.displayTutorialStep(step, tutorialTexts, tutorialCanBeDeferred) - } - }, - ExceptionHandler.rx() + tutorialStepIdentifier?.let { identifier -> + lifecycleScope.launchCatching { + val step = tutorialRepository.getTutorialStep(identifier).firstOrNull() + delay(1.toDuration(DurationUnit.SECONDS)) + if (step?.isValid == true && step.isManaged && step.shouldDisplay) { + val mainActivity = activity as? MainActivity ?: return@launchCatching + mainActivity.displayTutorialStep( + step, + tutorialTexts, + tutorialCanBeDeferred ) - ) + } + } } } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/BaseFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/BaseFragment.kt index 5a39ddbb4..9a9653b41 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/BaseFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/BaseFragment.kt @@ -5,17 +5,20 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.fragment.app.Fragment +import androidx.lifecycle.lifecycleScope import androidx.viewbinding.ViewBinding import com.habitrpg.android.habitica.HabiticaBaseApplication import com.habitrpg.android.habitica.components.UserComponent import com.habitrpg.android.habitica.data.TutorialRepository -import com.habitrpg.android.habitica.helpers.ExceptionHandler +import com.habitrpg.android.habitica.helpers.launchCatching import com.habitrpg.android.habitica.proxy.AnalyticsManager import com.habitrpg.android.habitica.ui.activities.MainActivity -import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers import io.reactivex.rxjava3.disposables.CompositeDisposable -import java.util.concurrent.TimeUnit +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.firstOrNull import javax.inject.Inject +import kotlin.time.DurationUnit +import kotlin.time.toDuration abstract class BaseFragment : Fragment() { @@ -72,21 +75,18 @@ abstract class BaseFragment : Fragment() { private fun showTutorialIfNeeded() { tutorialStepIdentifier?.let { identifier -> - compositeSubscription.add( - tutorialRepository.getTutorialStep(identifier) - .firstElement() - .delay(1, TimeUnit.SECONDS) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe( - { step -> - if (step.isValid && step.isManaged && step.shouldDisplay) { - val mainActivity = activity as? MainActivity ?: return@subscribe - mainActivity.displayTutorialStep(step, tutorialTexts, tutorialCanBeDeferred) - } - }, - ExceptionHandler.rx() + lifecycleScope.launchCatching { + val step = tutorialRepository.getTutorialStep(identifier).firstOrNull() + delay(1.toDuration(DurationUnit.SECONDS)) + if (step?.isValid == true && step.isManaged && step.shouldDisplay) { + val mainActivity = activity as? MainActivity ?: return@launchCatching + mainActivity.displayTutorialStep( + step, + tutorialTexts, + tutorialCanBeDeferred ) - ) + } + } } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/NavigationDrawerFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/NavigationDrawerFragment.kt index 8759207e8..ea8444cfe 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/NavigationDrawerFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/NavigationDrawerFragment.kt @@ -29,10 +29,10 @@ import com.habitrpg.android.habitica.databinding.DrawerMainBinding import com.habitrpg.android.habitica.extensions.getMinuteOrSeconds import com.habitrpg.android.habitica.extensions.getRemainingString import com.habitrpg.android.habitica.extensions.getShortRemainingString -import com.habitrpg.android.habitica.extensions.subscribeWithErrorHandler import com.habitrpg.android.habitica.helpers.AppConfigManager import com.habitrpg.android.habitica.helpers.ExceptionHandler import com.habitrpg.android.habitica.helpers.MainNavigationController +import com.habitrpg.android.habitica.helpers.launchCatching import com.habitrpg.android.habitica.models.WorldStateEvent import com.habitrpg.android.habitica.models.inventory.Item import com.habitrpg.android.habitica.models.promotions.HabiticaPromotion @@ -47,11 +47,11 @@ import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel import com.habitrpg.android.habitica.ui.viewmodels.NotificationsViewModel import com.habitrpg.android.habitica.ui.views.HabiticaSnackbar import com.habitrpg.common.habitica.extensions.getThemeColor -import io.reactivex.rxjava3.core.Flowable import io.reactivex.rxjava3.disposables.CompositeDisposable import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.combine import kotlinx.coroutines.launch import java.util.Calendar import java.util.Date @@ -68,16 +68,22 @@ class NavigationDrawerFragment : DialogFragment() { @Inject lateinit var socialRepository: SocialRepository + @Inject lateinit var inventoryRepository: InventoryRepository + @Inject lateinit var userRepository: UserRepository + @Inject lateinit var configManager: AppConfigManager + @Inject lateinit var contentRepository: ContentRepository + @Inject lateinit var sharedPreferences: SharedPreferences + @Inject lateinit var userViewModel: MainUserViewModel @@ -101,7 +107,10 @@ class NavigationDrawerFragment : DialogFragment() { override fun onCreate(savedInstanceState: Bundle?) { val context = context adapter = if (context != null) { - NavigationDrawerAdapter(context.getThemeColor(R.attr.colorPrimary), context.getThemeColor(R.attr.colorPrimaryOffset)) + NavigationDrawerAdapter( + context.getThemeColor(R.attr.colorPrimary), + context.getThemeColor(R.attr.colorPrimaryOffset) + ) } else { NavigationDrawerAdapter(0, 0) } @@ -130,8 +139,10 @@ class NavigationDrawerFragment : DialogFragment() { binding = DrawerMainBinding.bind(view) binding?.recyclerView?.adapter = adapter - binding?.recyclerView?.layoutManager = androidx.recyclerview.widget.LinearLayoutManager(context) - (binding?.recyclerView?.itemAnimator as? SimpleItemAnimator)?.supportsChangeAnimations = false + binding?.recyclerView?.layoutManager = + androidx.recyclerview.widget.LinearLayoutManager(context) + (binding?.recyclerView?.itemAnimator as? SimpleItemAnimator)?.supportsChangeAnimations = + false initializeMenuItems() subscriptions?.add( @@ -154,26 +165,25 @@ class NavigationDrawerFragment : DialogFragment() { ) ) - subscriptions?.add( - Flowable.combineLatest( - contentRepository.getWorldState(), inventoryRepository.getAvailableLimitedItems() - ) { state, items -> - return@combineLatest Pair(state, items) - }.subscribe( - { pair -> + lifecycleScope.launchCatching { + contentRepository.getWorldState() + .combine( + inventoryRepository.getAvailableLimitedItems() + ) { state, items -> Pair(state, items) } + .collect { pair -> val gearEvent = pair.first.events.firstOrNull { it.gear } createUpdatingJob("seasonal", { gearEvent?.isCurrentlyActive == true || pair.second.isNotEmpty() }, { val diff = (gearEvent?.end?.time ?: 0) - Date().time - if (diff < (1.toDuration(DurationUnit.HOURS).inWholeMilliseconds)) 1.toDuration(DurationUnit.SECONDS) else 1.toDuration(DurationUnit.MINUTES) + if (diff < (1.toDuration(DurationUnit.HOURS).inWholeMilliseconds)) 1.toDuration( + DurationUnit.SECONDS + ) else 1.toDuration(DurationUnit.MINUTES) }) { updateSeasonalMenuEntries(gearEvent, pair.second) } - }, - ExceptionHandler.rx() - ) - ) + } + } userViewModel.user.observe(viewLifecycleOwner) { if (it != null) { @@ -181,8 +191,22 @@ class NavigationDrawerFragment : DialogFragment() { } } - binding?.messagesButtonWrapper?.setOnClickListener { setSelection(R.id.inboxFragment, null, true, preventReselection = false) } - binding?.settingsButtonWrapper?.setOnClickListener { setSelection(R.id.prefsActivity, null, true, preventReselection = false) } + binding?.messagesButtonWrapper?.setOnClickListener { + setSelection( + R.id.inboxFragment, + null, + true, + preventReselection = false + ) + } + binding?.settingsButtonWrapper?.setOnClickListener { + setSelection( + R.id.prefsActivity, + null, + true, + preventReselection = false + ) + } binding?.notificationsButtonWrapper?.setOnClickListener { startNotificationsActivity() } } @@ -219,7 +243,8 @@ class NavigationDrawerFragment : DialogFragment() { shop.pillText = context?.getString(R.string.open) if (gearEvent?.isCurrentlyActive == true) { shop.isVisible = true - shop.subtitle = context?.getString(R.string.open_for, gearEvent.end?.getShortRemainingString()) + shop.subtitle = + context?.getString(R.string.open_for, gearEvent.end?.getShortRemainingString()) } else { shop.isVisible = false } @@ -228,7 +253,10 @@ class NavigationDrawerFragment : DialogFragment() { private fun updateUser(user: User) { binding?.avatarView?.setOnClickListener { - MainNavigationController.navigate(R.id.openProfileActivity, bundleOf(Pair("userID", user.id))) + MainNavigationController.navigate( + R.id.openProfileActivity, + bundleOf(Pair("userID", user.id)) + ) } setMessagesCount(user.inbox) @@ -289,7 +317,8 @@ class NavigationDrawerFragment : DialogFragment() { val daysDiff = TimeUnit.MILLISECONDS.toDays(msDiff) if (daysDiff <= 30) { context?.let { - subscriptionItem?.subtitle = user.purchased?.plan?.dateTerminated?.getRemainingString(it.resources) + subscriptionItem?.subtitle = + user.purchased?.plan?.dateTerminated?.getRemainingString(it.resources) subscriptionItem?.subtitleTextColor = when { daysDiff <= 2 -> ContextCompat.getColor(it, R.color.red_100) daysDiff <= 7 -> ContextCompat.getColor(it, R.color.brand_400) @@ -337,36 +366,182 @@ class NavigationDrawerFragment : DialogFragment() { private fun initializeMenuItems() { val items = ArrayList() context?.let { context -> - items.add(HabiticaDrawerItem(R.id.tasksFragment, SIDEBAR_TASKS, context.getString(R.string.sidebar_tasks))) - items.add(HabiticaDrawerItem(R.id.skillsFragment, SIDEBAR_SKILLS, context.getString(R.string.sidebar_skills))) - items.add(HabiticaDrawerItem(R.id.statsFragment, SIDEBAR_STATS, context.getString(R.string.sidebar_stats))) - items.add(HabiticaDrawerItem(R.id.achievementsFragment, SIDEBAR_ACHIEVEMENTS, context.getString(R.string.sidebar_achievements))) + items.add( + HabiticaDrawerItem( + R.id.tasksFragment, + SIDEBAR_TASKS, + context.getString(R.string.sidebar_tasks) + ) + ) + items.add( + HabiticaDrawerItem( + R.id.skillsFragment, + SIDEBAR_SKILLS, + context.getString(R.string.sidebar_skills) + ) + ) + items.add( + HabiticaDrawerItem( + R.id.statsFragment, + SIDEBAR_STATS, + context.getString(R.string.sidebar_stats) + ) + ) + items.add( + HabiticaDrawerItem( + R.id.achievementsFragment, + SIDEBAR_ACHIEVEMENTS, + context.getString(R.string.sidebar_achievements) + ) + ) - items.add(HabiticaDrawerItem(0, SIDEBAR_INVENTORY, context.getString(R.string.sidebar_shops), isHeader = true)) - items.add(HabiticaDrawerItem(R.id.marketFragment, SIDEBAR_SHOPS_MARKET, context.getString(R.string.market))) - items.add(HabiticaDrawerItem(R.id.questShopFragment, SIDEBAR_SHOPS_QUEST, context.getString(R.string.questShop))) - val seasonalShopEntry = HabiticaDrawerItem(R.id.seasonalShopFragment, SIDEBAR_SHOPS_SEASONAL, context.getString(R.string.seasonalShop)) + items.add( + HabiticaDrawerItem( + 0, + SIDEBAR_INVENTORY, + context.getString(R.string.sidebar_shops), + isHeader = true + ) + ) + items.add( + HabiticaDrawerItem( + R.id.marketFragment, + SIDEBAR_SHOPS_MARKET, + context.getString(R.string.market) + ) + ) + items.add( + HabiticaDrawerItem( + R.id.questShopFragment, + SIDEBAR_SHOPS_QUEST, + context.getString(R.string.questShop) + ) + ) + val seasonalShopEntry = HabiticaDrawerItem( + R.id.seasonalShopFragment, + SIDEBAR_SHOPS_SEASONAL, + context.getString(R.string.seasonalShop) + ) seasonalShopEntry.isVisible = false items.add(seasonalShopEntry) - items.add(HabiticaDrawerItem(R.id.timeTravelersShopFragment, SIDEBAR_SHOPS_TIMETRAVEL, context.getString(R.string.timeTravelers))) + items.add( + HabiticaDrawerItem( + R.id.timeTravelersShopFragment, + SIDEBAR_SHOPS_TIMETRAVEL, + context.getString(R.string.timeTravelers) + ) + ) - items.add(HabiticaDrawerItem(0, SIDEBAR_INVENTORY, context.getString(R.string.sidebar_section_inventory), isHeader = true)) - items.add(HabiticaDrawerItem(R.id.avatarOverviewFragment, SIDEBAR_AVATAR, context.getString(R.string.sidebar_avatar_equipment))) - items.add(HabiticaDrawerItem(R.id.itemsFragment, SIDEBAR_ITEMS, context.getString(R.string.sidebar_items))) - items.add(HabiticaDrawerItem(R.id.stableFragment, SIDEBAR_STABLE, context.getString(R.string.sidebar_stable))) - items.add(HabiticaDrawerItem(R.id.gemPurchaseActivity, SIDEBAR_GEMS, context.getString(R.string.sidebar_gems))) - items.add(HabiticaDrawerItem(R.id.subscriptionPurchaseActivity, SIDEBAR_SUBSCRIPTION, context.getString(R.string.sidebar_subscription))) + items.add( + HabiticaDrawerItem( + 0, + SIDEBAR_INVENTORY, + context.getString(R.string.sidebar_section_inventory), + isHeader = true + ) + ) + items.add( + HabiticaDrawerItem( + R.id.avatarOverviewFragment, + SIDEBAR_AVATAR, + context.getString(R.string.sidebar_avatar_equipment) + ) + ) + items.add( + HabiticaDrawerItem( + R.id.itemsFragment, + SIDEBAR_ITEMS, + context.getString(R.string.sidebar_items) + ) + ) + items.add( + HabiticaDrawerItem( + R.id.stableFragment, + SIDEBAR_STABLE, + context.getString(R.string.sidebar_stable) + ) + ) + items.add( + HabiticaDrawerItem( + R.id.gemPurchaseActivity, + SIDEBAR_GEMS, + context.getString(R.string.sidebar_gems) + ) + ) + items.add( + HabiticaDrawerItem( + R.id.subscriptionPurchaseActivity, + SIDEBAR_SUBSCRIPTION, + context.getString(R.string.sidebar_subscription) + ) + ) - items.add(HabiticaDrawerItem(0, SIDEBAR_SOCIAL, context.getString(R.string.sidebar_section_social), isHeader = true)) - items.add(HabiticaDrawerItem(R.id.partyFragment, SIDEBAR_PARTY, context.getString(R.string.sidebar_party))) - items.add(HabiticaDrawerItem(R.id.tavernFragment, SIDEBAR_TAVERN, context.getString(R.string.sidebar_tavern))) - items.add(HabiticaDrawerItem(R.id.guildOverviewFragment, SIDEBAR_GUILDS, context.getString(R.string.sidebar_guilds))) - items.add(HabiticaDrawerItem(R.id.challengesOverviewFragment, SIDEBAR_CHALLENGES, context.getString(R.string.sidebar_challenges))) + items.add( + HabiticaDrawerItem( + 0, + SIDEBAR_SOCIAL, + context.getString(R.string.sidebar_section_social), + isHeader = true + ) + ) + items.add( + HabiticaDrawerItem( + R.id.partyFragment, + SIDEBAR_PARTY, + context.getString(R.string.sidebar_party) + ) + ) + items.add( + HabiticaDrawerItem( + R.id.tavernFragment, + SIDEBAR_TAVERN, + context.getString(R.string.sidebar_tavern) + ) + ) + items.add( + HabiticaDrawerItem( + R.id.guildOverviewFragment, + SIDEBAR_GUILDS, + context.getString(R.string.sidebar_guilds) + ) + ) + items.add( + HabiticaDrawerItem( + R.id.challengesOverviewFragment, + SIDEBAR_CHALLENGES, + context.getString(R.string.sidebar_challenges) + ) + ) - items.add(HabiticaDrawerItem(0, SIDEBAR_ABOUT_HEADER, context.getString(R.string.sidebar_about), isHeader = true)) - items.add(HabiticaDrawerItem(R.id.newsFragment, SIDEBAR_NEWS, context.getString(R.string.sidebar_news))) - items.add(HabiticaDrawerItem(R.id.supportMainFragment, SIDEBAR_HELP, context.getString(R.string.sidebar_help))) - items.add(HabiticaDrawerItem(R.id.aboutFragment, SIDEBAR_ABOUT, context.getString(R.string.sidebar_about))) + items.add( + HabiticaDrawerItem( + 0, + SIDEBAR_ABOUT_HEADER, + context.getString(R.string.sidebar_about), + isHeader = true + ) + ) + items.add( + HabiticaDrawerItem( + R.id.newsFragment, + SIDEBAR_NEWS, + context.getString(R.string.sidebar_news) + ) + ) + items.add( + HabiticaDrawerItem( + R.id.supportMainFragment, + SIDEBAR_HELP, + context.getString(R.string.sidebar_help) + ) + ) + items.add( + HabiticaDrawerItem( + R.id.aboutFragment, + SIDEBAR_ABOUT, + context.getString(R.string.sidebar_about) + ) + ) } val promoItem = HabiticaDrawerItem(R.id.subscriptionPurchaseActivity, SIDEBAR_PROMO) @@ -375,7 +550,8 @@ class NavigationDrawerFragment : DialogFragment() { items.add(promoItem) if (configManager.showSubscriptionBanner()) { - val item = HabiticaDrawerItem(R.id.subscriptionPurchaseActivity, SIDEBAR_SUBSCRIPTION_PROMO) + val item = + HabiticaDrawerItem(R.id.subscriptionPurchaseActivity, SIDEBAR_SUBSCRIPTION_PROMO) item.itemViewType = 2 items.add(item) } @@ -421,14 +597,15 @@ class NavigationDrawerFragment : DialogFragment() { } } - private val notificationClickResult = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { - if (it.resultCode == Activity.RESULT_OK) { - (activity as? MainActivity)?.notificationsViewModel?.click( - it.data?.getStringExtra("notificationId") ?: "", - MainNavigationController - ) + private val notificationClickResult = + registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { + if (it.resultCode == Activity.RESULT_OK) { + (activity as? MainActivity)?.notificationsViewModel?.click( + it.data?.getStringExtra("notificationId") ?: "", + MainNavigationController + ) + } } - } /** * Users of this fragment must call this method to set UP the navigation drawer interactions. @@ -448,22 +625,22 @@ class NavigationDrawerFragment : DialogFragment() { this.drawerLayout?.setDrawerShadow(R.drawable.drawer_shadow, GravityCompat.START) // set UP the drawer's list view with items and click listener - subscriptions?.add( - viewModel.getNotificationCount().subscribeWithErrorHandler { + lifecycleScope.launchCatching { + viewModel.getNotificationCount().collect { setNotificationsCount(it) } - ) - subscriptions?.add( - viewModel.allNotificationsSeen().subscribeWithErrorHandler { + } + lifecycleScope.launchCatching { + viewModel.allNotificationsSeen().collect { setNotificationsSeen(it) } - ) - subscriptions?.add( - viewModel.getHasPartyNotification().subscribeWithErrorHandler { + } + lifecycleScope.launchCatching { + viewModel.getHasPartyNotification().collect { val partyMenuItem = getItemWithIdentifier(SIDEBAR_PARTY) partyMenuItem?.showBubble = it } - ) + } } fun openDrawer() { @@ -590,15 +767,20 @@ class NavigationDrawerFragment : DialogFragment() { } if (promotedItem == null) return@let promotedItem.pillText = context?.getString(R.string.sale) - promotedItem.pillBackground = context?.let { activePromo.pillBackgroundDrawable(it) } + promotedItem.pillBackground = + context?.let { activePromo.pillBackgroundDrawable(it) } createUpdatingJob(activePromo.promoType.name, { activePromo.isActive }, { - val diff = (activePromo.endDate.time - Date().time).toDuration(DurationUnit.SECONDS) + val diff = + (activePromo.endDate.time - Date().time).toDuration(DurationUnit.SECONDS) 1.toDuration(diff.getMinuteOrSeconds()) }) { if (activePromo.isActive) { - promotedItem.subtitle = context?.getString(R.string.sale_ends_in, activePromo.endDate.getShortRemainingString()) + promotedItem.subtitle = context?.getString( + R.string.sale_ends_in, + activePromo.endDate.getShortRemainingString() + ) updateItem(promotedItem) } else { promotedItem.subtitle = null diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/StatsFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/StatsFragment.kt index dc955b258..6476bd335 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/StatsFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/StatsFragment.kt @@ -13,7 +13,6 @@ import com.habitrpg.android.habitica.data.InventoryRepository import com.habitrpg.android.habitica.databinding.FragmentStatsBinding import com.habitrpg.android.habitica.extensions.addOkButton import com.habitrpg.android.habitica.extensions.setScaledPadding -import com.habitrpg.android.habitica.helpers.ExceptionHandler import com.habitrpg.android.habitica.helpers.UserStatComputer import com.habitrpg.android.habitica.helpers.launchCatching import com.habitrpg.android.habitica.models.user.Stats @@ -24,6 +23,7 @@ import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog import com.habitrpg.android.habitica.ui.views.stats.BulkAllocateStatsDialog import com.habitrpg.common.habitica.extensions.getThemeColor import com.habitrpg.shared.habitica.models.tasks.Attribute +import kotlinx.coroutines.flow.firstOrNull import javax.inject.Inject import kotlin.math.min @@ -181,9 +181,9 @@ class StatsFragment : BaseMainFragment() { } private fun allocatePoint(stat: Attribute) { - compositeSubscription.add( - userRepository.allocatePoint(stat).subscribe({ }, ExceptionHandler.rx()) - ) + lifecycleScope.launchCatching { + userRepository.allocatePoint(stat) + } } private fun updateAttributePoints(user: User) { @@ -263,74 +263,68 @@ class StatsFragment : BaseMainFragment() { outfitList.add(thisOutfit.weapon) } - compositeSubscription.add( - inventoryRepository.getEquipment(outfitList).firstElement() - .retry(1) - .subscribe( - { - val levelStat = min((user.stats?.lvl ?: 0) / 2.0f, 50f).toInt() + lifecycleScope.launchCatching { + val equipment = inventoryRepository.getEquipment(outfitList).firstOrNull() + val levelStat = min((user.stats?.lvl ?: 0) / 2.0f, 50f).toInt() - totalStrength = levelStat - totalIntelligence = levelStat - totalConstitution = levelStat - totalPerception = levelStat + totalStrength = levelStat + totalIntelligence = levelStat + totalConstitution = levelStat + totalPerception = levelStat - binding?.strengthStatsView?.levelValue = levelStat - binding?.intelligenceStatsView?.levelValue = levelStat - binding?.constitutionStatsView?.levelValue = levelStat - binding?.perceptionStatsView?.levelValue = levelStat + binding?.strengthStatsView?.levelValue = levelStat + binding?.intelligenceStatsView?.levelValue = levelStat + binding?.constitutionStatsView?.levelValue = levelStat + binding?.perceptionStatsView?.levelValue = levelStat - totalStrength += user.stats?.buffs?.str?.toInt() ?: 0 - totalIntelligence += user.stats?.buffs?._int?.toInt() ?: 0 - totalConstitution += user.stats?.buffs?.con?.toInt() ?: 0 - totalPerception += user.stats?.buffs?.per?.toInt() ?: 0 - binding?.strengthStatsView?.buffValue = user.stats?.buffs?.str?.toInt() ?: 0 - binding?.intelligenceStatsView?.buffValue = - user.stats?.buffs?._int?.toInt() ?: 0 - binding?.constitutionStatsView?.buffValue = - user.stats?.buffs?.con?.toInt() ?: 0 - binding?.perceptionStatsView?.buffValue = - user.stats?.buffs?.per?.toInt() ?: 0 + totalStrength += user.stats?.buffs?.str?.toInt() ?: 0 + totalIntelligence += user.stats?.buffs?._int?.toInt() ?: 0 + totalConstitution += user.stats?.buffs?.con?.toInt() ?: 0 + totalPerception += user.stats?.buffs?.per?.toInt() ?: 0 + binding?.strengthStatsView?.buffValue = user.stats?.buffs?.str?.toInt() ?: 0 + binding?.intelligenceStatsView?.buffValue = + user.stats?.buffs?._int?.toInt() ?: 0 + binding?.constitutionStatsView?.buffValue = + user.stats?.buffs?.con?.toInt() ?: 0 + binding?.perceptionStatsView?.buffValue = + user.stats?.buffs?.per?.toInt() ?: 0 - totalStrength += user.stats?.strength ?: 0 - totalIntelligence += user.stats?.intelligence ?: 0 - totalConstitution += user.stats?.constitution ?: 0 - totalPerception += user.stats?.per ?: 0 - binding?.strengthStatsView?.allocatedValue = user.stats?.strength ?: 0 - binding?.intelligenceStatsView?.allocatedValue = - user.stats?.intelligence ?: 0 - binding?.constitutionStatsView?.allocatedValue = - user.stats?.constitution ?: 0 - binding?.perceptionStatsView?.allocatedValue = user.stats?.per ?: 0 - val userStatComputer = UserStatComputer() - val statsRows = userStatComputer.computeClassBonus(it, user) + totalStrength += user.stats?.strength ?: 0 + totalIntelligence += user.stats?.intelligence ?: 0 + totalConstitution += user.stats?.constitution ?: 0 + totalPerception += user.stats?.per ?: 0 + binding?.strengthStatsView?.allocatedValue = user.stats?.strength ?: 0 + binding?.intelligenceStatsView?.allocatedValue = + user.stats?.intelligence ?: 0 + binding?.constitutionStatsView?.allocatedValue = + user.stats?.constitution ?: 0 + binding?.perceptionStatsView?.allocatedValue = user.stats?.per ?: 0 + val userStatComputer = UserStatComputer() + val statsRows = userStatComputer.computeClassBonus(equipment, user) - var strength = 0 - var intelligence = 0 - var constitution = 0 - var perception = 0 + var strength = 0 + var intelligence = 0 + var constitution = 0 + var perception = 0 - for (row in statsRows) { - if (row.javaClass == UserStatComputer.AttributeRow::class.java) { - val attributeRow = row as UserStatComputer.AttributeRow - strength += attributeRow.strVal.toInt() - intelligence += attributeRow.intVal.toInt() - constitution += attributeRow.conVal.toInt() - perception += attributeRow.perVal.toInt() - } - } + for (row in statsRows) { + if (row.javaClass == UserStatComputer.AttributeRow::class.java) { + val attributeRow = row as UserStatComputer.AttributeRow + strength += attributeRow.strVal.toInt() + intelligence += attributeRow.intVal.toInt() + constitution += attributeRow.conVal.toInt() + perception += attributeRow.perVal.toInt() + } + } - totalStrength += strength - totalIntelligence += intelligence - totalConstitution += constitution - totalPerception += perception - binding?.strengthStatsView?.equipmentValue = strength - binding?.intelligenceStatsView?.equipmentValue = intelligence - binding?.constitutionStatsView?.equipmentValue = constitution - binding?.perceptionStatsView?.equipmentValue = perception - }, - ExceptionHandler.rx() - ) - ) + totalStrength += strength + totalIntelligence += intelligence + totalConstitution += constitution + totalPerception += perception + binding?.strengthStatsView?.equipmentValue = strength + binding?.intelligenceStatsView?.equipmentValue = intelligence + binding?.constitutionStatsView?.equipmentValue = constitution + binding?.perceptionStatsView?.equipmentValue = perception + } } } 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 f09793524..919977db5 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 @@ -39,10 +39,9 @@ import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaBottomSheetDialog import com.habitrpg.common.habitica.extensions.dpToPx import com.habitrpg.common.habitica.extensions.getThemeColor -import io.reactivex.rxjava3.core.BackpressureStrategy -import io.reactivex.rxjava3.kotlin.combineLatest -import io.reactivex.rxjava3.subjects.BehaviorSubject -import io.reactivex.rxjava3.subjects.PublishSubject +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch import javax.inject.Inject @@ -71,8 +70,8 @@ class AvatarCustomizationFragment : internal var adapter: CustomizationRecyclerViewAdapter = CustomizationRecyclerViewAdapter() internal var layoutManager: FlexboxLayoutManager = FlexboxLayoutManager(activity, ROW) - private val currentFilter = BehaviorSubject.create() - private val ownedCustomizations = PublishSubject.create>() + private val currentFilter = MutableStateFlow(CustomizationFilter(false, type != "background")) + private val ownedCustomizations = MutableStateFlow>(emptyList()) override fun onCreateView( inflater: LayoutInflater, @@ -95,11 +94,11 @@ class AvatarCustomizationFragment : } } - compositeSubscription.add( - this.inventoryRepository.getInAppRewards() + lifecycleScope.launchCatching { + inventoryRepository.getInAppRewards() .map { rewards -> rewards.map { it.key } } - .subscribe({ adapter.setPinnedItemKeys(it) }, ExceptionHandler.rx()) - ) + .collect { adapter.setPinnedItemKeys(it) } + } return super.onCreateView(inflater, container, savedInstanceState) } @@ -127,7 +126,6 @@ class AvatarCustomizationFragment : this.loadCustomizations() userViewModel.user.observe(viewLifecycleOwner) { updateUser(it) } - currentFilter.onNext(CustomizationFilter(false, type != "background")) binding?.recyclerView?.doOnLayout { adapter.columnCount = it.width / (80.dpToPx(context)) @@ -182,15 +180,13 @@ class AvatarCustomizationFragment : private fun loadCustomizations() { val type = this.type ?: return - compositeSubscription.add( + lifecycleScope.launchCatching { customizationRepository.getCustomizations(type, category, false) - .combineLatest( - currentFilter.toFlowable(BackpressureStrategy.DROP), - ownedCustomizations.toFlowable(BackpressureStrategy.DROP) - ) - .subscribe( - { (customizations, filter, ownedCustomizations) -> - adapter.ownedCustomizations = ownedCustomizations.map { it.key + "_" + it.type + "_" + it.category } + .combine(currentFilter) { customizations, filter -> Pair(customizations, filter) } + .combine(ownedCustomizations) { pair, ownedCustomizations -> Triple(pair.first, pair.second, ownedCustomizations) } + .collect { (customizations, filter, ownedCustomizations) -> + adapter.ownedCustomizations = + ownedCustomizations.map { it.key + "_" + it.type + "_" + it.category } if (filter.isFiltering) { val displayedCustomizations = mutableListOf() for (customization in customizations) { @@ -213,13 +209,15 @@ class AvatarCustomizationFragment : } ) } - }, - ExceptionHandler.rx() - ) - ) + } + } if (type == "hair" && (category == "beard" || category == "mustache")) { val otherCategory = if (category == "mustache") "beard" else "mustache" - compositeSubscription.add(customizationRepository.getCustomizations(type, otherCategory, true).subscribe({ adapter.additionalSetItems = it }, ExceptionHandler.rx())) + lifecycleScope.launchCatching { + customizationRepository.getCustomizations(type, otherCategory, true).collect { + adapter.additionalSetItems = it + } + } } } @@ -236,7 +234,7 @@ class AvatarCustomizationFragment : fun updateUser(user: User?) { if (user == null) return this.updateActiveCustomization(user) - ownedCustomizations.onNext(user.purchased?.customizations?.filter { it.type == this.type && it.purchased }) + ownedCustomizations.value = user.purchased?.customizations?.filter { it.type == this.type && it.purchased } ?: emptyList() this.adapter.userSize = user.preferences?.size this.adapter.hairColor = user.preferences?.hair?.color this.adapter.gemBalance = user.gemCount @@ -286,17 +284,17 @@ class AvatarCustomizationFragment : binding.showMeWrapper.check(if (filter.onlyPurchased) R.id.show_purchased_button else R.id.show_all_button) binding.showMeWrapper.setOnCheckedChangeListener { _, checkedId -> filter.onlyPurchased = checkedId == R.id.show_purchased_button - currentFilter.onNext(filter) + currentFilter.value = filter } binding.clearButton.setOnClickListener { - currentFilter.onNext(CustomizationFilter(false, type != "background")) + currentFilter.value = CustomizationFilter(false, type != "background") dialog.dismiss() } if (type == "background") { binding.sortByWrapper.check(if (filter.ascending) R.id.oldest_button else R.id.newest_button) binding.sortByWrapper.setOnCheckedChangeListener { _, checkedId -> filter.ascending = checkedId == R.id.oldest_button - currentFilter.onNext(filter) + currentFilter.value = filter } configureMonthFilterButton(binding.januaryButton, 1, filter) configureMonthFilterButton(binding.febuaryButton, 2, filter) @@ -333,7 +331,7 @@ class AvatarCustomizationFragment : button.typeface = Typeface.create("sans-serif-medium", Typeface.NORMAL) filter.months.add(identifier) } - currentFilter.onNext(filter) + currentFilter.value = filter } } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/equipment/EquipmentDetailFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/equipment/EquipmentDetailFragment.kt index 6dc48579f..0f6e84cfc 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/equipment/EquipmentDetailFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/equipment/EquipmentDetailFragment.kt @@ -12,12 +12,13 @@ import com.habitrpg.android.habitica.R import com.habitrpg.android.habitica.components.UserComponent import com.habitrpg.android.habitica.data.InventoryRepository import com.habitrpg.android.habitica.databinding.FragmentRefreshRecyclerviewBinding -import com.habitrpg.android.habitica.helpers.MainNavigationController import com.habitrpg.android.habitica.helpers.ExceptionHandler +import com.habitrpg.android.habitica.helpers.MainNavigationController +import com.habitrpg.android.habitica.helpers.launchCatching import com.habitrpg.android.habitica.ui.adapter.inventory.EquipmentRecyclerViewAdapter import com.habitrpg.android.habitica.ui.fragments.BaseMainFragment -import com.habitrpg.common.habitica.helpers.EmptyItem import com.habitrpg.android.habitica.ui.helpers.SafeDefaultItemAnimator +import com.habitrpg.common.habitica.helpers.EmptyItem import kotlinx.coroutines.launch import javax.inject.Inject @@ -45,10 +46,11 @@ class EquipmentDetailFragment : container: ViewGroup?, savedInstanceState: Bundle? ): View? { - compositeSubscription.add( - this.adapter.equipEvents.flatMapMaybe { key -> inventoryRepository.equipGear(key, isCostume ?: false).firstElement() } - .subscribe({ }, ExceptionHandler.rx()) - ) + adapter.onEquip = { + lifecycleScope.launchCatching { + inventoryRepository.equipGear(it, isCostume ?: false) + } + } return super.onCreateView(inflater, container, savedInstanceState) } @@ -82,7 +84,11 @@ class EquipmentDetailFragment : binding?.recyclerView?.addItemDecoration(DividerItemDecoration(getActivity(), DividerItemDecoration.VERTICAL)) binding?.recyclerView?.itemAnimator = SafeDefaultItemAnimator() - type?.let { type -> inventoryRepository.getOwnedEquipment(type).subscribe({ this.adapter.data = it }, ExceptionHandler.rx()) } + type?.let { type -> + lifecycleScope.launchCatching { + inventoryRepository.getOwnedEquipment(type).collect { adapter.data = it } + } + } } override fun onDestroy() { diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/items/ItemDialogFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/items/ItemDialogFragment.kt index b8a24cb9d..d89dce1ee 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/items/ItemDialogFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/items/ItemDialogFragment.kt @@ -14,9 +14,9 @@ import com.habitrpg.android.habitica.data.UserRepository import com.habitrpg.android.habitica.databinding.FragmentItemsDialogBinding import com.habitrpg.android.habitica.extensions.addCloseButton import com.habitrpg.android.habitica.extensions.observeOnce -import com.habitrpg.android.habitica.extensions.subscribeWithErrorHandler import com.habitrpg.android.habitica.helpers.ExceptionHandler import com.habitrpg.android.habitica.helpers.MainNavigationController +import com.habitrpg.android.habitica.helpers.launchCatching import com.habitrpg.android.habitica.interactors.FeedPetUseCase import com.habitrpg.android.habitica.interactors.HatchPetUseCase import com.habitrpg.android.habitica.models.inventory.Egg @@ -183,69 +183,53 @@ class ItemDialogFragment : BaseDialogFragment() { adapter?.feedingPet = this.feedingPet } binding?.recyclerView?.adapter = adapter - - adapter?.let { adapter -> - compositeSubscription.add( - adapter.getSellItemFlowable() - .flatMap { item -> inventoryRepository.sellItem(item) } - .subscribe({ }, ExceptionHandler.rx()) - ) - - compositeSubscription.add( - adapter.getQuestInvitationFlowable() - .flatMap { quest -> inventoryRepository.inviteToQuest(quest) } - .subscribe( - { - lifecycleScope.launch(ExceptionHandler.coroutine()) { - socialRepository.retrieveGroup("party") - if (isModal) { - dismiss() - } else { - MainNavigationController.navigate(R.id.partyFragment) - } - } - }, - ExceptionHandler.rx() - ) - ) - compositeSubscription.add( - adapter.getOpenMysteryItemFlowable() - .flatMap { inventoryRepository.openMysteryItem(user) } - .doOnNext { - val activity = activity as? MainActivity - if (activity != null) { - val dialog = OpenedMysteryitemDialog(activity) - dialog.isCelebratory = true - dialog.setTitle(R.string.mystery_item_title) - dialog.binding.iconView.loadImage("shop_${it.key}") - dialog.binding.titleView.text = it.text - dialog.binding.descriptionView.text = it.notes - dialog.addButton(R.string.equip, true) { _, _ -> - inventoryRepository.equip("equipped", it.key ?: "").subscribe({}, ExceptionHandler.rx()) - } - dialog.addCloseButton() - dialog.enqueue() + adapter?.onSellItem = { + lifecycleScope.launchCatching { + inventoryRepository.sellItem(it) + } + } + adapter?.onQuestInvitation = { + lifecycleScope.launchCatching { + inventoryRepository.inviteToQuest(it) + MainNavigationController.navigate(R.id.partyFragment) + } + } + adapter?.onOpenMysteryItem = { + lifecycleScope.launchCatching { + val item = inventoryRepository.openMysteryItem(user) ?: return@launchCatching + val activity = activity as? MainActivity + if (activity != null) { + val dialog = OpenedMysteryitemDialog(activity) + dialog.isCelebratory = true + dialog.setTitle(R.string.mystery_item_title) + dialog.binding.iconView.loadImage("shop_${it.key}") + dialog.binding.titleView.text = item.text + dialog.binding.descriptionView.text = item.notes + dialog.addButton(R.string.equip, true) { _, _ -> + lifecycleScope.launchCatching { + inventoryRepository.equip("equipped", it.key) } } - .subscribe({ }, ExceptionHandler.rx()) - ) - compositeSubscription.add(adapter.hatchPetEvents.subscribeWithErrorHandler { hatchPet(it.first, it.second) }) - compositeSubscription.add(adapter.feedPetEvents.subscribeWithErrorHandler { feedPet(it) }) + dialog.addCloseButton() + dialog.enqueue() + } + } } + adapter?.onHatchPet = { pet, egg -> hatchPet(pet, egg) } } } private fun feedPet(food: Food) { val pet = feedingPet ?: return val activity = activity ?: return - parentSubscription?.add( - feedPetUseCase.observable( + lifecycleScope.launchCatching { + feedPetUseCase.callInteractor( FeedPetUseCase.RequestValues( pet, food, activity ) - ).subscribeWithErrorHandler {} - ) + ) + } } override fun onResume() { @@ -267,14 +251,14 @@ class ItemDialogFragment : BaseDialogFragment() { private fun hatchPet(potion: HatchingPotion, egg: Egg) { dismiss() val activity = activity ?: return - parentSubscription?.add( - hatchPetUseCase.observable( + activity.lifecycleScope.launchCatching { + hatchPetUseCase.callInteractor( HatchPetUseCase.RequestValues( potion, egg, activity ) - ).subscribeWithErrorHandler {} - ) + ) + } } private fun loadItems() { diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/items/ItemRecyclerFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/items/ItemRecyclerFragment.kt index 345df8dda..92dbffea9 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/items/ItemRecyclerFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/items/ItemRecyclerFragment.kt @@ -18,7 +18,6 @@ import com.habitrpg.android.habitica.data.UserRepository import com.habitrpg.android.habitica.databinding.FragmentItemsBinding import com.habitrpg.android.habitica.extensions.addCloseButton import com.habitrpg.android.habitica.extensions.observeOnce -import com.habitrpg.android.habitica.extensions.subscribeWithErrorHandler import com.habitrpg.android.habitica.helpers.ExceptionHandler import com.habitrpg.android.habitica.helpers.MainNavigationController import com.habitrpg.android.habitica.helpers.launchCatching @@ -141,50 +140,42 @@ class ItemRecyclerFragment : BaseFragment(), SwipeRefreshL adapter = ItemRecyclerAdapter(context) } binding?.recyclerView?.adapter = adapter - adapter?.useSpecialEvents?.subscribeWithErrorHandler { onSpecialItemSelected(it) }?.let { compositeSubscription.add(it) } - adapter?.let { adapter -> - compositeSubscription.add( - adapter.getSellItemFlowable() - .flatMap { item -> inventoryRepository.sellItem(item) } - .subscribe({ }, ExceptionHandler.rx()) - ) - - compositeSubscription.add( - adapter.getQuestInvitationFlowable() - .flatMap { quest -> inventoryRepository.inviteToQuest(quest) } - //.flatMap { socialRepository.retrieveGroup("party") } - .subscribe( - { - MainNavigationController.navigate(R.id.partyFragment) - }, - ExceptionHandler.rx() - ) - ) - compositeSubscription.add( - adapter.getOpenMysteryItemFlowable() - .flatMap { inventoryRepository.openMysteryItem(user) } - .doOnNext { - val activity = activity as? MainActivity - if (activity != null) { - val dialog = OpenedMysteryitemDialog(activity) - dialog.isCelebratory = true - dialog.setTitle(R.string.mystery_item_title) - dialog.binding.iconView.loadImage("shop_${it.key}") - dialog.binding.titleView.text = it.text - dialog.binding.descriptionView.text = it.notes - dialog.addButton(R.string.equip, true) { _, _ -> - inventoryRepository.equip("equipped", it.key ?: "").subscribe({}, ExceptionHandler.rx()) - } - dialog.addCloseButton() - dialog.enqueue() + adapter?.onUseSpecialItem = { onSpecialItemSelected(it) } + adapter?.onSellItem = { + lifecycleScope.launchCatching { + inventoryRepository.sellItem(it) + } + } + adapter?.onQuestInvitation = { + lifecycleScope.launchCatching { + inventoryRepository.inviteToQuest(it) + MainNavigationController.navigate(R.id.partyFragment) + } + } + adapter?.onOpenMysteryItem = { + lifecycleScope.launchCatching { + val item = inventoryRepository.openMysteryItem(user) ?: return@launchCatching + val activity = activity as? MainActivity + if (activity != null) { + val dialog = OpenedMysteryitemDialog(activity) + dialog.isCelebratory = true + dialog.setTitle(R.string.mystery_item_title) + dialog.binding.iconView.loadImage("shop_${it.key}") + dialog.binding.titleView.text = item.text + dialog.binding.descriptionView.text = item.notes + dialog.addButton(R.string.equip, true) { _, _ -> + lifecycleScope.launchCatching { + inventoryRepository.equip("equipped", it.key) } } - .subscribe({ }, ExceptionHandler.rx()) - ) - compositeSubscription.add(adapter.startHatchingEvents.subscribeWithErrorHandler { showHatchingDialog(it) }) - compositeSubscription.add(adapter.hatchPetEvents.subscribeWithErrorHandler { hatchPet(it.first, it.second) }) - compositeSubscription.addAll(adapter.startNewPartyEvents.subscribeWithErrorHandler { createNewParty() }) + dialog.addCloseButton() + dialog.enqueue() + } + } } + adapter?.onStartHatching = { showHatchingDialog(it) } + adapter?.onHatchPet = { pet, egg -> hatchPet(pet, egg) } + adapter?.onCreateNewParty = { createNewParty() } } } @@ -218,14 +209,14 @@ class ItemRecyclerFragment : BaseFragment(), SwipeRefreshL private fun hatchPet(potion: HatchingPotion, egg: Egg) { (activity as? BaseActivity)?.let { - compositeSubscription.add( - hatchPetUseCase.observable( + lifecycleScope.launchCatching { + hatchPetUseCase.callInteractor( HatchPetUseCase.RequestValues( potion, egg, it ) - ).subscribeWithErrorHandler {} - ) + ) + } } } 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 26adcc2f4..f54215d23 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 @@ -14,6 +14,7 @@ import com.habitrpg.android.habitica.data.SocialRepository import com.habitrpg.android.habitica.databinding.FragmentRefreshRecyclerviewBinding import com.habitrpg.android.habitica.helpers.AppConfigManager import com.habitrpg.android.habitica.helpers.ExceptionHandler +import com.habitrpg.android.habitica.helpers.launchCatching import com.habitrpg.android.habitica.models.shops.Shop import com.habitrpg.android.habitica.models.shops.ShopCategory import com.habitrpg.android.habitica.models.shops.ShopItem @@ -26,8 +27,8 @@ import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel import com.habitrpg.android.habitica.ui.views.CurrencyViews import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog import com.habitrpg.common.habitica.helpers.RecyclerViewState -import io.reactivex.rxjava3.kotlin.combineLatest import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch import javax.inject.Inject @@ -180,62 +181,48 @@ open class ShopFragment : BaseMainFragment() Shop.SEASONAL_SHOP -> "seasonal" else -> "" } - compositeSubscription.add( - this.inventoryRepository.retrieveShopInventory(shopUrl) - .map { shop1 -> - if (shop1.identifier == Shop.MARKET) { - val user = userViewModel.user.value - val specialCategory = ShopCategory() - specialCategory.text = getString(R.string.special) - if (user?.isValid == true && user.purchased?.plan?.isActive == true) { - val item = ShopItem.makeGemItem(context?.resources) - item.limitedNumberLeft = user.purchased?.plan?.numberOfGemsLeft - specialCategory.items.add(item) - } - specialCategory.items.add(ShopItem.makeFortifyItem(context?.resources)) - shop1.categories.add(specialCategory) - } - when (shop1.identifier) { - Shop.TIME_TRAVELERS_SHOP -> { - formatTimeTravelersShop(shop1) - } - Shop.SEASONAL_SHOP -> { - shop1.categories.sortWith( - compareBy { it.items.size != 1 } - .thenBy { it.items.firstOrNull()?.currency != "gold" } - .thenByDescending { it.items.firstOrNull()?.event?.end } - ) - shop1 - } - else -> { - shop1 - } - } + lifecycleScope.launchCatching({ + binding?.recyclerView?.state = RecyclerViewState.FAILED + }) { + val shop1 = inventoryRepository.retrieveShopInventory(shopUrl) ?: return@launchCatching + if (shop1.identifier == Shop.MARKET) { + val user = userViewModel.user.value + val specialCategory = ShopCategory() + specialCategory.text = getString(R.string.special) + if (user?.isValid == true && user.purchased?.plan?.isActive == true) { + val item = ShopItem.makeGemItem(context?.resources) + item.limitedNumberLeft = user.purchased?.plan?.numberOfGemsLeft + specialCategory.items.add(item) } - .subscribe( - { - this.shop = it - this.adapter?.setShop(it) - }, - { - binding?.recyclerView?.state = RecyclerViewState.FAILED - ExceptionHandler.reportError(it) - }, - { - binding?.refreshLayout?.isRefreshing = false - } - ) - ) + specialCategory.items.add(ShopItem.makeFortifyItem(context?.resources)) + shop1.categories.add(specialCategory) + } + when (shop1.identifier) { + Shop.TIME_TRAVELERS_SHOP -> { + formatTimeTravelersShop(shop1) + } + Shop.SEASONAL_SHOP -> { + shop1.categories.sortWith( + compareBy { it.items.size != 1 } + .thenBy { it.items.firstOrNull()?.currency != "gold" } + .thenByDescending { it.items.firstOrNull()?.event?.end } + ) + } + } + shop = shop1 + adapter?.setShop(shop1) + binding?.refreshLayout?.isRefreshing = false + } - compositeSubscription.add( - this.inventoryRepository.getOwnedItems() - .subscribe({ adapter?.setOwnedItems(it) }, ExceptionHandler.rx()) - ) - compositeSubscription.add( - this.inventoryRepository.getInAppRewards() + lifecycleScope.launchCatching { + inventoryRepository.getOwnedItems() + .collect { adapter?.setOwnedItems(it) } + } + lifecycleScope.launchCatching { + inventoryRepository.getInAppRewards() .map { rewards -> rewards.map { it.key } } - .subscribe({ adapter?.setPinnedItemKeys(it) }, ExceptionHandler.rx()) - ) + .collect { adapter?.setPinnedItemKeys(it) } + } } private fun formatTimeTravelersShop(shop: Shop): Shop { @@ -264,29 +251,22 @@ open class ShopFragment : BaseMainFragment() } private fun loadMarketGear() { - compositeSubscription.add( - inventoryRepository.retrieveMarketGear() - .combineLatest( - inventoryRepository.getOwnedEquipment().map { equipment -> equipment.map { it.key } } - ) - .map { (shop, equipment) -> - for (category in shop.categories) { + lifecycleScope.launchCatching { + val shop = inventoryRepository.retrieveMarketGear() + inventoryRepository.getOwnedEquipment() + .map { equipment -> equipment.map { it.key } } + .collect { equipment -> + for (category in shop?.categories ?: emptyList()) { val items = category.items.asSequence().filter { !equipment.contains(it.key) }.sortedBy { it.locked }.toList() category.items.clear() category.items.addAll(items) } - shop + gearCategories = shop?.categories + adapter?.gearCategories = shop?.categories ?: mutableListOf() } - .subscribe( - { - this.gearCategories = it.categories - adapter?.gearCategories = it.categories - }, - ExceptionHandler.rx() - ) - ) + } } override fun injectFragment(component: UserComponent) { diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/stable/MountDetailRecyclerFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/stable/MountDetailRecyclerFragment.kt index 2d108d89a..3ad7102b4 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/stable/MountDetailRecyclerFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/stable/MountDetailRecyclerFragment.kt @@ -12,6 +12,7 @@ import com.habitrpg.android.habitica.data.InventoryRepository import com.habitrpg.android.habitica.databinding.FragmentRefreshRecyclerviewBinding import com.habitrpg.android.habitica.extensions.getTranslatedType import com.habitrpg.android.habitica.helpers.ExceptionHandler +import com.habitrpg.android.habitica.helpers.launchCatching import com.habitrpg.android.habitica.models.inventory.Mount import com.habitrpg.android.habitica.models.inventory.StableSection import com.habitrpg.android.habitica.models.user.OwnedMount @@ -97,13 +98,12 @@ class MountDetailRecyclerFragment : binding?.recyclerView?.itemAnimator = SafeDefaultItemAnimator() this.loadItems() - adapter?.getEquipFlowable()?.flatMap { key -> inventoryRepository.equip("mount", key) } - ?.subscribe( - { - adapter?.currentMount = it.currentMount - }, - ExceptionHandler.rx() - )?.let { compositeSubscription.add(it) } + adapter?.onEquip = { + lifecycleScope.launchCatching { + val items = inventoryRepository.equip("mount", it) + adapter?.currentMount = items?.currentMount + } + } } userViewModel.user.observe(viewLifecycleOwner) { adapter?.currentMount = it?.currentMount } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/stable/PetDetailRecyclerFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/stable/PetDetailRecyclerFragment.kt index 3ec178450..abfc35864 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/stable/PetDetailRecyclerFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/stable/PetDetailRecyclerFragment.kt @@ -13,6 +13,7 @@ import com.habitrpg.android.habitica.databinding.FragmentRefreshRecyclerviewBind import com.habitrpg.android.habitica.extensions.getTranslatedType import com.habitrpg.android.habitica.extensions.subscribeWithErrorHandler import com.habitrpg.android.habitica.helpers.ExceptionHandler +import com.habitrpg.android.habitica.helpers.launchCatching import com.habitrpg.android.habitica.interactors.FeedPetUseCase import com.habitrpg.android.habitica.models.inventory.Egg import com.habitrpg.android.habitica.models.inventory.Food @@ -124,23 +125,19 @@ class PetDetailRecyclerFragment : binding?.recyclerView?.itemAnimator = SafeDefaultItemAnimator() this.loadItems() - compositeSubscription.add( - adapter.getEquipFlowable() - .flatMap { key -> inventoryRepository.equip("pet", key) } - .subscribe( - { - adapter.currentPet = it.currentPet - }, - ExceptionHandler.rx() - ) - ) + adapter.onEquip = { + lifecycleScope.launchCatching { + val items = inventoryRepository.equip("pet", it) + adapter.currentPet = items?.currentPet + } + } userViewModel.user.observe(viewLifecycleOwner) { adapter.currentPet = it?.currentPet } - compositeSubscription.add(adapter.feedFlowable.subscribe({ + adapter.onFeed = { pet, food -> showFeedingDialog( - it.first, - it.second + pet, + food ) - }, ExceptionHandler.rx())) + } view.post { setGridSpanCount(view.width) } } @@ -179,10 +176,9 @@ class PetDetailRecyclerFragment : return@map mountMap }.collect { adapter.setOwnedMounts(it) } } - compositeSubscription.add( - inventoryRepository.getOwnedItems(true) - .subscribe({ adapter.setOwnedItems(it) }, ExceptionHandler.rx()) - ) + lifecycleScope.launchCatching { + inventoryRepository.getOwnedItems(true).collect { adapter.setOwnedItems(it) } + } lifecycleScope.launch(ExceptionHandler.coroutine()) { val mounts = inventoryRepository.getMounts( animalType, @@ -231,14 +227,14 @@ class PetDetailRecyclerFragment : private fun showFeedingDialog(pet: Pet, food: Food?) { if (food != null) { val context = activity ?: context ?: return - compositeSubscription.add( - feedPetUseCase.observable( + lifecycleScope.launchCatching { + feedPetUseCase.callInteractor( FeedPetUseCase.RequestValues( pet, food, context ) - ).subscribeWithErrorHandler {} - ) + ) + } return } val fragment = ItemDialogFragment() diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/stable/StableRecyclerFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/stable/StableRecyclerFragment.kt index ac4ae7502..615c21d74 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/stable/StableRecyclerFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/stable/StableRecyclerFragment.kt @@ -15,6 +15,7 @@ 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.ExceptionHandler +import com.habitrpg.android.habitica.helpers.launchCatching import com.habitrpg.android.habitica.models.inventory.Egg import com.habitrpg.android.habitica.models.inventory.HatchingPotion import com.habitrpg.android.habitica.ui.adapter.inventory.StableRecyclerAdapter @@ -118,11 +119,11 @@ class StableRecyclerFragment : binding?.recyclerView?.itemAnimator = SafeDefaultItemAnimator() adapter?.let { - compositeSubscription.add( - it.getEquipFlowable() - .flatMap { key -> inventoryRepository.equip(if (itemType == "pets") "pet" else "mount", key) } - .subscribe({ }, ExceptionHandler.rx()) - ) + it.onEquip = { + lifecycleScope.launchCatching { + inventoryRepository.equip(if (itemType == "pets") "pet" else "mount", it) + } + } } } userViewModel.user.observe(viewLifecycleOwner) { diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/AccountPreferenceFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/AccountPreferenceFragment.kt index 09e002eae..95453313e 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/AccountPreferenceFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/AccountPreferenceFragment.kt @@ -205,12 +205,11 @@ class AccountPreferenceFragment : val dialog = HabiticaAlertDialog(context) dialog.setTitle(R.string.are_you_sure) dialog.addButton(R.string.disconnect, true) { _, _ -> - apiClient.disconnectSocial(network) - .subscribe({ - lifecycleScope.launch(ExceptionHandler.coroutine()) { - userRepository.retrieveUser(true, true) - } - displayDisconnectSuccess(networkName) }, ExceptionHandler.rx()) + lifecycleScope.launch { + apiClient.disconnectSocial(network) + userRepository.retrieveUser(true, true) + displayDisconnectSuccess(networkName) + } } dialog.addCancelButton() dialog.show() @@ -282,19 +281,17 @@ class AccountPreferenceFragment : passwordEditText?.showErrorIfNecessary() passwordRepeatEditText?.showErrorIfNecessary() if (passwordEditText?.isValid != true || passwordRepeatEditText?.isValid != true) return@addButton - userRepository.updatePassword( - oldPasswordEditText?.text ?: "", - passwordEditText.text ?: "", - passwordRepeatEditText.text ?: "") - .subscribe( - { - (activity as? SnackbarActivity)?.showSnackbar( - content = context.getString(R.string.password_changed), - displayType = HabiticaSnackbar.SnackbarDisplayType.SUCCESS - ) - }, - ExceptionHandler.rx() + lifecycleScope.launchCatching { + userRepository.updatePassword( + oldPasswordEditText?.text ?: "", + passwordEditText.text ?: "", + passwordRepeatEditText.text ?: "" ) + (activity as? SnackbarActivity)?.showSnackbar( + content = context.getString(R.string.password_changed), + displayType = HabiticaSnackbar.SnackbarDisplayType.SUCCESS + ) + } dialog.dismiss() } dialog.addCancelButton() @@ -331,16 +328,18 @@ class AccountPreferenceFragment : passwordRepeatEditText?.showErrorIfNecessary() 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 ?: "") - .subscribe( - { - (activity as? SnackbarActivity)?.showSnackbar( - content = context.getString(R.string.password_added), - displayType = HabiticaSnackbar.SnackbarDisplayType.SUCCESS - ) - }, - ExceptionHandler.rx() + lifecycleScope.launchCatching { + apiClient.registerUser( + user?.username ?: "", + email ?: "", + passwordEditText.text ?: "", + passwordRepeatEditText?.text ?: "" ) + (activity as? SnackbarActivity)?.showSnackbar( + content = context.getString(R.string.password_added), + displayType = HabiticaSnackbar.SnackbarDisplayType.SUCCESS + ) + } dialog.dismiss() } dialog.addCancelButton() @@ -369,16 +368,16 @@ class AccountPreferenceFragment : KeyboardUtil.dismissKeyboard(activity) emailEditText?.showErrorIfNecessary() if (emailEditText?.isValid != true) return@addButton - userRepository.updateEmail(emailEditText.text.toString(), passwordEditText?.text.toString()) - .subscribe( - { - lifecycleScope.launch(ExceptionHandler.coroutine()) { - userRepository.retrieveUser(true, true) - } - configurePreference(findPreference("email"), emailEditText.text.toString()) - }, - ExceptionHandler.rx() + lifecycleScope.launchCatching { + userRepository.updateEmail( + emailEditText.text.toString(), + passwordEditText?.text.toString() ) + lifecycleScope.launch(ExceptionHandler.coroutine()) { + userRepository.retrieveUser(true, true) + } + configurePreference(findPreference("email"), emailEditText.text.toString()) + } dialog.dismiss() } dialog.addCancelButton() @@ -442,24 +441,23 @@ class AccountPreferenceFragment : private fun deleteAccount(password: String) { val dialog = HabiticaProgressDialog.show(context, R.string.deleting_account) - compositeSubscription.add( - userRepository.deleteAccount(password).subscribe({ _ -> + lifecycleScope.launchCatching({ throwable -> + dialog?.dismiss() + if (throwable is HttpException && throwable.code() == 401) { + val errorDialog = context?.let { HabiticaAlertDialog(it) } + errorDialog?.setTitle(R.string.authentication_error_title) + errorDialog?.setMessage(R.string.incorrect_password) + errorDialog?.addCloseButton() + errorDialog?.show() + } + ExceptionHandler.reportError(throwable) + }) { + userRepository.deleteAccount(password) dialog?.dismiss() accountDialog.dismiss() context?.let { HabiticaBaseApplication.logout(it) } activity?.finish() - }) { throwable -> - dialog?.dismiss() - if (throwable is HttpException && throwable.code() == 401) { - val errorDialog = context?.let { HabiticaAlertDialog(it) } - errorDialog?.setTitle(R.string.authentication_error_title) - errorDialog?.setMessage(R.string.incorrect_password) - errorDialog?.addCloseButton() - errorDialog?.show() - } - ExceptionHandler.reportError(throwable) - } - ) + } } private fun showAccountResetConfirmation(user: User?) { diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/PreferencesFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/PreferencesFragment.kt index 8191c4983..5ff94f39d 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/PreferencesFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/PreferencesFragment.kt @@ -70,7 +70,9 @@ class PreferencesFragment : BasePreferencesFragment(), SharedPreferences.OnShare super.onViewCreated(view, savedInstanceState) listView.itemAnimator = null - userRepository.retrieveTeamPlans().subscribe({}, ExceptionHandler.rx()) + lifecycleScope.launchCatching { + userRepository.retrieveTeamPlans() + } } override fun setupPreferences() { @@ -227,7 +229,9 @@ class PreferencesFragment : BasePreferencesFragment(), SharedPreferences.OnShare "cds_time" -> { val timeval = sharedPreferences.getString("cds_time", "0") ?: "0" val hour = Integer.parseInt(timeval) - userRepository.changeCustomDayStart(hour).subscribe({ }, ExceptionHandler.rx()) + lifecycleScope.launchCatching { + userRepository.changeCustomDayStart(hour) + } val preference = findPreference(key) preference?.summary = preference?.entry } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/purchases/GiftBalanceGemsFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/purchases/GiftBalanceGemsFragment.kt index 6e6d6afd0..4dde1b73f 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/purchases/GiftBalanceGemsFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/purchases/GiftBalanceGemsFragment.kt @@ -10,10 +10,9 @@ 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.ExceptionHandler +import com.habitrpg.android.habitica.helpers.launchCatching import com.habitrpg.android.habitica.models.members.Member import com.habitrpg.android.habitica.ui.fragments.BaseFragment -import kotlinx.coroutines.launch import javax.inject.Inject class GiftBalanceGemsFragment : BaseFragment() { @@ -64,20 +63,12 @@ class GiftBalanceGemsFragment : BaseFragment() { try { val amount = binding?.giftEditText?.text.toString().toInt() giftedMember?.id?.let { - compositeSubscription.add( + lifecycleScope.launchCatching({ + isGifting = false + }) { socialRepository.transferGems(it, amount) - .doOnError { - isGifting = false - } - .subscribe( - { - lifecycleScope.launch(ExceptionHandler.coroutine()) { - userRepository.retrieveUser(false) - } - activity?.finish() - }, ExceptionHandler.rx() - ) - ) + userRepository.retrieveUser(false) + } } } catch (ignored: NumberFormatException) {} } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/purchases/SubscriptionFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/purchases/SubscriptionFragment.kt index 330503222..022da9244 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/purchases/SubscriptionFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/purchases/SubscriptionFragment.kt @@ -18,9 +18,10 @@ import com.habitrpg.android.habitica.databinding.FragmentSubscriptionBinding 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.helpers.ExceptionHandler +import com.habitrpg.android.habitica.helpers.launchCatching import com.habitrpg.android.habitica.models.user.User import com.habitrpg.android.habitica.ui.activities.GiftSubscriptionActivity import com.habitrpg.android.habitica.ui.fragments.BaseFragment @@ -88,15 +89,19 @@ class SubscriptionFragment : BaseFragment() { binding?.refreshLayout?.setOnRefreshListener { refresh() } - compositeSubscription.add( - inventoryRepository.getLatestMysteryItem().subscribe( - { - binding?.subBenefitsMysteryItemIcon?.loadImage("shop_set_mystery_${it.key?.split("_")?.last()}") - binding?.subBenefitsMysteryItemText?.text = context?.getString(R.string.subscribe_listitem3_description_new, it.text) - }, - ExceptionHandler.rx() - ) - ) + lifecycleScope.launchCatching { + inventoryRepository.getLatestMysteryItem().collect { + binding?.subBenefitsMysteryItemIcon?.loadImage( + "shop_set_mystery_${ + it.key?.split( + "_" + )?.last() + }" + ) + binding?.subBenefitsMysteryItemText?.text = + context?.getString(R.string.subscribe_listitem3_description_new, it.text) + } + } AmplitudeManager.sendNavigationEvent("subscription screen") } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/setup/AvatarSetupFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/setup/AvatarSetupFragment.kt index 31dc320b3..36cf20998 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/setup/AvatarSetupFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/setup/AvatarSetupFragment.kt @@ -15,7 +15,6 @@ import com.habitrpg.android.habitica.data.InventoryRepository import com.habitrpg.android.habitica.data.SetupCustomizationRepository import com.habitrpg.android.habitica.data.UserRepository import com.habitrpg.android.habitica.databinding.FragmentSetupAvatarBinding -import com.habitrpg.android.habitica.extensions.subscribeWithErrorHandler import com.habitrpg.android.habitica.helpers.launchCatching import com.habitrpg.android.habitica.models.SetupCustomization import com.habitrpg.android.habitica.models.user.User @@ -63,7 +62,11 @@ class AvatarSetupFragment : BaseFragment() { userRepository.updateUser(it) } } - adapter?.equipGearEvents?.flatMap { inventoryRepository.equip("equipped", it) }?.subscribeWithErrorHandler {}?.let { compositeSubscription.add(it) } + adapter?.onEquipGear = { + lifecycleScope.launchCatching { + inventoryRepository.equip("equipped", it) + } + } this.adapter?.user = this.user val layoutManager = LinearLayoutManager(activity) diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/setup/WelcomeFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/setup/WelcomeFragment.kt index 8746f1ca1..d4721ab31 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/setup/WelcomeFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/setup/WelcomeFragment.kt @@ -14,15 +14,17 @@ 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.helpers.launchCatching 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.MutableStateFlow +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.firstOrNull +import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch -import java.util.concurrent.TimeUnit import javax.inject.Inject class WelcomeFragment : BaseFragment() { @@ -34,16 +36,25 @@ class WelcomeFragment : BaseFragment() { override var binding: FragmentWelcomeBinding? = null - override fun createBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentWelcomeBinding { + override fun createBinding( + inflater: LayoutInflater, + container: ViewGroup? + ): FragmentWelcomeBinding { return FragmentWelcomeBinding.inflate(inflater, container, false) } - private val displayNameVerificationEvents = PublishSubject.create() - private val usernameVerificationEvents = PublishSubject.create() + private val displayNameVerificationEvents = MutableStateFlow(null) + private val usernameVerificationEvents = MutableStateFlow(null) private val checkmarkIcon: Drawable by lazy { context?.let { - BitmapDrawable(resources, HabiticaIconsHelper.imageOfCheckmark(ContextCompat.getColor(it, R.color.green_50), 1f)) + BitmapDrawable( + resources, + HabiticaIconsHelper.imageOfCheckmark( + ContextCompat.getColor(it, R.color.green_50), + 1f + ) + ) } ?: VectorDrawable() } private val alertIcon: Drawable by lazy { @@ -63,53 +74,74 @@ class WelcomeFragment : BaseFragment() { binding?.displayNameEditText?.addTextChangedListener( OnChangeTextWatcher { p0, _, _, _ -> - displayNameVerificationEvents.onNext(p0.toString()) + displayNameVerificationEvents.value = p0.toString() } ) binding?.usernameEditText?.addTextChangedListener( OnChangeTextWatcher { p0, _, _, _ -> - usernameVerificationEvents.onNext(p0.toString()) + usernameVerificationEvents.value = p0.toString() } ) - compositeSubscription.add( - displayNameVerificationEvents.toFlowable(BackpressureStrategy.DROP) - .map { it.length in 1..30 } - .subscribeWithErrorHandler { + lifecycleScope.launchCatching { + displayNameVerificationEvents + .map { it?.length in 1..30 } + .collect { if (it) { - binding?.displayNameEditText?.setCompoundDrawablesWithIntrinsicBounds(null, null, checkmarkIcon, null) + binding?.displayNameEditText?.setCompoundDrawablesWithIntrinsicBounds( + null, + null, + checkmarkIcon, + null + ) binding?.issuesTextView?.visibility = View.GONE } else { - binding?.displayNameEditText?.setCompoundDrawablesWithIntrinsicBounds(null, null, alertIcon, null) + binding?.displayNameEditText?.setCompoundDrawablesWithIntrinsicBounds( + null, + null, + alertIcon, + null + ) binding?.issuesTextView?.visibility = View.VISIBLE - binding?.issuesTextView?.text = context?.getString(R.string.display_name_length_error) + binding?.issuesTextView?.text = + context?.getString(R.string.display_name_length_error) } } - ) - compositeSubscription.add( - usernameVerificationEvents.toFlowable(BackpressureStrategy.DROP) - .filter { it.length in 1..30 } - .throttleLast(1, TimeUnit.SECONDS) - .flatMap { userRepository.verifyUsername(it) } - .subscribeWithErrorHandler { - if (it.isUsable) { - binding?.usernameEditText?.setCompoundDrawablesWithIntrinsicBounds(null, null, checkmarkIcon, null) + } + lifecycleScope.launchCatching { + usernameVerificationEvents + .filter { it?.length in 1..30 } + .filterNotNull() + .map { userRepository.verifyUsername(it) } + .collect { + if (it?.isUsable == true) { + binding?.usernameEditText?.setCompoundDrawablesWithIntrinsicBounds( + null, + null, + checkmarkIcon, + null + ) binding?.issuesTextView?.visibility = View.GONE } else { - binding?.usernameEditText?.setCompoundDrawablesWithIntrinsicBounds(null, null, alertIcon, null) + binding?.usernameEditText?.setCompoundDrawablesWithIntrinsicBounds( + null, + null, + alertIcon, + null + ) binding?.issuesTextView?.visibility = View.VISIBLE - binding?.issuesTextView?.text = it.issues.joinToString("\n") + binding?.issuesTextView?.text = it?.issues?.joinToString("\n") } - nameValidEvents.onNext(it.isUsable) + nameValidEvents.onNext(it?.isUsable) } - ) + } lifecycleScope.launch(ExceptionHandler.coroutine()) { val user = userRepository.getUser().firstOrNull() binding?.displayNameEditText?.setText(user?.profile?.name) - displayNameVerificationEvents.onNext(user?.profile?.name ?: "") + displayNameVerificationEvents.value = user?.profile?.name ?: "" binding?.usernameEditText?.setText(user?.authentication?.localAuthentication?.username) - usernameVerificationEvents.onNext(user?.username ?: "") + usernameVerificationEvents.value = user?.username ?: "" } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/skills/SkillTasksRecyclerViewFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/skills/SkillTasksRecyclerViewFragment.kt index e677357d5..116dd322c 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/skills/SkillTasksRecyclerViewFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/skills/SkillTasksRecyclerViewFragment.kt @@ -9,15 +9,11 @@ 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.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 import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel import com.habitrpg.shared.habitica.models.tasks.TaskType -import io.reactivex.rxjava3.core.BackpressureStrategy -import io.reactivex.rxjava3.core.Flowable -import io.reactivex.rxjava3.subjects.PublishSubject import kotlinx.coroutines.flow.map import javax.inject.Inject @@ -37,11 +33,7 @@ class SkillTasksRecyclerViewFragment : BaseFragment var adapter: SkillTasksRecyclerViewAdapter = SkillTasksRecyclerViewAdapter() internal var layoutManager: LinearLayoutManager? = null - private val taskSelectionEvents = PublishSubject.create() - - fun getTaskSelectionEvents(): Flowable { - return taskSelectionEvents.toFlowable(BackpressureStrategy.DROP) - } + var onTaskSelection: ((Task) -> Unit)? = null override fun injectFragment(component: UserComponent) { component.inject(this) @@ -54,14 +46,7 @@ class SkillTasksRecyclerViewFragment : BaseFragment binding?.recyclerView?.layoutManager = layoutManager adapter = SkillTasksRecyclerViewAdapter() - compositeSubscription.add( - adapter.getTaskSelectionEvents().subscribe( - { - taskSelectionEvents.onNext(it) - }, - ExceptionHandler.rx() - ) - ) + adapter.onTaskSelection = { onTaskSelection?.invoke(it) } binding?.recyclerView?.adapter = adapter val additionalGroupIDs = userViewModel.mirrorGroupTasks.toTypedArray() diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/skills/SkillsFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/skills/SkillsFragment.kt index 8b6322401..ba043322a 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/skills/SkillsFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/skills/SkillsFragment.kt @@ -28,8 +28,8 @@ import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel import com.habitrpg.android.habitica.ui.views.HabiticaIconsHelper import com.habitrpg.android.habitica.ui.views.HabiticaSnackbar import com.habitrpg.android.habitica.ui.views.HabiticaSnackbar.Companion.showSnackbar -import io.reactivex.rxjava3.core.Flowable import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.combine import kotlinx.coroutines.launch import javax.inject.Inject @@ -80,19 +80,19 @@ class SkillsFragment : BaseMainFragment() { adapter?.mana = user.stats?.mp ?: 0.0 adapter?.level = user.stats?.lvl ?: 0 adapter?.specialItems = user.items?.special - Flowable.combineLatest( - userRepository.getSkills(user), - userRepository.getSpecialItems(user) - ) { skills, items -> - val allEntries = mutableListOf() - for (skill in skills) { - allEntries.add(skill) - } - for (item in items) { - allEntries.add(item) - } - return@combineLatest allEntries - }.subscribe({ skills -> adapter?.setSkillList(skills) }, ExceptionHandler.rx()) + lifecycleScope.launchCatching { + userRepository.getSkills(user) + .combine(userRepository.getSpecialItems(user)) { skills, items -> + val allEntries = mutableListOf() + for (skill in skills) { + allEntries.add(skill) + } + for (item in items) { + allEntries.add(item) + } + return@combine allEntries + }.collect { skills -> adapter?.setSkillList(skills) } + } } private fun onSkillSelected(skill: Skill) { diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/InboxMessageListFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/InboxMessageListFragment.kt index 9c87e9eaf..dd37586d8 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/InboxMessageListFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/InboxMessageListFragment.kt @@ -23,6 +23,7 @@ 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.launchCatching import com.habitrpg.android.habitica.models.social.ChatMessage import com.habitrpg.android.habitica.ui.activities.FullProfileActivity import com.habitrpg.android.habitica.ui.activities.MainActivity @@ -264,7 +265,11 @@ class InboxMessageListFragment : BaseMainFragment socialRepository.deleteMessage(chatMessage).subscribe({ }, ExceptionHandler.rx()) } + .setPositiveButton(R.string.yes) { _, _ -> + lifecycleScope.launchCatching { + socialRepository.deleteMessage(chatMessage) + } + } .setNegativeButton(R.string.no, null).show() } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/InboxOverviewFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/InboxOverviewFragment.kt index d0dd57c43..be5499b91 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/InboxOverviewFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/InboxOverviewFragment.kt @@ -20,6 +20,7 @@ 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.launchCatching import com.habitrpg.android.habitica.models.social.InboxConversation import com.habitrpg.android.habitica.ui.fragments.BaseMainFragment import com.habitrpg.android.habitica.ui.helpers.dismissKeyboard @@ -51,7 +52,9 @@ class InboxOverviewFragment : BaseMainFragment(), androidx savedInstanceState: Bundle? ): View? { this.hidesToolbar = true - compositeSubscription.add(this.socialRepository.markPrivateMessagesRead(null).subscribe({ }, ExceptionHandler.rx())) + lifecycleScope.launchCatching { + socialRepository.markPrivateMessagesRead(null) + } return super.onCreateView(inflater, container, savedInstanceState) } @@ -141,15 +144,10 @@ class InboxOverviewFragment : BaseMainFragment(), androidx } private fun retrieveMessages() { - compositeSubscription.add( - this.socialRepository.retrieveInboxConversations() - .subscribe( - { - binding?.inboxRefreshLayout?.isRefreshing = false - }, - ExceptionHandler.rx() - ) - ) + lifecycleScope.launchCatching { + socialRepository.retrieveInboxConversations() + binding?.inboxRefreshLayout?.isRefreshing = false + } } override fun onRefresh() { 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 165f63010..403c3fee5 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 @@ -19,6 +19,7 @@ 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.launchCatching import com.habitrpg.android.habitica.models.inventory.Quest import com.habitrpg.android.habitica.models.inventory.QuestContent import com.habitrpg.android.habitica.models.members.Member @@ -42,16 +43,22 @@ class QuestDetailFragment : BaseMainFragment() { @Inject lateinit var socialRepository: SocialRepository + @Inject lateinit var inventoryRepository: InventoryRepository + @field:[Inject Named(AppModule.NAMED_USER_ID)] lateinit var userId: String + @Inject lateinit var userViewModel: MainUserViewModel override var binding: FragmentQuestDetailBinding? = null - override fun createBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentQuestDetailBinding { + override fun createBinding( + inflater: LayoutInflater, + container: ViewGroup? + ): FragmentQuestDetailBinding { return FragmentQuestDetailBinding.inflate(inflater, container, false) } @@ -107,9 +114,10 @@ class QuestDetailFragment : BaseMainFragment() { 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) - } + binding?.questLeaderView?.text = + context?.getString(R.string.quest_leader_header, member?.displayName) } + } val user = userViewModel.user.value if (binding?.questResponseWrapper != null) { @@ -142,7 +150,10 @@ class QuestDetailFragment : BaseMainFragment() { } binding?.titleView?.text = questContent.text // We need to do this, because the quest description can contain markdown AND HTML. - binding?.descriptionView?.setText(MarkdownParser.parseMarkdown(questContent.notes).toHtml().fromHtml(), TextView.BufferType.SPANNABLE) + binding?.descriptionView?.setText( + MarkdownParser.parseMarkdown(questContent.notes).toHtml().fromHtml(), + TextView.BufferType.SPANNABLE + ) binding?.questScrollImageView?.loadImage("inventory_quest_scroll_" + questContent.key) } @@ -158,7 +169,8 @@ class QuestDetailFragment : BaseMainFragment() { if (quest?.active == true && participant.participatesInQuest == false) { continue } - val participantView = inflater?.inflate(R.layout.quest_participant, binding?.questParticipantList, false) + val participantView = + inflater?.inflate(R.layout.quest_participant, binding?.questParticipantList, false) val textView = participantView?.findViewById(R.id.participant_name) as? TextView textView?.text = participant.displayName val statusTextView = participantView?.findViewById(R.id.status_view) as? TextView @@ -167,15 +179,30 @@ class QuestDetailFragment : BaseMainFragment() { when (participant.participatesInQuest) { null -> { statusTextView?.setText(R.string.pending) - statusTextView?.setTextColor(ContextCompat.getColor(it, R.color.text_ternary)) + statusTextView?.setTextColor( + ContextCompat.getColor( + it, + R.color.text_ternary + ) + ) } true -> { statusTextView?.setText(R.string.accepted) - statusTextView?.setTextColor(ContextCompat.getColor(it, R.color.text_green)) + statusTextView?.setTextColor( + ContextCompat.getColor( + it, + R.color.text_green + ) + ) } else -> { statusTextView?.setText(R.string.declined) - statusTextView?.setTextColor(ContextCompat.getColor(it, R.color.text_red)) + statusTextView?.setTextColor( + ContextCompat.getColor( + it, + R.color.text_red + ) + ) } } } @@ -196,8 +223,10 @@ class QuestDetailFragment : BaseMainFragment() { } else { binding?.participantsHeader?.setText(R.string.invitations) @SuppressLint("SetTextI18n") - binding?.participantsHeaderCount?.text = participantCount.toString() + "/" + participants?.size - beginQuestMessage = getString(R.string.quest_begin_message, participantCount, participants?.size) + binding?.participantsHeaderCount?.text = + participantCount.toString() + "/" + participants?.size + beginQuestMessage = + getString(R.string.quest_begin_message, participantCount, participants?.size) } } @@ -217,8 +246,9 @@ class QuestDetailFragment : BaseMainFragment() { alert.addButton(R.string.yes, true) { _, _ -> val party = party if (party != null) { - socialRepository.forceStartQuest(party) - .subscribe({ }, ExceptionHandler.rx()) + lifecycleScope.launchCatching { + socialRepository.forceStartQuest(party) + } } } alert.addButton(R.string.no, false) @@ -234,12 +264,11 @@ class QuestDetailFragment : BaseMainFragment() { .setMessage(R.string.quest_abort_message) .setPositiveButton(R.string.yes) { _, _ -> party?.id?.let { partyID -> - socialRepository.abortQuest(partyID) - .subscribe({ - lifecycleScope.launch(ExceptionHandler.coroutine()) { - userRepository.retrieveUser(true) - } - getActivity()?.supportFragmentManager?.popBackStack() }, ExceptionHandler.rx()) + lifecycleScope.launchCatching { + socialRepository.abortQuest(partyID) + userRepository.retrieveUser(true) + getActivity()?.supportFragmentManager?.popBackStack() + } } }.setNegativeButton(R.string.no) { _, _ -> } builder.show() @@ -248,13 +277,11 @@ class QuestDetailFragment : BaseMainFragment() { alert.setMessage(R.string.quest_cancel_message) alert.addButton(R.string.yes, true) { _, _ -> party?.id?.let { partyID -> - socialRepository.cancelQuest(partyID) - .subscribe({ - lifecycleScope.launch(ExceptionHandler.coroutine()) { - userRepository.retrieveUser(true) - } - getActivity()?.supportFragmentManager?.popBackStack() - }, ExceptionHandler.rx()) + lifecycleScope.launchCatching { + socialRepository.cancelQuest(partyID) + userRepository.retrieveUser(true) + getActivity()?.supportFragmentManager?.popBackStack() + } } } alert.addButton(R.string.no, false) @@ -269,13 +296,12 @@ class QuestDetailFragment : BaseMainFragment() { .setMessage(if (quest?.active == true) R.string.quest_leave_message else R.string.quest_leave_message_nostart) .setPositiveButton(R.string.yes) { _, _ -> party?.id?.let { partyID -> - socialRepository.leaveQuest(partyID) - .subscribe({ - lifecycleScope.launch(ExceptionHandler.coroutine()) { - socialRepository.retrieveGroup(partyID) - userRepository.retrieveUser(true) - } - getActivity()?.supportFragmentManager?.popBackStack() }, ExceptionHandler.rx()) + lifecycleScope.launchCatching { + socialRepository.leaveQuest(partyID) + socialRepository.retrieveGroup(partyID) + userRepository.retrieveUser(true) + getActivity()?.supportFragmentManager?.popBackStack() + } } }.setNegativeButton(R.string.no) { _, _ -> } builder.show() diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/challenges/ChallengeDetailFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/challenges/ChallengeDetailFragment.kt index 6f0e54624..16780f1b6 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/challenges/ChallengeDetailFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/challenges/ChallengeDetailFragment.kt @@ -11,7 +11,6 @@ import android.view.MenuItem import android.view.View import android.view.ViewGroup import androidx.core.net.toUri -import androidx.core.view.isVisible import androidx.lifecycle.lifecycleScope import com.habitrpg.android.habitica.R import com.habitrpg.android.habitica.components.UserComponent @@ -23,6 +22,7 @@ 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.launchCatching import com.habitrpg.android.habitica.models.members.Member import com.habitrpg.android.habitica.models.social.Challenge import com.habitrpg.android.habitica.models.tasks.Task @@ -39,7 +39,8 @@ 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 kotlinx.coroutines.launch +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.map import retrofit2.HttpException import javax.inject.Inject @@ -94,43 +95,20 @@ class ChallengeDetailFragment : BaseMainFragment FullProfileActivity.open(leaderID) } - loadTasks() - - binding?.joinButton?.setOnClickListener { - challenge?.let { challenge -> - challengeRepository.joinChallenge(challenge) - .subscribe({ - lifecycleScope.launch(ExceptionHandler.coroutine()) { - userRepository.retrieveUser(true) - } - }, ExceptionHandler.rx()) - } - } - binding?.leaveButton?.setOnClickListener { showChallengeLeaveDialog() } - - refresh() - } - - private fun loadTasks() { challengeID?.let { id -> - compositeSubscription.add( + lifecycleScope.launchCatching { challengeRepository.getChallenge(id) - .doOnNext { - set(it) - } .map { - return@map (it.leaderId ?: "") + set(it) + (it.leaderId ?: "") } - .filter { it.isNotEmpty() } - .subscribe({ - lifecycleScope.launch(ExceptionHandler.coroutine()) { - set(socialRepository.retrieveMember(it)) - } - }, ExceptionHandler.rx()) - ) - compositeSubscription.add( - challengeRepository.getChallengeTasks(id).subscribe( - { taskList -> + .distinctUntilChanged() + .collect { + set(socialRepository.retrieveMember(it)) + } + } + lifecycleScope.launchCatching { + challengeRepository.getChallengeTasks(id).collect { taskList -> binding?.taskGroupLayout?.removeAllViewsInLayout() val todos = ArrayList() @@ -163,20 +141,27 @@ class ChallengeDetailFragment : BaseMainFragment if (rewards.size > 0) { addRewards(rewards) } - }, - ExceptionHandler.rx() - ) - ) + } + } - compositeSubscription.add( - challengeRepository.isChallengeMember(id).subscribe( - { isMember -> - setJoined(isMember) - }, - ExceptionHandler.rx() - ) - ) + lifecycleScope.launchCatching { + challengeRepository.isChallengeMember(id).collect { isMember -> + setJoined(isMember) + } + } } + + binding?.joinButton?.setOnClickListener { + challenge?.let { challenge -> + lifecycleScope.launchCatching { + challengeRepository.joinChallenge(challenge) + userRepository.retrieveUser(true) + } + } + } + binding?.leaveButton?.setOnClickListener { showChallengeLeaveDialog() } + + refresh() } override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { @@ -235,18 +220,15 @@ class ChallengeDetailFragment : BaseMainFragment private fun refresh() { challengeID?.let { id -> - challengeRepository.retrieveChallenge(id) - .flatMap { challengeRepository.retrieveChallengeTasks(id) } - .subscribe({ taskList -> - if (binding?.taskGroupLayout?.childCount == 0 && taskList.tasks.isNotEmpty()) { - loadTasks() - } - }, { - if (it is HttpException && it.code() == 404) { - MainNavigationController.navigateBack() - } - ExceptionHandler.reportError(it) - }) + lifecycleScope.launchCatching({ + if (it is HttpException && it.code() == 404) { + MainNavigationController.navigateBack() + } + ExceptionHandler.reportError(it) + }) { + challengeRepository.retrieveChallenge(id) + challengeRepository.retrieveChallengeTasks(id) + } } } @@ -351,11 +333,15 @@ class ChallengeDetailFragment : BaseMainFragment alert.setMessage(this.getString(R.string.challenge_leave_description)) alert.addButton(R.string.leave_keep_tasks, true) { _, _ -> val challenge = challenge ?: return@addButton - challengeRepository.leaveChallenge(challenge, "keep-all").subscribe({}, ExceptionHandler.rx()) + lifecycleScope.launchCatching { + challengeRepository.leaveChallenge(challenge, "keep-all") + } } alert.addButton(R.string.leave_delete_tasks, isPrimary = false, isDestructive = true) { _, _ -> val challenge = challenge ?: return@addButton - challengeRepository.leaveChallenge(challenge, "remove-all").subscribe({}, ExceptionHandler.rx()) + lifecycleScope.launchCatching { + challengeRepository.leaveChallenge(challenge, "remove-all") + } } alert.setExtraCloseButtonVisibility(View.VISIBLE) alert.show() diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/challenges/ChallengeListFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/challenges/ChallengeListFragment.kt index 5587f488d..10ef6d705 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 @@ -14,6 +14,7 @@ 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.launchCatching import com.habitrpg.android.habitica.models.social.Challenge import com.habitrpg.android.habitica.models.social.Group import com.habitrpg.android.habitica.modules.AppModule @@ -21,26 +22,32 @@ import com.habitrpg.android.habitica.ui.adapter.social.ChallengesListViewAdapter 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 kotlinx.coroutines.flow.combine import kotlinx.coroutines.launch import javax.inject.Inject import javax.inject.Named -class ChallengeListFragment : BaseFragment(), androidx.swiperefreshlayout.widget.SwipeRefreshLayout.OnRefreshListener { +class ChallengeListFragment : BaseFragment(), + androidx.swiperefreshlayout.widget.SwipeRefreshLayout.OnRefreshListener { @Inject lateinit var challengeRepository: ChallengeRepository + @Inject lateinit var socialRepository: SocialRepository + @Inject lateinit var userRepository: UserRepository + @field:[Inject Named(AppModule.NAMED_USER_ID)] lateinit var userId: String override var binding: FragmentRefreshRecyclerviewBinding? = null - override fun createBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentRefreshRecyclerviewBinding { + override fun createBinding( + inflater: LayoutInflater, + container: ViewGroup? + ): FragmentRefreshRecyclerviewBinding { return FragmentRefreshRecyclerviewBinding.inflate(inflater, container, false) } @@ -68,7 +75,8 @@ class ChallengeListFragment : BaseFragment() super.onViewCreated(view, savedInstanceState) challengeAdapter = ChallengesListViewAdapter(viewUserChallengesOnly, userId) - challengeAdapter?.getOpenDetailFragmentFlowable()?.subscribe({ openDetailFragment(it) }, ExceptionHandler.rx()) + challengeAdapter?.getOpenDetailFragmentFlowable() + ?.subscribe({ openDetailFragment(it) }, ExceptionHandler.rx()) ?.let { compositeSubscription.add(it) } binding?.refreshLayout?.setOnRefreshListener(this) @@ -79,16 +87,18 @@ class ChallengeListFragment : BaseFragment() getString(R.string.empty_discover_description) ) } - binding?.recyclerView?.layoutManager = androidx.recyclerview.widget.LinearLayoutManager(this.activity) + binding?.recyclerView?.layoutManager = + androidx.recyclerview.widget.LinearLayoutManager(this.activity) binding?.recyclerView?.adapter = challengeAdapter if (!viewUserChallengesOnly) { binding?.recyclerView?.setBackgroundResource(R.color.content_background) } lifecycleScope.launch(ExceptionHandler.coroutine()) { - socialRepository.getGroup(Group.TAVERN_ID).combine(socialRepository.getUserGroups("guild")) { tavern, guilds -> - return@combine Pair(tavern, guilds) - }.collect { + 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) @@ -112,7 +122,11 @@ class ChallengeListFragment : BaseFragment() } private fun openDetailFragment(challengeID: String) { - MainNavigationController.navigate(ChallengesOverviewFragmentDirections.openChallengeDetail(challengeID)) + MainNavigationController.navigate( + ChallengesOverviewFragmentDirections.openChallengeDetail( + challengeID + ) + ) } override fun injectFragment(component: UserComponent) { @@ -130,24 +144,20 @@ class ChallengeListFragment : BaseFragment() } private fun loadLocalChallenges() { - val observable: Flowable> = if (viewUserChallengesOnly) { - challengeRepository.getUserChallenges() - } else { - challengeRepository.getChallenges() + lifecycleScope.launchCatching { + val flow = if (viewUserChallengesOnly) { + challengeRepository.getUserChallenges() + } else { + challengeRepository.getChallenges() + } + flow.collect { challenges -> + if (challenges.isEmpty()) { + retrieveChallengesPage() + } + this@ChallengeListFragment.challenges = challenges + challengeAdapter?.updateUnfilteredData(challenges) + } } - - compositeSubscription.add( - observable.subscribe( - { challenges -> - if (challenges.isEmpty()) { - retrieveChallengesPage() - } - this.challenges = challenges - challengeAdapter?.updateUnfilteredData(challenges) - }, - ExceptionHandler.rx() - ) - ) } internal fun retrieveChallengesPage(forced: Boolean = false) { @@ -155,19 +165,15 @@ class ChallengeListFragment : BaseFragment() return } setRefreshing(true) - compositeSubscription.add( - challengeRepository.retrieveChallenges(nextPageToLoad, viewUserChallengesOnly).doOnComplete { - setRefreshing(false) - }.subscribe( - { - if (it.size < 10) { - loadedAllData = true - } - nextPageToLoad += 1 - }, - ExceptionHandler.rx() - ) - ) + lifecycleScope.launchCatching { + val challenges = + challengeRepository.retrieveChallenges(nextPageToLoad, viewUserChallengesOnly) + setRefreshing(false) + if ((challenges?.size ?: 0) < 10) { + loadedAllData = true + } + nextPageToLoad += 1 + } } internal fun showFilterDialog() { @@ -175,7 +181,8 @@ class ChallengeListFragment : BaseFragment() ChallengeFilterDialogHolder.showDialog( it, filterGroups ?: emptyList(), - filterOptions) { + filterOptions + ) { changeFilter(it) } } 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 0439cf78e..3645a9ec5 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 @@ -17,6 +17,7 @@ import com.habitrpg.android.habitica.data.UserRepository import com.habitrpg.android.habitica.databinding.FragmentGuildDetailBinding import com.habitrpg.android.habitica.helpers.AppConfigManager import com.habitrpg.android.habitica.helpers.MainNavigationController +import com.habitrpg.android.habitica.helpers.launchCatching import com.habitrpg.android.habitica.models.members.Member import com.habitrpg.android.habitica.models.social.Challenge import com.habitrpg.android.habitica.models.social.Group @@ -31,6 +32,7 @@ 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.flow.firstOrNull import kotlinx.coroutines.launch import javax.inject.Inject @@ -133,11 +135,12 @@ class GuildDetailFragment : BaseFragment() { private fun getGroupChallenges(): List { val groupChallenges = mutableListOf() - userRepository.getUserFlowable().forEach { - it.challenges?.forEach { - challengeRepository.getChallenge(it.challengeID).forEach { - if (it.groupId.equals(viewModel?.groupID)) { - groupChallenges.add(it) + lifecycleScope.launchCatching { + userRepository.getUser().collect { + it?.challenges?.forEach { membership -> + val challenge = challengeRepository.getChallenge(membership.challengeID).firstOrNull() + if (challenge != null && challenge.groupId == viewModel?.groupID) { + groupChallenges.add(challenge) } } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/guilds/GuildListFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/guilds/GuildListFragment.kt index 848024aa1..72e4d972a 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/guilds/GuildListFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/guilds/GuildListFragment.kt @@ -12,6 +12,7 @@ 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.ExceptionHandler +import com.habitrpg.android.habitica.helpers.launchCatching import com.habitrpg.android.habitica.ui.adapter.social.GuildListAdapter import com.habitrpg.android.habitica.ui.fragments.BaseFragment import com.habitrpg.android.habitica.ui.helpers.KeyboardUtil @@ -20,7 +21,9 @@ import com.habitrpg.common.habitica.helpers.EmptyItem import kotlinx.coroutines.launch import javax.inject.Inject -class GuildListFragment : BaseFragment(), SearchView.OnQueryTextListener, SearchView.OnCloseListener, SwipeRefreshLayout.OnRefreshListener { +class GuildListFragment : BaseFragment(), + SearchView.OnQueryTextListener, SearchView.OnCloseListener, + SwipeRefreshLayout.OnRefreshListener { @Inject lateinit var socialRepository: SocialRepository @@ -28,7 +31,10 @@ class GuildListFragment : BaseFragment(), Se override var binding: FragmentRefreshRecyclerviewBinding? = null var onlyShowUsersGuilds: Boolean = false - override fun createBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentRefreshRecyclerviewBinding { + override fun createBinding( + inflater: LayoutInflater, + container: ViewGroup? + ): FragmentRefreshRecyclerviewBinding { return FragmentRefreshRecyclerviewBinding.inflate(inflater, container, false) } @@ -41,7 +47,8 @@ class GuildListFragment : BaseFragment(), Se override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - binding?.recyclerView?.layoutManager = androidx.recyclerview.widget.LinearLayoutManager(activity) + binding?.recyclerView?.layoutManager = + androidx.recyclerview.widget.LinearLayoutManager(activity) viewAdapter.socialRepository = socialRepository binding?.recyclerView?.adapter = viewAdapter binding?.recyclerView?.itemAnimator = SafeDefaultItemAnimator() @@ -61,15 +68,11 @@ class GuildListFragment : BaseFragment(), Se } } } else { - compositeSubscription.add( - this.socialRepository.getPublicGuilds() - .subscribe( - { groups -> - this@GuildListFragment.viewAdapter.setUnfilteredData(groups) - }, - ExceptionHandler.rx() - ) - ) + lifecycleScope.launchCatching { + socialRepository.getPublicGuilds().collect { groups -> + this@GuildListFragment.viewAdapter.setUnfilteredData(groups) + } + } } this.fetchGuilds() } @@ -80,15 +83,10 @@ class GuildListFragment : BaseFragment(), Se } internal fun fetchGuilds() { - compositeSubscription.add( - this.socialRepository.retrieveGroups(if (onlyShowUsersGuilds) "guilds" else "publicGuilds") - .subscribe( - { - binding?.refreshLayout?.isRefreshing = false - }, - ExceptionHandler.rx() - ) - ) + lifecycleScope.launchCatching { + socialRepository.retrieveGroups(if (onlyShowUsersGuilds) "guilds" else "publicGuilds") + binding?.refreshLayout?.isRefreshing = false + } } override fun onQueryTextSubmit(s: String?): Boolean { diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/party/NoPartyFragmentFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/party/NoPartyFragmentFragment.kt index 1843af8bd..f4f29c64a 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/party/NoPartyFragmentFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/party/NoPartyFragmentFragment.kt @@ -25,6 +25,7 @@ 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.launchCatching import com.habitrpg.android.habitica.ui.activities.GroupFormActivity import com.habitrpg.android.habitica.ui.fragments.BaseMainFragment import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel @@ -78,7 +79,9 @@ class NoPartyFragmentFragment : BaseMainFragment() { } binding?.invitationsView?.rejectCall = { - socialRepository.rejectGroupInvite(it).subscribe({ }, ExceptionHandler.rx()) + lifecycleScope.launchCatching { + socialRepository.rejectGroupInvite(it) + } binding?.invitationWrapper?.visibility = View.GONE } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/party/PartyDetailFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/party/PartyDetailFragment.kt index a24a53493..281b1df0c 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/party/PartyDetailFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/party/PartyDetailFragment.kt @@ -21,6 +21,7 @@ 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.launchCatching import com.habitrpg.android.habitica.models.inventory.QuestContent import com.habitrpg.android.habitica.models.members.Member import com.habitrpg.android.habitica.models.social.Challenge @@ -108,12 +109,10 @@ class PartyDetailFragment : BaseFragment() { } binding?.invitationsView?.rejectCall = { - socialRepository.rejectGroupInvite(it) - .subscribe({ - lifecycleScope.launch(ExceptionHandler.coroutine()) { - userRepository.retrieveUser(false, true) - } - }, ExceptionHandler.rx()) + lifecycleScope.launchCatching { + socialRepository.rejectGroupInvite(it) + userRepository.retrieveUser(false, true) + } } viewModel?.getGroupData()?.observe(viewLifecycleOwner) { updateParty(it) } @@ -370,11 +369,12 @@ class PartyDetailFragment : BaseFragment() { private fun getGroupChallenges(): List { val groupChallenges = mutableListOf() - userRepository.getUserFlowable().forEach { - it.challenges?.forEach { - challengeRepository.getChallenge(it.challengeID).forEach { - if (it.groupId.equals(viewModel?.groupID)) { - groupChallenges.add(it) + lifecycleScope.launchCatching { + userRepository.getUser().collect { + it?.challenges?.forEach { membership -> + val challenge = challengeRepository.getChallenge(membership.challengeID).firstOrNull() + if (challenge != null && challenge.groupId == viewModel?.groupID) { + groupChallenges.add(challenge) } } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/support/FAQDetailFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/support/FAQDetailFragment.kt index 9c9e8c51f..50140e9c0 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/support/FAQDetailFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/support/FAQDetailFragment.kt @@ -5,10 +5,11 @@ import android.text.method.LinkMovementMethod 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.FAQRepository import com.habitrpg.android.habitica.databinding.FragmentFaqDetailBinding -import com.habitrpg.android.habitica.helpers.ExceptionHandler +import com.habitrpg.android.habitica.helpers.launchCatching import com.habitrpg.android.habitica.ui.fragments.BaseMainFragment import com.habitrpg.common.habitica.helpers.MarkdownParser import javax.inject.Inject @@ -42,15 +43,12 @@ class FAQDetailFragment : BaseMainFragment() { binding?.questionTextView?.text = args.question binding?.answerTextView?.text = MarkdownParser.parseMarkdown(args.answer) } else { - compositeSubscription.add( - faqRepository.getArticle(args.position).subscribe( - { faq -> - binding?.questionTextView?.text = faq.question - binding?.answerTextView?.text = MarkdownParser.parseMarkdown(faq.answer) - }, - ExceptionHandler.rx() - ) - ) + lifecycleScope.launchCatching { + faqRepository.getArticle(args.position).collect { faq -> + binding?.questionTextView?.text = faq.question + binding?.answerTextView?.text = MarkdownParser.parseMarkdown(faq.answer) + } + } } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/support/FAQOverviewFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/support/FAQOverviewFragment.kt index e80d41327..9fecc4330 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/support/FAQOverviewFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/support/FAQOverviewFragment.kt @@ -7,18 +7,18 @@ import android.view.View import android.view.ViewGroup import android.widget.ImageView 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.FAQRepository import com.habitrpg.android.habitica.databinding.FragmentFaqOverviewBinding import com.habitrpg.android.habitica.databinding.SupportFaqItemBinding -import com.habitrpg.common.habitica.extensions.layoutInflater import com.habitrpg.android.habitica.helpers.MainNavigationController -import com.habitrpg.android.habitica.helpers.ExceptionHandler +import com.habitrpg.android.habitica.helpers.launchCatching import com.habitrpg.android.habitica.ui.fragments.BaseMainFragment -import com.habitrpg.common.habitica.helpers.setMarkdown import com.habitrpg.android.habitica.ui.views.HabiticaIconsHelper -import io.reactivex.rxjava3.functions.Consumer +import com.habitrpg.common.habitica.extensions.layoutInflater +import com.habitrpg.common.habitica.helpers.setMarkdown import javax.inject.Inject class FAQOverviewFragment : BaseMainFragment() { @@ -77,22 +77,23 @@ class FAQOverviewFragment : BaseMainFragment() { } private fun loadArticles() { - compositeSubscription.add( - faqRepository.getArticles().subscribe( - Consumer { - val context = context ?: return@Consumer - for (article in it) { - val binding = SupportFaqItemBinding.inflate(context.layoutInflater, binding?.faqLinearLayout, true) - binding.textView.text = article.question - binding.root.setOnClickListener { - val direction = FAQOverviewFragmentDirections.openFAQDetail(null, null) - direction.position = article.position ?: 0 - MainNavigationController.navigate(direction) - } + lifecycleScope.launchCatching { + faqRepository.getArticles().collect { + val context = context ?: return@collect + for (article in it) { + val binding = SupportFaqItemBinding.inflate( + context.layoutInflater, + binding?.faqLinearLayout, + true + ) + binding.textView.text = article.question + binding.root.setOnClickListener { + val direction = FAQOverviewFragmentDirections.openFAQDetail(null, null) + direction.position = article.position ?: 0 + MainNavigationController.navigate(direction) } - }, - ExceptionHandler.rx() - ) - ) + } + } + } } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/tasks/RewardsRecyclerviewFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/tasks/RewardsRecyclerviewFragment.kt index d646de22e..45f7fcd56 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/tasks/RewardsRecyclerviewFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/tasks/RewardsRecyclerviewFragment.kt @@ -61,14 +61,11 @@ class RewardsRecyclerviewFragment : TaskRecyclerViewFragment() { binding?.recyclerView?.itemAnimator = SafeDefaultItemAnimator() if (showCustomRewards) { - compositeSubscription.add( - inventoryRepository.getInAppRewards().subscribe( - { - (recyclerAdapter as? RewardsRecyclerViewAdapter)?.updateItemRewards(it) - }, - ExceptionHandler.rx() - ) - ) + lifecycleScope.launchCatching { + inventoryRepository.getInAppRewards().collect { + (recyclerAdapter as? RewardsRecyclerViewAdapter)?.updateItemRewards(it) + } + } } (recyclerAdapter as? RewardsRecyclerViewAdapter)?.purchaseCardEvents?.subscribe( 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 7184362d0..f964224de 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 @@ -29,6 +29,7 @@ import com.habitrpg.android.habitica.helpers.HapticFeedbackManager import com.habitrpg.android.habitica.helpers.MainNavigationController import com.habitrpg.android.habitica.helpers.NotificationsManager import com.habitrpg.android.habitica.helpers.SoundManager +import com.habitrpg.android.habitica.helpers.launchCatching import com.habitrpg.android.habitica.models.tasks.ChecklistItem import com.habitrpg.android.habitica.models.tasks.Task import com.habitrpg.android.habitica.ui.activities.MainActivity @@ -50,46 +51,56 @@ import com.habitrpg.common.habitica.helpers.EmptyItem import com.habitrpg.shared.habitica.models.responses.TaskDirection import com.habitrpg.shared.habitica.models.responses.TaskScoringResult import com.habitrpg.shared.habitica.models.tasks.TaskType -import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers import io.reactivex.rxjava3.disposables.CompositeDisposable import kotlinx.coroutines.Job import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.distinctUntilChangedBy +import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.takeWhile import kotlinx.coroutines.launch import java.util.Date -import java.util.concurrent.TimeUnit import javax.inject.Inject -open class TaskRecyclerViewFragment : BaseFragment(), androidx.swiperefreshlayout.widget.SwipeRefreshLayout.OnRefreshListener { +open class TaskRecyclerViewFragment : BaseFragment(), + androidx.swiperefreshlayout.widget.SwipeRefreshLayout.OnRefreshListener { private var taskFlowJob: Job? = null - val viewModel: TasksViewModel by viewModels({requireParentFragment()}) + val viewModel: TasksViewModel by viewModels({ requireParentFragment() }) internal var canEditTasks: Boolean = true internal var canScoreTaks: Boolean = true override var binding: FragmentRefreshRecyclerviewBinding? = null - override fun createBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentRefreshRecyclerviewBinding { + override fun createBinding( + inflater: LayoutInflater, + container: ViewGroup? + ): FragmentRefreshRecyclerviewBinding { return FragmentRefreshRecyclerviewBinding.inflate(inflater, container, false) } private var recyclerSubscription: CompositeDisposable = CompositeDisposable() var recyclerAdapter: TaskRecyclerViewAdapter? = null var itemAnimator = SafeDefaultItemAnimator() + @Inject lateinit var userRepository: UserRepository + @Inject lateinit var inventoryRepository: InventoryRepository + @Inject lateinit var taskRepository: TaskRepository + @Inject lateinit var soundManager: SoundManager + @Inject lateinit var configManager: AppConfigManager + @Inject lateinit var sharedPreferences: SharedPreferences + @Inject lateinit var notificationsManager: NotificationsManager @@ -114,7 +125,11 @@ open class TaskRecyclerViewFragment : BaseFragment HabitsRecyclerViewAdapter(R.layout.habit_item_card, viewModel) TaskType.DAILY -> DailiesRecyclerViewHolder(R.layout.daily_item_card, viewModel) TaskType.TODO -> TodosRecyclerViewAdapter(R.layout.todo_item_card, viewModel) - TaskType.REWARD -> RewardsRecyclerViewAdapter(null, R.layout.reward_item_card, viewModel) + TaskType.REWARD -> RewardsRecyclerViewAdapter( + null, + R.layout.reward_item_card, + viewModel + ) else -> null } @@ -129,7 +144,9 @@ open class TaskRecyclerViewFragment : BaseFragment notificationsManager.dismissTaskNotification(it1, it.first) } - }?.subscribeWithErrorHandler { scoreTask(it.first, it.second) }?.let { recyclerSubscription.add(it) } + }?.subscribeWithErrorHandler { scoreTask(it.first, it.second) } + ?.let { recyclerSubscription.add(it) } recyclerAdapter?.checklistItemScoreEvents?.subscribeWithErrorHandler { scoreChecklistItem(it.first, it.second) }?.let { recyclerSubscription.add(it) } - recyclerAdapter?.brokenTaskEvents?.subscribeWithErrorHandler { showBrokenChallengeDialog(it) }?.let { recyclerSubscription.add(it) } - recyclerAdapter?.adventureGuideOpenEvents?.subscribeWithErrorHandler { MainNavigationController.navigate(R.id.adventureGuideActivity) }?.let { recyclerSubscription.add(it) } + recyclerAdapter?.brokenTaskEvents?.subscribeWithErrorHandler { showBrokenChallengeDialog(it) } + ?.let { recyclerSubscription.add(it) } + recyclerAdapter?.adventureGuideOpenEvents?.subscribeWithErrorHandler { + MainNavigationController.navigate( + R.id.adventureGuideActivity + ) + }?.let { recyclerSubscription.add(it) } viewModel.ownerID.observe(viewLifecycleOwner) { canEditTasks = viewModel.isPersonalBoard @@ -172,7 +195,9 @@ open class TaskRecyclerViewFragment : BaseFragment HabiticaSnackbar.showSnackbar( - activity.snackbarContainer, null, getString(R.string.notification_purchase_reward), + activity.snackbarContainer, + null, + getString(R.string.notification_purchase_reward), BitmapDrawable(resources, HabiticaIconsHelper.imageOfGold()), ContextCompat.getColor(activity, R.color.yellow_10), "-$value", @@ -221,7 +246,10 @@ open class TaskRecyclerViewFragment : BaseFragment= (recyclerAdapter?.data?.size ?: 0)) { recyclerAdapter?.data?.get(newPosition - 1)?.position ?: newPosition } else { - (recyclerAdapter?.data?.get(newPosition + 1)?.position ?: newPosition) - 1 + (recyclerAdapter?.data?.get(newPosition + 1)?.position + ?: newPosition) - 1 } } //Factor in if adventure guide is shown. if (recyclerAdapter?.showAdventureGuide == true) { newPosition = newPosition - 1 } - compositeSubscription.add( + lifecycleScope.launchCatching { taskRepository.updateTaskPosition( taskType, validTaskId, newPosition ) - .delay(1, TimeUnit.SECONDS) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe( - { - }, - ExceptionHandler.rx() - ) - ) + } } } } @@ -332,7 +361,8 @@ open class TaskRecyclerViewFragment : BaseFragment - val taskCount = tasks.size - val dialog = HabiticaAlertDialog(it) - dialog.setTitle(R.string.broken_challenge) - dialog.setMessage(it.getString(R.string.broken_challenge_description, taskCount)) - dialog.addButton(it.getString(R.string.keep_x_tasks, taskCount), true) { _, _ -> - if (!task.isValid) return@addButton + lifecycleScope.launchCatching { + val tasks = taskRepository.getTasksForChallenge(task.challengeID).firstOrNull() + ?: return@launchCatching + val taskCount = tasks.size + val dialog = HabiticaAlertDialog(it) + dialog.setTitle(R.string.broken_challenge) + dialog.setMessage( + it.getString( + R.string.broken_challenge_description, + taskCount + ) + ) + dialog.addButton( + it.getString(R.string.keep_x_tasks, taskCount), + true + ) { _, _ -> + if (!task.isValid) return@addButton + lifecycleScope.launch { taskRepository.unlinkAllTasks(task.challengeID, "keep-all") - .subscribe({ - lifecycleScope.launch(ExceptionHandler.coroutine()) { - userRepository.retrieveUser(true, forced = true) - } - }, ExceptionHandler.rx()) + userRepository.retrieveUser(true, forced = true) } - dialog.addButton( - it.getString(R.string.delete_x_tasks, taskCount), - isPrimary = false, - isDestructive = true - ) { _, _ -> - if (!task.isValid) return@addButton + } + dialog.addButton( + it.getString(R.string.delete_x_tasks, taskCount), + isPrimary = false, + isDestructive = true + ) { _, _ -> + if (!task.isValid) return@addButton + lifecycleScope.launch { taskRepository.unlinkAllTasks(task.challengeID, "remove-all") - .subscribe({ - lifecycleScope.launch(ExceptionHandler.coroutine()) { - userRepository.retrieveUser(true, forced = true) - } - }, ExceptionHandler.rx()) - } - dialog.setExtraCloseButtonVisibility(View.VISIBLE) - dialog.show() - }, - ExceptionHandler.rx() - ) - } - } - - private fun setEmptyLabels() { - binding?.recyclerView?.emptyItem = if (viewModel.filterCount(taskType) > 0) { - when (this.taskType) { - TaskType.HABIT -> { - EmptyItem( - getString(R.string.empty_title_habits_filtered), - getString(R.string.empty_description_habits_filtered), - R.drawable.icon_habits - ) - } - TaskType.DAILY -> { - EmptyItem( - getString(R.string.empty_title_dailies_filtered), - getString(R.string.empty_description_dailies_filtered), - R.drawable.icon_dailies - ) - } - TaskType.TODO -> { - EmptyItem( - getString(R.string.empty_title_todos_filtered), - getString(R.string.empty_description_todos_filtered), - R.drawable.icon_todos - ) - } - TaskType.REWARD -> { - EmptyItem( - getString(R.string.empty_title_rewards_filtered), - null, - R.drawable.icon_rewards - ) - } - else -> EmptyItem("") - } - } else { - when (this.taskType) { - TaskType.HABIT -> { - EmptyItem( - getString(R.string.empty_title_habits), - getString(R.string.empty_description_habits), - R.drawable.icon_habits - ) - } - TaskType.DAILY -> { - EmptyItem( - getString(R.string.empty_title_dailies), - getString(R.string.empty_description_dailies), - R.drawable.icon_dailies - ) - } - TaskType.TODO -> { - EmptyItem( - getString(R.string.empty_title_todos), - getString(R.string.empty_description_todos), - R.drawable.icon_todos - ) - } - TaskType.REWARD -> { - EmptyItem( - getString(R.string.empty_title_rewards), - null, - R.drawable.icon_rewards - ) - } - else -> EmptyItem("") - } - } - } - - private fun scoreTask(task: Task, direction: TaskDirection) { - viewModel.scoreTask(task, direction) { result, value -> - handleTaskResult(result, value) - } - } - - override fun onSaveInstanceState(outState: Bundle) { - super.onSaveInstanceState(outState) - outState.putString(CLASS_TYPE_KEY, this.taskType.value) - } - - override val displayedClassName: String? - get() = this.taskType.value + super.displayedClassName - - override fun onRefresh() { - binding?.refreshLayout?.isRefreshing = true - viewModel.refreshData { - binding?.refreshLayout?.isRefreshing = false - } - } - - override fun onResume() { - super.onResume() - context?.let { recyclerAdapter?.taskDisplayMode = configManager.taskDisplayMode(it) } - setInnerAdapter() - } - - fun setActiveFilter(activeFilter: String) { - viewModel.setActiveFilter(taskType, activeFilter) - recyclerAdapter?.filter() - - setEmptyLabels() - - if (activeFilter == Task.FILTER_COMPLETED) { - compositeSubscription.add(taskRepository.retrieveCompletedTodos().subscribe({}, ExceptionHandler.rx())) - } - } - - private fun setPreferenceTaskFilters() { - (activity as? MainActivity)?.viewModel?.user?.observeOnce(this) { - if (it != null) { - when (taskType) { - TaskType.TODO -> viewModel.setActiveFilter( - TaskType.TODO, - Task.FILTER_ACTIVE - ) - TaskType.DAILY -> { - if (!viewModel.initialPreferenceFilterSet) { - viewModel.initialPreferenceFilterSet = true - if (it.isValid && it.preferences?.dailyDueDefaultView == true) { - viewModel.setActiveFilter(TaskType.DAILY, Task.FILTER_ACTIVE) - } - } - } - else -> {} - } - } - } - } - - private fun openTaskForm(task: Task) { - if (Date().time - (TasksFragment.lastTaskFormOpen?.time ?: 0) < 2000 || !task.isValid || !canEditTasks) { - return - } - - val bundle = Bundle() - bundle.putString(TaskFormActivity.TASK_TYPE_KEY, task.type?.value) - bundle.putString(TaskFormActivity.TASK_ID_KEY, task.id) - bundle.putDouble(TaskFormActivity.TASK_VALUE_KEY, task.value) - - if (task.canEdit(viewModel.userViewModel.userID)) { - MainNavigationController.navigate(R.id.taskFormActivity, bundle) - } else { - MainNavigationController.navigate(R.id.taskSummaryActivity, bundle) - } - TasksFragment.lastTaskFormOpen = Date() - } - - companion object { - private const val CLASS_TYPE_KEY = "CLASS_TYPE_KEY" - - fun newInstance(context: Context?, classType: TaskType): TaskRecyclerViewFragment { - val fragment = TaskRecyclerViewFragment() - fragment.taskType = classType - var tutorialTexts: List? = null - if (context != null) { - when (fragment.taskType) { - TaskType.HABIT -> { - fragment.tutorialStepIdentifier = "habits" - tutorialTexts = listOf(context.getString(R.string.tutorial_overview), context.getString(R.string.tutorial_habits_1), context.getString(R.string.tutorial_habits_2), context.getString(R.string.tutorial_habits_3), context.getString(R.string.tutorial_habits_4)) - } - TaskType.DAILY -> { - fragment.tutorialStepIdentifier = "dailies" - tutorialTexts = listOf(context.getString(R.string.tutorial_dailies_1), context.getString(R.string.tutorial_dailies_2)) - } - TaskType.TODO -> { - fragment.tutorialStepIdentifier = "todos" - tutorialTexts = listOf(context.getString(R.string.tutorial_todos_1), context.getString(R.string.tutorial_todos_2)) - } - TaskType.REWARD -> { - fragment.tutorialStepIdentifier = "rewards" - tutorialTexts = listOf(context.getString(R.string.tutorial_rewards_1), context.getString(R.string.tutorial_rewards_2)) + userRepository.retrieveUser(true, forced = true) } } - } - - if (tutorialTexts != null) { - fragment.tutorialTexts = ArrayList(tutorialTexts) - } - fragment.tutorialCanBeDeferred = false - - return fragment + dialog.setExtraCloseButtonVisibility(View.VISIBLE) + dialog.show() } } } + +private fun setEmptyLabels() { + binding?.recyclerView?.emptyItem = if (viewModel.filterCount(taskType) > 0) { + when (this.taskType) { + TaskType.HABIT -> { + EmptyItem( + getString(R.string.empty_title_habits_filtered), + getString(R.string.empty_description_habits_filtered), + R.drawable.icon_habits + ) + } + TaskType.DAILY -> { + EmptyItem( + getString(R.string.empty_title_dailies_filtered), + getString(R.string.empty_description_dailies_filtered), + R.drawable.icon_dailies + ) + } + TaskType.TODO -> { + EmptyItem( + getString(R.string.empty_title_todos_filtered), + getString(R.string.empty_description_todos_filtered), + R.drawable.icon_todos + ) + } + TaskType.REWARD -> { + EmptyItem( + getString(R.string.empty_title_rewards_filtered), + null, + R.drawable.icon_rewards + ) + } + else -> EmptyItem("") + } + } else { + when (this.taskType) { + TaskType.HABIT -> { + EmptyItem( + getString(R.string.empty_title_habits), + getString(R.string.empty_description_habits), + R.drawable.icon_habits + ) + } + TaskType.DAILY -> { + EmptyItem( + getString(R.string.empty_title_dailies), + getString(R.string.empty_description_dailies), + R.drawable.icon_dailies + ) + } + TaskType.TODO -> { + EmptyItem( + getString(R.string.empty_title_todos), + getString(R.string.empty_description_todos), + R.drawable.icon_todos + ) + } + TaskType.REWARD -> { + EmptyItem( + getString(R.string.empty_title_rewards), + null, + R.drawable.icon_rewards + ) + } + else -> EmptyItem("") + } + } +} + +private fun scoreTask(task: Task, direction: TaskDirection) { + viewModel.scoreTask(task, direction) { result, value -> + handleTaskResult(result, value) + } +} + +override fun onSaveInstanceState(outState: Bundle) { + super.onSaveInstanceState(outState) + outState.putString(CLASS_TYPE_KEY, this.taskType.value) +} + +override val displayedClassName: String? + get() = this.taskType.value + super.displayedClassName + +override fun onRefresh() { + binding?.refreshLayout?.isRefreshing = true + viewModel.refreshData { + binding?.refreshLayout?.isRefreshing = false + } +} + +override fun onResume() { + super.onResume() + context?.let { recyclerAdapter?.taskDisplayMode = configManager.taskDisplayMode(it) } + setInnerAdapter() +} + +fun setActiveFilter(activeFilter: String) { + viewModel.setActiveFilter(taskType, activeFilter) + recyclerAdapter?.filter() + + setEmptyLabels() + + if (activeFilter == Task.FILTER_COMPLETED) { + lifecycleScope.launchCatching { + taskRepository.retrieveCompletedTodos() + } + } +} + +private fun setPreferenceTaskFilters() { + (activity as? MainActivity)?.viewModel?.user?.observeOnce(this) { + if (it != null) { + when (taskType) { + TaskType.TODO -> viewModel.setActiveFilter( + TaskType.TODO, + Task.FILTER_ACTIVE + ) + TaskType.DAILY -> { + if (!viewModel.initialPreferenceFilterSet) { + viewModel.initialPreferenceFilterSet = true + if (it.isValid && it.preferences?.dailyDueDefaultView == true) { + viewModel.setActiveFilter(TaskType.DAILY, Task.FILTER_ACTIVE) + } + } + } + else -> {} + } + } + } +} + +private fun openTaskForm(task: Task) { + if (Date().time - (TasksFragment.lastTaskFormOpen?.time + ?: 0) < 2000 || !task.isValid || !canEditTasks + ) { + return + } + + val bundle = Bundle() + bundle.putString(TaskFormActivity.TASK_TYPE_KEY, task.type?.value) + bundle.putString(TaskFormActivity.TASK_ID_KEY, task.id) + bundle.putDouble(TaskFormActivity.TASK_VALUE_KEY, task.value) + + if (task.canEdit(viewModel.userViewModel.userID)) { + MainNavigationController.navigate(R.id.taskFormActivity, bundle) + } else { + MainNavigationController.navigate(R.id.taskSummaryActivity, bundle) + } + TasksFragment.lastTaskFormOpen = Date() +} + +companion object { + private const val CLASS_TYPE_KEY = "CLASS_TYPE_KEY" + + fun newInstance(context: Context?, classType: TaskType): TaskRecyclerViewFragment { + val fragment = TaskRecyclerViewFragment() + fragment.taskType = classType + var tutorialTexts: List? = null + if (context != null) { + when (fragment.taskType) { + TaskType.HABIT -> { + fragment.tutorialStepIdentifier = "habits" + tutorialTexts = listOf( + context.getString(R.string.tutorial_overview), + context.getString(R.string.tutorial_habits_1), + context.getString(R.string.tutorial_habits_2), + context.getString(R.string.tutorial_habits_3), + context.getString(R.string.tutorial_habits_4) + ) + } + TaskType.DAILY -> { + fragment.tutorialStepIdentifier = "dailies" + tutorialTexts = listOf( + context.getString(R.string.tutorial_dailies_1), + context.getString(R.string.tutorial_dailies_2) + ) + } + TaskType.TODO -> { + fragment.tutorialStepIdentifier = "todos" + tutorialTexts = listOf( + context.getString(R.string.tutorial_todos_1), + context.getString(R.string.tutorial_todos_2) + ) + } + TaskType.REWARD -> { + fragment.tutorialStepIdentifier = "rewards" + tutorialTexts = listOf( + context.getString(R.string.tutorial_rewards_1), + context.getString(R.string.tutorial_rewards_2) + ) + } + } + } + + if (tutorialTexts != null) { + fragment.tutorialTexts = ArrayList(tutorialTexts) + } + fragment.tutorialCanBeDeferred = false + + return fragment + } +} +} diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/tasks/TasksFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/tasks/TasksFragment.kt index 51ef3782b..fc188fbcc 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/tasks/TasksFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/tasks/TasksFragment.kt @@ -17,6 +17,7 @@ import androidx.core.content.ContextCompat import androidx.core.content.edit import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels +import androidx.lifecycle.lifecycleScope import androidx.viewpager2.adapter.FragmentStateAdapter import androidx.viewpager2.widget.ViewPager2 import com.habitrpg.android.habitica.HabiticaBaseApplication @@ -25,7 +26,7 @@ import com.habitrpg.android.habitica.components.UserComponent import com.habitrpg.android.habitica.databinding.FragmentViewpagerBinding import com.habitrpg.android.habitica.extensions.setTintWith import com.habitrpg.android.habitica.helpers.AmplitudeManager -import com.habitrpg.android.habitica.helpers.ExceptionHandler +import com.habitrpg.android.habitica.helpers.launchCatching import com.habitrpg.android.habitica.ui.activities.TaskFormActivity import com.habitrpg.android.habitica.ui.fragments.BaseMainFragment import com.habitrpg.android.habitica.ui.viewmodels.TasksViewModel @@ -257,9 +258,9 @@ class TasksFragment : BaseMainFragment(), SearchView.O if (bottomNavigation == null) { return } - compositeSubscription.add( - tutorialRepository.getTutorialSteps(listOf("habits", "dailies", "todos", "rewards")).subscribe( - { tutorialSteps -> + lifecycleScope.launchCatching { + tutorialRepository.getTutorialSteps(listOf("habits", "dailies", "todos", "rewards")) + .collect { tutorialSteps -> val activeTutorialFragments = ArrayList() for (step in tutorialSteps) { var id = -1 @@ -291,7 +292,8 @@ class TasksFragment : BaseMainFragment(), SearchView.O } } if (activeTutorialFragments.size == 1) { - val fragment = viewFragmentsDictionary?.get(indexForTaskType(activeTutorialFragments[0])) + val fragment = + viewFragmentsDictionary?.get(indexForTaskType(activeTutorialFragments[0])) if (fragment?.tutorialTexts != null && context != null) { val finalText = context?.getString(R.string.tutorial_tasks_complete) if (!fragment.tutorialTexts.contains(finalText) && finalText != null) { @@ -299,10 +301,8 @@ class TasksFragment : BaseMainFragment(), SearchView.O } } } - }, - ExceptionHandler.rx() - ) - ) + } + } } // endregion diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/helpers/AutocompleteAdapter.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/helpers/AutocompleteAdapter.kt index d624d7787..0812b69c6 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/helpers/AutocompleteAdapter.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/helpers/AutocompleteAdapter.kt @@ -43,11 +43,11 @@ class AutocompleteAdapter( lastAutocomplete = Date().time userResults = arrayListOf() isAutocompletingUsers = true - socialRepository.findUsernames(constraint.toString().drop(1), autocompleteContext, groupID).blockingSubscribe { + /*socialRepository.findUsernames(constraint.toString().drop(1), autocompleteContext, groupID).blockingSubscribe { userResults = it filterResults.values = userResults filterResults.count = userResults.size - } + }*/ } else { filterResults.values = userResults filterResults.count = userResults.size diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/MountViewHolder.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/MountViewHolder.kt index 77cb28d8b..0bc6bae05 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/MountViewHolder.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/MountViewHolder.kt @@ -9,12 +9,11 @@ import com.habitrpg.android.habitica.R import com.habitrpg.android.habitica.databinding.MountOverviewItemBinding import com.habitrpg.android.habitica.extensions.inflate import com.habitrpg.android.habitica.models.inventory.Mount -import com.habitrpg.common.habitica.extensions.DataBindingUtils import com.habitrpg.android.habitica.ui.menu.BottomSheetMenu import com.habitrpg.android.habitica.ui.menu.BottomSheetMenuItem -import io.reactivex.rxjava3.subjects.PublishSubject +import com.habitrpg.common.habitica.extensions.DataBindingUtils -class MountViewHolder(parent: ViewGroup, private val equipEvents: PublishSubject) : androidx.recyclerview.widget.RecyclerView.ViewHolder(parent.inflate(R.layout.mount_overview_item)), View.OnClickListener { +class MountViewHolder(parent: ViewGroup, private val onEquip: ((String) -> Unit)?) : androidx.recyclerview.widget.RecyclerView.ViewHolder(parent.inflate(R.layout.mount_overview_item)), View.OnClickListener { private var binding: MountOverviewItemBinding = MountOverviewItemBinding.bind(itemView) private var owned: Boolean = false var animal: Mount? = null @@ -60,7 +59,7 @@ class MountViewHolder(parent: ViewGroup, private val equipEvents: PublishSubject val labelId = if (hasCurrentMount) R.string.unequip else R.string.equip menu.addMenuItem(BottomSheetMenuItem(resources.getString(labelId))) menu.setSelectionRunnable { - animal?.let { equipEvents.onNext(it.key ?: "") } + animal?.let { onEquip?.invoke(it.key ?: "") } } menu.show() } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/PetViewHolder.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/PetViewHolder.kt index f17959cfe..be791b6f7 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/PetViewHolder.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/PetViewHolder.kt @@ -13,16 +13,15 @@ import com.habitrpg.android.habitica.models.inventory.Egg import com.habitrpg.android.habitica.models.inventory.Food import com.habitrpg.android.habitica.models.inventory.HatchingPotion import com.habitrpg.android.habitica.models.inventory.Pet -import com.habitrpg.common.habitica.extensions.DataBindingUtils import com.habitrpg.android.habitica.ui.menu.BottomSheetMenu import com.habitrpg.android.habitica.ui.menu.BottomSheetMenuItem import com.habitrpg.android.habitica.ui.views.dialogs.PetSuggestHatchDialog -import io.reactivex.rxjava3.subjects.PublishSubject +import com.habitrpg.common.habitica.extensions.DataBindingUtils class PetViewHolder( parent: ViewGroup, - private val equipEvents: PublishSubject, - private val feedEvents: PublishSubject>, + private val onEquip: ((String) -> Unit)?, + private val onFeed: ((Pet, Food?) -> Unit)?, private val ingredientsReceiver: ((Animal, ((Pair) -> Unit)) -> Unit)? ) : androidx.recyclerview.widget.RecyclerView.ViewHolder(parent.inflate(R.layout.pet_detail_item)), View.OnClickListener { @@ -130,16 +129,16 @@ class PetViewHolder( when (index) { 0 -> { animal?.let { - equipEvents.onNext(it.key ?: "") + onEquip?.invoke(it.key ?: "") } } 1 -> { - feedEvents.onNext(Pair(pet, null)) + onFeed?.invoke(pet, null) } 2 -> { val saddle = Food() saddle.key = "Saddle" - feedEvents.onNext(Pair(pet, saddle)) + onFeed?.invoke(pet, saddle) } } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/AuthenticationViewModel.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/AuthenticationViewModel.kt index 8c5b6958c..dce3fc33d 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/AuthenticationViewModel.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/AuthenticationViewModel.kt @@ -17,16 +17,15 @@ import com.google.android.gms.common.ConnectionResult import com.google.android.gms.common.GoogleApiAvailability import com.google.android.gms.common.GooglePlayServicesUtil import com.google.android.gms.common.Scopes -import com.google.android.gms.common.UserRecoverableException import com.habitrpg.android.habitica.BuildConfig import com.habitrpg.android.habitica.HabiticaBaseApplication import com.habitrpg.android.habitica.R import com.habitrpg.android.habitica.data.ApiClient import com.habitrpg.android.habitica.data.UserRepository import com.habitrpg.android.habitica.extensions.addCloseButton -import com.habitrpg.android.habitica.helpers.ExceptionHandler import com.habitrpg.android.habitica.helpers.SignInWithAppleResult import com.habitrpg.android.habitica.helpers.SignInWithAppleService +import com.habitrpg.android.habitica.helpers.launchCatching import com.habitrpg.android.habitica.models.user.User import com.habitrpg.android.habitica.proxy.AnalyticsManager import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog @@ -34,13 +33,8 @@ import com.habitrpg.common.habitica.api.HostConfig import com.habitrpg.common.habitica.helpers.KeyHelper import com.habitrpg.common.habitica.models.auth.UserAuthResponse import com.willowtreeapps.signinwithapplebutton.SignInWithAppleConfiguration -import io.reactivex.rxjava3.core.Flowable import io.reactivex.rxjava3.disposables.CompositeDisposable -import io.reactivex.rxjava3.exceptions.Exceptions -import io.reactivex.rxjava3.schedulers.Schedulers import kotlinx.coroutines.MainScope -import kotlinx.coroutines.launch -import java.io.IOException import javax.inject.Inject class AuthenticationViewModel() { @@ -120,46 +114,25 @@ class AuthenticationViewModel() { val scopesString = Scopes.PROFILE + " " + Scopes.EMAIL val scopes = "oauth2:$scopesString" var newUser = false - compositeSubscription.add( - Flowable.defer { - try { - @Suppress("Deprecation") - return@defer Flowable.just(GoogleAuthUtil.getToken(activity, googleEmail ?: "", scopes)) - } catch (e: IOException) { - throw Exceptions.propagate(e) - } catch (e: GoogleAuthException) { - throw Exceptions.propagate(e) - } catch (e: UserRecoverableException) { - return@defer Flowable.empty() + MainScope().launchCatching({ throwable -> + if (recoverFromPlayServicesErrorResult == null) return@launchCatching + throwable.cause?.let { + if (GoogleAuthException::class.java.isAssignableFrom(it.javaClass)) { + handleGoogleAuthException( + throwable.cause as GoogleAuthException, + activity, + recoverFromPlayServicesErrorResult + ) } } - .subscribeOn(Schedulers.io()) - .flatMap { token -> apiClient.connectSocial("google", googleEmail ?: "", token) } - .doOnNext { - newUser = it.newUser - handleAuthResponse(it) - } - .subscribe( - { - MainScope().launch(ExceptionHandler.coroutine()) { - val user = userRepository.retrieveUser(true, true) ?: return@launch - onSuccess(user, newUser) - } - }, - { throwable -> - if (recoverFromPlayServicesErrorResult == null) return@subscribe - throwable.cause?.let { - if (GoogleAuthException::class.java.isAssignableFrom(it.javaClass)) { - handleGoogleAuthException( - throwable.cause as GoogleAuthException, - activity, - recoverFromPlayServicesErrorResult - ) - } - } - } - ) - ) + }) { + val token = GoogleAuthUtil.getToken(activity, googleEmail ?: "", scopes) + val response = apiClient.connectSocial("google", googleEmail ?: "", token) ?: return@launchCatching + newUser = response.newUser + handleAuthResponse(response) + val user = userRepository.retrieveUser(true, true) ?: return@launchCatching + onSuccess(user, newUser) + } } private fun handleGoogleAuthException( diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/BaseViewModel.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/BaseViewModel.kt index e4656357d..f54e5b2dc 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/BaseViewModel.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/BaseViewModel.kt @@ -8,7 +8,6 @@ import com.habitrpg.android.habitica.components.UserComponent import com.habitrpg.android.habitica.data.UserRepository import com.habitrpg.android.habitica.helpers.ExceptionHandler import com.habitrpg.android.habitica.models.user.User -import io.reactivex.rxjava3.disposables.CompositeDisposable import kotlinx.coroutines.launch import javax.inject.Inject @@ -33,12 +32,9 @@ abstract class BaseViewModel(initializeComponent: Boolean = true) : ViewModel() override fun onCleared() { userRepository.close() - disposable.clear() super.onCleared() } - internal val disposable = CompositeDisposable() - fun updateUser(path: String, value: Any) { viewModelScope.launch(ExceptionHandler.coroutine()) { userRepository.updateUser(path, value) 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 0b07975b4..06f351c1b 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 @@ -11,6 +11,7 @@ import com.habitrpg.android.habitica.data.SocialRepository 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.launchCatching import com.habitrpg.android.habitica.models.members.Member import com.habitrpg.android.habitica.models.social.Challenge import com.habitrpg.android.habitica.models.social.ChatMessage @@ -20,6 +21,7 @@ import io.realm.kotlin.toFlow import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.filterNotNull +import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch @@ -37,8 +39,10 @@ open class GroupViewModel(initializeComponent: Boolean) : BaseViewModel(initiali @Inject lateinit var challengeRepository: ChallengeRepository + @Inject lateinit var socialRepository: SocialRepository + @Inject lateinit var notificationsManager: NotificationsManager @@ -52,13 +56,11 @@ open class GroupViewModel(initializeComponent: Boolean) : BaseViewModel(initiali .flatMapLatest { socialRepository.getGroup(it) } private val group = groupFlow.asLiveData() - private val leaderFlow = groupFlow.map { it?.leaderID } .filterNotNull() .flatMapLatest { socialRepository.retrieveMember(it).toFlow() } private val leader = leaderFlow.asLiveData() - private val isMemberFlow = groupIDFlow .filterNotNull() .flatMapLatest { socialRepository.getGroupMembership(it) } @@ -87,21 +89,14 @@ open class GroupViewModel(initializeComponent: Boolean) : BaseViewModel(initiali if (groupID == groupIDState.value) return groupIDState.value = groupID - disposable.add( - notificationsManager.getNotifications().firstElement().map { - it.filter { notification -> + viewModelScope.launchCatching { + val notifications = + notificationsManager.getNotifications().firstOrNull()?.filter { notification -> val data = notification.data as? NewChatMessageData data?.group?.id == groupID - } - } - .filter { it.isNotEmpty() } - .flatMapPublisher { userRepository.readNotification(it.first().id) } - .subscribe( - { - }, - ExceptionHandler.rx() - ) - ) + } ?: return@launchCatching + notifications.forEach { userRepository.readNotification(it.id) } + } } val groupID: String? @@ -136,10 +131,9 @@ open class GroupViewModel(initializeComponent: Boolean) : BaseViewModel(initiali } fun inviteToGroup(inviteData: HashMap) { - disposable.add( + viewModelScope.launchCatching { socialRepository.inviteToGroup(group.value?.id ?: "", inviteData) - .subscribe({ }, ExceptionHandler.rx()) - ) + } } fun updateOrCreateGroup(bundle: Bundle?) { @@ -154,12 +148,12 @@ open class GroupViewModel(initializeComponent: Boolean) : BaseViewModel(initiali bundle?.getBoolean("leaderCreateChallenge") ) } else { - socialRepository.updateGroup( - group.value, bundle?.getString("name"), - bundle?.getString("description"), - bundle?.getString("leader"), - bundle?.getBoolean("leaderCreateChallenge") - ) + socialRepository.updateGroup( + group.value, bundle?.getString("name"), + bundle?.getString("description"), + bundle?.getString("leader"), + bundle?.getBoolean("leaderCreateChallenge") + ) } } } @@ -170,8 +164,10 @@ open class GroupViewModel(initializeComponent: Boolean) : BaseViewModel(initiali function: (() -> Unit)? = null ) { if (!keepChallenges) { - for (challenge in groupChallenges) { - challengeRepository.leaveChallenge(challenge, "remove-all").subscribe({}, ExceptionHandler.rx()) + viewModelScope.launchCatching { + for (challenge in groupChallenges) { + challengeRepository.leaveChallenge(challenge, "remove-all") + } } } viewModelScope.launch(ExceptionHandler.coroutine()) { @@ -181,7 +177,7 @@ open class GroupViewModel(initializeComponent: Boolean) : BaseViewModel(initiali } fun joinGroup(id: String? = null, function: (() -> Unit)? = null) { - viewModelScope.launch(ExceptionHandler.coroutine()) { + viewModelScope.launchCatching { socialRepository.joinGroup(id ?: groupID) function?.invoke() } @@ -189,14 +185,18 @@ open class GroupViewModel(initializeComponent: Boolean) : BaseViewModel(initiali fun rejectGroupInvite(id: String? = null) { groupID?.let { - disposable.add(socialRepository.rejectGroupInvite(id ?: it).subscribe({ }, ExceptionHandler.rx())) + viewModelScope.launchCatching { + socialRepository.rejectGroupInvite(id ?: it) + } } } fun markMessagesSeen() { groupID?.let { if (groupViewType != GroupViewType.TAVERN && it.isNotEmpty() && gotNewMessages) { - socialRepository.markMessagesSeen(it) + viewModelScope.launchCatching { + socialRepository.markMessagesSeen(it) + } } } } @@ -204,15 +204,14 @@ open class GroupViewModel(initializeComponent: Boolean) : BaseViewModel(initiali fun likeMessage(message: ChatMessage) { val index = _chatMessages.value?.indexOf(message) if (index == null || index < 0) return - disposable.add( - socialRepository.likeMessage(message).subscribe( - { - val list = _chatMessages.value?.toMutableList() - list?.set(index, it) - _chatMessages.postValue(list) - }, ExceptionHandler.rx() - ) - ) + viewModelScope.launchCatching { + val message = socialRepository.likeMessage(message) + val list = _chatMessages.value?.toMutableList() + if (message != null) { + list?.set(index, message) + } + _chatMessages.postValue(list) + } } fun deleteMessage(chatMessage: ChatMessage) { @@ -220,30 +219,29 @@ open class GroupViewModel(initializeComponent: Boolean) : BaseViewModel(initiali val list = _chatMessages.value?.toMutableList() list?.remove(chatMessage) _chatMessages.postValue(list) - disposable.add( - socialRepository.deleteMessage(chatMessage).subscribe({ - }, { - list?.add(oldIndex, chatMessage) - _chatMessages.postValue(list) - ExceptionHandler.reportError(it) - }) - ) + viewModelScope.launch(ExceptionHandler.coroutine { + list?.add(oldIndex, chatMessage) + _chatMessages.postValue(list) + ExceptionHandler.reportError(it) + }) { + socialRepository.deleteMessage(chatMessage) + } } fun postGroupChat(chatText: String, onComplete: () -> Unit, onError: () -> Unit) { groupID?.let { groupID -> - socialRepository.postGroupChat(groupID, chatText).subscribe( - { - val list = _chatMessages.value?.toMutableList() - list?.add(0, it.message) - _chatMessages.postValue(list) - onComplete() - }, - { error -> - ExceptionHandler.reportError(error) - onError() + viewModelScope.launch(ExceptionHandler.coroutine { + ExceptionHandler.reportError(it) + onError() + }) { + val response = socialRepository.postGroupChat(groupID, chatText) + val list = _chatMessages.value?.toMutableList() + if (response != null) { + list?.add(0, response.message) } - ) + _chatMessages.postValue(list) + onComplete() + } } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/MainActivityViewModel.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/MainActivityViewModel.kt index d1110e573..9c6c215d4 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/MainActivityViewModel.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/MainActivityViewModel.kt @@ -12,6 +12,7 @@ import com.habitrpg.android.habitica.data.TaskRepository import com.habitrpg.android.habitica.helpers.AmplitudeManager import com.habitrpg.android.habitica.helpers.ExceptionHandler import com.habitrpg.android.habitica.helpers.TaskAlarmManager +import com.habitrpg.android.habitica.helpers.launchCatching import com.habitrpg.android.habitica.helpers.notifications.PushNotificationManager import com.habitrpg.android.habitica.models.TutorialStep import com.habitrpg.android.habitica.models.inventory.Egg @@ -19,11 +20,10 @@ import com.habitrpg.android.habitica.proxy.AnalyticsManager import com.habitrpg.android.habitica.ui.TutorialView import com.habitrpg.common.habitica.api.HostConfig import com.habitrpg.shared.habitica.models.responses.MaintenanceResponse -import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers -import io.reactivex.rxjava3.schedulers.Schedulers import io.realm.kotlin.isValid +import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.launch -import java.util.* +import java.util.Date import javax.inject.Inject class MainActivityViewModel : BaseViewModel(), TutorialView.OnTutorialReaction { @@ -101,10 +101,9 @@ class MainActivityViewModel : BaseViewModel(), TutorialView.OnTutorialReaction { } contentRepository.retrieveContent() } - disposable.add( + viewModelScope.launchCatching { userRepository.retrieveTeamPlans() - .subscribe({ }, ExceptionHandler.rx()) - ) + } } } @@ -131,20 +130,13 @@ class MainActivityViewModel : BaseViewModel(), TutorialView.OnTutorialReaction { } fun ifNeedsMaintenance(onResult: ((MaintenanceResponse) -> Unit)) { - disposable.add( - this.maintenanceService.maintenanceStatus - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe( - { maintenanceResponse -> - if (maintenanceResponse.activeMaintenance == null) { - return@subscribe - } - onResult(maintenanceResponse) - }, - ExceptionHandler.rx() - ) - ) + viewModelScope.launchCatching { + val maintenanceResponse = maintenanceService.getMaintenanceStatus() + if (maintenanceResponse?.activeMaintenance == null) { + return@launchCatching + } + onResult(maintenanceResponse) + } } fun getToolbarTitle( @@ -154,21 +146,17 @@ class MainActivityViewModel : BaseViewModel(), TutorialView.OnTutorialReaction { onSuccess: ((CharSequence?) -> Unit) ) { if (id == R.id.petDetailRecyclerFragment || id == R.id.mountDetailRecyclerFragment) { - disposable.add( - inventoryRepository.getItem("egg", eggType ?: "").firstElement().subscribe( - { - if (!it.isValid()) return@subscribe - onSuccess( - if (id == R.id.petDetailRecyclerFragment) { - (it as? Egg)?.text - } else { - (it as? Egg)?.mountText - } - ) - }, - ExceptionHandler.rx() + viewModelScope.launchCatching { + val item = inventoryRepository.getItem("egg", eggType ?: "").firstOrNull() + if (item?.isValid() != true) return@launchCatching + onSuccess( + if (id == R.id.petDetailRecyclerFragment) { + (item as? Egg)?.text + } else { + (item as? Egg)?.mountText + } ) - ) + } } else { onSuccess( if (id == R.id.promoInfoFragment) { diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/NotificationsViewModel.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/NotificationsViewModel.kt index 42f00777f..292a1465c 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/NotificationsViewModel.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/NotificationsViewModel.kt @@ -6,9 +6,10 @@ import androidx.lifecycle.viewModelScope 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.helpers.ExceptionHandler import com.habitrpg.android.habitica.helpers.MainNavigationController import com.habitrpg.android.habitica.helpers.NotificationsManager -import com.habitrpg.android.habitica.helpers.ExceptionHandler +import com.habitrpg.android.habitica.helpers.launchCatching import com.habitrpg.android.habitica.models.social.UserParty import com.habitrpg.android.habitica.models.user.User import com.habitrpg.common.habitica.models.Notification @@ -20,11 +21,11 @@ import com.habitrpg.common.habitica.models.notifications.NewStuffData import com.habitrpg.common.habitica.models.notifications.PartyInvitationData import com.habitrpg.common.habitica.models.notifications.PartyInvite import com.habitrpg.common.habitica.models.notifications.QuestInvitationData -import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers -import io.reactivex.rxjava3.core.BackpressureStrategy -import io.reactivex.rxjava3.core.Flowable -import io.reactivex.rxjava3.functions.BiFunction -import io.reactivex.rxjava3.subjects.BehaviorSubject +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch import javax.inject.Inject @@ -51,15 +52,13 @@ open class NotificationsViewModel : BaseViewModel() { private var party: UserParty? = null - private val customNotifications: BehaviorSubject> = BehaviorSubject.create() + private val customNotifications = MutableStateFlow>(emptyList()) override fun inject(component: UserComponent) { component.inject(this) } init { - customNotifications.onNext(emptyList()) - userViewModel.user.observeForever { if (it == null) return@observeForever party = it.party @@ -72,42 +71,35 @@ open class NotificationsViewModel : BaseViewModel() { notification.data = data notifications.add(notification) } - customNotifications.onNext(notifications) + customNotifications.value = notifications } } - fun getNotifications(): Flowable> { + fun getNotifications(): Flow> { val serverNotifications = notificationsManager.getNotifications() .map { filterSupportedTypes(it) } - return Flowable.combineLatest( - serverNotifications, - customNotifications.toFlowable(BackpressureStrategy.LATEST), - BiFunction { - serverNotificationsList, customNotificationsList -> - if (serverNotificationsList.firstOrNull { notification -> notification.type == Notification.Type.NEW_STUFF.type } != null) { - return@BiFunction serverNotificationsList + customNotificationsList.filter { notification -> notification.type != Notification.Type.NEW_STUFF.type } - } - return@BiFunction serverNotificationsList + customNotificationsList + return serverNotifications.combine(customNotifications) { serverNotificationsList, customNotificationsList -> + if (serverNotificationsList.firstOrNull { notification -> notification.type == Notification.Type.NEW_STUFF.type } != null) { + return@combine serverNotificationsList + customNotificationsList.filter { notification -> notification.type != Notification.Type.NEW_STUFF.type } } - ) - .map { it.sortedBy { notification -> notification.priority } } - .observeOn(AndroidSchedulers.mainThread()) + return@combine serverNotificationsList + customNotificationsList + }.map { it.sortedBy { notification -> notification.priority } } } - fun getNotificationCount(): Flowable { + fun getNotificationCount(): Flow { return getNotifications() .map { it.count() } .distinctUntilChanged() } - fun allNotificationsSeen(): Flowable { + fun allNotificationsSeen(): Flow { return getNotifications() .map { it.all { notification -> notification.seen != false } } .distinctUntilChanged() } - fun getHasPartyNotification(): Flowable { + fun getHasPartyNotification(): Flow { return getNotifications() .map { it.find { notification -> @@ -197,17 +189,14 @@ open class NotificationsViewModel : BaseViewModel() { fun dismissNotification(notification: Notification) { if (isCustomNotification(notification)) { if (isCustomNewStuffNotification(notification)) { - customNotifications.onNext( - customNotifications.value?.filterNot { it.id == notification.id } ?: listOf() - ) + customNotifications.value = customNotifications.value.filterNot { it.id == notification.id } } return } - disposable.add( + viewModelScope.launchCatching { userRepository.readNotification(notification.id) - .subscribe({}, ExceptionHandler.rx()) - ) + } } fun dismissAllNotifications(notifications: List) { @@ -230,10 +219,9 @@ open class NotificationsViewModel : BaseViewModel() { val notificationIds = HashMap>() notificationIds["notificationIds"] = dismissableIds - disposable.add( + viewModelScope.launchCatching { userRepository.readNotifications(notificationIds) - .subscribe({}, ExceptionHandler.rx()) - ) + } } fun markNotificationsAsSeen(notifications: List) { @@ -249,10 +237,9 @@ open class NotificationsViewModel : BaseViewModel() { val notificationIds = HashMap>() notificationIds["notificationIds"] = unseenIds - disposable.add( + viewModelScope.launchCatching { userRepository.seeNotifications(notificationIds) - .subscribe({}, ExceptionHandler.rx()) - ) + } } private fun findNotification(id: String): Notification? { @@ -367,43 +354,28 @@ open class NotificationsViewModel : BaseViewModel() { fun rejectGroupInvite(groupId: String?) { groupId?.let { - disposable.add( + viewModelScope.launchCatching { socialRepository.rejectGroupInvite(it) - .subscribe( - { - refreshUser() - }, - ExceptionHandler.rx() - ) - ) + refreshUser() + } } } private fun acceptQuestInvitation() { party?.id?.let { - disposable.add( + viewModelScope.launchCatching { socialRepository.acceptQuest(null, it) - .subscribe( - { - refreshUser() - }, - ExceptionHandler.rx() - ) - ) + refreshUser() + } } } private fun rejectQuestInvitation() { party?.id?.let { - disposable.add( + viewModelScope.launchCatching { socialRepository.rejectQuest(null, it) - .subscribe( - { - refreshUser() - }, - ExceptionHandler.rx() - ) - ) + refreshUser() + } } } 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 344def085..e00498adc 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 @@ -4,6 +4,7 @@ import androidx.lifecycle.asLiveData import androidx.lifecycle.viewModelScope import com.habitrpg.android.habitica.components.UserComponent import com.habitrpg.android.habitica.helpers.ExceptionHandler +import com.habitrpg.android.habitica.helpers.launchCatching import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.flatMapLatest @@ -39,33 +40,21 @@ class PartyViewModel(initializeComponent: Boolean) : GroupViewModel(initializeCo fun acceptQuest() { groupID?.let { groupID -> - disposable.add( + viewModelScope.launchCatching { socialRepository.acceptQuest(null, groupID) - .subscribe({ - viewModelScope.launch(ExceptionHandler.coroutine()) { - socialRepository.retrieveGroup(groupID) - userRepository.retrieveUser() - } - }, - ExceptionHandler.rx() - ) - ) + socialRepository.retrieveGroup(groupID) + userRepository.retrieveUser() + } } } fun rejectQuest() { groupID?.let { groupID -> - disposable.add( + viewModelScope.launchCatching { socialRepository.rejectQuest(null, groupID) - .subscribe({ - viewModelScope.launch(ExceptionHandler.coroutine()) { - socialRepository.retrieveGroup(groupID) - userRepository.retrieveUser() - } - }, - ExceptionHandler.rx() - ) - ) + socialRepository.retrieveGroup(groupID) + userRepository.retrieveUser() + } } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/StableViewModel.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/StableViewModel.kt index 588ce4585..6ed5036b6 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/StableViewModel.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/StableViewModel.kt @@ -12,6 +12,7 @@ import com.habitrpg.android.habitica.components.UserComponent import com.habitrpg.android.habitica.data.InventoryRepository import com.habitrpg.android.habitica.extensions.getTranslatedType import com.habitrpg.android.habitica.helpers.ExceptionHandler +import com.habitrpg.android.habitica.helpers.launchCatching import com.habitrpg.android.habitica.models.inventory.Animal import com.habitrpg.android.habitica.models.inventory.Egg import com.habitrpg.android.habitica.models.inventory.Mount @@ -78,7 +79,9 @@ class StableViewModel(private val application: Application?, private val itemTyp }.collect { _items.value = mapAnimals(animals, it) } - disposable.add(inventoryRepository.getOwnedItems(true).subscribe({ _ownedItems.value = it }, ExceptionHandler.rx())) + viewModelScope.launchCatching { + _ownedItems.value = inventoryRepository.getOwnedItems(true).firstOrNull() + } _mounts.value = if ("pets" == itemType) { inventoryRepository.getMounts().firstOrNull() ?: emptyList() } else { @@ -129,7 +132,7 @@ class StableViewModel(private val application: Application?, private val itemTyp val isOwned = when (itemType) { "pets" -> { val ownedPet = ownedAnimals[animal.key] as? OwnedPet - ownedPet?.trained ?: 0 > 0 + (ownedPet?.trained ?: 0) > 0 } "mounts" -> { val ownedMount = ownedAnimals[animal.key] as? OwnedMount diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/TasksViewModel.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/TasksViewModel.kt index abaf697de..2a63d6f42 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/TasksViewModel.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/TasksViewModel.kt @@ -14,6 +14,7 @@ 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.GroupPlanInfoProvider +import com.habitrpg.android.habitica.helpers.launchCatching import com.habitrpg.android.habitica.models.TeamPlan import com.habitrpg.android.habitica.models.tasks.Task import com.habitrpg.shared.habitica.models.responses.TaskDirection @@ -81,9 +82,9 @@ class TasksViewModel : BaseViewModel(), GroupPlanInfoProvider { } } } - compositeSubscription.add( - userRepository.retrieveTeamPlans().subscribe({}, ExceptionHandler.rx()) - ) + viewModelScope.launchCatching { + userRepository.retrieveTeamPlans() + } } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/inventory/equipment/EquipmentOverviewViewModel.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/inventory/equipment/EquipmentOverviewViewModel.kt index befd2219f..31133d63f 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/inventory/equipment/EquipmentOverviewViewModel.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/inventory/equipment/EquipmentOverviewViewModel.kt @@ -1,8 +1,9 @@ package com.habitrpg.android.habitica.ui.viewmodels.inventory.equipment +import androidx.lifecycle.viewModelScope import com.habitrpg.android.habitica.components.UserComponent import com.habitrpg.android.habitica.data.InventoryRepository -import com.habitrpg.android.habitica.helpers.ExceptionHandler +import com.habitrpg.android.habitica.helpers.launchCatching import com.habitrpg.android.habitica.models.inventory.Equipment import com.habitrpg.android.habitica.ui.viewmodels.BaseViewModel import javax.inject.Inject @@ -21,10 +22,10 @@ class EquipmentOverviewViewModel : BaseViewModel() { } fun getGear(key: String, onSuccess: (Equipment) -> Unit) { - disposable.add( - inventoryRepository.getEquipment(key).subscribe({ + viewModelScope.launchCatching { + inventoryRepository.getEquipment(key).collect { onSuccess(it) - }, ExceptionHandler.rx()) - ) + } + } } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/dialogs/PetSuggestHatchDialog.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/dialogs/PetSuggestHatchDialog.kt index 0cd43b1da..566af2cb9 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/dialogs/PetSuggestHatchDialog.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/dialogs/PetSuggestHatchDialog.kt @@ -11,7 +11,6 @@ import com.habitrpg.android.habitica.HabiticaBaseApplication import com.habitrpg.android.habitica.R import com.habitrpg.android.habitica.databinding.DialogHatchPetButtonBinding import com.habitrpg.android.habitica.databinding.DialogPetSuggestHatchBinding -import com.habitrpg.android.habitica.extensions.subscribeWithErrorHandler import com.habitrpg.android.habitica.helpers.ExceptionHandler import com.habitrpg.android.habitica.helpers.launchCatching import com.habitrpg.android.habitica.interactors.HatchPetUseCase @@ -176,12 +175,14 @@ class PetSuggestHatchDialog(context: Context) : HabiticaAlertDialog(context) { private fun hatchPet(potion: HatchingPotion, egg: Egg) { (getActivity() as? BaseActivity)?.let { - hatchPetUseCase.observable( - HatchPetUseCase.RequestValues( - potion, egg, - it + it.lifecycleScope.launchCatching { + hatchPetUseCase.callInteractor( + HatchPetUseCase.RequestValues( + potion, egg, + it + ) ) - ).subscribeWithErrorHandler {} + } } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/shops/PurchaseDialog.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/shops/PurchaseDialog.kt index b17cbb98d..fde907d89 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/shops/PurchaseDialog.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/shops/PurchaseDialog.kt @@ -46,6 +46,7 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.MainScope import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.launch import java.lang.Integer.max @@ -108,7 +109,10 @@ class PurchaseDialog(context: Context, component: UserComponent?, val item: Shop } shopItem.isTypeGear -> { contentView = PurchaseDialogGearContent(context) - inventoryRepository.getEquipment(shopItem.key).firstElement().subscribe({ contentView.setEquipment(it) }, ExceptionHandler.rx()) + lifecycleScope.launchCatching { + inventoryRepository.getEquipment(shopItem.key).firstOrNull() + ?.let { contentView.setEquipment(it) } + } checkGearClass() } "gems" == shopItem.purchaseType -> contentView = PurchaseDialogGemsContent(context) @@ -263,8 +267,9 @@ class PurchaseDialog(context: Context, component: UserComponent?, val item: Shop } shopItem = item - - compositeSubscription.add(userRepository.getUserFlowable().subscribe({ this.setUser(it) }, ExceptionHandler.rx())) + lifecycleScope.launchCatching { + userRepository.getUser().filterNotNull().collect { setUser(it) } + } } private fun setUser(user: User) { diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/stats/BulkAllocateStatsDialog.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/stats/BulkAllocateStatsDialog.kt index ab6c2836a..dbc0415fe 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/stats/BulkAllocateStatsDialog.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/stats/BulkAllocateStatsDialog.kt @@ -5,11 +5,13 @@ import android.content.Context import android.os.Bundle 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.UserRepository import com.habitrpg.android.habitica.databinding.DialogBulkAllocateBinding import com.habitrpg.android.habitica.helpers.ExceptionHandler +import com.habitrpg.android.habitica.helpers.launchCatching import com.habitrpg.common.habitica.extensions.getThemeColor import com.habitrpg.common.habitica.extensions.layoutInflater import io.reactivex.rxjava3.disposables.Disposable @@ -59,21 +61,15 @@ class BulkAllocateStatsDialog(context: Context, component: UserComponent?) : Ale private fun saveChanges() { getButton(BUTTON_POSITIVE).isEnabled = false - userRepository.bulkAllocatePoints( - binding.strengthSliderView.currentValue, - binding.intelligenceSliderView.currentValue, - binding.constitutionSliderView.currentValue, - binding.perceptionSliderView.currentValue - ) - .subscribe( - { - this.dismiss() - }, - { - ExceptionHandler.reportError(it) - this.dismiss() - } + lifecycleScope.launchCatching { + userRepository.bulkAllocatePoints( + binding.strengthSliderView.currentValue, + binding.intelligenceSliderView.currentValue, + binding.constitutionSliderView.currentValue, + binding.perceptionSliderView.currentValue ) + dismiss() + } } override fun onCreate(savedInstanceState: Bundle?) { diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/tasks/TaskFilterDialog.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/tasks/TaskFilterDialog.kt index eac11188b..eab805918 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/tasks/TaskFilterDialog.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/tasks/TaskFilterDialog.kt @@ -14,21 +14,20 @@ import androidx.appcompat.widget.AppCompatCheckBox import androidx.core.content.ContextCompat import androidx.core.widget.CompoundButtonCompat import androidx.core.widget.TextViewCompat +import androidx.lifecycle.lifecycleScope import com.habitrpg.android.habitica.R import com.habitrpg.android.habitica.components.UserComponent import com.habitrpg.android.habitica.data.TagRepository import com.habitrpg.android.habitica.databinding.DialogTaskFilterBinding import com.habitrpg.android.habitica.databinding.EditTagItemBinding import com.habitrpg.android.habitica.extensions.OnChangeTextWatcher -import com.habitrpg.android.habitica.helpers.ExceptionHandler +import com.habitrpg.android.habitica.helpers.launchCatching import com.habitrpg.android.habitica.models.Tag import com.habitrpg.android.habitica.models.tasks.Task import com.habitrpg.android.habitica.ui.viewmodels.TasksViewModel import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaBottomSheetDialog import com.habitrpg.common.habitica.extensions.getThemeColor import com.habitrpg.shared.habitica.models.tasks.TaskType -import io.reactivex.rxjava3.core.Observable -import io.reactivex.rxjava3.disposables.Disposable import java.util.UUID import javax.inject.Inject @@ -36,8 +35,6 @@ class TaskFilterDialog(context: Context, component: UserComponent?) : HabiticaBo lateinit var viewModel: TasksViewModel private val binding = DialogTaskFilterBinding.inflate(layoutInflater) - var tagDisposale: Disposable? = null - @Inject lateinit var repository: TagRepository @@ -99,14 +96,15 @@ class TaskFilterDialog(context: Context, component: UserComponent?) : HabiticaBo } override fun dismiss() { - tagDisposale?.dispose() super.dismiss() } override fun show() { - tagDisposale = viewModel.tagRepository.getTags().subscribe({ - setTags(it) - }, ExceptionHandler.rx()) + lifecycleScope.launchCatching { + viewModel.tagRepository.getTags().collect { + setTags(it) + } + } super.show() this.window?.clearFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM) } @@ -193,9 +191,12 @@ class TaskFilterDialog(context: Context, component: UserComponent?) : HabiticaBo createTagViews() binding.tagEditButton.setText(R.string.edit_tag_btn_edit) this.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN) - repository.updateTags(editedTags.values).toObservable().flatMap { tags -> Observable.fromIterable(tags) }.subscribe({ tag -> editedTags.remove(tag.id) }, ExceptionHandler.rx()) - repository.createTags(createdTags.values).toObservable().flatMap { tags -> Observable.fromIterable(tags) }.subscribe({ tag -> createdTags.remove(tag.id) }, ExceptionHandler.rx()) - repository.deleteTags(deletedTags).subscribe({ deletedTags.clear() }, ExceptionHandler.rx()) + lifecycleScope.launchCatching { + + repository.updateTags(editedTags.values).forEach { editedTags.remove(it.id) } + repository.createTags(createdTags.values).forEach { tag -> createdTags.remove(tag.id) } + repeat(repository.deleteTags(deletedTags).size) { deletedTags.clear() } + } } private fun createTagEditViews() { diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/yesterdailies/YesterdailyDialog.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/yesterdailies/YesterdailyDialog.kt index 155833d03..1f0bb6a6f 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/yesterdailies/YesterdailyDialog.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/yesterdailies/YesterdailyDialog.kt @@ -15,21 +15,24 @@ import com.habitrpg.android.habitica.data.TaskRepository import com.habitrpg.android.habitica.data.UserRepository import com.habitrpg.android.habitica.helpers.AmplitudeManager import com.habitrpg.android.habitica.helpers.ExceptionHandler +import com.habitrpg.android.habitica.helpers.launchCatching import com.habitrpg.android.habitica.models.tasks.ChecklistItem import com.habitrpg.android.habitica.models.tasks.Task import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog import com.habitrpg.common.habitica.extensions.dpToPx import com.habitrpg.common.habitica.extensions.isUsingNightModeResources import com.habitrpg.shared.habitica.models.tasks.TaskType -import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers -import io.reactivex.rxjava3.core.Observable import kotlinx.coroutines.MainScope +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.firstOrNull +import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch import java.lang.ref.WeakReference import java.util.Calendar import java.util.Date -import java.util.concurrent.TimeUnit import kotlin.math.abs +import kotlin.time.DurationUnit +import kotlin.time.toDuration class YesterdailyDialog private constructor( context: Context, @@ -220,77 +223,57 @@ class YesterdailyDialog private constructor( taskRepository: TaskRepository ) { if (userRepository != null && userId != null) { - Observable.just("") - .delay(500, TimeUnit.MILLISECONDS, AndroidSchedulers.mainThread()) - .filter { !userRepository.isClosed } - .flatMapMaybe { userRepository.getUserFlowable().firstElement() } - .filter { it.needsCron } - .flatMapMaybe { - val cal = Calendar.getInstance() - cal.add(Calendar.DATE, -1) - taskRepository.retrieveDailiesFromDate(cal.time).firstElement() + MainScope().launchCatching { + delay(500.toDuration(DurationUnit.MILLISECONDS)) + if (userRepository.isClosed) { + return@launchCatching } - .map { - it.tasks.values.filter { task -> - return@filter task.type == TaskType.DAILY && task.isDue == true && !task.completed && task.yesterDaily - } + val user = userRepository.getUser().firstOrNull() + if (user?.needsCron != true) { + return@launchCatching + } + val cal = Calendar.getInstance() + cal.add(Calendar.DATE, -1) + val tasks = taskRepository.retrieveDailiesFromDate(cal.time)?.tasks?.values?.filter { task -> + return@filter task.type == TaskType.DAILY && task.isDue == true && !task.completed && task.yesterDaily && !task.isGroupTask + } + if (displayedDialog?.get()?.isShowing == true) { + return@launchCatching } - .retry(1) - .throttleFirst(2, TimeUnit.SECONDS) - .filter { - if (displayedDialog?.get()?.isShowing == true) { - return@filter false - } - if (abs((lastCronRun?.time ?: 0) - Date().time) < 60 * 60 * 1000L) { - return@filter false - } - return@filter true + if (abs((lastCronRun?.time ?: 0) - Date().time) < 60 * 60 * 1000L) { + return@launchCatching } - .firstElement() - .map { - it.filter { !it.isGroupTask } - } - .zipWith( - taskRepository.getTasksFlowable(TaskType.DAILY, null, emptyArray()) - .firstElement() - .map { - val taskMap = mutableMapOf() - it.forEachIndexed { index, task -> taskMap[task.id ?: ""] = index } - taskMap - } - ) { yesterdayTasks, dailies -> - yesterdayTasks.sortedBy { dailies[it.id ?: ""] } - } - .subscribe( - { tasks -> - val additionalData = HashMap() - additionalData["task count"] = tasks.size - AmplitudeManager.sendEvent( - "show cron", - AmplitudeManager.EVENT_CATEGORY_BEHAVIOUR, - AmplitudeManager.EVENT_HITTYPE_EVENT, - additionalData - ) + val dailies = taskRepository.getTasks(TaskType.DAILY, null, emptyArray()).map { + val taskMap = mutableMapOf() + it.forEachIndexed { index, task -> taskMap[task.id ?: ""] = index } + taskMap + }.firstOrNull() + val sortedTasks = tasks?.sortedBy { dailies?.get(it.id ?: "") } - if (tasks.isNotEmpty()) { - displayedDialog = WeakReference( - showDialog( - activity, - userRepository, - taskRepository, - tasks - ) - ) - } else { - lastCronRun = Date() - MainScope().launch(ExceptionHandler.coroutine()) { - userRepository.runCron() - } - } - }, - ExceptionHandler.rx() + val additionalData = HashMap() + additionalData["task count"] = sortedTasks?.size ?: 0 + AmplitudeManager.sendEvent( + "show cron", + AmplitudeManager.EVENT_CATEGORY_BEHAVIOUR, + AmplitudeManager.EVENT_HITTYPE_EVENT, + additionalData ) + + if (sortedTasks?.isNotEmpty() == true) { + displayedDialog = WeakReference( + showDialog( + activity, + userRepository, + taskRepository, + sortedTasks + ) + ) + } else { + lastCronRun = Date() + userRepository.runCron() + } + } } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/widget/AvatarStatsWidgetProvider.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/widget/AvatarStatsWidgetProvider.kt index 74e5cf493..62f511e6f 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/widget/AvatarStatsWidgetProvider.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/widget/AvatarStatsWidgetProvider.kt @@ -11,7 +11,7 @@ import android.widget.RemoteViews import com.habitrpg.android.habitica.HabiticaBaseApplication import com.habitrpg.android.habitica.R import com.habitrpg.android.habitica.extensions.withImmutableFlag -import com.habitrpg.android.habitica.helpers.ExceptionHandler +import com.habitrpg.android.habitica.helpers.launchCatching import com.habitrpg.android.habitica.models.user.User import com.habitrpg.android.habitica.ui.activities.MainActivity import com.habitrpg.android.habitica.ui.views.HabiticaIconsHelper @@ -19,6 +19,9 @@ import com.habitrpg.common.habitica.extensions.dpToPx import com.habitrpg.common.habitica.helpers.HealthFormatter import com.habitrpg.common.habitica.helpers.NumberAbbreviator import com.habitrpg.common.habitica.views.AvatarView +import kotlinx.coroutines.MainScope +import kotlinx.coroutines.flow.firstOrNull +import kotlinx.coroutines.launch class AvatarStatsWidgetProvider : BaseWidgetProvider() { @@ -52,10 +55,12 @@ class AvatarStatsWidgetProvider : BaseWidgetProvider() { avatarView.layoutParams = layoutParams this.setUp() - userRepository.getUserFlowable().subscribe({ - user = it - updateData() - }, ExceptionHandler.rx()) + MainScope().launchCatching { + userRepository.getUser().collect { + user = it + updateData() + } + } } override fun onUpdate( @@ -79,10 +84,11 @@ class AvatarStatsWidgetProvider : BaseWidgetProvider() { this.context = context if (user == null) { - userRepository.getUserFlowable().firstElement().subscribe({ - user = it + MainScope().launch { + val user = userRepository.getUser().firstOrNull() ?: return@launch + this@AvatarStatsWidgetProvider.user = user updateData(appWidgetIds) - }, ExceptionHandler.rx()) + } } else { updateData() } From 3bd68fedf01879ef8e84e1fdcb58209e92d030f3 Mon Sep 17 00:00:00 2001 From: Phillip Thelen Date: Thu, 17 Nov 2022 14:19:21 +0100 Subject: [PATCH 31/36] Fully remove RxJava --- Habitica/build.gradle | 17 +- .../habitica/HabiticaBaseApplication.kt | 9 +- .../data/implementation/ApiClientImpl.kt | 11 +- .../data/local/ContentLocalRepository.kt | 1 - .../habitica/data/local/TagLocalRepository.kt | 1 - .../data/local/TaskLocalRepository.kt | 3 +- .../RealmBaseLocalRepository.kt | 14 -- .../RealmTaskLocalRepository.kt | 17 +- .../RealmUserLocalRepository.kt | 3 - .../executors/PostExecutionThread.java | 8 - .../android/habitica/executors/UIThread.java | 19 --- .../habitica/extensions/Base-Extensions.kt | 15 +- .../extensions/Flowable-Extensions.kt | 10 -- .../habitica/extensions/RxJava-Extensions.kt | 63 ------- .../habitica/helpers/AmplitudeManager.kt | 51 ++++-- .../habitica/helpers/ExceptionHandler.kt | 7 - .../habitica/helpers/SoundFileLoader.kt | 75 ++++----- .../android/habitica/helpers/SoundManager.kt | 21 ++- .../interactors/CheckClassSelectionUseCase.kt | 3 +- .../android/habitica/interactors/UseCase.kt | 13 -- .../android/habitica/modules/ApiModule.kt | 2 - .../android/habitica/modules/AppModule.kt | 8 - .../ui/activities/GroupInviteActivity.kt | 4 +- .../habitica/ui/activities/SetupActivity.kt | 9 +- ...stomizationEquipmentRecyclerViewAdapter.kt | 159 ------------------ .../ui/adapter/NavigationDrawerAdapter.kt | 14 +- .../ui/adapter/SkillsRecyclerViewAdapter.kt | 12 +- .../inventory/MountDetailRecyclerAdapter.kt | 2 - .../inventory/PetDetailRecyclerAdapter.kt | 3 - .../adapter/inventory/ShopRecyclerAdapter.kt | 8 +- .../social/ChallengesListViewAdapter.kt | 12 +- .../adapter/social/ChatRecyclerViewAdapter.kt | 45 ++--- .../ui/adapter/social/InboxAdapter.kt | 41 ++--- .../tasks/DailiesRecyclerViewHolder.kt | 8 +- .../tasks/HabitsRecyclerViewAdapter.kt | 6 +- .../RealmBaseTasksRecyclerViewAdapter.kt | 28 +-- .../tasks/RewardsRecyclerViewAdapter.kt | 31 ++-- .../adapter/tasks/TaskRecyclerViewAdapter.kt | 13 +- .../adapter/tasks/TodosRecyclerViewAdapter.kt | 8 +- .../ui/fragments/BaseDialogFragment.kt | 9 - .../habitica/ui/fragments/BaseFragment.kt | 9 - .../ui/fragments/NavigationDrawerFragment.kt | 24 +-- .../inventory/items/ItemDialogFragment.kt | 3 - .../inventory/items/ItemRecyclerFragment.kt | 1 - .../fragments/inventory/shops/ShopFragment.kt | 4 +- .../stable/PetDetailRecyclerFragment.kt | 2 - .../preferences/BasePreferencesFragment.kt | 4 - .../ui/fragments/setup/WelcomeFragment.kt | 5 +- .../ui/fragments/skills/SkillsFragment.kt | 3 +- .../ui/fragments/social/ChatFragment.kt | 75 +++------ .../social/InboxMessageListFragment.kt | 60 ++----- .../challenges/ChallengeListFragment.kt | 5 +- .../tasks/RewardsRecyclerviewFragment.kt | 10 +- .../tasks/TaskRecyclerViewFragment.kt | 46 ++--- .../ui/viewHolders/ChatRecyclerViewHolder.kt | 26 ++- .../viewHolders/tasks/BaseTaskViewHolder.kt | 46 ++--- .../tasks/ChecklistedViewHolder.kt | 23 ++- .../ui/viewmodels/AuthenticationViewModel.kt | 3 - .../ui/viewmodels/MainUserViewModel.kt | 4 - .../habitica/ui/viewmodels/TasksViewModel.kt | 3 - .../ui/views/dialogs/PetSuggestHatchDialog.kt | 15 +- .../habitica/ui/views/shops/PurchaseDialog.kt | 15 +- .../ui/views/social/QuestProgressView.kt | 31 ++-- .../ui/views/stats/BulkAllocateStatsDialog.kt | 8 - .../habitica/proxy/AnalyticsManagerImpl.kt | 3 +- build.gradle | 14 +- 66 files changed, 309 insertions(+), 916 deletions(-) delete mode 100644 Habitica/src/main/java/com/habitrpg/android/habitica/executors/PostExecutionThread.java delete mode 100644 Habitica/src/main/java/com/habitrpg/android/habitica/executors/UIThread.java delete mode 100644 Habitica/src/main/java/com/habitrpg/android/habitica/extensions/Flowable-Extensions.kt delete mode 100644 Habitica/src/main/java/com/habitrpg/android/habitica/extensions/RxJava-Extensions.kt delete mode 100644 Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/CustomizationEquipmentRecyclerViewAdapter.kt diff --git a/Habitica/build.gradle b/Habitica/build.gradle index 34fa27a2b..2dbde8c1d 100644 --- a/Habitica/build.gradle +++ b/Habitica/build.gradle @@ -35,7 +35,6 @@ dependencies { exclude module: 'okhttp' } implementation "com.squareup.retrofit2:converter-gson:$retrofit_version" - implementation "com.squareup.retrofit2:adapter-rxjava3:$retrofit_version" //Dependency Injection implementation "com.google.dagger:dagger:$daggerhilt_version" @@ -54,17 +53,11 @@ dependencies { implementation('com.jaredrummler:android-device-names:2.1.0') // IAP Handling / Verification - implementation "com.android.billingclient:billing-ktx:5.0.0" + implementation "com.android.billingclient:billing-ktx:5.1.0" implementation 'fr.avianey.com.viewpagerindicator:library:2.4.1@aar' - //RxJava - implementation 'io.reactivex.rxjava3:rxandroid:3.0.0' - implementation 'io.reactivex.rxjava3:rxjava:3.1.4' - implementation 'io.reactivex.rxjava3:rxkotlin:3.0.1' - implementation 'io.reactivex.rxjava2:rxjava:2.2.21' - implementation "com.github.akarnokd:rxjava3-bridge:3.0.2" //Analytics - implementation "com.amplitude:android-sdk:$amplitude_version" + implementation "com.amplitude:analytics-android:$amplitude_version" //Tests testImplementation 'io.kotest:kotest-runner-junit5:5.3.0' @@ -80,9 +73,9 @@ dependencies { androidTestImplementation 'androidx.test:runner:1.4.0' androidTestImplementation 'androidx.test:rules:1.4.0' debugImplementation 'androidx.fragment:fragment-testing:1.5.4' - androidTestImplementation 'androidx.test.ext:junit:1.1.3' + androidTestImplementation 'androidx.test.ext:junit:1.1.4' androidTestImplementation 'androidx.test:core-ktx:1.4.0' - androidTestImplementation 'androidx.test.ext:junit-ktx:1.1.3' + androidTestImplementation 'androidx.test.ext:junit-ktx:1.1.4' androidTestImplementation 'io.mockk:mockk-android:1.12.3' androidTestImplementation 'io.kotest:kotest-assertions-core:5.3.0' androidTestUtil("androidx.test:orchestrator:1.4.1") @@ -172,7 +165,7 @@ android { } composeOptions { - kotlinCompilerExtensionVersion = "1.3.2" + kotlinCompilerExtensionVersion = "1.4.0-alpha02" } signingConfigs { diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/HabiticaBaseApplication.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/HabiticaBaseApplication.kt index 276ea59d2..c2f65d854 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/HabiticaBaseApplication.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/HabiticaBaseApplication.kt @@ -18,8 +18,6 @@ import android.util.Log import androidx.appcompat.app.AppCompatDelegate import androidx.core.content.edit import androidx.preference.PreferenceManager -import com.amplitude.api.Amplitude -import com.amplitude.api.Identify import com.google.android.gms.wearable.Wearable import com.google.firebase.analytics.FirebaseAnalytics import com.google.firebase.installations.FirebaseInstallations @@ -29,6 +27,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.AmplitudeManager import com.habitrpg.android.habitica.helpers.ExceptionHandler import com.habitrpg.android.habitica.helpers.notifications.PushNotificationManager import com.habitrpg.android.habitica.modules.UserModule @@ -76,11 +75,7 @@ abstract class HabiticaBaseApplication : Application(), Application.ActivityLife if (!BuildConfig.DEBUG) { try { - Amplitude.getInstance().initialize(this, getString(R.string.amplitude_app_id)).enableForegroundTracking(this) - val identify = Identify() - .setOnce("androidStore", BuildConfig.STORE) - .set("launch_screen", sharedPrefs.getString("launch_screen", "")) - Amplitude.getInstance().identify(identify) + AmplitudeManager.initialize(this, sharedPrefs) } catch (ignored: Resources.NotFoundException) { } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/ApiClientImpl.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/ApiClientImpl.kt index 3b56c2116..c56099ff0 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/ApiClientImpl.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/ApiClientImpl.kt @@ -1,7 +1,6 @@ package com.habitrpg.android.habitica.data.implementation import android.content.Context -import com.amplitude.api.Amplitude import com.google.gson.JsonSyntaxException import com.habitrpg.android.habitica.BuildConfig import com.habitrpg.android.habitica.HabiticaBaseApplication @@ -51,7 +50,6 @@ import com.habitrpg.shared.habitica.models.responses.FeedResponse import com.habitrpg.shared.habitica.models.responses.Status import com.habitrpg.shared.habitica.models.responses.TaskDirectionData import com.habitrpg.shared.habitica.models.responses.VerifyUsernameResponse -import io.reactivex.rxjava3.functions.Consumer import okhttp3.Cache import okhttp3.OkHttpClient import okhttp3.Request @@ -59,7 +57,6 @@ import okhttp3.logging.HttpLoggingInterceptor import retrofit2.Converter import retrofit2.HttpException import retrofit2.Retrofit -import retrofit2.adapter.rxjava3.RxJava3CallAdapterFactory import retrofit2.converter.gson.GsonConverterFactory import java.io.File import java.io.IOException @@ -76,7 +73,7 @@ class ApiClientImpl( private val analyticsManager: AnalyticsManager, private val notificationsManager: NotificationsManager, private val context: Context -) : Consumer, ApiClient { +): ApiClient { private lateinit var retrofitAdapter: Retrofit @@ -157,7 +154,6 @@ class ApiClientImpl( retrofitAdapter = Retrofit.Builder() .client(client) .baseUrl(server.toString()) - .addCallAdapterFactory(RxJava3CallAdapterFactory.create()) .addConverterFactory(converter) .build() @@ -211,7 +207,7 @@ class ApiClientImpl( return process { apiService.loginApple(mapOf(Pair("code", authToken))) } } - override fun accept(throwable: Throwable) { + fun accept(throwable: Throwable) { val throwableClass = throwable.javaClass if (SocketTimeoutException::class.java.isAssignableFrom(throwableClass)) { return @@ -221,7 +217,7 @@ class ApiClientImpl( this.showConnectionProblemDialog(R.string.internal_error_api) } else if (throwableClass == SocketTimeoutException::class.java || UnknownHostException::class.java == throwableClass || IOException::class.java == throwableClass) { this.showConnectionProblemDialog(R.string.network_error_no_network_body) - } else if (retrofit2.adapter.rxjava3.HttpException::class.java.isAssignableFrom(throwable.javaClass) || HttpException::class.java.isAssignableFrom(throwable.javaClass)) { + } else if (HttpException::class.java.isAssignableFrom(throwable.javaClass)) { val error = throwable as HttpException val res = getErrorResponse(error) val status = error.code() @@ -319,7 +315,6 @@ class ApiClientImpl( this.hostConfig.userID = userID ?: "" this.hostConfig.apiKey = apiToken ?: "" analyticsManager.setUserIdentifier(this.hostConfig.userID) - Amplitude.getInstance().userId = this.hostConfig.userID } override suspend fun getStatus(): Status? = process { apiService.getStatus() } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/ContentLocalRepository.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/ContentLocalRepository.kt index 670a0dbce..d8a664dea 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/ContentLocalRepository.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/ContentLocalRepository.kt @@ -2,7 +2,6 @@ package com.habitrpg.android.habitica.data.local import com.habitrpg.android.habitica.models.ContentResult import com.habitrpg.android.habitica.models.WorldState -import io.reactivex.rxjava3.core.Flowable import kotlinx.coroutines.flow.Flow interface ContentLocalRepository : BaseLocalRepository { diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/TagLocalRepository.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/TagLocalRepository.kt index b4a2cf921..6ca4b3c14 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/TagLocalRepository.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/TagLocalRepository.kt @@ -1,7 +1,6 @@ package com.habitrpg.android.habitica.data.local import com.habitrpg.android.habitica.models.Tag -import io.reactivex.rxjava3.core.Flowable import kotlinx.coroutines.flow.Flow interface TagLocalRepository : BaseLocalRepository { diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/TaskLocalRepository.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/TaskLocalRepository.kt index e8a3a0f24..710518cfc 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/TaskLocalRepository.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/TaskLocalRepository.kt @@ -5,7 +5,6 @@ import com.habitrpg.android.habitica.models.tasks.TaskList import com.habitrpg.android.habitica.models.user.User import com.habitrpg.shared.habitica.models.tasks.TaskType import com.habitrpg.shared.habitica.models.tasks.TasksOrder -import io.reactivex.rxjava3.core.Maybe import kotlinx.coroutines.flow.Flow interface TaskLocalRepository : BaseLocalRepository { @@ -26,7 +25,7 @@ interface TaskLocalRepository : BaseLocalRepository { fun getTaskAtPosition(taskType: String, position: Int): Flow - fun updateIsdue(daily: TaskList): Maybe + fun updateIsdue(daily: TaskList): TaskList fun updateTaskPositions(taskOrder: List) fun saveCompletedTodos(userId: String, tasks: MutableCollection) diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/implementation/RealmBaseLocalRepository.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/implementation/RealmBaseLocalRepository.kt index a6c661e3d..8ece9e579 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/implementation/RealmBaseLocalRepository.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/implementation/RealmBaseLocalRepository.kt @@ -1,12 +1,9 @@ package com.habitrpg.android.habitica.data.local.implementation import com.habitrpg.android.habitica.data.local.BaseLocalRepository -import com.habitrpg.android.habitica.extensions.filterMap import com.habitrpg.android.habitica.models.BaseMainObject import com.habitrpg.android.habitica.models.BaseObject import com.habitrpg.android.habitica.models.user.User -import hu.akarnokd.rxjava3.bridge.RxJavaBridge -import io.reactivex.rxjava3.core.Flowable import io.realm.Realm import io.realm.RealmObject import io.realm.kotlin.deleteFromRealm @@ -115,15 +112,4 @@ abstract class RealmBaseLocalRepository internal constructor(override var realm: .filter { it.isLoaded && it.isValid && !it.isEmpty() } .map { it.firstOrNull() } } - - fun queryUserFlowable(userID: String): Flowable { - return RxJavaBridge.toV3Flowable( - realm.where(User::class.java) - .equalTo("id", userID) - .findAll() - .asFlowable() - ) - .filter { it.isLoaded && it.isValid && !it.isEmpty() } - .filterMap { it.first() } - } } 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 51ff44e96..239cfee26 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 @@ -8,8 +8,6 @@ import com.habitrpg.android.habitica.models.tasks.TaskList import com.habitrpg.android.habitica.models.user.User import com.habitrpg.shared.habitica.models.tasks.TaskType import com.habitrpg.shared.habitica.models.tasks.TasksOrder -import io.reactivex.rxjava3.core.Flowable -import io.reactivex.rxjava3.core.Maybe import io.realm.Realm import io.realm.RealmResults import io.realm.Sort @@ -227,15 +225,12 @@ class RealmTaskLocalRepository(realm: Realm) : RealmBaseLocalRepository(realm), .filterNotNull() } - override fun updateIsdue(daily: TaskList): Maybe { - return Flowable.just(realm.where(Task::class.java).equalTo("typeValue", TaskType.DAILY.value).findAll()) - .firstElement() - .map { tasks -> - realm.beginTransaction() - tasks.filter { daily.tasks.containsKey(it.id) }.forEach { it.isDue = daily.tasks[it.id]?.isDue } - realm.commitTransaction() - daily - } + override fun updateIsdue(daily: TaskList): TaskList { + val tasks = realm.where(Task::class.java).equalTo("typeValue", TaskType.DAILY.value).findAll() + realm.beginTransaction() + tasks.filter { daily.tasks.containsKey(it.id) }.forEach { it.isDue = daily.tasks[it.id]?.isDue } + realm.commitTransaction() + return daily } override fun updateTaskPositions(taskOrder: List) { 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 f835f4017..a6485a566 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 @@ -1,7 +1,6 @@ package com.habitrpg.android.habitica.data.local.implementation import com.habitrpg.android.habitica.data.local.UserLocalRepository -import com.habitrpg.android.habitica.extensions.filterMap import com.habitrpg.android.habitica.models.Achievement import com.habitrpg.android.habitica.models.QuestAchievement import com.habitrpg.android.habitica.models.Skill @@ -12,12 +11,10 @@ import com.habitrpg.android.habitica.models.social.ChatMessage import com.habitrpg.android.habitica.models.social.Group import com.habitrpg.android.habitica.models.user.User import com.habitrpg.android.habitica.models.user.UserQuestStatus -import hu.akarnokd.rxjava3.bridge.RxJavaBridge import io.realm.Realm import io.realm.kotlin.toFlow import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.filterNotNull diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/executors/PostExecutionThread.java b/Habitica/src/main/java/com/habitrpg/android/habitica/executors/PostExecutionThread.java deleted file mode 100644 index a8b6b9ba5..000000000 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/executors/PostExecutionThread.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.habitrpg.android.habitica.executors; - - -import io.reactivex.rxjava3.core.Scheduler; - -public interface PostExecutionThread { - Scheduler getScheduler(); -} diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/executors/UIThread.java b/Habitica/src/main/java/com/habitrpg/android/habitica/executors/UIThread.java deleted file mode 100644 index 9617314a2..000000000 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/executors/UIThread.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.habitrpg.android.habitica.executors; - -import javax.inject.Inject; -import javax.inject.Singleton; - -import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers; -import io.reactivex.rxjava3.core.Scheduler; - -@Singleton -public class UIThread implements PostExecutionThread { - - @Inject - public UIThread() {} - - @Override - public Scheduler getScheduler() { - return AndroidSchedulers.mainThread(); - } -} diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/extensions/Base-Extensions.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/extensions/Base-Extensions.kt index afab812d6..d2514ac9c 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/extensions/Base-Extensions.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/extensions/Base-Extensions.kt @@ -1,13 +1,18 @@ package com.habitrpg.android.habitica.extensions -import io.reactivex.rxjava3.core.Completable -import java.util.concurrent.TimeUnit +import com.habitrpg.android.habitica.helpers.launchCatching +import kotlinx.coroutines.MainScope +import kotlinx.coroutines.delay +import kotlin.time.DurationUnit +import kotlin.time.toDuration /** * Created by phillip on 01.02.18. */ -fun runDelayed(interval: Long, timeUnit: TimeUnit, function: () -> Unit) { - Completable.complete().delay(interval, timeUnit) - .subscribe(function) +fun runDelayed(interval: Long, timeUnit: DurationUnit, function: () -> Unit) { + MainScope().launchCatching { + delay(interval.toDuration(timeUnit)) + function() + } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/extensions/Flowable-Extensions.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/extensions/Flowable-Extensions.kt deleted file mode 100644 index c27f2de98..000000000 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/extensions/Flowable-Extensions.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.habitrpg.android.habitica.extensions - -import com.habitrpg.android.habitica.helpers.ExceptionHandler -import io.reactivex.rxjava3.core.Flowable -import io.reactivex.rxjava3.disposables.Disposable -import io.reactivex.rxjava3.functions.Consumer - -fun Flowable.subscribeWithErrorHandler(function: Consumer): Disposable { - return subscribe(function, ExceptionHandler.rx()) -} diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/extensions/RxJava-Extensions.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/extensions/RxJava-Extensions.kt deleted file mode 100644 index 52ef4419f..000000000 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/extensions/RxJava-Extensions.kt +++ /dev/null @@ -1,63 +0,0 @@ -package com.habitrpg.android.habitica.extensions - -import com.habitrpg.common.habitica.extensions.Optional -import io.reactivex.rxjava3.core.Flowable -import io.reactivex.rxjava3.core.Maybe -import io.reactivex.rxjava3.core.Observable -import io.reactivex.rxjava3.core.Single - -fun > Flowable.filterOptionalDoOnEmpty(function: () -> Unit): Flowable { - return this.doOnNext { if (it.isEmpty) function() } - .filter { !it.isEmpty } - .map { it.assertedValue } -} - -fun > Flowable.filterMapEmpty(): Flowable { - return this.filter { !it.isEmpty } - .map { it.assertedValue } -} - -fun Flowable.filterMap(function: (T) -> S?): Flowable { - return this.map { Optional(function(it)) } - .filter { !it.isEmpty } - .map { it.assertedValue } -} - -fun > Observable.filterOptionalDoOnEmpty(function: () -> Unit): Observable { - return this.doOnNext { if (it.isEmpty) function() } - .filter { !it.isEmpty } - .map { it.assertedValue } -} - -fun > Observable.filterMapEmpty(): Observable { - return this.filter { !it.isEmpty } - .map { it.assertedValue } -} - -fun Observable.filterMap(function: (T) -> S?): Observable { - return this.map { Optional(function(it)) } - .filter { !it.isEmpty } - .map { it.assertedValue } -} - -fun > Single.filterOptionalDoOnEmpty(function: () -> Unit): Maybe { - return this.doOnSuccess { if (it.isEmpty) function() } - .filter { !it.isEmpty } - .map { it.assertedValue } -} - -fun > Single.filterMapEmpty(): Maybe { - return this.filter { !it.isEmpty } - .map { it.assertedValue } -} - -fun > Maybe.filterOptionalDoOnEmpty(function: () -> Unit): Maybe { - return this.doOnSuccess { if (it.isEmpty) function() } - .filter { !it.isEmpty } - .map { it.assertedValue } -} - -fun > Maybe.filterMapEmpty(): Maybe { - return this.filter { !it.isEmpty } - .map { it.assertedValue } -} diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/AmplitudeManager.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/AmplitudeManager.kt index bcbe7b826..c8e33ed26 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/AmplitudeManager.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/AmplitudeManager.kt @@ -1,9 +1,12 @@ package com.habitrpg.android.habitica.helpers -import com.amplitude.api.Amplitude +import android.content.Context +import android.content.SharedPreferences +import com.amplitude.android.Amplitude +import com.amplitude.android.Configuration +import com.amplitude.android.events.Identify import com.habitrpg.android.habitica.BuildConfig -import org.json.JSONException -import org.json.JSONObject +import com.habitrpg.android.habitica.R object AmplitudeManager { var EVENT_CATEGORY_BEHAVIOUR = "behaviour" @@ -14,6 +17,8 @@ object AmplitudeManager { var EVENT_HITTYPE_REMOVE_WIDGET = "remove" var EVENT_HITTYPE_UPDATE_WIDGET = "update" + lateinit var amplitude: Amplitude + @JvmOverloads fun sendEvent( eventAction: String?, @@ -24,20 +29,20 @@ object AmplitudeManager { if (BuildConfig.DEBUG) { return } - val eventProperties = JSONObject() - try { - eventProperties.put("eventAction", eventAction) - eventProperties.put("eventCategory", eventCategory) - eventProperties.put("hitType", hitType) - eventProperties.put("status", "displayed") - if (additionalData != null) { - for ((key, value) in additionalData) { - eventProperties.put(key, value) - } + val data = mutableMapOf( + "eventAction" to eventAction, + "eventCategory" to eventCategory, + "hitType" to hitType, + "status" to "displayed" + ) + if (additionalData != null) { + for ((key, value) in additionalData) { + data.put(key, value) } - } catch (exception: JSONException) { } - Amplitude.getInstance().logEvent(eventAction, eventProperties) + if (eventAction != null) { + amplitude.track(eventAction, data) + } } fun sendNavigationEvent(page: String) { @@ -45,4 +50,20 @@ object AmplitudeManager { additionalData["page"] = page sendEvent("navigated", EVENT_CATEGORY_NAVIGATION, EVENT_HITTYPE_PAGEVIEW, additionalData) } + + fun initialize(context: Context, sharedPrefs: SharedPreferences) { + amplitude = Amplitude( + Configuration( + context.getString(R.string.amplitude_app_id), + context + ) + ) + + val identify = Identify() + .setOnce("androidStore", BuildConfig.STORE) + sharedPrefs.getString("launch_screen", "")?.let { + identify.set("launch_screen", it) + } + amplitude.identify(identify) + } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/ExceptionHandler.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/ExceptionHandler.kt index 2e52bb4ec..ea1bce780 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/ExceptionHandler.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/ExceptionHandler.kt @@ -3,7 +3,6 @@ package com.habitrpg.android.habitica.helpers import android.util.Log import com.habitrpg.android.habitica.BuildConfig import com.habitrpg.android.habitica.proxy.AnalyticsManager -import io.reactivex.rxjava3.functions.Consumer import kotlinx.coroutines.CoroutineExceptionHandler import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch @@ -30,11 +29,6 @@ class ExceptionHandler { } } - fun rx(): Consumer { - // Can't be turned into a lambda, because it then doesn't work for some reason. - return Consumer { reportError(it) } - } - fun reportError(throwable: Throwable) { if (BuildConfig.DEBUG) { try { @@ -46,7 +40,6 @@ class ExceptionHandler { !HttpException::class.java.isAssignableFrom(throwable.javaClass) && !retrofit2.HttpException::class.java.isAssignableFrom(throwable.javaClass) && !EOFException::class.java.isAssignableFrom(throwable.javaClass) && - !retrofit2.adapter.rxjava3.HttpException::class.java.isAssignableFrom(throwable.javaClass) && throwable !is ConnectionShutdownException ) { instance.analyticsManager?.logException(throwable) diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/SoundFileLoader.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/SoundFileLoader.kt index 98033f7d1..0e61f48af 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/SoundFileLoader.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/SoundFileLoader.kt @@ -4,16 +4,15 @@ import android.annotation.SuppressLint import android.content.Context import android.os.Environment import com.habitrpg.android.habitica.HabiticaBaseApplication -import io.reactivex.rxjava3.core.Observable -import io.reactivex.rxjava3.core.Single -import io.reactivex.rxjava3.schedulers.Schedulers -import java.io.File -import java.io.IOException +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext import okhttp3.OkHttpClient import okhttp3.Request import okhttp3.Response import okio.buffer import okio.sink +import java.io.File +import java.io.IOException // based on http://stackoverflow.com/questions/29838565/downloading-files-using-okhttp-okio-and-rxjava class SoundFileLoader(private val context: Context) { @@ -26,52 +25,42 @@ class SoundFileLoader(private val context: Context) { } @SuppressLint("SetWorldReadable", "ReturnCount") - fun download(files: List): Single> { - return Observable.fromIterable(files) - .flatMap( - { audioFile -> + suspend fun download(files: List): List { + return files.map { audioFile -> + withContext(Dispatchers.IO) { val file = File(getFullAudioFilePath(audioFile)) if (file.exists() && file.length() > 5000) { // Important, or else the MediaPlayer can't access this file file.setReadable(true, false) audioFile.file = file - return@flatMap Observable.just(audioFile) + return@withContext audioFile + } + val request = Request.Builder().url(audioFile.webUrl).build() + + val response: Response + try { + response = client.newCall(request).execute() + if (!response.isSuccessful) { + throw IOException() + } + } catch (io: IOException) { + return@withContext audioFile } - val fileObservable = Observable.create { sub -> - val request = Request.Builder().url(audioFile.webUrl).build() - - val response: Response - try { - response = client.newCall(request).execute() - if (!response.isSuccessful) { - throw IOException() - } - } catch (io: IOException) { - sub.onComplete() - return@create - } - - try { - val sink = file.sink().buffer() - sink.writeAll(response.body!!.source()) - sink.flush() - sink.close() - } catch (io: IOException) { - sub.onComplete() - return@create - } - - file.setReadable(true, false) - audioFile.file = file - sub.onNext(audioFile) - sub.onComplete() + try { + val sink = file.sink().buffer() + sink.writeAll(response.body!!.source()) + sink.flush() + sink.close() + } catch (io: IOException) { + return@withContext audioFile } - fileObservable.subscribeOn(Schedulers.io()) - }, - 5 - ) - .toList() + + file.setReadable(true, false) + audioFile.file = file + return@withContext audioFile + } + } } private fun getFullAudioFilePath(soundFile: SoundFile): String = diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/SoundManager.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/SoundManager.kt index 178e461e4..4eba474b2 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/SoundManager.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/SoundManager.kt @@ -1,7 +1,7 @@ package com.habitrpg.android.habitica.helpers import com.habitrpg.android.habitica.HabiticaBaseApplication -import io.reactivex.rxjava3.schedulers.Schedulers +import kotlinx.coroutines.MainScope import javax.inject.Inject class SoundManager { @@ -33,8 +33,9 @@ class SoundManager { soundFiles.add(SoundFile(soundTheme, SoundPlusHabit)) soundFiles.add(SoundFile(soundTheme, SoundReward)) soundFiles.add(SoundFile(soundTheme, SoundTodo)) - soundFileLoader.download(soundFiles) - .subscribe({}, ExceptionHandler.rx()) + MainScope().launchCatching { + soundFileLoader.download(soundFiles) + } } fun loadAndPlayAudio(type: String) { @@ -48,14 +49,12 @@ class SoundManager { val soundFiles = ArrayList() soundFiles.add(SoundFile(soundTheme, type)) - soundFileLoader.download(soundFiles).observeOn(Schedulers.newThread()).subscribe( - { - val file = soundFiles[0] - loadedSoundFiles[type] = file - file.play() - }, - ExceptionHandler.rx() - ) + MainScope().launchCatching { + val newFiles = soundFileLoader.download(soundFiles) + val file = newFiles[0] + loadedSoundFiles[type] = file + file.play() + } } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/interactors/CheckClassSelectionUseCase.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/interactors/CheckClassSelectionUseCase.kt index ecbace013..1c2cf3fb7 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/interactors/CheckClassSelectionUseCase.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/interactors/CheckClassSelectionUseCase.kt @@ -3,12 +3,11 @@ package com.habitrpg.android.habitica.interactors import android.app.Activity import android.content.Intent import android.os.Bundle -import com.habitrpg.android.habitica.executors.PostExecutionThread import com.habitrpg.android.habitica.models.user.User import com.habitrpg.android.habitica.ui.activities.ClassSelectionActivity import javax.inject.Inject -class CheckClassSelectionUseCase @Inject constructor(postExecutionThread: PostExecutionThread) : FlowUseCase() { +class CheckClassSelectionUseCase @Inject constructor() : FlowUseCase() { override suspend fun run(requestValues: RequestValues) { val user = requestValues.user diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/interactors/UseCase.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/interactors/UseCase.kt index 45e028064..82222ccf0 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/interactors/UseCase.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/interactors/UseCase.kt @@ -1,21 +1,8 @@ package com.habitrpg.android.habitica.interactors -import com.habitrpg.android.habitica.executors.PostExecutionThread -import io.reactivex.rxjava3.core.Flowable import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext -abstract class UseCase protected constructor(private val postExecutionThread: PostExecutionThread) { - protected abstract fun buildUseCaseObservable(requestValues: Q): Flowable - fun observable(requestValues: Q): Flowable { - return buildUseCaseObservable(requestValues) - .subscribeOn(postExecutionThread.scheduler) - .observeOn(postExecutionThread.scheduler) - } - - interface RequestValues -} - abstract class FlowUseCase { protected abstract suspend fun run(requestValues: Q): T suspend fun callInteractor(requestValues: Q): T { diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/modules/ApiModule.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/modules/ApiModule.kt index 66ee1e4f2..56d23b7f7 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/modules/ApiModule.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/modules/ApiModule.kt @@ -14,7 +14,6 @@ import com.habitrpg.common.habitica.helpers.KeyHelper import dagger.Module import dagger.Provides import retrofit2.Retrofit -import retrofit2.adapter.rxjava3.RxJava3CallAdapterFactory import retrofit2.converter.gson.GsonConverterFactory import java.lang.ref.WeakReference import javax.inject.Singleton @@ -66,7 +65,6 @@ open class ApiModule { fun providesMaintenanceApiService(gsonConverter: GsonConverterFactory): MaintenanceApiService { val adapter = Retrofit.Builder() .baseUrl("https://habitica-assets.s3.amazonaws.com/mobileApp/endpoint/") - .addCallAdapterFactory(RxJava3CallAdapterFactory.create()) .addConverterFactory(gsonConverter) .build() return adapter.create(MaintenanceApiService::class.java) diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/modules/AppModule.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/modules/AppModule.kt index 1737dc1f0..25e29814e 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/modules/AppModule.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/modules/AppModule.kt @@ -7,8 +7,6 @@ import android.content.res.Resources import androidx.preference.PreferenceManager import com.habitrpg.android.habitica.data.ApiClient import com.habitrpg.android.habitica.data.ContentRepository -import com.habitrpg.android.habitica.executors.PostExecutionThread -import com.habitrpg.android.habitica.executors.UIThread import com.habitrpg.android.habitica.helpers.AppConfigManager import com.habitrpg.android.habitica.helpers.SoundFileLoader import com.habitrpg.android.habitica.helpers.SoundManager @@ -84,12 +82,6 @@ class AppModule(private val application: Application) { return SoundManager() } - @Provides - @Singleton - fun providePostExecutionThread(uiThread: UIThread): PostExecutionThread { - return uiThread - } - @Provides @Singleton fun pushNotificationManager( diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/GroupInviteActivity.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/GroupInviteActivity.kt index 8ad8fbf1e..aca79ac48 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/GroupInviteActivity.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/GroupInviteActivity.kt @@ -19,9 +19,9 @@ import com.habitrpg.android.habitica.ui.fragments.social.party.PartyInviteFragme import com.habitrpg.android.habitica.ui.helpers.dismissKeyboard import com.habitrpg.android.habitica.ui.views.HabiticaSnackbar import com.habitrpg.android.habitica.ui.views.HabiticaSnackbar.Companion.showSnackbar -import java.util.concurrent.TimeUnit import javax.inject.Inject import javax.inject.Named +import kotlin.time.DurationUnit class GroupInviteActivity : BaseActivity() { @@ -70,7 +70,7 @@ class GroupInviteActivity : BaseActivity() { dismissKeyboard() if (fragments.size > binding.viewPager.currentItem && fragments[binding.viewPager.currentItem].values.isNotEmpty()) { showSnackbar(binding.snackbarView, "Invite Sent!", HabiticaSnackbar.SnackbarDisplayType.SUCCESS) - runDelayed(1, TimeUnit.SECONDS, this::finish) + runDelayed(1, DurationUnit.SECONDS, this::finish) } else { finish() } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/SetupActivity.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/SetupActivity.kt index 54e51ff75..09f88528b 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/SetupActivity.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/SetupActivity.kt @@ -32,7 +32,6 @@ 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 @@ -260,9 +259,7 @@ class SetupActivity : BaseActivity(), ViewPager.OnPageChangeListener { else -> { val fragment = WelcomeFragment() welcomeFragment = fragment - welcomeFragment?.nameValidEvents?.toFlowable(BackpressureStrategy.DROP)?.subscribe { - setNextButtonEnabled(it) - } + welcomeFragment?.onNameValid = { setNextButtonEnabled(it == true) } fragment } } @@ -283,9 +280,7 @@ class SetupActivity : BaseActivity(), ViewPager.OnPageChangeListener { } is WelcomeFragment -> { welcomeFragment = item - item.nameValidEvents.toFlowable(BackpressureStrategy.DROP).subscribe { - setNextButtonEnabled(it) - } + item.onNameValid = { setNextButtonEnabled(it == true) } } } return item diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/CustomizationEquipmentRecyclerViewAdapter.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/CustomizationEquipmentRecyclerViewAdapter.kt deleted file mode 100644 index a1e8f30ba..000000000 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/CustomizationEquipmentRecyclerViewAdapter.kt +++ /dev/null @@ -1,159 +0,0 @@ -package com.habitrpg.android.habitica.ui.adapter - -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.widget.ImageView -import android.widget.LinearLayout -import android.widget.TextView -import androidx.core.content.ContextCompat -import androidx.core.os.bundleOf -import com.habitrpg.android.habitica.R -import com.habitrpg.android.habitica.databinding.CustomizationGridItemBinding -import com.habitrpg.android.habitica.helpers.MainNavigationController -import com.habitrpg.android.habitica.models.inventory.CustomizationSet -import com.habitrpg.android.habitica.models.inventory.Equipment -import com.habitrpg.common.habitica.extensions.loadImage -import com.habitrpg.android.habitica.ui.views.HabiticaIconsHelper -import com.habitrpg.common.habitica.views.PixelArtView -import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog -import io.reactivex.rxjava3.core.BackpressureStrategy -import io.reactivex.rxjava3.core.Flowable -import io.reactivex.rxjava3.subjects.PublishSubject - -class CustomizationEquipmentRecyclerViewAdapter : androidx.recyclerview.widget.RecyclerView.Adapter() { - - var gemBalance: Int = 0 - var equipmentList: MutableList = - ArrayList() - set(value) { - field = value - notifyDataSetChanged() - } - var activeEquipment: String? = null - set(value) { - field = value - this.notifyDataSetChanged() - } - - private val selectCustomizationEvents = PublishSubject.create() - private val unlockCustomizationEvents = PublishSubject.create() - - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): androidx.recyclerview.widget.RecyclerView.ViewHolder { - val viewID: Int = R.layout.customization_grid_item - - val view = LayoutInflater.from(parent.context).inflate(viewID, parent, false) - return EquipmentViewHolder(view) - } - - override fun onBindViewHolder( - holder: androidx.recyclerview.widget.RecyclerView.ViewHolder, - position: Int - ) { - (holder as EquipmentViewHolder).bind(equipmentList[position]) - } - - override fun getItemCount(): Int { - return equipmentList.size - } - - override fun getItemViewType(position: Int): Int { - if (equipmentList.size <= position) return 0 - return if (this.equipmentList[position].javaClass == CustomizationSet::class.java) { - 0 - } else { - 1 - } - } - - fun setEquipment(newEquipmentList: List) { - this.equipmentList = newEquipmentList.toMutableList() - val emptyEquipment = Equipment() - equipmentList.add(0, emptyEquipment) - this.notifyDataSetChanged() - } - - fun getSelectCustomizationEvents(): Flowable { - return selectCustomizationEvents.toFlowable(BackpressureStrategy.DROP) - } - - fun getUnlockCustomizationEvents(): Flowable { - return unlockCustomizationEvents.toFlowable(BackpressureStrategy.DROP) - } - - internal inner class EquipmentViewHolder(itemView: View) : androidx.recyclerview.widget.RecyclerView.ViewHolder(itemView), View.OnClickListener { - - private val binding = CustomizationGridItemBinding.bind(itemView) - var equipment: Equipment? = null - - init { - itemView.setOnClickListener(this) - } - - fun bind(equipment: Equipment) { - this.equipment = equipment - binding.imageView.loadImage("shop_" + this.equipment?.key) - if (equipment.owned == true || equipment.value == 0.0) { - binding.buyButton.visibility = View.GONE - } else { - binding.buyButton.visibility = View.VISIBLE - binding.priceLabel.currency = "gems" - binding.priceLabel.value = if (equipment.gearSet == "animal") { - 2.0 - } else { - equipment.value - } - } - - if (activeEquipment == equipment.key || (activeEquipment?.contains("base_0") == true && equipment.key?.isNotBlank() != true)) { - binding.wrapper.background = ContextCompat.getDrawable(itemView.context, R.drawable.layout_rounded_bg_window_tint_border) - } else { - binding.wrapper.background = ContextCompat.getDrawable(itemView.context, R.drawable.layout_rounded_bg_window) - } - } - - override fun onClick(v: View) { - if (equipment?.owned != true && (equipment?.value ?: 0.0) > 0.0) { - val dialogContent = LayoutInflater.from(itemView.context).inflate(R.layout.dialog_purchase_customization, null) as LinearLayout - - val imageView = dialogContent.findViewById(R.id.imageView) - imageView.loadImage("shop_" + this.equipment?.key) - - val priceLabel = dialogContent.findViewById(R.id.priceLabel) - priceLabel.text = if (equipment?.gearSet == "animal") { - 2.0 - } else { - equipment?.value ?: 0 - }.toString() - - (dialogContent.findViewById(R.id.gem_icon) as? ImageView)?.setImageBitmap( - HabiticaIconsHelper.imageOfGem()) - - val dialog = HabiticaAlertDialog(itemView.context) - dialog.addButton(R.string.purchase_button, true) { _, _ -> - if (equipment?.value ?: 0.0 > gemBalance) { - MainNavigationController.navigate(R.id.gemPurchaseActivity, bundleOf(Pair("openSubscription", false))) - return@addButton - } - - equipment?.let { - unlockCustomizationEvents.onNext(it) - } - } - dialog.setTitle(R.string.purchase_customization) - dialog.setAdditionalContentView(dialogContent) - dialog.addButton(R.string.reward_dialog_dismiss, false) - dialog.show() - return - } - - if (equipment?.key == activeEquipment) { - return - } - - equipment?.let { - selectCustomizationEvents.onNext(it) - } - } - } -} diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/NavigationDrawerAdapter.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/NavigationDrawerAdapter.kt index 23e19efff..870e5985c 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/NavigationDrawerAdapter.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/NavigationDrawerAdapter.kt @@ -14,9 +14,6 @@ import com.habitrpg.android.habitica.ui.views.promo.PromoMenuViewHolder import com.habitrpg.android.habitica.ui.views.promo.SubscriptionBuyGemsPromoView import com.habitrpg.android.habitica.ui.views.promo.SubscriptionBuyGemsPromoViewHolder import com.habitrpg.common.habitica.extensions.dpToPx -import io.reactivex.rxjava3.core.BackpressureStrategy -import io.reactivex.rxjava3.core.Flowable -import io.reactivex.rxjava3.subjects.PublishSubject class NavigationDrawerAdapter(tintColor: Int, backgroundTintColor: Int) : RecyclerView.Adapter() { @@ -46,14 +43,11 @@ class NavigationDrawerAdapter(tintColor: Int, backgroundTintColor: Int) : Recycl notifyDataSetChanged() } - private val itemSelectedEvents = PublishSubject.create() - private val promoClosedSubject = PublishSubject.create() + var itemSelectedEvents: ((HabiticaDrawerItem) -> Unit)? = null + var promoClosedSubject: ((String) -> Unit)? = null var activePromo: HabiticaPromotion? = null - fun getItemSelectionEvents(): Flowable = itemSelectedEvents.toFlowable(BackpressureStrategy.DROP) - fun getPromoCloseEvents(): Flowable = promoClosedSubject.toFlowable(BackpressureStrategy.DROP) - fun getItemWithIdentifier(identifier: String): HabiticaDrawerItem? = items.find { it.identifier == identifier } @@ -81,7 +75,7 @@ class NavigationDrawerAdapter(tintColor: Int, backgroundTintColor: Int) : Recycl val itemHolder = holder as? DrawerItemViewHolder itemHolder?.tintColor = tintColor itemHolder?.bind(drawerItem, drawerItem.transitionId == selectedItem) - itemHolder?.itemView?.setOnClickListener { itemSelectedEvents.onNext(drawerItem) } + itemHolder?.itemView?.setOnClickListener { itemSelectedEvents?.invoke(drawerItem) } } getItemViewType(position) == 1 -> { (holder as? SectionHeaderViewHolder)?.backgroundTintColor = backgroundTintColor @@ -91,7 +85,7 @@ class NavigationDrawerAdapter(tintColor: Int, backgroundTintColor: Int) : Recycl activePromo?.let { promo -> (holder as? PromoMenuViewHolder)?.bind(promo) (holder as? PromoMenuViewHolder)?.promoView?.binding?.closeButton?.setOnClickListener { - promoClosedSubject.onNext(promo.identifier) + promoClosedSubject?.invoke(promo.identifier) } } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/SkillsRecyclerViewAdapter.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/SkillsRecyclerViewAdapter.kt index 043b5a1cd..26f0b6121 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/SkillsRecyclerViewAdapter.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/SkillsRecyclerViewAdapter.kt @@ -10,20 +10,16 @@ import androidx.recyclerview.widget.RecyclerView import com.habitrpg.android.habitica.R import com.habitrpg.android.habitica.databinding.SkillListItemBinding import com.habitrpg.android.habitica.extensions.inflate -import com.habitrpg.common.habitica.extensions.isUsingNightModeResources import com.habitrpg.android.habitica.models.Skill import com.habitrpg.android.habitica.models.user.OwnedItem -import com.habitrpg.common.habitica.extensions.loadImage import com.habitrpg.android.habitica.ui.views.HabiticaIconsHelper -import io.reactivex.rxjava3.core.BackpressureStrategy -import io.reactivex.rxjava3.core.Flowable -import io.reactivex.rxjava3.subjects.PublishSubject +import com.habitrpg.common.habitica.extensions.isUsingNightModeResources +import com.habitrpg.common.habitica.extensions.loadImage import io.realm.RealmList class SkillsRecyclerViewAdapter : RecyclerView.Adapter() { - private val useSkillSubject = PublishSubject.create() - val useSkillEvents: Flowable = useSkillSubject.toFlowable(BackpressureStrategy.DROP) + var onUseSkill: ((Skill) -> Unit)? = null var mana: Double = 0.0 set(value) { @@ -130,7 +126,7 @@ class SkillsRecyclerViewAdapter : RecyclerView.Adapter() { var onEquip: ((String) -> Unit)? = null private var ownedMounts: Map? = null - private val equipEvents = PublishSubject.create() var currentMount: String? = null set(value) { field = value diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/inventory/PetDetailRecyclerAdapter.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/inventory/PetDetailRecyclerAdapter.kt index f8452d3d0..9f030f4ce 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/inventory/PetDetailRecyclerAdapter.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/inventory/PetDetailRecyclerAdapter.kt @@ -20,7 +20,6 @@ import com.habitrpg.android.habitica.ui.viewHolders.SectionViewHolder import com.habitrpg.android.habitica.ui.views.dialogs.PetSuggestHatchDialog import com.habitrpg.common.habitica.extensions.loadImage import com.habitrpg.common.habitica.helpers.Animations -import io.reactivex.rxjava3.subjects.PublishSubject class PetDetailRecyclerAdapter : androidx.recyclerview.widget.RecyclerView.Adapter() { var onFeed: ((Pet, Food?) -> Unit)? = null @@ -34,8 +33,6 @@ class PetDetailRecyclerAdapter : androidx.recyclerview.widget.RecyclerView.Adapt field = value notifyDataSetChanged() } - private val equipEvents = PublishSubject.create() - private val feedEvents = PublishSubject.create>() private var ownsSaddles: Boolean = false private var itemList: List = ArrayList() diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/inventory/ShopRecyclerAdapter.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/inventory/ShopRecyclerAdapter.kt index 08bdeea26..f6103f4a0 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/inventory/ShopRecyclerAdapter.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/inventory/ShopRecyclerAdapter.kt @@ -18,9 +18,6 @@ import com.habitrpg.android.habitica.models.user.OwnedItem import com.habitrpg.android.habitica.models.user.User import com.habitrpg.android.habitica.ui.viewHolders.SectionViewHolder import com.habitrpg.android.habitica.ui.viewHolders.ShopItemViewHolder -import io.reactivex.rxjava3.core.BackpressureStrategy -import io.reactivex.rxjava3.core.Flowable -import io.reactivex.rxjava3.subjects.BehaviorSubject class ShopRecyclerAdapter : androidx.recyclerview.widget.RecyclerView.Adapter() { @@ -28,8 +25,7 @@ class ShopRecyclerAdapter : androidx.recyclerview.widget.RecyclerView.Adapter = HashMap() - private val changeClassSubject = BehaviorSubject.create() - val changeClassEvents: Flowable = changeClassSubject.toFlowable(BackpressureStrategy.DROP) + var changeClassEvents: ((String) -> Unit)? = null var shopSpriteSuffix: String = "" set(value) { @@ -128,7 +124,7 @@ class ShopRecyclerAdapter : androidx.recyclerview.widget.RecyclerView.Adapter? = null private var challengeMemberships: List? = null - private val openChallengeFragmentEvents = PublishSubject.create() + var onOpenChallengeFragment: ((String) -> Unit)? = null override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ChallengeViewHolder { return ChallengeViewHolder(parent.inflate(R.layout.challenge_item), viewUserChallengesOnly) @@ -36,7 +34,7 @@ class ChallengesListViewAdapter( holder.itemView.setOnClickListener { if (challenge.isManaged && challenge.isValid) { challenge.id?.let { - openChallengeFragmentEvents.onNext(it) + onOpenChallengeFragment?.invoke(it) } } } @@ -76,10 +74,6 @@ class ChallengesListViewAdapter( } } - fun getOpenDetailFragmentFlowable(): Flowable { - return openChallengeFragmentEvents.toFlowable(BackpressureStrategy.DROP) - } - class ChallengeViewHolder internal constructor( itemView: View, private val viewUserChallengesOnly: Boolean diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/social/ChatRecyclerViewAdapter.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/social/ChatRecyclerViewAdapter.kt index 52e936006..867050fdc 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/social/ChatRecyclerViewAdapter.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/social/ChatRecyclerViewAdapter.kt @@ -12,9 +12,6 @@ import com.habitrpg.android.habitica.models.user.User import com.habitrpg.android.habitica.ui.adapter.BaseRecyclerViewAdapter import com.habitrpg.android.habitica.ui.adapter.DiffCallback import com.habitrpg.android.habitica.ui.viewHolders.ChatRecyclerMessageViewHolder -import io.reactivex.rxjava3.core.BackpressureStrategy -import io.reactivex.rxjava3.core.Flowable -import io.reactivex.rxjava3.subjects.PublishSubject class ChatDiffCallback(oldList: List, newList: List) : DiffCallback(oldList, newList) { @@ -39,11 +36,11 @@ class ChatRecyclerViewAdapter(user: User?, private val isTavern: Boolean) : Base private var expandedMessageId: String? = null var onMessageLike: ((ChatMessage) -> Unit)? = null - private val userLabelClickEvents = PublishSubject.create() - private val deleteMessageEvents = PublishSubject.create() - private val flagMessageEvents = PublishSubject.create() - private val replyMessageEvents = PublishSubject.create() - private val copyMessageEvents = PublishSubject.create() + var onOpenProfile: ((String) -> Unit)? = null + var onDeleteMessage: ((ChatMessage) -> Unit)? = null + var onFlagMessage: ((ChatMessage) -> Unit)? = null + var onReply: ((String) -> Unit)? = null + var onCopyMessage: ((ChatMessage) -> Unit)? = null override fun getDiffCallback( oldList: List, @@ -83,12 +80,12 @@ class ChatRecyclerViewAdapter(user: User?, private val isTavern: Boolean) : Base expandedMessageId == message.id ) chatHolder.onShouldExpand = { expandMessage(message, position) } - chatHolder.onLikeMessage = { onMessageLike?.invoke(it) } - chatHolder.onOpenProfile = { userLabelClickEvents.onNext(it) } - chatHolder.onReply = { replyMessageEvents.onNext(it) } - chatHolder.onCopyMessage = { copyMessageEvents.onNext(it) } - chatHolder.onFlagMessage = { flagMessageEvents.onNext(it) } - chatHolder.onDeleteMessage = { deleteMessageEvents.onNext(it) } + chatHolder.onLikeMessage = onMessageLike + chatHolder.onOpenProfile = onOpenProfile + chatHolder.onReply = onReply + chatHolder.onCopyMessage = onCopyMessage + chatHolder.onFlagMessage = onFlagMessage + chatHolder.onDeleteMessage = onDeleteMessage } } @@ -97,26 +94,6 @@ class ChatRecyclerViewAdapter(user: User?, private val isTavern: Boolean) : Base return if (data[position].isSystemMessage) 0 else 1 } - fun getUserLabelClickFlowable(): Flowable { - return userLabelClickEvents.toFlowable(BackpressureStrategy.DROP) - } - - fun getFlagMessageClickFlowable(): Flowable { - return flagMessageEvents.toFlowable(BackpressureStrategy.DROP) - } - - fun getDeleteMessageFlowable(): Flowable { - return deleteMessageEvents.toFlowable(BackpressureStrategy.DROP) - } - - fun getReplyMessageEvents(): Flowable { - return replyMessageEvents.toFlowable(BackpressureStrategy.DROP) - } - - fun getCopyMessageFlowable(): Flowable { - return copyMessageEvents.toFlowable(BackpressureStrategy.DROP) - } - private fun expandMessage(message: ChatMessage, position: Int?) { expandedMessageId = if (expandedMessageId == message.id) { null diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/social/InboxAdapter.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/social/InboxAdapter.kt index f9db46083..829ea2315 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/social/InboxAdapter.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/social/InboxAdapter.kt @@ -11,20 +11,17 @@ import com.habitrpg.android.habitica.models.user.User import com.habitrpg.android.habitica.ui.viewHolders.ChatRecyclerIntroViewHolder import com.habitrpg.android.habitica.ui.viewHolders.ChatRecyclerMessageViewHolder import com.habitrpg.android.habitica.ui.viewHolders.ChatRecyclerViewHolder -import io.reactivex.rxjava3.core.BackpressureStrategy -import io.reactivex.rxjava3.core.Flowable -import io.reactivex.rxjava3.subjects.PublishSubject class InboxAdapter(private var user: User?, private var replyToUser: Member?) : PagedListAdapter(DIFF_CALLBACK) { private val FIRST_MESSAGE = 0 private val NORMAL_MESSAGE = 1 private var expandedMessageId: String? = null - private val userLabelClickEvents = PublishSubject.create() - private val deleteMessageEvents = PublishSubject.create() - private val flagMessageEvents = PublishSubject.create() - private val replyMessageEvents = PublishSubject.create() - private val copyMessageEvents = PublishSubject.create() + var onOpenProfile: ((String) -> Unit)? = null + var onDeleteMessage: ((ChatMessage) -> Unit)? = null + var onFlagMessage: ((ChatMessage) -> Unit)? = null + var onReply: ((String) -> Unit)? = null + var onCopyMessage: ((ChatMessage) -> Unit)? = null private fun isPositionIntroMessage(position: Int): Boolean { return (position == super.getItemCount() - 1) @@ -52,7 +49,7 @@ class InboxAdapter(private var user: User?, private var replyToUser: Member?) : if (firstMessage) { val introHolder = holder as ChatRecyclerIntroViewHolder introHolder.bind(replyToUser) - introHolder.onOpenProfile = { userLabelClickEvents.onNext(it) } + introHolder.onOpenProfile = onOpenProfile } else { val message: ChatMessage = getItem(position) ?: return val messageHolder = holder as ChatRecyclerMessageViewHolder @@ -63,30 +60,14 @@ class InboxAdapter(private var user: User?, private var replyToUser: Member?) : expandedMessageId == message.id ) messageHolder.onShouldExpand = { expandMessage(message.id, position) } - messageHolder.onOpenProfile = { userLabelClickEvents.onNext(it) } - messageHolder.onReply = { replyMessageEvents.onNext(it) } - messageHolder.onCopyMessage = { copyMessageEvents.onNext(it) } - messageHolder.onFlagMessage = { flagMessageEvents.onNext(it) } - messageHolder.onDeleteMessage = { deleteMessageEvents.onNext(it) } + messageHolder.onOpenProfile = onOpenProfile + messageHolder.onReply = onReply + messageHolder.onCopyMessage = onCopyMessage + messageHolder.onFlagMessage = onFlagMessage + messageHolder.onDeleteMessage = onDeleteMessage } } - fun getUserLabelClickFlowable(): Flowable { - return userLabelClickEvents.toFlowable(BackpressureStrategy.DROP) - } - - fun getFlagMessageClickFlowable(): Flowable { - return flagMessageEvents.toFlowable(BackpressureStrategy.DROP) - } - - fun getDeleteMessageFlowable(): Flowable { - return deleteMessageEvents.toFlowable(BackpressureStrategy.DROP) - } - - fun getCopyMessageFlowable(): Flowable { - return copyMessageEvents.toFlowable(BackpressureStrategy.DROP) - } - private fun expandMessage(id: String, position: Int) { if (isPositionIntroMessage(position)) return diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/tasks/DailiesRecyclerViewHolder.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/tasks/DailiesRecyclerViewHolder.kt index 4dde8bfa9..ed6562879 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/tasks/DailiesRecyclerViewHolder.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/tasks/DailiesRecyclerViewHolder.kt @@ -10,14 +10,14 @@ class DailiesRecyclerViewHolder(layoutResource: Int, viewModel: TasksViewModel) override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { return if (viewType == 0) { DailyViewHolder( - getContentView(parent), { task, direction -> taskScoreEventsSubject.onNext(Pair(task, direction)) }, - { task, item -> checklistItemScoreSubject.onNext(Pair(task, item)) }, + getContentView(parent), { task, direction -> taskScoreEvents?.invoke(task, direction) }, + { task, item -> checklistItemScoreEvents?.invoke(task, item) }, { task -> - taskOpenEventsSubject.onNext(task) + taskOpenEvents?.invoke(task.first, task.second) }, { task -> - brokenTaskEventsSubject.onNext(task) + brokenTaskEvents?.invoke(task) }, viewModel) } else { super.onCreateViewHolder(parent, viewType) diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/tasks/HabitsRecyclerViewAdapter.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/tasks/HabitsRecyclerViewAdapter.kt index f61beed69..61bbde0e2 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/tasks/HabitsRecyclerViewAdapter.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/tasks/HabitsRecyclerViewAdapter.kt @@ -10,13 +10,13 @@ class HabitsRecyclerViewAdapter(layoutResource: Int, viewModel: TasksViewModel) override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { return if (viewType == 0) { HabitViewHolder( - getContentView(parent), { task, direction -> taskScoreEventsSubject.onNext(Pair(task, direction)) }, + getContentView(parent), { task, direction -> taskScoreEvents?.invoke(task, direction) }, { task -> - taskOpenEventsSubject.onNext(task) + taskOpenEvents?.invoke(task.first, task.second) }, { task -> - brokenTaskEventsSubject.onNext(task) + brokenTaskEvents?.invoke(task) }, viewModel) } else { super.onCreateViewHolder(parent, viewType) diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/tasks/RealmBaseTasksRecyclerViewAdapter.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/tasks/RealmBaseTasksRecyclerViewAdapter.kt index 801e10a1c..eca730ce2 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/tasks/RealmBaseTasksRecyclerViewAdapter.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/tasks/RealmBaseTasksRecyclerViewAdapter.kt @@ -19,10 +19,6 @@ import com.habitrpg.android.habitica.ui.views.HabiticaIconsHelper import com.habitrpg.common.habitica.extensions.dpToPx import com.habitrpg.common.habitica.extensions.layoutInflater import com.habitrpg.shared.habitica.models.responses.TaskDirection -import io.reactivex.rxjava3.core.BackpressureStrategy -import io.reactivex.rxjava3.core.Flowable -import io.reactivex.rxjava3.functions.Action -import io.reactivex.rxjava3.subjects.PublishSubject import io.realm.OrderedRealmCollection abstract class RealmBaseTasksRecyclerViewAdapter( @@ -50,18 +46,12 @@ abstract class RealmBaseTasksRecyclerViewAdapter( } } - private var errorButtonEventsSubject: PublishSubject = PublishSubject.create() - override val errorButtonEvents: Flowable = errorButtonEventsSubject.toFlowable(BackpressureStrategy.DROP) - protected var taskScoreEventsSubject: PublishSubject> = PublishSubject.create() - override val taskScoreEvents: Flowable> = taskScoreEventsSubject.toFlowable(BackpressureStrategy.DROP) - protected var checklistItemScoreSubject: PublishSubject> = PublishSubject.create() - override val checklistItemScoreEvents: Flowable> = checklistItemScoreSubject.toFlowable(BackpressureStrategy.DROP) - protected var taskOpenEventsSubject: PublishSubject> = PublishSubject.create() - override val taskOpenEvents: Flowable> = taskOpenEventsSubject.toFlowable(BackpressureStrategy.DROP) - protected var brokenTaskEventsSubject: PublishSubject = PublishSubject.create() - override val brokenTaskEvents: Flowable = brokenTaskEventsSubject.toFlowable(BackpressureStrategy.DROP) - protected var adventureGuideOpenSubject: PublishSubject = PublishSubject.create() - override val adventureGuideOpenEvents: Flowable = adventureGuideOpenSubject.toFlowable(BackpressureStrategy.DROP) + override var errorButtonEvents: ((String) -> Unit)? = null + override var taskScoreEvents: ((Task, TaskDirection) -> Unit)? = null + override var checklistItemScoreEvents: ((Task, ChecklistItem) -> Unit)? = null + override var taskOpenEvents: ((Task, View) -> Unit)? = null + override var brokenTaskEvents: ((Task) -> Unit)? = null + override var adventureGuideOpenEvents: ((Boolean) -> Unit)? = null override fun getItemId(index: Int): Long = index.toLong() @@ -82,11 +72,11 @@ abstract class RealmBaseTasksRecyclerViewAdapter( holder.userID = user?.id holder.isLocked = !viewModel.canScoreTask(item) holder.bind(item, position, taskDisplayMode, viewModel.ownerID.value) - holder.errorButtonClicked = Action { - errorButtonEventsSubject.onNext("") + holder.errorButtonClicked = { + errorButtonEvents?.invoke("") } } else if (holder is AdventureGuideViewHolder) { - holder.itemView.setOnClickListener { adventureGuideOpenSubject.onNext(true) } + holder.itemView.setOnClickListener { adventureGuideOpenEvents?.invoke(true) } user?.let { holder.update(it) } } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/tasks/RewardsRecyclerViewAdapter.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/tasks/RewardsRecyclerViewAdapter.kt index cdd5ee55f..3eae1407f 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/tasks/RewardsRecyclerViewAdapter.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/tasks/RewardsRecyclerViewAdapter.kt @@ -14,9 +14,6 @@ import com.habitrpg.android.habitica.ui.viewHolders.ShopItemViewHolder import com.habitrpg.android.habitica.ui.viewHolders.tasks.RewardViewHolder import com.habitrpg.android.habitica.ui.viewmodels.TasksViewModel import com.habitrpg.shared.habitica.models.responses.TaskDirection -import io.reactivex.rxjava3.core.BackpressureStrategy -import io.reactivex.rxjava3.core.Flowable -import io.reactivex.rxjava3.subjects.PublishSubject class RewardsRecyclerViewAdapter( private var customRewards: List?, @@ -34,19 +31,13 @@ class RewardsRecyclerViewAdapter( override var showAdventureGuide: Boolean = false private var inAppRewards: List? = null - private val errorButtonEventsSubject: PublishSubject = PublishSubject.create() - override val errorButtonEvents: Flowable = errorButtonEventsSubject.toFlowable(BackpressureStrategy.DROP) - private var taskScoreEventsSubject: PublishSubject> = PublishSubject.create() - override val taskScoreEvents: Flowable> = taskScoreEventsSubject.toFlowable(BackpressureStrategy.LATEST) - private var checklistItemScoreSubject: PublishSubject> = PublishSubject.create() - override val checklistItemScoreEvents: Flowable> = checklistItemScoreSubject.toFlowable(BackpressureStrategy.DROP) - private var taskOpenEventsSubject: PublishSubject> = PublishSubject.create() - override val taskOpenEvents: Flowable> = taskOpenEventsSubject.toFlowable(BackpressureStrategy.LATEST) - private var brokenTaskEventsSubject: PublishSubject = PublishSubject.create() - override val brokenTaskEvents: Flowable = brokenTaskEventsSubject.toFlowable(BackpressureStrategy.DROP) - override val adventureGuideOpenEvents: Flowable? = null - private var purchaseCardSubject: PublishSubject = PublishSubject.create() - val purchaseCardEvents: Flowable = purchaseCardSubject.toFlowable(BackpressureStrategy.LATEST) + override var errorButtonEvents: ((String) -> Unit)? = null + override var taskScoreEvents: ((Task, TaskDirection) -> Unit)? = null + override var checklistItemScoreEvents: ((Task, ChecklistItem) -> Unit)? = null + override var taskOpenEvents: ((Task, View) -> Unit)? = null + override var brokenTaskEvents: ((Task) -> Unit)? = null + override var adventureGuideOpenEvents: ((Boolean) -> Unit)? = null + var purchaseCardEvents: ((ShopItem) -> Unit)? = null override var taskDisplayMode: String = "standard" set(value) { @@ -78,16 +69,16 @@ class RewardsRecyclerViewAdapter( getContentView(parent), { task, direction -> if (task.value <= (user?.stats?.gp ?: 0.0)) { - taskScoreEventsSubject.onNext(Pair(task, direction)) + taskScoreEvents?.invoke(task, direction) } }, - { task -> taskOpenEventsSubject.onNext(task) }, { + { task -> taskOpenEvents?.invoke(task.first, task.second) }, { task -> - brokenTaskEventsSubject.onNext(task) + brokenTaskEvents?.invoke(task) }, viewModel) } else { val viewHolder = ShopItemViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.row_shopitem, parent, false)) - viewHolder.purchaseCardAction = { purchaseCardSubject.onNext(it) } + viewHolder.purchaseCardAction = { purchaseCardEvents?.invoke(it) } viewHolder } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/tasks/TaskRecyclerViewAdapter.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/tasks/TaskRecyclerViewAdapter.kt index 73e650fa3..fd240c504 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/tasks/TaskRecyclerViewAdapter.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/tasks/TaskRecyclerViewAdapter.kt @@ -5,14 +5,13 @@ import com.habitrpg.android.habitica.models.tasks.ChecklistItem import com.habitrpg.android.habitica.models.tasks.Task import com.habitrpg.android.habitica.models.user.User import com.habitrpg.shared.habitica.models.responses.TaskDirection -import io.reactivex.rxjava3.core.Flowable interface TaskRecyclerViewAdapter { var user: User? var showAdventureGuide: Boolean var data: List - val errorButtonEvents: Flowable + var errorButtonEvents: ((String) -> Unit)? var taskDisplayMode: String @@ -24,9 +23,9 @@ interface TaskRecyclerViewAdapter { fun updateUnfilteredData(data: List?) - val taskScoreEvents: Flowable> - val checklistItemScoreEvents: Flowable> - val taskOpenEvents: Flowable> - val brokenTaskEvents: Flowable - val adventureGuideOpenEvents: Flowable? + var taskScoreEvents: ((Task, TaskDirection) -> Unit)? + var checklistItemScoreEvents: ((Task, ChecklistItem) -> Unit)? + var taskOpenEvents: ((Task, View) -> Unit)? + var brokenTaskEvents: ((Task) -> Unit)? + var adventureGuideOpenEvents: ((Boolean) -> Unit)? } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/tasks/TodosRecyclerViewAdapter.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/tasks/TodosRecyclerViewAdapter.kt index 5be12d9b6..da757abc4 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/tasks/TodosRecyclerViewAdapter.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/tasks/TodosRecyclerViewAdapter.kt @@ -10,14 +10,14 @@ class TodosRecyclerViewAdapter(layoutResource: Int, viewModel: TasksViewModel) : override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { return if (viewType == 0) { TodoViewHolder( - getContentView(parent), { task, direction -> taskScoreEventsSubject.onNext(Pair(task, direction)) }, - { task, item -> checklistItemScoreSubject.onNext(Pair(task, item)) }, + getContentView(parent), { task, direction -> taskScoreEvents?.invoke(task, direction) }, + { task, item -> checklistItemScoreEvents?.invoke(task, item) }, { task -> - taskOpenEventsSubject.onNext(task) + taskOpenEvents?.invoke(task.first, task.second) }, { task -> - brokenTaskEventsSubject.onNext(task) + brokenTaskEvents?.invoke(task) }, viewModel) } else { super.onCreateViewHolder(parent, viewType) diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/BaseDialogFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/BaseDialogFragment.kt index ff6881ffe..8b895d801 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/BaseDialogFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/BaseDialogFragment.kt @@ -13,7 +13,6 @@ import com.habitrpg.android.habitica.data.TutorialRepository import com.habitrpg.android.habitica.helpers.AmplitudeManager import com.habitrpg.android.habitica.helpers.launchCatching import com.habitrpg.android.habitica.ui.activities.MainActivity -import io.reactivex.rxjava3.disposables.CompositeDisposable import kotlinx.coroutines.delay import kotlinx.coroutines.flow.firstOrNull import javax.inject.Inject @@ -32,8 +31,6 @@ abstract class BaseDialogFragment : BottomSheetDialogFragment( protected var tutorialCanBeDeferred = true var tutorialTexts: MutableList = ArrayList() - protected var compositeSubscription: CompositeDisposable = CompositeDisposable() - open val displayedClassName: String? get() = this.javaClass.simpleName @@ -51,8 +48,6 @@ abstract class BaseDialogFragment : BottomSheetDialogFragment( container: ViewGroup?, savedInstanceState: Bundle? ): View? { - compositeSubscription = CompositeDisposable() - val additionalData = HashMap() additionalData["page"] = this.javaClass.simpleName AmplitudeManager.sendEvent("navigate", AmplitudeManager.EVENT_CATEGORY_NAVIGATION, AmplitudeManager.EVENT_HITTYPE_PAGEVIEW, additionalData) @@ -89,10 +84,6 @@ abstract class BaseDialogFragment : BottomSheetDialogFragment( override fun onDestroyView() { binding = null - if (!compositeSubscription.isDisposed) { - compositeSubscription.dispose() - } - super.onDestroyView() } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/BaseFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/BaseFragment.kt index 9a9653b41..df1c81f81 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/BaseFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/BaseFragment.kt @@ -13,7 +13,6 @@ import com.habitrpg.android.habitica.data.TutorialRepository import com.habitrpg.android.habitica.helpers.launchCatching import com.habitrpg.android.habitica.proxy.AnalyticsManager import com.habitrpg.android.habitica.ui.activities.MainActivity -import io.reactivex.rxjava3.disposables.CompositeDisposable import kotlinx.coroutines.delay import kotlinx.coroutines.flow.firstOrNull import javax.inject.Inject @@ -34,8 +33,6 @@ abstract class BaseFragment : Fragment() { protected var tutorialCanBeDeferred = true var tutorialTexts: List = ArrayList() - protected var compositeSubscription: CompositeDisposable = CompositeDisposable() - var shouldInitializeComponent = true open val displayedClassName: String? @@ -60,8 +57,6 @@ abstract class BaseFragment : Fragment() { container: ViewGroup?, savedInstanceState: Bundle? ): View? { - compositeSubscription = CompositeDisposable() - binding = createBinding(inflater, container) return binding?.root } @@ -92,10 +87,6 @@ abstract class BaseFragment : Fragment() { override fun onDestroyView() { binding = null - if (!compositeSubscription.isDisposed) { - compositeSubscription.dispose() - } - super.onDestroyView() } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/NavigationDrawerFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/NavigationDrawerFragment.kt index ea8444cfe..7d2141e62 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/NavigationDrawerFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/NavigationDrawerFragment.kt @@ -30,7 +30,6 @@ import com.habitrpg.android.habitica.extensions.getMinuteOrSeconds import com.habitrpg.android.habitica.extensions.getRemainingString import com.habitrpg.android.habitica.extensions.getShortRemainingString import com.habitrpg.android.habitica.helpers.AppConfigManager -import com.habitrpg.android.habitica.helpers.ExceptionHandler import com.habitrpg.android.habitica.helpers.MainNavigationController import com.habitrpg.android.habitica.helpers.launchCatching import com.habitrpg.android.habitica.models.WorldStateEvent @@ -47,7 +46,6 @@ import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel import com.habitrpg.android.habitica.ui.viewmodels.NotificationsViewModel import com.habitrpg.android.habitica.ui.views.HabiticaSnackbar import com.habitrpg.common.habitica.extensions.getThemeColor -import io.reactivex.rxjava3.disposables.CompositeDisposable import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.delay @@ -97,8 +95,6 @@ class NavigationDrawerFragment : DialogFragment() { private lateinit var adapter: NavigationDrawerAdapter - private var subscriptions: CompositeDisposable? = null - val isDrawerOpen: Boolean get() = drawerLayout?.isDrawerOpen(GravityCompat.START) ?: false @@ -114,7 +110,6 @@ class NavigationDrawerFragment : DialogFragment() { } else { NavigationDrawerAdapter(0, 0) } - subscriptions = CompositeDisposable() HabiticaBaseApplication.userComponent?.inject(this) super.onCreate(savedInstanceState) @@ -145,25 +140,15 @@ class NavigationDrawerFragment : DialogFragment() { false initializeMenuItems() - subscriptions?.add( - adapter.getItemSelectionEvents().subscribe( - { + adapter.itemSelectedEvents = { setSelection(it.transitionId, it.bundle, true) - }, - ExceptionHandler.rx() - ) - ) - subscriptions?.add( - adapter.getPromoCloseEvents().subscribe( - { + } + adapter.promoClosedSubject = { sharedPreferences.edit { putBoolean("hide$it", true) } updatePromo() - }, - ExceptionHandler.rx() - ) - ) + } lifecycleScope.launchCatching { contentRepository.getWorldState() @@ -354,7 +339,6 @@ class NavigationDrawerFragment : DialogFragment() { } override fun onDestroy() { - subscriptions?.dispose() socialRepository.close() inventoryRepository.close() userRepository.close() diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/items/ItemDialogFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/items/ItemDialogFragment.kt index d89dce1ee..fafd3ead9 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/items/ItemDialogFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/items/ItemDialogFragment.kt @@ -36,7 +36,6 @@ 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 import kotlinx.coroutines.flow.onEach @@ -45,8 +44,6 @@ import javax.inject.Inject class ItemDialogFragment : BaseDialogFragment() { - var parentSubscription: CompositeDisposable? = null - @Inject lateinit var inventoryRepository: InventoryRepository @Inject diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/items/ItemRecyclerFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/items/ItemRecyclerFragment.kt index 92dbffea9..b654c31c0 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/items/ItemRecyclerFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/items/ItemRecyclerFragment.kt @@ -190,7 +190,6 @@ class ItemRecyclerFragment : BaseFragment(), SwipeRefreshL } fragment.isHatching = true fragment.isFeeding = false - fragment.parentSubscription = compositeSubscription parentFragmentManager.let { fragment.show(it, "hatchingDialog") } } 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 f54215d23..3003204cd 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 @@ -93,9 +93,9 @@ open class ShopFragment : BaseMainFragment() adapter?.context = context binding?.recyclerView?.adapter = adapter binding?.recyclerView?.itemAnimator = SafeDefaultItemAnimator() - adapter?.changeClassEvents?.subscribe { + adapter?.changeClassEvents = { showClassChangeDialog(it) - }?.let { compositeSubscription.add(it) } + } } if (binding?.recyclerView?.layoutManager == null) { diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/stable/PetDetailRecyclerFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/stable/PetDetailRecyclerFragment.kt index abfc35864..a085a2a91 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/stable/PetDetailRecyclerFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/stable/PetDetailRecyclerFragment.kt @@ -11,7 +11,6 @@ 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.extensions.subscribeWithErrorHandler import com.habitrpg.android.habitica.helpers.ExceptionHandler import com.habitrpg.android.habitica.helpers.launchCatching import com.habitrpg.android.habitica.interactors.FeedPetUseCase @@ -243,7 +242,6 @@ class PetDetailRecyclerFragment : fragment.isHatching = false fragment.itemType = "food" fragment.itemTypeText = getString(R.string.food) - fragment.parentSubscription = compositeSubscription parentFragmentManager.let { fragment.show(it, "feedDialog") } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/BasePreferencesFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/BasePreferencesFragment.kt index 1a2d9c3d9..ee1baa742 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/BasePreferencesFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/BasePreferencesFragment.kt @@ -9,7 +9,6 @@ import com.habitrpg.android.habitica.R import com.habitrpg.android.habitica.data.UserRepository import com.habitrpg.android.habitica.models.user.User import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel -import io.reactivex.rxjava3.disposables.CompositeDisposable import javax.inject.Inject abstract class BasePreferencesFragment : PreferenceFragmentCompat() { @@ -21,8 +20,6 @@ abstract class BasePreferencesFragment : PreferenceFragmentCompat() { internal open var user: User? = null - internal val compositeSubscription = CompositeDisposable() - override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, @@ -34,7 +31,6 @@ abstract class BasePreferencesFragment : PreferenceFragmentCompat() { override fun onDestroy() { userRepository.close() - compositeSubscription.dispose() super.onDestroy() } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/setup/WelcomeFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/setup/WelcomeFragment.kt index d4721ab31..a196ddb15 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/setup/WelcomeFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/setup/WelcomeFragment.kt @@ -18,7 +18,6 @@ import com.habitrpg.android.habitica.helpers.ExceptionHandler import com.habitrpg.android.habitica.helpers.launchCatching import com.habitrpg.android.habitica.ui.fragments.BaseFragment import com.habitrpg.android.habitica.ui.views.HabiticaIconsHelper -import io.reactivex.rxjava3.subjects.PublishSubject import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.filterNotNull @@ -29,7 +28,7 @@ import javax.inject.Inject class WelcomeFragment : BaseFragment() { - val nameValidEvents = PublishSubject.create() + var onNameValid: ((Boolean?) -> Unit)? = null @Inject lateinit var userRepository: UserRepository @@ -132,7 +131,7 @@ class WelcomeFragment : BaseFragment() { binding?.issuesTextView?.visibility = View.VISIBLE binding?.issuesTextView?.text = it?.issues?.joinToString("\n") } - nameValidEvents.onNext(it?.isUsable) + onNameValid?.invoke(it?.isUsable) } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/skills/SkillsFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/skills/SkillsFragment.kt index ba043322a..1166cb6c3 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/skills/SkillsFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/skills/SkillsFragment.kt @@ -13,7 +13,6 @@ import androidx.lifecycle.lifecycleScope 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.ExceptionHandler import com.habitrpg.android.habitica.helpers.launchCatching import com.habitrpg.android.habitica.models.Skill @@ -52,7 +51,7 @@ class SkillsFragment : BaseMainFragment() { savedInstanceState: Bundle? ): View? { adapter = SkillsRecyclerViewAdapter() - adapter?.useSkillEvents?.subscribeWithErrorHandler { onSkillSelected(it) }?.let { compositeSubscription.add(it) } + adapter?.onUseSkill = { onSkillSelected(it) } this.tutorialStepIdentifier = "skills" this.tutorialTexts = listOf(getString(R.string.tutorial_skills)) diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/ChatFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/ChatFragment.kt index 62347b671..9f783fa7d 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/ChatFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/ChatFragment.kt @@ -7,13 +7,13 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.LinearLayoutManager import com.habitrpg.android.habitica.MainNavDirections 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.models.social.ChatMessage import com.habitrpg.android.habitica.ui.activities.FullProfileActivity @@ -25,11 +25,10 @@ import com.habitrpg.android.habitica.ui.viewmodels.GroupViewModel import com.habitrpg.android.habitica.ui.views.HabiticaSnackbar.Companion.showSnackbar import com.habitrpg.android.habitica.ui.views.HabiticaSnackbar.SnackbarDisplayType 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 java.util.concurrent.TimeUnit +import kotlinx.coroutines.delay import javax.inject.Inject +import kotlin.time.DurationUnit +import kotlin.time.toDuration class ChatFragment() : BaseFragment() { @@ -52,7 +51,6 @@ class ChatFragment() : BaseFragment() { private var navigatedOnceToFragment = false private var isScrolledToBottom = true private var isFirstRefresh = true - private var refreshDisposable: Disposable? = null var autocompleteContext: String = "" override fun injectFragment(component: UserComponent) { @@ -69,16 +67,11 @@ class ChatFragment() : BaseFragment() { chatAdapter = ChatRecyclerViewAdapter(null, true) chatAdapter?.let { adapter -> - compositeSubscription.add( - adapter.getUserLabelClickFlowable().subscribe( - { userId -> FullProfileActivity.open(userId) }, - ExceptionHandler.rx() - ) - ) - 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.onOpenProfile = { userId -> FullProfileActivity.open(userId) } + adapter.onDeleteMessage = { this.showDeleteConfirmationDialog(it) } + adapter.onFlagMessage = { this.showFlagConfirmationDialog(it) } + adapter.onReply = { setReplyTo(it) } + adapter.onCopyMessage = { this.copyMessageToClipboard(it) } adapter.onMessageLike = { viewModel?.likeMessage(it) } } @@ -108,48 +101,18 @@ class ChatFragment() : BaseFragment() { } viewModel?.user?.observe( - viewLifecycleOwner, - { - chatAdapter?.user = it - binding?.chatBarView?.hasAcceptedGuidelines = it?.flags?.communityGuidelinesAccepted == true - } - ) - } - - override fun onDestroyView() { - super.onDestroyView() - stopAutoRefreshing() - } - - override fun onResume() { - super.onResume() - startAutoRefreshing() - } - - override fun onPause() { - super.onPause() - stopAutoRefreshing() - } - - private fun startAutoRefreshing() { - if (refreshDisposable?.isDisposed != true) { - refreshDisposable?.dispose() + viewLifecycleOwner + ) { + chatAdapter?.user = it + binding?.chatBarView?.hasAcceptedGuidelines = + it?.flags?.communityGuidelinesAccepted == true } - refreshDisposable = Observable.interval(30, TimeUnit.SECONDS) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe( - { - refresh() - }, - ExceptionHandler.rx() - ) - refresh() - } - private fun stopAutoRefreshing() { - if (refreshDisposable?.isDisposed != true) { - refreshDisposable?.dispose() - refreshDisposable = null + lifecycleScope.launchWhenResumed { + while (true) { + refresh() + delay(30.toDuration(DurationUnit.SECONDS)) + } } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/InboxMessageListFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/InboxMessageListFragment.kt index dd37586d8..76baff135 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/InboxMessageListFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/InboxMessageListFragment.kt @@ -35,12 +35,8 @@ import com.habitrpg.android.habitica.ui.viewmodels.InboxViewModelFactory import com.habitrpg.android.habitica.ui.views.HabiticaSnackbar import com.habitrpg.android.habitica.ui.views.HabiticaSnackbar.Companion.showSnackbar 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 @@ -65,7 +61,6 @@ class InboxMessageListFragment : BaseMainFragment - compositeSubscription.add( - adapter.getUserLabelClickFlowable().subscribe( - { + adapter.onOpenProfile = { 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())) + } + adapter.onDeleteMessage = { showDeleteConfirmationDialog(it) } + adapter.onFlagMessage = { showFlagConfirmationDialog(it) } + adapter.onCopyMessage = { copyMessageToClipboard(it) } } } @@ -132,13 +122,19 @@ class InboxMessageListFragment : BaseMainFragment() super.onViewCreated(view, savedInstanceState) challengeAdapter = ChallengesListViewAdapter(viewUserChallengesOnly, userId) - challengeAdapter?.getOpenDetailFragmentFlowable() - ?.subscribe({ openDetailFragment(it) }, ExceptionHandler.rx()) - ?.let { compositeSubscription.add(it) } - + challengeAdapter?.onOpenChallengeFragment = { openDetailFragment(it) } binding?.refreshLayout?.setOnRefreshListener(this) if (viewUserChallengesOnly) { diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/tasks/RewardsRecyclerviewFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/tasks/RewardsRecyclerviewFragment.kt index 45f7fcd56..cd86dd2e4 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/tasks/RewardsRecyclerviewFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/tasks/RewardsRecyclerviewFragment.kt @@ -13,7 +13,6 @@ import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.LinearLayoutManager import com.habitrpg.android.habitica.R -import com.habitrpg.android.habitica.extensions.subscribeWithErrorHandler import com.habitrpg.android.habitica.helpers.ExceptionHandler import com.habitrpg.android.habitica.helpers.launchCatching import com.habitrpg.android.habitica.models.shops.ShopItem @@ -68,15 +67,12 @@ class RewardsRecyclerviewFragment : TaskRecyclerViewFragment() { } } - (recyclerAdapter as? RewardsRecyclerViewAdapter)?.purchaseCardEvents?.subscribe( - { + (recyclerAdapter as? RewardsRecyclerViewAdapter)?.purchaseCardEvents = { selectedCard = it val intent = Intent(activity, SkillMemberActivity::class.java) cardSelectedResult.launch(intent) - }, - ExceptionHandler.rx() - )?.let { compositeSubscription.add(it) } - recyclerAdapter?.brokenTaskEvents?.subscribeWithErrorHandler { showBrokenChallengeDialog(it) }?.let { compositeSubscription.add(it) } + } + recyclerAdapter?.brokenTaskEvents = { showBrokenChallengeDialog(it) } viewModel.user.observe(viewLifecycleOwner) { (recyclerAdapter as? RewardsRecyclerViewAdapter)?.user = 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 f964224de..2ca698d74 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 @@ -22,7 +22,6 @@ import com.habitrpg.android.habitica.data.UserRepository import com.habitrpg.android.habitica.databinding.FragmentRefreshRecyclerviewBinding import com.habitrpg.android.habitica.extensions.observeOnce import com.habitrpg.android.habitica.extensions.setScaledPadding -import com.habitrpg.android.habitica.extensions.subscribeWithErrorHandler import com.habitrpg.android.habitica.helpers.AppConfigManager import com.habitrpg.android.habitica.helpers.ExceptionHandler import com.habitrpg.android.habitica.helpers.HapticFeedbackManager @@ -51,7 +50,6 @@ import com.habitrpg.common.habitica.helpers.EmptyItem import com.habitrpg.shared.habitica.models.responses.TaskDirection import com.habitrpg.shared.habitica.models.responses.TaskScoringResult import com.habitrpg.shared.habitica.models.tasks.TaskType -import io.reactivex.rxjava3.disposables.CompositeDisposable import kotlinx.coroutines.Job import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.distinctUntilChangedBy @@ -79,7 +77,6 @@ open class TaskRecyclerViewFragment : BaseFragment val adapter: BaseRecyclerViewAdapter<*, *>? = when (this.taskType) { TaskType.HABIT -> HabitsRecyclerViewAdapter(R.layout.habit_item_card, viewModel) @@ -142,33 +135,28 @@ open class TaskRecyclerViewFragment : BaseFragment notificationsManager.dismissTaskNotification(it1, it.first) } - }?.subscribeWithErrorHandler { scoreTask(it.first, it.second) } - ?.let { recyclerSubscription.add(it) } - recyclerAdapter?.checklistItemScoreEvents?.subscribeWithErrorHandler { - scoreChecklistItem(it.first, it.second) - }?.let { recyclerSubscription.add(it) } - recyclerAdapter?.brokenTaskEvents?.subscribeWithErrorHandler { showBrokenChallengeDialog(it) } - ?.let { recyclerSubscription.add(it) } - recyclerAdapter?.adventureGuideOpenEvents?.subscribeWithErrorHandler { + } + recyclerAdapter?.taskOpenEvents = { task, view -> + openTaskForm(task) + } + recyclerAdapter?.taskScoreEvents = { task, direction -> + playSound(direction) + context?.let { it1 -> notificationsManager.dismissTaskNotification(it1, task) } + scoreTask(task, direction) + } + recyclerAdapter?.checklistItemScoreEvents = { task, item -> + scoreChecklistItem(task, item) + } + recyclerAdapter?.brokenTaskEvents = { showBrokenChallengeDialog(it) } + recyclerAdapter?.adventureGuideOpenEvents = { MainNavigationController.navigate( R.id.adventureGuideActivity ) - }?.let { recyclerSubscription.add(it) } + } viewModel.ownerID.observe(viewLifecycleOwner) { canEditTasks = viewModel.isPersonalBoard diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/ChatRecyclerViewHolder.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/ChatRecyclerViewHolder.kt index 517fbed3f..ce016af29 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/ChatRecyclerViewHolder.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/ChatRecyclerViewHolder.kt @@ -13,7 +13,6 @@ import com.habitrpg.android.habitica.databinding.ChatItemBinding import com.habitrpg.android.habitica.databinding.TavernChatIntroItemBinding import com.habitrpg.android.habitica.extensions.getAgoString import com.habitrpg.android.habitica.extensions.setScaledPadding -import com.habitrpg.android.habitica.helpers.ExceptionHandler import com.habitrpg.android.habitica.models.members.Member import com.habitrpg.android.habitica.models.social.ChatMessage import com.habitrpg.android.habitica.models.user.User @@ -24,9 +23,10 @@ import com.habitrpg.common.habitica.extensions.DataBindingUtils import com.habitrpg.common.habitica.extensions.dpToPx import com.habitrpg.common.habitica.helpers.MarkdownParser import com.habitrpg.common.habitica.helpers.setParsedMarkdown -import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers -import io.reactivex.rxjava3.core.Maybe -import io.reactivex.rxjava3.schedulers.Schedulers +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.MainScope +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext open class ChatRecyclerViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) @@ -185,17 +185,13 @@ class ChatRecyclerMessageViewHolder( binding.messageText.setParsedMarkdown(chatMessage?.parsedText) if (msg.parsedText == null) { binding.messageText.text = chatMessage?.text - Maybe.just(chatMessage?.text ?: "") - .map { MarkdownParser.parseMarkdown(it) } - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe( - { parsedText -> - chatMessage?.parsedText = parsedText - binding.messageText.setParsedMarkdown(parsedText) - }, - ExceptionHandler.rx() - ) + MainScope().launch(Dispatchers.IO) { + val parsedText = MarkdownParser.parseMarkdown(chatMessage?.text ?: "") + withContext(Dispatchers.Main) { + chatMessage?.parsedText = parsedText + binding.messageText.setParsedMarkdown(parsedText) + } + } } val username = user?.formattedUsername diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/tasks/BaseTaskViewHolder.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/tasks/BaseTaskViewHolder.kt index 1af36d06d..81b1e558e 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/tasks/BaseTaskViewHolder.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/tasks/BaseTaskViewHolder.kt @@ -14,7 +14,6 @@ import android.widget.TextView import androidx.core.content.ContextCompat import com.habitrpg.android.habitica.R import com.habitrpg.android.habitica.helpers.GroupPlanInfoProvider -import com.habitrpg.android.habitica.helpers.ExceptionHandler import com.habitrpg.android.habitica.models.tasks.Task import com.habitrpg.android.habitica.ui.viewHolders.BindableViewHolder import com.habitrpg.android.habitica.ui.views.EllipsisTextView @@ -23,13 +22,10 @@ import com.habitrpg.common.habitica.extensions.getThemeColor import com.habitrpg.common.habitica.helpers.MarkdownParser import com.habitrpg.common.habitica.helpers.setParsedMarkdown import com.habitrpg.shared.habitica.models.responses.TaskDirection -import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers -import io.reactivex.rxjava3.core.Single -import io.reactivex.rxjava3.functions.Action -import io.reactivex.rxjava3.schedulers.Schedulers import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.MainScope import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext abstract class BaseTaskViewHolder constructor( itemView: View, @@ -42,7 +38,7 @@ abstract class BaseTaskViewHolder constructor( var task: Task? = null var movingFromPosition: Int? = null - var errorButtonClicked: Action? = null + var errorButtonClicked: (() -> Unit)? = null var userID: String? = null var isLocked = false protected var context: Context @@ -101,7 +97,7 @@ abstract class BaseTaskViewHolder constructor( titleTextView.setOnClickListener { onTouch(it, null) } notesTextView?.setOnClickListener { onTouch(it, null) } - errorIconView?.setOnClickListener { errorButtonClicked?.run() } + errorIconView?.setOnClickListener { errorButtonClicked?.invoke() } notesTextView?.movementMethod = LinkMovementMethod.getInstance() titleTextView.movementMethod = LinkMovementMethod.getInstance() @@ -169,17 +165,13 @@ abstract class BaseTaskViewHolder constructor( } else { titleTextView.text = data.text if (data.text.isNotEmpty()) { - Single.just(data.text) - .map { MarkdownParser.parseMarkdown(it) } - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe( - { parsedText -> - data.parsedText = parsedText - titleTextView.setParsedMarkdown(parsedText) - }, - ExceptionHandler.rx() - ) + scope.launch(Dispatchers.IO) { + val parsedText = MarkdownParser.parseMarkdown(data.text) + withContext(Dispatchers.Main) { + data.parsedText = parsedText + titleTextView.setParsedMarkdown(parsedText) + } + } } } if (displayMode != "minimal") { @@ -196,17 +188,13 @@ abstract class BaseTaskViewHolder constructor( if (notes.isEmpty()) { return@let } - Single.just(notes) - .map { MarkdownParser.parseMarkdown(it) } - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe( - { parsedNotes -> - data.parsedNotes = parsedNotes - notesTextView?.setParsedMarkdown(parsedNotes) - }, - ExceptionHandler.rx() - ) + scope.launch(Dispatchers.IO) { + val parsedNotes = MarkdownParser.parseMarkdown(notes) + withContext(Dispatchers.Main) { + data.parsedNotes = parsedNotes + notesTextView?.setParsedMarkdown(parsedNotes) + } + } } } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/tasks/ChecklistedViewHolder.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/tasks/ChecklistedViewHolder.kt index 499445b24..cae10b607 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/tasks/ChecklistedViewHolder.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/tasks/ChecklistedViewHolder.kt @@ -22,9 +22,10 @@ import com.habitrpg.common.habitica.helpers.MarkdownParser import com.habitrpg.common.habitica.helpers.setParsedMarkdown import com.habitrpg.shared.habitica.models.responses.TaskDirection import com.habitrpg.shared.habitica.models.tasks.TaskType -import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers -import io.reactivex.rxjava3.core.Observable -import io.reactivex.rxjava3.schedulers.Schedulers +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.MainScope +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext abstract class ChecklistedViewHolder( itemView: View, @@ -131,16 +132,12 @@ abstract class ChecklistedViewHolder( textView?.text = item.text textView?.setTextColor(ContextCompat.getColor(context, if (item.completed) R.color.text_dimmed else R.color.text_secondary)) if (item.text != null) { - Observable.just(item.text ?: "") - .map { MarkdownParser.parseMarkdown(it) } - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe( - { - textView?.setParsedMarkdown(it) - }, - ExceptionHandler.rx() - ) + MainScope().launch(Dispatchers.IO) { + val parsedText = MarkdownParser.parseMarkdown(item.text ?: "") + withContext(Dispatchers.Main) { + textView?.setParsedMarkdown(parsedText) + } + } } val checkmark = itemView?.findViewById(R.id.checkmark) checkmark?.drawable?.setTintMode(PorterDuff.Mode.SRC_ATOP) diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/AuthenticationViewModel.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/AuthenticationViewModel.kt index dce3fc33d..c73bd6e2f 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/AuthenticationViewModel.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/AuthenticationViewModel.kt @@ -33,7 +33,6 @@ import com.habitrpg.common.habitica.api.HostConfig import com.habitrpg.common.habitica.helpers.KeyHelper import com.habitrpg.common.habitica.models.auth.UserAuthResponse import com.willowtreeapps.signinwithapplebutton.SignInWithAppleConfiguration -import io.reactivex.rxjava3.disposables.CompositeDisposable import kotlinx.coroutines.MainScope import javax.inject.Inject @@ -52,8 +51,6 @@ class AuthenticationViewModel() { @JvmField var keyHelper: KeyHelper? = null - private var compositeSubscription = CompositeDisposable() - var googleEmail: String? = null init { diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/MainUserViewModel.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/MainUserViewModel.kt index a141c608d..ba0b0ec5b 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/MainUserViewModel.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/MainUserViewModel.kt @@ -9,7 +9,6 @@ import com.habitrpg.android.habitica.models.TeamPlan import com.habitrpg.android.habitica.models.invitations.PartyInvite import com.habitrpg.android.habitica.models.members.Member import com.habitrpg.android.habitica.models.user.User -import io.reactivex.rxjava3.disposables.CompositeDisposable import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.MainScope import kotlinx.coroutines.flow.MutableStateFlow @@ -55,11 +54,8 @@ class MainUserViewModel(private val providedUserID: String, val userRepository: fun onCleared() { userRepository.close() - disposable.clear() } - internal val disposable = CompositeDisposable() - fun updateUser(path: String, value: Any) { MainScope().launch(ExceptionHandler.coroutine()) { userRepository.updateUser(path, value) diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/TasksViewModel.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/TasksViewModel.kt index 2a63d6f42..72f6b0510 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/TasksViewModel.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/TasksViewModel.kt @@ -20,7 +20,6 @@ import com.habitrpg.android.habitica.models.tasks.Task import com.habitrpg.shared.habitica.models.responses.TaskDirection import com.habitrpg.shared.habitica.models.responses.TaskScoringResult import com.habitrpg.shared.habitica.models.tasks.TaskType -import io.reactivex.rxjava3.disposables.CompositeDisposable import io.realm.Case import io.realm.OrderedRealmCollection import io.realm.RealmQuery @@ -30,8 +29,6 @@ import java.util.Date import javax.inject.Inject class TasksViewModel : BaseViewModel(), GroupPlanInfoProvider { - private var compositeSubscription: CompositeDisposable = CompositeDisposable() - override fun inject(component: UserComponent) { component.inject(this) } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/dialogs/PetSuggestHatchDialog.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/dialogs/PetSuggestHatchDialog.kt index 566af2cb9..468bed602 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/dialogs/PetSuggestHatchDialog.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/dialogs/PetSuggestHatchDialog.kt @@ -11,7 +11,6 @@ import com.habitrpg.android.habitica.HabiticaBaseApplication import com.habitrpg.android.habitica.R import com.habitrpg.android.habitica.databinding.DialogHatchPetButtonBinding import com.habitrpg.android.habitica.databinding.DialogPetSuggestHatchBinding -import com.habitrpg.android.habitica.helpers.ExceptionHandler import com.habitrpg.android.habitica.helpers.launchCatching import com.habitrpg.android.habitica.interactors.HatchPetUseCase import com.habitrpg.android.habitica.models.inventory.Animal @@ -22,8 +21,7 @@ import com.habitrpg.android.habitica.ui.activities.BaseActivity import com.habitrpg.android.habitica.ui.activities.MainActivity import com.habitrpg.common.habitica.extensions.DataBindingUtils import com.habitrpg.common.habitica.extensions.loadImage -import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers -import io.reactivex.rxjava3.core.Observable +import kotlinx.coroutines.MainScope import java.util.Locale import javax.inject.Inject @@ -162,14 +160,9 @@ class PetSuggestHatchDialog(context: Context) : HabiticaAlertDialog(context) { DataBindingUtils.loadImage(context, imageName) { val resources = context.resources ?: return@loadImage val drawable = if (hasMount) it else BitmapDrawable(resources, it.toBitmap().extractAlpha()) - Observable.just(drawable) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe( - { - binding.petView.bitmap = drawable.toBitmap() - }, - ExceptionHandler.rx() - ) + MainScope().launchCatching { + binding.petView.bitmap = drawable.toBitmap() + } } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/shops/PurchaseDialog.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/shops/PurchaseDialog.kt index fde907d89..aaf51f764 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/shops/PurchaseDialog.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/shops/PurchaseDialog.kt @@ -40,8 +40,6 @@ import com.habitrpg.android.habitica.ui.views.insufficientCurrency.InsufficientG import com.habitrpg.android.habitica.ui.views.insufficientCurrency.InsufficientHourglassesDialog import com.habitrpg.android.habitica.ui.views.insufficientCurrency.InsufficientSubscriberGemsDialog import com.habitrpg.android.habitica.ui.views.tasks.form.StepperValueFormView -import io.reactivex.rxjava3.disposables.CompositeDisposable -import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.MainScope @@ -221,7 +219,6 @@ class PurchaseDialog(context: Context, component: UserComponent?, val item: Shop } } - private val compositeSubscription: CompositeDisposable = CompositeDisposable() var shopIdentifier: String? = null private var user: User? = null var isPinned: Boolean = false @@ -313,13 +310,9 @@ class PurchaseDialog(context: Context, component: UserComponent?, val item: Shop userRepository.close() inventoryRepository.close() limitedTextViewJob?.cancel() - if (!compositeSubscription.isDisposed) { - compositeSubscription.dispose() - } super.dismiss() } - @OptIn(DelicateCoroutinesApi::class) private fun onBuyButtonClicked() { if (shopItem.isValid && !shopItem.locked) { val gemsLeft = if (shopItem.limitedNumberLeft != null) shopItem.limitedNumberLeft else 0 @@ -368,7 +361,7 @@ class PurchaseDialog(context: Context, component: UserComponent?, val item: Shop ) HapticFeedbackManager.tap(contentView) val snackbarText = arrayOf("") - val observable: (suspend () -> Unit) + val observable: (suspend () -> Any?) if (shopIdentifier != null && shopIdentifier == Shop.TIME_TRAVELERS_SHOP || "mystery_set" == shopItem.purchaseType || shopItem.currency == "hourglasses") { observable = if (shopItem.purchaseType == "gear") { { inventoryRepository.purchaseMysterySet(shopItem.key) } @@ -408,10 +401,8 @@ class PurchaseDialog(context: Context, component: UserComponent?, val item: Shop observable = { inventoryRepository.purchaseItem(shopItem.purchaseType, shopItem.key, quantity) } } lifecycleScope.launchCatching { - observable() - val text = if (snackbarText[0].isNotEmpty()) { - snackbarText[0] - } else { + val result = observable() ?: return@launchCatching + val text = snackbarText[0].ifEmpty { context.getString(R.string.successful_purchase, shopItem.text) } val rightTextColor = when (item.currency) { diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/social/QuestProgressView.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/social/QuestProgressView.kt index 0ffdf322c..fa8e349ed 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/social/QuestProgressView.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/social/QuestProgressView.kt @@ -20,6 +20,7 @@ import com.habitrpg.android.habitica.R import com.habitrpg.android.habitica.databinding.QuestCollectBinding import com.habitrpg.android.habitica.databinding.QuestProgressBinding import com.habitrpg.android.habitica.helpers.ExceptionHandler +import com.habitrpg.android.habitica.helpers.launchCatching import com.habitrpg.android.habitica.models.inventory.Quest import com.habitrpg.android.habitica.models.inventory.QuestContent import com.habitrpg.android.habitica.models.inventory.QuestProgressCollect @@ -32,8 +33,7 @@ import com.habitrpg.common.habitica.extensions.DataBindingUtils import com.habitrpg.common.habitica.extensions.layoutInflater import com.habitrpg.common.habitica.extensions.loadImage import com.habitrpg.common.habitica.helpers.setMarkdown -import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers -import io.reactivex.rxjava3.core.Observable +import kotlinx.coroutines.MainScope class QuestProgressView : LinearLayout { private val binding = QuestProgressBinding.inflate(context.layoutInflater, this, true) @@ -181,22 +181,17 @@ class QuestProgressView : LinearLayout { val iconView = ImageView(context) if (strike.wasHit) { DataBindingUtils.loadImage(context, "rage_strike_${strike.key}") { - Observable.just(it) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe( - { drawable -> - val bitmap = drawable.toBitmap() - val displayDensity = resources.displayMetrics.density - val width = bitmap.width * displayDensity - val height = bitmap.height * displayDensity - val scaledImage = Bitmap.createScaledBitmap(bitmap, width.toInt(), height.toInt(), false) - iconView.setImageBitmap(HabiticaIconsHelper.imageOfRageStrikeActive(context, scaledImage)) - iconView.setOnClickListener { - showActiveStrikeAlert(strike.key) - } - }, - ExceptionHandler.rx() - ) + MainScope().launchCatching { + val bitmap = it.toBitmap() + val displayDensity = resources.displayMetrics.density + val width = bitmap.width * displayDensity + val height = bitmap.height * displayDensity + val scaledImage = Bitmap.createScaledBitmap(bitmap, width.toInt(), height.toInt(), false) + iconView.setImageBitmap(HabiticaIconsHelper.imageOfRageStrikeActive(context, scaledImage)) + iconView.setOnClickListener { + showActiveStrikeAlert(strike.key) + } + } } } else { iconView.setImageBitmap(HabiticaIconsHelper.imageOfRageStrikeInactive()) diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/stats/BulkAllocateStatsDialog.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/stats/BulkAllocateStatsDialog.kt index dbc0415fe..056e47016 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/stats/BulkAllocateStatsDialog.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/stats/BulkAllocateStatsDialog.kt @@ -14,7 +14,6 @@ import com.habitrpg.android.habitica.helpers.ExceptionHandler import com.habitrpg.android.habitica.helpers.launchCatching import com.habitrpg.common.habitica.extensions.getThemeColor import com.habitrpg.common.habitica.extensions.layoutInflater -import io.reactivex.rxjava3.disposables.Disposable import kotlinx.coroutines.MainScope import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.launch @@ -25,8 +24,6 @@ class BulkAllocateStatsDialog(context: Context, component: UserComponent?) : Ale @Inject lateinit var userRepository: UserRepository - var subscription: Disposable? = null - private val allocatedPoints: Int get() { var value = 0 @@ -137,11 +134,6 @@ class BulkAllocateStatsDialog(context: Context, component: UserComponent?) : Ale } } - override fun dismiss() { - subscription?.dispose() - super.dismiss() - } - @SuppressLint("SetTextI18n") private fun updateTitle() { binding.allocatedTitle.text = "$allocatedPoints/$pointsToAllocate" diff --git a/Habitica/src/release/java/com/habitrpg/android/habitica/proxy/AnalyticsManagerImpl.kt b/Habitica/src/release/java/com/habitrpg/android/habitica/proxy/AnalyticsManagerImpl.kt index 666fb780d..b9340c9b8 100644 --- a/Habitica/src/release/java/com/habitrpg/android/habitica/proxy/AnalyticsManagerImpl.kt +++ b/Habitica/src/release/java/com/habitrpg/android/habitica/proxy/AnalyticsManagerImpl.kt @@ -2,7 +2,6 @@ package com.habitrpg.android.habitica.proxy import android.content.Context import android.os.Bundle -import com.amplitude.api.Amplitude import com.google.firebase.analytics.FirebaseAnalytics import com.google.firebase.crashlytics.FirebaseCrashlytics @@ -16,7 +15,7 @@ class AnalyticsManagerImpl(context: Context) : AnalyticsManager { override fun setUserIdentifier(identifier: String) { FirebaseCrashlytics.getInstance().setUserId(identifier) - Amplitude.getInstance().userId = identifier + AmplitudeManager.amplitude.userId = identifier } override fun setUserProperty(identifier: String, value: String) { diff --git a/build.gradle b/build.gradle index 2df4a4e19..76a6daafa 100644 --- a/build.gradle +++ b/build.gradle @@ -7,20 +7,20 @@ buildscript { app_version_name = '' app_version_code = 0 - amplitude_version = '3.35.1' + amplitude_version = '1.5.1' appcompat_version = '1.5.1' - coil_version = '2.1.0' - compose_version = '1.3.0' + coil_version = '2.2.2' + compose_version = '1.3.1' core_ktx_version = '1.9.0' coroutines_version = '1.6.4' - daggerhilt_version = '2.42' + daggerhilt_version = '2.44.2' firebase_bom = '30.2.0' - kotlin_version = '1.7.20' + kotlin_version = '1.7.21' lifecycle_version = '2.5.1' markwon_version = '4.6.2' - moshi_version = '1.13.0' + moshi_version = '1.14.0' navigation_version = '2.5.3' - okhttp_version = '4.9.3' + okhttp_version = '4.10.0' play_wearables_version = '18.0.0' play_auth_version = '20.3.0' preferences_version = '1.2.0' From 2ea10966227338be4141f98259fdd400a151823d Mon Sep 17 00:00:00 2001 From: Phillip Thelen Date: Thu, 17 Nov 2022 17:33:15 +0100 Subject: [PATCH 32/36] build basic task summary view --- Habitica/AndroidManifest.xml | 10 ++ Habitica/res/drawable-mdpi/completed.png | Bin 0 -> 396 bytes Habitica/res/drawable-xhdpi/completed.png | Bin 0 -> 509 bytes Habitica/res/drawable-xxhdpi/completed.png | Bin 0 -> 661 bytes Habitica/res/values/strings.xml | 8 + .../helpers/MainNavigationController.kt | 2 +- .../helpers/TaskDescriptionBuilder.kt | 52 ++++++ .../android/habitica/models/tasks/Task.kt | 4 - .../habitica/models/tasks/TaskGroupPlan.kt | 3 +- .../ui/activities/TaskSummaryActivity.kt | 156 +++++++++++++++++- .../tasks/TaskRecyclerViewFragment.kt | 6 +- 11 files changed, 228 insertions(+), 13 deletions(-) create mode 100644 Habitica/res/drawable-mdpi/completed.png create mode 100644 Habitica/res/drawable-xhdpi/completed.png create mode 100644 Habitica/res/drawable-xxhdpi/completed.png create mode 100644 Habitica/src/main/java/com/habitrpg/android/habitica/helpers/TaskDescriptionBuilder.kt diff --git a/Habitica/AndroidManifest.xml b/Habitica/AndroidManifest.xml index ed0a0b018..798548e15 100644 --- a/Habitica/AndroidManifest.xml +++ b/Habitica/AndroidManifest.xml @@ -148,6 +148,16 @@ android:name="android.support.PARENT_ACTIVITY" android:value=".ui.activities.MainActivity" /> + + + 4R2FDE#L(|8ySX>JrB~@jmLH-lhT3A#GmLPe` qj~^@ig9cjPq|t1B0n>!_U$H00003e=m{Vpu?c~|UKifL9-#EunT0|- znVD8vH~hO!-^~Bb>zn2cL7~|!JZ})50d@gu;^?B6nA!Kd-Y2Qec~gz?fufkvak8Ul z;Kv%wg_z zPFyX37D8dJT2N`8iV}?8Fa0e{07?jjxkxEH*tAzq155MV_(6EkqP`!r+z?D18-k7b;VMO4A2%eiJ4z>0O@5R zJzgB4a&fc+-R1zqbtz`*i0cz7k=)!|cnGg=DCETfhRrLF00000NkvXXu0mjf0(s6j literal 0 HcmV?d00001 diff --git a/Habitica/res/drawable-xxhdpi/completed.png b/Habitica/res/drawable-xxhdpi/completed.png new file mode 100644 index 0000000000000000000000000000000000000000..d84b65cb2426f8fd97b5ada9b0a88c1b82bc445b GIT binary patch literal 661 zcmV;G0&4wB0Ma{|gq(t=PoAlQRMj=&NS^x+q^6@)sm zQ=0^-zfBUScE2x8>K$M$1v8wu}ZC^at&y z7r~K#y)K~v^mycXE|6`}?o#pf{g zwTV5KHu5o0(}Z8~Qyfjq2C<`wFybc=V-1#knuRP)gcT2*-EHn9u;j}$T4AD8ymY+? zgaO~;g}Lj1!9=Bad4EOVJQE?r3&w@4V8TlL^txywl=w;AawM^rqpkQ)jT8EE^da7q z4^$>JRfCn7*VynJ%7o{27kKhh7LJAPHg^*OpTQ9y;sr0koT~|6;stNfsI3W~;svka zbd?F;;sx*dfXPG%@j{SzyK{ruQj(u17pSzEoaPhVm5%MW7OL#`Y!U_z><_Qeq3O~D>Sp+K&!Ao v_D_jefAnkoyV*deuftcY9LMp-qZGUYNj^A)5^n-B00000NkvXXu0mjf_xB{~ literal 0 HcmV?d00001 diff --git a/Habitica/res/values/strings.xml b/Habitica/res/values/strings.xml index acd6d9e5e..a6684100c 100644 --- a/Habitica/res/values/strings.xml +++ b/Habitica/res/values/strings.xml @@ -53,6 +53,7 @@ Your Username and/or Password was incorrect. Save Copy + Title Notes Difficulty Tags @@ -1256,6 +1257,13 @@ Copy shared tasks Group Plan Settings Task Summary + This is a **%s** Habit that is **%s**. + This is a **%s** Task that is due **%s**. + This is a **%s** Task that does not have a due date. + This is a **%s** Task that repeats **%s**. + positive and negative + Assigned to + Completed at %s You You, %d other diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/MainNavigationController.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/MainNavigationController.kt index a5c6082ba..a5d4089c1 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/MainNavigationController.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/MainNavigationController.kt @@ -70,6 +70,6 @@ object MainNavigationController { } fun navigateBack() { - navController?.navigateUp() + navController?.popBackStack() } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/TaskDescriptionBuilder.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/TaskDescriptionBuilder.kt new file mode 100644 index 000000000..bc4c2c40b --- /dev/null +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/TaskDescriptionBuilder.kt @@ -0,0 +1,52 @@ +package com.habitrpg.android.habitica.helpers + +import android.content.Context +import com.habitrpg.android.habitica.R +import com.habitrpg.android.habitica.models.tasks.Task +import com.habitrpg.shared.habitica.models.tasks.TaskType +import java.text.DateFormat +import java.util.Date + +class TaskDescriptionBuilder(private val context: Context) { + + fun describe(task: Task): String { + return when (task.type) { + TaskType.HABIT -> context.getString(R.string.habit_summary_description, describeHabitDirections(task.up ?: false, task.down ?: false), describeDifficulty(task.priority)) + TaskType.TODO -> { + if (task.dueDate != null) { + context.getString(R.string.todo_summary_description_duedate, describeDifficulty(task.priority), describeDate(task.dueDate!!)) + } else { + context.getString(R.string.todo_summary_description, describeDifficulty(task.priority)) + } + } + TaskType.DAILY -> context.getString(R.string.daily_summary_description, describeDifficulty(task.priority), "sometimes") + else -> "" + } + } + + private val dateFormatter = DateFormat.getDateInstance() + + private fun describeDate(date: Date): String { + return dateFormatter.format(date) + } + + private fun describeHabitDirections(up: Boolean, down: Boolean): String { + return if (up && down) { + context.getString(R.string.positive_and_negative) + } else if (up) { + context.getString(R.string.positive_habit_form) + } else { + context.getString(R.string.negative_habit_form) + } + } + + private fun describeDifficulty(difficulty: Float): String { + return when (difficulty) { + 0.1f -> context.getString(R.string.trivial) + 1.0f -> context.getString(R.string.easy) + 1.5f -> context.getString(R.string.medium) + 2.0f -> context.getString(R.string.hard) + else -> "" + } + } +} 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 2cd447a1e..19fed52b9 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 @@ -501,10 +501,6 @@ open class Task : RealmObject, BaseMainObject, Parcelable, BaseTask { return daysOfMonth } - fun canEdit(userID: String): Boolean { - return true - } - companion object CREATOR : Parcelable.Creator { override fun createFromParcel(source: Parcel): Task = Task(source) diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/models/tasks/TaskGroupPlan.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/models/tasks/TaskGroupPlan.kt index 2e936526c..e0b16b555 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/models/tasks/TaskGroupPlan.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/models/tasks/TaskGroupPlan.kt @@ -9,11 +9,12 @@ import java.util.Date @RealmClass(embedded = true) open class GroupAssignedDetails: RealmObject(), BaseObject { - var assignedDate: String? = null + var assignedDate: Date? = null var assignedUsername: String? = null var assignedUserID: String? = null var assigningUsername: String? = null var completed: Boolean = false + var completedDate: Date? = null } @RealmClass(embedded = true) diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/TaskSummaryActivity.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/TaskSummaryActivity.kt index 0e53ac81f..363baaade 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/TaskSummaryActivity.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/TaskSummaryActivity.kt @@ -1,21 +1,49 @@ package com.habitrpg.android.habitica.ui.activities import android.os.Bundle +import android.view.Window +import android.view.WindowManager import androidx.activity.compose.setContent +import androidx.activity.viewModels +import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.heightIn +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.Button +import androidx.compose.material.ButtonDefaults +import androidx.compose.material.MaterialTheme import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.livedata.observeAsState +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.ColorFilter +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.colorResource +import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.core.content.ContextCompat +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.asLiveData 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.helpers.MainNavigationController +import com.habitrpg.android.habitica.helpers.TaskDescriptionBuilder import com.habitrpg.android.habitica.ui.viewmodels.BaseViewModel +import com.habitrpg.common.habitica.helpers.MarkdownParser +import com.habitrpg.shared.habitica.models.tasks.TaskType +import java.text.DateFormat import javax.inject.Inject class TaskSummaryViewModel(val taskId: String) : BaseViewModel() { @@ -26,18 +54,36 @@ class TaskSummaryViewModel(val taskId: String) : BaseViewModel() { override fun inject(component: UserComponent) { component.inject(this) } + + @Suppress("UNCHECKED_CAST") + class Factory(private val taskID: String): ViewModelProvider.Factory { + override fun create(modelClass: Class): T { + return TaskSummaryViewModel(taskID) as T + } + } } class TaskSummaryActivity: BaseActivity() { override fun getLayoutResId(): Int? = null + private val viewModel: TaskSummaryViewModel by viewModels { TaskSummaryViewModel.Factory(intent.extras?.getString( + TaskFormActivity.TASK_ID_KEY + ) ?: "") } + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { - + TaskSummaryView(viewModel = viewModel) } } + override fun onStart() { + super.onStart() + val window: Window = window + window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) + window.statusBarColor = ContextCompat.getColor(this, viewModel.task.value?.lightTaskColor ?: R.color.brand_300) + } + override fun injectActivity(component: UserComponent?) { component?.inject(this) } @@ -45,9 +91,113 @@ class TaskSummaryActivity: BaseActivity() { @Composable fun TaskSummaryView(viewModel: TaskSummaryViewModel) { + val taskDescriptionBuilder = TaskDescriptionBuilder(LocalContext.current) val task by viewModel.task.observeAsState() - Column { - Text(stringResource(R.string.task_summary), Modifier) + val titleModifier = Modifier.padding(top = 30.dp) + val textModifier = Modifier.padding(top = 4.dp) + val completedTimeFormat = DateFormat.getTimeInstance() + val darkestColor = colorResource(task?.darkestTaskColor ?: R.color.text_primary) + Column(Modifier.background(colorResource(task?.lightTaskColor ?: R.color.brand_300))) { + Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(0.dp, 4.dp)) { + Button(onClick = { + MainNavigationController.navigateBack() + }, colors = ButtonDefaults.textButtonColors(contentColor = darkestColor), elevation = ButtonDefaults.elevation(0.dp)) { + Image(painterResource(R.drawable.ic_arrow_back_white_36dp), stringResource(R.string.action_back), colorFilter = ColorFilter.tint(colorResource(task?.darkestTaskColor ?: R.color.white))) + } + Text( + stringResource(R.string.task_summary), + fontSize = 18.sp, + fontWeight = FontWeight.Medium, + color = darkestColor, + modifier = Modifier.padding(start = 6.dp) + ) + } + Column( + Modifier + .background( + MaterialTheme.colors.background, + RoundedCornerShape(topStart = 12.dp, topEnd = 12.dp) + ) + .padding(20.dp, 5.dp) + .fillMaxWidth()) { + Text(stringResource(R.string.title), fontSize = 16.sp, color = darkestColor, fontWeight = FontWeight.Medium, modifier = titleModifier) + Text(task?.text ?: "", fontSize = 16.sp, color = MaterialTheme.colors.onBackground, modifier = textModifier) + if (task?.notes?.isNotBlank() == true) { + Text( + stringResource(R.string.notes), + fontSize = 16.sp, + color = MaterialTheme.colors.onSecondary, + fontWeight = FontWeight.Medium, + modifier = titleModifier + ) + Text( + task?.notes ?: "", + fontSize = 16.sp, + color = MaterialTheme.colors.onBackground, + modifier = textModifier + ) + } + if (task?.type != TaskType.REWARD) { + Text( + stringResource(R.string.description), + fontSize = 16.sp, + color = darkestColor, + fontWeight = FontWeight.Medium, + modifier = titleModifier + ) + Text(MarkdownParser.parseMarkdown(task?.let { taskDescriptionBuilder.describe(it) } ?: "").toString(), + fontSize = 16.sp, + color = MaterialTheme.colors.onBackground, + modifier = textModifier) + } + if (task?.checklist?.isNotEmpty() == true) { + task?.checklist?.let { checklist -> + Text(stringResource(R.string.checklist), fontSize = 16.sp, color = darkestColor, fontWeight = FontWeight.Medium, modifier = titleModifier) + for (item in checklist) { + Text(item.text ?: "", fontSize = 16.sp, fontWeight = FontWeight.Medium, modifier = Modifier + .background(colorResource(R.color.gray_700)) + .padding(15.dp) + .fillMaxWidth()) + } + } + } + if (task?.group?.assignedUsersDetail?.isNotEmpty() == true) { + Text(stringResource(R.string.assigned_to), fontSize = 16.sp, color = darkestColor, fontWeight = FontWeight.Medium, modifier = titleModifier.padding(bottom = 4.dp)) + for (item in task?.group?.assignedUsersDetail ?: emptyList()) { + UserRow(item.assignedUsername ?: "", Modifier + .padding(vertical = 4.dp) + .background(colorResource(R.color.gray_700), RoundedCornerShape(8.dp)) + .padding(15.dp, 12.dp) + .heightIn(min = 24.dp) + .fillMaxWidth(), + extraContent = if (item.completed) ({ + Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(top = 4.dp)) { + Image(painterResource(R.drawable.completed), null) + Text(stringResource(R.string.completed_at, + item.completedDate?.let { completedTimeFormat.format(it) } ?: ""), + fontSize = 14.sp, + color = colorResource(R.color.green_10), modifier = Modifier.padding(start = 4.dp)) + } + }) else null) + } + } + } + } +} + +@Composable +fun UserRow(username: String, modifier: Modifier = Modifier, extraContent: @Composable (() -> Unit)? = null) { + Row(verticalAlignment = Alignment.CenterVertically, modifier = modifier) { + Column { + Text( + "@$username", fontSize = 16.sp, fontWeight = FontWeight.Medium, modifier = Modifier + .background(colorResource(R.color.gray_700)) + .fillMaxWidth() + ) + if (extraContent != null) { + extraContent() + } + } } } 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 2ca698d74..ab4602dcf 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 @@ -66,7 +66,6 @@ open class TaskRecyclerViewFragment : BaseFragment Date: Thu, 17 Nov 2022 19:01:54 +0100 Subject: [PATCH 33/36] more colors --- Habitica/build.gradle | 2 + Habitica/res/drawable-mdpi/arrow_back.png | Bin 0 -> 184 bytes Habitica/res/drawable-xhdpi/arrow_back.png | Bin 0 -> 214 bytes Habitica/res/drawable-xxhdpi/arrow_back.png | Bin 0 -> 283 bytes .../android/habitica/models/tasks/Task.kt | 26 ++ .../ui/activities/TaskSummaryActivity.kt | 279 ++++++++++++------ common/src/main/res/values/colors.xml | 14 + 7 files changed, 230 insertions(+), 91 deletions(-) create mode 100644 Habitica/res/drawable-mdpi/arrow_back.png create mode 100644 Habitica/res/drawable-xhdpi/arrow_back.png create mode 100644 Habitica/res/drawable-xxhdpi/arrow_back.png diff --git a/Habitica/build.gradle b/Habitica/build.gradle index 2dbde8c1d..babbc5ce3 100644 --- a/Habitica/build.gradle +++ b/Habitica/build.gradle @@ -107,6 +107,8 @@ dependencies { implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version" implementation "com.google.android.material:compose-theme-adapter:1.1.21" + implementation "androidx.compose.material3:material3:1.0.1" + implementation "com.google.accompanist:accompanist-systemuicontroller:0.27.1" implementation 'androidx.activity:activity-compose:1.6.1' implementation "androidx.compose.runtime:runtime-livedata:$compose_version" diff --git a/Habitica/res/drawable-mdpi/arrow_back.png b/Habitica/res/drawable-mdpi/arrow_back.png new file mode 100644 index 0000000000000000000000000000000000000000..5a6ce17d7b9fc1d8378d22023512ff15d39c3025 GIT binary patch literal 184 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`oCO|{#S9GG!XV7ZFl&wkP>``W z$lZxy-8q?;Kn_c~qpu?a!^VE@KZ&eBzOSc?V@L(#+sOwx85B5N_g{3#4VitVBR7SW zHCAWSYW^mN`@1=15}w(}nfO!+);;<1Xx5qoU)df`m(z+iacXv5C-`y39utK{f)W3< a-kJ)D{$Y6eINB0uFoUP7pUXO@geCxRp*SP} literal 0 HcmV?d00001 diff --git a/Habitica/res/drawable-xhdpi/arrow_back.png b/Habitica/res/drawable-xhdpi/arrow_back.png new file mode 100644 index 0000000000000000000000000000000000000000..eb2fd3b180689b7ed1ccff0e4961828ae46b1ce8 GIT binary patch literal 214 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz&H|6fVg?2=RS;(M3{v?36l5$8 za(7}_cTVOdki(Mh=_y1 zHj4zZ{BixKa=m=h9f=tS?yk2=xbS4|osRZB#Y)YMdydHbkUpt8J?7KXCz=6Ir`n2b zX!LEFF}b2!V_soPS3rxB$ij|(rJ}gajxCLj-3C4d%=5&AJ2jdbp98IB@O1TaS?83{ F1ORKtNOk}K literal 0 HcmV?d00001 diff --git a/Habitica/res/drawable-xxhdpi/arrow_back.png b/Habitica/res/drawable-xxhdpi/arrow_back.png new file mode 100644 index 0000000000000000000000000000000000000000..92e71e95dc3f9751767bf39b47eb4200b2ebd8ee GIT binary patch literal 283 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA1|-9oezpTC&H|6fVg?393lL^>oo1K-6l5$8 za(7}_cTVOdki(Mh=e=~7Y#+1w3N(xYqE6D zj!u6U!M_vd9<)?A`8+1TkyYmPj#wb|EoJpAJiExGukk5_B-$^V-Ftv-sY<0tEiKL6%VKfWAzWD<16f1yOZO1y)v z{-cJBBRa}g@)k??B|PL1LnaEBbDTTB=B}fQ!$Ch6hm%@L3o^PmVqS1}KR%p&>qLMu P(CrMKu6{1-oD!M return R.color.maroon_700 + this.value < -10 -> return R.color.red_700 + this.value < -1 -> return R.color.orange_700 + this.value < 1 -> return R.color.yellow_700 + this.value < 5 -> return R.color.green_700 + this.value < 10 -> return R.color.teal_700 + else -> R.color.blue_700 + } + } + + val extraExtraLightTaskColor: Int + get() { + return when { + this.value < -20 -> return R.color.maroon_600 + this.value < -10 -> return R.color.red_600 + this.value < -1 -> return R.color.orange_600 + this.value < 1 -> return R.color.yellow_600 + this.value < 5 -> return R.color.green_600 + this.value < 10 -> return R.color.teal_600 + else -> R.color.blue_600 + } + } + val extraLightTaskColor: Int get() { return when { diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/TaskSummaryActivity.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/TaskSummaryActivity.kt index 363baaade..5df9960c4 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/TaskSummaryActivity.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/TaskSummaryActivity.kt @@ -1,14 +1,13 @@ package com.habitrpg.android.habitica.ui.activities import android.os.Bundle -import android.view.Window -import android.view.WindowManager import androidx.activity.compose.setContent import androidx.activity.viewModels import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.padding @@ -18,30 +17,37 @@ import androidx.compose.material.ButtonDefaults import androidx.compose.material.MaterialTheme import androidx.compose.material.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.livedata.observeAsState import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.shadow +import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.colorResource import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.text.SpanStyle +import androidx.compose.ui.text.buildAnnotatedString import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.withStyle import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import androidx.core.content.ContextCompat import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.asLiveData +import com.google.accompanist.systemuicontroller.rememberSystemUiController 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.helpers.MainNavigationController import com.habitrpg.android.habitica.helpers.TaskDescriptionBuilder +import com.habitrpg.android.habitica.ui.theme.HabiticaTheme import com.habitrpg.android.habitica.ui.viewmodels.BaseViewModel -import com.habitrpg.common.habitica.helpers.MarkdownParser import com.habitrpg.shared.habitica.models.tasks.TaskType import java.text.DateFormat import javax.inject.Inject @@ -56,34 +62,33 @@ class TaskSummaryViewModel(val taskId: String) : BaseViewModel() { } @Suppress("UNCHECKED_CAST") - class Factory(private val taskID: String): ViewModelProvider.Factory { + class Factory(private val taskID: String) : ViewModelProvider.Factory { override fun create(modelClass: Class): T { return TaskSummaryViewModel(taskID) as T } } } -class TaskSummaryActivity: BaseActivity() { +class TaskSummaryActivity : BaseActivity() { override fun getLayoutResId(): Int? = null - private val viewModel: TaskSummaryViewModel by viewModels { TaskSummaryViewModel.Factory(intent.extras?.getString( - TaskFormActivity.TASK_ID_KEY - ) ?: "") } + private val viewModel: TaskSummaryViewModel by viewModels { + TaskSummaryViewModel.Factory( + intent.extras?.getString( + TaskFormActivity.TASK_ID_KEY + ) ?: "" + ) + } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { - TaskSummaryView(viewModel = viewModel) + HabiticaTheme { + TaskSummaryView(viewModel = viewModel) + } } } - override fun onStart() { - super.onStart() - val window: Window = window - window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) - window.statusBarColor = ContextCompat.getColor(this, viewModel.task.value?.lightTaskColor ?: R.color.brand_300) - } - override fun injectActivity(component: UserComponent?) { component?.inject(this) } @@ -95,103 +100,195 @@ fun TaskSummaryView(viewModel: TaskSummaryViewModel) { val task by viewModel.task.observeAsState() val titleModifier = Modifier.padding(top = 30.dp) val textModifier = Modifier.padding(top = 4.dp) - val completedTimeFormat = DateFormat.getTimeInstance() - val darkestColor = colorResource(task?.darkestTaskColor ?: R.color.text_primary) - Column(Modifier.background(colorResource(task?.lightTaskColor ?: R.color.brand_300))) { - Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(0.dp, 4.dp)) { - Button(onClick = { - MainNavigationController.navigateBack() - }, colors = ButtonDefaults.textButtonColors(contentColor = darkestColor), elevation = ButtonDefaults.elevation(0.dp)) { - Image(painterResource(R.drawable.ic_arrow_back_white_36dp), stringResource(R.string.action_back), colorFilter = ColorFilter.tint(colorResource(task?.darkestTaskColor ?: R.color.white))) - } - Text( - stringResource(R.string.task_summary), - fontSize = 18.sp, - fontWeight = FontWeight.Medium, - color = darkestColor, - modifier = Modifier.padding(start = 6.dp) - ) + val completedTimeFormat = DateFormat.getTimeInstance(DateFormat.SHORT) + if (task != null) { + val darkestColor = colorResource(task?.darkestTaskColor ?: R.color.text_primary) + val systemUiController = rememberSystemUiController() + val statusBarColor = colorResource(task?.lightTaskColor ?: R.color.brand_400) + val lightestColor = colorResource(task?.lightestTaskColor ?: R.color.window_background) + DisposableEffect(systemUiController) { + systemUiController.setStatusBarColor(statusBarColor, darkIcons = true) + systemUiController.setNavigationBarColor(lightestColor) + onDispose {} } - Column( - Modifier - .background( - MaterialTheme.colors.background, - RoundedCornerShape(topStart = 12.dp, topEnd = 12.dp) - ) - .padding(20.dp, 5.dp) - .fillMaxWidth()) { - Text(stringResource(R.string.title), fontSize = 16.sp, color = darkestColor, fontWeight = FontWeight.Medium, modifier = titleModifier) - Text(task?.text ?: "", fontSize = 16.sp, color = MaterialTheme.colors.onBackground, modifier = textModifier) - if (task?.notes?.isNotBlank() == true) { + Column(Modifier.background(colorResource(task?.lightTaskColor ?: R.color.brand_300))) { + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.padding(vertical = 2.dp) + ) { + Button( + onClick = { + MainNavigationController.navigateBack() + }, + colors = ButtonDefaults.textButtonColors(contentColor = darkestColor), + elevation = ButtonDefaults.elevation(0.dp) + ) { + Image( + painterResource(R.drawable.arrow_back), + stringResource(R.string.action_back), + colorFilter = ColorFilter.tint( + colorResource( + task?.darkestTaskColor ?: R.color.white + ) + ) + ) + } Text( - stringResource(R.string.notes), - fontSize = 16.sp, - color = MaterialTheme.colors.onSecondary, + stringResource(R.string.task_summary), + fontSize = 18.sp, fontWeight = FontWeight.Medium, - modifier = titleModifier - ) - Text( - task?.notes ?: "", - fontSize = 16.sp, - color = MaterialTheme.colors.onBackground, - modifier = textModifier + color = darkestColor, + modifier = Modifier.padding(start = 0.dp) ) } - if (task?.type != TaskType.REWARD) { + Column( + Modifier + .shadow(16.dp) + .background( + lightestColor, + RoundedCornerShape(topStart = 12.dp, topEnd = 12.dp) + ) + .padding(20.dp, 5.dp) + .fillMaxWidth() + .fillMaxHeight() + ) { Text( - stringResource(R.string.description), + stringResource(R.string.title), fontSize = 16.sp, color = darkestColor, fontWeight = FontWeight.Medium, modifier = titleModifier ) - Text(MarkdownParser.parseMarkdown(task?.let { taskDescriptionBuilder.describe(it) } ?: "").toString(), - fontSize = 16.sp, - color = MaterialTheme.colors.onBackground, - modifier = textModifier) - } - if (task?.checklist?.isNotEmpty() == true) { - task?.checklist?.let { checklist -> - Text(stringResource(R.string.checklist), fontSize = 16.sp, color = darkestColor, fontWeight = FontWeight.Medium, modifier = titleModifier) - for (item in checklist) { - Text(item.text ?: "", fontSize = 16.sp, fontWeight = FontWeight.Medium, modifier = Modifier - .background(colorResource(R.color.gray_700)) - .padding(15.dp) - .fillMaxWidth()) + Text( + task?.text ?: "", fontSize = 16.sp, color = darkestColor, + fontWeight = FontWeight.Normal, modifier = textModifier + ) + if (task?.notes?.isNotBlank() == true) { + Text( + stringResource(R.string.notes), + fontSize = 16.sp, + color = darkestColor, + fontWeight = FontWeight.Medium, + modifier = titleModifier + ) + Text( + task?.notes ?: "", + fontSize = 16.sp, + color = darkestColor, + fontWeight = FontWeight.Normal, + modifier = textModifier + ) + } + if (task?.type != TaskType.REWARD) { + Text( + stringResource(R.string.description), + fontSize = 16.sp, + color = darkestColor, + fontWeight = FontWeight.Medium, + modifier = titleModifier + ) + Text(task?.let { taskDescriptionBuilder.describe(it) }!!.makeBoldComposable(), + fontSize = 16.sp, + color = darkestColor, + fontWeight = FontWeight.Normal, + modifier = textModifier) + } + if (task?.checklist?.isNotEmpty() == true) { + task?.checklist?.let { checklist -> + Text( + stringResource(R.string.checklist), + fontSize = 16.sp, + color = darkestColor, + fontWeight = FontWeight.Medium, + modifier = titleModifier.padding(bottom = 4.dp) + ) + for (item in checklist) { + Text( + item.text ?: "", + fontSize = 16.sp, + fontWeight = FontWeight.Medium, + color = darkestColor, + modifier = Modifier + .padding(vertical = 4.dp) + .background( + colorResource( + task?.extraExtraLightTaskColor ?: R.color.gray_700 + ), + RoundedCornerShape(8.dp) + ) + .padding(15.dp) + .fillMaxWidth() + ) + } } } - } - if (task?.group?.assignedUsersDetail?.isNotEmpty() == true) { - Text(stringResource(R.string.assigned_to), fontSize = 16.sp, color = darkestColor, fontWeight = FontWeight.Medium, modifier = titleModifier.padding(bottom = 4.dp)) - for (item in task?.group?.assignedUsersDetail ?: emptyList()) { - UserRow(item.assignedUsername ?: "", Modifier - .padding(vertical = 4.dp) - .background(colorResource(R.color.gray_700), RoundedCornerShape(8.dp)) - .padding(15.dp, 12.dp) - .heightIn(min = 24.dp) - .fillMaxWidth(), - extraContent = if (item.completed) ({ - Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(top = 4.dp)) { - Image(painterResource(R.drawable.completed), null) - Text(stringResource(R.string.completed_at, - item.completedDate?.let { completedTimeFormat.format(it) } ?: ""), - fontSize = 14.sp, - color = colorResource(R.color.green_10), modifier = Modifier.padding(start = 4.dp)) - } - }) else null) + if (task?.group?.assignedUsersDetail?.isNotEmpty() == true) { + Text( + stringResource(R.string.assigned_to), + fontSize = 16.sp, + color = darkestColor, + fontWeight = FontWeight.Medium, + modifier = titleModifier.padding(bottom = 4.dp) + ) + for (item in task?.group?.assignedUsersDetail ?: emptyList()) { + UserRow( + item.assignedUsername ?: "", Modifier + .padding(vertical = 4.dp) + .background( + colorResource( + task?.extraExtraLightTaskColor ?: R.color.gray_700 + ), + RoundedCornerShape(8.dp) + ) + .padding(15.dp, 12.dp) + .heightIn(min = 24.dp) + .fillMaxWidth(), + color = darkestColor, + extraContent = if (item.completed) ({ + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.padding(top = 4.dp) + ) { + Image(painterResource(R.drawable.completed), null) + Text(stringResource(R.string.completed_at, + item.completedDate?.let { completedTimeFormat.format(it) } + ?: ""), + fontSize = 14.sp, + color = colorResource(R.color.green_10), + modifier = Modifier.padding(start = 4.dp)) + } + }) else null + ) + } } } } } } +private fun String.makeBoldComposable(): AnnotatedString { + return buildAnnotatedString { + var isBold = false + for (segment in split("**")) { + withStyle(style = SpanStyle(fontWeight = if (isBold) FontWeight.SemiBold else FontWeight.Normal)) { + append(segment) + } + isBold = !isBold + } + } +} + @Composable -fun UserRow(username: String, modifier: Modifier = Modifier, extraContent: @Composable (() -> Unit)? = null) { +fun UserRow( + username: String, + modifier: Modifier = Modifier, + extraContent: @Composable (() -> Unit)? = null, + color: Color? = null +) { Row(verticalAlignment = Alignment.CenterVertically, modifier = modifier) { Column { Text( - "@$username", fontSize = 16.sp, fontWeight = FontWeight.Medium, modifier = Modifier - .background(colorResource(R.color.gray_700)) + "@$username", fontSize = 16.sp, fontWeight = FontWeight.Medium, color = color ?: MaterialTheme.colors.primary, modifier = Modifier .fillMaxWidth() ) if (extraContent != null) { diff --git a/common/src/main/res/values/colors.xml b/common/src/main/res/values/colors.xml index 1488f6ea8..e38a8810f 100644 --- a/common/src/main/res/values/colors.xml +++ b/common/src/main/res/values/colors.xml @@ -16,6 +16,8 @@ @color/brand_100 + #FFF5F5 + #FCEDED #f19595 #DE3F3F #C92B2B @@ -23,6 +25,8 @@ #7D0C0C #4C0001 + #FFF6F7 + #FCEEEF #ffb6b8 #FF6165 #F74E52 @@ -30,30 +34,40 @@ #BF262B #6c0406 + #FFF9F5 + #FCF3ED #ffc8a7 #FF944C #FA8537 #F47825 #A85219 + #FFFCF6 + #FCF3E5 #fedead #FFBE5D #FFB445 #FFA624 #EE9109 + #F5FFFB + #EDFCF7 #77f4c7 #24CC8F #20B780 #1CA372 #168059 + #F5FEFF + #EDFBFC #8eedf6 #3BCAD7 #34B5C1 #26A0AB #1A7078 + #FAFDFF + #EEF5F9 #a9dcf6 #50B5E9 #46A7D9 From 472a35281f823c7f1c8cb62ce57fbb6d4f849dea Mon Sep 17 00:00:00 2001 From: Phillip Thelen Date: Tue, 22 Nov 2022 15:48:09 +0100 Subject: [PATCH 34/36] More colors --- .../res/drawable/habit_scoring_circle.xml | 2 +- .../drawable/layout_rounded_bg_task_form.xml | 2 +- .../res/drawable/layout_rounded_bg_tinted.xml | 6 ++++ .../res/drawable/task_form_control_bg.xml | 6 ++-- Habitica/res/layout/activity_task_form.xml | 8 +++-- .../res/layout/dialog_pet_suggest_hatch.xml | 2 +- Habitica/res/layout/form_stepper_value.xml | 18 +++++----- .../res/layout/task_form_checklist_item.xml | 2 +- Habitica/res/values/attrs.xml | 2 ++ Habitica/res/values/colors.xml | 2 +- Habitica/res/values/styles.xml | 35 +++++++++++++++++-- .../ui/activities/TaskFormActivity.kt | 12 +++---- .../ui/activities/TaskSummaryActivity.kt | 5 ++- .../views/tasks/form/ChecklistItemFormView.kt | 4 +-- .../tasks/form/HabitResetStreakButtons.kt | 11 +++--- .../views/tasks/form/ReminderItemFormView.kt | 2 +- .../views/tasks/form/TaskDifficultyButtons.kt | 5 +-- .../tasks/form/TaskSchedulingControls.kt | 11 +++--- common/src/main/res/values/colors.xml | 7 ++-- 19 files changed, 92 insertions(+), 50 deletions(-) create mode 100644 Habitica/res/drawable/layout_rounded_bg_tinted.xml diff --git a/Habitica/res/drawable/habit_scoring_circle.xml b/Habitica/res/drawable/habit_scoring_circle.xml index 170bb9a37..909dbeb54 100644 --- a/Habitica/res/drawable/habit_scoring_circle.xml +++ b/Habitica/res/drawable/habit_scoring_circle.xml @@ -2,5 +2,5 @@ - + \ No newline at end of file diff --git a/Habitica/res/drawable/layout_rounded_bg_task_form.xml b/Habitica/res/drawable/layout_rounded_bg_task_form.xml index a9bd8afc2..68e8a515b 100644 --- a/Habitica/res/drawable/layout_rounded_bg_task_form.xml +++ b/Habitica/res/drawable/layout_rounded_bg_task_form.xml @@ -2,5 +2,5 @@ - + \ No newline at end of file diff --git a/Habitica/res/drawable/layout_rounded_bg_tinted.xml b/Habitica/res/drawable/layout_rounded_bg_tinted.xml new file mode 100644 index 000000000..d65e9884c --- /dev/null +++ b/Habitica/res/drawable/layout_rounded_bg_tinted.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/Habitica/res/drawable/task_form_control_bg.xml b/Habitica/res/drawable/task_form_control_bg.xml index 4e8a5ba4f..66ba48871 100644 --- a/Habitica/res/drawable/task_form_control_bg.xml +++ b/Habitica/res/drawable/task_form_control_bg.xml @@ -2,14 +2,14 @@ - + - + - + diff --git a/Habitica/res/layout/activity_task_form.xml b/Habitica/res/layout/activity_task_form.xml index 203cafad7..6d316f609 100644 --- a/Habitica/res/layout/activity_task_form.xml +++ b/Habitica/res/layout/activity_task_form.xml @@ -29,7 +29,7 @@ android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent" - android:background="?attr/colorContentBackground" + android:background="?attr/colorTintedBackground" android:paddingBottom="@dimen/spacing_large"> + android:layout_marginBottom="10dp"> + android:layout_marginEnd="10dp"/> + app:shimmer_highlight_alpha="0.4"> - + app:tint="?colorPrimaryDark" /> + + diff --git a/Habitica/res/values/colors.xml b/Habitica/res/values/colors.xml index 9d565e0b7..15ebbeee3 100644 --- a/Habitica/res/values/colors.xml +++ b/Habitica/res/values/colors.xml @@ -19,7 +19,7 @@ #c8ffffff #23000000 - #F9F9F9 + @color/window_background @color/green_10 diff --git a/Habitica/res/values/styles.xml b/Habitica/res/values/styles.xml index 72e59c4da..15ef2d604 100644 --- a/Habitica/res/values/styles.xml +++ b/Habitica/res/values/styles.xml @@ -38,6 +38,8 @@ @color/content_background @color/window_background + @color/brand_800 + @color/brand_700 @style/PopupTheme @style/PopupTheme @@ -93,6 +95,8 @@ @color/red_50 @color/red_1 @color/red_1 + @color/red_700 + @color/red_600 @@ -311,7 +312,7 @@ @@ -708,7 +709,7 @@ @@ -975,8 +976,8 @@