Begin implementing party seeking feature

This commit is contained in:
Phillip Thelen 2023-03-07 12:52:07 +01:00
parent f80c169a3a
commit fd5661aa9e
15 changed files with 269 additions and 54 deletions

View file

@ -1,4 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.compose.ui.platform.ComposeView xmlns:android="http://schemas.android.com/apk/res/android"
<androidx.compose.ui.platform.ComposeView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/compose_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
android:layout_height="match_parent" />

View file

@ -191,6 +191,12 @@
android:layout_height="wrap_content"
android:text="@string/members"
style="@style/SegmentTitle"/>
<Button
android:id="@+id/find_new_member"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/find_new_member"
style="@style/HabiticaButton.Gray"/>
<LinearLayout
android:id="@+id/members_wrapper"
android:layout_width="match_parent"
@ -208,4 +214,4 @@
android:text="@string/leave_party"/>
</LinearLayout>
</androidx.core.widget.NestedScrollView>
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>

View file

@ -67,6 +67,11 @@
android:name="com.habitrpg.android.habitica.ui.fragments.social.party.NoPartyFragmentFragment"
android:label="@string/sidebar_party">
</fragment>
<fragment
android:id="@+id/partyInvitationFragment"
android:name="com.habitrpg.android.habitica.ui.fragments.social.party.PartyInvitePagerFragment"
android:label="@string/sidebar_party">
</fragment>
<fragment
android:id="@+id/questDetailFragment"
android:name="com.habitrpg.android.habitica.ui.fragments.social.QuestDetailFragment">
@ -516,4 +521,4 @@
app:argType="string" />
<deepLink app:uri="habitica.com/promo/web?url={url}" />
</fragment>
</navigation>
</navigation>

View file

@ -462,4 +462,7 @@ interface ApiService {
@POST("tasks/{taskID}/needs-work/{userID}")
suspend fun markTaskNeedsWork(@Path("taskID") taskID: String, @Path("userID") userID: String): HabitResponse<Task>
@GET("party-seekers")
suspend fun retrievePartySeekingUsers(): HabitResponse<List<Member>>
}

View file

@ -96,6 +96,9 @@ import com.habitrpg.android.habitica.ui.fragments.social.party.NoPartyFragmentFr
import com.habitrpg.android.habitica.ui.fragments.social.party.PartyDetailFragment;
import com.habitrpg.android.habitica.ui.fragments.social.party.PartyFragment;
import com.habitrpg.android.habitica.ui.fragments.social.party.PartyInviteFragment;
import com.habitrpg.android.habitica.ui.fragments.social.party.PartyInvitePagerFragment;
import com.habitrpg.android.habitica.ui.fragments.social.party.PartySeekingFragment;
import com.habitrpg.android.habitica.ui.fragments.social.party.PartySeekingViewModel;
import com.habitrpg.android.habitica.ui.fragments.support.BugFixFragment;
import com.habitrpg.android.habitica.ui.fragments.support.FAQDetailFragment;
import com.habitrpg.android.habitica.ui.fragments.support.FAQOverviewFragment;
@ -376,4 +379,10 @@ public interface UserComponent {
void inject(@NotNull AvatarEquipmentFragment avatarEquipmentFragment);
void inject(@NotNull BirthdayActivity birthdayActivity);
void inject(@NotNull PartySeekingFragment partySeekingFragment);
void inject(@NotNull PartySeekingViewModel partySeekingViewModel);
void inject(@NotNull PartyInvitePagerFragment partyInvitePagerFragment);
}

View file

@ -276,4 +276,5 @@ interface ApiClient {
suspend fun updateMember(memberID: String, updateData: Map<String, Any?>): Member?
suspend fun getHallMember(userId: String): Member?
suspend fun markTaskNeedsWork(taskID: String, userID: String): Task?
suspend fun retrievePartySeekingUsers() : List<Member>?
}

View file

@ -121,4 +121,5 @@ interface SocialRepository : BaseRepository {
suspend fun blockMember(userID: String): List<String>?
fun getMember(userID: String?): Flow<Member?>
suspend fun updateMember(memberID: String, key: String, value: Any?): Member?
suspend fun retrievePartySeekingUsers(): List<Member>?
}

View file

@ -622,6 +622,10 @@ class ApiClientImpl(
return process { apiService.markTaskNeedsWork(taskID, userID) }
}
override suspend fun retrievePartySeekingUsers() : List<Member>? {
return process { apiService.retrievePartySeekingUsers() }
}
override suspend fun getMember(memberId: String) = processResponse(apiService.getMember(memberId))
override suspend fun getMemberWithUsername(username: String) = processResponse(apiService.getMemberWithUsername(username))

View file

