From ade03176be06491d056ee3ab7ff5b938af9f06ea Mon Sep 17 00:00:00 2001 From: shubhsherl Date: Thu, 3 Jan 2019 23:06:07 +0530 Subject: [PATCH 1/6] add bottomSheet for members --- .../presentation/ChatRoomPresenter.kt | 38 +++++- .../chatrooms/adapter/model/RoomUiModel.kt | 3 + .../createchannel/ui/CreateChannelFragment.kt | 8 +- .../android/members/adapter/MembersAdapter.kt | 49 ++++++-- .../android/members/adapter/ViewHolder.kt | 93 +++++++++++++++ .../members/presentation/MembersPresenter.kt | 111 +++++++++++++++++- .../members/ui/GroupMemberBottomSheet.kt | 76 ++++++++++++ .../android/members/ui/MembersFragment.kt | 13 +- .../android/members/uimodel/MemberUiModel.kt | 4 + app/src/main/res/layout/item_member.xml | 36 ++++++ .../main/res/menu/group_member_actions.xml | 32 +++++ app/src/main/res/values/styles.xml | 7 ++ 12 files changed, 438 insertions(+), 32 deletions(-) create mode 100644 app/src/main/java/chat/rocket/android/members/adapter/ViewHolder.kt create mode 100644 app/src/main/java/chat/rocket/android/members/ui/GroupMemberBottomSheet.kt create mode 100644 app/src/main/res/menu/group_member_actions.xml diff --git a/app/src/main/java/chat/rocket/android/chatroom/presentation/ChatRoomPresenter.kt b/app/src/main/java/chat/rocket/android/chatroom/presentation/ChatRoomPresenter.kt index 2b8633243f..a95ac3d3ec 100644 --- a/app/src/main/java/chat/rocket/android/chatroom/presentation/ChatRoomPresenter.kt +++ b/app/src/main/java/chat/rocket/android/chatroom/presentation/ChatRoomPresenter.kt @@ -2,6 +2,7 @@ package chat.rocket.android.chatroom.presentation import android.graphics.Bitmap import android.net.Uri +import android.util.Log import chat.rocket.android.R import chat.rocket.android.analytics.AnalyticsManager import chat.rocket.android.analytics.event.SubscriptionTypeEvent @@ -139,12 +140,16 @@ class ChatRoomPresenter @Inject constructor( } else { emptyList() } + Log.d("OPOPOP",chatRoles.toString()) } catch (ex: Exception) { Timber.e(ex) chatRoles = emptyList() } finally { // User has at least an 'owner' or 'moderator' role. - val canModerate = isOwnerOrMod() + val isOwner = isOwner() + val isMod= isModerator() + val isLeader = isLeader() +// val canModerate = isOwnerOrMod() // Can post anyway if has the 'post-readonly' permission on server. val room = dbManager.getRoom(roomId) room?.let { @@ -153,8 +158,11 @@ class ChatRoomPresenter @Inject constructor( launchUI(strategy) { view.onRoomUpdated(roomUiModel = roomUiModel.copy( broadcast = chatIsBroadcast, - canModerate = canModerate, - writable = roomUiModel.writable || canModerate + isOwner = isOwner, + isLeader = isLeader, + isMod = isMod, + canModerate = isOwner || isMod, + writable = roomUiModel.writable || isOwner || isLeader )) } } @@ -192,9 +200,27 @@ class ChatRoomPresenter @Inject constructor( chatRoomId?.let { manager.removeRoomChannel(it) } } - private fun isOwnerOrMod(): Boolean { - return chatRoles.firstOrNull { it.user.username == currentLoggedUsername }?.roles?.any { - it == "owner" || it == "moderator" +// private fun isOwnerOrMod(): Boolean { +// return chatRoles.firstOrNull { it.user.username == currentLoggedUsername }?.roles?.any { +// it == "owner" || it == "moderator" +// } ?: false +// } + + private fun isOwner(): Boolean { + return chatRoles.find { it.user.username == currentLoggedUsername }?.roles?.any { + it == "owner" + } ?: false + } + + private fun isModerator(): Boolean { + return chatRoles.find { it.user.username == currentLoggedUsername }?.roles?.any { + it == "moderator" + } ?: false + } + + private fun isLeader(): Boolean { + return chatRoles.find { it.user.username == currentLoggedUsername }?.roles?.any { + it == "leader" } ?: false } diff --git a/app/src/main/java/chat/rocket/android/chatrooms/adapter/model/RoomUiModel.kt b/app/src/main/java/chat/rocket/android/chatrooms/adapter/model/RoomUiModel.kt index 8531d41a70..adf219e78d 100644 --- a/app/src/main/java/chat/rocket/android/chatrooms/adapter/model/RoomUiModel.kt +++ b/app/src/main/java/chat/rocket/android/chatrooms/adapter/model/RoomUiModel.kt @@ -18,6 +18,9 @@ data class RoomUiModel( val status: UserStatus? = null, val username: String? = null, val broadcast: Boolean = false, + val isOwner: Boolean = false, + val isLeader: Boolean = false, + val isMod: Boolean = false, val canModerate: Boolean = false, val writable: Boolean = true, val muted: List = emptyList() diff --git a/app/src/main/java/chat/rocket/android/createchannel/ui/CreateChannelFragment.kt b/app/src/main/java/chat/rocket/android/createchannel/ui/CreateChannelFragment.kt index 57f60a6f18..f61df85db4 100644 --- a/app/src/main/java/chat/rocket/android/createchannel/ui/CreateChannelFragment.kt +++ b/app/src/main/java/chat/rocket/android/createchannel/ui/CreateChannelFragment.kt @@ -44,9 +44,11 @@ class CreateChannelFragment : Fragment(), CreateChannelView, ActionMode.Callback @Inject lateinit var analyticsManager: AnalyticsManager private var actionMode: ActionMode? = null - private val adapter: MembersAdapter = MembersAdapter { - it.username?.run { processSelectedMember(this) } - } + private val adapter: MembersAdapter = MembersAdapter ({ + if (it.username != null) { + processSelectedMember(it.username) + } + }, null) private val compositeDisposable = CompositeDisposable() private var channelType: String = RoomType.CHANNEL private var isChannelReadOnly: Boolean = false diff --git a/app/src/main/java/chat/rocket/android/members/adapter/MembersAdapter.kt b/app/src/main/java/chat/rocket/android/members/adapter/MembersAdapter.kt index a8dbdcc8cd..51795d6036 100644 --- a/app/src/main/java/chat/rocket/android/members/adapter/MembersAdapter.kt +++ b/app/src/main/java/chat/rocket/android/members/adapter/MembersAdapter.kt @@ -1,24 +1,24 @@ package chat.rocket.android.members.adapter -import android.view.View +import android.util.Log +import android.view.MenuItem import android.view.ViewGroup import androidx.recyclerview.widget.RecyclerView import chat.rocket.android.R +import chat.rocket.android.members.presentation.MembersPresenter import chat.rocket.android.members.uimodel.MemberUiModel -import chat.rocket.android.util.extensions.content import chat.rocket.android.util.extensions.inflate -import kotlinx.android.synthetic.main.avatar.view.* -import kotlinx.android.synthetic.main.item_member.view.* -class MembersAdapter( - private val listener: (MemberUiModel) -> Unit -) : RecyclerView.Adapter() { + +class MembersAdapter(private val listener: (MemberUiModel) -> Unit, presenter: MembersPresenter?) : + RecyclerView.Adapter() { private var dataSet: List = ArrayList() + private val enableActions: Boolean = true - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MembersAdapter.ViewHolder = - ViewHolder(parent.inflate(R.layout.item_member)) + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder = + ViewHolder(parent.inflate(R.layout.item_member), actionsListener) - override fun onBindViewHolder(holder: MembersAdapter.ViewHolder, position: Int) = + override fun onBindViewHolder(holder: ViewHolder, position: Int) = holder.bind(dataSet[position], listener) override fun getItemCount(): Int = dataSet.size @@ -39,6 +39,7 @@ class MembersAdapter( notifyItemRangeInserted(previousDataSetSize, dataSet.size) } +<<<<<<< HEAD class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { fun bind(memberUiModel: MemberUiModel, listener: (MemberUiModel) -> Unit) = with(itemView) { @@ -47,6 +48,34 @@ class MembersAdapter( text_member.setCompoundDrawablesRelativeWithIntrinsicBounds( DrawableHelper.getUserStatusDrawable(memberUiModel.status, context), null, null, null) setOnClickListener { listener(memberUiModel) } +======= + private val actionsListener = object : ViewHolder.ActionsListener { + override fun isActionsEnabled(): Boolean = enableActions + override fun onActionSelected(item: MenuItem, member: MemberUiModel) { + member.apply { + when (item.itemId) { + R.id.action_member_set_owner-> { + presenter?.toggleOwner(this.userId, this.roles?.contains("owner") == true ) + } + R.id.action_member_set_leader-> { + presenter?.toggleLeader(this.userId, this.roles?.contains("leader") == true) + } + R.id.action_member_set_moderator->{ + presenter?.toggleModerator(this.userId, this.roles?.contains("moderator") == true) + } + R.id.action_member_ignore->{ + presenter?.toggleIgnore(this.userId, true) + } + R.id.action_member_mute->{ + presenter?.toggleMute(this.username, true) + } + R.id.action_member_remove->{ + presenter?.removeUser(this.userId) + } + else -> TODO("Not implemented") + } + } +>>>>>>> e494c1b5... add bottomSheet for members } } } diff --git a/app/src/main/java/chat/rocket/android/members/adapter/ViewHolder.kt b/app/src/main/java/chat/rocket/android/members/adapter/ViewHolder.kt new file mode 100644 index 0000000000..2e90cd555e --- /dev/null +++ b/app/src/main/java/chat/rocket/android/members/adapter/ViewHolder.kt @@ -0,0 +1,93 @@ +package chat.rocket.android.members.adapter + +import android.view.ContextThemeWrapper +import android.view.MenuItem +import android.view.View +import androidx.appcompat.app.AppCompatActivity +import androidx.core.view.isVisible +import androidx.recyclerview.widget.RecyclerView +import chat.rocket.android.R +import chat.rocket.android.chatroom.uimodel.MessageUiModel +import chat.rocket.android.members.ui.GroupMemberBottomSheet +import chat.rocket.android.members.uimodel.MemberUiModel +import chat.rocket.android.util.extensions.content +import chat.rocket.android.util.extensions.inflate +import chat.rocket.android.util.extensions.toList +import kotlinx.android.synthetic.main.avatar.view.* +import kotlinx.android.synthetic.main.item_member.view.* + + +class ViewHolder( + itemView: View, + private val listener: ActionsListener +) : RecyclerView.ViewHolder(itemView), MenuItem.OnMenuItemClickListener { + var data: MemberUiModel? = null + + init { + setupActionMenu(itemView) + } + + fun bind(memberUiModel: MemberUiModel, listener: (MemberUiModel) -> Unit) = with(itemView) { + data = memberUiModel + image_avatar.setImageURI(memberUiModel.avatarUri) + text_member.content = memberUiModel.displayName + text_member.setCompoundDrawablesRelativeWithIntrinsicBounds(DrawableHelper.getUserStatusDrawable(memberUiModel.status, context), null, null, null) + text_member_owner.content = "Owner" + text_member_leader.content = "Leader" + text_member_moderator.content = "Mod" + text_member_owner.isVisible =memberUiModel.roles?.contains("owner") == true + text_member_leader.isVisible = memberUiModel.roles?.contains("leader") == true + text_member_moderator.isVisible = memberUiModel.roles?.contains("moderator") == true + setOnClickListener { listener(memberUiModel) } + } + + interface ActionsListener { + fun isActionsEnabled(): Boolean + fun onActionSelected(item: MenuItem, member: MemberUiModel) + } + + internal fun setupActionMenu(view: View) { + view.setOnLongClickListener{ + data?.let { + val menuItems = view.context.inflate(R.menu.group_member_actions).toList() + menuItems.find { it.itemId == R.id.action_member_set_owner }?.apply { + if (it.roles?.contains("owner") == true) title = "Remove as Owner" + } + menuItems.find { it.itemId == R.id.action_member_set_leader }?.apply { + if (it.roles?.contains("leader") == true) title = "Remove as Leader" + } + menuItems.find { it.itemId == R.id.action_member_set_moderator }?.apply { + if (it.roles?.contains("moderator") == true) title = "Remove as Moderator" + } + menuItems.find { it.itemId == R.id.action_member_mute }?.apply { +// if (it.roles.contains("owner")) title = "Remove Owner" + } + menuItems.find { it.itemId == R.id.action_member_ignore }?.apply { +// if (it.roles.contains("owner")) title = "Remove Owner" + } + menuItems.find { it.itemId == R.id.action_member_remove }?.apply { +// if (it.roles.contains("owner")) title = "Remove Owner" + } + view.context?.let { + if (it is ContextThemeWrapper && it is AppCompatActivity) { + with(it) { + val actionsBottomSheet = GroupMemberBottomSheet() + actionsBottomSheet.addItems(menuItems, this@ViewHolder) + actionsBottomSheet.show(supportFragmentManager, null) + } + } + } + } + true + } + } + + override fun onMenuItemClick(item: MenuItem): Boolean { + data?.let { + listener.onActionSelected(item, it) + } + return true + } +} + + diff --git a/app/src/main/java/chat/rocket/android/members/presentation/MembersPresenter.kt b/app/src/main/java/chat/rocket/android/members/presentation/MembersPresenter.kt index 882648bad2..aeb43a3dcc 100644 --- a/app/src/main/java/chat/rocket/android/members/presentation/MembersPresenter.kt +++ b/app/src/main/java/chat/rocket/android/members/presentation/MembersPresenter.kt @@ -8,11 +8,16 @@ import chat.rocket.android.members.uimodel.MemberUiModel import chat.rocket.android.members.uimodel.MemberUiModelMapper import chat.rocket.android.server.infraestructure.RocketChatClientFactory import chat.rocket.android.util.extension.launchUI +import chat.rocket.android.util.retryDB +import chat.rocket.android.util.retryIO import chat.rocket.common.RocketChatException +import chat.rocket.common.model.RoomType import chat.rocket.common.model.roomTypeOf import chat.rocket.common.util.ifNull import chat.rocket.core.RocketChatClient -import chat.rocket.core.internal.rest.getMembers +import chat.rocket.core.internal.rest.* +import chat.rocket.core.model.ChatRoomRole +import chat.rocket.core.model.Command import timber.log.Timber import javax.inject.Inject import javax.inject.Named @@ -29,6 +34,8 @@ class MembersPresenter @Inject constructor( ) { private val client: RocketChatClient = factory.create(currentServer) private var offset: Long = 0 + private lateinit var roomType: RoomType + private lateinit var roomId: String /** * Loads all the chat room members for the given room id. @@ -40,9 +47,19 @@ class MembersPresenter @Inject constructor( try { view.showLoading() dbManager.getRoom(roomId)?.let { - val members = - client.getMembers(roomId, roomTypeOf(it.chatRoom.type), offset, 60) - val memberUiModels = mapper.mapToUiModelList(members.result) + val members = client.getMembers(roomId, roomTypeOf(it.chatRoom.type), offset, 60) + this@MembersPresenter.roomId = it.chatRoom.id + this@MembersPresenter.roomType = roomTypeOf(it.chatRoom.type) + val chatRoomRole = if (roomType !is RoomType.DirectMessage) { + client.chatRoomRoles(roomType = roomType, roomName = it.chatRoom.name) + } else { + emptyList() + } + var memberUiModels = mapper.mapToUiModelList(members.result) + memberUiModels.forEach { + val userId = it.userId + it.roles = chatRoomRole.find { it.user.id == userId}?.roles + } view.showMembers(memberUiModels, members.total) offset += 1 * 60L }.ifNull { @@ -67,4 +84,90 @@ class MembersPresenter @Inject constructor( } } } + + fun toggleOwner(userId: String, isOwner: Boolean = false) { + launchUI(strategy) { + try { + if (isOwner) + retryIO(description = "removeOwner($roomId, $roomType, $userId)") { client.removeOwner(roomId, roomType, userId) } + else + retryIO(description = "addOwner($roomId, $roomType, $userId)") { client.addOwner(roomId, roomType, userId) } + this@MembersPresenter.loadChatRoomsMembers(roomId) + } catch (ex: RocketChatException) { + view.showMessage(ex.message!!) // TODO Remove. + Timber.e(ex) // FIXME: Right now we are only catching the exception with Timber. + } + } + } + + fun toggleLeader(userId: String, isLeader: Boolean = false) { + launchUI(strategy) { + try { + if (isLeader) + retryIO(description = "removeLeader($roomId, $roomType, $userId)") { client.removeLeader(roomId, roomType, userId) } + else + retryIO(description = "addLeader($roomId, $roomType, $userId)") { client.addLeader(roomId, roomType, userId) } + this@MembersPresenter.loadChatRoomsMembers(roomId) + } catch (ex: RocketChatException) { + view.showMessage(ex.message!!) // TODO Remove. + Timber.e(ex) // FIXME: Right now we are only catching the exception with Timber. + } + } + } + + fun toggleModerator(userId: String, isModerator: Boolean = false) { + launchUI(strategy) { + try { + if (isModerator) + retryIO(description = "removeModerator($roomId, $roomType, $userId)") { client.removeModerator(roomId, roomType, userId) } + else + retryIO(description = "addModerator($roomId, $roomType, $userId)") { client.addModerator(roomId, roomType, userId) } + this@MembersPresenter.loadChatRoomsMembers(roomId) + } catch (ex: RocketChatException) { + view.showMessage(ex.message!!) // TODO Remove. + Timber.e(ex) // FIXME: Right now we are only catching the exception with Timber. + } + } + } + + fun toggleIgnore(userId: String, isIgnored: Boolean = false) { + launchUI(strategy) { + try { + retryIO(description = "ignoreUser($roomId, $userId, ${!isIgnored})") { client.ignoreUser(roomId, userId, !isIgnored) } + } catch (ex: RocketChatException) { + view.showMessage(ex.message!!) // TODO Remove. + Timber.e(ex) // FIXME: Right now we are only catching the exception with Timber. + } + } + } + + fun toggleMute(username: String?, isMuted: Boolean = false) { + launchUI(strategy) { + try { + if (isMuted) + retryIO("runCommand(unmute, $username, $roomId)") { + client.runCommand(Command("unmute", username), roomId) + } + else + retryIO("runCommand(mute, $username, $roomId)") { + client.runCommand(Command("mute", username), roomId) + } + } catch (ex: RocketChatException) { + view.showMessage(ex.message!!) // TODO Remove. + Timber.e(ex) // FIXME: Right now we are only catching the exception with Timber. + } + } + } + + fun removeUser(userId: String) { + launchUI(strategy) { + try { + retryIO(description = "removeUser($roomId, $roomType, $userId)") { client.removeOwner(roomId, roomType, userId) } + } catch (ex: RocketChatException) { + view.showMessage(ex.message!!) // TODO Remove. + Timber.e(ex) // FIXME: Right now we are only catching the exception with Timber. + } + } + } + } diff --git a/app/src/main/java/chat/rocket/android/members/ui/GroupMemberBottomSheet.kt b/app/src/main/java/chat/rocket/android/members/ui/GroupMemberBottomSheet.kt new file mode 100644 index 0000000000..ae275788ee --- /dev/null +++ b/app/src/main/java/chat/rocket/android/members/ui/GroupMemberBottomSheet.kt @@ -0,0 +1,76 @@ +package chat.rocket.android.members.ui + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.MenuItem +import android.view.View +import android.view.ViewGroup +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import chat.rocket.android.R +import com.google.android.material.bottomsheet.BottomSheetDialogFragment +import kotlinx.android.synthetic.main.message_action_item.view.* +import kotlinx.android.synthetic.main.message_bottomsheet.* + +class GroupMemberBottomSheet: BottomSheetDialogFragment() { + + private val adapter = GroupMemberActionAdapter() + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { + return inflater.inflate(R.layout.message_bottomsheet, container, false) + } + + fun addItems(items: List, itemClickListener: MenuItem.OnMenuItemClickListener) { + adapter.addItems(items, ActionItemClickListener(dismissAction = { dismiss() }, + itemClickListener = itemClickListener)) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + bottomsheet_recycler_view.layoutManager = LinearLayoutManager(context) + bottomsheet_recycler_view.adapter = adapter + } + + private class ActionItemClickListener( + val dismissAction: () -> Unit, + val itemClickListener: MenuItem.OnMenuItemClickListener + ) + + private class GroupMemberActionAdapter : RecyclerView.Adapter() { + + private lateinit var itemClickListener: ActionItemClickListener + private val menuItems = mutableListOf() + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): GroupMemberActionViewHolder { + return GroupMemberActionViewHolder( + LayoutInflater.from(parent.context).inflate(R.layout.message_action_item, parent, false) + ) + } + + override fun getItemCount() = menuItems.size + + override fun onBindViewHolder(holder: GroupMemberActionViewHolder, position: Int) { + holder.bind(menuItems[position], itemClickListener) + } + + fun addItems(items: List, itemClickListener: ActionItemClickListener) { + this.itemClickListener = itemClickListener + menuItems.clear() + menuItems.addAll(items) + notifyDataSetChanged() + } + } + + private class GroupMemberActionViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { + + fun bind(item: MenuItem, itemClickListener: ActionItemClickListener) { + with(itemView) { + message_action_title.text = item.title + message_action_icon.setImageDrawable(item.icon) + setOnClickListener { + itemClickListener.itemClickListener.onMenuItemClick(item) + itemClickListener.dismissAction.invoke() + } + } + } + } +} diff --git a/app/src/main/java/chat/rocket/android/members/ui/MembersFragment.kt b/app/src/main/java/chat/rocket/android/members/ui/MembersFragment.kt index 261f6eab7d..124f14847a 100644 --- a/app/src/main/java/chat/rocket/android/members/ui/MembersFragment.kt +++ b/app/src/main/java/chat/rocket/android/members/ui/MembersFragment.kt @@ -41,8 +41,7 @@ class MembersFragment : Fragment(), MembersView { lateinit var presenter: MembersPresenter @Inject lateinit var analyticsManager: AnalyticsManager - private val adapter: MembersAdapter = - MembersAdapter { memberUiModel -> presenter.toMemberDetails(memberUiModel) } + private lateinit var adapter: MembersAdapter private val linearLayoutManager = LinearLayoutManager(context) private lateinit var chatRoomId: String @@ -117,13 +116,9 @@ class MembersFragment : Fragment(), MembersView { private fun setupRecyclerView() { ui { - recycler_view.layoutManager = LinearLayoutManager(context) - recycler_view.addItemDecoration( - DividerItemDecoration( - it, - DividerItemDecoration.HORIZONTAL - ) - ) + adapter = MembersAdapter ({ memberUiModel -> presenter.toMemberDetails(memberUiModel) }, presenter) + recycler_view.layoutManager = linearLayoutManager + recycler_view.addItemDecoration(DividerItemDecoration(it, DividerItemDecoration.HORIZONTAL)) recycler_view.adapter = adapter } } diff --git a/app/src/main/java/chat/rocket/android/members/uimodel/MemberUiModel.kt b/app/src/main/java/chat/rocket/android/members/uimodel/MemberUiModel.kt index 1487578230..0674f6b944 100644 --- a/app/src/main/java/chat/rocket/android/members/uimodel/MemberUiModel.kt +++ b/app/src/main/java/chat/rocket/android/members/uimodel/MemberUiModel.kt @@ -17,6 +17,7 @@ class MemberUiModel( val realName: String? val username: String? val email: String? + var roles: List? val utcOffset: Float? val status: UserStatus? @@ -27,6 +28,7 @@ class MemberUiModel( username = getUserUsername() email = getUserEmail() utcOffset = getUserUtcOffset() + roles = getUserRoles() status = getUserStatus() } @@ -52,5 +54,7 @@ class MemberUiModel( private fun getUserUtcOffset(): Float? = member.utcOffset + private fun getUserRoles(): List? = member.roles + private fun getUserStatus(): UserStatus? = member.status } diff --git a/app/src/main/res/layout/item_member.xml b/app/src/main/res/layout/item_member.xml index fbe241c4d3..eecd87c8a2 100644 --- a/app/src/main/res/layout/item_member.xml +++ b/app/src/main/res/layout/item_member.xml @@ -31,4 +31,40 @@ tools:drawableStart="@drawable/ic_status_invisible_12dp" tools:text="Ronald Perkins" /> + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/group_member_actions.xml b/app/src/main/res/menu/group_member_actions.xml new file mode 100644 index 0000000000..820834f944 --- /dev/null +++ b/app/src/main/res/menu/group_member_actions.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 18a0298ab1..42b256ff33 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -244,6 +244,13 @@ 16sp + +