diff --git a/app/src/main/java/live/hms/app2/ui/home/HomeFragment.kt b/app/src/main/java/live/hms/app2/ui/home/HomeFragment.kt index 1db957e09..dc1bc1339 100644 --- a/app/src/main/java/live/hms/app2/ui/home/HomeFragment.kt +++ b/app/src/main/java/live/hms/app2/ui/home/HomeFragment.kt @@ -14,6 +14,7 @@ import androidx.activity.result.contract.ActivityResultContracts import androidx.appcompat.app.AlertDialog import androidx.core.content.ContextCompat import androidx.core.content.edit +import androidx.core.os.bundleOf import androidx.core.widget.doOnTextChanged import androidx.fragment.app.Fragment import androidx.navigation.fragment.findNavController @@ -82,10 +83,7 @@ class HomeFragment : Fragment() { override fun onOptionsItemSelected(item: MenuItem): Boolean { when (item.itemId) { R.id.action_settings -> { - findNavController().navigate( - HomeFragmentDirections.actionHomeFragmentToSettingsFragment(SettingsMode.HOME) - ) - + findNavController().navigate(HomeFragmentDirections.actionHomeFragmentToSettingsFragment()) } R.id.action_email_logs -> { requireContext().startActivity( @@ -263,13 +261,13 @@ class HomeFragment : Fragment() { private fun enableButton() { binding.btnJoinNow.isEnabled = true binding.btnJoinNow.background = - ContextCompat.getDrawable(requireContext(), R.drawable.primary_blue_round_drawable) + ContextCompat.getDrawable(requireContext(), live.hms.roomkit.R.drawable.primary_blue_round_drawable) } private fun disableButton() { binding.btnJoinNow.isEnabled = false binding.btnJoinNow.background = - ContextCompat.getDrawable(requireContext(), R.drawable.primary_disabled_round_drawable) + ContextCompat.getDrawable(requireContext(), live.hms.roomkit.R.drawable.primary_disabled_round_drawable) } diff --git a/app/src/main/res/drawable/primary_disabled_round_drawable.xml b/app/src/main/res/drawable/primary_disabled_round_drawable.xml deleted file mode 100644 index f3f70896d..000000000 --- a/app/src/main/res/drawable/primary_disabled_round_drawable.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/app/src/main/res/navigation/home_nav_graph.xml b/app/src/main/res/navigation/home_nav_graph.xml index ddcbeabb4..0ee940ab9 100644 --- a/app/src/main/res/navigation/home_nav_graph.xml +++ b/app/src/main/res/navigation/home_nav_graph.xml @@ -24,12 +24,6 @@ android:label="@string/settings_fragment" tools:layout="@layout/fragment_settings"> - - - \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index f1652e547..072711724 100644 --- a/gradle.properties +++ b/gradle.properties @@ -22,5 +22,5 @@ kotlin.code.style=official 100MS_APP_VERSION_CODE=643 100MS_APP_VERSION_NAME=5.8.872 hmsRoomKitGroup=live.100ms -HMS_ROOM_KIT_VERSION=1.1.8 +HMS_ROOM_KIT_VERSION=1.1.9 android.suppressUnsupportedCompileSdk=33 diff --git a/room-kit/build.gradle b/room-kit/build.gradle index 345594ae5..2349ed668 100644 --- a/room-kit/build.gradle +++ b/room-kit/build.gradle @@ -69,7 +69,7 @@ dependencies { implementation 'androidx.constraintlayout:constraintlayout:2.1.4' implementation 'androidx.percentlayout:percentlayout:1.0.0' - def hmsVersion = "2.8.5" + def hmsVersion = "2.8.6" // To add dependencies of specific module implementation "live.100ms:android-sdk:$hmsVersion" implementation "live.100ms:video-view:$hmsVersion" diff --git a/room-kit/src/main/java/live/hms/roomkit/ui/meeting/MeetingActivity.kt b/room-kit/src/main/java/live/hms/roomkit/ui/meeting/MeetingActivity.kt index 30dfb75c7..34e5cdccd 100644 --- a/room-kit/src/main/java/live/hms/roomkit/ui/meeting/MeetingActivity.kt +++ b/room-kit/src/main/java/live/hms/roomkit/ui/meeting/MeetingActivity.kt @@ -8,11 +8,9 @@ import android.widget.Toast import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.viewModels import androidx.appcompat.app.AppCompatActivity -import androidx.core.os.bundleOf import androidx.core.view.ViewCompat import androidx.core.view.WindowInsetsCompat import androidx.lifecycle.lifecycleScope -import androidx.navigation.findNavController import androidx.navigation.fragment.NavHostFragment import androidx.recyclerview.widget.DefaultItemAnimator import androidx.recyclerview.widget.DiffUtil @@ -21,9 +19,6 @@ import live.hms.roomkit.R import live.hms.roomkit.animation.RootViewDeferringInsetsCallback import live.hms.roomkit.databinding.ActivityMeetingBinding import live.hms.roomkit.ui.HMSPrebuiltOptions -import live.hms.roomkit.ui.meeting.chat.combined.CHAT_TAB_TITLE -import live.hms.roomkit.ui.meeting.chat.combined.ChatParticipantCombinedFragment -import live.hms.roomkit.ui.meeting.chat.combined.OPEN_TO_PARTICIPANTS import live.hms.roomkit.ui.notification.CardStackLayoutManager import live.hms.roomkit.ui.notification.CardStackListener import live.hms.roomkit.ui.notification.Direction @@ -31,7 +26,6 @@ import live.hms.roomkit.ui.notification.HMSNotification import live.hms.roomkit.ui.notification.HMSNotificationAdapter import live.hms.roomkit.ui.notification.HMSNotificationDiffCallBack import live.hms.roomkit.ui.notification.HMSNotificationType -import live.hms.roomkit.ui.polls.display.POLL_TO_DISPLAY import live.hms.roomkit.ui.polls.display.PollDisplayFragment import live.hms.roomkit.ui.settings.SettingsStore import live.hms.roomkit.util.ROOM_CODE @@ -94,31 +88,9 @@ class MeetingActivity : AppCompatActivity() { binding.progressBar.visibility = View.VISIBLE - //todo show a loader UI - meetingViewModel.initSdk(roomCode, token, hmsPrebuiltOption, object : HMSActionResultListener { - override fun onError(error: HMSException) { - runOnUiThread { - Toast.makeText(this@MeetingActivity, error.message, Toast.LENGTH_SHORT).show() - finish() - } - } + meetingViewModel.initSdk(roomCode, token, hmsPrebuiltOption, null) - override fun onSuccess() { - runOnUiThread { - binding.progressBar.visibility = View.GONE - val navHostFragment = - supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment - val navController = navHostFragment.navController - val topFragment = navHostFragment.childFragmentManager.fragments.firstOrNull() - if (settingsStore?.showPreviewBeforeJoin == true && (topFragment is MeetingFragment).not()) navController?.setGraph( - R.navigation.meeting_nav_graph, intent.extras - ) - else navController?.setGraph(R.navigation.no_preview_nav_graph, intent.extras) - - initViewModels() - } - } - }) + initObservers() window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) @@ -138,7 +110,7 @@ class MeetingActivity : AppCompatActivity() { _binding = null } - private fun initViewModels() { + private fun initObservers() { meetingViewModel.recordingState.observe(this) { invalidateOptionsMenu() } @@ -158,6 +130,25 @@ class MeetingActivity : AppCompatActivity() { tryRemovingNotification(it) } + meetingViewModel.roomLayoutLiveData.observe(this) {success -> + if(success) { + binding.progressBar.visibility = View.GONE + val navHostFragment = + supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment + val navController = navHostFragment.navController + val topFragment = navHostFragment.childFragmentManager.fragments.firstOrNull() + navController.setGraph( + R.navigation.meeting_nav_graph, intent.extras + ) + /*if (settingsStore?.showPreviewBeforeJoin == true && (topFragment is MeetingFragment).not()) navController?.setGraph( + R.navigation.meeting_nav_graph, intent.extras + ) + else navController?.setGraph(R.navigation.no_preview_nav_graph, intent.extras)*/ + } else { + Toast.makeText(this@MeetingActivity, "Error while Getting Room Layout Data", Toast.LENGTH_SHORT).show() + finish() + } + } } private fun triggerNotification(hmsNotification: HMSNotification) { diff --git a/room-kit/src/main/java/live/hms/roomkit/ui/meeting/MeetingFragment.kt b/room-kit/src/main/java/live/hms/roomkit/ui/meeting/MeetingFragment.kt index 5b129f3f1..82d984d5d 100644 --- a/room-kit/src/main/java/live/hms/roomkit/ui/meeting/MeetingFragment.kt +++ b/room-kit/src/main/java/live/hms/roomkit/ui/meeting/MeetingFragment.kt @@ -111,7 +111,7 @@ class MeetingFragment : Fragment() { private val chatAdapter by lazy { ChatAdapter({ message -> launchMessageOptionsDialog.launch(meetingViewModel, - childFragmentManager, message) }, ::onChatClick, { MessageOptionsBottomSheet.showMessageOptions(meetingViewModel)}) + childFragmentManager, message) }, ::onChatClick, { message -> MessageOptionsBottomSheet.showMessageOptions(meetingViewModel, message)}) } private val chatViewModel: ChatViewModel by activityViewModels { @@ -265,17 +265,36 @@ class MeetingFragment : Fragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) binding.applyTheme() - initObservers() - initButtons() - initOnBackPress() - if (meetingViewModel.state.value is MeetingState.Disconnected) { - // Handles configuration changes - meetingViewModel.startMeeting() + if (savedInstanceState != null) { + // Recreated Fragment + meetingViewModel.roomLayoutLiveData.observe(viewLifecycleOwner) {success -> + if (success) { + meetingViewModel.state.observe(viewLifecycleOwner) { state -> + if (state is MeetingState.Ongoing) { + hideProgressBar() + isMeetingOngoing = true + meetingViewModel.state.removeObservers(viewLifecycleOwner) + initializeUI() + } + } + meetingViewModel.roomLayoutLiveData.removeObservers(viewLifecycleOwner) + meetingViewModel.startMeeting() + } else { + this.activity?.finish() + } + } } else { - //start HLS stream + initializeUI() startHLSStreamingIfRequired() } + } + + private fun initializeUI() { + initButtons() + initObservers() + initOnBackPress() + binding.chatMessages.isHeightContrained = true PauseChatUIUseCase().setChatPauseVisible( binding.chatOptionsCard, @@ -301,9 +320,12 @@ class MeetingFragment : Fragment() { binding.chatExtra, meetingViewModel.prebuiltInfoContainer::isChatEnabled, meetingViewModel::availableRecipientsForChat, - chatViewModel::currentlySelectedRbacRecipient + chatViewModel::currentlySelectedRbacRecipient, + chatViewModel.currentlySelectedRecipientRbac, ) - + meetingViewModel.peerLeaveUpdate.observe(viewLifecycleOwner) { + chatViewModel.updatePeerLeave(it) + } if(meetingViewModel.prebuiltInfoContainer.chatInitialStateOpen()) { binding.buttonOpenChat.setIconDisabled(R.drawable.ic_chat_message) } else { @@ -535,12 +557,10 @@ class MeetingFragment : Fragment() { Log.d("RecordingState", event.message) } is MeetingViewModel.Event.RtmpEvent -> { - meetingViewModel.triggerErrorNotification(event.message) - Log.d("RecordingState", event.message) + Log.i("RecordingState", event.message) } is MeetingViewModel.Event.ServerRecordEvent -> { - meetingViewModel.triggerErrorNotification(event.message) - Log.d("RecordingState", event.message) + Log.i("RecordingState", event.message) } is MeetingViewModel.Event.HlsEvent, is MeetingViewModel.Event.HlsRecordingEvent -> { Log.d("RecordingState", "HlsEvent: ${event}") @@ -865,7 +885,7 @@ class MeetingFragment : Fragment() { ) binding.buttonRaiseHand.visibility = View.GONE - WindowCompat.setDecorFitsSystemWindows(activity!!.window, true) + WindowCompat.setDecorFitsSystemWindows(requireActivity().window, true) showSystemBars() } @@ -953,7 +973,7 @@ class MeetingFragment : Fragment() { })?.start() - val screenHeight = activity!!.window.decorView.height + val screenHeight = requireActivity().window.decorView.height binding.bottomControls.animate() ?.translationY(0f)?.setDuration(300)?.setListener(object : AnimatorListener { override fun onAnimationStart(animation: Animator) { @@ -1001,9 +1021,9 @@ class MeetingFragment : Fragment() { private fun hideControlBars() { val topMenu = binding.topMenu val bottomMenu = binding.bottomControls - val screenHeight = activity!!.window.decorView.height + val screenHeight = requireActivity().window.decorView.height controlBarsVisible = false - topMenu?.animate() + topMenu.animate() ?.translationY(-(topMenu.height.toFloat()))?.setDuration(300) ?.setListener(object : AnimatorListener { override fun onAnimationStart(animation: Animator) { @@ -1350,53 +1370,6 @@ class MeetingFragment : Fragment() { }) } - fun roleChangeRemote() { - - val isAllowedToMuteUnmute = - meetingViewModel.isAllowedToMutePeers() && meetingViewModel.isAllowedToAskUnmutePeers() - var remotePeersAreMute: Boolean? = null - if (isAllowedToMuteUnmute) { - remotePeersAreMute = meetingViewModel.areAllRemotePeersMute() - } - - val cancelRoleName = "Cancel" - val availableRoles = meetingViewModel.getAvailableRoles().map { it.name } - val rolesToSend = availableRoles.plus(cancelRoleName) - binding.roleSpinner.root.initAdapters( - rolesToSend, - if (remotePeersAreMute == null) "Nothing to change" else if (remotePeersAreMute) "Remote Unmute Role" else "Remote Mute Role", - object : AdapterView.OnItemSelectedListener { - override fun onItemSelected( - parent: AdapterView<*>?, - view: View?, - position: Int, - id: Long - ) { - val stringRole = parent?.adapter?.getItem(position) as String - if (remotePeersAreMute == null) { - Toast.makeText( - requireContext(), - "No remote peers, or their audio tracks are absent", - Toast.LENGTH_LONG - ).show() - } else { - if (stringRole != cancelRoleName) { - meetingViewModel.remoteMute( - !remotePeersAreMute, - listOf(stringRole) - ) - } - } - } - - override fun onNothingSelected(parent: AdapterView<*>?) { - // Nothing - } - - }) - binding.roleSpinner.root.performClick() - } - fun inflateExitFlow() { if (meetingViewModel.isAllowedToEndMeeting() || (meetingViewModel.isAllowedToHlsStream() && meetingViewModel.isHlsRunning())) { MultipleLeaveOptionBottomSheet() diff --git a/room-kit/src/main/java/live/hms/roomkit/ui/meeting/MeetingViewModel.kt b/room-kit/src/main/java/live/hms/roomkit/ui/meeting/MeetingViewModel.kt index 820ea910d..3450c88b2 100644 --- a/room-kit/src/main/java/live/hms/roomkit/ui/meeting/MeetingViewModel.kt +++ b/room-kit/src/main/java/live/hms/roomkit/ui/meeting/MeetingViewModel.kt @@ -45,6 +45,7 @@ import live.hms.video.polls.models.HmsPollState import live.hms.video.polls.models.answer.PollAnswerResponse import live.hms.video.polls.models.question.HMSPollQuestion import live.hms.video.polls.models.question.HMSPollQuestionType +import live.hms.video.polls.network.PollLeaderboardResponse import live.hms.video.sdk.* import live.hms.video.sdk.models.* import live.hms.video.sdk.models.enums.* @@ -81,11 +82,13 @@ class MeetingViewModel( HMSLogSettings(LogAlarmManager.DEFAULT_DIR_SIZE, true) private var isPrebuiltDebug by Delegates.notNull() val roleChange = MutableLiveData() - private var numRoleChanges = 0 - val roleChangeSingleShot : LiveData = roleChange.map { numRoleChanges++ } + var roleOnJoining : HMSRole? = null private set + var localPeerId : String? = null + private set + fun isLargeRoom() = hmsRoom?.isLargeRoom?:false private val hmsTrackSettings = HMSTrackSettings.Builder() @@ -154,11 +157,12 @@ class MeetingViewModel( roomCode: String, token: String, hmsPrebuiltOptions: HMSPrebuiltOptions?, - onHMSActionResultListener: HMSActionResultListener + onHMSActionResultListener: HMSActionResultListener? ) { this.prebuiltOptions = hmsPrebuiltOptions if (hasValidToken) { - onHMSActionResultListener.onSuccess() + onHMSActionResultListener?.onSuccess() + roomLayoutLiveData.postValue(true) return } //if empty is uses the prod token url else uses the debug token url @@ -178,7 +182,8 @@ class MeetingViewModel( object : HMSTokenListener { override fun onError(error: HMSException) { hasValidToken = false - onHMSActionResultListener.onError(error) + onHMSActionResultListener?.onError(error) + roomLayoutLiveData.postValue(false) } override fun onTokenSuccess(token: String) { @@ -186,12 +191,10 @@ class MeetingViewModel( } }) - - } - fun joinRoomUsingToken(token: String, hmsPrebuiltOptions: HMSPrebuiltOptions?, onHMSActionResultListener: HMSActionResultListener) { + fun joinRoomUsingToken(token: String, hmsPrebuiltOptions: HMSPrebuiltOptions?, onHMSActionResultListener: HMSActionResultListener?) { val initURL: String = if (hmsPrebuiltOptions?.endPoints?.containsKey("init") == true) hmsPrebuiltOptions.endPoints["init"].orEmpty() @@ -206,15 +209,18 @@ class MeetingViewModel( HMSLayoutListener { override fun onError(error: HMSException) { Log.e(TAG, "onError: ", error) - onHMSActionResultListener.onError(error) + onHMSActionResultListener?.onError(error) + roomLayoutLiveData.postValue(false) } override fun onLayoutSuccess(layoutConfig: HMSRoomLayout) { hmsRoomLayout = layoutConfig prebuiltInfoContainer.setParticipantLabelInfo(hmsRoomLayout) + Log.d("Pratim", "Setting HMS Config") setHmsConfig(hmsPrebuiltOptions, token, initURL) kotlin.runCatching { setTheme(layoutConfig.data?.getOrNull(0)?.themes?.getOrNull(0)?.palette!!) } - onHMSActionResultListener.onSuccess() + onHMSActionResultListener?.onSuccess() + roomLayoutLiveData.postValue(true) } }) @@ -314,6 +320,7 @@ class MeetingViewModel( val hmsRemoveNotificationEvent = MutableLiveData() val updateGridLayoutDimensions = SingleLiveEvent() val hmsScreenShareBottomSheetEvent = SingleLiveEvent() + val roomLayoutLiveData : MutableLiveData = MutableLiveData() fun setMeetingViewMode(mode: MeetingViewMode) { if (mode != meetingViewMode.value) { @@ -450,6 +457,7 @@ class MeetingViewModel( // Live data which changes on any change of peer val peerLiveData = MutableLiveData() val participantPeerUpdate = MutableLiveData() + val peerLeaveUpdate = MutableLiveData(null) private val _peerMetadataNameUpdate = MutableLiveData>() val peerMetadataNameUpdate: LiveData> = _peerMetadataNameUpdate @@ -497,6 +505,7 @@ class MeetingViewModel( } override fun onPreview(room: HMSRoom, localTracks: Array) { + Log.d("Pratim", "onPreview called") unMuteAllTracks(localTracks) previewUpdateData.postValue(Pair(room, localTracks)) } @@ -737,6 +746,7 @@ class MeetingViewModel( Log.d(TAG, "SessionId is: ${room.sessionId}") Log.d(TAG, "Room started at: ${room.startedAt}") roleOnJoining = room.localPeer?.hmsRole + localPeerId = room.localPeer?.peerID // get the hls URL from the Room, if it exists val hlsUrl = room.hlsStreamingState.variants?.get(0)?.hlsStreamUrl @@ -797,6 +807,7 @@ class MeetingViewModel( peerLiveData.postValue(hmsPeer) } participantPeerUpdate.postValue(Unit) + peerLeaveUpdate.postValue(hmsPeer.peerID) } HMSPeerUpdate.PEER_JOINED -> { @@ -1003,8 +1014,10 @@ class MeetingViewModel( if(message.type != HMSMessageType.CHAT) return broadcastsReceived.postValue( + ChatMessage( - message, false + message, false, + message.recipient.recipientPeer?.peerID == localPeerId ) ) } @@ -2017,6 +2030,10 @@ class MeetingViewModel( } }) + + fun fetchLeaderboard(pollId: String, completion: HmsTypedActionResultListener) { + localHmsInteractivityCenter.fetchLeaderboard(pollId, count = 200, completion = completion) + } fun startPoll(currentList: List, pollCreationInfo: PollCreationInfo) { // To start a poll diff --git a/room-kit/src/main/java/live/hms/roomkit/ui/meeting/MessageOptionsBottomSheet.kt b/room-kit/src/main/java/live/hms/roomkit/ui/meeting/MessageOptionsBottomSheet.kt index a56f8587c..28138f791 100644 --- a/room-kit/src/main/java/live/hms/roomkit/ui/meeting/MessageOptionsBottomSheet.kt +++ b/room-kit/src/main/java/live/hms/roomkit/ui/meeting/MessageOptionsBottomSheet.kt @@ -37,8 +37,8 @@ class MessageOptionsBottomSheet(private val chatMessage: ChatMessage, ): BottomSheetDialogFragment() { companion object { - fun showMessageOptions(meetingViewModel: MeetingViewModel) : Boolean { - val allowedToBlock = meetingViewModel.isAllowedToBlockFromChat() + fun showMessageOptions(meetingViewModel: MeetingViewModel, message: ChatMessage) : Boolean { + val allowedToBlock = meetingViewModel.isAllowedToBlockFromChat() && !message.isSentByMe val allowedToPin = meetingViewModel.isAllowedToPinMessages() val allowedToHideMessages = meetingViewModel.isAllowedToHideMessages() return allowedToPin || allowedToBlock || allowedToHideMessages diff --git a/room-kit/src/main/java/live/hms/roomkit/ui/meeting/PauseChatUIUseCase.kt b/room-kit/src/main/java/live/hms/roomkit/ui/meeting/PauseChatUIUseCase.kt index a8fad9fe0..e2b08c8c9 100644 --- a/room-kit/src/main/java/live/hms/roomkit/ui/meeting/PauseChatUIUseCase.kt +++ b/room-kit/src/main/java/live/hms/roomkit/ui/meeting/PauseChatUIUseCase.kt @@ -16,10 +16,4 @@ class PauseChatUIUseCase { } } - fun observeChatPauseState(viewLifecycleOwner: LifecycleOwner, meetingViewModel: MeetingViewModel) { - meetingViewModel.chatPauseState.observe(viewLifecycleOwner) { chatState -> - // Set the chat as paused (can use the block UI I guess) - - } - } } \ No newline at end of file diff --git a/room-kit/src/main/java/live/hms/roomkit/ui/meeting/PrebuiltInfoContainer.kt b/room-kit/src/main/java/live/hms/roomkit/ui/meeting/PrebuiltInfoContainer.kt index 5d861f492..3c250911f 100644 --- a/room-kit/src/main/java/live/hms/roomkit/ui/meeting/PrebuiltInfoContainer.kt +++ b/room-kit/src/main/java/live/hms/roomkit/ui/meeting/PrebuiltInfoContainer.kt @@ -24,17 +24,17 @@ class PrebuiltInfoContainer(private val hmssdk: HMSSDK) { fun isChatEnabled(): Boolean = // how do we even know if it's in hls? What if they have both? - roleMap[localPeer.hmsRole.name]?.screens?.conferencing?.hlsLiveStreaming - ?.elements?.chat != null || - roleMap[localPeer.hmsRole.name]?.screens?.conferencing - ?.default?.elements?.chat != null + roleMap[localPeer.hmsRole.name]?.screens?.conferencing?.hlsLiveStreaming + ?.elements?.chat != null || + roleMap[localPeer.hmsRole.name]?.screens?.conferencing + ?.default?.elements?.chat != null fun chatInitialStateOpen(): Boolean { val isChatInitialOpen = roleMap[localPeer.hmsRole.name]?.screens?.conferencing?.hlsLiveStreaming?.elements?.chat?.initialState == "CHAT_STATE_OPEN" || - roleMap[localPeer.hmsRole.name]?.screens?.conferencing - ?.default?.elements?.chat?.initialState == "CHAT_STATE_OPEN" + roleMap[localPeer.hmsRole.name]?.screens?.conferencing + ?.default?.elements?.chat?.initialState == "CHAT_STATE_OPEN" // Initial open is only valid for overlay chat return isChatOverlay() && isChatInitialOpen @@ -42,8 +42,8 @@ class PrebuiltInfoContainer(private val hmssdk: HMSSDK) { fun isChatOverlay() = roleMap[localPeer.hmsRole.name]?.screens?.conferencing?.hlsLiveStreaming?.elements?.chat?.overlayView == true || - roleMap[localPeer.hmsRole.name]?.screens?.conferencing - ?.default?.elements?.chat?.overlayView == true + roleMap[localPeer.hmsRole.name]?.screens?.conferencing + ?.default?.elements?.chat?.overlayView == true fun onStageExp(role : String) = @@ -69,7 +69,8 @@ class PrebuiltInfoContainer(private val hmssdk: HMSSDK) { Recipient.Everyone } else if (recipient.roles.isNotEmpty()) { - val name = recipient.roles.first() + val localPeerRole = hmssdk.getLocalPeer()?.hmsRole?.name + val name = recipient.roles.filter { it != localPeerRole }.firstOrNull() val role = hmssdk.getRoles().find { it.name == name } if(role != null) Recipient.Role(role) @@ -111,10 +112,10 @@ class PrebuiltInfoContainer(private val hmssdk: HMSSDK) { ?.default?.elements?.chat?.realTimeControls?.canDisableChat == true fun isAllowedToHideMessages(): Boolean = - roleMap[localPeer.hmsRole.name]?.screens?.conferencing?.hlsLiveStreaming - ?.elements?.chat?.realTimeControls?.canHideMessage == true || - roleMap[localPeer.hmsRole.name]?.screens?.conferencing - ?.default?.elements?.chat?.realTimeControls?.canHideMessage == true + roleMap[localPeer.hmsRole.name]?.screens?.conferencing?.hlsLiveStreaming + ?.elements?.chat?.realTimeControls?.canHideMessage == true || + roleMap[localPeer.hmsRole.name]?.screens?.conferencing + ?.default?.elements?.chat?.realTimeControls?.canHideMessage == true fun getChatTitle(): String { val hlsTitle = roleMap[localPeer.hmsRole.name]?.screens?.conferencing?.hlsLiveStreaming diff --git a/room-kit/src/main/java/live/hms/roomkit/ui/meeting/chat/ChatAdapter.kt b/room-kit/src/main/java/live/hms/roomkit/ui/meeting/chat/ChatAdapter.kt index 247e59cde..00fa19540 100644 --- a/room-kit/src/main/java/live/hms/roomkit/ui/meeting/chat/ChatAdapter.kt +++ b/room-kit/src/main/java/live/hms/roomkit/ui/meeting/chat/ChatAdapter.kt @@ -17,7 +17,7 @@ import java.util.* class ChatAdapter(private val openMessageOptions : (ChatMessage) -> Unit, val onClick: () -> Unit = {}, - private val shouldShowMessageOptions : () -> Boolean + private val shouldShowMessageOptions : (ChatMessage) -> Boolean ) : ListAdapter(DIFFUTIL_CALLBACK) { private val formatter = SimpleDateFormat("h:mm a", Locale.getDefault()) companion object { @@ -54,23 +54,31 @@ class ChatAdapter(private val openMessageOptions : (ChatMessage) -> Unit, time.text = formatter.format(Date(sentMessage.time)) } - sentTo.visibility = if(sentMessage.sentTo == null) - View.GONE + val isSentToVisible = sentMessage.toGroup != null + sentTo.visibility = if(isSentToVisible) + View.VISIBLE else - View.VISIBLE - - toGroup.visibility = if(sentMessage.toGroup == null) View.GONE - else - View.VISIBLE - sentBackground.visibility = if(sentMessage.toGroup == null && sentMessage.sentTo == null) { + if(isSentToVisible) { + val r = binding.root.resources + sentTo.text = + r.getString(R.string.chat_to_label, + sentMessage.toGroup, + if(sentMessage.isDmToMe || sentMessage.isDm) + r.getString(R.string.chat_to_dm_label) + else + r.getString(R.string.chat_to_group_label) + ) + } + + sentBackground.visibility = if(sentMessage.toGroup == null) { View.GONE } else View.VISIBLE - sentTo.text = sentMessage.sentTo - toGroup.text = sentMessage.toGroup - viewMore.visibility = if(shouldShowMessageOptions()) View.VISIBLE else View.GONE + + + viewMore.visibility = if(shouldShowMessageOptions(sentMessage)) View.VISIBLE else View.GONE } } } diff --git a/room-kit/src/main/java/live/hms/roomkit/ui/meeting/chat/ChatMessage.kt b/room-kit/src/main/java/live/hms/roomkit/ui/meeting/chat/ChatMessage.kt index 7bb152d72..084504082 100644 --- a/room-kit/src/main/java/live/hms/roomkit/ui/meeting/chat/ChatMessage.kt +++ b/room-kit/src/main/java/live/hms/roomkit/ui/meeting/chat/ChatMessage.kt @@ -3,9 +3,11 @@ package live.hms.roomkit.ui.meeting.chat import live.hms.video.sdk.models.HMSMessage import live.hms.video.sdk.models.enums.HMSMessageRecipientType import live.hms.video.sdk.models.role.HMSRole -import java.util.* import android.os.Parcelable import kotlinx.parcelize.Parcelize +import live.hms.roomkit.ui.meeting.chat.rbac.RecipientItem +import live.hms.video.sdk.models.HMSMessageRecipient +import live.hms.video.sdk.models.HMSPeer const val DEFAULT_SENDER_NAME = "Participant" @@ -17,38 +19,44 @@ data class ChatMessage constructor( val time: Long? = null, val message: String, val isSentByMe: Boolean, + val isDmToMe : Boolean, + val isDm : Boolean, var messageId : String? = null, - val sentTo : String?, val toGroup : String?, val senderPeerId : String?, val senderRoleName : String?, - val userIdForBlockList : String? + val userIdForBlockList : String?, ) : Parcelable { companion object { - fun sendTo(message: HMSMessage) : String? = sendTo(message.recipient.recipientType, - message.recipient.recipientRoles) + fun sendTo(recipient: HMSMessageRecipient, sentByMe: Boolean, sentToMe: Boolean) : String? = + sendTo( + recipient.recipientType, + recipient.recipientPeer, + recipient.recipientRoles, + sentToMe + ) - fun sendTo(recipient: HMSMessageRecipientType, - roles : List?) : String? = when(recipient) { + fun sendTo( + recipient: HMSMessageRecipientType, + peer: HMSPeer?, + roles: List?, + sentToMe: Boolean + ) : String? = when(recipient) { HMSMessageRecipientType.BROADCAST -> null - HMSMessageRecipientType.PEER -> "Direct Message" + HMSMessageRecipientType.PEER -> if(sentToMe) "You" else peer?.name HMSMessageRecipientType.ROLES -> roles?.firstOrNull()?.name ?: "Role" } - - fun toGroup(recipient: HMSMessageRecipientType) = when(recipient) { - HMSMessageRecipientType.PEER, HMSMessageRecipientType.BROADCAST -> null - HMSMessageRecipientType.ROLES -> "To Group" - } } - constructor(message: HMSMessage, sentByMe: Boolean) : this( + constructor(message: HMSMessage, sentByMe: Boolean, sentToMe : Boolean) : this( if(sentByMe) "You" else message.sender?.name ?: DEFAULT_SENDER_NAME, message.sender?.name ?: DEFAULT_SENDER_NAME, message.serverReceiveTime, message.message, sentByMe, + sentToMe, + message.recipient.recipientType == HMSMessageRecipientType.PEER, messageId = message.messageId, - sentTo = sendTo(message), - toGroup = toGroup(message.recipient.recipientType), + toGroup = sendTo(message.recipient, sentByMe, sentToMe), message.sender?.peerID, message.sender?.hmsRole?.name, message.sender?.customerUserID diff --git a/room-kit/src/main/java/live/hms/roomkit/ui/meeting/chat/ChatUseCase.kt b/room-kit/src/main/java/live/hms/roomkit/ui/meeting/chat/ChatUseCase.kt index 2dc44a552..f0a911e46 100644 --- a/room-kit/src/main/java/live/hms/roomkit/ui/meeting/chat/ChatUseCase.kt +++ b/room-kit/src/main/java/live/hms/roomkit/ui/meeting/chat/ChatUseCase.kt @@ -7,6 +7,7 @@ import android.widget.LinearLayout import android.widget.TextView import androidx.appcompat.widget.LinearLayoutCompat import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.recyclerview.widget.LinearSmoothScroller @@ -68,8 +69,8 @@ class ChatUseCase { fun initiate( messages: MutableLiveData>, chatPauseState: MutableLiveData, - roleChanged : MutableLiveData, //used to refresh options - blockList : MutableLiveData>, + roleChanged: MutableLiveData, //used to refresh options + blockList: MutableLiveData>, viewlifecycleOwner: LifecycleOwner, chatAdapter: ChatAdapter, recyclerview: SingleSideFadeRecyclerview, @@ -83,8 +84,9 @@ class ChatUseCase { chatPausedContainer: LinearLayoutCompat, recipientPickerContainer: LinearLayout, isChatEnabled: () -> Boolean, - getAllowedRecipients : () -> AllowedToMessageParticipants?, - currentRbac : () -> Recipient? + getAllowedRecipients: () -> AllowedToMessageParticipants?, + currentRbac: () -> Recipient?, + currentlySelectedRecipientRbac: LiveData // canShowIndicator : () -> Boolean = {true} ) { @@ -113,6 +115,10 @@ class ChatUseCase { } recyclerview.adapter = chatAdapter toggleEmptyIndicator(emptyIndicator, messages.value) + + currentlySelectedRecipientRbac.observe(viewlifecycleOwner) { + updateState() + } // Chat pause observer blockList.observe(viewlifecycleOwner) { updateState() @@ -206,6 +212,8 @@ class ChatUseCase { } ChatUiVisibilityState.RecipientSelectNeeded -> { // Change the picker maybe? + bannedText.visibility = View.GONE + chatPausedContainer.visibility = View.GONE } ChatUiVisibilityState.Enabled -> { bannedText.visibility = View.GONE diff --git a/room-kit/src/main/java/live/hms/roomkit/ui/meeting/chat/ChatViewModel.kt b/room-kit/src/main/java/live/hms/roomkit/ui/meeting/chat/ChatViewModel.kt index eefb6553b..b5cdeed39 100644 --- a/room-kit/src/main/java/live/hms/roomkit/ui/meeting/chat/ChatViewModel.kt +++ b/room-kit/src/main/java/live/hms/roomkit/ui/meeting/chat/ChatViewModel.kt @@ -6,7 +6,6 @@ import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import kotlinx.coroutines.MainScope import kotlinx.coroutines.launch -import live.hms.roomkit.ui.meeting.chat.rbac.ChatMessageViewFilterHelper import live.hms.video.error.HMSException import live.hms.video.sdk.HMSMessageResultListener import live.hms.video.sdk.HMSSDK @@ -17,7 +16,6 @@ import live.hms.video.sdk.models.enums.HMSMessageType import live.hms.video.sdk.models.role.HMSRole class ChatViewModel(private val hmssdk: HMSSDK) : ViewModel() { - private val chatmessageViewFilterHelper = ChatMessageViewFilterHelper() companion object { private const val TAG = "ChatViewModel" @@ -33,9 +31,7 @@ class ChatViewModel(private val hmssdk: HMSSDK) : ViewModel() { fun updateSelectedRecipientChatBottomSheet(recipient: Recipient?) { // Set a filter for the messages. - chatmessageViewFilterHelper.setFilter(recipient) _currentlySelectedRecipient.postValue(recipient) - messages.postValue(chatmessageViewFilterHelper.getSearchFilteredPeersIfNeeded(_messages)) } val currentlySelectedRecipientRbac : LiveData = _currentlySelectedRecipient @@ -46,50 +42,67 @@ class ChatViewModel(private val hmssdk: HMSSDK) : ViewModel() { null -> {} // no-op if it's null Recipient.Everyone -> broadcast( ChatMessage( - "You", - hmssdk.getLocalPeer()?.name ?: DEFAULT_SENDER_NAME, - null, // Let the server alone set the time - messageStr, - true, - null, - ChatMessage.sendTo(HMSMessageRecipientType.BROADCAST, null), - ChatMessage.toGroup(HMSMessageRecipientType.BROADCAST), - hmssdk.getLocalPeer()?.peerID, - hmssdk.getLocalPeer()?.hmsRole?.name, - hmssdk.getLocalPeer()?.customerUserID ?: "" + senderName = "You", + localSenderRealNameForPinMessage = hmssdk.getLocalPeer()?.name ?: DEFAULT_SENDER_NAME, + time = null, // Let the server alone set the time + message = messageStr, + isSentByMe = true, + isDmToMe = false, + isDm = false, + messageId = null, + toGroup = ChatMessage.sendTo( + recipient = HMSMessageRecipientType.BROADCAST, + peer = null, + roles = null, + false + ), + senderPeerId = hmssdk.getLocalPeer()?.peerID, + senderRoleName = hmssdk.getLocalPeer()?.hmsRole?.name, + userIdForBlockList = hmssdk.getLocalPeer()?.customerUserID ?: "" ) ) is Recipient.Peer -> directMessage( ChatMessage( - "You", - hmssdk.getLocalPeer()?.name ?: DEFAULT_SENDER_NAME, - null, // Let the server alone set the time - messageStr, - true, - - null, - ChatMessage.sendTo(HMSMessageRecipientType.PEER, null), - ChatMessage.toGroup(HMSMessageRecipientType.PEER), - hmssdk.getLocalPeer()?.peerID, - hmssdk.getLocalPeer()?.hmsRole?.name, - hmssdk.getLocalPeer()?.customerUserID ?: "" + senderName = "You", + localSenderRealNameForPinMessage = hmssdk.getLocalPeer()?.name ?: DEFAULT_SENDER_NAME, + time = null, // Let the server alone set the time + message = messageStr, + isSentByMe = true, + isDmToMe = false, + isDm = true, + messageId = null, + toGroup = ChatMessage.sendTo( + recipient = HMSMessageRecipientType.PEER, + peer = recipient.peer, + roles = null, + sentToMe = false + ), + senderPeerId = hmssdk.getLocalPeer()?.peerID, + senderRoleName = hmssdk.getLocalPeer()?.hmsRole?.name, + userIdForBlockList = hmssdk.getLocalPeer()?.customerUserID ?: "" ), recipient.peer ) is Recipient.Role -> groupMessage( ChatMessage( - "You", - hmssdk.getLocalPeer()?.name ?: DEFAULT_SENDER_NAME, - null, // Let the server alone set the time - messageStr, - true, - null, - ChatMessage.sendTo(HMSMessageRecipientType.ROLES, listOf(recipient.role)), - ChatMessage.toGroup(HMSMessageRecipientType.ROLES), - hmssdk.getLocalPeer()?.peerID, - hmssdk.getLocalPeer()?.hmsRole?.name, - hmssdk.getLocalPeer()?.customerUserID + senderName = "You", + localSenderRealNameForPinMessage = hmssdk.getLocalPeer()?.name ?: DEFAULT_SENDER_NAME, + time = null, // Let the server alone set the time + message = messageStr, + isSentByMe = true, + isDmToMe = false, + isDm = false, + messageId = null, + toGroup = ChatMessage.sendTo( + recipient = HMSMessageRecipientType.ROLES, + peer = null, + roles = listOf(recipient.role), + sentToMe = false + ), + senderPeerId = hmssdk.getLocalPeer()?.peerID, + senderRoleName = hmssdk.getLocalPeer()?.hmsRole?.name, + userIdForBlockList = hmssdk.getLocalPeer()?.customerUserID ), recipient.role ) } @@ -108,7 +121,7 @@ class ChatViewModel(private val hmssdk: HMSSDK) : ViewModel() { override fun onSuccess(hmsMessage: HMSMessage) { // Request Successfully sent to server MainScope().launch { - addMessage(ChatMessage(hmsMessage, true)) + addMessage(ChatMessage(hmsMessage, true, false)) } } @@ -128,7 +141,7 @@ class ChatViewModel(private val hmssdk: HMSSDK) : ViewModel() { override fun onSuccess(hmsMessage: HMSMessage) { // Request Successfully sent to server MainScope().launch { - addMessage(ChatMessage(hmsMessage, true)) + addMessage(ChatMessage(hmsMessage, true, false)) } } @@ -147,7 +160,7 @@ class ChatViewModel(private val hmssdk: HMSSDK) : ViewModel() { override fun onSuccess(hmsMessage: HMSMessage) { // Request Successfully sent to server MainScope().launch { - addMessage(ChatMessage(hmsMessage, true)) + addMessage(ChatMessage(hmsMessage, true, false)) } } @@ -178,7 +191,7 @@ class ChatViewModel(private val hmssdk: HMSSDK) : ViewModel() { unreadMessagesCount.postValue(unreadMessagesCount.value?.plus(1)) } _messages.add(message) - messages.postValue(chatmessageViewFilterHelper.getSearchFilteredPeersIfNeeded(_messages)) + messages.postValue(_messages) } } @@ -201,7 +214,7 @@ class ChatViewModel(private val hmssdk: HMSSDK) : ViewModel() { this.messageIdsToHide = messageIdsToHide // Refresh the current list _messages = _messages.filter { !messageIdsToHide.contains(it.messageId) }.toMutableList() - messages.postValue(chatmessageViewFilterHelper.getSearchFilteredPeersIfNeeded(_messages)) + messages.postValue(_messages) } // The blocklist throws away all messages from the blocked @@ -219,7 +232,7 @@ class ChatViewModel(private val hmssdk: HMSSDK) : ViewModel() { // Refresh the current list _messages = _messages.filter { !blockedPeerIds.contains(it.userIdForBlockList) }.toMutableList() - messages.postValue(chatmessageViewFilterHelper.getSearchFilteredPeersIfNeeded(_messages)) + messages.postValue(_messages) } } @@ -231,4 +244,14 @@ class ChatViewModel(private val hmssdk: HMSSDK) : ViewModel() { fun currentlySelectedRbacRecipient() : Recipient?{ return _currentlySelectedRecipient.value } + + fun updatePeerLeave(leavingPeerId : String?) { + if(leavingPeerId == null) + return + + val current = _currentlySelectedRecipient.value + if(current is Recipient.Peer && current.peer.peerID == leavingPeerId) + _currentlySelectedRecipient.postValue(null) + + } } diff --git a/room-kit/src/main/java/live/hms/roomkit/ui/meeting/chat/Recipient.kt b/room-kit/src/main/java/live/hms/roomkit/ui/meeting/chat/Recipient.kt index 16b82bc2c..115cf18c9 100644 --- a/room-kit/src/main/java/live/hms/roomkit/ui/meeting/chat/Recipient.kt +++ b/room-kit/src/main/java/live/hms/roomkit/ui/meeting/chat/Recipient.kt @@ -4,6 +4,9 @@ import live.hms.video.sdk.models.HMSMessageRecipient import live.hms.video.sdk.models.HMSPeer import live.hms.video.sdk.models.enums.HMSMessageRecipientType import live.hms.video.sdk.models.role.HMSRole +import android.os.Parcelable +import kotlinx.parcelize.Parcelize + sealed class Recipient { object Everyone : Recipient() { diff --git a/room-kit/src/main/java/live/hms/roomkit/ui/meeting/chat/combined/ChatRbacRecipientHandling.kt b/room-kit/src/main/java/live/hms/roomkit/ui/meeting/chat/combined/ChatRbacRecipientHandling.kt index 62463cf9a..b9ed74025 100644 --- a/room-kit/src/main/java/live/hms/roomkit/ui/meeting/chat/combined/ChatRbacRecipientHandling.kt +++ b/room-kit/src/main/java/live/hms/roomkit/ui/meeting/chat/combined/ChatRbacRecipientHandling.kt @@ -9,28 +9,34 @@ import live.hms.roomkit.ui.theme.HMSPrebuiltTheme import live.hms.roomkit.ui.theme.getColorOrDefault class ChatRbacRecipientHandling { - fun updateChipRecipientUI(sendToChipText : MaterialTextView, - recipient: Recipient?) { - //TODO might be able to change the UI to handle send to no one here. - if(recipient == null) - return - sendToChipText.text = recipient.toString() + fun updateChipRecipientUI( + sendToChipText: MaterialTextView, + recipient: Recipient? + ) { + + sendToChipText.text = recipient?.toString() + ?: sendToChipText.resources.getString(R.string.chat_rbac_picker_send_to) // Set the drawable next to it - val chevron = when(recipient) { + val chevron = when (recipient) { Recipient.Everyone -> R.drawable.tiny_chip_everyone - is Recipient.Peer, - is Recipient.Role -> R.drawable.tiny_chip_dm + is Recipient.Role -> R.drawable.tiny_chip_roles + is Recipient.Peer -> R.drawable.tiny_chip_dm + null -> null } - sendToChipText.drawableStart = AppCompatResources.getDrawable( - sendToChipText.context, chevron - )?.apply { - setTint( - getColorOrDefault( - HMSPrebuiltTheme.getColours()?.onSurfaceMedium, - HMSPrebuiltTheme.getDefaults().onsurface_med_emp + sendToChipText.drawableStart = if (chevron == null) { + null + } else { + AppCompatResources.getDrawable( + sendToChipText.context, chevron + )?.apply { + setTint( + getColorOrDefault( + HMSPrebuiltTheme.getColours()?.onSurfaceMedium, + HMSPrebuiltTheme.getDefaults().onsurface_med_emp + ) ) - ) + } } } } \ No newline at end of file diff --git a/room-kit/src/main/java/live/hms/roomkit/ui/meeting/chat/combined/CombinedChatFragmentTab.kt b/room-kit/src/main/java/live/hms/roomkit/ui/meeting/chat/combined/CombinedChatFragmentTab.kt index 2355caeea..449f6b484 100644 --- a/room-kit/src/main/java/live/hms/roomkit/ui/meeting/chat/combined/CombinedChatFragmentTab.kt +++ b/room-kit/src/main/java/live/hms/roomkit/ui/meeting/chat/combined/CombinedChatFragmentTab.kt @@ -26,7 +26,7 @@ class CombinedChatFragmentTab(val dismissAllowingStateLoss: KFunction0) : private val launchMessageOptionsDialog = LaunchMessageOptionsDialog() private val chatAdapter by lazy { ChatAdapter({ message -> launchMessageOptionsDialog.launch(meetingViewModel, - childFragmentManager, message) },{}, { MessageOptionsBottomSheet.showMessageOptions(meetingViewModel)}) + childFragmentManager, message) },{}, { message -> MessageOptionsBottomSheet.showMessageOptions(meetingViewModel, message)}) } private val pinnedMessageUiUseCase = PinnedMessageUiUseCase() @@ -53,18 +53,11 @@ class CombinedChatFragmentTab(val dismissAllowingStateLoss: KFunction0) : } - var roleChangeSingleShot = -1 override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - roleChangeSingleShot = meetingViewModel.roleChangeSingleShot.value ?:0 + binding.sendToBackground.setOnSingleClickListener { RoleBasedChatBottomSheet.launch(childFragmentManager, chatViewModel) } - meetingViewModel.roleChangeSingleShot.observe(viewLifecycleOwner) { - if(roleChangeSingleShot != it) { - roleChangeSingleShot = it - dismissAllowingStateLoss() - } - } meetingViewModel.initPrebuiltChatMessageRecipient.observe(viewLifecycleOwner) { chatViewModel.setInitialRecipient(it.first, it.second) } @@ -98,7 +91,8 @@ class CombinedChatFragmentTab(val dismissAllowingStateLoss: KFunction0) : binding.chatExtra, meetingViewModel.prebuiltInfoContainer::isChatEnabled, meetingViewModel::availableRecipientsForChat, - chatViewModel::currentlySelectedRbacRecipient + chatViewModel::currentlySelectedRbacRecipient, + chatViewModel.currentlySelectedRecipientRbac ) meetingViewModel.broadcastsReceived.observe(viewLifecycleOwner) { chatViewModel.receivedMessage(it) @@ -107,6 +101,9 @@ class CombinedChatFragmentTab(val dismissAllowingStateLoss: KFunction0) : pinnedMessageUiUseCase.messagesUpdate(pinnedMessages, binding.pinnedMessagesDisplay) } + meetingViewModel.peerLeaveUpdate.observe(viewLifecycleOwner) { + chatViewModel.updatePeerLeave(it) + } } } diff --git a/room-kit/src/main/java/live/hms/roomkit/ui/meeting/chat/combined/LaunchMessageOptionsDialog.kt b/room-kit/src/main/java/live/hms/roomkit/ui/meeting/chat/combined/LaunchMessageOptionsDialog.kt index 797ce0047..8a02ad694 100644 --- a/room-kit/src/main/java/live/hms/roomkit/ui/meeting/chat/combined/LaunchMessageOptionsDialog.kt +++ b/room-kit/src/main/java/live/hms/roomkit/ui/meeting/chat/combined/LaunchMessageOptionsDialog.kt @@ -14,10 +14,10 @@ class LaunchMessageOptionsDialog { chatMessage: ChatMessage, ) { // If the user can't block or pin message, hide the entire dialog. - val allowedToBlock = meetingViewModel.isAllowedToBlockFromChat() + val allowedToBlock = meetingViewModel.isAllowedToBlockFromChat() && !chatMessage.isSentByMe val allowedToPin = meetingViewModel.isAllowedToPinMessages() val allowedToHideMessages = meetingViewModel.isAllowedToHideMessages() - if(!MessageOptionsBottomSheet.showMessageOptions(meetingViewModel)) + if(!MessageOptionsBottomSheet.showMessageOptions(meetingViewModel, chatMessage)) return MessageOptionsBottomSheet(chatMessage, allowedToBlock, allowedToPin, allowedToHideMessages).apply { diff --git a/room-kit/src/main/java/live/hms/roomkit/ui/meeting/chat/rbac/ChatMessageViewFilterHelper.kt b/room-kit/src/main/java/live/hms/roomkit/ui/meeting/chat/rbac/ChatMessageViewFilterHelper.kt deleted file mode 100644 index 6114dfecc..000000000 --- a/room-kit/src/main/java/live/hms/roomkit/ui/meeting/chat/rbac/ChatMessageViewFilterHelper.kt +++ /dev/null @@ -1,37 +0,0 @@ -package live.hms.roomkit.ui.meeting.chat.rbac - -import live.hms.roomkit.ui.meeting.chat.ChatMessage -import live.hms.roomkit.ui.meeting.chat.Recipient - -/** - * This filter is the one which selects which peer's messages you see - * When you select a DM vs Everyone. - * Or a role vs Everyone. - * If Everyone is selected that means there's no filtering. - */ -class ChatMessageViewFilterHelper { - private var filterRecipient : Recipient? = null -// private var filterGroup : String? = null - fun setFilter(recipient: Recipient?) { - filterRecipient = recipient - } - private fun isSearching() = filterRecipient == Recipient.Everyone - fun getSearchFilteredPeersIfNeeded(m : List) : List { - - return when(val filterTargetRecipient = filterRecipient) { - Recipient.Everyone -> m // no change - // Always include your own messages - is Recipient.Peer -> m.filter { - it.senderPeerId == filterTargetRecipient.peer.peerID || it.isSentByMe - } - // Always include your own messages - is Recipient.Role -> m.filter { - it.senderRoleName == filterTargetRecipient.role.name || it.isSentByMe - } - // Just receive messages even if you can't sent - null -> m - } - - } - -} \ No newline at end of file diff --git a/room-kit/src/main/java/live/hms/roomkit/ui/meeting/chat/rbac/RecipientHeader.kt b/room-kit/src/main/java/live/hms/roomkit/ui/meeting/chat/rbac/RecipientHeader.kt index 7f67d2104..c71f25463 100644 --- a/room-kit/src/main/java/live/hms/roomkit/ui/meeting/chat/rbac/RecipientHeader.kt +++ b/room-kit/src/main/java/live/hms/roomkit/ui/meeting/chat/rbac/RecipientHeader.kt @@ -1,6 +1,7 @@ package live.hms.roomkit.ui.meeting.chat.rbac import android.view.View +import androidx.appcompat.content.res.AppCompatResources import com.xwray.groupie.ExpandableGroup import com.xwray.groupie.ExpandableItem import com.xwray.groupie.viewbinding.BindableItem @@ -8,8 +9,11 @@ import live.hms.roomkit.R import live.hms.roomkit.databinding.LayoutRoleBasedChatMessageBottomSheetItemHeaderBinding import live.hms.roomkit.setOnSingleClickListener import live.hms.roomkit.ui.meeting.chat.Recipient +import live.hms.roomkit.ui.theme.HMSPrebuiltTheme import live.hms.roomkit.ui.theme.applyTheme - +import live.hms.roomkit.ui.theme.getColorOrDefault +const val RECIPIENT_PEERS = "PARTICIPANTS" +const val RECIPIENT_ROLES = "ROLES" class RecipientHeader(private val recipientHeaderName: String) : BindableItem( recipientHeaderName.hashCode().toLong() @@ -22,6 +26,23 @@ class RecipientHeader(private val recipientHeaderName: String) : with(viewBinding) { applyTheme() name.text = recipientHeaderName + val headerIcon = when(recipientHeaderName) { + // everyone is just a recipient item not a header + RECIPIENT_ROLES -> R.drawable.role_rbac_icon + RECIPIENT_PEERS -> R.drawable.dm_rbac_icon + else -> R.drawable.left_arrow + } + image.setImageDrawable( + AppCompatResources.getDrawable( + viewBinding.root.context, headerIcon + )?.apply { + setTint( + getColorOrDefault( + HMSPrebuiltTheme.getColours()?.onSurfaceMedium, + HMSPrebuiltTheme.getDefaults().onsurface_med_emp + ) + ) + }) // Don't expand/collapse right now // root.setOnSingleClickListener { // expandableGroup.onToggleExpanded() diff --git a/room-kit/src/main/java/live/hms/roomkit/ui/meeting/chat/rbac/RecipientItem.kt b/room-kit/src/main/java/live/hms/roomkit/ui/meeting/chat/rbac/RecipientItem.kt index f97ca1970..2c3bb3247 100644 --- a/room-kit/src/main/java/live/hms/roomkit/ui/meeting/chat/rbac/RecipientItem.kt +++ b/room-kit/src/main/java/live/hms/roomkit/ui/meeting/chat/rbac/RecipientItem.kt @@ -1,12 +1,15 @@ package live.hms.roomkit.ui.meeting.chat.rbac import android.view.View +import androidx.appcompat.content.res.AppCompatResources import com.xwray.groupie.viewbinding.BindableItem import live.hms.roomkit.R import live.hms.roomkit.databinding.LayoutRoleBasedChatMessageBottomSheetItemRecipientBinding import live.hms.roomkit.setOnSingleClickListener import live.hms.roomkit.ui.meeting.chat.Recipient +import live.hms.roomkit.ui.theme.HMSPrebuiltTheme import live.hms.roomkit.ui.theme.applyTheme +import live.hms.roomkit.ui.theme.getColorOrDefault class RecipientItem(private val recipient: Recipient, private val currentSelectedRecipient: Recipient?, diff --git a/room-kit/src/main/java/live/hms/roomkit/ui/meeting/chat/rbac/RoleBasedChatBottomSheet.kt b/room-kit/src/main/java/live/hms/roomkit/ui/meeting/chat/rbac/RoleBasedChatBottomSheet.kt index e7d5f56c2..b71170f74 100644 --- a/room-kit/src/main/java/live/hms/roomkit/ui/meeting/chat/rbac/RoleBasedChatBottomSheet.kt +++ b/room-kit/src/main/java/live/hms/roomkit/ui/meeting/chat/rbac/RoleBasedChatBottomSheet.kt @@ -7,6 +7,7 @@ import android.view.View import android.view.ViewGroup import androidx.fragment.app.FragmentManager import androidx.fragment.app.activityViewModels +import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.LinearLayoutManager import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetDialog @@ -14,6 +15,7 @@ import com.google.android.material.bottomsheet.BottomSheetDialogFragment import com.xwray.groupie.ExpandableGroup import com.xwray.groupie.Group import com.xwray.groupie.GroupieAdapter +import kotlinx.coroutines.launch import live.hms.roomkit.R import live.hms.roomkit.databinding.LayoutRoleBasedChatBottomSheetSelectorBinding import live.hms.roomkit.ui.meeting.AllowedToMessageParticipants @@ -21,18 +23,32 @@ import live.hms.roomkit.ui.meeting.MeetingViewModel import live.hms.roomkit.ui.meeting.MeetingViewModelFactory import live.hms.roomkit.ui.meeting.chat.ChatViewModel import live.hms.roomkit.ui.meeting.chat.Recipient +import live.hms.roomkit.ui.meeting.participants.ChatRecipientSearchUseCase import live.hms.roomkit.ui.meeting.participants.MessageHeaderItemDecoration import live.hms.roomkit.ui.theme.HMSPrebuiltTheme import live.hms.roomkit.ui.theme.applyTheme import live.hms.roomkit.ui.theme.getColorOrDefault import live.hms.roomkit.util.viewLifecycle import live.hms.video.sdk.HMSSDK +import live.hms.video.signal.init.HMSRoomLayout +import live.hms.video.sdk.models.HMSPeer +/** + * The chip that lets you select who to chat with opens this. + * This determines what options to show based on what the role is allowed in [HMSRoomLayout]. + * @param getSelectedRecipient the [ChatViewModel] keeps the state and knows what recipient + * is selected, so this fragment needs to load that info from it. + * @param recipientSelected if the dialog changes what recipient is selected this communicates + * it back to [ChatViewModel]. + */ class RoleBasedChatBottomSheet( private val getSelectedRecipient: () -> Recipient?, private val recipientSelected: (Recipient) -> Unit ) : BottomSheetDialogFragment() { + private var initialRecipients : List = emptyList() + private var allowedParticipants : AllowedToMessageParticipants? = null + private val chatRecipientSearchUseCase : ChatRecipientSearchUseCase = ChatRecipientSearchUseCase(::updateListWithPeers) private var binding by viewLifecycle() private val groupieAdapter = GroupieAdapter() @@ -80,6 +96,7 @@ class RoleBasedChatBottomSheet( dismissAllowingStateLoss() } + chatRecipientSearchUseCase.initSearchView(binding.textInputSearch, lifecycleScope) with(binding.optionsGrid) { adapter = groupieAdapter layoutManager = LinearLayoutManager(context) @@ -94,25 +111,79 @@ class RoleBasedChatBottomSheet( ) } + // This would break many things if it were called when no participants were available. // crash early to point it out. - val allowedParticipants = meetingViewModel.availableRecipientsForChat()!! - val initialRecipients = initialAddRecipients(allowedParticipants) + updateInitialRecipients() groupieAdapter.update(initialRecipients) - if (allowedParticipants.peers) { - // Update all peers everytime the peers change - meetingViewModel.participantPeerUpdate.observe(viewLifecycleOwner) { - groupieAdapter.update( - initialRecipients.plus( - getUpdatedPeersGroup( - meetingViewModel.hmsSDK, getSelectedRecipient() - ) - ) - ) + + // There's no need for role change to emit the first time this runs. + meetingViewModel.roleChange.observe(viewLifecycleOwner) { + // When the role changes, the allowed participants might have changed. + lifecycleScope.launch { + updateInitialRecipients() + updateListWithPeers() + } + } + // Update all peers everytime the peers change + meetingViewModel.participantPeerUpdate.observe(viewLifecycleOwner) { + lifecycleScope.launch { + updateListWithPeers() } } + } + private suspend fun updateListWithPeers() { + val list = if(chatRecipientSearchUseCase.isSearching()) { + val filteredPeers = chatRecipientSearchUseCase + .getFilteredPeers(meetingViewModel.hmsSDK.getRemotePeers()) + listOf(getUpdatedPeersGroup( + filteredPeers, getSelectedRecipient() + )) + } else { + val peers = getPeerGroup() + if(peers == null) { + initialRecipients + } else { + initialRecipients.plus(peers) + } + } + + groupieAdapter.update(list) + // Toggle empty view + binding.emptyView.visibility = if (list.isEmpty()) + View.VISIBLE + else + View.GONE + + } + + private fun updateInitialRecipients() { + allowedParticipants = meetingViewModel.availableRecipientsForChat() + initialRecipients = initialAddRecipients(getAllowedParticipants()) + chatRecipientSearchUseCase.setSearchVisibility(binding.containerSearch, getAllowedParticipants()) + } + + private fun getAllowedParticipants() = allowedParticipants!! + private fun getInitialRecipients() = initialRecipients + + private fun getPeerGroup(): ExpandableGroup? { + return if (getAllowedParticipants().peers) { + val peers = meetingViewModel.hmsSDK.getRemotePeers() + // Remove the "participants" option if there are no others. + if (peers.isEmpty()) + null + else + getUpdatedPeersGroup( + peers, getSelectedRecipient() + ) + } else { + null + } + } + + private fun onRecipientSelected(recipient: Recipient) { recipientSelected(recipient) dismissAllowingStateLoss() @@ -147,7 +218,7 @@ class RoleBasedChatBottomSheet( ) } // Separate headers and roles - val rolesGroup = ExpandableGroup(RecipientHeader("ROLES"), true).apply { + val rolesGroup = ExpandableGroup(RecipientHeader(RECIPIENT_ROLES), true).apply { addAll(rolesToAdd) } recipients.add(rolesGroup) @@ -157,12 +228,12 @@ class RoleBasedChatBottomSheet( } private fun getUpdatedPeersGroup( - hmsSDK: HMSSDK, + peers : List, currentSelectedRecipient: Recipient? ): ExpandableGroup { - return ExpandableGroup(RecipientHeader("PARTICIPANTS"), true).apply { + return ExpandableGroup(RecipientHeader(RECIPIENT_PEERS), true).apply { addAll( - hmsSDK.getRemotePeers().map { + peers.map { RecipientItem( Recipient.Peer(it), currentSelectedRecipient, diff --git a/room-kit/src/main/java/live/hms/roomkit/ui/meeting/participants/ChatRecipientSearchUseCase.kt b/room-kit/src/main/java/live/hms/roomkit/ui/meeting/participants/ChatRecipientSearchUseCase.kt new file mode 100644 index 000000000..c456950d9 --- /dev/null +++ b/room-kit/src/main/java/live/hms/roomkit/ui/meeting/participants/ChatRecipientSearchUseCase.kt @@ -0,0 +1,35 @@ +package live.hms.roomkit.ui.meeting.participants + +import android.view.View +import android.widget.EditText +import androidx.core.widget.addTextChangedListener +import androidx.lifecycle.LifecycleCoroutineScope +import com.google.android.material.card.MaterialCardView +import com.google.android.material.textfield.TextInputEditText +import kotlinx.coroutines.launch +import live.hms.roomkit.ui.meeting.AllowedToMessageParticipants +import live.hms.video.sdk.models.HMSPeer + +class ChatRecipientSearchUseCase(private val updateList : suspend () -> Unit) { + private var filterText : String? = null + fun isSearching() = !filterText.isNullOrEmpty() + fun setSearchVisibility(containerSearch : MaterialCardView, + allowedToMessageParticipants: AllowedToMessageParticipants) { + containerSearch.visibility = if(allowedToMessageParticipants.peers) View.VISIBLE else View.GONE + } + fun initSearchView(textInputSearch : EditText, scope : LifecycleCoroutineScope) { + textInputSearch.apply { + addTextChangedListener { text -> + scope.launch { + filterText = text.toString() + updateList() + } + } + } + } + + fun getFilteredPeers(remotePeers : List) : List { + val filterText = filterText ?: return remotePeers + return remotePeers.filter { it.name.contains(filterText, ignoreCase = true) } + } +} \ No newline at end of file diff --git a/room-kit/src/main/java/live/hms/roomkit/ui/meeting/participants/ParticipantsTabFragment.kt b/room-kit/src/main/java/live/hms/roomkit/ui/meeting/participants/ParticipantsTabFragment.kt index f4372ccbb..24b339033 100644 --- a/room-kit/src/main/java/live/hms/roomkit/ui/meeting/participants/ParticipantsTabFragment.kt +++ b/room-kit/src/main/java/live/hms/roomkit/ui/meeting/participants/ParticipantsTabFragment.kt @@ -16,6 +16,7 @@ import live.hms.roomkit.databinding.LayoutParticipantsMergeBinding import live.hms.roomkit.ui.meeting.MeetingState import live.hms.roomkit.ui.meeting.MeetingViewModel import live.hms.roomkit.ui.meeting.MeetingViewModelFactory +import live.hms.roomkit.ui.theme.applyTheme import live.hms.roomkit.util.viewLifecycle class ParticipantsTabFragment(val dismissFragment: () -> Unit) : Fragment() { @@ -45,7 +46,7 @@ class ParticipantsTabFragment(val dismissFragment: () -> Unit) : Fragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) -// binding.applyTheme() + binding.applyTheme() initViewModels() initOnBackPress() initViews() diff --git a/room-kit/src/main/java/live/hms/roomkit/ui/meeting/participants/ParticipantsUseCase.kt b/room-kit/src/main/java/live/hms/roomkit/ui/meeting/participants/ParticipantsUseCase.kt index e7253651e..723dcee16 100644 --- a/room-kit/src/main/java/live/hms/roomkit/ui/meeting/participants/ParticipantsUseCase.kt +++ b/room-kit/src/main/java/live/hms/roomkit/ui/meeting/participants/ParticipantsUseCase.kt @@ -1,5 +1,6 @@ package live.hms.roomkit.ui.meeting.participants +import android.widget.EditText import androidx.core.content.res.ResourcesCompat import androidx.core.widget.addTextChangedListener import androidx.lifecycle.LifecycleCoroutineScope @@ -108,7 +109,7 @@ class ParticipantsUseCase(val meetingViewModel: MeetingViewModel, } } - fun initSearchView(textInputSearch : TextInputEditText, scope : LifecycleCoroutineScope) { + fun initSearchView(textInputSearch : EditText, scope : LifecycleCoroutineScope) { textInputSearch.apply { addTextChangedListener { text -> scope.launch { diff --git a/room-kit/src/main/java/live/hms/roomkit/ui/polls/PollQuestionViewHolder.kt b/room-kit/src/main/java/live/hms/roomkit/ui/polls/PollQuestionViewHolder.kt index b86fadf25..2a873fc3a 100644 --- a/room-kit/src/main/java/live/hms/roomkit/ui/polls/PollQuestionViewHolder.kt +++ b/room-kit/src/main/java/live/hms/roomkit/ui/polls/PollQuestionViewHolder.kt @@ -2,6 +2,7 @@ package live.hms.roomkit.ui.polls import android.text.Editable import android.text.TextWatcher +import android.util.Log import android.view.View import android.widget.AdapterView import android.widget.AdapterView.OnItemSelectedListener @@ -123,11 +124,19 @@ class PollQuestionViewHolder( it.onOptionTextChanged = { optionIndex, changedText -> val question = getItem(bindingAdapterPosition).currentQuestion as QuestionUi.ChoiceQuestions - val changedList: List = question.options.toMutableList().apply { - this[optionIndex] = changedText + val options = question.options + if(optionIndex >= options.size) + Log.e(TAG,"Skip invalid index change") + else { + val changedList: List = options.toMutableList().apply { + this[optionIndex] = changedText + } + question.options = changedList + validateSaveButtonEnabledState( + getItem(bindingAdapterPosition), + binding.saveButton + ) } - question.options = changedList - validateSaveButtonEnabledState(getItem(bindingAdapterPosition), binding.saveButton) } it.onSingleOptionSelected = { position -> updateSelection(bindingAdapterPosition, listOf(position), null) diff --git a/room-kit/src/main/java/live/hms/roomkit/ui/polls/PollsCreationFragment.kt b/room-kit/src/main/java/live/hms/roomkit/ui/polls/PollsCreationFragment.kt index 8a44b401c..767e1df30 100644 --- a/room-kit/src/main/java/live/hms/roomkit/ui/polls/PollsCreationFragment.kt +++ b/room-kit/src/main/java/live/hms/roomkit/ui/polls/PollsCreationFragment.kt @@ -16,6 +16,7 @@ import live.hms.roomkit.databinding.LayoutPollsCreationBinding import live.hms.roomkit.ui.meeting.MeetingState import live.hms.roomkit.ui.meeting.MeetingViewModel import live.hms.roomkit.ui.polls.display.PollDisplayFragment +import live.hms.roomkit.ui.polls.leaderboard.LeaderBoardBottomSheetFragment import live.hms.roomkit.ui.polls.previous.PreviousPollsAdaptor import live.hms.roomkit.ui.polls.previous.PreviousPollsInfo import live.hms.roomkit.ui.theme.applyTheme diff --git a/room-kit/src/main/java/live/hms/roomkit/ui/polls/display/PollDisplayFragment.kt b/room-kit/src/main/java/live/hms/roomkit/ui/polls/display/PollDisplayFragment.kt index 16681d5ad..8a33e9553 100644 --- a/room-kit/src/main/java/live/hms/roomkit/ui/polls/display/PollDisplayFragment.kt +++ b/room-kit/src/main/java/live/hms/roomkit/ui/polls/display/PollDisplayFragment.kt @@ -18,6 +18,7 @@ import kotlinx.coroutines.launch import live.hms.roomkit.R import live.hms.roomkit.databinding.LayoutPollsDisplayBinding import live.hms.roomkit.ui.meeting.MeetingViewModel +import live.hms.roomkit.ui.polls.leaderboard.LeaderBoardBottomSheetFragment import live.hms.roomkit.ui.theme.applyTheme import live.hms.roomkit.ui.theme.pollsStatusLiveDraftEnded import live.hms.roomkit.util.setOnSingleClickListener @@ -84,7 +85,7 @@ class PollDisplayFragment : BottomSheetDialogFragment() { meetingViewModel::saveInfoMultiChoice, meetingViewModel::saveSkipped, meetingViewModel::endPoll - ) + ) { LeaderBoardBottomSheetFragment.launch(it, requireFragmentManager()) } poll = returnedPoll diff --git a/room-kit/src/main/java/live/hms/roomkit/ui/polls/display/PollDisplayQuestionHolder.kt b/room-kit/src/main/java/live/hms/roomkit/ui/polls/display/PollDisplayQuestionHolder.kt index 1d3b9da39..c0ddcd06c 100644 --- a/room-kit/src/main/java/live/hms/roomkit/ui/polls/display/PollDisplayQuestionHolder.kt +++ b/room-kit/src/main/java/live/hms/roomkit/ui/polls/display/PollDisplayQuestionHolder.kt @@ -7,9 +7,7 @@ import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import androidx.viewbinding.ViewBinding import live.hms.roomkit.R -import live.hms.roomkit.databinding.GoLiveBottomSheetBinding import live.hms.roomkit.databinding.LayoutEndPollButtonBinding -import live.hms.roomkit.databinding.LayoutLaunchPollButtonBinding import live.hms.roomkit.databinding.LayoutPollsDisplayChoicesQuesionBinding import live.hms.roomkit.databinding.LayoutQuizDisplayShortAnswerBinding import live.hms.roomkit.drawableStart @@ -38,6 +36,7 @@ class PollDisplayQuestionHolder( val skipped : (question : HMSPollQuestion, poll : HmsPoll) -> Unit, val endPoll : (HmsPoll) -> Unit, val canEndPoll : Boolean, + val showLeaderBoard: (pollId: String) -> Unit, ) : RecyclerView.ViewHolder(binding.root) { private val adapter = AnswerOptionsAdapter(canRoleViewVotes) { answersSelected -> @@ -65,8 +64,9 @@ class PollDisplayQuestionHolder( } } else { + //todd with(binding as LayoutEndPollButtonBinding) { - launchPollQuiz.text = "End Poll" + launchPollQuiz.text = if (poll.category == HmsPollCategory.QUIZ) "End Quiz" else "End Poll" if(poll.state == HmsPollState.STARTED && canEndPoll) { launchPollQuiz.alertButtonEnabled() launchPollQuiz.setOnClickListener { @@ -76,6 +76,16 @@ class PollDisplayQuestionHolder( } else { binding.root.visibility = View.GONE } + + if (poll.state == HmsPollState.STOPPED && poll.category == HmsPollCategory.QUIZ) { + binding.root.visibility = View.VISIBLE + launchPollQuiz.text = "View Results" + launchPollQuiz.buttonEnabled() + launchPollQuiz.setOnClickListener { + showLeaderBoard(poll.pollId) + } + binding.root.visibility = View.VISIBLE + } } } } diff --git a/room-kit/src/main/java/live/hms/roomkit/ui/polls/display/PollsDisplayAdaptor.kt b/room-kit/src/main/java/live/hms/roomkit/ui/polls/display/PollsDisplayAdaptor.kt index 1897e3c82..02a5f7c6f 100644 --- a/room-kit/src/main/java/live/hms/roomkit/ui/polls/display/PollsDisplayAdaptor.kt +++ b/room-kit/src/main/java/live/hms/roomkit/ui/polls/display/PollsDisplayAdaptor.kt @@ -2,12 +2,10 @@ package live.hms.roomkit.ui.polls.display import android.view.LayoutInflater import android.view.ViewGroup -import androidx.core.graphics.drawable.DrawableCompat.applyTheme import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.ListAdapter import androidx.viewbinding.ViewBinding import live.hms.roomkit.databinding.LayoutEndPollButtonBinding -import live.hms.roomkit.databinding.LayoutLaunchPollButtonBinding import live.hms.roomkit.databinding.LayoutPollsDisplayChoicesQuesionBinding import live.hms.roomkit.databinding.LayoutQuizDisplayShortAnswerBinding import live.hms.roomkit.ui.theme.applyTheme @@ -52,6 +50,7 @@ class PollsDisplayAdaptor( val saveInfoMultiChoice : (question : HMSPollQuestion, List?, hmsPoll : HmsPoll) -> Boolean, val skipped : (question : HMSPollQuestion, poll : HmsPoll) -> Unit, val endPoll : (HmsPoll) -> Unit, + val showLeaderBoard : (pollId : String) -> Unit, ) : ListAdapter>( DIFFUTIL_CALLBACK ) { @@ -103,7 +102,7 @@ class PollsDisplayAdaptor( EndPollViewType -> LayoutEndPollButtonBinding.inflate(LayoutInflater.from(parent.context), parent, false) else -> null } - val questionHolder = PollDisplayQuestionHolder(view!!, canViewResponses(getPoll, localPeer), getPoll, ::setTextAnswer, saveInfoSingleChoice, saveInfoMultiChoice, skipped, endPoll, getPoll.createdBy?.peerID == localPeer.peerID) + val questionHolder = PollDisplayQuestionHolder(view!!, canViewResponses(getPoll, localPeer), getPoll, ::setTextAnswer, saveInfoSingleChoice, saveInfoMultiChoice, skipped, endPoll, getPoll.createdBy?.peerID == localPeer.peerID, showLeaderBoard) if(viewType == HMSPollQuestionType.multiChoice.ordinal || viewType == HMSPollQuestionType.singleChoice.ordinal) { updater.add(questionHolder) } diff --git a/room-kit/src/main/java/live/hms/roomkit/ui/polls/leaderboard/LeaderBoardBottomSheetFragment.kt b/room-kit/src/main/java/live/hms/roomkit/ui/polls/leaderboard/LeaderBoardBottomSheetFragment.kt new file mode 100644 index 000000000..62d5350ef --- /dev/null +++ b/room-kit/src/main/java/live/hms/roomkit/ui/polls/leaderboard/LeaderBoardBottomSheetFragment.kt @@ -0,0 +1,209 @@ +package live.hms.roomkit.ui.polls.leaderboard + +import android.app.Dialog +import android.content.res.Resources +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.Toast +import androidx.fragment.app.FragmentManager +import androidx.fragment.app.activityViewModels +import androidx.lifecycle.lifecycleScope +import androidx.recyclerview.widget.GridLayoutManager +import com.google.android.material.bottomsheet.BottomSheetBehavior +import com.google.android.material.bottomsheet.BottomSheetDialog +import com.google.android.material.bottomsheet.BottomSheetDialogFragment +import com.xwray.groupie.GroupieAdapter +import kotlinx.coroutines.launch +import live.hms.roomkit.R +import live.hms.roomkit.databinding.LayoutChatParticipantCombinedBinding +import live.hms.roomkit.databinding.LayoutQuizLeaderboardBinding +import live.hms.roomkit.ui.meeting.InsetItemDecoration +import live.hms.roomkit.ui.meeting.MeetingViewModel +import live.hms.roomkit.ui.polls.display.POLL_TO_DISPLAY +import live.hms.roomkit.ui.polls.display.PollDisplayFragment +import live.hms.roomkit.ui.polls.leaderboard.item.ApplyRadiusatVertex +import live.hms.roomkit.ui.polls.leaderboard.item.LeaderBoardHeader +import live.hms.roomkit.ui.polls.leaderboard.item.LeaderBoardNameSection +import live.hms.roomkit.ui.polls.leaderboard.item.LeaderBoardSubGrid +import live.hms.roomkit.ui.theme.HMSPrebuiltTheme +import live.hms.roomkit.ui.theme.applyTheme +import live.hms.roomkit.ui.theme.getColorOrDefault +import live.hms.roomkit.util.contextSafe +import live.hms.roomkit.util.viewLifecycle +import live.hms.video.error.HMSException +import live.hms.video.polls.models.HmsPoll +import live.hms.video.polls.network.PollLeaderboardResponse +import live.hms.video.sdk.HmsTypedActionResultListener + + +class LeaderBoardBottomSheetFragment : BottomSheetDialogFragment() { + + private var binding by viewLifecycle() + private val meetingViewModel: MeetingViewModel by activityViewModels() + var poll: HmsPoll? = null + + val leaderBoardListadapter = GroupieAdapter() + + + companion object { + const val TAG: String = "LeaderBoardBottomSheetFragment" + fun launch(pollId: String, fm: FragmentManager) { + val args = Bundle().apply { + putString(POLL_TO_DISPLAY, pollId) + } + + LeaderBoardBottomSheetFragment().apply { arguments = args }.show( + fm, PollDisplayFragment.TAG + ) + } + } + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? + ): View { + binding = LayoutQuizLeaderboardBinding.inflate(inflater, container, false) + .also { it.applyTheme() } + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + dialog?.let { + val sheet = it as BottomSheetDialog + sheet.behavior.skipCollapsed = true + sheet.behavior.peekHeight = Resources.getSystem().displayMetrics.heightPixels + sheet.behavior.state = BottomSheetBehavior.STATE_EXPANDED + } + + + lifecycleScope.launch { + val pollId: String = + (arguments?.getString(POLL_TO_DISPLAY) ?: dismissAllowingStateLoss()).toString() + poll = meetingViewModel.getPollForPollId(pollId) + leaderBoardListadapter.spanCount = 12 + + binding.backButton.setOnClickListener { dismissAllowingStateLoss() } + binding.closeBtn.setOnClickListener { dismissAllowingStateLoss() } + binding.leaderboardRecyclerView.apply { + adapter = leaderBoardListadapter + layoutManager = GridLayoutManager(context, leaderBoardListadapter.spanCount).apply { + spanSizeLookup = leaderBoardListadapter.spanSizeLookup + } + addItemDecoration( + InsetItemDecoration( + getColorOrDefault( + HMSPrebuiltTheme.getColours()?.backgroundDefault, + HMSPrebuiltTheme.getDefaults().background_default + ), resources.getDimension(R.dimen.twelve_dp).toInt(), "inset", "inset" + ) + ) + + } + + meetingViewModel.fetchLeaderboard(pollId, + object : HmsTypedActionResultListener { + override fun onSuccess(result: PollLeaderboardResponse) { + contextSafe { context, activity -> + activity.runOnUiThread { + binding.heading.text = poll?.title + loadList(result) + } + } + } + + override fun onError(error: HMSException) { + contextSafe { _, activity -> + Toast.makeText( + activity, + "Error fetching leaderboard ${error.localizedMessage}", + Toast.LENGTH_SHORT + ).show() + dismissAllowingStateLoss() + } + } + + }) + } + } + + fun loadList(model: PollLeaderboardResponse) { + leaderBoardListadapter.clear() + + val isAverageTimeEmpty = + model.summary?.averageTime == null + val isAverageScoreEmpty = + model.summary?.averageScore == null + val isCorrectAnswerEmpty = + model.summary?.respondedCorrectlyPeersCount == null + val isTotalPeerCountEmpty = + model.summary?.totalPeersCount == null || model.summary?.totalPeersCount == 0 + + + if (isAverageScoreEmpty.not() || isAverageTimeEmpty.not() || isCorrectAnswerEmpty.not() || isTotalPeerCountEmpty.not()) { + leaderBoardListadapter.add(LeaderBoardHeader("Participation Summary")) + } + + if (model.summary != null) with(model.summary!!) { + if (isTotalPeerCountEmpty.not()) { + leaderBoardListadapter.add( + LeaderBoardSubGrid( + "VOTED", + "${(((respondedPeersCount?.toFloat()?:1f)/(totalPeersCount?.toFloat()?:1f)) * 100.0f).toInt()}% (${(respondedPeersCount ?: 0)}/${(totalPeersCount ?: 0)})" + ) + ) + } + + if (isCorrectAnswerEmpty.not()) { + leaderBoardListadapter.add( + LeaderBoardSubGrid( + "CORRECT ANSWERS", "${(((respondedCorrectlyPeersCount?.toFloat()?:1f)/(totalPeersCount?.toFloat()?:1f)) * 100.0f).toInt()}% (${(respondedCorrectlyPeersCount ?: 0)}/${(totalPeersCount ?: 0)})" + ) + ) + } + if (isAverageTimeEmpty.not()) { + leaderBoardListadapter.add( + LeaderBoardSubGrid( + "AVG. TIME TAKEN", "${averageTime?.toInt().toString()} sec" + ) + ) + } + + if (isAverageScoreEmpty.not()) { + leaderBoardListadapter.add( + LeaderBoardSubGrid( + "AVG. SCORE", averageScore.toString() + ) + ) + } + } + + + if (model.entries.isNullOrEmpty().not()) { + leaderBoardListadapter.add(LeaderBoardHeader("Leaderboard")) + + val rankTOColorMap = mapOf( + "1" to "#D69516", "2" to "#3E3E3E", "3" to "#583B0F" + ) + model.entries?.forEachIndexed { index, entry -> + leaderBoardListadapter.add( + LeaderBoardNameSection( + titleStr = entry.peer?.username.orEmpty(), + subtitleStr = "${entry.score}/${poll?.questions?.map { it.weight }?.toList()?.sum()?:0} points", + rankStr = entry.position.toString(), + isSelected = true, + timetakenStr = "${if (entry.duration == 0L) "" else entry.duration}", + correctAnswerStr = "${entry.correctResponses}/${poll?.questions?.size ?: 0}", + position = if (index == 0) ApplyRadiusatVertex.TOP + else if (index == model.entries?.size?.minus(1)) ApplyRadiusatVertex.BOTTOM + else ApplyRadiusatVertex.NONE, + rankBackGroundColor = rankTOColorMap.getOrDefault(entry.position.toString(), HMSPrebuiltTheme.getColours()?.secondaryDefault) + ) + ) + } + } + + } +} \ No newline at end of file diff --git a/room-kit/src/main/java/live/hms/roomkit/ui/polls/leaderboard/item/LeaderBoardHeader.kt b/room-kit/src/main/java/live/hms/roomkit/ui/polls/leaderboard/item/LeaderBoardHeader.kt new file mode 100644 index 000000000..c54a40c22 --- /dev/null +++ b/room-kit/src/main/java/live/hms/roomkit/ui/polls/leaderboard/item/LeaderBoardHeader.kt @@ -0,0 +1,34 @@ +package live.hms.roomkit.ui.polls.leaderboard.item + +import android.view.View +import com.xwray.groupie.ExpandableGroup +import com.xwray.groupie.ExpandableItem +import com.xwray.groupie.viewbinding.BindableItem +import live.hms.roomkit.R +import live.hms.roomkit.databinding.LayoutHeaderBinding +import live.hms.roomkit.databinding.LayoutRoleBasedChatMessageBottomSheetItemHeaderBinding +import live.hms.roomkit.ui.theme.applyTheme + +class LeaderBoardHeader(private val titlestr: String, private val subtitle: String = "") : + BindableItem() { + + override fun bind( + viewBinding: LayoutHeaderBinding, position: Int + ) { + with(viewBinding) { + this.title.text = titlestr + + if (subtitle.isEmpty()) this.subheading.visibility = View.GONE + else this.subheading.visibility = View.VISIBLE + this.subheading.text = subtitle + applyTheme() + } + } + + override fun getLayout(): Int = R.layout.layout_header + + + override fun initializeViewBinding(view: View): LayoutHeaderBinding = + LayoutHeaderBinding.bind(view) + +} \ No newline at end of file diff --git a/room-kit/src/main/java/live/hms/roomkit/ui/polls/leaderboard/item/LeaderBoardNameSection.kt b/room-kit/src/main/java/live/hms/roomkit/ui/polls/leaderboard/item/LeaderBoardNameSection.kt new file mode 100644 index 000000000..12cdeae9c --- /dev/null +++ b/room-kit/src/main/java/live/hms/roomkit/ui/polls/leaderboard/item/LeaderBoardNameSection.kt @@ -0,0 +1,104 @@ +package live.hms.roomkit.ui.polls.leaderboard.item + +import android.view.View +import com.xwray.groupie.viewbinding.BindableItem +import live.hms.roomkit.R +import live.hms.roomkit.databinding.ItemNameSectionBinding +import live.hms.roomkit.ui.theme.HMSPrebuiltTheme +import live.hms.roomkit.ui.theme.applyTheme +import live.hms.roomkit.ui.theme.getColorOrDefault +import live.hms.roomkit.ui.theme.getShape +import live.hms.roomkit.ui.theme.setBackgroundAndColor + +enum class ApplyRadiusatVertex { + TOP, + ALL_CORNERS, + BOTTOM, + NONE +} +class LeaderBoardNameSection( + private val titleStr: String, + private val subtitleStr: String, + private val rankStr: String, + private val timetakenStr: String? = null, + private val correctAnswerStr: String? = null, + private val isSelected: Boolean, + private val position: ApplyRadiusatVertex = ApplyRadiusatVertex.NONE, + private val rankBackGroundColor: String? = HMSPrebuiltTheme.getColours()?.secondaryDefault, +) : BindableItem() { + + + override fun bind(viewBinding: ItemNameSectionBinding, position: Int) { + //themes + setSelectedView(isSelected, viewBinding) + + + with(viewBinding) { + applyTheme() + heading.text = titleStr + subtitle.text = subtitleStr + + if ((rankStr == "0").not()) + rank.text = rankStr + else + rank.text = "-" + + if (timetakenStr.isNullOrEmpty().not()) { + timeTaken.visibility = View.VISIBLE + timeTaken.text = "${timetakenStr.toString()}s" + } else { + timeTaken.visibility = View.GONE + } + + if (correctAnswerStr.isNullOrEmpty().not()) { + correctAnswer.visibility = View.VISIBLE + correctAnswer.text = correctAnswerStr.toString() + } else { + correctAnswer.visibility = View.GONE + } + + if (rankStr == "1") + trophyicon.visibility = View.VISIBLE + else + trophyicon.visibility = View.GONE + + rank.setBackgroundAndColor( + rankBackGroundColor, + HMSPrebuiltTheme.getDefaults().secondary_default, + R.drawable.circle_secondary_80 + ) + + + } + } + + private fun setSelectedView(isSelected: Boolean, v: ItemNameSectionBinding) { + v.rootLayout.background = if (isSelected.not()) { + getShape(position).apply { + setTint( + getColorOrDefault( + HMSPrebuiltTheme.getColours()?.backgroundDefault, + HMSPrebuiltTheme.getDefaults().background_default + ) + ) + } + } else { + getShape(position).apply { + setTint( + getColorOrDefault( + HMSPrebuiltTheme.getColours()?.surfaceDefault, + HMSPrebuiltTheme.getDefaults().surface_bright, + ) + ) + } + } + } + + + override fun getLayout(): Int = R.layout.item_name_section + + + + override fun initializeViewBinding(view: View) = ItemNameSectionBinding.bind(view) + +} \ No newline at end of file diff --git a/room-kit/src/main/java/live/hms/roomkit/ui/polls/leaderboard/item/LeaderBoardSubGrid.kt b/room-kit/src/main/java/live/hms/roomkit/ui/polls/leaderboard/item/LeaderBoardSubGrid.kt new file mode 100644 index 000000000..1bcfacd63 --- /dev/null +++ b/room-kit/src/main/java/live/hms/roomkit/ui/polls/leaderboard/item/LeaderBoardSubGrid.kt @@ -0,0 +1,37 @@ +package live.hms.roomkit.ui.polls.leaderboard.item + +import android.view.View +import androidx.annotation.DrawableRes +import com.xwray.groupie.viewbinding.BindableItem +import live.hms.roomkit.R +import live.hms.roomkit.databinding.ItemGridOptionBinding +import live.hms.roomkit.databinding.ItemGridSubTextBinding +import live.hms.roomkit.ui.theme.HMSPrebuiltTheme +import live.hms.roomkit.ui.theme.applyTheme +import live.hms.roomkit.ui.theme.getColorOrDefault +import live.hms.roomkit.ui.theme.getShape + +class LeaderBoardSubGrid( + private var title: String, private var subtitle: String +) : BindableItem() { + + + override fun bind(viewBinding: ItemGridSubTextBinding, position: Int) { + //themes + viewBinding.applyTheme() + viewBinding.subtitle.text = subtitle + viewBinding.heading.text = title + + + } + + + override fun getLayout(): Int = R.layout.item_grid_sub_text + + override fun getSpanSize(spanCount: Int, position: Int): Int { + return spanCount / 2 + } + + override fun initializeViewBinding(view: View) = ItemGridSubTextBinding.bind(view) + +} \ No newline at end of file diff --git a/room-kit/src/main/java/live/hms/roomkit/ui/settings/SettingsFragment.kt b/room-kit/src/main/java/live/hms/roomkit/ui/settings/SettingsFragment.kt index dee6fac5f..7b7ed6949 100644 --- a/room-kit/src/main/java/live/hms/roomkit/ui/settings/SettingsFragment.kt +++ b/room-kit/src/main/java/live/hms/roomkit/ui/settings/SettingsFragment.kt @@ -9,7 +9,6 @@ import android.widget.AutoCompleteTextView import androidx.appcompat.app.AlertDialog import androidx.core.widget.addTextChangedListener import androidx.fragment.app.Fragment -import androidx.navigation.fragment.navArgs import com.google.android.material.switchmaterial.SwitchMaterial import com.google.android.material.textfield.TextInputEditText import com.google.android.material.textfield.TextInputLayout @@ -70,7 +69,6 @@ class SettingsFragment : Fragment() { } private var binding by viewLifecycle() - private val args: SettingsFragmentArgs by navArgs() private lateinit var settings: SettingsStore private lateinit var commitHelper: SettingsStore.MultiCommitHelper @@ -84,7 +82,7 @@ class SettingsFragment : Fragment() { binding = FragmentSettingsBinding.inflate(inflater, container, false) settings = SettingsStore(requireContext()) commitHelper = settings.MultiCommitHelper() - mode = args.mode + mode = SettingsMode.HOME // Do not open this in meetings. initEditTexts() initAutoCompleteViews() diff --git a/room-kit/src/main/java/live/hms/roomkit/ui/settings/SettingsMode.kt b/room-kit/src/main/java/live/hms/roomkit/ui/settings/SettingsMode.kt index dd1223ed6..c29b62d5e 100644 --- a/room-kit/src/main/java/live/hms/roomkit/ui/settings/SettingsMode.kt +++ b/room-kit/src/main/java/live/hms/roomkit/ui/settings/SettingsMode.kt @@ -1,5 +1,8 @@ package live.hms.roomkit.ui.settings +import androidx.annotation.Keep + +@Keep enum class SettingsMode { HOME, MEETING, diff --git a/room-kit/src/main/java/live/hms/roomkit/ui/theme/ThemeExt.kt b/room-kit/src/main/java/live/hms/roomkit/ui/theme/ThemeExt.kt index 96c02ce20..a7452fe3b 100644 --- a/room-kit/src/main/java/live/hms/roomkit/ui/theme/ThemeExt.kt +++ b/room-kit/src/main/java/live/hms/roomkit/ui/theme/ThemeExt.kt @@ -9,6 +9,7 @@ import android.graphics.drawable.shapes.RoundRectShape import android.view.View import android.view.ViewGroup import android.widget.Button +import android.widget.EditText import android.widget.ImageView import android.widget.TextView import androidx.annotation.ColorInt @@ -37,6 +38,7 @@ import live.hms.roomkit.drawableLeft import live.hms.roomkit.drawableStart import live.hms.roomkit.setGradient import live.hms.roomkit.ui.meeting.participants.EnabledMenuOptions +import live.hms.roomkit.ui.polls.leaderboard.item.ApplyRadiusatVertex import live.hms.roomkit.util.EmailUtils import live.hms.roomkit.util.EmailUtils.addAlpha import live.hms.roomkit.util.dp @@ -1589,9 +1591,20 @@ internal fun LayoutChatParticipantCombinedBinding.applyTheme(hideParticipantTab } -fun getShape(): ShapeDrawable { +fun getShape(radiusAt : ApplyRadiusatVertex = ApplyRadiusatVertex.ALL_CORNERS): ShapeDrawable { val eightDp = 8.dp().toFloat() - val lines = floatArrayOf(eightDp,eightDp,eightDp,eightDp,eightDp,eightDp,eightDp,eightDp,eightDp) + + val lines = floatArrayOf( + if(radiusAt == ApplyRadiusatVertex.TOP || radiusAt == ApplyRadiusatVertex.ALL_CORNERS) eightDp else 0f, + if(radiusAt == ApplyRadiusatVertex.TOP || radiusAt == ApplyRadiusatVertex.ALL_CORNERS) eightDp else 0f, + if(radiusAt == ApplyRadiusatVertex.TOP || radiusAt == ApplyRadiusatVertex.ALL_CORNERS) eightDp else 0f, + if(radiusAt == ApplyRadiusatVertex.TOP || radiusAt == ApplyRadiusatVertex.ALL_CORNERS) eightDp else 0f, + if(radiusAt == ApplyRadiusatVertex.BOTTOM || radiusAt == ApplyRadiusatVertex.ALL_CORNERS) eightDp else 0f, + if(radiusAt == ApplyRadiusatVertex.BOTTOM || radiusAt == ApplyRadiusatVertex.ALL_CORNERS) eightDp else 0f, + if(radiusAt == ApplyRadiusatVertex.BOTTOM || radiusAt == ApplyRadiusatVertex.ALL_CORNERS) eightDp else 0f, + if(radiusAt == ApplyRadiusatVertex.BOTTOM || radiusAt == ApplyRadiusatVertex.ALL_CORNERS) eightDp else 0f + ) + return ShapeDrawable( RoundRectShape( lines, null, @@ -1665,27 +1678,30 @@ private fun configureChatControlsTheme( ) ) - sendToBackground.strokeColor = getColorOrDefault( - HMSPrebuiltTheme.getColours()?.borderBright, - HMSPrebuiltTheme.getDefaults().border_bright - ) - sendToBackground.setBackgroundColor( - getColorOrDefault( - HMSPrebuiltTheme.getColours()?.surfaceDefault, - HMSPrebuiltTheme.getDefaults().surface_default - ) - ) + sendToBackground.background = getShape().apply { + val color = getColorOrDefault( + HMSPrebuiltTheme.getColours()?.primaryDefault, + HMSPrebuiltTheme.getDefaults().primary_default) + colorFilter = + BlendModeColorFilterCompat.createBlendModeColorFilterCompat(color, BlendModeCompat.SRC) + } sendToChipText.setTextColor( getColorOrDefault( - HMSPrebuiltTheme.getColours()?.onSurfaceHigh, - HMSPrebuiltTheme.getDefaults().onsurface_high_emp + HMSPrebuiltTheme.getColours()?.onPrimaryHigh, + HMSPrebuiltTheme.getDefaults().onprimary_high_emp ) ) + sendToBackground.strokeWidth = 0 +// sendToBackground.strokeColor = getColorOrDefault( +// HMSPrebuiltTheme.getColours()?.borderBright, +// HMSPrebuiltTheme.getDefaults().border_bright +// ) + sendToChipText.drawableEnd?.setTint( getColorOrDefault( - HMSPrebuiltTheme.getColours()?.onSurfaceHigh, - HMSPrebuiltTheme.getDefaults().onsurface_high_emp + HMSPrebuiltTheme.getColours()?.onPrimaryHigh, + HMSPrebuiltTheme.getDefaults().onprimary_high_emp ) ) } @@ -1742,16 +1758,12 @@ internal fun ListItemChatBinding.applyTheme() { HMSPrebuiltTheme.getColours()?.onSurfaceHigh, HMSPrebuiltTheme.getDefaults().onsurface_high_emp)) time.setTextColor(getColorOrDefault( - HMSPrebuiltTheme.getColours()?.onSurfaceMedium, - HMSPrebuiltTheme.getDefaults().onsurface_med_emp)) - - toGroup.setTextColor(getColorOrDefault( - HMSPrebuiltTheme.getColours()?.onSurfaceHigh, - HMSPrebuiltTheme.getDefaults().onsurface_high_emp)) + HMSPrebuiltTheme.getColours()?.onSurfaceLow, + HMSPrebuiltTheme.getDefaults().onsurface_low_emp)) sentTo.setTextColor(getColorOrDefault( - HMSPrebuiltTheme.getColours()?.onSurfaceHigh, - HMSPrebuiltTheme.getDefaults().onsurface_high_emp)) + HMSPrebuiltTheme.getColours()?.onSurfaceMedium, + HMSPrebuiltTheme.getDefaults().onsurface_med_emp)) } internal fun HlsFragmentLayoutBinding.applyTheme() { @@ -1826,8 +1838,7 @@ internal fun FragmentParticipantsBinding.applyTheme() { } internal fun LayoutParticipantsMergeBinding.applyTheme() { - containerSearch.applyTheme() - textInputSearch.applyTheme() + searchViewTheme(containerSearch, textInputSearch) } private fun backgroundShape(inset: Boolean = false, innerRadii : Float = 8.dp().toFloat()): ShapeDrawable { val lines = floatArrayOf( @@ -2213,6 +2224,34 @@ fun MaterialCardView.isSelectedStroke(isSelected : Boolean) { } +fun LayoutQuizLeaderboardBinding.applyTheme() { + backButton.backgroundTintList = + ColorStateList.valueOf(getColorOrDefault(HMSPrebuiltTheme.getColours()?.onSurfaceMedium, HMSPrebuiltTheme.getDefaults().onsurface_med_emp)) + heading.setTextColor( + getColorOrDefault( + HMSPrebuiltTheme.getColours()?.onSurfaceHigh, + HMSPrebuiltTheme.getDefaults().onsurface_high_emp + ) + ) + closeBtn.backgroundTintList = + ColorStateList.valueOf(getColorOrDefault(HMSPrebuiltTheme.getColours()?.onSurfaceMedium, HMSPrebuiltTheme.getDefaults().onsurface_med_emp)) + + root.setBackgroundColor( + getColorOrDefault( + HMSPrebuiltTheme.getColours()?.surfaceDim, + HMSPrebuiltTheme.getDefaults().onsurface_high_emp + ) + ) + pollsLive.setTextColor(getColorOrDefault( + HMSPrebuiltTheme.getColours()?.onSurfaceHigh, + HMSPrebuiltTheme.getDefaults().onprimary_high_emp + )) + pollsLive.setBackgroundColor(getColorOrDefault( + HMSPrebuiltTheme.getColours()?.surfaceDefault, + HMSPrebuiltTheme.getDefaults().error_default + )) +} + fun LayoutPollsDisplayBinding.applyTheme() { backButton.backgroundTintList = @@ -2237,6 +2276,106 @@ fun LayoutPollsDisplayBinding.applyTheme() { HMSPrebuiltTheme.getColours()?.alertErrorDefault, HMSPrebuiltTheme.getDefaults().error_default )) +} + +fun LayoutHeaderBinding.applyTheme() { + subheading.setTextColor( + getColorOrDefault( + HMSPrebuiltTheme.getColours()?.onSurfaceLow, + HMSPrebuiltTheme.getDefaults().onsurface_low_emp + ) + ) + + root.setBackgroundColor( + getColorOrDefault( + HMSPrebuiltTheme.getColours()?.surfaceDim, + HMSPrebuiltTheme.getDefaults().onsurface_high_emp + ) + ) + title.setTextColor( + getColorOrDefault( + HMSPrebuiltTheme.getColours()?.onSurfaceHigh, + HMSPrebuiltTheme.getDefaults().onsurface_high_emp + ) + ) + +} + +fun ItemGridSubTextBinding.applyTheme() { + subtitle.setTextColor( + getColorOrDefault( + HMSPrebuiltTheme.getColours()?.onSurfaceMedium, + HMSPrebuiltTheme.getDefaults().onsurface_low_emp + ) + ) + + heading.setTextColor( + getColorOrDefault( + HMSPrebuiltTheme.getColours()?.onSurfaceHigh, + HMSPrebuiltTheme.getDefaults().onsurface_high_emp + ) + ) + + rootLayout.background = getShape().apply { + setTint( + getColorOrDefault( + HMSPrebuiltTheme.getColours()?.surfaceDefault, + HMSPrebuiltTheme.getDefaults().surface_bright, + ) + ) + } + + +} + +fun ItemNameSectionBinding.applyTheme() { + + subtitle.setTextColor( + getColorOrDefault( + HMSPrebuiltTheme.getColours()?.onSurfaceMedium, + HMSPrebuiltTheme.getDefaults().onsurface_low_emp + ) + ) + + correctAnswer.setTextColor( + getColorOrDefault( + HMSPrebuiltTheme.getColours()?.onSurfaceMedium, + HMSPrebuiltTheme.getDefaults().onsurface_low_emp + ) + ) + + correctAnswer.drawableStart?.setTint( + getColorOrDefault( + HMSPrebuiltTheme.getColours()?.onSurfaceMedium, + HMSPrebuiltTheme.getDefaults().onsurface_low_emp + ) + ) + + timeTaken.drawableStart?.setTint( + getColorOrDefault( + HMSPrebuiltTheme.getColours()?.onSurfaceMedium, + HMSPrebuiltTheme.getDefaults().onsurface_low_emp + ) + ) + + timeTaken.setTextColor( + getColorOrDefault( + HMSPrebuiltTheme.getColours()?.onSurfaceMedium, + HMSPrebuiltTheme.getDefaults().onsurface_low_emp + ) + ) + + heading.setTextColor( + getColorOrDefault( + HMSPrebuiltTheme.getColours()?.onSurfaceHigh, + HMSPrebuiltTheme.getDefaults().onsurface_high_emp + ) + ) + + + + + } fun LayoutPollsDisplayChoicesQuesionBinding.applyTheme() { @@ -2656,7 +2795,33 @@ fun BottomSheetMessageOptionsBinding.applyTheme() { ) } +private fun searchViewTheme(containerSearch : MaterialCardView, textInputSearch : EditText) { + + textInputSearch.setTextColor(getColorOrDefault( + HMSPrebuiltTheme.getColours()?.onSurfaceHigh, + HMSPrebuiltTheme.getDefaults().onsurface_high_emp + )) + textInputSearch.setHintTextColor( + getColorOrDefault( + HMSPrebuiltTheme.getColours()?.onSurfaceMedium, + HMSPrebuiltTheme.getDefaults().onsurface_med_emp + ) + ) + textInputSearch.setBackgroundColor(getColorOrDefault( + HMSPrebuiltTheme.getColours()?.surfaceDim, + HMSPrebuiltTheme.getDefaults().surface_dim + )) + textInputSearch.drawableStart?.setTint(getColorOrDefault( + HMSPrebuiltTheme.getColours()?.onSurfaceMedium, + HMSPrebuiltTheme.getDefaults().onsurface_med_emp + )) + containerSearch.strokeWidth = 1.dp() + containerSearch.strokeColor = getColorOrDefault( + HMSPrebuiltTheme.getColours()?.borderBright, + HMSPrebuiltTheme.getDefaults().border_bright + ) +} fun LayoutRoleBasedChatBottomSheetSelectorBinding.applyTheme() { root.background = dialogBackground(this.root.resources) border5.setBackgroundColor( @@ -2665,7 +2830,13 @@ fun LayoutRoleBasedChatBottomSheetSelectorBinding.applyTheme() { HMSPrebuiltTheme.getDefaults().border_bright ) ) - + emptyView.setTextColor( + getColorOrDefault( + HMSPrebuiltTheme.getColours()?.onSurfaceLow, + HMSPrebuiltTheme.getDefaults().onsurface_low_emp + ) + ) + searchViewTheme(containerSearch, textInputSearch) } fun LayoutRoleBasedChatMessageBottomSheetItemHeaderBinding.applyTheme() { name.setTextColor( @@ -2678,8 +2849,8 @@ fun LayoutRoleBasedChatMessageBottomSheetItemHeaderBinding.applyTheme() { fun LayoutRoleBasedChatMessageBottomSheetItemRecipientBinding.applyTheme() { image.drawable.setTint( getColorOrDefault( - HMSPrebuiltTheme.getColours()?.onSurfaceHigh, - HMSPrebuiltTheme.getDefaults().onsurface_high_emp + HMSPrebuiltTheme.getColours()?.onPrimaryMedium, + HMSPrebuiltTheme.getDefaults().onprimary_med_emp ) ) tick.drawable.setTint( diff --git a/room-kit/src/main/res/drawable/clock.xml b/room-kit/src/main/res/drawable/clock.xml new file mode 100644 index 000000000..ecccbd005 --- /dev/null +++ b/room-kit/src/main/res/drawable/clock.xml @@ -0,0 +1,10 @@ + + + diff --git a/room-kit/src/main/res/drawable/dm_rbac_icon.xml b/room-kit/src/main/res/drawable/dm_rbac_icon.xml new file mode 100644 index 000000000..0d7e35004 --- /dev/null +++ b/room-kit/src/main/res/drawable/dm_rbac_icon.xml @@ -0,0 +1,10 @@ + + + diff --git a/room-kit/src/main/res/drawable/everyone_rbac_icon.xml b/room-kit/src/main/res/drawable/everyone_rbac_icon.xml index 1d3815b82..458553454 100644 --- a/room-kit/src/main/res/drawable/everyone_rbac_icon.xml +++ b/room-kit/src/main/res/drawable/everyone_rbac_icon.xml @@ -4,7 +4,29 @@ android:viewportWidth="20" android:viewportHeight="20"> + + + + + + + diff --git a/room-kit/src/main/res/drawable/role_rbac_icon.xml b/room-kit/src/main/res/drawable/role_rbac_icon.xml new file mode 100644 index 000000000..e4499ed71 --- /dev/null +++ b/room-kit/src/main/res/drawable/role_rbac_icon.xml @@ -0,0 +1,10 @@ + + + diff --git a/room-kit/src/main/res/drawable/tick_check_in_circle.xml b/room-kit/src/main/res/drawable/tick_check_in_circle.xml new file mode 100644 index 000000000..6a5cb1baf --- /dev/null +++ b/room-kit/src/main/res/drawable/tick_check_in_circle.xml @@ -0,0 +1,13 @@ + + + + diff --git a/room-kit/src/main/res/drawable/tiny_chip_dm.xml b/room-kit/src/main/res/drawable/tiny_chip_dm.xml index e1020481e..f3086dae8 100644 --- a/room-kit/src/main/res/drawable/tiny_chip_dm.xml +++ b/room-kit/src/main/res/drawable/tiny_chip_dm.xml @@ -4,7 +4,7 @@ android:viewportWidth="16" android:viewportHeight="16"> diff --git a/room-kit/src/main/res/drawable/tiny_chip_everyone.xml b/room-kit/src/main/res/drawable/tiny_chip_everyone.xml index e52b207ce..4fb468b0c 100644 --- a/room-kit/src/main/res/drawable/tiny_chip_everyone.xml +++ b/room-kit/src/main/res/drawable/tiny_chip_everyone.xml @@ -4,7 +4,29 @@ android:viewportWidth="16" android:viewportHeight="16"> + + + + + + + diff --git a/room-kit/src/main/res/drawable/tiny_chip_roles.xml b/room-kit/src/main/res/drawable/tiny_chip_roles.xml new file mode 100644 index 000000000..e3e1a8976 --- /dev/null +++ b/room-kit/src/main/res/drawable/tiny_chip_roles.xml @@ -0,0 +1,10 @@ + + + diff --git a/room-kit/src/main/res/drawable/tiny_down_chevron.xml b/room-kit/src/main/res/drawable/tiny_down_chevron.xml index 16d1444a9..6d8d5b1e3 100644 --- a/room-kit/src/main/res/drawable/tiny_down_chevron.xml +++ b/room-kit/src/main/res/drawable/tiny_down_chevron.xml @@ -4,7 +4,7 @@ android:viewportWidth="12" android:viewportHeight="12"> diff --git a/room-kit/src/main/res/layout/fragment_meeting.xml b/room-kit/src/main/res/layout/fragment_meeting.xml index fbe711db4..85599f89a 100644 --- a/room-kit/src/main/res/layout/fragment_meeting.xml +++ b/room-kit/src/main/res/layout/fragment_meeting.xml @@ -356,7 +356,7 @@ android:drawableEnd="@drawable/tiny_down_chevron" android:maxLines="1" android:singleLine="true" - android:text="To Group" + android:text="@string/chat_rbac_picker_send_to" android:textSize="12sp" app:fontFamily="@font/inter_regular" tools:layout_editor_absoluteY="2dp" /> @@ -369,6 +369,7 @@ diff --git a/room-kit/src/main/res/layout/fragment_preview.xml b/room-kit/src/main/res/layout/fragment_preview.xml index 762feff8e..14025cad3 100644 --- a/room-kit/src/main/res/layout/fragment_preview.xml +++ b/room-kit/src/main/res/layout/fragment_preview.xml @@ -379,6 +379,7 @@ android:layout_height="46dp" android:elevation="8dp" android:gravity="center" + android:layout_marginStart="8dp" android:paddingHorizontal="16dp" android:paddingVertical="8dp" android:visibility="invisible" diff --git a/room-kit/src/main/res/layout/item_grid_sub_text.xml b/room-kit/src/main/res/layout/item_grid_sub_text.xml new file mode 100644 index 000000000..56cc70a26 --- /dev/null +++ b/room-kit/src/main/res/layout/item_grid_sub_text.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + diff --git a/room-kit/src/main/res/layout/item_name_section.xml b/room-kit/src/main/res/layout/item_name_section.xml new file mode 100644 index 000000000..9f3374ac1 --- /dev/null +++ b/room-kit/src/main/res/layout/item_name_section.xml @@ -0,0 +1,116 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/room-kit/src/main/res/layout/layout_chat_participant_combined_tab_chat.xml b/room-kit/src/main/res/layout/layout_chat_participant_combined_tab_chat.xml index ff88f666e..ccf2bd3fc 100644 --- a/room-kit/src/main/res/layout/layout_chat_participant_combined_tab_chat.xml +++ b/room-kit/src/main/res/layout/layout_chat_participant_combined_tab_chat.xml @@ -133,7 +133,7 @@ android:drawableEnd="@drawable/tiny_down_chevron" android:maxLines="1" android:singleLine="true" - android:text="To Group" + android:text="@string/chat_rbac_picker_send_to" android:textSize="12sp" app:fontFamily="@font/inter_regular" tools:layout_editor_absoluteY="2dp" /> @@ -146,6 +146,7 @@ diff --git a/room-kit/src/main/res/layout/layout_chat_send_to_chip.xml b/room-kit/src/main/res/layout/layout_chat_send_to_chip.xml index 56968f38e..1789f5329 100644 --- a/room-kit/src/main/res/layout/layout_chat_send_to_chip.xml +++ b/room-kit/src/main/res/layout/layout_chat_send_to_chip.xml @@ -20,7 +20,7 @@ android:lineHeight="16px" android:maxLines="1" android:singleLine="true" - android:text="To Group" + android:text="@string/chat_rbac_picker_send_to" android:textSize="12sp" app:fontFamily="@font/inter_regular" tools:layout_editor_absoluteY="2dp" /> diff --git a/room-kit/src/main/res/layout/layout_end_poll_button.xml b/room-kit/src/main/res/layout/layout_end_poll_button.xml index a9f2188bb..a54504df5 100644 --- a/room-kit/src/main/res/layout/layout_end_poll_button.xml +++ b/room-kit/src/main/res/layout/layout_end_poll_button.xml @@ -9,6 +9,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:gravity="center" + android:layout_marginBottom="@dimen/spacing_d3" android:paddingHorizontal="@dimen/spacing_d2" android:paddingVertical="@dimen/spacing_d1" tools:text="Launch Poll" diff --git a/room-kit/src/main/res/layout/layout_header.xml b/room-kit/src/main/res/layout/layout_header.xml new file mode 100644 index 000000000..468bfd317 --- /dev/null +++ b/room-kit/src/main/res/layout/layout_header.xml @@ -0,0 +1,31 @@ + + + + + + + + + + \ No newline at end of file diff --git a/room-kit/src/main/res/layout/layout_participants_merge.xml b/room-kit/src/main/res/layout/layout_participants_merge.xml index dd7a68b6a..1d4d1c026 100644 --- a/room-kit/src/main/res/layout/layout_participants_merge.xml +++ b/room-kit/src/main/res/layout/layout_participants_merge.xml @@ -17,22 +17,23 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> - + android:layout_width="match_parent" + android:layout_marginTop="@dimen/spacing_d2"> - - + android:inputType="textNoSuggestions" + android:padding="@dimen/spacing_d2" /> + diff --git a/room-kit/src/main/res/layout/layout_polls_display.xml b/room-kit/src/main/res/layout/layout_polls_display.xml index 73303eb10..1f4e5c730 100644 --- a/room-kit/src/main/res/layout/layout_polls_display.xml +++ b/room-kit/src/main/res/layout/layout_polls_display.xml @@ -19,6 +19,9 @@ android:src="@drawable/left_arrow" /> + + + + + + + + + + + + + + + + + + + + + + + diff --git a/room-kit/src/main/res/layout/layout_role_based_chat_bottom_sheet_selector.xml b/room-kit/src/main/res/layout/layout_role_based_chat_bottom_sheet_selector.xml index 6178dea63..acfcac310 100644 --- a/room-kit/src/main/res/layout/layout_role_based_chat_bottom_sheet_selector.xml +++ b/room-kit/src/main/res/layout/layout_role_based_chat_bottom_sheet_selector.xml @@ -3,6 +3,7 @@ xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent" + xmlns:tools="http://schemas.android.com/tools" app:cardCornerRadius="@dimen/sizeteen_dp" android:paddingBottom="16dp" android:id="@id/rootLayout" @@ -47,22 +48,65 @@ - - + android:layout_height="wrap_content" + android:layout_marginHorizontal="@dimen/spacing_d3" + android:layout_marginTop="@dimen/spacing_d2"> + + + + + + + + + + + diff --git a/room-kit/src/main/res/layout/layout_role_based_chat_message_bottom_sheet_item_header.xml b/room-kit/src/main/res/layout/layout_role_based_chat_message_bottom_sheet_item_header.xml index 1e7c9fe3e..9fe00e4a6 100644 --- a/room-kit/src/main/res/layout/layout_role_based_chat_message_bottom_sheet_item_header.xml +++ b/room-kit/src/main/res/layout/layout_role_based_chat_message_bottom_sheet_item_header.xml @@ -4,9 +4,17 @@ android:layout_height="wrap_content" android:paddingTop="@dimen/spacing_d2" android:paddingHorizontal="@dimen/spacing_d2" + android:gravity="center_vertical" xmlns:tools="http://schemas.android.com/tools" android:orientation="horizontal"> + + - - - - - - + /> - @@ -93,19 +90,6 @@ - - - - - - - @@ -74,19 +71,6 @@ - - - - - - Chat has been paused by %1$s. Block from Chat Chat Paused + to %1$s %2$s + (Group) + (DM) + You + Choose a Recipient + No Recipients Yet \ No newline at end of file diff --git a/room-kit/src/main/res/values/themes.xml b/room-kit/src/main/res/values/themes.xml index 11e804051..a49d7b2a1 100644 --- a/room-kit/src/main/res/values/themes.xml +++ b/room-kit/src/main/res/values/themes.xml @@ -56,6 +56,7 @@