@ -48,6 +48,10 @@ class SocialRepositoryImpl(
return apiClient.updateMember(memberID, mapOf(key to value))
}
override suspend fun retrievePartySeekingUsers() : List<Member>? {
return apiClient.retrievePartySeekingUsers()
}
override fun getGroupMembership(id: String) = localRepository.getGroupMembership(userID, id)
override fun getGroupMemberships(): Flow<List<GroupMembership>> {

View file

@ -9,6 +9,7 @@ import android.widget.TextView
import androidx.appcompat.widget.AppCompatEditText
import androidx.core.content.ContextCompat
import androidx.core.os.bundleOf
import androidx.core.view.isVisible
import androidx.lifecycle.lifecycleScope
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.components.UserComponent
@ -93,6 +94,9 @@ class PartyDetailFragment : BaseFragment<FragmentPartyDetailBinding>() {
binding?.questDetailButton?.setOnClickListener { questDetailButtonClicked() }
binding?.leaveButton?.setOnClickListener { leaveParty() }
binding?.findNewMember?.setOnClickListener {
MainNavigationController.navigate(R.id.partyInvitationFragment)
}
binding?.invitationsView?.setLeader = null
binding?.invitationsView?.acceptCall = {
@ -153,6 +157,8 @@ class PartyDetailFragment : BaseFragment<FragmentPartyDetailBinding>() {
binding?.questImageWrapper?.visibility = View.GONE
binding?.questProgressView?.visibility = View.GONE
}
binding?.findNewMember?.isVisible = viewModel?.isLeader == true
}
private fun updateUser(user: User?) {

View file

@ -56,11 +56,10 @@ class PartyFragment : BaseMainFragment<FragmentViewpagerBinding>() {
viewModel.groupViewType = GroupViewType.PARTY
viewModel.getGroupData().observe(
viewLifecycleOwner,
{
updateGroupUI(it)
}
)
viewLifecycleOwner
) {
updateGroupUI(it)
}
binding?.viewPager?.currentItem = 0

View file

@ -1,70 +1,38 @@
package com.habitrpg.android.habitica.ui.fragments.social.party
import android.os.Bundle
import android.text.InputType
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.EditText
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.components.UserComponent
import com.habitrpg.android.habitica.databinding.FragmentPartyInviteBinding
import com.habitrpg.android.habitica.databinding.FragmentComposeBinding
import com.habitrpg.android.habitica.helpers.AppConfigManager
import com.habitrpg.android.habitica.ui.fragments.BaseFragment
import com.habitrpg.android.habitica.ui.viewmodels.BaseViewModel
import javax.inject.Inject
class PartyInviteFragment : BaseFragment<FragmentPartyInviteBinding>() {
class PartyInviteViewModel: BaseViewModel() {
override fun inject(component : UserComponent) {
}
}
class PartyInviteFragment : BaseFragment<FragmentComposeBinding>() {
@Inject
lateinit var configManager: AppConfigManager
override var binding: FragmentPartyInviteBinding? = null
override var binding: FragmentComposeBinding? = null
override fun createBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentPartyInviteBinding {
return FragmentPartyInviteBinding.inflate(inflater, container, false)
override fun createBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentComposeBinding {
return FragmentComposeBinding.inflate(inflater, container, false)
}
var isEmailInvite: Boolean = false
val values: Array<String>
get() {
val values = ArrayList<String>()
for (i in 0 until (binding?.invitationWrapper?.childCount ?: 0)) {
val valueEditText = binding?.invitationWrapper?.getChildAt(i) as? EditText
if (valueEditText?.text?.toString()?.isNotEmpty() == true) {
values.add(valueEditText.text.toString())
}
}
return values.toTypedArray()
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
if (isEmailInvite) {
binding?.inviteDescription?.text = getString(R.string.invite_email_description)
} else {
binding?.inviteDescription?.text = getString(R.string.invite_username_description)
}
addInviteField()
binding?.addInviteButton?.setOnClickListener { addInviteField() }
}
override fun injectFragment(component: UserComponent) {
component.inject(this)
}
private fun addInviteField() {
val editText = EditText(context)
if (isEmailInvite) {
editText.setHint(R.string.email)
editText.inputType = InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS
} else {
editText.setHint(R.string.username)
}
binding?.invitationWrapper?.addView(editText)
}
}

View file

@ -0,0 +1,80 @@
package com.habitrpg.android.habitica.ui.fragments.social.party
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.viewpager2.adapter.FragmentStateAdapter
import com.google.android.material.tabs.TabLayoutMediator
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.components.UserComponent
import com.habitrpg.android.habitica.databinding.FragmentViewpagerBinding
import com.habitrpg.android.habitica.ui.fragments.BaseMainFragment
class PartyInvitePagerFragment : BaseMainFragment<FragmentViewpagerBinding>() {
override var binding : FragmentViewpagerBinding? = null
override fun createBinding(
inflater : LayoutInflater,
container : ViewGroup?
) : FragmentViewpagerBinding {
return FragmentViewpagerBinding.inflate(inflater, container, false)
}
override fun onCreateView(
inflater : LayoutInflater,
container : ViewGroup?,
savedInstanceState : Bundle?
) : View? {
this.usesTabLayout = true
this.hidesToolbar = true
return super.onCreateView(inflater, container, savedInstanceState)
}
override fun onViewCreated(view : View, savedInstanceState : Bundle?) {
super.onViewCreated(view, savedInstanceState)
setViewPagerAdapter()
binding?.viewPager?.currentItem = 0
}
override fun injectFragment(component : UserComponent) {
component.inject(this)
}
private fun setViewPagerAdapter() {
val fragmentManager = childFragmentManager
binding?.viewPager?.adapter = object : FragmentStateAdapter(fragmentManager, lifecycle) {
override fun createFragment(position : Int) : Fragment {
return when (position) {
0 -> {
PartySeekingFragment()
}
1 -> {
PartyInviteFragment()
}
else -> Fragment()
}
}
override fun getItemCount() : Int {
return 2
}
}
tabLayout?.let {
binding?.viewPager?.let { it1 ->
TabLayoutMediator(it, it1) { tab, position ->
tab.text = when (position) {
0 -> context?.getString(R.string.list)
1 -> context?.getString(R.string.by_invite)
else -> ""
}
}.attach()
}
}
}
}

View file

@ -0,0 +1,127 @@
package com.habitrpg.android.habitica.ui.fragments.social.party
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.Text
import androidx.compose.material.pullrefresh.pullRefresh
import androidx.compose.material.pullrefresh.rememberPullRefreshState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.fragment.app.viewModels
import androidx.lifecycle.viewModelScope
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.components.UserComponent
import com.habitrpg.android.habitica.data.SocialRepository
import com.habitrpg.android.habitica.databinding.FragmentComposeBinding
import com.habitrpg.android.habitica.models.members.Member
import com.habitrpg.android.habitica.ui.fragments.BaseFragment
import com.habitrpg.android.habitica.ui.theme.HabiticaTheme
import com.habitrpg.android.habitica.ui.viewmodels.BaseViewModel
import com.habitrpg.common.habitica.helpers.launchCatching
import javax.inject.Inject
class PartySeekingViewModel: BaseViewModel() {
val isRefreshing = mutableStateOf(false)
@Inject
lateinit var socialRepository: SocialRepository
val seekingUsers = mutableStateOf<List<Member>>(emptyList())
override fun inject(component : UserComponent) {
component.inject(this)
}
init {
retrieveUsers()
}
fun retrieveUsers() {
isRefreshing.value = true
viewModelScope.launchCatching {
seekingUsers.value = socialRepository.retrievePartySeekingUsers() ?: emptyList()
isRefreshing.value = false
}
}
}
class PartySeekingFragment: BaseFragment<FragmentComposeBinding>() {
val viewModel: PartySeekingViewModel by viewModels()
override var binding: FragmentComposeBinding? = null
override fun createBinding(
inflater : LayoutInflater,
container : ViewGroup?
) : FragmentComposeBinding {
return FragmentComposeBinding.inflate(inflater)
}
override fun injectFragment(component : UserComponent) {
component.inject(this)
}
override fun onCreateView(
inflater : LayoutInflater,
container : ViewGroup?,
savedInstanceState : Bundle?
) : View? {
val view = super.onCreateView(inflater, container, savedInstanceState)
binding?.composeView?.setContent {
HabiticaTheme {
PartySeekingView(viewModel)
}
}
return view
}
override fun onResume() {
super.onResume()
viewModel.retrieveUsers()
}
}
@Composable
fun PartySeekingListItem(user: Member,
modifier : Modifier = Modifier) {
Column(modifier.fillMaxWidth()) {
Text(user.username ?: "")
}
}
@OptIn(ExperimentalMaterialApi::class)
@Composable
fun PartySeekingView(
viewModel: PartySeekingViewModel,
modifier : Modifier = Modifier
) {
val users: List<Member> by viewModel.seekingUsers
val refreshing by viewModel.isRefreshing
val pullRefreshState = rememberPullRefreshState(refreshing, { viewModel.retrieveUsers() })
LazyColumn(modifier.pullRefresh(pullRefreshState)) {
item {
Column(horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier.fillMaxWidth().padding(top = 22.dp, bottom = 14.dp)) {
Text(stringResource(R.string.find_more_members), color = HabiticaTheme.colors.textPrimary, fontSize = 16.sp, fontWeight = FontWeight.Medium)
Text(stringResource(R.string.habiticans_looking_party), color = HabiticaTheme.colors.textSecondary)
}
}
items(users) {
PartySeekingListItem(user = it)
}
}
}

View file

@ -31,7 +31,7 @@ class HostConfig {
}
} else {
val address = sharedPreferences.getString("server_url", null)
if (address != null && address.isNotEmpty()) {
if (!address.isNullOrEmpty()) {
this.address = address
} else {
this.address = context.getString(com.habitrpg.common.habitica.R.string.base_url)