Skip to content

Instantly share code, notes, and snippets.

@rohitjakhar
Created October 25, 2021 06:41
Show Gist options
  • Save rohitjakhar/faabeb7772a5ddaa04782245fc983763 to your computer and use it in GitHub Desktop.
Save rohitjakhar/faabeb7772a5ddaa04782245fc983763 to your computer and use it in GitHub Desktop.
package com.parrychat.android.ui.call
import android.Manifest
import android.annotation.SuppressLint
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.MotionEvent
import android.view.SurfaceView
import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
import android.view.animation.AnimationUtils
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs
import androidx.recyclerview.widget.LinearLayoutManager
import coil.load
import com.google.android.flexbox.FlexDirection
import com.google.android.flexbox.FlexboxLayoutManager
import com.google.android.material.bottomsheet.BottomSheetDialog
import com.parrychat.android.R
import com.parrychat.android.data.model.HostProfileModel
import com.parrychat.android.data.model.MessageModel
import com.parrychat.android.data.model.MessageType
import com.parrychat.android.data.model.UserProfileModel
import com.parrychat.android.databinding.DialogStickersBinding
import com.parrychat.android.databinding.FragmentOngoingCallBinding
import com.parrychat.android.ui.chat.StickerAdapter
import com.parrychat.android.util.NetworkResponse
import com.parrychat.android.util.autoCleaned
import com.vmadalin.easypermissions.EasyPermissions
import com.vmadalin.easypermissions.annotations.AfterPermissionGranted
import dagger.hilt.android.AndroidEntryPoint
import io.agora.rtc.Constants.CHANNEL_PROFILE_COMMUNICATION
import io.agora.rtc.IRtcEngineEventHandler
import io.agora.rtc.RtcEngine
import io.agora.rtc.video.VideoCanvas
import io.agora.rtc.video.VideoEncoderConfiguration
import io.agora.rtc.video.VideoEncoderConfiguration.VD_640x480
import io.agora.rtm.RtmChannel
import io.agora.rtm.RtmClient
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.collectLatest
@AndroidEntryPoint
class OngoingCallFragment : Fragment() {
private var binding: FragmentOngoingCallBinding by autoCleaned()
private lateinit var hostProfileModel: HostProfileModel
private lateinit var userProfileModel: UserProfileModel
private val viewModel: OngoingCallViewModel by viewModels()
private val navArgs: OngoingCallFragmentArgs by navArgs()
private val mAdapter: OngoingCallAdapter by lazy {
OngoingCallAdapter {
if (userProfileModel.userCoin > it.giftPrice) {
stickerDialog.dismiss()
viewModel.sendMessage(
messageModel = MessageModel(
profileImage = hostProfileModel.hostProfileImage,
type = MessageType.STICKER,
mediaUrl = it.giftImage,
giftPrice = it.giftPrice
),
channelId = navArgs.channelId
)
viewModel.deductCoin(it.giftPrice)
viewLifecycleOwner.lifecycleScope.launchWhenStarted {
val animation =
AnimationUtils.loadAnimation(requireContext(), R.anim.amin_sticker_send)
binding.ivStickerSent.visibility = View.VISIBLE
binding.ivStickerSent.load(it.giftImage)
binding.ivStickerSent.animation = animation
delay(1500)
binding.ivStickerSent.visibility = View.GONE
animation.reset()
}
} else {
// TODO: Show dialog for less coin and buy coin
}
}
}
val stickerDialog by lazy { BottomSheetDialog(requireContext()) }
private val stickerAdapter: StickerAdapter by lazy {
StickerAdapter {
if (userProfileModel.userCoin > it.price) {
stickerDialog.dismiss()
viewModel.sendMessage(
messageModel = MessageModel(
profileImage = hostProfileModel.hostProfileImage,
type = MessageType.STICKER,
mediaUrl = it.image,
giftPrice = it.price
),
channelId = navArgs.channelId
)
viewModel.deductCoin(it.price)
viewLifecycleOwner.lifecycleScope.launchWhenStarted {
val animation =
AnimationUtils.loadAnimation(requireContext(), R.anim.amin_sticker_send)
binding.ivStickerSent.visibility = View.VISIBLE
binding.ivStickerSent.load(it.image)
binding.ivStickerSent.animation = animation
delay(1500)
binding.ivStickerSent.visibility = View.GONE
animation.reset()
}
} else {
// TODO: Show dialog for less coin and buy coin
}
}
}
private val stickerBinding: DialogStickersBinding by lazy {
DialogStickersBinding.inflate(layoutInflater)
}
private var dX: Float = 0F
private var dY: Float = 0F
private var lastAction: Int = 0
private lateinit var mRtcChannel: RtmChannel
private lateinit var mRtcClient: RtmClient
private val mLocalView by lazy {
SurfaceView(requireContext())
}
private val mRtcEngineEventHandler =
object : IRtcEngineEventHandler() {
override fun onJoinChannelSuccess(channel: String?, uid: Int, elapsed: Int) {
super.onJoinChannelSuccess(channel, uid, elapsed)
Log.d("rohit", "user joind channel: $uid")
requireActivity().runOnUiThread {
setupRemoteVideo(uid)
}
}
override fun onUserOffline(uid: Int, reason: Int) {
super.onUserOffline(uid, reason)
}
override fun onFirstRemoteVideoFrame(uid: Int, width: Int, height: Int, elapsed: Int) {
super.onFirstRemoteVideoFrame(uid, width, height, elapsed)
requireActivity().runOnUiThread {
setupRemoteVideo(uid)
}
}
override fun onUserJoined(uid: Int, elapsed: Int) {
super.onUserJoined(uid, elapsed)
Log.d("rohit", "user joind: $uid")
requireActivity().runOnUiThread {
setupRemoteVideo(uid)
}
}
}
private lateinit var mRtcEngine: RtcEngine
@SuppressLint("ClickableViewAccessibility")
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
binding = FragmentOngoingCallBinding.inflate(inflater, container, false)
requireActivity().window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
/*requireActivity().window.setFlags(
WindowManager.LayoutParams.FLAG_SECURE,
WindowManager.LayoutParams.FLAG_SECURE
)*/
binding.flLocalContainer.setOnTouchListener(
View.OnTouchListener { view, event ->
when (event?.action) {
MotionEvent.ACTION_DOWN -> {
dX = view.x - event.rawX
dY = view.y - event.rawY
}
MotionEvent.ACTION_MOVE -> {
val displacement = event.rawX + dX
view.animate()
.x(displacement)
.y(event.rawY + dY)
.setDuration(0)
.start()
}
else -> { // Note the block
return@OnTouchListener false
}
}
true
}
)
return binding.root
}
@ExperimentalCoroutinesApi
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel.loadSticker()
viewModel.getUserDetails()
viewModel.getHostDetails(navArgs.hostId)
initEngineAndJoinChannel()
collectUerProfile()
collectHostProfile()
initClick()
collectSticker()
stickerBinding.tvStickers.apply {
adapter = stickerAdapter
layoutManager = FlexboxLayoutManager(requireContext(), FlexDirection.ROW)
}
}
private fun collectHostProfile() = viewLifecycleOwner.lifecycleScope.launchWhenCreated {
viewModel.hostProfile.collectLatest {
when (it) {
is NetworkResponse.Failure -> {
findNavController().navigateUp()
}
is NetworkResponse.Loading -> {
}
is NetworkResponse.Success -> {
it.data?.let { hostProfile ->
hostProfileModel = hostProfile
}
}
}
}
}
private fun collectUerProfile() =
viewLifecycleOwner.lifecycleScope.launchWhenStarted {
viewModel.userDetails.collectLatest {
when (it) {
is NetworkResponse.Failure -> findNavController().navigateUp()
is NetworkResponse.Loading -> {
}
is NetworkResponse.Success -> {
it.data?.let {
userProfileModel = it
}
}
}
}
}
private fun collectSticker() {
viewLifecycleOwner.lifecycleScope.launchWhenStarted {
viewModel.stickerPack.collectLatest { networkResponse ->
when (networkResponse) {
is NetworkResponse.Failure -> {
}
is NetworkResponse.Loading -> {
}
is NetworkResponse.Success -> {
networkResponse.data?.let {
stickerAdapter.submitList(it)
}
}
}
}
}
}
private fun initClick() = binding.apply {
imgStickerPack.setOnClickListener {
stickerDialog.setContentView(stickerBinding.root)
stickerDialog.create()
stickerDialog.show()
collectMessages()
}
imgMute.setOnClickListener {
toggleMute(imgMute.isChecked)
}
imgflipCamera.setOnClickListener {
mRtcEngine.switchCamera()
}
imgEndCall.setOnClickListener {
endCall()
}
imgSpeaker.setOnClickListener {
toggetSwitch(imgSpeaker.isChecked)
}
imgSend.setOnClickListener {
if (etChatBox.text.toString().isNotEmpty()) {
viewModel.sendMessage(
messageModel = MessageModel(message = etChatBox.text.toString()),
navArgs.channelId
)
} else {
return@setOnClickListener
}
}
imgStickerPack.setOnClickListener {
stickerDialog.setContentView(stickerBinding.root)
stickerDialog.create()
stickerDialog.show()
}
}
private fun toggleMute(isMute: Boolean) {
mRtcEngine.muteLocalVideoStream(isMute)
}
private fun toggetSwitch(speakertoggle: Boolean) {
mRtcEngine.setEnableSpeakerphone(speakertoggle)
}
private fun endCall() {
}
private fun collectMessages() = viewLifecycleOwner.lifecycleScope.launchWhenStarted {
viewModel.messages.collectLatest {
when (it) {
is NetworkResponse.Failure -> {
}
is NetworkResponse.Loading -> {
}
is NetworkResponse.Success -> {
binding.rvChat.apply {
adapter = mAdapter
layoutManager = LinearLayoutManager(requireContext())
}
it.data?.let { messageList ->
mAdapter.submitList(messageList)
binding.rvChat.smoothScrollToPosition(mAdapter.itemCount)
}
}
}
}
}
@AfterPermissionGranted(PERMISSION_REQ_ID_CAMERA)
private fun initEngineAndJoinChannel() {
if (EasyPermissions.hasPermissions(
requireContext(),
Manifest.permission.CAMERA,
Manifest.permission.RECORD_AUDIO
)
) {
initlizesEngine()
joinChannel()
setupLocalVideo()
} else {
EasyPermissions.requestPermissions(
this,
getString(R.string.permissions_needed),
PERMISSION_REQ_ID_RECORD_AUDIO,
Manifest.permission.CAMERA,
Manifest.permission.RECORD_AUDIO
)
}
}
private fun initlizesEngine() {
try {
mRtcEngine = RtcEngine.create(
requireContext(),
resources.getString(R.string.agora_app_id),
mRtcEngineEventHandler
)
setUpSession()
} catch (e: Exception) {
Log.d("test", "error: ${e.localizedMessage}")
}
}
private fun setUpSession() {
mRtcEngine.setChannelProfile(CHANNEL_PROFILE_COMMUNICATION)
mRtcEngine.enableVideo()
mRtcEngine.enableAudio()
mRtcEngine.setVideoEncoderConfiguration(
VideoEncoderConfiguration(
VD_640x480,
VideoEncoderConfiguration.FRAME_RATE.FRAME_RATE_FPS_30,
VideoEncoderConfiguration.STANDARD_BITRATE,
VideoEncoderConfiguration.ORIENTATION_MODE.ORIENTATION_MODE_FIXED_PORTRAIT
)
)
}
private fun setupLocalVideo() {
mRtcEngine.enableVideo()
mRtcEngine.enableAudio()
Log.d("test", "setup local")
val surface = RtcEngine.CreateRendererView(requireActivity().baseContext)
surface.setZOrderMediaOverlay(true)
binding.flLocalContainer.addView(surface)
mRtcEngine.setupLocalVideo(VideoCanvas(surface, VideoCanvas.RENDER_MODE_FIT, 0))
mRtcEngine.enableVideo()
}
private fun joinChannel() {
mRtcEngine.joinChannel(
"006f3e0c67ee8d9449b97a77e44d7a934f8IADLhkMRMvaK2iz44nSwP/UFdl4tzeSwXXqU6W9p9r5sCgx+f9gAAAAAEADEGoyZS0l0YQEAAQBKSXRh",
"test",
"",
0
)
// mRtcEngine.joinChannel(navArgs.userToken, navArgs.channelId, "", 0)
}
private fun setupRemoteVideo(uid: Int) {
Log.d("rohit", "in remote video")
val remoteSurface = RtcEngine.CreateRendererView(requireActivity().baseContext)
binding.flRemoteContainer.addView(remoteSurface)
mRtcEngine.setupRemoteVideo(VideoCanvas(remoteSurface, VideoCanvas.RENDER_MODE_FILL, uid))
}
private fun removeRemoteVideo() {
binding.flRemoteContainer.removeView(mLocalView)
removeLocalVideo()
leaveChannel()
}
private fun removeLocalVideo() {
}
private fun leaveChannel() {
mRtcEngine.leaveChannel()
// TODO: 9/7/21 Handle UI after Remove
}
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this)
}
override fun onDestroy() {
super.onDestroy()
leaveChannel()
RtcEngine.destroy()
}
companion object {
private const val PERMISSION_REQ_ID_RECORD_AUDIO = 22
private const val PERMISSION_REQ_ID_CAMERA = PERMISSION_REQ_ID_RECORD_AUDIO + 1
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment