diff --git a/Habitica/res/values/attrs.xml b/Habitica/res/values/attrs.xml
index ef3dd7101..b6605a567 100644
--- a/Habitica/res/values/attrs.xml
+++ b/Habitica/res/values/attrs.xml
@@ -6,6 +6,7 @@
+
diff --git a/Habitica/res/values/strings.xml b/Habitica/res/values/strings.xml
index 24af9b3e8..10ffc1745 100644
--- a/Habitica/res/values/strings.xml
+++ b/Habitica/res/values/strings.xml
@@ -1367,11 +1367,11 @@
Damage to a boss or found collection items will be stored until you resume damage
List
By Invite
- Here’s a list of Habiticans looking to join a Party
+ Here’s a list of players looking to join a Party
There\'s no one looking for a Party right now. You can check back later!
Invited
Invite with @username or email
- Send an invite directly to Habiticans you know
+ Send an invite directly to players you know
Username or email address
Want to join a Party with others but don’t know any other players? Let Party leaders know you’re looking for an invite!
Looking for a Party?
@@ -1384,7 +1384,7 @@
New gear is released during the seasonal Galas. Until then, theres %d pieces of gear in the Enchanted Armoire to find!
New gear is released during the seasonal Galas. The Enchanted Armoire gets new stock every month too!
Unlock %s gear and skills
- Rescind Invite
+ Cancel Invite
Rescinded
Pending Invite
Input invalid
diff --git a/Habitica/res/values/styles.xml b/Habitica/res/values/styles.xml
index e87d077be..c3e7340fc 100644
--- a/Habitica/res/values/styles.xml
+++ b/Habitica/res/values/styles.xml
@@ -39,6 +39,7 @@
- @color/content_background
- @color/content_background_offset
+ - @color/offset_background
- @color/window_background
- @color/brand_800
- @color/transparent
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/NavigationDrawerAdapter.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/NavigationDrawerAdapter.kt
index 7947d5cdf..d89e39053 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/NavigationDrawerAdapter.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/NavigationDrawerAdapter.kt
@@ -44,10 +44,8 @@ class NavigationDrawerAdapter(tintColor: Int, backgroundTintColor: Int) : Recycl
internal val items: MutableList = ArrayList()
var selectedItem: Int? = null
set(value) {
- val oldValue = field
field = value
- oldValue?.let { notifyItemChanged(it) }
- value?.let { notifyItemChanged(it) }
+ notifyDataSetChanged()
}
var itemSelectedEvents: ((HabiticaDrawerItem) -> Unit)? = null
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/ChatFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/ChatFragment.kt
index c260a6d16..612516715 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/ChatFragment.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/ChatFragment.kt
@@ -8,6 +8,8 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.viewModels
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
@@ -49,7 +51,9 @@ class ChatFragment : BaseFragment() {
return FragmentChatBinding.inflate(inflater, container, false)
}
- lateinit var viewModel : GroupViewModel
+ val viewModel: GroupViewModel by viewModels(
+ ownerProducer = { parentFragment as Fragment }
+ )
@Inject
lateinit var configManager : AppConfigManager
@@ -131,6 +135,11 @@ class ChatFragment : BaseFragment() {
}
}
+ override fun onResume() {
+ super.onResume()
+ setNavigatedToFragment()
+ }
+
private fun setReplyTo(username : String?) {
val previousMessage = binding?.chatBarView?.message ?: ""
if (previousMessage.contains("@$username")) {
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/TavernFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/TavernFragment.kt
index 5a2a04100..98874cb32 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/TavernFragment.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/TavernFragment.kt
@@ -18,8 +18,8 @@ import com.habitrpg.android.habitica.models.social.Group
import com.habitrpg.android.habitica.ui.fragments.BaseMainFragment
import com.habitrpg.android.habitica.ui.viewmodels.GroupViewModel
import com.habitrpg.android.habitica.ui.viewmodels.GroupViewType
-import javax.inject.Inject
import dagger.hilt.android.AndroidEntryPoint
+import javax.inject.Inject
@AndroidEntryPoint
class TavernFragment : BaseMainFragment() {
@@ -87,9 +87,7 @@ class TavernFragment : BaseMainFragment() {
TavernDetailFragment()
}
1 -> {
- val fragment = ChatFragment()
- fragment.viewModel = viewModel
- fragment
+ ChatFragment()
}
else -> Fragment()
}
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 510a4881a..31dafad41 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
@@ -9,6 +9,8 @@ import android.view.View
import android.view.ViewGroup
import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.view.isVisible
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.viewModels
import androidx.lifecycle.lifecycleScope
import com.habitrpg.android.habitica.MainNavDirections
import com.habitrpg.android.habitica.R
@@ -54,16 +56,18 @@ class GuildDetailFragment : BaseFragment() {
return FragmentGuildDetailBinding.inflate(inflater, container, false)
}
- var viewModel: GroupViewModel? = null
+ val viewModel: GroupViewModel by viewModels(
+ ownerProducer = { parentFragment as Fragment }
+ )
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding?.refreshLayout?.setOnRefreshListener { this.refresh() }
- viewModel?.getGroupData()?.observe(viewLifecycleOwner, { updateGuild(it) })
- viewModel?.getLeaderData()?.observe(viewLifecycleOwner, { setLeader(it) })
- viewModel?.getIsMemberData()?.observe(viewLifecycleOwner, { updateMembership(it) })
+ viewModel.getGroupData().observe(viewLifecycleOwner, { updateGuild(it) })
+ viewModel.getLeaderData().observe(viewLifecycleOwner, { setLeader(it) })
+ viewModel.getIsMemberData().observe(viewLifecycleOwner, { updateMembership(it) })
binding?.guildDescription?.movementMethod = LinkMovementMethod.getInstance()
binding?.guildSummary?.movementMethod = LinkMovementMethod.getInstance()
@@ -72,7 +76,7 @@ class GuildDetailFragment : BaseFragment() {
leaveGuild()
}
binding?.joinButton?.setOnClickListener {
- viewModel?.joinGroup {
+ viewModel.joinGroup {
(this.activity as? SnackbarActivity)?.showSnackbar(title = getString(R.string.joined_guild))
}
}
@@ -81,7 +85,7 @@ class GuildDetailFragment : BaseFragment() {
sendInvitesResult.launch(intent)
}
binding?.leaderWrapper?.setOnClickListener {
- viewModel?.leaderID?.let { leaderID ->
+ viewModel.leaderID?.let { leaderID ->
val profileDirections = MainNavDirections.openProfileActivity(leaderID)
MainNavigationController.navigate(profileDirections)
}
@@ -106,7 +110,7 @@ class GuildDetailFragment : BaseFragment() {
private val sendInvitesResult = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
if (it.resultCode == Activity.RESULT_OK) {
val inviteData = HashMap()
- inviteData["inviter"] = viewModel?.user?.value?.profile?.name ?: ""
+ inviteData["inviter"] = viewModel.user.value?.profile?.name ?: ""
val emails = it.data?.getStringArrayExtra(GroupInviteActivity.EMAILS_KEY)
if (emails != null && emails.isNotEmpty()) {
val invites = ArrayList>()
@@ -124,12 +128,12 @@ class GuildDetailFragment : BaseFragment() {
userIDs.forEach { invites.add(it) }
inviteData["usernames"] = invites
}
- viewModel?.inviteToGroup(inviteData)
+ viewModel.inviteToGroup(inviteData)
}
}
private fun refresh() {
- viewModel?.retrieveGroup {
+ viewModel.retrieveGroup {
binding?.refreshLayout?.isRefreshing = false
}
}
@@ -140,7 +144,7 @@ class GuildDetailFragment : BaseFragment() {
userRepository.getUser().collect {
it?.challenges?.forEach { membership ->
val challenge = challengeRepository.getChallenge(membership.challengeID).firstOrNull()
- if (challenge != null && challenge.groupId == viewModel?.groupID) {
+ if (challenge != null && challenge.groupId == viewModel.groupID) {
groupChallenges.add(challenge)
}
}
@@ -200,12 +204,11 @@ class GuildDetailFragment : BaseFragment() {
}
companion object {
- fun newInstance(viewModel: GroupViewModel?): GuildDetailFragment {
+ fun newInstance(): GuildDetailFragment {
val args = Bundle()
val fragment = GuildDetailFragment()
fragment.arguments = args
- fragment.viewModel = viewModel
return fragment
}
}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/guilds/GuildFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/guilds/GuildFragment.kt
index 008eb7932..f2ac76947 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/guilds/GuildFragment.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/guilds/GuildFragment.kt
@@ -90,7 +90,6 @@ class GuildFragment : BaseMainFragment() {
}
if (childFragment is GuildDetailFragment) {
guildInformationFragment = childFragment
- childFragment.viewModel = viewModel
}
}
}
@@ -144,12 +143,11 @@ class GuildFragment : BaseMainFragment() {
when (position) {
0 -> {
- guildInformationFragment = GuildDetailFragment.newInstance(viewModel)
+ guildInformationFragment = GuildDetailFragment.newInstance()
fragment = guildInformationFragment
}
1 -> {
chatFragment = ChatFragment()
- chatFragment?.viewModel = viewModel
fragment = chatFragment
}
else -> fragment = Fragment()
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 7c3f75808..0bf3eaf23 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
@@ -11,6 +11,8 @@ import androidx.compose.foundation.layout.Column
import androidx.core.content.ContextCompat
import androidx.core.os.bundleOf
import androidx.core.view.isVisible
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.viewModels
import androidx.lifecycle.lifecycleScope
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.data.ChallengeRepository
@@ -54,8 +56,9 @@ import javax.inject.Inject
@AndroidEntryPoint
class PartyDetailFragment : BaseFragment() {
-
- var viewModel: PartyViewModel? = null
+ val viewModel: PartyViewModel by viewModels(
+ ownerProducer = { parentFragment as Fragment }
+ )
override var binding: FragmentPartyDetailBinding? = null
@@ -99,7 +102,7 @@ class PartyDetailFragment : BaseFragment() {
binding?.invitationsView?.getLeader = null
binding?.invitationsView?.acceptCall = {
- viewModel?.joinGroup(it) {
+ viewModel.joinGroup(it) {
lifecycleScope.launch(ExceptionHandler.coroutine()) {
val user = userRepository.retrieveUser(false)
parentFragmentManager.popBackStack()
@@ -114,39 +117,38 @@ class PartyDetailFragment : BaseFragment() {
binding?.invitationsView?.rejectCall = {
lifecycleScope.launchCatching {
socialRepository.rejectGroupInvite(it)
- userRepository.retrieveUser(false, true)
+ userRepository.retrieveUser(withTasks = false, forced = true)
}
}
binding?.invitesWrapper?.setContent {
HabiticaTheme {
- val invitedMembers = viewModel?.pendingInvites
Column {
- for (invitedMember in (invitedMembers ?: emptyList())) {
- val state = viewModel?.pendingInviteStates?.getOrDefault(
+ for (invitedMember in viewModel.pendingInvites) {
+ val state = viewModel.pendingInviteStates.getOrDefault(
invitedMember.id,
LoadingButtonState.CONTENT
- ) ?: LoadingButtonState.CONTENT
+ )
PartySeekingListItem(
user = invitedMember,
inviteState = state,
- isInvited = state != LoadingButtonState.SUCCESS,
+ isInvited = true,
showHeader = true,
showExtendedInfo = false,
onInvite = {
- viewModel?.rescindInvite(invitedMember)
+ viewModel.rescindInvite(invitedMember)
})
}
}
}
}
- viewModel?.getGroupData()?.observe(viewLifecycleOwner) { updateParty(it) }
- viewModel?.user?.observe(viewLifecycleOwner) { updateUser(it) }
- viewModel?.getMembersData()?.observe(viewLifecycleOwner) { updateMembersList(it) }
+ viewModel.getGroupData().observe(viewLifecycleOwner) { updateParty(it) }
+ viewModel.user.observe(viewLifecycleOwner) { updateUser(it) }
+ viewModel.getMembersData().observe(viewLifecycleOwner) { updateMembersList(it) }
}
private fun refreshParty() {
- viewModel?.retrieveGroup {
+ viewModel.retrieveGroup {
binding?.refreshLayout?.isRefreshing = false
}
}
@@ -180,7 +182,7 @@ class PartyDetailFragment : BaseFragment() {
binding?.questProgressView?.visibility = View.GONE
}
- binding?.findNewMember?.isVisible = viewModel?.isLeader == true
+ binding?.findNewMember?.isVisible = viewModel.isLeader == true
}
private fun updateUser(user: User?) {
@@ -205,7 +207,7 @@ class PartyDetailFragment : BaseFragment() {
}
}
- binding?.questProgressView?.configure(user, viewModel?.isUserOnQuest)
+ binding?.questProgressView?.configure(user, viewModel.isUserOnQuest)
if ((user.invitations?.parties?.count() ?: 0) > 0) {
binding?.partyInvitationWrapper?.visibility = View.VISIBLE
@@ -243,7 +245,7 @@ class PartyDetailFragment : BaseFragment() {
}
private fun showParticipantButtons(): Boolean {
- return viewModel?.showParticipantButtons() ?: false
+ return viewModel.showParticipantButtons()
}
private fun updateQuestContent(questContent: QuestContent) {
@@ -277,15 +279,15 @@ class PartyDetailFragment : BaseFragment() {
)
)
}
- if (viewModel?.isQuestActive == true) {
+ if (viewModel.isQuestActive) {
binding?.questProgressView?.visibility = View.VISIBLE
binding?.questProgressView?.setData(
questContent,
- viewModel?.getGroupData()?.value?.quest?.progress
+ viewModel.getGroupData().value?.quest?.progress
)
- val questParticipants = viewModel?.getGroupData()?.value?.quest?.members
- if (questParticipants?.find { it.key == viewModel?.userViewModel?.userID } != null) {
+ val questParticipants = viewModel.getGroupData().value?.quest?.members
+ if (questParticipants?.find { it.key == viewModel.userViewModel.userID } != null) {
binding?.questParticipationView?.text =
context?.getString(R.string.number_participants, questParticipants.size)
} else {
@@ -304,7 +306,7 @@ class PartyDetailFragment : BaseFragment() {
}
} else {
binding?.questProgressView?.visibility = View.GONE
- val members = viewModel?.getGroupData()?.value?.quest?.members
+ val members = viewModel.getGroupData().value?.quest?.members
val responded = members?.filter { it.isParticipating != null }
binding?.questParticipationView?.text =
context?.getString(R.string.number_responded, responded?.size, members?.size)
@@ -312,7 +314,7 @@ class PartyDetailFragment : BaseFragment() {
}
private fun updateMembersList(members: List?) {
- val leaderID = viewModel?.leaderID
+ val leaderID = viewModel.leaderID
members?.forEachIndexed { index, member ->
val memberView = (
if ((binding?.membersWrapper?.childCount ?: 0) > index) {
@@ -324,18 +326,18 @@ class PartyDetailFragment : BaseFragment() {
}
) ?: return@forEachIndexed
val viewHolder = GroupMemberViewHolder(memberView)
- viewHolder.bind(member, leaderID ?: "", viewModel?.user?.value?.id)
+ viewHolder.bind(member, leaderID ?: "", viewModel.user.value?.id)
viewHolder.onClickEvent = {
FullProfileActivity.open(member.id)
}
viewHolder.sendMessageEvent = {
- member.id.let { showSendMessageToUserDialog(it, member.displayName) }
+ showSendMessageToUserDialog(member.id, member.displayName)
}
viewHolder.transferOwnershipEvent = {
- member.id.let { showTransferOwnerShipDialog(it, member.displayName) }
+ showTransferOwnerShipDialog(member.id, member.displayName)
}
viewHolder.removeMemberEvent = {
- member.id.let { showRemoveMemberDialog(it, member.displayName) }
+ showRemoveMemberDialog(member.id, member.displayName)
}
}
}
@@ -376,7 +378,7 @@ class PartyDetailFragment : BaseFragment() {
val dialog = context?.let { HabiticaAlertDialog(it) }
dialog?.addButton(R.string.transfer, true) { _, _ ->
lifecycleScope.launch(ExceptionHandler.coroutine()) {
- socialRepository.transferGroupOwnership(viewModel?.groupID ?: "", userID)
+ socialRepository.transferGroupOwnership(viewModel.groupID ?: "", userID)
(activity as? MainActivity)?.snackbarContainer?.let { it1 ->
HabiticaSnackbar.showSnackbar(
it1,
@@ -402,7 +404,7 @@ class PartyDetailFragment : BaseFragment() {
val dialog = context?.let { HabiticaAlertDialog(it) }
dialog?.addButton(R.string.remove, true) { _, _ ->
lifecycleScope.launch(ExceptionHandler.coroutine()) {
- socialRepository.removeMemberFromGroup(viewModel?.groupID ?: "", userID)
+ socialRepository.removeMemberFromGroup(viewModel.groupID ?: "", userID)
(activity as? MainActivity)?.snackbarContainer?.let { it1 ->
HabiticaSnackbar.showSnackbar(
it1,
@@ -434,7 +436,7 @@ class PartyDetailFragment : BaseFragment() {
it?.challenges?.forEach { membership ->
val challenge =
challengeRepository.getChallenge(membership.challengeID).firstOrNull()
- if (challenge != null && challenge.groupId == viewModel?.groupID) {
+ if (challenge != null && challenge.groupId == viewModel.groupID) {
groupChallenges.add(challenge)
}
}
@@ -454,7 +456,7 @@ class PartyDetailFragment : BaseFragment() {
alert.setTitle(R.string.party_challenges)
alert.setMessage(R.string.leave_party_challenges_confirmation)
alert.addButton(R.string.keep_challenges, true) { _, _ ->
- viewModel?.leaveGroup(groupChallenges, true) {
+ viewModel.leaveGroup(groupChallenges, true) {
parentFragmentManager.popBackStack()
MainNavigationController.navigate(R.id.noPartyFragment)
}
@@ -464,7 +466,7 @@ class PartyDetailFragment : BaseFragment() {
false,
isDestructive = true
) { _, _ ->
- viewModel?.leaveGroup(groupChallenges, false) {
+ viewModel.leaveGroup(groupChallenges, false) {
parentFragmentManager.popBackStack()
MainNavigationController.navigate(R.id.noPartyFragment)
}
@@ -480,7 +482,7 @@ class PartyDetailFragment : BaseFragment() {
isPrimary = true,
isDestructive = true
) { _, _ ->
- viewModel?.leaveGroup(groupChallenges, false) {
+ viewModel.leaveGroup(groupChallenges, false) {
parentFragmentManager.popBackStack()
MainNavigationController.navigate(R.id.noPartyFragment)
}
@@ -494,12 +496,12 @@ class PartyDetailFragment : BaseFragment() {
private fun onQuestAccept() {
HapticFeedbackManager.tap(requireView())
- viewModel?.acceptQuest()
+ viewModel.acceptQuest()
}
private fun onQuestReject() {
HapticFeedbackManager.tap(requireView())
- viewModel?.rejectQuest()
+ viewModel.rejectQuest()
}
private fun questDetailButtonClicked() {
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/party/PartyFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/party/PartyFragment.kt
index 451d9e7d3..3440e029e 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/party/PartyFragment.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/party/PartyFragment.kt
@@ -13,7 +13,6 @@ import androidx.activity.result.contract.ActivityResultContracts
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.viewpager2.adapter.FragmentStateAdapter
-import androidx.viewpager2.widget.ViewPager2
import com.google.android.material.tabs.TabLayoutMediator
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.databinding.FragmentViewpagerBinding
@@ -30,7 +29,6 @@ import dagger.hilt.android.AndroidEntryPoint
class PartyFragment : BaseMainFragment() {
private var detailFragment: PartyDetailFragment? = null
- private var chatFragment: ChatFragment? = null
private var viewPagerAdapter: FragmentStateAdapter? = null
internal val viewModel: PartyViewModel by viewModels()
@@ -189,13 +187,10 @@ class PartyFragment : BaseMainFragment() {
return when (position) {
0 -> {
detailFragment = PartyDetailFragment()
- detailFragment?.viewModel = viewModel
detailFragment
}
1 -> {
- chatFragment = ChatFragment()
- chatFragment?.viewModel = viewModel
- chatFragment
+ ChatFragment()
}
else -> Fragment()
} ?: Fragment()
@@ -207,24 +202,6 @@ class PartyFragment : BaseMainFragment() {
}
binding?.viewPager?.adapter = viewPagerAdapter
- binding?.viewPager?.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
- override fun onPageScrolled(
- position: Int,
- positionOffset: Float,
- positionOffsetPixels: Int
- ) {
- if (position == 1) {
- chatFragment?.setNavigatedToFragment()
- }
- }
-
- override fun onPageSelected(position: Int) {
- if (position == 1) {
- chatFragment?.setNavigatedToFragment()
- }
- }
- })
-
tabLayout?.let {
binding?.viewPager?.let { it1 ->
TabLayoutMediator(it, it1) { tab, position ->
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/party/PartyInviteFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/party/PartyInviteFragment.kt
index 72f7be3aa..a91c3c35c 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/party/PartyInviteFragment.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/party/PartyInviteFragment.kt
@@ -43,6 +43,7 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
@@ -51,6 +52,7 @@ import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.data.SocialRepository
import com.habitrpg.android.habitica.data.UserRepository
import com.habitrpg.android.habitica.databinding.FragmentComposeBinding
+import com.habitrpg.android.habitica.helpers.MainNavigationController
import com.habitrpg.android.habitica.models.invitations.InviteResponse
import com.habitrpg.android.habitica.ui.fragments.BaseFragment
import com.habitrpg.android.habitica.ui.theme.HabiticaTheme
@@ -61,8 +63,11 @@ import com.habitrpg.common.habitica.extensions.isValidEmail
import com.habitrpg.common.habitica.helpers.launchCatching
import dagger.hilt.android.AndroidEntryPoint
import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.delay
import java.util.UUID
import javax.inject.Inject
+import kotlin.time.DurationUnit
+import kotlin.time.toDuration
fun uUIDFromStringOrNull(name: String): UUID? {
return try {
@@ -120,7 +125,9 @@ class PartyInviteFragment : BaseFragment() {
val view = super.onCreateView(inflater, container, savedInstanceState)
binding?.composeView?.setContent {
HabiticaTheme {
- PartyInviteView(viewModel)
+ PartyInviteView(viewModel) {
+ MainNavigationController.navigateBack()
+ }
}
}
return view
@@ -130,7 +137,8 @@ class PartyInviteFragment : BaseFragment() {
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun PartyInviteView(
- viewModel: PartyInviteViewModel
+ viewModel: PartyInviteViewModel,
+ dismiss: () -> Unit
) {
var inviteButtonState: LoadingButtonState by remember { mutableStateOf(LoadingButtonState.CONTENT) }
val scope = rememberCoroutineScope()
@@ -152,11 +160,13 @@ fun PartyInviteView(
stringResource(R.string.invite_with_username_email),
color = HabiticaTheme.colors.textPrimary,
fontSize = 16.sp,
- fontWeight = FontWeight.Medium
+ fontWeight = FontWeight.Medium,
+ modifier = Modifier.padding(bottom = 4.dp)
)
Text(
stringResource(R.string.habiticans_send_invite),
- color = HabiticaTheme.colors.textSecondary
+ color = HabiticaTheme.colors.textSecondary,
+ style = TextStyle(fontSize = 16.sp, fontWeight = FontWeight.Normal),
)
}
}
@@ -209,6 +219,7 @@ fun PartyInviteView(
viewModel.invites[index] = value
},
singleLine = true,
+ textStyle = TextStyle(fontSize = 16.sp),
placeholder = { Text(stringResource(R.string.username_or_email)) },
colors = TextFieldDefaults.textFieldColors(
backgroundColor = Color.Transparent,
@@ -241,6 +252,8 @@ fun PartyInviteView(
inviteButtonState = LoadingButtonState.SUCCESS
viewModel.invites.clear()
viewModel.invites.add("")
+ delay(2.toDuration(DurationUnit.SECONDS))
+ dismiss()
} else {
inviteButtonState = LoadingButtonState.FAILED
}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/party/PartySeekingFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/party/PartySeekingFragment.kt
index f73d3c701..35f19080a 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/party/PartySeekingFragment.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/party/PartySeekingFragment.kt
@@ -30,6 +30,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
@@ -57,6 +58,7 @@ import com.habitrpg.android.habitica.ui.viewmodels.BaseViewModel
import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel
import com.habitrpg.android.habitica.ui.views.LoadingButton
import com.habitrpg.android.habitica.ui.views.LoadingButtonState
+import com.habitrpg.android.habitica.ui.views.LoadingButtonType
import com.habitrpg.android.habitica.ui.views.progress.HabiticaCircularProgressView
import com.habitrpg.android.habitica.ui.views.progress.HabiticaPullRefreshIndicator
import com.habitrpg.android.habitica.ui.views.social.PartySeekingListItem
@@ -138,14 +140,16 @@ fun InviteButton(
modifier : Modifier = Modifier,
isAlreadyInvited: Boolean = false,
) {
- LoadingButton(state = state, onClick = onClick, colors = ButtonDefaults.buttonColors(
+ LoadingButton(state = state, onClick = onClick,
+ type = if (isAlreadyInvited) LoadingButtonType.DESTRUCTIVE else LoadingButtonType.NORMAL,
+ colors = ButtonDefaults.buttonColors(
backgroundColor = if (isAlreadyInvited) HabiticaTheme.colors.errorBackground else HabiticaTheme.colors.tintedUiSub,
contentColor = Color.White,
), modifier = modifier, successContent = {
if (isAlreadyInvited) {
- Text(stringResource(R.string.invited))
- } else {
Text(stringResource(R.string.rescinded))
+ } else {
+ Text(stringResource(R.string.invited))
}
}) {
if (isAlreadyInvited) {
@@ -177,18 +181,20 @@ fun PartySeekingView(
Column(
horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier
.fillMaxWidth()
- .padding(top = 22.dp, bottom = 14.dp)
+ .padding(top = 36.dp, bottom = 14.dp)
) {
Text(
stringResource(R.string.find_more_members),
color = HabiticaTheme.colors.textPrimary,
fontSize = 16.sp,
- fontWeight = FontWeight.Medium
+ fontWeight = FontWeight.Medium,
+ modifier = Modifier.padding(bottom = 4.dp)
)
if (pageData.itemCount == 0 && pageData.loadState.refresh is LoadState.NotLoading && pageData.loadState.append is LoadState.NotLoading) {
Text(
stringResource(R.string.habiticans_looking_party_empty),
textAlign = TextAlign.Center,
+ style = TextStyle(fontSize = 16.sp, fontWeight = FontWeight.Normal),
color = HabiticaTheme.colors.textSecondary, modifier = Modifier
.width(250.dp)
.align(alignment = Alignment.CenterHorizontally)
@@ -226,13 +232,13 @@ fun PartySeekingView(
viewModel.inviteStates[member.id] = LoadingButtonState.LOADING
val response: Any? = if (viewModel.successfulInvites.contains(member.id)) viewModel.rescindInvite(member) else viewModel.inviteUser(member)
if (response != null) {
+ viewModel.inviteStates[member.id] = LoadingButtonState.SUCCESS
+ delay(4.toDuration(DurationUnit.SECONDS))
if (viewModel.successfulInvites.contains(member.id)) {
viewModel.successfulInvites.remove(member.id)
} else {
viewModel.successfulInvites.add(member.id)
}
- viewModel.inviteStates[member.id] = LoadingButtonState.SUCCESS
- delay(4.toDuration(DurationUnit.SECONDS))
viewModel.inviteStates[member.id] = LoadingButtonState.CONTENT
} else {
viewModel.inviteStates[member.id] = LoadingButtonState.FAILED
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/theme/HabiticaTheme.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/theme/HabiticaTheme.kt
index 8d473b0a7..6866ba14c 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/theme/HabiticaTheme.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/theme/HabiticaTheme.kt
@@ -136,6 +136,7 @@ object HabiticaTheme {
windowBackground = Color(context.getThemeColor(R.attr.colorWindowBackground)),
contentBackground = Color(context.getThemeColor(R.attr.colorContentBackground)),
contentBackgroundOffset = Color(context.getThemeColor(R.attr.colorContentBackgroundOffset)),
+ offsetBackground = Color(context.getThemeColor(R.attr.colorOffsetBackground)),
textPrimary = Color(context.getThemeColor(R.attr.textColorPrimary)),
textSecondary = Color(context.getThemeColor(R.attr.textColorSecondary)),
textTertiary = Color(ContextCompat.getColor(context, R.color.text_ternary)),
@@ -157,6 +158,7 @@ class HabiticaColors(
val windowBackground: Color,
val contentBackground: Color,
val contentBackgroundOffset: Color,
+ val offsetBackground: Color,
val textPrimary: Color,
val textSecondary: Color,
val textTertiary: Color,
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/tasks/TodoViewHolder.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/tasks/TodoViewHolder.kt
index cd92725ab..e5c9f7e92 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/tasks/TodoViewHolder.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/tasks/TodoViewHolder.kt
@@ -7,7 +7,7 @@ import com.habitrpg.android.habitica.helpers.GroupPlanInfoProvider
import com.habitrpg.android.habitica.models.tasks.ChecklistItem
import com.habitrpg.android.habitica.models.tasks.Task
import com.habitrpg.shared.habitica.models.responses.TaskDirection
-import java.text.DateFormat
+import java.text.SimpleDateFormat
class TodoViewHolder(
itemView: View,
@@ -18,7 +18,7 @@ class TodoViewHolder(
assignedTextProvider: GroupPlanInfoProvider?
) : ChecklistedViewHolder(itemView, scoreTaskFunc, scoreChecklistItemFunc, openTaskFunc, brokenTaskFunc, assignedTextProvider) {
- private val dateFormatter: DateFormat = android.text.format.DateFormat.getDateFormat(context)
+ private val dateFormatter = SimpleDateFormat.getDateInstance()
override fun bind(
data: Task,
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/LoadingButton.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/LoadingButton.kt
index 6a7cdb249..a5b76a0af 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/LoadingButton.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/LoadingButton.kt
@@ -63,38 +63,52 @@ enum class LoadingButtonState {
SUCCESS
}
+enum class LoadingButtonType {
+ NORMAL,
+ DESTRUCTIVE
+}
+
@OptIn(ExperimentalAnimationApi::class)
@Composable
fun LoadingButton(
state : LoadingButtonState,
onClick : () -> Unit,
modifier : Modifier = Modifier,
+ type : LoadingButtonType = LoadingButtonType.NORMAL,
elevation : ButtonElevation? = ButtonDefaults.elevation(0.dp),
shape : Shape = MaterialTheme.shapes.medium,
border : BorderStroke? = null,
- colors : ButtonColors = ButtonDefaults.buttonColors(
- backgroundColor = HabiticaTheme.colors.tintedUiSub,
- contentColor = Color.White
- ),
+ colors : ButtonColors? = null,
contentPadding : PaddingValues = ButtonDefaults.ContentPadding,
successContent : (@Composable RowScope.() -> Unit)? = null,
failedContent : (@Composable RowScope.() -> Unit)? = null,
content : @Composable RowScope.() -> Unit
) {
+ val colorStyle = if (type == LoadingButtonType.DESTRUCTIVE) ButtonDefaults.buttonColors(
+ backgroundColor = HabiticaTheme.colors.errorBackground,
+ contentColor = Color.White,
+ disabledBackgroundColor = HabiticaTheme.colors.offsetBackground,
+ disabledContentColor = HabiticaTheme.colors.textQuad
+ ) else ButtonDefaults.buttonColors(
+ backgroundColor = HabiticaTheme.colors.tintedUiSub,
+ contentColor = Color.White,
+ disabledBackgroundColor = HabiticaTheme.colors.offsetBackground,
+ disabledContentColor = HabiticaTheme.colors.textQuad
+ )
val colorSpec = tween(350)
val backgroundColor = animateColorAsState(
targetValue = when (state) {
LoadingButtonState.FAILED -> HabiticaTheme.colors.errorBackground
LoadingButtonState.SUCCESS -> Color.Transparent
- else -> colors.backgroundColor(enabled = state != LoadingButtonState.DISABLED).value
+ else -> colorStyle.backgroundColor(enabled = state != LoadingButtonState.DISABLED).value
},
animationSpec = colorSpec
)
val contentColor = animateColorAsState(
targetValue = when (state) {
LoadingButtonState.FAILED -> Color.White
- LoadingButtonState.SUCCESS -> HabiticaTheme.colors.successColor
- else -> colors.contentColor(enabled = state != LoadingButtonState.DISABLED).value
+ LoadingButtonState.SUCCESS -> if (type == LoadingButtonType.DESTRUCTIVE) HabiticaTheme.colors.errorColor else HabiticaTheme.colors.successColor
+ else -> colorStyle.contentColor(enabled = state != LoadingButtonState.DISABLED).value
},
animationSpec = colorSpec
)
@@ -124,7 +138,7 @@ fun LoadingButton(
shape = shape,
border = if (state == LoadingButtonState.SUCCESS) BorderStroke(
borderWidth.value,
- HabiticaTheme.colors.successBackground
+ if (type == LoadingButtonType.DESTRUCTIVE) HabiticaTheme.colors.errorColor else HabiticaTheme.colors.successColor
) else border,
colors = buttonColors,
contentPadding = PaddingValues(0.dp)
diff --git a/fastlane/changelog.txt b/fastlane/changelog.txt
index 3829a57c6..b1919b8c1 100644
--- a/fastlane/changelog.txt
+++ b/fastlane/changelog.txt
@@ -1,9 +1,9 @@
-New in 4.1.9:
--Various subscription status optimizations
--Party invites will show the sender’s username and profile
--You can view, complete, assign, and add tasks to your Group Plan’s shared task board
--Tap your name on a task screen to switch to different task boards
--More intuitive system notification settings
--New Avatar Customization interface
--Audio will be controlled by media volume now
--Task details are now tinted based on task health
+New in 4.2:
+It's easier than ever to party up with our newest feature: Look for Party and Find Members!
+
+- Solo players can let Party leaders know they want an invite by going to Menu > Party and tapping 'Look for Party'
+- Party leaders can see players looking for Party by tapping 'Find Members' on the Party screen and send invites from the list
+- Armoire shows in Market when you have all gear
+- Change class from Market when viewing gear from another class
+- Various bug fixes and improvements
+
diff --git a/version.properties b/version.properties
index 960d7e317..297f3909d 100644
--- a/version.properties
+++ b/version.properties
@@ -1,2 +1,2 @@
NAME=4.2
-CODE=5991
\ No newline at end of file
+CODE=6001
\ No newline at end of file