From 9e3ba67f1313617e824b1b592997b2f3df2dd589 Mon Sep 17 00:00:00 2001 From: Phillip Thelen Date: Mon, 4 Oct 2021 14:28:32 +0200 Subject: [PATCH 01/16] move more code to user viewmodel --- Habitica/build.gradle | 4 +- Habitica/res/values/strings.xml | 1 + .../habitica/components/UserComponent.java | 3 + .../ui/fragments/NavigationDrawerFragment.kt | 4 +- .../equipment/EquipmentDetailFragment.kt | 1 - .../equipment/EquipmentOverviewFragment.kt | 87 ++++++++----------- .../ui/fragments/social/ChatFragment.kt | 2 +- .../social/guilds/GuildDetailFragment.kt | 2 +- .../social/party/PartyDetailFragment.kt | 4 +- .../habitica/ui/viewmodels/BaseViewModel.kt | 14 +-- .../habitica/ui/viewmodels/PartyViewModel.kt | 4 +- .../equipment/EquipmentOverviewViewModel.kt | 25 ++++++ 12 files changed, 84 insertions(+), 67 deletions(-) create mode 100644 Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/inventory/equipment/EquipmentOverviewViewModel.kt diff --git a/Habitica/build.gradle b/Habitica/build.gradle index 40a08c857..c924bba11 100644 --- a/Habitica/build.gradle +++ b/Habitica/build.gradle @@ -116,10 +116,12 @@ dependencies { implementation 'com.nex3z:flow-layout:1.2.2' implementation 'androidx.core:core-ktx:1.6.0' - implementation "androidx.lifecycle:lifecycle-extensions:2.2.0" + implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1" + implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.3.1" implementation "androidx.lifecycle:lifecycle-common-java8:2.3.1" implementation 'androidx.navigation:navigation-fragment-ktx:2.3.5' implementation 'androidx.navigation:navigation-ui-ktx:2.3.5' + implementation "androidx.fragment:fragment-ktx:1.3.6" implementation "androidx.paging:paging-runtime-ktx:3.0.1" implementation 'com.plattysoft.leonids:LeonidsLib:1.3.2' implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2' diff --git a/Habitica/res/values/strings.xml b/Habitica/res/values/strings.xml index 64a224eea..7c6a4859d 100644 --- a/Habitica/res/values/strings.xml +++ b/Habitica/res/values/strings.xml @@ -1183,4 +1183,5 @@ Terms of Service %.01f dmg pending %s remaining + Sale ends in %s 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 f62177ff5..c24273044 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 @@ -103,6 +103,7 @@ import com.habitrpg.android.habitica.ui.fragments.tasks.TeamBoardFragment; import com.habitrpg.android.habitica.ui.viewmodels.GroupViewModel; import com.habitrpg.android.habitica.ui.viewmodels.InboxViewModel; import com.habitrpg.android.habitica.ui.viewmodels.NotificationsViewModel; +import com.habitrpg.android.habitica.ui.viewmodels.inventory.equipment.EquipmentOverviewViewModel; import com.habitrpg.android.habitica.ui.views.insufficientCurrency.InsufficientGemsDialog; import com.habitrpg.android.habitica.ui.views.shops.PurchaseDialog; import com.habitrpg.android.habitica.ui.views.social.ChatBarView; @@ -346,4 +347,6 @@ public interface UserComponent { void inject(@NotNull PromoWebFragment promoWebFragment); void inject(@NotNull ItemDialogFragment itemDialogFragment); + + void inject(@NotNull EquipmentOverviewViewModel equipmentOverviewViewModel); } 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 485918e97..07fe20db1 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 @@ -16,6 +16,7 @@ import androidx.core.os.bundleOf import androidx.core.view.GravityCompat import androidx.fragment.app.DialogFragment import androidx.lifecycle.lifecycleScope +import androidx.recyclerview.widget.SimpleItemAnimator import com.habitrpg.android.habitica.HabiticaBaseApplication import com.habitrpg.android.habitica.R import com.habitrpg.android.habitica.data.ContentRepository @@ -181,6 +182,7 @@ class NavigationDrawerFragment : DialogFragment() { binding?.recyclerView?.adapter = adapter binding?.recyclerView?.layoutManager = androidx.recyclerview.widget.LinearLayoutManager(context) + (binding?.recyclerView?.itemAnimator as? SimpleItemAnimator)?.supportsChangeAnimations = false initializeMenuItems() subscriptions?.add( @@ -675,7 +677,7 @@ class NavigationDrawerFragment : DialogFragment() { val diff = activePromo.endDate.time - Date().time if (diff < (Duration.hours(1).inWholeMilliseconds)) Duration.seconds(1) else Duration.minutes(1) }) { - promotedItem.subtitle = context?.getString(R.string.x_remaining, activePromo.endDate.getShortRemainingString()) + promotedItem.subtitle = context?.getString(R.string.sale_ends_in, activePromo.endDate.getShortRemainingString()) updateItem(promotedItem) } } ?: run { 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 1ee163b59..665316463 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 @@ -33,7 +33,6 @@ class EquipmentDetailFragment : } var type: String? = null - var typeText: String? = null var equippedGear: String? = null var isCostume: Boolean? = null 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 index 45101cbb1..6a015a12f 100644 --- 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 @@ -4,38 +4,25 @@ 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.data.InventoryRepository import com.habitrpg.android.habitica.databinding.FragmentEquipmentOverviewBinding import com.habitrpg.android.habitica.helpers.MainNavigationController -import com.habitrpg.android.habitica.helpers.RxErrorHandler import com.habitrpg.android.habitica.models.user.Gear -import com.habitrpg.android.habitica.models.user.User +import com.habitrpg.android.habitica.models.user.Outfit import com.habitrpg.android.habitica.ui.fragments.BaseMainFragment -import javax.inject.Inject +import com.habitrpg.android.habitica.ui.viewmodels.inventory.equipment.EquipmentOverviewViewModel 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) } - @Inject - lateinit var inventoryRepository: InventoryRepository - - override var user: User? - get() = super.user - set(value) { - super.user = value - if (this::inventoryRepository.isInitialized) { - value?.items?.gear?.let { - updateGearData(it) - } - } - } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) @@ -46,22 +33,30 @@ class EquipmentOverviewFragment : BaseMainFragment + 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) } - binding?.autoEquipSwitch?.setOnCheckedChangeListener { _, isChecked -> userRepository.updateUser("preferences.autoEquip", isChecked).subscribe({ }, RxErrorHandler.handleEmptyError()) } - binding?.costumeSwitch?.setOnCheckedChangeListener { _, isChecked -> userRepository.updateUser("preferences.costume", isChecked).subscribe({ }, RxErrorHandler.handleEmptyError()) } + viewModel.user.observe(viewLifecycleOwner) { + it?.items?.gear?.let { + updateGearData(it) + } + binding?.autoEquipSwitch?.isChecked = user?.preferences?.autoEquip ?: false + binding?.costumeSwitch?.isChecked = user?.preferences?.costume ?: false - user?.items?.gear?.let { - updateGearData(it) + if (user?.preferences?.costume == true) { + binding?.costumeView?.alpha = 1.0f + binding?.costumeView?.isEnabled = true + } else { + binding?.costumeView?.alpha = 0.6f + binding?.costumeView?.isEnabled = false + } } } - override fun onDestroy() { - inventoryRepository.close() - super.onDestroy() - } - override fun injectFragment(component: UserComponent) { component.inject(this) } @@ -71,31 +66,17 @@ class EquipmentOverviewFragment : BaseMainFragment() { viewModel?.updateUser("flags.communityGuidelinesAccepted", true) } - viewModel?.getUserData()?.observe( + viewModel?.user?.observe( viewLifecycleOwner, { chatAdapter?.user = 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 5d2de4c30..1a80da036 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 @@ -111,7 +111,7 @@ class GuildDetailFragment : BaseFragment() { private val sendInvitesResult = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { if (it.resultCode == Activity.RESULT_OK) { val inviteData = HashMap() - inviteData["inviter"] = viewModel?.getUserData()?.value?.profile?.name ?: "" + inviteData["inviter"] = viewModel?.user?.value?.profile?.name ?: "" if (it.data?.getBooleanExtra(GroupInviteActivity.IS_EMAIL_KEY, false) == true) { val emails = it.data?.getStringArrayExtra(GroupInviteActivity.EMAILS_KEY) val invites = ArrayList>() 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 8dfdb6ec6..1ca9c25bf 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 @@ -114,7 +114,7 @@ class PartyDetailFragment : BaseFragment() { } viewModel?.getGroupData()?.observe(viewLifecycleOwner, { updateParty(it) }) - viewModel?.getUserData()?.observe(viewLifecycleOwner, { updateUser(it) }) + viewModel?.user?.observe(viewLifecycleOwner, { updateUser(it) }) viewModel?.getMembersData()?.observe(viewLifecycleOwner, { updateMembersList(it) }) } @@ -269,7 +269,7 @@ class PartyDetailFragment : BaseFragment() { } ) ?: return@forEachIndexed val viewHolder = GroupMemberViewHolder(memberView) - viewHolder.bind(member, leaderID ?: "", viewModel?.getUserData()?.value?.id) + viewHolder.bind(member, leaderID ?: "", viewModel?.user?.value?.id) viewHolder.onClickEvent = { FullProfileActivity.open(member.id ?: "") } 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 32417f81d..23551b00d 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 @@ -7,6 +7,7 @@ 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.RxErrorHandler +import com.habitrpg.android.habitica.models.inventory.Equipment import com.habitrpg.android.habitica.models.user.User import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers import io.reactivex.rxjava3.disposables.CompositeDisposable @@ -17,10 +18,13 @@ abstract class BaseViewModel(initializeComponent: Boolean = true) : ViewModel() @Inject lateinit var userRepository: UserRepository - private val user: MutableLiveData by lazy { + private val _user: MutableLiveData by lazy { loadUserFromLocal() MutableLiveData() } + val user: LiveData by lazy { + _user + } init { if (initializeComponent) { @@ -38,13 +42,13 @@ abstract class BaseViewModel(initializeComponent: Boolean = true) : ViewModel() internal val disposable = CompositeDisposable() - fun getUserData(): LiveData = user - private fun loadUserFromLocal() { - disposable.add(userRepository.getUser().observeOn(AndroidSchedulers.mainThread()).subscribe({ user.value = it }, RxErrorHandler.handleEmptyError())) + disposable.add(userRepository.getUser().observeOn(AndroidSchedulers.mainThread()) + .subscribe({ _user.value = it }, RxErrorHandler.handleEmptyError())) } fun updateUser(path: String, value: Any) { - disposable.add(userRepository.updateUser(path, value).subscribe({ }, RxErrorHandler.handleEmptyError())) + disposable.add(userRepository.updateUser(path, value) + .subscribe({ }, RxErrorHandler.handleEmptyError())) } } 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 effce0176..03d59dc03 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 @@ -20,7 +20,7 @@ class PartyViewModel(initializeComponent: Boolean) : GroupViewModel(initializeCo internal val isUserOnQuest: Boolean get() = !( - getGroupData().value?.quest?.members?.none { it.key == getUserData().value?.id } + getGroupData().value?.quest?.members?.none { it.key == user.value?.id } ?: true ) @@ -88,7 +88,7 @@ class PartyViewModel(initializeComponent: Boolean) : GroupViewModel(initializeCo } fun showParticipantButtons(): Boolean { - val user = getUserData().value + val user = user.value return !(user?.party == null || user.party?.quest == null) && !isQuestActive && user.party?.quest?.RSVPNeeded == true } 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 new file mode 100644 index 000000000..ebc740ec1 --- /dev/null +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/inventory/equipment/EquipmentOverviewViewModel.kt @@ -0,0 +1,25 @@ +package com.habitrpg.android.habitica.ui.viewmodels.inventory.equipment + +import androidx.lifecycle.SavedStateHandle +import com.habitrpg.android.habitica.components.UserComponent +import com.habitrpg.android.habitica.data.InventoryRepository +import com.habitrpg.android.habitica.helpers.RxErrorHandler +import com.habitrpg.android.habitica.models.inventory.Equipment +import com.habitrpg.android.habitica.ui.viewmodels.BaseViewModel +import javax.inject.Inject + +class EquipmentOverviewViewModel(savedStateHandle: SavedStateHandle): BaseViewModel() { + + @Inject + lateinit var inventoryRepository: InventoryRepository + + override fun inject(component: UserComponent) { + component.inject(this) + } + + fun getGear(key: String, onSuccess: (Equipment) -> Unit) { + disposable.add(inventoryRepository.getEquipment(key).subscribe( { + onSuccess(it) + }, RxErrorHandler.handleEmptyError())) + } +} From 66af24813ae546c9c0cbe89bdb39cc303cec7bf9 Mon Sep 17 00:00:00 2001 From: Phillip Thelen Date: Mon, 4 Oct 2021 15:12:34 +0200 Subject: [PATCH 02/16] Fix inviting to guilds --- Habitica/res/layout/quest_menu_view.xml | 27 +++++++++++++++++-- .../android/habitica/api/ApiService.kt | 2 +- .../android/habitica/data/ApiClient.kt | 2 +- .../android/habitica/data/SocialRepository.kt | 2 +- .../data/implementation/ApiClientImpl.kt | 2 +- .../implementation/SocialRepositoryImpl.kt | 2 +- .../ui/fragments/NavigationDrawerFragment.kt | 1 + .../social/guilds/GuildDetailFragment.kt | 17 ++++++------ .../habitica/ui/views/social/QuestMenuView.kt | 12 ++++++--- 9 files changed, 49 insertions(+), 18 deletions(-) diff --git a/Habitica/res/layout/quest_menu_view.xml b/Habitica/res/layout/quest_menu_view.xml index d519ec75e..a156a763f 100644 --- a/Habitica/res/layout/quest_menu_view.xml +++ b/Habitica/res/layout/quest_menu_view.xml @@ -36,9 +36,8 @@ android:textSize="12sp"/> + + + + \ No newline at end of file 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 795d5463c..4c9a92965 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 @@ -218,7 +218,7 @@ interface ApiService { fun seenMessages(@Path("gid") groupId: String): Flowable> @POST("groups/{gid}/invite") - fun inviteToGroup(@Path("gid") groupId: String, @Body inviteData: Map): Flowable> + fun inviteToGroup(@Path("gid") groupId: String, @Body inviteData: Map): Flowable>> @POST("groups/{gid}/reject-invite") fun rejectGroupInvite(@Path("gid") groupId: String): 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 88586a755..c3a7eed58 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 @@ -153,7 +153,7 @@ interface ApiClient { fun seenMessages(groupId: String): Flowable - fun inviteToGroup(groupId: String, inviteData: Map): Flowable + fun inviteToGroup(groupId: String, inviteData: Map): Flowable> fun rejectGroupInvite(groupId: String): Flowable 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 61da77a81..68db3419d 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 @@ -51,7 +51,7 @@ interface SocialRepository : BaseRepository { fun getGroupMembers(id: String): Flowable> fun retrieveGroupMembers(id: String, includeAllPublicFields: Boolean): Flowable> - fun inviteToGroup(id: String, inviteData: Map): Flowable + fun inviteToGroup(id: String, inviteData: Map): Flowable> fun getMember(userId: String?): Flowable fun getMemberWithUsername(username: String?): Flowable 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 c6d60fcd5..38c17e637 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 @@ -554,7 +554,7 @@ class ApiClientImpl // private OnHabitsAPIResult mResultListener; return apiService.seenMessages(groupId).compose(configureApiCallObserver()) } - override fun inviteToGroup(groupId: String, inviteData: Map): Flowable { + override fun inviteToGroup(groupId: String, inviteData: Map): Flowable> { return apiService.inviteToGroup(groupId, inviteData).compose(configureApiCallObserver()) } 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 c58fabf92..784a45634 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 @@ -241,7 +241,7 @@ class SocialRepositoryImpl(localRepository: SocialLocalRepository, apiClient: Ap .doOnNext { members -> localRepository.saveGroupMembers(id, members) } } - override fun inviteToGroup(id: String, inviteData: Map): Flowable = apiClient.inviteToGroup(id, inviteData) + override fun inviteToGroup(id: String, inviteData: Map): Flowable> = apiClient.inviteToGroup(id, inviteData) override fun getMember(userId: String?): Flowable { return if (userId == null) { 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 07fe20db1..452d898e5 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 @@ -280,6 +280,7 @@ class NavigationDrawerFragment : DialogFragment() { it.quest?.key ?: "" } .flatMapMaybe { inventoryRepository.getQuestContent(it).firstElement() } + .filter { (it.boss?.hp ?: 0) > 0 } .subscribe( { questContent = 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 1a80da036..c0b64f33c 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 @@ -112,23 +112,24 @@ class GuildDetailFragment : BaseFragment() { if (it.resultCode == Activity.RESULT_OK) { val inviteData = HashMap() inviteData["inviter"] = viewModel?.user?.value?.profile?.name ?: "" - if (it.data?.getBooleanExtra(GroupInviteActivity.IS_EMAIL_KEY, false) == true) { - val emails = it.data?.getStringArrayExtra(GroupInviteActivity.EMAILS_KEY) + val emails = it.data?.getStringArrayExtra(GroupInviteActivity.EMAILS_KEY) + if (emails != null && emails.isNotEmpty()) { val invites = ArrayList>() - emails?.forEach { email -> + emails.forEach { email -> val invite = HashMap() invite["name"] = "" invite["email"] = email invites.add(invite) } inviteData["emails"] = invites - } else { - val userIDs = it.data?.getStringArrayExtra(GroupInviteActivity.USER_IDS_KEY) - val invites = mutableListOf() - userIDs?.forEach { invites.add(it) } + } + val userIDs = it.data?.getStringArrayExtra(GroupInviteActivity.USER_IDS_KEY) + if (userIDs != null && userIDs.isNotEmpty()) { + val invites = ArrayList() + userIDs.forEach { invites.add(it) } inviteData["usernames"] = invites } - viewModel?.inviteToGroup(inviteData) + viewModel.inviteToGroup(inviteData) } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/social/QuestMenuView.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/social/QuestMenuView.kt index 4e0f0bfe9..3e80cb578 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/social/QuestMenuView.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/social/QuestMenuView.kt @@ -36,6 +36,7 @@ class QuestMenuView : LinearLayout { orientation = VERTICAL binding.heartIconView.setImageBitmap(HabiticaIconsHelper.imageOfHeartDarkBg()) + binding.rageIconView.setImageBitmap(HabiticaIconsHelper.imageOfRage()) binding.pendingDamageIconView.setImageBitmap(HabiticaIconsHelper.imageOfDamage()) @@ -50,16 +51,21 @@ class QuestMenuView : LinearLayout { fun configure(quest: Quest) { binding.healthBarView.setCurrentValue(quest.progress?.hp ?: 0.0) + binding.rageBarView.setCurrentValue(quest.progress?.rage ?: 0.0) } fun configure(questContent: QuestContent) { this.questContent = questContent binding.healthBarView.setMaxValue(questContent.boss?.hp?.toDouble() ?: 0.0) - binding.bottomView.setBackgroundColor(questContent.colors?.darkColor ?: 0) - //binding.bossArtView.setBackgroundColor(questContent.colors?.mediumColor ?: 0) - //DataBindingUtils.loadImage(binding.bossArtView, "quest_" + questContent.key) binding.bossNameView.text = questContent.boss?.name binding.typeTextView.text = context.getString(R.string.boss_quest) + + if (questContent.boss?.hasRage == true) { + binding.rageView.visibility = View.VISIBLE + binding.rageBarView.setMaxValue(questContent.boss?.rage?.value ?: 0.0) + } else { + binding.rageView.visibility = View.GONE + } } fun configure(user: User) { From 24aab1ed38bac17b78f26e6c6c670b69d8cdb498 Mon Sep 17 00:00:00 2001 From: Phillip Thelen Date: Wed, 6 Oct 2021 13:53:49 +0200 Subject: [PATCH 03/16] Finish 3.4 --- Habitica/build.gradle | 2 +- .../habitica/HabiticaPurchaseVerifier.kt | 43 ++++++++++++++----- .../data/implementation/ApiClientImpl.kt | 1 - .../habitica/helpers/AppConfigManager.kt | 3 ++ .../models/promotions/HabiticaPromotion.kt | 5 +++ .../ui/fragments/NavigationDrawerFragment.kt | 36 ++++++---------- .../social/guilds/GuildDetailFragment.kt | 2 +- fastlane/changelog.txt | 2 +- 8 files changed, 58 insertions(+), 36 deletions(-) diff --git a/Habitica/build.gradle b/Habitica/build.gradle index c924bba11..ae8b8bdab 100644 --- a/Habitica/build.gradle +++ b/Habitica/build.gradle @@ -155,7 +155,7 @@ android { buildConfigField "String", "TESTING_LEVEL", "\"production\"" resConfigs "en", "bg", "de", "en-rGB", "es", "fr", "hr-rHR", "in", "it", "iw", "ja", "ko", "lt", "nl", "pl", "pt-rBR", "pt-rPT", "ru", "tr", "zh", "zh-rTW" - versionCode 3052 + versionCode 3059 versionName "3.4" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/HabiticaPurchaseVerifier.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/HabiticaPurchaseVerifier.kt index f96c84d9d..52e234277 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/HabiticaPurchaseVerifier.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/HabiticaPurchaseVerifier.kt @@ -26,32 +26,35 @@ class HabiticaPurchaseVerifier(context: Context, apiClient: ApiClient) : BasePur private val context: Context override fun doVerify(purchases: List, requestListener: RequestListener>) { val verifiedPurchases: MutableList = ArrayList(purchases.size) + val allPurchases = purchases.toMutableList() for (purchase in purchases) { if (purchasedOrderList.contains(purchase.orderId)) { verifiedPurchases.add(purchase) - requestListener.onSuccess(verifiedPurchases) + processedPurchase(purchase, allPurchases, verifiedPurchases, requestListener) } else { when { PurchaseTypes.allGemTypes.contains(purchase.sku) -> { val validationRequest = buildValidationRequest(purchase) apiClient.validatePurchase(validationRequest).subscribe({ purchasedOrderList.add(purchase.orderId) - requestListener.onSuccess(verifiedPurchases) + verifiedPurchases.add(purchase) + processedPurchase(purchase, allPurchases, verifiedPurchases, requestListener) val giftedID = removeGift(purchase.sku) EventBus.getDefault().post(ConsumablePurchasedEvent(purchase, giftedID)) }) { throwable: Throwable -> - handleError(throwable, purchase, requestListener, verifiedPurchases) + handleError(throwable, purchase, allPurchases, requestListener, verifiedPurchases) } } PurchaseTypes.allSubscriptionNoRenewTypes.contains(purchase.sku) -> { val validationRequest = buildValidationRequest(purchase) apiClient.validateNoRenewSubscription(validationRequest).subscribe({ purchasedOrderList.add(purchase.orderId) - requestListener.onSuccess(verifiedPurchases) + verifiedPurchases.add(purchase) + processedPurchase(purchase, allPurchases, verifiedPurchases, requestListener) val giftedID = removeGift(purchase.sku) EventBus.getDefault().post(ConsumablePurchasedEvent(purchase, giftedID)) }) { throwable: Throwable -> - handleError(throwable, purchase, requestListener, verifiedPurchases) + handleError(throwable, purchase, allPurchases, requestListener, verifiedPurchases) } } PurchaseTypes.allSubscriptionTypes.contains(purchase.sku) -> { @@ -63,11 +66,11 @@ class HabiticaPurchaseVerifier(context: Context, apiClient: ApiClient) : BasePur apiClient.validateSubscription(validationRequest).subscribe({ purchasedOrderList.add(purchase.orderId) verifiedPurchases.add(purchase) - requestListener.onSuccess(verifiedPurchases) + processedPurchase(purchase, allPurchases, verifiedPurchases, requestListener) FirebaseAnalytics.getInstance(context).logEvent("user_subscribed", null) EventBus.getDefault().post(UserSubscribedEvent()) }) { throwable: Throwable -> - handleError(throwable, purchase, requestListener, verifiedPurchases) + handleError(throwable, purchase, allPurchases, requestListener, verifiedPurchases) } } } @@ -79,6 +82,22 @@ class HabiticaPurchaseVerifier(context: Context, apiClient: ApiClient) : BasePur savePendingGifts() } + private fun processedPurchase( + purchase: Purchase, + allPurchases: MutableList, + verifiedPurchases: MutableList, + requestListener: RequestListener> + ) { + allPurchases.remove(purchase) + if (allPurchases.isEmpty()) { + if (verifiedPurchases.isEmpty()) { + requestListener.onError(ResponseCodes.ERROR, Exception()) + } else { + requestListener.onSuccess(verifiedPurchases) + } + } + } + private fun buildValidationRequest(purchase: Purchase): PurchaseValidationRequest { val validationRequest = PurchaseValidationRequest() validationRequest.sku = purchase.sku @@ -92,13 +111,17 @@ class HabiticaPurchaseVerifier(context: Context, apiClient: ApiClient) : BasePur return validationRequest } - private fun handleError(throwable: Throwable, purchase: Purchase, requestListener: RequestListener>, verifiedPurchases: MutableList) { + private fun handleError(throwable: Throwable, purchase: Purchase, + allPurchases: MutableList, + requestListener: RequestListener>, + verifiedPurchases: MutableList) { (throwable as? HttpException)?.let { error -> if (error.code() == 401) { val res = apiClient.getErrorResponse(throwable) if (res.message != null && res.message == "RECEIPT_ALREADY_USED") { purchasedOrderList.add(purchase.orderId) - requestListener.onSuccess(verifiedPurchases) + verifiedPurchases.add(purchase) + processedPurchase(purchase, allPurchases, verifiedPurchases, requestListener) EventBus.getDefault().post(ConsumablePurchasedEvent(purchase)) removeGift(purchase.sku) return @@ -106,7 +129,7 @@ class HabiticaPurchaseVerifier(context: Context, apiClient: ApiClient) : BasePur } } FirebaseCrashlytics.getInstance().recordException(throwable) - requestListener.onError(ResponseCodes.ERROR, Exception()) + processedPurchase(purchase, allPurchases, verifiedPurchases, requestListener) } private fun loadPendingGifts(): MutableMap { 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 38c17e637..942b46c64 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 @@ -12,7 +12,6 @@ import com.habitrpg.android.habitica.api.GSonFactoryCreator import com.habitrpg.android.habitica.api.HostConfig import com.habitrpg.android.habitica.api.Server import com.habitrpg.android.habitica.data.ApiClient -import com.habitrpg.android.habitica.events.ConsumablePurchasedEvent import com.habitrpg.android.habitica.events.ShowConnectionProblemEvent import com.habitrpg.android.habitica.helpers.NotificationsManager import com.habitrpg.android.habitica.models.* 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 8965115dd..8f0195f5d 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 @@ -126,6 +126,9 @@ class AppConfigManager(contentRepository: ContentRepository?) { if (promo == null && remoteConfig.getString("activePromo").isNotBlank()) { promo = getHabiticaPromotionFromKey(remoteConfig.getString("activePromo"), null, null) } + if (promo?.isActive != true) { + return null + } if (promo is HabiticaWebPromotion) { promo.url = surveyURL() } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/models/promotions/HabiticaPromotion.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/models/promotions/HabiticaPromotion.kt index 0c145f841..149d4362c 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/models/promotions/HabiticaPromotion.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/models/promotions/HabiticaPromotion.kt @@ -17,6 +17,11 @@ enum class PromoType { } abstract class HabiticaPromotion { + val isActive: Boolean + get() { + val now = Date() + return startDate.before(now) && endDate.after(now) + } abstract val identifier: String abstract val promoType: PromoType 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 452d898e5..8b9228c87 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,6 +29,7 @@ import com.habitrpg.android.habitica.helpers.AppConfigManager import com.habitrpg.android.habitica.helpers.MainNavigationController import com.habitrpg.android.habitica.helpers.RxErrorHandler import com.habitrpg.android.habitica.models.WorldState +import com.habitrpg.android.habitica.models.WorldStateEvent import com.habitrpg.android.habitica.models.inventory.Item import com.habitrpg.android.habitica.models.inventory.Quest import com.habitrpg.android.habitica.models.inventory.QuestContent @@ -99,6 +100,7 @@ class NavigationDrawerFragment : DialogFragment() { private fun updateQuestDisplay() { val quest = this.quest val questContent = this.questContent + return if (quest == null || questContent == null || !quest.active) { binding?.questMenuView?.visibility = View.GONE context?.let { @@ -126,20 +128,6 @@ class NavigationDrawerFragment : DialogFragment() { } binding?.questMenuView?.setBackgroundColor(context?.getThemeColor(R.attr.colorPrimaryDark) ?: 0) - /* Reenable this once the boss art can be displayed correctly. - - val preferences = context?.getSharedPreferences("collapsible_sections", 0) - if (preferences?.getBoolean("boss_art_collapsed", false) == true) { - questMenuView.hideBossArt() - } else { - questMenuView.showBossArt() - }*/ - //binding?.questMenuView?.hideBossArt() - - /*getItemWithIdentifier(SIDEBAR_TAVERN)?.let { tavern -> - tavern.subtitle = context?.getString(R.string.active_world_boss) - adapter.updateItem(tavern) - }*/ binding?.questMenuView?.setOnClickListener { setSelection(R.id.partyFragment) /*val context = this.context @@ -239,12 +227,12 @@ class NavigationDrawerFragment : DialogFragment() { { pair -> val gearEvent = pair.first.events.firstOrNull { it.gear } createUpdatingJob("seasonal", { - gearEvent?.end?.after(Date()) == true || pair.second.isNotEmpty() + gearEvent?.isCurrentlyActive == true || pair.second.isNotEmpty() }, { val diff = (gearEvent?.end?.time ?: 0) - Date().time if (diff < (Duration.hours(1).inWholeMilliseconds)) Duration.seconds(1) else Duration.minutes(1) }) { - updateSeasonalMenuEntries(pair.first, pair.second) + updateSeasonalMenuEntries(gearEvent, pair.second) } }, RxErrorHandler.handleEmptyError() @@ -313,7 +301,7 @@ class NavigationDrawerFragment : DialogFragment() { } } - private fun updateSeasonalMenuEntries(worldState: WorldState, items: List) { + private fun updateSeasonalMenuEntries(gearEvent: WorldStateEvent?, items: List) { val market = getItemWithIdentifier(SIDEBAR_SHOPS_MARKET) ?: return if (items.isNotEmpty() && items.firstOrNull()?.event?.end?.after(Date()) == true) { market.pillText = context?.getString(R.string.something_new) @@ -326,8 +314,7 @@ class NavigationDrawerFragment : DialogFragment() { val shop = getItemWithIdentifier(SIDEBAR_SHOPS_SEASONAL) ?: return shop.pillText = context?.getString(R.string.open) - val gearEvent = worldState.events.firstOrNull { it.gear } - if (gearEvent?.end?.after(Date()) == true) { + if (gearEvent?.isCurrentlyActive == true) { shop.isVisible = true shop.subtitle = context?.getString(R.string.open_for, gearEvent.end?.getShortRemainingString()) } else { @@ -673,13 +660,18 @@ class NavigationDrawerFragment : DialogFragment() { promotedItem.pillText = context?.getString(R.string.sale) promotedItem.pillBackground = context?.let { activePromo.pillBackgroundDrawable(it) } createUpdatingJob(activePromo.promoType.name, { - activePromo.endDate.after(Date()) + activePromo.isActive }, { val diff = activePromo.endDate.time - Date().time if (diff < (Duration.hours(1).inWholeMilliseconds)) Duration.seconds(1) else Duration.minutes(1) }) { - promotedItem.subtitle = context?.getString(R.string.sale_ends_in, activePromo.endDate.getShortRemainingString()) - updateItem(promotedItem) + if (activePromo.isActive) { + promotedItem.subtitle = context?.getString(R.string.sale_ends_in, activePromo.endDate.getShortRemainingString()) + updateItem(promotedItem) + } else { + promotedItem.subtitle = null + updateItem(promotedItem) + } } } ?: run { promoItem.isVisible = false 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 c0b64f33c..093539c71 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 @@ -129,7 +129,7 @@ class GuildDetailFragment : BaseFragment() { userIDs.forEach { invites.add(it) } inviteData["usernames"] = invites } - viewModel.inviteToGroup(inviteData) + viewModel?.inviteToGroup(inviteData) } } diff --git a/fastlane/changelog.txt b/fastlane/changelog.txt index a0fb0b394..d06852bc4 100644 --- a/fastlane/changelog.txt +++ b/fastlane/changelog.txt @@ -1 +1 @@ -We’ve improved more of our notifications so they bring you to the relevant screen when tapped. When customizing your avatar, you’ll be able to see your avatar updating in real time as you select different options. You can also see your current Gems when gifting Gems from your balance now. We’ve fixed a few bugs too, including one where tasks wouldn’t load when the app is left running in the background, the stats widget works again, and another where Dailies wouldn’t filter properly on launch. +In this update we’ve fixed some bugs, added more seasonal event support, and made a few quality of life improvements! Sending Guild and Party invites through email should work more reliably. We’ve improved task drag and drop logic when filters are applied to make the order more consistent. Habit streak is now referred to as ‘Habit Counter’ to better reflect the actual behavior. Issues with subscriptions not cancelling fully or getting errors when buying multiple Gem packages should be resolved. From f4179c575044f0a5a789ac0101b01150e2b94054 Mon Sep 17 00:00:00 2001 From: Phillip Thelen Date: Thu, 7 Oct 2021 17:20:52 +0200 Subject: [PATCH 04/16] small changes to IAP code --- Habitica/build.gradle | 4 ++-- .../habitica/HabiticaPurchaseVerifier.kt | 16 +++++++-------- .../habitica/helpers/PurchaseHandler.kt | 20 +++++++++++-------- .../equipment/EquipmentOverviewFragment.kt | 19 +++++++----------- .../purchases/GemsPurchaseFragment.kt | 7 +++++-- .../purchases/GiftPurchaseGemsFragment.kt | 7 +++++-- .../purchases/SubscriptionFragment.kt | 14 ++++++++----- 7 files changed, 48 insertions(+), 39 deletions(-) diff --git a/Habitica/build.gradle b/Habitica/build.gradle index ae8b8bdab..c861e0679 100644 --- a/Habitica/build.gradle +++ b/Habitica/build.gradle @@ -155,8 +155,8 @@ android { buildConfigField "String", "TESTING_LEVEL", "\"production\"" resConfigs "en", "bg", "de", "en-rGB", "es", "fr", "hr-rHR", "in", "it", "iw", "ja", "ko", "lt", "nl", "pl", "pt-rBR", "pt-rPT", "ru", "tr", "zh", "zh-rTW" - versionCode 3059 - versionName "3.4" + versionCode 3063 + versionName "3.4.0.1" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/HabiticaPurchaseVerifier.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/HabiticaPurchaseVerifier.kt index 52e234277..a357088b1 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/HabiticaPurchaseVerifier.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/HabiticaPurchaseVerifier.kt @@ -2,6 +2,7 @@ package com.habitrpg.android.habitica import android.content.Context import android.content.SharedPreferences +import androidx.core.content.edit import androidx.preference.PreferenceManager import com.google.firebase.analytics.FirebaseAnalytics import com.google.firebase.crashlytics.FirebaseCrashlytics @@ -76,10 +77,9 @@ class HabiticaPurchaseVerifier(context: Context, apiClient: ApiClient) : BasePur } } } - val edit = preferences?.edit() - edit?.putStringSet(PURCHASED_PRODUCTS_KEY, purchasedOrderList) - edit?.apply() - savePendingGifts() + preferences?.edit { + edit?.putStringSet(PURCHASED_PRODUCTS_KEY, purchasedOrderList) + } } private fun processedPurchase( @@ -154,6 +154,7 @@ class HabiticaPurchaseVerifier(context: Context, apiClient: ApiClient) : BasePur private const val PENDING_GIFTS_KEY = "PENDING_GIFTS" private var pendingGifts: MutableMap = HashMap() private var preferences: SharedPreferences? = null + fun addGift(sku: String?, userID: String?) { pendingGifts[sku] = userID savePendingGifts() @@ -168,10 +169,9 @@ class HabiticaPurchaseVerifier(context: Context, apiClient: ApiClient) : BasePur private fun savePendingGifts() { val jsonObject = JSONObject(pendingGifts as Map<*, *>) val jsonString = jsonObject.toString() - val editor = preferences?.edit() - editor?.remove(PENDING_GIFTS_KEY) - editor?.putString(PENDING_GIFTS_KEY, jsonString) - editor?.apply() + preferences?.edit { + putString(PENDING_GIFTS_KEY, jsonString) + } } } 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 45a415a1e..65fb1736b 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 @@ -4,6 +4,8 @@ import android.app.Activity import android.content.Intent import com.habitrpg.android.habitica.HabiticaBaseApplication import com.habitrpg.android.habitica.proxy.AnalyticsManager +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext import org.solovyev.android.checkout.* import java.util.* import kotlin.coroutines.resume @@ -74,16 +76,18 @@ open class PurchaseHandler(activity: Activity, val analyticsManager: AnalyticsMa return purchases.skus.firstOrNull() } - private suspend fun loadInventory(type: String, skus: List): Inventory.Products? = suspendCoroutine { cont -> - val request = Inventory.Request.create().loadAllPurchases().loadSkus(type, skus) - try { - inventory?.load(request) { - cont.resume(it) + private suspend fun loadInventory(type: String, skus: List): Inventory.Products? = withContext(Dispatchers.Main) { + suspendCoroutine { cont -> + val request = Inventory.Request.create().loadAllPurchases().loadSkus(type, skus) + try { + inventory?.load(request) { + cont.resume(it) + } + } catch (e: NullPointerException) { + cont.resumeWithException(e) } - } catch (e: NullPointerException) { - cont.resumeWithException(e) + if (inventory == null) cont.resume(null) } - if (inventory == null) cont.resume(null) } fun purchaseSubscription(sku: Sku, onSuccess: (() -> Unit)) { 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 index 6a015a12f..2d0fb80de 100644 --- 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 @@ -12,6 +12,7 @@ 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() { @@ -47,13 +48,7 @@ class EquipmentOverviewFragment : BaseMainFragment(), GemPurchaseActivity.CheckoutFragment { @@ -98,8 +99,10 @@ class GemsPurchaseFragment : BaseFragment(), GemPurc override fun setupCheckout() { CoroutineScope(Dispatchers.IO).launch { val skus = purchaseHandler?.getAllGemSKUs() - for (sku in skus ?: emptyList()) { - updateButtonLabel(sku.id.code, sku.price) + withContext(Dispatchers.Main) { + for (sku in skus ?: emptyList()) { + updateButtonLabel(sku.id.code, sku.price) + } } } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/purchases/GiftPurchaseGemsFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/purchases/GiftPurchaseGemsFragment.kt index d8f400fb5..d77903a25 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/purchases/GiftPurchaseGemsFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/purchases/GiftPurchaseGemsFragment.kt @@ -18,6 +18,7 @@ import com.habitrpg.android.habitica.ui.fragments.BaseFragment import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import javax.inject.Inject class GiftPurchaseGemsFragment : BaseFragment() { @@ -63,8 +64,10 @@ class GiftPurchaseGemsFragment : BaseFragment() fun setupCheckout() { CoroutineScope(Dispatchers.IO).launch { val skus = purchaseHandler?.getAllGemSKUs() - for (sku in skus ?: emptyList()) { - updateButtonLabel(sku.id.code, sku.price) + withContext(Dispatchers.Main) { + for (sku in skus ?: emptyList()) { + updateButtonLabel(sku.id.code, sku.price) + } } } } 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 ba4a1381d..d35565f89 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 @@ -35,6 +35,7 @@ import com.habitrpg.android.habitica.ui.views.subscriptions.SubscriptionOptionVi import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import org.greenrobot.eventbus.Subscribe import org.solovyev.android.checkout.Inventory import org.solovyev.android.checkout.Purchase @@ -145,12 +146,15 @@ class SubscriptionFragment : BaseFragment(), GemPur override fun setupCheckout() { CoroutineScope(Dispatchers.IO).launch { val subscriptions = purchaseHandler?.getAllSubscriptionProducts() ?: return@launch - for (sku in subscriptions.skus) { - updateButtonLabel(sku, sku.price, subscriptions) + skus = subscriptions.skus + withContext(Dispatchers.Main) { + for (sku in subscriptions.skus) { + updateButtonLabel(sku, sku.price, subscriptions) + } + selectSubscription(PurchaseTypes.Subscription1Month) + hasLoadedSubscriptionOptions = true + updateSubscriptionInfo() } - selectSubscription(PurchaseTypes.Subscription1Month) - hasLoadedSubscriptionOptions = true - updateSubscriptionInfo() } } From 2affda87a8f5befd7766af5be34e92bc48d38377 Mon Sep 17 00:00:00 2001 From: Phillip Thelen Date: Fri, 8 Oct 2021 10:58:04 +0200 Subject: [PATCH 05/16] finish bugfix update --- Habitica/build.gradle | 4 ++-- Habitica/res/values-en-rGB/strings.xml | 2 +- Habitica/res/values/strings.xml | 2 +- .../android/habitica/HabiticaPurchaseVerifier.kt | 2 +- .../habitica/ui/fragments/NavigationDrawerFragment.kt | 10 +++------- 5 files changed, 8 insertions(+), 12 deletions(-) diff --git a/Habitica/build.gradle b/Habitica/build.gradle index c861e0679..b24331949 100644 --- a/Habitica/build.gradle +++ b/Habitica/build.gradle @@ -155,8 +155,8 @@ android { buildConfigField "String", "TESTING_LEVEL", "\"production\"" resConfigs "en", "bg", "de", "en-rGB", "es", "fr", "hr-rHR", "in", "it", "iw", "ja", "ko", "lt", "nl", "pl", "pt-rBR", "pt-rPT", "ru", "tr", "zh", "zh-rTW" - versionCode 3063 - versionName "3.4.0.1" + versionCode 3067 + versionName "3.4.0.2" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } diff --git a/Habitica/res/values-en-rGB/strings.xml b/Habitica/res/values-en-rGB/strings.xml index eb3fd6d5f..807f162bb 100644 --- a/Habitica/res/values-en-rGB/strings.xml +++ b/Habitica/res/values-en-rGB/strings.xml @@ -1101,5 +1101,5 @@ This promotion only applies during the limited time event. This event starts on October 29th at 8:00 AM EDT (12:00 UTC) and will end November 2nd at 8:00 PM EDT (00:00 UTC). The promo offer is only available when buying Gems for yourself. Between October 29th and November 2nd, simply purchase any Gem bundle like usual and your account will be credited with the promotional amount of Gems. More Gems to spend, share, or save for any future releases! View Gem Bundles - The Fall Gala is in full swing so we thought it was the perfect time to introduce our first ever Gem Sale! Now you will get more Gems with each purchase than ever before. + The Fall Gala is in full swing so we thought it was the perfect time for a Gem Sale! Now you will get more Gems with each purchase than ever before. diff --git a/Habitica/res/values/strings.xml b/Habitica/res/values/strings.xml index 7c6a4859d..784016d1d 100644 --- a/Habitica/res/values/strings.xml +++ b/Habitica/res/values/strings.xml @@ -1114,7 +1114,7 @@ How it works Limitations Between %s and %s, simply purchase any Gem bundle like usual and your account will be credited with the promotional amount of Gems. More Gems to spend, share, or save for any future releases! - The Fall Gala is in full swing so we thought it was the perfect time to introduce our first ever Gem Sale! Now you will get more Gems with each purchase than ever before. + The Fall Gala is in full swing so we thought it was the perfect time for a Gem Sale! Now you will get more Gems with each purchase than ever before. View Gem Bundles Between %s and %s, simply purchase any Gem bundle like usual and your account will be credited with the promotional amount of Gems. More Gems to spend, share, or save for any future releases! This promotion only applies during the limited time event. This event starts on %s (12:00 UTC) and will end %s (00:00 UTC). The promo offer is only available when buying Gems for yourself. diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/HabiticaPurchaseVerifier.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/HabiticaPurchaseVerifier.kt index a357088b1..5d062fcb8 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/HabiticaPurchaseVerifier.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/HabiticaPurchaseVerifier.kt @@ -78,7 +78,7 @@ class HabiticaPurchaseVerifier(context: Context, apiClient: ApiClient) : BasePur } } preferences?.edit { - edit?.putStringSet(PURCHASED_PRODUCTS_KEY, purchasedOrderList) + putStringSet(PURCHASED_PRODUCTS_KEY, purchasedOrderList) } } 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 8b9228c87..cf625dd3d 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 @@ -642,13 +642,9 @@ class NavigationDrawerFragment : DialogFragment() { activePromo = configManager.activePromo() val promoItem = getItemWithIdentifier(SIDEBAR_PROMO) ?: return activePromo?.let { activePromo -> - if (sharedPreferences.getBoolean("hide${activePromo.identifier}", false)) { - promoItem.isVisible = true - adapter.activePromo = activePromo - } else { - promoItem.isVisible = false - } - + promoItem.isVisible = + !sharedPreferences.getBoolean("hide${activePromo.identifier}", false) + adapter.activePromo = activePromo var promotedItem: HabiticaDrawerItem? = null if (activePromo.promoType == PromoType.GEMS_AMOUNT || activePromo.promoType == PromoType.GEMS_PRICE) { promotedItem = getItemWithIdentifier(SIDEBAR_GEMS) From 8a678620e350b990d15fec3e341f5154cb6be9ee Mon Sep 17 00:00:00 2001 From: Phillip Thelen Date: Fri, 8 Oct 2021 11:54:09 +0200 Subject: [PATCH 06/16] Fixes #1605 Fixes #1636 --- Habitica/build.gradle | 10 +++++----- .../android/habitica/ui/activities/PrefsActivity.kt | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Habitica/build.gradle b/Habitica/build.gradle index b24331949..6d0268738 100644 --- a/Habitica/build.gradle +++ b/Habitica/build.gradle @@ -47,8 +47,8 @@ dependencies { implementation 'com.squareup.retrofit2:adapter-rxjava3:2.9.0' //Dependency Injection - implementation 'com.google.dagger:dagger:2.38' - kapt 'com.google.dagger:dagger-compiler:2.38' + implementation 'com.google.dagger:dagger:2.39.1' + kapt 'com.google.dagger:dagger-compiler:2.39.1' compileOnly 'javax.annotation:javax.annotation-api:1.3.2' compileOnly 'com.github.pengrad:jdk9-deps:1.0' //App Compatibility and Material Design @@ -82,8 +82,8 @@ dependencies { //Analytics implementation 'com.amplitude:android-sdk:2.30.0' // Image Management Library - implementation("io.coil-kt:coil:1.2.2") - implementation("io.coil-kt:coil-gif:1.2.2") + implementation("io.coil-kt:coil:1.4.0") + implementation("io.coil-kt:coil-gif:1.4.0") //Tests testImplementation 'io.kotest:kotest-runner-junit5:4.6.2' @@ -156,7 +156,7 @@ android { resConfigs "en", "bg", "de", "en-rGB", "es", "fr", "hr-rHR", "in", "it", "iw", "ja", "ko", "lt", "nl", "pl", "pt-rBR", "pt-rPT", "ru", "tr", "zh", "zh-rTW" versionCode 3067 - versionName "3.4.0.2" + versionName "3.4.1" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/PrefsActivity.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/PrefsActivity.kt index 8cfa2adde..b468fe561 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/PrefsActivity.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/PrefsActivity.kt @@ -20,7 +20,7 @@ class PrefsActivity : BaseActivity(), PreferenceFragmentCompat.OnPreferenceStart setupToolbar(findViewById(R.id.toolbar)) supportFragmentManager.beginTransaction() - .add(R.id.fragment_container, PreferencesFragment()) + .replace(R.id.fragment_container, PreferencesFragment()) .commit() } From 0fa134632ccfe8520d3cd86656ec6df959392e14 Mon Sep 17 00:00:00 2001 From: Phillip Thelen Date: Fri, 8 Oct 2021 13:45:19 +0200 Subject: [PATCH 07/16] Improve notification handling --- .../GroupActivityNotification.kt | 7 ++- .../GuildInviteLocalNotification.kt | 15 +++--- .../HabiticaLocalNotification.kt | 8 +-- .../PartyInviteLocalNotification.kt | 15 +++--- .../QuestInviteLocalNotification.kt | 10 ++-- ...ReceivedPrivateMessageLocalNotification.kt | 7 ++- .../habitica/interactors/NotifyUserUseCase.kt | 20 +++---- .../LocalNotificationActionReceiver.kt | 52 ++++++++++++++----- .../habitica/receivers/TaskReceiver.kt | 20 +++++-- 9 files changed, 104 insertions(+), 50 deletions(-) diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/notifications/GroupActivityNotification.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/notifications/GroupActivityNotification.kt index 2b80cb16f..2b4db0b6e 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/notifications/GroupActivityNotification.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/notifications/GroupActivityNotification.kt @@ -1,5 +1,6 @@ package com.habitrpg.android.habitica.helpers.notifications +import android.app.Notification import android.app.NotificationManager import android.app.PendingIntent import android.content.Context @@ -40,6 +41,7 @@ class GroupActivityNotification(context: Context, identifier: String?) : Habitic oldMessages.add(data) return super.configureNotificationBuilder(data) .setStyle(style) + .setCategory(Notification.CATEGORY_MESSAGE) .setExtras(bundleOf(Pair("messages", bundleOf(Pair("messages", oldMessages))))) } @@ -55,8 +57,8 @@ class GroupActivityNotification(context: Context, identifier: String?) : Habitic ) } - override fun setNotificationActions(data: Map) { - super.setNotificationActions(data) + override fun setNotificationActions(notificationId: Int, data: Map) { + super.setNotificationActions(notificationId, data) val groupID = data["groupID"] ?: return val actionName = context.getString(R.string.group_message_reply) @@ -68,6 +70,7 @@ class GroupActivityNotification(context: Context, identifier: String?) : Habitic val intent = Intent(context, LocalNotificationActionReceiver::class.java) intent.action = actionName intent.putExtra("groupID", groupID) + intent.putExtra("NOTIFICATION_ID", notificationId) val replyPendingIntent: PendingIntent = PendingIntent.getBroadcast( context, groupID.hashCode(), diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/notifications/GuildInviteLocalNotification.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/notifications/GuildInviteLocalNotification.kt index e9e3f2ecc..80c5e0421 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/notifications/GuildInviteLocalNotification.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/notifications/GuildInviteLocalNotification.kt @@ -16,16 +16,18 @@ class GuildInviteLocalNotification(context: Context, identifier: String?) : Habi intent.putExtra("groupID", data?.get("groupID")) } - override fun setNotificationActions(data: Map) { - super.setNotificationActions(data) + override fun setNotificationActions(notificationId: Int, data: Map) { + super.setNotificationActions(notificationId, data) val res = context.resources val acceptInviteIntent = Intent(context, LocalNotificationActionReceiver::class.java) acceptInviteIntent.action = res.getString(R.string.accept_guild_invite) - acceptInviteIntent.putExtra("groupID", this.data?.get("groupID")) + val groupID = data.get("groupID") + acceptInviteIntent.putExtra("groupID", groupID) + acceptInviteIntent.putExtra("NOTIFICATION_ID", notificationId) val pendingIntentAccept = PendingIntent.getBroadcast( context, - 3000, + groupID.hashCode(), acceptInviteIntent, PendingIntent.FLAG_UPDATE_CURRENT ) @@ -33,10 +35,11 @@ class GuildInviteLocalNotification(context: Context, identifier: String?) : Habi val rejectInviteIntent = Intent(context, LocalNotificationActionReceiver::class.java) rejectInviteIntent.action = res.getString(R.string.reject_guild_invite) - rejectInviteIntent.putExtra("groupID", this.data?.get("groupID")) + rejectInviteIntent.putExtra("groupID", groupID) + acceptInviteIntent.putExtra("NOTIFICATION_ID", notificationId) val pendingIntentReject = PendingIntent.getBroadcast( context, - 2000, + groupID.hashCode() + 1, rejectInviteIntent, PendingIntent.FLAG_UPDATE_CURRENT ) 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 b85a6369e..f44f7d0b8 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 @@ -44,19 +44,21 @@ abstract class HabiticaLocalNotification(protected var context: Context, protect notificationBuilder = notificationBuilder.setContentText(message) } - this.setNotificationActions(data) + val notificationId = getNotificationID(data) + this.setNotificationActions(notificationId, data) val notificationManager = NotificationManagerCompat.from(context) - notificationManager.notify(getNotificationID(data), notificationBuilder.build()) + notificationManager.notify(notificationId, notificationBuilder.build()) } fun setExtras(data: Map) { this.data = data } - protected open fun setNotificationActions(data: Map) { + protected open fun setNotificationActions(notificationId: Int, data: Map) { val intent = Intent(context, MainActivity::class.java) configureMainIntent(intent) + intent.putExtra("NOTIFICATION_ID", notificationId) val pendingIntent = PendingIntent.getActivity( context, 3000, diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/notifications/PartyInviteLocalNotification.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/notifications/PartyInviteLocalNotification.kt index 14ddb6ebd..21c0cc3f7 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/notifications/PartyInviteLocalNotification.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/notifications/PartyInviteLocalNotification.kt @@ -11,16 +11,18 @@ import com.habitrpg.android.habitica.receivers.LocalNotificationActionReceiver */ class PartyInviteLocalNotification(context: Context, identifier: String?) : HabiticaLocalNotification(context, identifier) { - override fun setNotificationActions(data: Map) { - super.setNotificationActions(data) + override fun setNotificationActions(notificationId: Int, data: Map) { + super.setNotificationActions(notificationId, data) val res = context.resources val acceptInviteIntent = Intent(context, LocalNotificationActionReceiver::class.java) acceptInviteIntent.action = res.getString(R.string.accept_party_invite) - acceptInviteIntent.putExtra("groupID", this.data?.get("groupID")) + val groupID = data.get("groupID") + acceptInviteIntent.putExtra("groupID", groupID) + acceptInviteIntent.putExtra("NOTIFICATION_ID", notificationId) val pendingIntentAccept = PendingIntent.getBroadcast( context, - 3000, + groupID.hashCode(), acceptInviteIntent, PendingIntent.FLAG_UPDATE_CURRENT ) @@ -28,10 +30,11 @@ class PartyInviteLocalNotification(context: Context, identifier: String?) : Habi val rejectInviteIntent = Intent(context, LocalNotificationActionReceiver::class.java) rejectInviteIntent.action = res.getString(R.string.reject_party_invite) - rejectInviteIntent.putExtra("groupID", this.data?.get("groupID")) + rejectInviteIntent.putExtra("groupID", groupID) + rejectInviteIntent.putExtra("NOTIFICATION_ID", notificationId) val pendingIntentReject = PendingIntent.getBroadcast( context, - 2000, + groupID.hashCode() + 1, rejectInviteIntent, PendingIntent.FLAG_UPDATE_CURRENT ) diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/notifications/QuestInviteLocalNotification.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/notifications/QuestInviteLocalNotification.kt index 2f347a31a..1c3bb5906 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/notifications/QuestInviteLocalNotification.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/notifications/QuestInviteLocalNotification.kt @@ -15,15 +15,16 @@ class QuestInviteLocalNotification(context: Context, identifier: String?) : Habi return 1000 } - override fun setNotificationActions(data: Map) { - super.setNotificationActions(data) + override fun setNotificationActions(notificationId: Int, data: Map) { + super.setNotificationActions(notificationId, data) val res = context.resources val acceptInviteIntent = Intent(context, LocalNotificationActionReceiver::class.java) acceptInviteIntent.action = res.getString(R.string.accept_quest_invite) + acceptInviteIntent.putExtra("NOTIFICATION_ID", notificationId) val pendingIntentAccept = PendingIntent.getBroadcast( context, - 3000, + 3001, acceptInviteIntent, PendingIntent.FLAG_UPDATE_CURRENT ) @@ -31,9 +32,10 @@ class QuestInviteLocalNotification(context: Context, identifier: String?) : Habi val rejectInviteIntent = Intent(context, LocalNotificationActionReceiver::class.java) rejectInviteIntent.action = res.getString(R.string.reject_quest_invite) + rejectInviteIntent.putExtra("NOTIFICATION_ID", notificationId) val pendingIntentReject = PendingIntent.getBroadcast( context, - 2000, + 2001, rejectInviteIntent, PendingIntent.FLAG_UPDATE_CURRENT ) diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/notifications/ReceivedPrivateMessageLocalNotification.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/notifications/ReceivedPrivateMessageLocalNotification.kt index 1f51863f9..8b8502254 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/notifications/ReceivedPrivateMessageLocalNotification.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/notifications/ReceivedPrivateMessageLocalNotification.kt @@ -1,5 +1,6 @@ package com.habitrpg.android.habitica.helpers.notifications +import android.app.Notification import android.app.NotificationManager import android.app.PendingIntent import android.content.Context @@ -36,6 +37,7 @@ class ReceivedPrivateMessageLocalNotification(context: Context, identifier: Stri } notification = notification .setContentTitle(notificationTitle) + .setCategory(Notification.CATEGORY_MESSAGE) .setStyle(style) title = null } else { @@ -48,8 +50,8 @@ class ReceivedPrivateMessageLocalNotification(context: Context, identifier: Stri return data["senderName"].hashCode() } - override fun setNotificationActions(data: Map) { - super.setNotificationActions(data) + override fun setNotificationActions(notificationId: Int, data: Map) { + super.setNotificationActions(notificationId, data) val senderID = data["replyTo"] ?: return val actionName = context.getString(R.string.inbox_message_reply) @@ -61,6 +63,7 @@ class ReceivedPrivateMessageLocalNotification(context: Context, identifier: Stri val intent = Intent(context, LocalNotificationActionReceiver::class.java) intent.action = actionName intent.putExtra("senderID", senderID) + intent.putExtra("NOTIFICATION_ID", notificationId) val replyPendingIntent: PendingIntent = PendingIntent.getBroadcast( context, senderID.hashCode(), 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 5187fb51a..7145fe98b 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 @@ -109,28 +109,28 @@ constructor( return textView } - fun getNotificationAndAddStatsToUserAsText(xp: Double, hp: Double, gold: Double, mp: Double): Pair { + fun getNotificationAndAddStatsToUserAsText(xp: Double?, hp: Double?, gold: Double?, mp: Double?): Pair { val builder = SpannableStringBuilder() var displayType = SnackbarDisplayType.NORMAL - if (xp > 0) { - builder.append(" + ").append(xp.round(2).toString()).append(" Exp") + if ((xp ?: 0.0) > 0) { + builder.append(" + ").append(xp?.round(2).toString()).append(" Exp") } if (hp != 0.0) { displayType = SnackbarDisplayType.FAILURE - builder.append(" - ").append(abs(hp.round(2)).toString()).append(" Health") + builder.append(" - ").append(abs(hp?.round(2) ?: 0.0).toString()).append(" Health") } if (gold != 0.0) { - if (gold > 0) { - builder.append(" + ").append(gold.round(2).toString()) - } else if (gold < 0) { + if ((gold ?: 0.0) > 0) { + builder.append(" + ").append(gold?.round(2).toString()) + } else if ((gold ?: 0.0) < 0) { displayType = SnackbarDisplayType.FAILURE - builder.append(" - ").append(abs(gold.round(2)).toString()) + builder.append(" - ").append(abs(gold?.round(2) ?: 0.0).toString()) } builder.append(" Gold") } - if (mp > 0) { - builder.append(" + ").append(mp.round(2).toString()).append(" Mana") + if ((mp ?: 0.0) > 0) { + builder.append(" + ").append(mp?.round(2).toString()).append(" Mana") } return Pair(builder, displayType) 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 b29ce5103..5bd4483ff 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 @@ -4,6 +4,9 @@ import android.app.NotificationManager import android.content.BroadcastReceiver import android.content.Context import android.content.Intent +import android.text.Spannable +import android.text.SpannableStringBuilder +import android.widget.Toast import androidx.core.app.NotificationManagerCompat import androidx.core.app.RemoteInput import com.habitrpg.android.habitica.HabiticaBaseApplication @@ -13,37 +16,44 @@ import com.habitrpg.android.habitica.data.SocialRepository import com.habitrpg.android.habitica.data.TaskRepository import com.habitrpg.android.habitica.data.UserRepository import com.habitrpg.android.habitica.helpers.RxErrorHandler +import com.habitrpg.android.habitica.interactors.NotifyUserUseCase import com.habitrpg.android.habitica.models.user.User import javax.inject.Inject class LocalNotificationActionReceiver : BroadcastReceiver() { @Inject lateinit var userRepository: UserRepository + @Inject lateinit var socialRepository: SocialRepository + @Inject lateinit var taskRepository: TaskRepository + @Inject lateinit var apiClient: ApiClient private var user: User? = null - private var groupID: String? = null - private var senderID: String? = null + private val groupID: String? + get() = intent?.extras?.getString("groupID") + private val senderID: String? + get() = intent?.extras?.getString("senderID") + private val taskID: String? + get() = intent?.extras?.getString("taskID") private var context: Context? = null private var intent: Intent? = null override fun onReceive(context: Context, intent: Intent) { HabiticaBaseApplication.userComponent?.inject(this) this.intent = intent - groupID = intent.extras?.getString("groupID") - senderID = intent.extras?.getString("senderID") this.context = context handleLocalNotificationAction(intent.action) } private fun handleLocalNotificationAction(action: String?) { - val notificationManager = this.context?.getSystemService(Context.NOTIFICATION_SERVICE) as? NotificationManager - notificationManager?.cancelAll() + val notificationManager = + this.context?.getSystemService(Context.NOTIFICATION_SERVICE) as? NotificationManager + notificationManager?.cancel(intent?.extras?.getInt("NOTIFICATION_ID") ?: -1) when (action) { context?.getString(R.string.accept_party_invite) -> { groupID?.let { @@ -52,7 +62,8 @@ class LocalNotificationActionReceiver : BroadcastReceiver() { } context?.getString(R.string.reject_party_invite) -> { groupID?.let { - socialRepository.rejectGroupInvite(it).subscribe({ }, RxErrorHandler.handleEmptyError()) + socialRepository.rejectGroupInvite(it) + .subscribe({ }, RxErrorHandler.handleEmptyError()) } } context?.getString(R.string.accept_quest_invite) -> { @@ -68,7 +79,8 @@ class LocalNotificationActionReceiver : BroadcastReceiver() { } context?.getString(R.string.reject_guild_invite) -> { groupID?.let { - socialRepository.rejectGroupInvite(it).subscribe({ }, RxErrorHandler.handleEmptyError()) + socialRepository.rejectGroupInvite(it) + .subscribe({ }, RxErrorHandler.handleEmptyError()) } } context?.getString(R.string.group_message_reply) -> { @@ -76,7 +88,9 @@ class LocalNotificationActionReceiver : BroadcastReceiver() { getMessageText(context?.getString(R.string.group_message_reply))?.let { message -> socialRepository.postGroupChat(it, message).subscribe( { - context?.let { c -> NotificationManagerCompat.from(c).cancel(it.hashCode()) } + context?.let { c -> + NotificationManagerCompat.from(c).cancel(it.hashCode()) + } }, RxErrorHandler.handleEmptyError() ) @@ -86,19 +100,33 @@ class LocalNotificationActionReceiver : BroadcastReceiver() { context?.getString(R.string.inbox_message_reply) -> { senderID?.let { getMessageText(context?.getString(R.string.inbox_message_reply))?.let { message -> - socialRepository.postPrivateMessage(it, message).subscribe({ }, RxErrorHandler.handleEmptyError()) + socialRepository.postPrivateMessage(it, message) + .subscribe({ }, RxErrorHandler.handleEmptyError()) } } } context?.getString(R.string.complete_task_action) -> { - intent?.extras?.getString("taskID")?.let { + taskID?.let { taskRepository.taskChecked(null, it, up = true, force = false) { - }.subscribe({}, RxErrorHandler.handleEmptyError()) + }.subscribe({ + val pair = NotifyUserUseCase.getNotificationAndAddStatsToUserAsText( + it?.experienceDelta, + it?.healthDelta, + it?.goldDelta, + it?.manaDelta + ) + showToast(pair.first) + }, RxErrorHandler.handleEmptyError()) } } } } + private fun showToast(text: Spannable) { + val toast = Toast.makeText(context, text, Toast.LENGTH_LONG) + toast.show() + } + private fun getMessageText(key: String?): String? { return RemoteInput.getResultsFromIntent(intent)?.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 85743da84..2425fd7f9 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 @@ -1,10 +1,12 @@ package com.habitrpg.android.habitica.receivers +import android.app.Notification import android.app.PendingIntent import android.content.BroadcastReceiver import android.content.Context import android.content.Intent import android.media.RingtoneManager +import android.os.Build import androidx.core.app.NotificationCompat import androidx.core.app.NotificationManagerCompat import com.habitrpg.android.habitica.HabiticaBaseApplication @@ -62,21 +64,29 @@ class TaskReceiver : BroadcastReceiver() { val pendingIntent = PendingIntent.getActivity(context, System.currentTimeMillis().toInt(), intent, 0) val soundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION) - val notificationBuilder = NotificationCompat.Builder(context, "default") + var notificationBuilder = NotificationCompat.Builder(context, "default") .setSmallIcon(R.drawable.ic_gryphon_white) .setContentTitle(task.text) + .setStyle(NotificationCompat.BigTextStyle() + .bigText(task.notes)) .setPriority(NotificationCompat.PRIORITY_MAX) .setSound(soundUri) .setAutoCancel(true) .setContentIntent(pendingIntent) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + notificationBuilder = notificationBuilder.setCategory(Notification.CATEGORY_REMINDER) + } + if (task.type == Task.TYPE_DAILY || task.type == Task.TYPE_TODO) { - val completeIntent = Intent(context, LocalNotificationActionReceiver::class.java) - completeIntent.action = context.getString(R.string.complete_task_action) - completeIntent.putExtra("taskID", task.id) + val completeIntent = Intent(context, LocalNotificationActionReceiver::class.java).apply { + action = context.getString(R.string.complete_task_action) + putExtra("taskID", task.id) + putExtra("NOTIFICATION_ID", task.id.hashCode()) + } val pendingIntentComplete = PendingIntent.getBroadcast( context, - 3000, + task.id.hashCode(), completeIntent, PendingIntent.FLAG_UPDATE_CURRENT ) From 9f0f49291a3b8d83f6fb5e892fa7c2ca008e5f91 Mon Sep 17 00:00:00 2001 From: Phillip Thelen Date: Fri, 8 Oct 2021 16:35:10 +0200 Subject: [PATCH 08/16] Fixes #1625 --- .../local/implementation/RealmTagLocalRepository.kt | 8 +++++--- .../local/implementation/RealmUserLocalRepository.kt | 11 +++++++++++ 2 files changed, 16 insertions(+), 3 deletions(-) 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 28e0e317d..8f1e93c8c 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 @@ -8,11 +8,13 @@ import io.realm.Realm class RealmTagLocalRepository(realm: Realm) : RealmBaseLocalRepository(realm), TagLocalRepository { override fun deleteTag(tagID: String) { - val tag = realm.where(Tag::class.java).equalTo("id", tagID).findFirst() - executeTransaction { tag?.deleteFromRealm() } + val tags = realm.where(Tag::class.java).equalTo("id", tagID).findAll() + executeTransaction { tags.deleteAllFromRealm() } } override fun getTags(userId: String): Flowable> { - return RxJavaBridge.toV3Flowable(realm.where(Tag::class.java).equalTo("userId", userId).findAll().asFlowable()) + return RxJavaBridge.toV3Flowable( + realm.where(Tag::class.java).equalTo("userId", userId).findAll().asFlowable() + ) } } 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 3d9a8a4e7..455c2b00d 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 @@ -90,6 +90,17 @@ class RealmUserLocalRepository(realm: Realm) : RealmBaseLocalRepository(realm), } } executeTransaction { realm1 -> realm1.insertOrUpdate(user) } + removeOldTags(user.id ?: "", user.tags) + } + + private fun removeOldTags(userId: String, onlineTags: List) { + val tags = realm.where(Tag::class.java).equalTo("userId", userId).findAll().createSnapshot() + val tagsToDelete = tags.filterNot { onlineTags.contains(it) } + executeTransaction { + for (tag in tagsToDelete) { + tag.deleteFromRealm() + } + } } override fun saveMessages(messages: List) { From 197e2f0a35e02fd2b98be8693dc4812879883d3a Mon Sep 17 00:00:00 2001 From: Phillip Thelen Date: Mon, 11 Oct 2021 12:03:11 +0200 Subject: [PATCH 09/16] Improve language handling. Fixes #1587 --- Habitica/build.gradle | 2 +- .../habitica/HabiticaBaseApplication.kt | 20 +++++++++++++++++++ .../habitica/ui/activities/BaseActivity.kt | 15 ++++++++++++-- 3 files changed, 34 insertions(+), 3 deletions(-) diff --git a/Habitica/build.gradle b/Habitica/build.gradle index 6d0268738..9d83d954c 100644 --- a/Habitica/build.gradle +++ b/Habitica/build.gradle @@ -155,7 +155,7 @@ android { buildConfigField "String", "TESTING_LEVEL", "\"production\"" resConfigs "en", "bg", "de", "en-rGB", "es", "fr", "hr-rHR", "in", "it", "iw", "ja", "ko", "lt", "nl", "pl", "pt-rBR", "pt-rPT", "ru", "tr", "zh", "zh-rTW" - versionCode 3067 + versionCode 3070 versionName "3.4.1" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 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 216c0d5a2..27f27e7bc 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/HabiticaBaseApplication.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/HabiticaBaseApplication.kt @@ -6,9 +6,11 @@ import android.content.Intent import android.content.SharedPreferences import android.content.pm.PackageInfo import android.content.pm.PackageManager +import android.content.res.Configuration import android.content.res.Resources import android.database.DatabaseErrorHandler import android.database.sqlite.SQLiteDatabase +import android.os.Build import android.os.Build.VERSION.SDK_INT import android.util.Log import androidx.appcompat.app.AppCompatDelegate @@ -31,6 +33,7 @@ import com.habitrpg.android.habitica.api.HostConfig 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.LanguageHelper import com.habitrpg.android.habitica.helpers.RxErrorHandler import com.habitrpg.android.habitica.helpers.notifications.PushNotificationManager import com.habitrpg.android.habitica.modules.UserModule @@ -46,6 +49,7 @@ import org.solovyev.android.checkout.Billing import org.solovyev.android.checkout.Cache import org.solovyev.android.checkout.Checkout import org.solovyev.android.checkout.PurchaseVerifier +import java.util.Locale import javax.inject.Inject // contains all HabiticaApplicationLogic except dagger componentInitialisation @@ -78,6 +82,7 @@ abstract class HabiticaBaseApplication : Application() { super.onCreate() setupRealm() setupDagger() + setLocale() setupRemoteConfig() setupNotifications() createBillingAndCheckout() @@ -117,6 +122,21 @@ abstract class HabiticaBaseApplication : Application() { checkIfNewVersion() } + private fun setLocale() { + val resources = resources + val configuration: Configuration = resources.configuration + val languageHelper = LanguageHelper(sharedPrefs.getString("language", "en")) + if (if (SDK_INT >= Build.VERSION_CODES.N) { + configuration.locales.isEmpty || configuration.locales[0] != languageHelper.locale + } else { + configuration.locale != languageHelper.locale + } + ) { + configuration.setLocale(languageHelper.locale) + resources.updateConfiguration(configuration, null) + } + } + protected open fun setupRealm() { Realm.init(this) val builder = RealmConfiguration.Builder() 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 62a584a0d..335d7ff5f 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 @@ -4,6 +4,7 @@ import android.content.Context import android.content.SharedPreferences import android.content.res.Configuration import android.content.res.Resources +import android.os.Build import android.os.Bundle import android.view.LayoutInflater import android.view.Menu @@ -61,7 +62,7 @@ abstract class BaseActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this) val languageHelper = LanguageHelper(sharedPreferences.getString("language", "en")) - resources.forceLocale(languageHelper.locale) + resources.forceLocale(this, languageHelper.locale) delegate.localNightMode = when (sharedPreferences.getString("theme_mode", "system")) { "light" -> AppCompatDelegate.MODE_NIGHT_NO "dark" -> AppCompatDelegate.MODE_NIGHT_YES @@ -77,6 +78,13 @@ abstract class BaseActivity : AppCompatActivity() { compositeSubscription = CompositeDisposable() } + override fun onRestart() { + super.onRestart() + val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this) + val languageHelper = LanguageHelper(sharedPreferences.getString("language", "en")) + resources.forceLocale(this, languageHelper.locale) + } + override fun onStart() { super.onStart() EventBus.getDefault().register(this) @@ -222,9 +230,12 @@ abstract class BaseActivity : AppCompatActivity() { } } -private fun Resources.forceLocale(locale: Locale) { +private fun Resources.forceLocale(activity: BaseActivity, locale: Locale) { Locale.setDefault(locale) val configuration = Configuration() configuration.setLocale(locale) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + activity.createConfigurationContext(configuration) + } updateConfiguration(configuration, displayMetrics) } From 675c2961549673b9a2c5c231946ab21f446f1070 Mon Sep 17 00:00:00 2001 From: Phillip Thelen Date: Mon, 11 Oct 2021 13:03:21 +0200 Subject: [PATCH 10/16] Fixes #1628 --- .../habitrpg/android/habitica/ui/activities/MainActivity.kt | 4 +++- .../habitica/ui/views/dialogs/HabiticaAlertDialog.kt | 6 +++--- 2 files changed, 6 insertions(+), 4 deletions(-) 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 d7092d3d6..5294858bf 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 @@ -404,6 +404,8 @@ open class MainActivity : BaseActivity(), TutorialView.OnTutorialReaction { if (binding.toolbarTitle.text?.isNotBlank() != true) { navigationController.currentDestination?.let { updateToolbarTitle(it, null) } } + + showAchievementDialog(ShowAchievementDialog("", "", "")) } override fun onPause() { @@ -847,7 +849,7 @@ open class MainActivity : BaseActivity(), TutorialView.OnTutorialReaction { } @Subscribe - fun showWonAchievementDialog(event: ShowWonChallengeDialog) { + fun showWonChallengeDialog(event: ShowWonChallengeDialog) { retrieveUser(true) lifecycleScope.launch(context = Dispatchers.Main) { val dialog = WonChallengeDialog(this@MainActivity) diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/dialogs/HabiticaAlertDialog.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/dialogs/HabiticaAlertDialog.kt index 4ae3dba98..542349857 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/dialogs/HabiticaAlertDialog.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/dialogs/HabiticaAlertDialog.kt @@ -312,12 +312,12 @@ open class HabiticaAlertDialog(context: Context) : AlertDialog(context, R.style. private fun checkIfQueueAvailable(): Boolean { val currentDialog = dialogQueue.firstOrNull() ?: return true - if (currentDialog.isShowing) { - return false + return if (currentDialog.isShowing && currentDialog.getActivity()?.isFinishing != true) { + false } else { // The Dialog was probably dismissed in a weird way. Clear it out so that the queue doesn't get stuck dialogQueue.removeAt(0) - return true + true } } } From 249b144f5dd1f37ce1ffde35fadf30cbedf24902 Mon Sep 17 00:00:00 2001 From: Phillip Thelen Date: Mon, 11 Oct 2021 14:09:55 +0200 Subject: [PATCH 11/16] Fixes #1607 --- .../android/habitica/ui/AvatarView.kt | 21 +++++++++---------- .../habitica/ui/activities/MainActivity.kt | 2 -- 2 files changed, 10 insertions(+), 13 deletions(-) diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/AvatarView.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/AvatarView.kt index 5572b178f..b3166efa8 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/AvatarView.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/AvatarView.kt @@ -12,6 +12,7 @@ import androidx.core.view.marginStart import androidx.core.view.marginTop import coil.clear import coil.load +import coil.target.ImageViewTarget import com.habitrpg.android.habitica.BuildConfig import com.habitrpg.android.habitica.R import com.habitrpg.android.habitica.extensions.dpToPx @@ -143,17 +144,15 @@ class AvatarView : FrameLayout { imageView.load(DataBindingUtils.BASE_IMAGE_URL + DataBindingUtils.getFullFilename(layerName)) { allowHardware(false) - target( - {}, - { + target(object : ImageViewTarget(imageView) { + override fun onError(error: Drawable?) { + super.onError(error) onLayerComplete() - }, - { - if (imageView.tag != layerName) { - return@target - } - val bounds = getLayerBounds(layerKey, layerName, it) - imageView.setImageDrawable(it) + } + + override fun onSuccess(result: Drawable) { + super.onSuccess(result) + val bounds = getLayerBounds(layerKey, layerName, result) imageView.imageMatrix = avatarMatrix val layoutParams = imageView.layoutParams as? LayoutParams layoutParams?.topMargin = bounds.top @@ -163,7 +162,7 @@ class AvatarView : FrameLayout { imageView.layoutParams = layoutParams onLayerComplete() } - ) + }) } } while (i < (imageViewHolder.size)) { 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 5294858bf..a1f9e4625 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 @@ -404,8 +404,6 @@ open class MainActivity : BaseActivity(), TutorialView.OnTutorialReaction { if (binding.toolbarTitle.text?.isNotBlank() != true) { navigationController.currentDestination?.let { updateToolbarTitle(it, null) } } - - showAchievementDialog(ShowAchievementDialog("", "", "")) } override fun onPause() { From 9105ea66e06d3b58bd2dc8f282036c12bf27b0af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=28=C2=B4=E2=8C=A3=60=CA=83=C6=AA=29?= Date: Mon, 18 Oct 2021 08:32:51 -0700 Subject: [PATCH 12/16] remove deleted tag from active tags (#1652) --- .../habitrpg/android/habitica/ui/views/tasks/TaskFilterDialog.kt | 1 + 1 file changed, 1 insertion(+) 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 5cb70d0a9..bc02379a1 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 @@ -224,6 +224,7 @@ class TaskFilterDialog(context: Context, component: UserComponent?) : HabiticaAl if (editedTags.containsKey(tag.id)) { editedTags.remove(tag.id) } + activeTags.remove(tag.id) tags.remove(tag) tagsList.removeView(wrapper) } From d381c5c1cfbcc226c7ba889b35ca499324d332c5 Mon Sep 17 00:00:00 2001 From: pauliancu97 <32488931+pauliancu97@users.noreply.github.com> Date: Mon, 18 Oct 2021 18:34:14 +0300 Subject: [PATCH 13/16] Bug fix for reminder dialog date picker not taking into consideration the first day of the week setting (#1654) * added resources to run project * added fix for remainder date dialog not taking into consideration the first day of the week setting * added back example files --- .../habitica/ui/views/tasks/form/ReminderContainer.kt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/tasks/form/ReminderContainer.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/tasks/form/ReminderContainer.kt index 2b46746e8..f2af43075 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/tasks/form/ReminderContainer.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/tasks/form/ReminderContainer.kt @@ -55,6 +55,12 @@ class ReminderContainer @JvmOverloads constructor( } var firstDayOfWeek: Int? = null + set(value) { + children + .filterIsInstance() + .forEach { it.firstDayOfWeek = value } + field = value + } init { orientation = VERTICAL From 47b922135bb03b8f591bdd63c8808c47043d8440 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=28=C2=B4=E2=8C=A3=60=CA=83=C6=AA=29?= Date: Mon, 18 Oct 2021 08:36:54 -0700 Subject: [PATCH 14/16] Clear loadedSoundFiles when changing audio theme (#1656) - fixes #1650 - fixes preloadAllFiles --- .../habitrpg/android/habitica/helpers/SoundManager.kt | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) 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 940ff3765..d4387f280 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,6 @@ package com.habitrpg.android.habitica.helpers import com.habitrpg.android.habitica.HabiticaBaseApplication -import io.reactivex.rxjava3.core.Maybe import io.reactivex.rxjava3.schedulers.Schedulers import javax.inject.Inject @@ -17,9 +16,10 @@ class SoundManager { HabiticaBaseApplication.userComponent?.inject(this) } - fun preloadAllFiles(): Maybe> { + fun preloadAllFiles() { + loadedSoundFiles.clear() if (soundTheme == SoundThemeOff) { - return Maybe.empty() + return } val soundFiles = ArrayList() @@ -33,7 +33,8 @@ class SoundManager { soundFiles.add(SoundFile(soundTheme, SoundPlusHabit)) soundFiles.add(SoundFile(soundTheme, SoundReward)) soundFiles.add(SoundFile(soundTheme, SoundTodo)) - return soundFileLoader.download(soundFiles).toMaybe() + soundFileLoader.download(soundFiles) + .subscribe({}, RxErrorHandler.handleEmptyError()) } fun loadAndPlayAudio(type: String) { From 1d2906af29b7876f08593842823d0d599785500b Mon Sep 17 00:00:00 2001 From: pauliancu97 <32488931+pauliancu97@users.noreply.github.com> Date: Mon, 18 Oct 2021 18:39:33 +0300 Subject: [PATCH 15/16] Bug fix for message badge not updated (#1651) * added resources to run project * fixed bug of messages badge not behaving as specified * removed commented code line * removed some unused imports * added back the example files --- .../android/habitica/data/SocialRepository.kt | 2 ++ .../implementation/SocialRepositoryImpl.kt | 25 ++++++++++++++++--- .../RealmSocialLocalRepository.kt | 7 ++++++ .../habitica/models/social/ChatMessage.kt | 2 ++ .../android/habitica/models/user/Inbox.kt | 1 + .../ui/fragments/NavigationDrawerFragment.kt | 24 +++++++++++++----- .../social/InboxMessageListFragment.kt | 11 +++++++- 7 files changed, 62 insertions(+), 10 deletions(-) diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/SocialRepository.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/SocialRepository.kt index 68db3419d..b0c0ef49a 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 @@ -60,6 +60,8 @@ interface SocialRepository : BaseRepository { fun markPrivateMessagesRead(user: User?): Flowable + fun markSomePrivateMessagesAsRead(user: User?, messages: List) + fun transferGroupOwnership(groupID: String, userID: String): Flowable fun removeMemberFromGroup(groupID: String, userID: String): Flowable> diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/SocialRepositoryImpl.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/SocialRepositoryImpl.kt index 784a45634..c726a01e9 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 @@ -264,12 +264,31 @@ class SocialRepositoryImpl(localRepository: SocialLocalRepository, apiClient: Ap } override fun markPrivateMessagesRead(user: User?): Flowable { + if (user?.isManaged == true) { + localRepository.modify(user) { + it.inbox?.hasUserSeenInbox = true + } + } return apiClient.markPrivateMessagesRead() - .doOnNext { - if (user?.isManaged == true) { - localRepository.modify(user) { it.inbox?.newMessages = 0 } + } + + override fun markSomePrivateMessagesAsRead(user: User?, messages: List) { + if (user?.isManaged == true) { + val numOfUnseenMessages = messages.count { !it.isSeen } + localRepository.modify(user) { + val numOfNewMessagesFromInbox = it.inbox?.newMessages ?: 0 + if (numOfNewMessagesFromInbox > numOfUnseenMessages) { + it.inbox?.newMessages = numOfNewMessagesFromInbox - numOfUnseenMessages + } else { + it.inbox?.newMessages = 0 } } + } + for (message in messages.filter { it.isManaged && !it.isSeen }) { + localRepository.modify(message) { + it.isSeen = true + } + } } override fun getUserGroups(type: String?): Flowable> = localRepository.getUserGroups(userID, type) 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 efbced2a6..07329b047 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 @@ -64,6 +64,13 @@ class RealmSocialLocalRepository(realm: Realm) : RealmBaseLocalRepository(realm) override fun saveInboxMessages(userID: String, recipientID: String, messages: List, page: Int) { messages.forEach { it.userID = userID } + for (message in messages) { + val existingMessage = realm.where(ChatMessage::class.java) + .equalTo("id", message.id) + .findAll() + .firstOrNull() + message.isSeen = existingMessage != null + } save(messages) if (page != 0) return val existingMessages = realm.where(ChatMessage::class.java).equalTo("isInboxMessage", true).equalTo("uuid", recipientID).findAll() diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/models/social/ChatMessage.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/models/social/ChatMessage.kt index a73d4990d..f7678d441 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/models/social/ChatMessage.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/models/social/ChatMessage.kt @@ -58,6 +58,8 @@ open class ChatMessage : RealmObject(), BaseMainObject { val formattedUsername: String? get() = if (username != null) "@$username" else null + var isSeen: Boolean = true + fun userLikesMessage(userId: String?): Boolean { return likes?.any { userId == it.id } ?: false } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/models/user/Inbox.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/models/user/Inbox.kt index 51620f7d2..9aacccd7d 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/models/user/Inbox.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/models/user/Inbox.kt @@ -10,4 +10,5 @@ open class Inbox : RealmObject(), BaseObject { var optOut: Boolean = false var blocks: RealmList = RealmList() var newMessages: Int = 0 + var hasUserSeenInbox: Boolean = false } 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 cf625dd3d..a1b694030 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 @@ -36,6 +36,7 @@ import com.habitrpg.android.habitica.models.inventory.QuestContent import com.habitrpg.android.habitica.models.promotions.HabiticaPromotion import com.habitrpg.android.habitica.models.promotions.PromoType import com.habitrpg.android.habitica.models.social.Group +import com.habitrpg.android.habitica.models.user.Inbox import com.habitrpg.android.habitica.models.user.User import com.habitrpg.android.habitica.ui.activities.MainActivity import com.habitrpg.android.habitica.ui.activities.NotificationsActivity @@ -324,7 +325,7 @@ class NavigationDrawerFragment : DialogFragment() { } private fun updateUser(user: User) { - setMessagesCount(user.inbox?.newMessages ?: 0) + setMessagesCount(user.inbox) setSettingsCount(if (user.flags?.verifiedUsername != true) 1 else 0) setDisplayName(user.profile?.name) setUsername(user.formattedUsername) @@ -619,12 +620,23 @@ class NavigationDrawerFragment : DialogFragment() { } } - private fun setMessagesCount(unreadMessages: Int) { - if (unreadMessages == 0) { - binding?.messagesBadge?.visibility = View.GONE - } else { + private fun setMessagesCount(inbox: Inbox?) { + val numOfUnreadMessages = inbox?.newMessages ?: 0 + if (numOfUnreadMessages != 0) { binding?.messagesBadge?.visibility = View.VISIBLE - binding?.messagesBadge?.text = unreadMessages.toString() + binding?.messagesBadge?.text = numOfUnreadMessages.toString() + context?.let { + val color = if (inbox?.hasUserSeenInbox != true) { + it.getThemeColor(R.attr.colorAccent) + } else { + ContextCompat.getColor(it, R.color.gray_200) + } + val background = binding?.messagesBadge?.background as? GradientDrawable + background?.color = ColorStateList.valueOf(color) + binding?.messagesBadge?.setTextColor(ContextCompat.getColor(it, R.color.white)) + } + } else { + binding?.messagesBadge?.visibility = View.GONE } } 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 6e189dfd1..5603a0ca5 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 @@ -88,7 +88,12 @@ class InboxMessageListFragment : BaseMainFragment) { + socialRepository.markSomePrivateMessagesAsRead(user, messages) + } + private fun startAutoRefreshing() { if (refreshDisposable != null && refreshDisposable?.isDisposed != true) { refreshDisposable?.dispose() From dd51b95f53ed51cd648975d950c20c5c5aef05b0 Mon Sep 17 00:00:00 2001 From: pauliancu97 <32488931+pauliancu97@users.noreply.github.com> Date: Mon, 18 Oct 2021 18:42:51 +0300 Subject: [PATCH 16/16] Bug fix bailey notification not dismissed (#1655) * added resources to run project * fixed bug of bailey notification not being dismissed * added examples files back --- .../ui/viewmodels/NotificationsViewModel.kt | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) 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 37cbd2372..1696cd616 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 @@ -61,7 +61,7 @@ open class NotificationsViewModel : BaseViewModel() { var notifications = convertInvitationsToNotifications(it) if (it.flags?.newStuff == true) { val notification = Notification() - notification.id = "new-stuff-notification" + notification.id = "custom-new-stuff-notification" notification.type = Notification.Type.NEW_STUFF.type val data = NewStuffData() notification.data = data @@ -179,11 +179,19 @@ open class NotificationsViewModel : BaseViewModel() { * instead of one of the ones coming from server. */ private fun isCustomNotification(notification: Notification): Boolean { - return notification.id.startsWith("custom-") || notification.id == "new-stuff-notification" + return notification.id.startsWith("custom-") } + private fun isCustomNewStuffNotification(notification: Notification) = + notification.id == "custom-new-stuff-notification" + fun dismissNotification(notification: Notification) { if (isCustomNotification(notification)) { + if (isCustomNewStuffNotification(notification)) { + customNotifications.onNext( + customNotifications.value?.filterNot { it.id == notification.id } + ) + } return } @@ -199,6 +207,13 @@ open class NotificationsViewModel : BaseViewModel() { .filter { !actionableNotificationTypes.contains(it.type) } .map { it.id } + val customNewStuffNotification = notifications + .firstOrNull { isCustomNewStuffNotification(it) } + + if (customNewStuffNotification != null) { + dismissNotification(customNewStuffNotification) + } + if (dismissableIds.isEmpty()) { return }