mirror of
https://github.com/sudoxnym/habitica-android.git
synced 2026-07-15 18:51:58 +00:00
party invite flow improvements
This commit is contained in:
parent
3619ed2810
commit
89b7408c3b
17 changed files with 138 additions and 116 deletions
|
|
@ -6,6 +6,7 @@
|
|||
<attr name="colorTintedBackground" format="color" />
|
||||
<attr name="colorTintedBackgroundOffset" format="color" />
|
||||
<attr name="colorContentBackgroundOffset" format="color" />
|
||||
<attr name="colorOffsetBackground" format="color" />
|
||||
<attr name="colorWindowBackground" format="color" />
|
||||
<attr name="colorGradientTransparentBackground" format="color" />
|
||||
<attr name="textColorPrimary" format="color" />
|
||||
|
|
|
|||
|
|
@ -1367,11 +1367,11 @@
|
|||
<string name="pause_damage_3_description">Damage to a boss or found collection items will be stored until you resume damage</string>
|
||||
<string name="list">List</string>
|
||||
<string name="by_invite">By Invite</string>
|
||||
<string name="habiticans_looking_party">Here’s a list of Habiticans looking to join a Party</string>
|
||||
<string name="habiticans_looking_party">Here’s a list of players looking to join a Party</string>
|
||||
<string name="habiticans_looking_party_empty">There\'s no one looking for a Party right now. You can check back later!</string>
|
||||
<string name="invited">Invited</string>
|
||||
<string name="invite_with_username_email">Invite with @username or email</string>
|
||||
<string name="habiticans_send_invite">Send an invite directly to Habiticans you know</string>
|
||||
<string name="habiticans_send_invite">Send an invite directly to players you know</string>
|
||||
<string name="username_or_email">Username or email address</string>
|
||||
<string name="seeking_party_description">Want to join a Party with others but don’t know any other players? Let Party leaders know you’re looking for an invite!</string>
|
||||
<string name="seeking_party_title">Looking for a Party?</string>
|
||||
|
|
@ -1384,7 +1384,7 @@
|
|||
<string name="shop_armoire_description">New gear is released during the seasonal Galas. Until then, theres %d pieces of gear in the Enchanted Armoire to find!</string>
|
||||
<string name="shop_armoire_empty_description">New gear is released during the seasonal Galas. The Enchanted Armoire gets new stock every month too!</string>
|
||||
<string name="unlock_gear_and_skills">Unlock %s gear and skills</string>
|
||||
<string name="rescind_invite">Rescind Invite</string>
|
||||
<string name="rescind_invite">Cancel Invite</string>
|
||||
<string name="rescinded">Rescinded</string>
|
||||
<string name="pending_invite">Pending Invite</string>
|
||||
<string name="invalid_input">Input invalid</string>
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@
|
|||
|
||||
<item name="colorContentBackground">@color/content_background</item>
|
||||
<item name="colorContentBackgroundOffset">@color/content_background_offset</item>
|
||||
<item name="colorOffsetBackground">@color/offset_background</item>
|
||||
<item name="colorWindowBackground">@color/window_background</item>
|
||||
<item name="colorTintedBackground">@color/brand_800</item>
|
||||
<item name="colorGradientTransparentBackground">@color/transparent</item>
|
||||
|
|
|
|||
|
|
@ -44,10 +44,8 @@ class NavigationDrawerAdapter(tintColor: Int, backgroundTintColor: Int) : Recycl
|
|||
internal val items: MutableList<HabiticaDrawerItem> = 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
|
||||
|
|
|
|||
|
|
@ -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<FragmentChatBinding>() {
|
|||
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<FragmentChatBinding>() {
|
|||
}
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
setNavigatedToFragment()
|
||||
}
|
||||
|
||||
private fun setReplyTo(username : String?) {
|
||||
val previousMessage = binding?.chatBarView?.message ?: ""
|
||||
if (previousMessage.contains("@$username")) {
|
||||
|
|
|
|||
|
|
@ -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<FragmentViewpagerBinding>() {
|
||||
|
|
@ -87,9 +87,7 @@ class TavernFragment : BaseMainFragment<FragmentViewpagerBinding>() {
|
|||
TavernDetailFragment()
|
||||
}
|
||||
1 -> {
|
||||
val fragment = ChatFragment()
|
||||
fragment.viewModel = viewModel
|
||||
fragment
|
||||
ChatFragment()
|
||||
}
|
||||
else -> Fragment()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<FragmentGuildDetailBinding>() {
|
|||
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<FragmentGuildDetailBinding>() {
|
|||
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<FragmentGuildDetailBinding>() {
|
|||
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<FragmentGuildDetailBinding>() {
|
|||
private val sendInvitesResult = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
||||
if (it.resultCode == Activity.RESULT_OK) {
|
||||
val inviteData = HashMap<String, Any>()
|
||||
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<HashMap<String, String>>()
|
||||
|
|
@ -124,12 +128,12 @@ class GuildDetailFragment : BaseFragment<FragmentGuildDetailBinding>() {
|
|||
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<FragmentGuildDetailBinding>() {
|
|||
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<FragmentGuildDetailBinding>() {
|
|||
}
|
||||
|
||||
companion object {
|
||||
fun newInstance(viewModel: GroupViewModel?): GuildDetailFragment {
|
||||
fun newInstance(): GuildDetailFragment {
|
||||
val args = Bundle()
|
||||
|
||||
val fragment = GuildDetailFragment()
|
||||
fragment.arguments = args
|
||||
fragment.viewModel = viewModel
|
||||
return fragment
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -90,7 +90,6 @@ class GuildFragment : BaseMainFragment<FragmentViewpagerBinding>() {
|
|||
}
|
||||
if (childFragment is GuildDetailFragment) {
|
||||
guildInformationFragment = childFragment
|
||||
childFragment.viewModel = viewModel
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -144,12 +143,11 @@ class GuildFragment : BaseMainFragment<FragmentViewpagerBinding>() {
|
|||
|
||||
when (position) {
|
||||
0 -> {
|
||||
guildInformationFragment = GuildDetailFragment.newInstance(viewModel)
|
||||
guildInformationFragment = GuildDetailFragment.newInstance()
|
||||
fragment = guildInformationFragment
|
||||
}
|
||||
1 -> {
|
||||
chatFragment = ChatFragment()
|
||||
chatFragment?.viewModel = viewModel
|
||||
fragment = chatFragment
|
||||
}
|
||||
else -> fragment = Fragment()
|
||||
|
|
|
|||
|
|
@ -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<FragmentPartyDetailBinding>() {
|
||||
|
||||
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<FragmentPartyDetailBinding>() {
|
|||
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<FragmentPartyDetailBinding>() {
|
|||
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<FragmentPartyDetailBinding>() {
|
|||
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<FragmentPartyDetailBinding>() {
|
|||
}
|
||||
}
|
||||
|
||||
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<FragmentPartyDetailBinding>() {
|
|||
}
|
||||
|
||||
private fun showParticipantButtons(): Boolean {
|
||||
return viewModel?.showParticipantButtons() ?: false
|
||||
return viewModel.showParticipantButtons()
|
||||
}
|
||||
|
||||
private fun updateQuestContent(questContent: QuestContent) {
|
||||
|
|
@ -277,15 +279,15 @@ class PartyDetailFragment : BaseFragment<FragmentPartyDetailBinding>() {
|
|||
)
|
||||
)
|
||||
}
|
||||
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<FragmentPartyDetailBinding>() {
|
|||
}
|
||||
} 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<FragmentPartyDetailBinding>() {
|
|||
}
|
||||
|
||||
private fun updateMembersList(members: List<Member>?) {
|
||||
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<FragmentPartyDetailBinding>() {
|
|||
}
|
||||
) ?: 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<FragmentPartyDetailBinding>() {
|
|||
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<FragmentPartyDetailBinding>() {
|
|||
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<FragmentPartyDetailBinding>() {
|
|||
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<FragmentPartyDetailBinding>() {
|
|||
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<FragmentPartyDetailBinding>() {
|
|||
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<FragmentPartyDetailBinding>() {
|
|||
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<FragmentPartyDetailBinding>() {
|
|||
|
||||
private fun onQuestAccept() {
|
||||
HapticFeedbackManager.tap(requireView())
|
||||
viewModel?.acceptQuest()
|
||||
viewModel.acceptQuest()
|
||||
}
|
||||
|
||||
private fun onQuestReject() {
|
||||
HapticFeedbackManager.tap(requireView())
|
||||
viewModel?.rejectQuest()
|
||||
viewModel.rejectQuest()
|
||||
}
|
||||
|
||||
private fun questDetailButtonClicked() {
|
||||
|
|
|
|||
|
|
@ -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<FragmentViewpagerBinding>() {
|
||||
|
||||
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<FragmentViewpagerBinding>() {
|
|||
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<FragmentViewpagerBinding>() {
|
|||
}
|
||||
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 ->
|
||||
|
|
|
|||
|
|
@ -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<FragmentComposeBinding>() {
|
|||
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<FragmentComposeBinding>() {
|
|||
@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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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<Color>(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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -1,2 +1,2 @@
|
|||
NAME=4.2
|
||||
CODE=5991
|
||||
CODE=6001
|
||||
Loading…
Reference in a new issue