Skip to content

Fix Participant List Update #142

@peacejokanola

Description

@peacejokanola

Corrected Code for ParticipantsFragment.kt

package com.example.call_activity

import android.content.Context
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.lifecycle.lifecycleScope
import chat.sphinx.call_activity.R
import chat.sphinx.call_activity.databinding.FragmentParticipantsBinding
import chat.sphinx.concept_image_loader.ImageLoader
import chat.sphinx.resources.setBackgroundRandomColor
import chat.sphinx.wrapper_common.util.getInitials
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import com.squareup.moshi.Moshi
import io.livekit.android.room.participant.Participant
import kotlinx.coroutines.launch
import dagger.hilt.android.AndroidEntryPoint
import java.util.Locale
import javax.inject.Inject
import androidx.core.view.isVisible
import androidx.recyclerview.widget.RecyclerView

@androidentrypoint
class ParticipantsBottomSheetFragment : BottomSheetDialogFragment() {

@Inject
lateinit var imageLoader: ImageLoader<ImageView>

private lateinit var adapter: ParticipantAdapter

private var _binding: FragmentParticipantsBinding? = null
private val binding get() = _binding!! // Only use when safe

private var participants: MutableList<Participant> = mutableListOf()
private var participantColors: MutableMap<String, Int> = mutableMapOf()

companion object {
    fun newInstance(
        participants: MutableList<Participant>,
        participantColors: MutableMap<String, Int>
    ) = ParticipantsBottomSheetFragment().apply {
        this.participants = participants
        this.participantColors = participantColors
    }
}

override fun onCreateView(
    inflater: LayoutInflater, container: ViewGroup?,
    savedInstanceState: Bundle?
): View {
    _binding = FragmentParticipantsBinding.inflate(inflater, container, false)

    adapter = ParticipantAdapter(requireContext(), participants, imageLoader, lifecycleScope, participantColors)
    binding.participantsRv.adapter = adapter

    updateParticipantCount()

    binding.closeButton.setOnClickListener {
        dismiss()
    }

    return binding.root
}

override fun onDestroyView() {
    super.onDestroyView()
    _binding = null // Avoid memory leaks
}

fun setParticipants(
    newParticipants: MutableList<Participant>,
    newParticipantColors: MutableMap<String, Int>
) {
    this.participants = newParticipants
    this.participantColors = newParticipantColors

    if (_binding != null) { // Ensure the view exists
        updateParticipantCount()
        adapter.setParticipants(participants, participantColors)
    }
}

private fun updateParticipantCount() {
    binding.participantCountText.text = when (participants.size) {
        1 -> getString(R.string.one_participant)
        else -> getString(R.string.participant_count, participants.size)
    }
}

class ParticipantAdapter(
    context: Context,
    private var participants: MutableList<Participant>,
    private val imageLoader: ImageLoader<ImageView>,
    private val lifecycleScope: androidx.lifecycle.LifecycleCoroutineScope,
    private var participantColors: MutableMap<String, Int>
) : RecyclerView.Adapter<ParticipantAdapter.ViewHolder>() {

    private val moshi: Moshi = Moshi.Builder().build() // Reuse instead of recreating in getView()

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        val view = LayoutInflater.from(parent.context)
            .inflate(R.layout.participant_list_item, parent, false)
        return ViewHolder(view)
    }

    override fun onBindViewHolder(viewHolder: ViewHolder, position: Int) {
        val participant = participants[position]

        // Set participant name
        viewHolder.nameTextView.text = participant.name

        // Camera status
        viewHolder.cameraStatusImageView.visibility = if (participant.isCameraEnabled()) View.VISIBLE else View.GONE
        if (viewHolder.cameraStatusImageView.isVisible) {
            viewHolder.cameraStatusImageView.setImageResource(R.drawable.camera)
        }

        // Microphone status
        viewHolder.micStatusImageView.setImageResource(
            if (participant.isMicrophoneEnabled()) R.drawable.mic else R.drawable.mic_off
        )

        // Load profile picture or initials
        val participantMetaData = participant.metadata?.toParticipantMetaDataOrNull(moshi)

        participantMetaData?.profilePictureUrl?.let { imageUrl ->
            viewHolder.textViewInitials.visibility = View.GONE
            viewHolder.profileImageView.visibility = View.VISIBLE
            viewHolder.profileImageView.setImageDrawable(null)

            lifecycleScope.launch {
                imageLoader.load(viewHolder.profileImageView, imageUrl)
            }
        } ?: run {
            val initials = participant.name?.getInitials()

            viewHolder.profileImageView.visibility = View.GONE
            viewHolder.textViewInitials.apply {
                visibility = View.VISIBLE
                text = initials?.uppercase(Locale.getDefault()) ?: ""

                val sciKey = participant.getNonEmptySCI()
                val color = participantColors[sciKey]
                setBackgroundRandomColor(R.drawable.circle_icon_4, color)
            }
        }
    }

    override fun getItemCount(): Int = participants.size

    fun setParticipants(
        participants: MutableList<Participant>,
        participantColors: MutableMap<String, Int>
    ) {
        this.participants = participants
        this.participantColors = participantColors
        notifyDataSetChanged()
    }

    inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        val nameTextView: TextView = itemView.findViewById(R.id.participantName)
        val cameraStatusImageView: ImageView = itemView.findViewById(R.id.cameraStatus)
        val micStatusImageView: ImageView = itemView.findViewById(R.id.micStatus)
        val profileImageView: ImageView = itemView.findViewById(R.id.profileImageView)
        val textViewInitials: TextView = itemView.findViewById(R.id.textViewInitials)
    }

}

}

Corrected Code for fragment_participants.xml

<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal"
    android:gravity="center_vertical"
    android:layout_margin="16dp">


    <TextView
        android:id="@+id/participantCountText"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:textSize="18sp"
        android:textColor="@android:color/white"
        android:layout_weight="1"/>



    <ImageButton
        android:id="@+id/closeButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@android:drawable/ic_menu_close_clear_cancel"
        android:layout_gravity="end"
        android:background="@android:color/transparent" />

</LinearLayout>


<androidx.recyclerview.widget.RecyclerView
    android:id="@+id/participants_rv"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
    android:orientation="vertical"
    tools:listitem="@layout/participant_list_item"
    />

<View
    android:layout_width="match_parent"
    android:layout_height="1dp"
    android:background="@color/blackSemiTransparent"
    android:layout_marginTop="8dp"/>

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions