diff --git a/Habitica/res/drawable/ic_more_horiz_black_18dp.xml b/Habitica/res/drawable/ic_more_horiz_black_18dp.xml new file mode 100644 index 000000000..773ff13ff --- /dev/null +++ b/Habitica/res/drawable/ic_more_horiz_black_18dp.xml @@ -0,0 +1,4 @@ + + + + diff --git a/Habitica/res/layout/party_member.xml b/Habitica/res/layout/party_member.xml index 402f3df06..d97072e71 100644 --- a/Habitica/res/layout/party_member.xml +++ b/Habitica/res/layout/party_member.xml @@ -21,12 +21,26 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical"> + - + + + @@ -115,5 +129,4 @@ - \ No newline at end of file diff --git a/Habitica/res/menu/party_member_menu.xml b/Habitica/res/menu/party_member_menu.xml new file mode 100644 index 000000000..1cb35b19e --- /dev/null +++ b/Habitica/res/menu/party_member_menu.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/Habitica/res/values/strings.xml b/Habitica/res/values/strings.xml index 639c9f0a4..c9ef00077 100644 --- a/Habitica/res/values/strings.xml +++ b/Habitica/res/values/strings.xml @@ -947,4 +947,13 @@ You sent a %s New messages in %s You received %d messages from %s + Remove + Transfer Ownership + Remove Member + Send Message + Ownership transferred to %s + Are you sure you want to transfer ownership to %s + Are you sure you want to remove %s from the group? + Transfer + %s was removed from the group 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 43ca1b994..c20e80d46 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 @@ -175,7 +175,10 @@ interface ApiService { fun createGroup(@Body item: Group): Flowable> @PUT("groups/{id}") - fun updateGroup(@Path("id") id: String, @Body item: Group): Flowable> + fun updateGroup(@Path("id") id: String, @Body item: Group): Flowable> + + @POST("groups/{groupID}/removeMember/{userID}") + fun removeMemberFromGroup(@Path("groupID") groupID: String, @Path("userID") userID: String): Flowable> @GET("groups/{gid}/chat") fun listGroupChat(@Path("gid") groupId: String): Flowable>> diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/components/UserComponent.java b/Habitica/src/main/java/com/habitrpg/android/habitica/components/UserComponent.java index 0aa76c96b..bf5017a78 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/components/UserComponent.java +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/components/UserComponent.java @@ -89,7 +89,6 @@ import com.habitrpg.android.habitica.ui.fragments.social.challenges.ChallengesOv 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.PartyMemberListFragment; import com.habitrpg.android.habitica.ui.fragments.tasks.TaskRecyclerViewFragment; import com.habitrpg.android.habitica.ui.fragments.tasks.TasksFragment; import com.habitrpg.android.habitica.ui.viewmodels.GroupViewModel; @@ -179,8 +178,6 @@ public interface UserComponent { void inject(PartyInviteFragment partyInviteFragment); - void inject(PartyMemberListFragment partyMemberListFragment); - void inject(ChatListFragment chatListFragment); void inject(GroupInformationFragment groupInformationFragment); 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 6b5c2bc04..0434c2138 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 @@ -129,7 +129,8 @@ interface ApiClient { fun getGroup(groupId: String): Flowable fun createGroup(group: Group): Flowable - fun updateGroup(id: String, item: Group): Flowable + fun updateGroup(id: String, item: Group): Flowable + fun removeMemberFromGroup(groupID: String, userID: String): Flowable fun listGroupChat(groupId: String): Flowable> diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/SocialRepository.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/SocialRepository.kt index 6901a03c5..93ed41789 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 @@ -40,7 +40,7 @@ interface SocialRepository : BaseRepository { fun joinGroup(id: String?): Flowable fun createGroup(name: String?, description: String?, leader: String?, type: String?, privacy: String?, leaderCreateChallenge: Boolean?): Flowable - fun updateGroup(group: Group?, name: String?, description: String?, leader: String?, leaderCreateChallenge: Boolean?): Flowable + fun updateGroup(group: Group?, name: String?, description: String?, leader: String?, leaderCreateChallenge: Boolean?): Flowable fun retrieveGroups(type: String): Flowable> fun getGroups(type: String): Flowable> @@ -65,6 +65,10 @@ interface SocialRepository : BaseRepository { fun markPrivateMessagesRead(user: User?): Flowable + + fun transferGroupOwnership(groupID: String, userID: String): Flowable + fun removeMemberFromGroup(groupID: String, userID: String): Flowable> + fun acceptQuest(user: User?, partyId: String = "party"): Flowable fun rejectQuest(user: User?, partyId: String = "party"): Flowable diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/ApiClientImpl.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/ApiClientImpl.kt index 5123f1fb3..c53e62e96 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 @@ -476,10 +476,14 @@ class ApiClientImpl//private OnHabitsAPIResult mResultListener; return apiService.createGroup(group).compose(configureApiCallObserver()) } - override fun updateGroup(id: String, item: Group): Flowable { + override fun updateGroup(id: String, item: Group): Flowable { return apiService.updateGroup(id, item).compose(configureApiCallObserver()) } + override fun removeMemberFromGroup(groupID: String, userID: String): Flowable { + return apiService.removeMemberFromGroup(groupID, userID).compose(configureApiCallObserver()) + } + override fun listGroupChat(groupId: String): Flowable> { return apiService.listGroupChat(groupId).compose(configureApiCallObserver()) } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/SocialRepositoryImpl.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/SocialRepositoryImpl.kt index b5ff7386e..b308087b2 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 @@ -20,6 +20,25 @@ import io.reactivex.functions.Consumer import io.realm.RealmResults class SocialRepositoryImpl(localRepository: SocialLocalRepository, apiClient: ApiClient, userID: String) : BaseRepositoryImpl(localRepository, apiClient, userID), SocialRepository { + override fun transferGroupOwnership(groupID: String, userID: String): Flowable { + return localRepository.getGroup(groupID) + .map { + val group = localRepository.getUnmanagedCopy(it) + group.leaderID = userID + group + } + .flatMap { + apiClient.updateGroup(it.id, it) + } + } + + override fun removeMemberFromGroup(groupID: String, userID: String): Flowable> { + return apiClient.removeMemberFromGroup(groupID, userID) + .flatMap { + retrieveGroupMembers(groupID, true) + } + } + override fun getChatmessage(messageID: String): Flowable { return localRepository.getChatMessage(messageID) } @@ -152,7 +171,7 @@ class SocialRepositoryImpl(localRepository: SocialLocalRepository, apiClient: Ap } } - override fun updateGroup(group: Group?, name: String?, description: String?, leader: String?, leaderCreateChallenge: Boolean?): Flowable { + override fun updateGroup(group: Group?, name: String?, description: String?, leader: String?, leaderCreateChallenge: Boolean?): Flowable { if (group == null) { return Flowable.empty() } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/social/PartyMemberRecyclerViewAdapter.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/social/PartyMemberRecyclerViewAdapter.kt index 071b60621..8135ac277 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/social/PartyMemberRecyclerViewAdapter.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/social/PartyMemberRecyclerViewAdapter.kt @@ -23,7 +23,7 @@ class PartyMemberRecyclerViewAdapter(data: OrderedRealmCollection?, auto override fun onBindViewHolder(holder: GroupMemberViewHolder, position: Int) { data?.let { - holder.bind(it[position], leaderID) + holder.bind(it[position], leaderID, null) holder.onClickEvent = { userClickedEvents.onNext(it[position].id ?: "") } 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 96dffae4c..68afa5dcf 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 @@ -13,6 +13,7 @@ import com.facebook.drawee.view.SimpleDraweeView import com.habitrpg.android.habitica.R import com.habitrpg.android.habitica.components.UserComponent import com.habitrpg.android.habitica.data.InventoryRepository +import com.habitrpg.android.habitica.data.SocialRepository import com.habitrpg.android.habitica.extensions.inflate import com.habitrpg.android.habitica.helpers.MainNavigationController import com.habitrpg.android.habitica.helpers.RxErrorHandler @@ -22,15 +23,18 @@ import com.habitrpg.android.habitica.models.social.Group import com.habitrpg.android.habitica.models.user.User import com.habitrpg.android.habitica.modules.AppModule import com.habitrpg.android.habitica.ui.activities.FullProfileActivity +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.ItemRecyclerFragment import com.habitrpg.android.habitica.ui.helpers.* import com.habitrpg.android.habitica.ui.viewHolders.GroupMemberViewHolder.GroupMemberViewHolder import com.habitrpg.android.habitica.ui.viewmodels.PartyViewModel +import com.habitrpg.android.habitica.ui.views.HabiticaSnackbar import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog import com.habitrpg.android.habitica.ui.views.social.OldQuestProgressView import io.reactivex.functions.Consumer import io.realm.RealmResults +import net.pherth.android.emoji_library.EmojiEditText import javax.inject.Inject import javax.inject.Named @@ -39,6 +43,8 @@ class PartyDetailFragment : BaseFragment() { var viewModel: PartyViewModel? = null + @Inject + lateinit var socialRepository: SocialRepository @Inject lateinit var inventoryRepository: InventoryRepository @field:[Inject Named(AppModule.NAMED_USER_ID)] @@ -189,15 +195,83 @@ class PartyDetailFragment : BaseFragment() { for (member in members) { val memberView = membersWrapper?.inflate(R.layout.party_member, false) ?: continue val viewHolder = GroupMemberViewHolder(memberView) - viewHolder.bind(member, leaderID ?: "") + viewHolder.bind(member, leaderID ?: "", viewModel?.getUserData()?.value?.id) viewHolder.onClickEvent = { FullProfileActivity.open(member.id ?: "") } + viewHolder.sendMessageEvent = { + member.id?.let { showSendMessageToUserDialog(it, member.displayName) } + } + viewHolder.transferOwnershipEvent = { + member.id?.let { showTransferOwnerShipDialog(it, member.displayName) } + } + viewHolder.removeMemberEvent = { + member.id?.let { showRemoveMemberDialog(it, member.displayName) } + } membersWrapper?.addView(memberView) } } } + private fun showSendMessageToUserDialog(userID: String, username: String) { + 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 newMessageTitle = newMessageView.findViewById(R.id.new_message_title) + newMessageTitle.text = String.format(getString(R.string.profile_send_message_to), username) + + val addMessageDialog = context?.let { HabiticaAlertDialog(it) } + addMessageDialog?.addButton(android.R.string.ok, true) { _, _ -> + socialRepository.postPrivateMessage(userID, emojiEditText.text.toString()) + .subscribe(Consumer { + (activity as? MainActivity)?.snackbarContainer?.let { it1 -> + HabiticaSnackbar.showSnackbar(it1, + String.format(getString(R.string.profile_message_sent_to), username), HabiticaSnackbar.SnackbarDisplayType.NORMAL) + } + }, RxErrorHandler.handleEmptyError()) + activity?.dismissKeyboard() + } + addMessageDialog?.addButton(android.R.string.cancel, false) { _, _ -> activity?.dismissKeyboard() } + addMessageDialog?.setAdditionalContentView(newMessageView) + addMessageDialog?.show() + } + + private fun showTransferOwnerShipDialog(userID: String, displayName: String) { + val dialog = context?.let { HabiticaAlertDialog(it) } + dialog?.addButton(R.string.transfer, true) { _, _ -> + socialRepository.transferGroupOwnership(viewModel?.groupID ?: "", userID) + .subscribe(Consumer { + (activity as? MainActivity)?.snackbarContainer?.let { it1 -> + HabiticaSnackbar.showSnackbar(it1, + String.format(getString(R.string.transferred_ownership), displayName), HabiticaSnackbar.SnackbarDisplayType.NORMAL) + } + }, RxErrorHandler.handleEmptyError()) + activity?.dismissKeyboard() + } + dialog?.addButton(android.R.string.cancel, false) { _, _ -> activity?.dismissKeyboard() } + dialog?.setTitle(context?.getString(R.string.transfer_ownership_confirm, displayName)) + dialog?.show() + } + + private fun showRemoveMemberDialog(userID: String, displayName: String) { + val dialog = context?.let { HabiticaAlertDialog(it) } + dialog?.addButton(R.string.remove, true) { _, _ -> + socialRepository.removeMemberFromGroup(viewModel?.groupID ?: "", userID) + .subscribe(Consumer { + (activity as? MainActivity)?.snackbarContainer?.let { it1 -> + HabiticaSnackbar.showSnackbar(it1, + String.format(getString(R.string.removed_member), displayName), HabiticaSnackbar.SnackbarDisplayType.NORMAL) + } + }, RxErrorHandler.handleEmptyError()) + activity?.dismissKeyboard() + } + dialog?.addButton(android.R.string.cancel, false) { _, _ -> activity?.dismissKeyboard() } + dialog?.setTitle(context?.getString(R.string.remove_member_confirm, displayName)) + dialog?.show() + } + private fun inviteNewQuest() { val fragment = ItemRecyclerFragment() fragment.itemType = "quests" diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/party/PartyMemberListFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/party/PartyMemberListFragment.kt deleted file mode 100644 index 28c57a4d6..000000000 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/party/PartyMemberListFragment.kt +++ /dev/null @@ -1,76 +0,0 @@ -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 com.habitrpg.android.habitica.R -import com.habitrpg.android.habitica.components.UserComponent -import com.habitrpg.android.habitica.data.SocialRepository -import com.habitrpg.android.habitica.extensions.inflate -import com.habitrpg.android.habitica.helpers.RxErrorHandler -import com.habitrpg.android.habitica.ui.activities.FullProfileActivity -import com.habitrpg.android.habitica.ui.adapter.social.PartyMemberRecyclerViewAdapter -import com.habitrpg.android.habitica.ui.fragments.BaseFragment -import com.habitrpg.android.habitica.ui.helpers.SafeDefaultItemAnimator -import com.habitrpg.android.habitica.ui.helpers.bindView -import com.habitrpg.android.habitica.ui.viewmodels.PartyViewModel -import io.reactivex.functions.Consumer -import javax.inject.Inject - -class PartyMemberListFragment : BaseFragment() { - - var viewModel: PartyViewModel? = null - - @Inject - lateinit var socialRepository: SocialRepository - - private val recyclerView: androidx.recyclerview.widget.RecyclerView? by bindView(R.id.recyclerView) - private val refreshLayout: androidx.swiperefreshlayout.widget.SwipeRefreshLayout? by bindView(R.id.refreshLayout) - private var adapter: PartyMemberRecyclerViewAdapter? = null - private var partyId: String? = null - - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - super.onCreateView(inflater, container, savedInstanceState) - return container?.inflate(R.layout.fragment_refresh_recyclerview) - } - - override fun injectFragment(component: UserComponent) { - component.inject(this) - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - recyclerView?.layoutManager = androidx.recyclerview.widget.LinearLayoutManager(context) - adapter = PartyMemberRecyclerViewAdapter(null, true) - adapter?.getUserClickedEvents()?.subscribe(Consumer { userId -> FullProfileActivity.open(userId) }, RxErrorHandler.handleEmptyError())?.let { compositeSubscription.add(it) } - recyclerView?.adapter = adapter - recyclerView?.itemAnimator = SafeDefaultItemAnimator() - - refreshLayout?.setOnRefreshListener { this.refreshMembers() } - getUsers() - } - - private fun refreshMembers() { - setRefreshing(true) - compositeSubscription.add(socialRepository.retrieveGroupMembers(partyId ?: "", true).doOnComplete { setRefreshing(false) }.subscribe(Consumer { }, RxErrorHandler.handleEmptyError())) - } - - private fun setRefreshing(isRefreshing: Boolean) { - refreshLayout?.isRefreshing = isRefreshing - } - - fun setPartyId(id: String) { - this.partyId = id - } - - private fun getUsers() { - if (partyId == null) { - return - } - compositeSubscription.add(socialRepository.getGroupMembers(partyId ?: "").firstElement().subscribe(Consumer { users -> - adapter?.updateData(users) - }, RxErrorHandler.handleEmptyError())) - } -} diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/GroupMemberViewHolder/MemberViewHolder.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/GroupMemberViewHolder/MemberViewHolder.kt index 4026cf78b..60c9b0c48 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/GroupMemberViewHolder/MemberViewHolder.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/GroupMemberViewHolder/MemberViewHolder.kt @@ -1,8 +1,11 @@ package com.habitrpg.android.habitica.ui.viewHolders.GroupMemberViewHolder +import android.view.MenuItem import android.view.View +import android.widget.ImageButton import android.widget.ImageView import android.widget.TextView +import androidx.appcompat.widget.PopupMenu import com.habitrpg.android.habitica.R import com.habitrpg.android.habitica.models.members.Member import com.habitrpg.android.habitica.models.user.Stats @@ -12,8 +15,11 @@ import com.habitrpg.android.habitica.ui.views.HabiticaIconsHelper import com.habitrpg.android.habitica.ui.views.HabiticaProgressBar import com.habitrpg.android.habitica.ui.views.social.UsernameLabel -class GroupMemberViewHolder(itemView: View) : androidx.recyclerview.widget.RecyclerView.ViewHolder(itemView) { +class GroupMemberViewHolder(itemView: View) : androidx.recyclerview.widget.RecyclerView.ViewHolder(itemView), PopupMenu.OnMenuItemClickListener { + + private var currentUserID: String? = null + private var leaderID: String? = null private val avatarView: AvatarView by bindView(R.id.avatarView) private val displayNameTextView: UsernameLabel by bindView(R.id.display_name_textview) private val sublineTextView: TextView by bindView(R.id.subline_textview) @@ -25,25 +31,57 @@ class GroupMemberViewHolder(itemView: View) : androidx.recyclerview.widget.Recyc private val healthTextView: TextView by bindView(R.id.health_textview) private val experienceTextView: TextView by bindView(R.id.experience_textview) private val manaTextView: TextView by bindView(R.id.mana_textview) + private val moreButton: ImageButton by bindView(R.id.more_button) //private val leaderTextView: TextView by bindView(R.id.leader_textview) var onClickEvent: (() -> Unit)? = null + var sendMessageEvent: (() -> Unit)? = null + var removeMemberEvent: (() -> Unit)? = null + var transferOwnershipEvent: (() -> Unit)? = null init { buffIconView.setImageBitmap(HabiticaIconsHelper.imageOfBuffIcon()) itemView.setOnClickListener { onClickEvent?.invoke() } + moreButton.setOnClickListener { showOptionsPopup() } } - fun bind(user: Member, leaderID: String?) { + private fun showOptionsPopup() { + val popup = PopupMenu(itemView.context, moreButton) + popup.setOnMenuItemClickListener(this) + val inflater = popup.menuInflater + inflater.inflate(R.menu.party_member_menu, popup.menu) + popup.menu.findItem(R.id.transfer_ownership).isVisible = currentUserID == leaderID + popup.menu.findItem(R.id.remove).isVisible = currentUserID == leaderID + popup.show() + } + + override fun onMenuItemClick(item: MenuItem?): Boolean { + when (item?.itemId) { + R.id.send_message -> { sendMessageEvent?.invoke() } + R.id.transfer_ownership -> { transferOwnershipEvent?.invoke() } + R.id.remove -> { removeMemberEvent?.invoke() } + } + return true + } + + fun bind(user: Member, leaderID: String?, userID: String?) { avatarView.setAvatar(user) + this.leaderID = leaderID + this.currentUserID = userID + + if (user.id == userID) { + moreButton.visibility = View.GONE + } else { + moreButton.visibility = View.VISIBLE + } user.stats?.let { healthBar.set(it.hp ?: 0.0, it.maxHealth?.toDouble() ?: 50.0) - healthTextView.text = "${it.hp?.toInt()} / ${it.maxHealth?.toInt()}" + healthTextView.text = "${it.hp?.toInt()} / ${it.maxHealth}" experienceBar.set(it.exp ?: 0.0, it.toNextLevel?.toDouble() ?: 0.0) - experienceTextView.text = "${it.exp?.toInt()} / ${it.toNextLevel?.toInt()}" + experienceTextView.text = "${it.exp?.toInt()} / ${it.toNextLevel}" manaBar.set(it.mp ?: 0.0, it.maxMP?.toDouble() ?: 0.0) - manaTextView.text = "${it.mp?.toInt()} / ${it.maxMP?.toInt()}" + manaTextView.text = "${it.mp?.toInt()} / ${it.maxMP}" } displayNameTextView.username = user.profile?.name displayNameTextView.tier = user.contributor?.level ?: 0