diff --git a/app/src/main/java/chat/rocket/android/chatdetails/presentation/ChatDetailsPresenter.kt b/app/src/main/java/chat/rocket/android/chatdetails/presentation/ChatDetailsPresenter.kt index ae8c3b8ea5..64350bcc0e 100644 --- a/app/src/main/java/chat/rocket/android/chatdetails/presentation/ChatDetailsPresenter.kt +++ b/app/src/main/java/chat/rocket/android/chatdetails/presentation/ChatDetailsPresenter.kt @@ -74,8 +74,8 @@ class ChatDetailsPresenter @Inject constructor( navigator.toFileList(chatRoomId) } - fun toMembers(chatRoomId: String) { - navigator.toMembersList(chatRoomId) + fun toMembers(chatRoomId: String, isOwner: Boolean, isMod: Boolean) { + navigator.toMembersList(chatRoomId, isOwner, isMod) } fun toMentions(chatRoomId: String) { diff --git a/app/src/main/java/chat/rocket/android/chatdetails/ui/ChatDetailsFragment.kt b/app/src/main/java/chat/rocket/android/chatdetails/ui/ChatDetailsFragment.kt index 09ac8cac2e..70db7a7dc1 100644 --- a/app/src/main/java/chat/rocket/android/chatdetails/ui/ChatDetailsFragment.kt +++ b/app/src/main/java/chat/rocket/android/chatdetails/ui/ChatDetailsFragment.kt @@ -35,8 +35,9 @@ fun newInstance( chatRoomId: String, chatRoomType: String, isSubscribed: Boolean, - isFavorite: Boolean, - disableMenu: Boolean + disableMenu: Boolean, + isOwner: Boolean, + isMod: Boolean ): ChatDetailsFragment { return ChatDetailsFragment().apply { arguments = Bundle(5).apply { @@ -45,6 +46,8 @@ fun newInstance( putBoolean(BUNDLE_IS_SUBSCRIBED, isSubscribed) putBoolean(BUNDLE_IS_FAVORITE, isFavorite) putBoolean(BUNDLE_DISABLE_MENU, disableMenu) + putBoolean(BUNDLE_CHAT_ROOM_OWNER, isOwner) + putBoolean(BUNDLE_CHAT_ROOM_MOD, isMod) } } } @@ -54,6 +57,8 @@ internal const val MENU_ACTION_FAVORITE_REMOVE_FAVORITE = 1 internal const val MENU_ACTION_VIDEO_CALL = 2 private const val BUNDLE_CHAT_ROOM_ID = "BUNDLE_CHAT_ROOM_ID" +private const val BUNDLE_CHAT_ROOM_MOD = "BUNDLE_CHAT_ROOM_MOD" +private const val BUNDLE_CHAT_ROOM_OWNER = "BUNDLE_CHAT_ROOM_OWNER" private const val BUNDLE_CHAT_ROOM_TYPE = "BUNDLE_CHAT_ROOM_TYPE" private const val BUNDLE_IS_SUBSCRIBED = "BUNDLE_IS_SUBSCRIBED" private const val BUNDLE_IS_FAVORITE = "BUNDLE_IS_FAVORITE" @@ -74,22 +79,24 @@ class ChatDetailsFragment : Fragment(), ChatDetailsView { internal lateinit var chatRoomId: String internal lateinit var chatRoomType: String private var isSubscribed: Boolean = true - internal var isFavorite: Boolean = false + private var isOwner: Boolean = false + private var isMod: Boolean = false private var disableMenu: Boolean = false override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) AndroidSupportInjection.inject(this) - - arguments?.run { - chatRoomId = getString(BUNDLE_CHAT_ROOM_ID) - chatRoomType = getString(BUNDLE_CHAT_ROOM_TYPE) - isSubscribed = getBoolean(BUNDLE_IS_SUBSCRIBED) - isFavorite = getBoolean(BUNDLE_IS_FAVORITE) - disableMenu = getBoolean(BUNDLE_DISABLE_MENU) - } ?: requireNotNull(arguments) { "no arguments supplied when the fragment was instantiated" } - - setHasOptionsMenu(true) + val bundle = arguments + if (bundle != null) { + chatRoomId = bundle.getString(BUNDLE_CHAT_ROOM_ID) + chatRoomType = bundle.getString(BUNDLE_CHAT_ROOM_TYPE) + isSubscribed = bundle.getBoolean(BUNDLE_IS_SUBSCRIBED) + disableMenu = bundle.getBoolean(BUNDLE_DISABLE_MENU) + isOwner = bundle.getBoolean(BUNDLE_CHAT_ROOM_OWNER) + isMod = bundle.getBoolean(BUNDLE_CHAT_ROOM_MOD) + } else { + requireNotNull(bundle) { "no arguments supplied when the fragment was instantiated" } + } } override fun onCreateView( @@ -166,7 +173,7 @@ class ChatDetailsFragment : Fragment(), ChatDetailsView { getString(R.string.title_members), R.drawable.ic_people_outline_black_24dp ) { - presenter.toMembers(chatRoomId!!) + presenter.toMembers(chatRoomId!!, isOwner, isMod) } } diff --git a/app/src/main/java/chat/rocket/android/chatroom/presentation/ChatRoomNavigator.kt b/app/src/main/java/chat/rocket/android/chatroom/presentation/ChatRoomNavigator.kt index be76240db6..45bc822803 100644 --- a/app/src/main/java/chat/rocket/android/chatroom/presentation/ChatRoomNavigator.kt +++ b/app/src/main/java/chat/rocket/android/chatroom/presentation/ChatRoomNavigator.kt @@ -67,23 +67,25 @@ class ChatRoomNavigator(internal val activity: ChatRoomActivity) { chatRoomId: String, chatRoomType: String, isChatRoomSubscribed: Boolean, - isChatRoomFavorite: Boolean, - isMenuDisabled: Boolean + isMenuDisabled: Boolean, + isOwner: Boolean, + isModerator: Boolean ) { activity.addFragmentBackStack(TAG_CHAT_DETAILS_FRAGMENT, R.id.fragment_container) { chat.rocket.android.chatdetails.ui.newInstance( chatRoomId, chatRoomType, isChatRoomSubscribed, - isChatRoomFavorite, - isMenuDisabled + isMenuDisabled, + isOwner, + isModerator ) } } - fun toMembersList(chatRoomId: String) { + fun toMembersList(chatRoomId: String, isOwner: Boolean, isMod: Boolean) { activity.addFragmentBackStack(TAG_MEMBERS_FRAGMENT, R.id.fragment_container) { - chat.rocket.android.members.ui.newInstance(chatRoomId) + chat.rocket.android.members.ui.newInstance(chatRoomId, isOwner, isMod) } } 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..5515fb04a9 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 @@ -144,7 +144,7 @@ class ChatRoomPresenter @Inject constructor( chatRoles = emptyList() } finally { // User has at least an 'owner' or 'moderator' role. - val canModerate = isOwnerOrMod() + val canModerate = isOwner() || isModerator() // Can post anyway if has the 'post-readonly' permission on server. val room = dbManager.getRoom(roomId) room?.let { @@ -192,9 +192,15 @@ 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 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 } @@ -896,7 +902,7 @@ class ChatRoomPresenter @Inject constructor( isFavorite: Boolean, isMenuDisabled: Boolean ) { - navigator.toChatDetails(chatRoomId, chatRoomType, isSubscribed, isFavorite, isMenuDisabled) + navigator.toChatDetails(chatRoomId, chatRoomType, isSubscribed, isMenuDisabled, isOwner(), isModerator()) } fun loadChatRoomsSuggestions() { 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..92caff92cb 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, true, false) 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..779c6d71d5 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,25 +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?, private val isOwner: Boolean, private val isMod: Boolean) : + 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, isOwner, isMod) - override fun onBindViewHolder(holder: MembersAdapter.ViewHolder, position: Int) = - holder.bind(dataSet[position], listener) + override fun onBindViewHolder(holder: ViewHolder, position: Int) = + holder.bind(dataSet[position], position, listener) override fun getItemCount(): Int = dataSet.size @@ -39,14 +38,57 @@ class MembersAdapter( notifyItemRangeInserted(previousDataSetSize, dataSet.size) } - class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { - - fun bind(memberUiModel: MemberUiModel, listener: (MemberUiModel) -> Unit) = with(itemView) { - image_avatar.setImageURI(memberUiModel.avatarUri) - text_member.content = memberUiModel.displayName - 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, index: Int) { + member.apply { + when (item.itemId) { + R.id.action_member_set_owner-> { + val isOwner = this.roles?.contains("owner") == true + presenter?.toggleOwner(this.userId, isOwner) { + if (isOwner) + dataSet[index].roles = dataSet[index].roles?.filterNot { it == "owner"} + else + dataSet[index].roles = dataSet[index].roles?.plus("owner") + notifyItemChanged(index) + } + } + R.id.action_member_set_leader-> { + val isLeader = this.roles?.contains("leader") == true + presenter?.toggleLeader(this.userId, isLeader) { + if (isLeader) + dataSet[index].roles = dataSet[index].roles?.filterNot { it == "leader"} + else + dataSet[index].roles = dataSet[index].roles?.plus("leader") + notifyItemChanged(index) + } + } + R.id.action_member_set_moderator-> { + val isMod = this.roles?.contains("moderator") == true + presenter?.toggleModerator(this.userId, isMod) { + if (isMod) + dataSet[index].roles = dataSet[index].roles?.filterNot { it == "moderator" } + else + dataSet[index].roles = dataSet[index].roles?.plus("moderator") + notifyItemChanged(index) + } + } + R.id.action_member_mute-> { + presenter?.toggleMute(this.username, this.muted) { + dataSet[index].muted = !this.muted + notifyItemChanged(index) + } + } + R.id.action_member_remove-> { + presenter?.removeUser(this.userId) { + dataSet = dataSet.filterIndexed{ position, _-> position != index } + notifyItemRemoved(index) + notifyItemRangeChanged(index, dataSet.size) + } + } + else -> TODO("Not implemented") + } + } } } } 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..785f8c740a --- /dev/null +++ b/app/src/main/java/chat/rocket/android/members/adapter/ViewHolder.kt @@ -0,0 +1,101 @@ +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.lifecycle.Lifecycle +import androidx.recyclerview.widget.RecyclerView +import chat.rocket.android.R +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, + private val isOwner: Boolean, + private val isMod: Boolean +) : RecyclerView.ViewHolder(itemView), MenuItem.OnMenuItemClickListener { + var data: MemberUiModel? = null + var index: Int = 0 + + fun bind(memberUiModel: MemberUiModel, position: Int, listener: (MemberUiModel) -> Unit) = with(itemView) { + data = memberUiModel + index = position + image_avatar.setImageURI(memberUiModel.avatarUri) + text_member.content = memberUiModel.displayName + text_member.setCompoundDrawablesRelativeWithIntrinsicBounds(DrawableHelper.getUserStatusDrawable(memberUiModel.status, context), null, null, null) + text_member_owner.setText(R.string.owner) + text_member_leader.setText(R.string.leader) + text_member_moderator.setText(R.string.moderator) + 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) } + setupActionMenu(itemView) + } + + interface ActionsListener { + fun isActionsEnabled(): Boolean + fun onActionSelected(item: MenuItem, member: MemberUiModel, index: Int) + } + + internal fun setupActionMenu(view: View) { + view.setOnLongClickListener{ + data?.let { + var menuItems = view.context.inflate(R.menu.group_member_actions).toList() + if (!isOwner && !isMod) + menuItems = menuItems.filter { it.itemId == R.id.action_member_mute } + else if (!isOwner && isMod) + menuItems = menuItems.filter { it.itemId == R.id.action_member_mute || it.itemId == R.id.action_member_remove} + menuItems.find { it.itemId == R.id.action_member_set_owner }?.apply { + if (it.roles?.contains("owner") == true) setTitle(R.string.action_remove_owner) + } + menuItems.find { it.itemId == R.id.action_member_set_leader }?.apply { + if (it.roles?.contains("leader") == true) setTitle(R.string.action_remove_leader) + } + menuItems.find { it.itemId == R.id.action_member_set_moderator }?.apply { + if (it.roles?.contains("moderator") == true) setTitle(R.string.action_remove_moderator) + } + menuItems.find { it.itemId == R.id.action_member_mute }?.apply { + if (it.muted) { + setTitle(R.string.action_unmute_user) + setIcon(R.drawable.ic_mic_on_24dp) + } + } +// TODO: Check why ignore is not working +// menuItems.find { it.itemId == R.id.action_member_ignore }?.apply { +// if (it.roles.contains("owner")) title = "Remove Owner" +// } + view.context?.let { + if (it is ContextThemeWrapper && it.baseContext is AppCompatActivity) { + with(it.baseContext as AppCompatActivity) { + if (this.lifecycle.currentState.isAtLeast(Lifecycle.State.RESUMED)) { + 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, index) + } + 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..75c38023e0 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,15 @@ 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.extensions.isNotNullNorEmpty +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.Command import timber.log.Timber import javax.inject.Inject import javax.inject.Named @@ -29,6 +33,10 @@ 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 + private var totalMembers: Long = 0 + var isRoomOwner: Boolean = false /** * Loads all the chat room members for the given room id. @@ -40,10 +48,27 @@ class MembersPresenter @Inject constructor( try { view.showLoading() dbManager.getRoom(roomId)?.let { - val members = - client.getMembers(roomId, roomTypeOf(it.chatRoom.type), offset, 60) + 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() + } + val muted = it.chatRoom.muted + val currentUserId = client.me().id + val memberUiModels = mapper.mapToUiModelList(members.result) + memberUiModels.forEach { + val userId = it.userId + val username = it.username + it.roles = chatRoomRole.find { it.user.id == userId}?.roles + if (it.userId == currentUserId) this@MembersPresenter.isRoomOwner = it.roles?.contains("owner") == true + it.muted = muted?.find { it == username }.isNotNullNorEmpty() + } view.showMembers(memberUiModels, members.total) + totalMembers = members.total offset += 1 * 60L }.ifNull { Timber.e("Couldn't find a room with id: $roomId at current server.") @@ -67,4 +92,82 @@ class MembersPresenter @Inject constructor( } } } + + fun toggleOwner(userId: String, isOwner: Boolean = false, notifier: () ->Unit) { + 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) } + notifier() + } 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, notifier: () ->Unit) { + 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) } + notifier() + } 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, notifier: () ->Unit) { + 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) } + notifier() + } 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, notifier: () ->Unit) { + 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) + } + notifier() + } 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, notifier: () ->Unit) { + launchUI(strategy) { + try { + retryIO(description = "removeUser($roomId, $roomType, $userId)") { client.removeUser(roomId, roomType, userId) } + notifier() + view.setMemberCount(--totalMembers) + } 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/presentation/MembersView.kt b/app/src/main/java/chat/rocket/android/members/presentation/MembersView.kt index baf6b56f7e..0992922dcd 100644 --- a/app/src/main/java/chat/rocket/android/members/presentation/MembersView.kt +++ b/app/src/main/java/chat/rocket/android/members/presentation/MembersView.kt @@ -13,4 +13,11 @@ interface MembersView: LoadingView, MessageView { * @param total The total number of members. */ fun showMembers(dataSet: List, total: Long) + + /** + * Set count of members of a room. + * + * @param totalMembers The total number of members. + */ + fun setMemberCount(totalMembers: Long?) } \ No newline at end of file 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..a617d22892 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 @@ -27,32 +27,46 @@ import kotlinx.android.synthetic.main.app_bar_chat_room.* import kotlinx.android.synthetic.main.fragment_members.* import javax.inject.Inject -fun newInstance(chatRoomId: String): Fragment = MembersFragment().apply { - arguments = Bundle(1).apply { - putString(BUNDLE_CHAT_ROOM_ID, chatRoomId) + +fun newInstance(chatRoomId: String, isOwner: Boolean, isMod: Boolean): Fragment { + return MembersFragment().apply { + arguments = Bundle(1).apply { + putString(BUNDLE_CHAT_ROOM_ID, chatRoomId) + putBoolean(BUNDLE_CHAT_ROOM_OWNER, isOwner) + putBoolean(BUNDLE_CHAT_ROOM_MOD, isMod) + } } } internal const val TAG_MEMBERS_FRAGMENT = "MembersFragment" private const val BUNDLE_CHAT_ROOM_ID = "chat_room_id" +private const val BUNDLE_CHAT_ROOM_MOD = "chat_room_mod" +private const val BUNDLE_CHAT_ROOM_OWNER = "chat_room_owner" class MembersFragment : Fragment(), MembersView { @Inject 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 + private var isOwner: Boolean = false + private var isMod: Boolean = false override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) AndroidSupportInjection.inject(this) - arguments?.run { - chatRoomId = getString(BUNDLE_CHAT_ROOM_ID, "") - } ?: requireNotNull(arguments) { "no arguments supplied when the fragment was instantiated" } + val bundle = arguments + if (bundle != null) { + chatRoomId = bundle.getString(BUNDLE_CHAT_ROOM_ID) + isOwner = bundle.getBoolean(BUNDLE_CHAT_ROOM_OWNER) + isMod = bundle.getBoolean(BUNDLE_CHAT_ROOM_MOD) + + } else { + requireNotNull(bundle) { "no arguments supplied when the fragment was instantiated" } + } } override fun onCreateView( @@ -97,6 +111,10 @@ class MembersFragment : Fragment(), MembersView { ui { view_loading.isVisible = true } } + override fun setMemberCount(totalMembers: Long?){ + setupToolbar(totalMembers) + } + override fun hideLoading() { ui { view_loading.isVisible = false } } @@ -117,12 +135,13 @@ class MembersFragment : Fragment(), MembersView { private fun setupRecyclerView() { ui { + adapter = MembersAdapter ({ memberUiModel -> presenter.toMemberDetails(memberUiModel) }, presenter, isOwner, isMod) recycler_view.layoutManager = LinearLayoutManager(context) recycler_view.addItemDecoration( - DividerItemDecoration( - it, - DividerItemDecoration.HORIZONTAL - ) + 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..67bdc2046c 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,8 @@ class MemberUiModel( val realName: String? val username: String? val email: String? + var roles: List? + var muted: Boolean val utcOffset: Float? val status: UserStatus? @@ -27,6 +29,8 @@ class MemberUiModel( username = getUserUsername() email = getUserEmail() utcOffset = getUserUtcOffset() + roles = getUserRoles() + muted = false status = getUserStatus() } @@ -52,5 +56,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/drawable/ic_ignore_24dp.xml b/app/src/main/res/drawable/ic_ignore_24dp.xml new file mode 100644 index 0000000000..40474cc5c3 --- /dev/null +++ b/app/src/main/res/drawable/ic_ignore_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_leader.xml b/app/src/main/res/drawable/ic_leader.xml new file mode 100644 index 0000000000..fbdfeee4bc --- /dev/null +++ b/app/src/main/res/drawable/ic_leader.xml @@ -0,0 +1,4 @@ + + + diff --git a/app/src/main/res/drawable/ic_mic_off_24dp.xml b/app/src/main/res/drawable/ic_mic_off_24dp.xml new file mode 100644 index 0000000000..dc2873bcee --- /dev/null +++ b/app/src/main/res/drawable/ic_mic_off_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_mic_on_24dp.xml b/app/src/main/res/drawable/ic_mic_on_24dp.xml new file mode 100644 index 0000000000..4d47f4a042 --- /dev/null +++ b/app/src/main/res/drawable/ic_mic_on_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_moderator.xml b/app/src/main/res/drawable/ic_moderator.xml new file mode 100644 index 0000000000..5768d75740 --- /dev/null +++ b/app/src/main/res/drawable/ic_moderator.xml @@ -0,0 +1,4 @@ + + + diff --git a/app/src/main/res/drawable/ic_owner.xml b/app/src/main/res/drawable/ic_owner.xml new file mode 100644 index 0000000000..a1b30fe7a9 --- /dev/null +++ b/app/src/main/res/drawable/ic_owner.xml @@ -0,0 +1,5 @@ + + + + diff --git a/app/src/main/res/drawable/ic_remove_red_24dp.xml b/app/src/main/res/drawable/ic_remove_red_24dp.xml new file mode 100644 index 0000000000..14d28d7ba1 --- /dev/null +++ b/app/src/main/res/drawable/ic_remove_red_24dp.xml @@ -0,0 +1,11 @@ + + + + + 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..8b1f3a999f --- /dev/null +++ b/app/src/main/res/menu/group_member_actions.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 3dfbad4ae3..43250de3ec 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -349,4 +349,19 @@ *erforderlich Ihr Bericht wurde gesendet! + + Set as Owner + Set as Moderator + Remove as Owner + Remove as Leader + Remove as Moderator + Mute User + Unmute User + Remove from Room + Set as Leader + + Owner + Leader + Mod + diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 54af22f1e8..759a95e34c 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -343,5 +343,20 @@ Submit *required - Your report has been sent! + Your report has been sent! + + + Set as Owner + Set as Moderator + Remove as Owner + Remove as Leader + Remove as Moderator + Mute User + Unmute User + Remove from Room + Set as Leader + + Owner + Leader + Mod diff --git a/app/src/main/res/values-fa/strings.xml b/app/src/main/res/values-fa/strings.xml index 29026d081b..0a91d3e9a5 100644 --- a/app/src/main/res/values-fa/strings.xml +++ b/app/src/main/res/values-fa/strings.xml @@ -342,4 +342,19 @@ واگذاری *required گزارش شما فرستاده شد + + + Set as Owner + Set as Moderator + Remove as Owner + Remove as Leader + Remove as Moderator + Mute User + Unmute User + Remove from Room + Set as Leader + + Owner + Leader + Mod diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 4d69d7b63f..154267743b 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -340,7 +340,22 @@ Fuseau horaire - Soumettre - *Obligatoire - Votre rapport a été envoyé! + Submit + *required + Your report has been sent! + + + Set as Owner + Set as Moderator + Remove as Owner + Remove as Leader + Remove as Moderator + Mute User + Unmute User + Remove from Room + Set as Leader + + Owner + Leader + Mod diff --git a/app/src/main/res/values-hi-rIN/strings.xml b/app/src/main/res/values-hi-rIN/strings.xml index 00e260222e..36f68c56ef 100644 --- a/app/src/main/res/values-hi-rIN/strings.xml +++ b/app/src/main/res/values-hi-rIN/strings.xml @@ -344,4 +344,19 @@ जमा करें * आवश्यक आपकी रिपोर्ट भेज दी गई है! + + + Set as Owner + Set as Moderator + Remove as Owner + Remove as Leader + Remove as Moderator + Mute User + Unmute User + Remove from Room + Set as Leader + + Owner + Leader + Mod diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index d0c7219017..3384e4f6c5 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -339,4 +339,19 @@ Invia *necessario Il tuo resoconto è stato inviato! + + + Set as Owner + Set as Moderator + Remove as Owner + Remove as Leader + Remove as Moderator + Mute User + Unmute User + Remove from Room + Set as Leader + + Owner + Leader + Mod diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index 97b67eb741..48ed7c089d 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -343,5 +343,20 @@ Submit *required - Your report has been sent! + Your report has been sent! + + + Set as Owner + Set as Moderator + Remove as Owner + Remove as Leader + Remove as Moderator + Mute User + Unmute User + Remove from Room + Set as Leader + + Owner + Leader + Mod \ No newline at end of file diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index aab1531b47..198d77cb16 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -343,4 +343,19 @@ Enviar *obrigatório A mensagem foi reportada! + + + Set as Owner + Set as Moderator + Remove as Owner + Remove as Leader + Remove as Moderator + Mute User + Unmute User + Remove from Room + Set as Leader + + Owner + Leader + Mod diff --git a/app/src/main/res/values-ru-rRU/strings.xml b/app/src/main/res/values-ru-rRU/strings.xml index ba90c51586..c6da4e6c1c 100644 --- a/app/src/main/res/values-ru-rRU/strings.xml +++ b/app/src/main/res/values-ru-rRU/strings.xml @@ -340,4 +340,19 @@ Отправить *требуется Ваша жалоба была отправлена! + + + Set as Owner + Set as Moderator + Remove as Owner + Remove as Leader + Remove as Moderator + Mute User + Unmute User + Remove from Room + Set as Leader + + Owner + Leader + Mod diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index 69719c7923..6dc8971c5f 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -343,5 +343,20 @@ Submit *required - Your report has been sent! + Your report has been sent! + + + Set as Owner + Set as Moderator + Remove as Owner + Remove as Leader + Remove as Moderator + Mute User + Unmute User + Remove from Room + Set as Leader + + Owner + Leader + Mod diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index b63e0f60f3..6f65dee8ff 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -341,5 +341,20 @@ Submit *required - Your report has been sent! + Your report has been sent! + + + Set as Owner + Set as Moderator + Remove as Owner + Remove as Leader + Remove as Moderator + Mute User + Unmute User + Remove from Room + Set as Leader + + Owner + Leader + Mod diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 4bacccc750..83a74ac721 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -342,4 +342,19 @@ 提交 *必须 您的报告已提交! + + + Set as Owner + Set as Moderator + Remove as Owner + Remove as Leader + Remove as Moderator + Mute User + Unmute User + Remove from Room + Set as Leader + + Owner + Leader + Mod diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index a2d2384b68..681cfff86d 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -354,6 +354,22 @@ https://github.com/RocketChat/java-code-styles/blob/master/CODING_STYLE.md#strin Timezone Status + + Set as Owner + Set as Leader + Set as Moderator + Remove as Owner + Remove as Leader + Remove as Moderator + Mute User + Unmute User + Remove from Room + + Owner + Leader + Mod + + Submit *required 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 + +