diff --git a/Habitica/res/layout/fragment_party_detail.xml b/Habitica/res/layout/fragment_party_detail.xml index 1655ff4e6..96cf154a6 100644 --- a/Habitica/res/layout/fragment_party_detail.xml +++ b/Habitica/res/layout/fragment_party_detail.xml @@ -202,6 +202,10 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical"/> + diff --git a/Habitica/res/values/strings.xml b/Habitica/res/values/strings.xml index 92f524b3c..a62075004 100644 --- a/Habitica/res/values/strings.xml +++ b/Habitica/res/values/strings.xml @@ -1385,6 +1385,7 @@ Unlock %s gear and skills Rescind Invite Rescinded + Pending Invite You 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 c30554774..e25973c5b 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 @@ -286,6 +286,10 @@ interface ApiService { @POST("groups/{gid}/quests/invite/{questKey}") suspend fun inviteToQuest(@Path("gid") groupId: String, @Path("questKey") questKey: String): HabitResponse + @GET("groups/{gid}/invites") + suspend fun getGroupInvites(@Path("gid") groupId: String, + @Query("includeAllPublicFields") includeAllPublicFields: Boolean?): HabitResponse> + @POST("groups/{gid}/quests/abort") suspend fun abortQuest(@Path("gid") groupId: String): HabitResponse 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 440bb8e27..d5c2dc78c 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 @@ -278,4 +278,5 @@ interface ApiClient { suspend fun getHallMember(userId: String): Member? suspend fun markTaskNeedsWork(taskID: String, userID: String): Task? suspend fun retrievePartySeekingUsers(page: Int) : List? + suspend fun getGroupInvites(groupId: String, includeAllPublicFields: Boolean?): List? } 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 e11bd825c..ef50e34f9 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 @@ -123,4 +123,5 @@ interface SocialRepository : BaseRepository { fun getMember(userID: String?): Flow suspend fun updateMember(memberID: String, key: String, value: Any?): Member? suspend fun retrievePartySeekingUsers(page: Int = 0): List? + suspend fun retrievegroupInvites(id: String, includeAllPublicFields: Boolean): List? } 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 da4f50614..a1f15560c 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 @@ -578,6 +578,10 @@ class ApiClientImpl( return process { apiService.rejectGroupInvite(groupId) } } + override suspend fun getGroupInvites(groupId: String, includeAllPublicFields: Boolean?): List? { + return process { apiService.getGroupInvites(groupId, includeAllPublicFields) } + } + override suspend fun acceptQuest(groupId: String): Void? { return process { apiService.acceptQuest(groupId) } } 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 2babb5f00..bfdf2d0d6 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 @@ -275,6 +275,9 @@ class SocialRepositoryImpl( } } + override suspend fun retrievegroupInvites(id: String, includeAllPublicFields: Boolean) = apiClient.getGroupInvites(id, includeAllPublicFields) + + override suspend fun retrieveMemberWithUsername(username: String?, fromHall: Boolean): Member? { return retrieveMember(username, fromHall) } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/UserRepositoryImpl.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/UserRepositoryImpl.kt index 8c6f4922b..ad7389c0c 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/UserRepositoryImpl.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/UserRepositoryImpl.kt @@ -213,7 +213,7 @@ class UserRepositoryImpl( override suspend fun sendPasswordResetEmail(email: String) = apiClient.sendPasswordResetEmail(email) override suspend fun updateLoginName(newLoginName: String, password: String?): User? { - if (password != null && password.isNotEmpty()) { + if (!password.isNullOrEmpty()) { apiClient.updateLoginName(newLoginName.trim(), password.trim()) } else { apiClient.updateUsername(newLoginName.trim()) 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 d6ee9757b..7c3f75808 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 @@ -7,6 +7,7 @@ import android.view.ViewGroup import android.widget.Button import android.widget.TextView import androidx.appcompat.widget.AppCompatEditText +import androidx.compose.foundation.layout.Column import androidx.core.content.ContextCompat import androidx.core.os.bundleOf import androidx.core.view.isVisible @@ -30,10 +31,13 @@ import com.habitrpg.android.habitica.ui.activities.MainActivity import com.habitrpg.android.habitica.ui.fragments.BaseFragment import com.habitrpg.android.habitica.ui.fragments.inventory.items.ItemDialogFragment import com.habitrpg.android.habitica.ui.helpers.dismissKeyboard +import com.habitrpg.android.habitica.ui.theme.HabiticaTheme import com.habitrpg.android.habitica.ui.viewHolders.GroupMemberViewHolder import com.habitrpg.android.habitica.ui.viewmodels.PartyViewModel import com.habitrpg.android.habitica.ui.views.HabiticaSnackbar +import com.habitrpg.android.habitica.ui.views.LoadingButtonState import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog +import com.habitrpg.android.habitica.ui.views.social.PartySeekingListItem import com.habitrpg.common.habitica.extensions.DataBindingUtils import com.habitrpg.common.habitica.extensions.dpToPx import com.habitrpg.common.habitica.extensions.loadImage @@ -55,7 +59,10 @@ class PartyDetailFragment : BaseFragment() { override var binding: FragmentPartyDetailBinding? = null - override fun createBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentPartyDetailBinding { + override fun createBinding( + inflater: LayoutInflater, + container: ViewGroup? + ): FragmentPartyDetailBinding { return FragmentPartyDetailBinding.inflate(inflater, container, false) } @@ -110,6 +117,28 @@ class PartyDetailFragment : BaseFragment() { userRepository.retrieveUser(false, true) } } + binding?.invitesWrapper?.setContent { + HabiticaTheme { + val invitedMembers = viewModel?.pendingInvites + Column { + for (invitedMember in (invitedMembers ?: emptyList())) { + val state = viewModel?.pendingInviteStates?.getOrDefault( + invitedMember.id, + LoadingButtonState.CONTENT + ) ?: LoadingButtonState.CONTENT + PartySeekingListItem( + user = invitedMember, + inviteState = state, + isInvited = state != LoadingButtonState.SUCCESS, + showHeader = true, + showExtendedInfo = false, + onInvite = { + viewModel?.rescindInvite(invitedMember) + }) + } + } + } + } viewModel?.getGroupData()?.observe(viewLifecycleOwner) { updateParty(it) } viewModel?.user?.observe(viewLifecycleOwner) { updateUser(it) } @@ -138,7 +167,8 @@ class PartyDetailFragment : BaseFragment() { binding?.questImageWrapper?.visibility = View.VISIBLE lifecycleScope.launch(Dispatchers.Main) { delay(500) - val content = inventoryRepository.getQuestContent(party.quest?.key ?: "").firstOrNull() + val content = + inventoryRepository.getQuestContent(party.quest?.key ?: "").firstOrNull() if (content != null) { updateQuestContent(content) } @@ -239,17 +269,36 @@ class PartyDetailFragment : BaseFragment() { } binding?.questImageWrapper?.alpha = 1.0f binding?.questProgressView?.alpha = 1.0f - context?.let { binding?.questParticipationView?.setTextColor(ContextCompat.getColor(it, R.color.text_quad)) } + context?.let { + binding?.questParticipationView?.setTextColor( + ContextCompat.getColor( + it, + R.color.text_quad + ) + ) + } if (viewModel?.isQuestActive == true) { binding?.questProgressView?.visibility = View.VISIBLE - binding?.questProgressView?.setData(questContent, viewModel?.getGroupData()?.value?.quest?.progress) + binding?.questProgressView?.setData( + questContent, + viewModel?.getGroupData()?.value?.quest?.progress + ) 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) + binding?.questParticipationView?.text = + context?.getString(R.string.number_participants, questParticipants.size) } else { - binding?.questParticipationView?.text = context?.getString(R.string.not_participating) - context?.let { binding?.questParticipationView?.setTextColor(ContextCompat.getColor(it, R.color.red_10)) } + binding?.questParticipationView?.text = + context?.getString(R.string.not_participating) + context?.let { + binding?.questParticipationView?.setTextColor( + ContextCompat.getColor( + it, + R.color.red_10 + ) + ) + } binding?.questImageWrapper?.alpha = 0.5f binding?.questProgressView?.alpha = 0.5f } @@ -257,7 +306,8 @@ class PartyDetailFragment : BaseFragment() { binding?.questProgressView?.visibility = View.GONE 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) + binding?.questParticipationView?.text = + context?.getString(R.string.number_responded, responded?.size, members?.size) } } @@ -294,7 +344,8 @@ class PartyDetailFragment : BaseFragment() { val factory = LayoutInflater.from(context) val newMessageView = factory.inflate(R.layout.profile_new_message_dialog, null) - val emojiEditText = newMessageView.findViewById(R.id.edit_new_message_text) + val emojiEditText = + newMessageView.findViewById(R.id.edit_new_message_text) val newMessageTitle = newMessageView.findViewById(R.id.new_message_title) newMessageTitle.text = String.format(getString(R.string.profile_send_message_to), username) @@ -306,13 +357,17 @@ class PartyDetailFragment : BaseFragment() { (activity as? MainActivity)?.snackbarContainer?.let { it1 -> HabiticaSnackbar.showSnackbar( it1, - String.format(getString(R.string.profile_message_sent_to), username), HabiticaSnackbar.SnackbarDisplayType.NORMAL + String.format(getString(R.string.profile_message_sent_to), username), + HabiticaSnackbar.SnackbarDisplayType.NORMAL ) } } activity?.dismissKeyboard() } - addMessageDialog?.addButton(android.R.string.cancel, false) { _, _ -> activity?.dismissKeyboard() } + addMessageDialog?.addButton( + android.R.string.cancel, + false + ) { _, _ -> activity?.dismissKeyboard() } addMessageDialog?.setAdditionalContentView(newMessageView) addMessageDialog?.show() } @@ -325,7 +380,8 @@ class PartyDetailFragment : BaseFragment() { (activity as? MainActivity)?.snackbarContainer?.let { it1 -> HabiticaSnackbar.showSnackbar( it1, - String.format(getString(R.string.transferred_ownership), displayName), HabiticaSnackbar.SnackbarDisplayType.NORMAL + String.format(getString(R.string.transferred_ownership), displayName), + HabiticaSnackbar.SnackbarDisplayType.NORMAL ) } } @@ -333,7 +389,12 @@ class PartyDetailFragment : BaseFragment() { } dialog?.addButton(android.R.string.cancel, false) { _, _ -> activity?.dismissKeyboard() } dialog?.setTitle(context?.getString(R.string.transfer_ownership_confirm)) - dialog?.setMessage(context?.getString(R.string.transfer_ownership_confirm_message, displayName)) + dialog?.setMessage( + context?.getString( + R.string.transfer_ownership_confirm_message, + displayName + ) + ) dialog?.show() } @@ -345,7 +406,8 @@ class PartyDetailFragment : BaseFragment() { (activity as? MainActivity)?.snackbarContainer?.let { it1 -> HabiticaSnackbar.showSnackbar( it1, - String.format(getString(R.string.removed_member), displayName), HabiticaSnackbar.SnackbarDisplayType.NORMAL + String.format(getString(R.string.removed_member), displayName), + HabiticaSnackbar.SnackbarDisplayType.NORMAL ) } } @@ -370,7 +432,8 @@ class PartyDetailFragment : BaseFragment() { lifecycleScope.launchCatching { userRepository.getUser().collect { it?.challenges?.forEach { membership -> - val challenge = challengeRepository.getChallenge(membership.challengeID).firstOrNull() + val challenge = + challengeRepository.getChallenge(membership.challengeID).firstOrNull() if (challenge != null && challenge.groupId == viewModel?.groupID) { groupChallenges.add(challenge) } @@ -396,7 +459,11 @@ class PartyDetailFragment : BaseFragment() { MainNavigationController.navigate(R.id.noPartyFragment) } } - alert.addButton(R.string.leave_challenges_delete_tasks, false, isDestructive = true) { _, _ -> + alert.addButton( + R.string.leave_challenges_delete_tasks, + false, + isDestructive = true + ) { _, _ -> viewModel?.leaveGroup(groupChallenges, false) { parentFragmentManager.popBackStack() MainNavigationController.navigate(R.id.noPartyFragment) @@ -408,7 +475,11 @@ class PartyDetailFragment : BaseFragment() { val alert = HabiticaAlertDialog(context) alert.setTitle(R.string.leave_party_confirmation) alert.setMessage(R.string.rejoin_party) - alert.addButton(R.string.leave, isPrimary = true, isDestructive = true) { _, _ -> + alert.addButton( + R.string.leave, + isPrimary = true, + isDestructive = true + ) { _, _ -> viewModel?.leaveGroup(groupChallenges, false) { parentFragmentManager.popBackStack() MainNavigationController.navigate(R.id.noPartyFragment) 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 401066fe1..27e0c156b 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 @@ -210,7 +210,7 @@ fun PartySeekingView( user = it, inviteState =viewModel.inviteStates[it.id] ?: LoadingButtonState.CONTENT, isInvited = viewModel.successfulInvites.contains(it.id), - modifier = Modifier.animateItemPlacement() + modifier = Modifier.animateItemPlacement().padding(horizontal = 14.dp) ) { member -> scope.launchCatching({ viewModel.inviteStates[member.id] = LoadingButtonState.FAILED diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/GroupViewModel.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/GroupViewModel.kt index e6f3117d6..2e6dbc1d8 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/GroupViewModel.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/GroupViewModel.kt @@ -1,6 +1,8 @@ package com.habitrpg.android.habitica.ui.viewmodels import android.os.Bundle +import androidx.compose.runtime.mutableStateListOf +import androidx.compose.runtime.mutableStateMapOf import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.asLiveData @@ -14,12 +16,14 @@ import com.habitrpg.android.habitica.models.members.Member import com.habitrpg.android.habitica.models.social.Challenge import com.habitrpg.android.habitica.models.social.ChatMessage import com.habitrpg.android.habitica.models.social.Group +import com.habitrpg.android.habitica.ui.views.LoadingButtonState import com.habitrpg.common.habitica.helpers.ExceptionHandler import com.habitrpg.common.habitica.helpers.launchCatching import com.habitrpg.common.habitica.models.notifications.NewChatMessageData import dagger.hilt.android.lifecycle.HiltViewModel import io.realm.kotlin.toFlow import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.filterNotNull @@ -29,8 +33,10 @@ import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch import retrofit2.HttpException import javax.inject.Inject +import kotlin.time.DurationUnit +import kotlin.time.toDuration -enum class GroupViewType(internal val order : String) { +enum class GroupViewType(internal val order: String) { PARTY("party"), GUILD("guild"), TAVERN("tavern") @@ -39,17 +45,17 @@ enum class GroupViewType(internal val order : String) { @OptIn(ExperimentalCoroutinesApi::class) @HiltViewModel open class GroupViewModel @Inject constructor( - userRepository : UserRepository, - userViewModel : MainUserViewModel, - val challengeRepository : ChallengeRepository, - val socialRepository : SocialRepository, - val notificationsManager : NotificationsManager + userRepository: UserRepository, + userViewModel: MainUserViewModel, + val challengeRepository: ChallengeRepository, + val socialRepository: SocialRepository, + val notificationsManager: NotificationsManager ) : BaseViewModel(userRepository, userViewModel) { protected val groupIDState = MutableStateFlow(null) - val groupIDFlow : Flow = groupIDState + val groupIDFlow: Flow = groupIDState - var groupViewType : GroupViewType? = null + var groupViewType: GroupViewType? = null private val groupFlow = groupIDFlow .filterNotNull() @@ -67,21 +73,21 @@ open class GroupViewModel @Inject constructor( .map { it != null } private val isMemberData = isMemberFlow.asLiveData() - private val _chatMessages : MutableLiveData> by lazy { + private val _chatMessages: MutableLiveData> by lazy { MutableLiveData>(listOf()) } - val chatmessages : LiveData> by lazy { + val chatmessages: LiveData> by lazy { _chatMessages } - var gotNewMessages : Boolean = false + var gotNewMessages: Boolean = false override fun onCleared() { socialRepository.close() super.onCleared() } - fun setGroupID(groupID : String) { + fun setGroupID(groupID: String) { if (groupID == groupIDState.value) return groupIDState.value = groupID @@ -95,22 +101,25 @@ open class GroupViewModel @Inject constructor( } } - val groupID : String? + val groupID: String? get() = groupIDState.value - val isMember : Boolean + val isMember: Boolean get() = isMemberData.value ?: false - val leaderID : String? + val leaderID: String? get() = group.value?.leaderID - val isLeader : Boolean + val isLeader: Boolean get() = user.value?.id == leaderID - val isPublicGuild : Boolean + val isPublicGuild: Boolean get() = group.value?.privacy == "public" - fun getGroupData() : LiveData = group - fun getLeaderData() : LiveData = leader - fun getIsMemberData() : LiveData = isMemberData + val pendingInvites = mutableStateListOf() + val pendingInviteStates = mutableStateMapOf() - fun retrieveGroup(function : (() -> Unit)?) { + fun getGroupData(): LiveData = group + fun getLeaderData(): LiveData = leader + fun getIsMemberData(): LiveData = isMemberData + + fun retrieveGroup(function: (() -> Unit)?) { if (groupID?.isNotEmpty() == true) { viewModelScope.launch( ExceptionHandler.coroutine { @@ -122,19 +131,23 @@ open class GroupViewModel @Inject constructor( val group = socialRepository.retrieveGroup(groupID ?: "") if (groupViewType == GroupViewType.PARTY) { socialRepository.retrievePartyMembers(group?.id ?: "", true) + val invites = + socialRepository.retrievegroupInvites(group?.id ?: "", true) ?: emptyList() + pendingInvites.clear() + pendingInvites.addAll(invites) } function?.invoke() } } } - fun inviteToGroup(inviteData : HashMap) { + fun inviteToGroup(inviteData: HashMap) { viewModelScope.launchCatching { socialRepository.inviteToGroup(group.value?.id ?: "", inviteData) } } - fun updateOrCreateGroup(bundle : Bundle?) { + fun updateOrCreateGroup(bundle: Bundle?) { viewModelScope.launch(ExceptionHandler.coroutine()) { if (group.value == null) { socialRepository.createGroup( @@ -157,9 +170,9 @@ open class GroupViewModel @Inject constructor( } fun leaveGroup( - groupChallenges : List, - keepChallenges : Boolean = true, - function : (() -> Unit)? = null + groupChallenges: List, + keepChallenges: Boolean = true, + function: (() -> Unit)? = null ) { if (!keepChallenges) { viewModelScope.launchCatching { @@ -175,14 +188,14 @@ open class GroupViewModel @Inject constructor( } } - fun joinGroup(id : String? = null, function : (() -> Unit)? = null) { + fun joinGroup(id: String? = null, function: (() -> Unit)? = null) { viewModelScope.launchCatching { socialRepository.joinGroup(id ?: groupID) function?.invoke() } } - fun rejectGroupInvite(id : String? = null) { + fun rejectGroupInvite(id: String? = null) { groupID?.let { viewModelScope.launchCatching { socialRepository.rejectGroupInvite(id ?: it) @@ -200,7 +213,7 @@ open class GroupViewModel @Inject constructor( } } - fun likeMessage(message : ChatMessage) { + fun likeMessage(message: ChatMessage) { viewModelScope.launchCatching { val message = socialRepository.likeMessage(message) val index = _chatMessages.value?.indexOfFirst { it.id == message?.id } @@ -216,7 +229,7 @@ open class GroupViewModel @Inject constructor( } } - fun deleteMessage(chatMessage : ChatMessage) { + fun deleteMessage(chatMessage: ChatMessage) { val oldIndex = _chatMessages.value?.indexOf(chatMessage) ?: return val list = _chatMessages.value?.toMutableList() list?.remove(chatMessage) @@ -232,7 +245,7 @@ open class GroupViewModel @Inject constructor( } } - fun postGroupChat(chatText : String, onComplete : () -> Unit, onError : () -> Unit) { + fun postGroupChat(chatText: String, onComplete: () -> Unit, onError: () -> Unit) { groupID?.let { groupID -> viewModelScope.launch( ExceptionHandler.coroutine { @@ -251,7 +264,7 @@ open class GroupViewModel @Inject constructor( } } - fun retrieveGroupChat(onComplete : () -> Unit) { + fun retrieveGroupChat(onComplete: () -> Unit) { var groupID = groupID if (groupViewType == GroupViewType.PARTY) { groupID = "party" @@ -267,7 +280,7 @@ open class GroupViewModel @Inject constructor( } } - fun updateGroup(bundle : Bundle?) { + fun updateGroup(bundle: Bundle?) { viewModelScope.launch(ExceptionHandler.coroutine()) { socialRepository.updateGroup( group.value, @@ -278,4 +291,16 @@ open class GroupViewModel @Inject constructor( ) } } + + fun rescindInvite(invitedMember: Member) { + pendingInviteStates[invitedMember.id] = LoadingButtonState.LOADING + viewModelScope.launchCatching({ + pendingInviteStates[invitedMember.id] = LoadingButtonState.FAILED + }) { + socialRepository.removeMemberFromGroup(groupID ?: "", invitedMember.id) + pendingInviteStates[invitedMember.id] = LoadingButtonState.SUCCESS + delay(1.toDuration(DurationUnit.SECONDS)) + pendingInvites.remove(invitedMember) + } + } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/social/PartySeekingListItem.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/social/PartySeekingListItem.kt index 6f130a113..4a85215ef 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/social/PartySeekingListItem.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/social/PartySeekingListItem.kt @@ -46,13 +46,14 @@ fun PartySeekingListItem( modifier : Modifier = Modifier, inviteState : LoadingButtonState = LoadingButtonState.LOADING, isInvited: Boolean = false, + showHeader: Boolean = false, + showExtendedInfo: Boolean = true, onInvite : (Member) -> Unit ) { Column( modifier .fillMaxWidth() - .padding(horizontal = 14.dp) - .padding(bottom = 4.dp) + .padding(bottom = 6.dp) .background(HabiticaTheme.colors.windowBackground, HabiticaTheme.shapes.large) .padding(14.dp) ) { @@ -68,6 +69,14 @@ fun PartySeekingListItem( verticalArrangement = Arrangement.Top, modifier = Modifier.fillMaxWidth() ) { + if (showHeader) { + Text( + stringResource(R.string.pending_invite).uppercase(), + fontSize = 12.sp, + color = HabiticaTheme.colors.textQuad, + modifier = Modifier.padding(bottom = 4.dp) + ) + } ProvideTextStyle(value = TextStyle(fontSize = 14.sp)) { ComposableUsernameLabel( user.displayName, @@ -101,18 +110,22 @@ fun PartySeekingListItem( hasClass = user.hasClass ) } - Text( - stringResource(R.string.x_checkins, user.loginIncentives), - fontWeight = FontWeight.Medium, - fontSize = 14.sp, - color = HabiticaTheme.colors.textPrimary - ) - Text( - Locale(user.preferences?.language ?: "en").getDisplayName(Locale.getDefault()), - fontWeight = FontWeight.Medium, - fontSize = 14.sp, - color = HabiticaTheme.colors.textPrimary - ) + if (showExtendedInfo) { + Text( + stringResource(R.string.x_checkins, user.loginIncentives), + fontWeight = FontWeight.Medium, + fontSize = 14.sp, + color = HabiticaTheme.colors.textPrimary + ) + Text( + Locale( + user.preferences?.language ?: "en" + ).getDisplayName(Locale.getDefault()), + fontWeight = FontWeight.Medium, + fontSize = 14.sp, + color = HabiticaTheme.colors.textPrimary + ) + } } } InviteButton(