Made call history detail fragment scrollable, removed viewLifecycleOwner param from adapters that do not require it

This commit is contained in:
Sylvain Berfini 2023-11-14 13:55:36 +01:00
parent e62b1b4999
commit efdfc809bc
29 changed files with 359 additions and 297 deletions

View file

@ -23,7 +23,6 @@ import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.annotation.UiThread
import androidx.databinding.DataBindingUtil
import androidx.lifecycle.LifecycleOwner
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
@ -31,7 +30,7 @@ import org.linphone.R
import org.linphone.databinding.CallConferenceParticipantListCellBinding
import org.linphone.ui.call.model.ConferenceParticipantModel
class ConferenceParticipantsListAdapter(private val viewLifecycleOwner: LifecycleOwner) :
class ConferenceParticipantsListAdapter :
ListAdapter<ConferenceParticipantModel, RecyclerView.ViewHolder>(ParticipantDiffCallback()) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
@ -41,7 +40,6 @@ class ConferenceParticipantsListAdapter(private val viewLifecycleOwner: Lifecycl
parent,
false
)
binding.lifecycleOwner = viewLifecycleOwner
return ViewHolder(binding)
}

View file

@ -83,6 +83,12 @@ abstract class AbstractNewTransferCallFragment : GenericCallFragment() {
abstract val title: String
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
adapter = ContactsAndSuggestionsListAdapter()
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
@ -94,7 +100,6 @@ abstract class AbstractNewTransferCallFragment : GenericCallFragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
postponeEnterTransition()
binding.lifecycleOwner = viewLifecycleOwner
@ -109,9 +114,7 @@ abstract class AbstractNewTransferCallFragment : GenericCallFragment() {
viewModel.hideNumpad()
}
adapter = ContactsAndSuggestionsListAdapter(viewLifecycleOwner)
binding.contactsAndSuggestionsList.setHasFixedSize(true)
binding.contactsAndSuggestionsList.adapter = adapter
val headerItemDecoration = RecyclerViewHeaderDecoration(requireContext(), adapter, true)
binding.contactsAndSuggestionsList.addItemDecoration(headerItemDecoration)
@ -131,6 +134,10 @@ abstract class AbstractNewTransferCallFragment : GenericCallFragment() {
val count = adapter.itemCount
adapter.submitList(it)
if (binding.contactsAndSuggestionsList.adapter != adapter) {
binding.contactsAndSuggestionsList.adapter = adapter
}
if (count == 0) {
(view.parent as? ViewGroup)?.doOnPreDraw {
startPostponedEnterTransition()

View file

@ -42,6 +42,12 @@ class ConferenceParticipantsListFragment : GenericCallFragment() {
private lateinit var adapter: ConferenceParticipantsListAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
adapter = ConferenceParticipantsListAdapter()
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
@ -61,9 +67,7 @@ class ConferenceParticipantsListFragment : GenericCallFragment() {
binding.lifecycleOwner = viewLifecycleOwner
binding.viewModel = viewModel
adapter = ConferenceParticipantsListAdapter(viewLifecycleOwner)
binding.participantsList.setHasFixedSize(true)
binding.participantsList.adapter = adapter
binding.participantsList.layoutManager = LinearLayoutManager(requireContext())
binding.setBackClickListener {
@ -73,6 +77,10 @@ class ConferenceParticipantsListFragment : GenericCallFragment() {
viewModel.conferenceModel.participants.observe(viewLifecycleOwner) {
Log.i("$TAG participants list updated with [${it.size}] items")
adapter.submitList(it)
if (binding.participantsList.adapter != adapter) {
binding.participantsList.adapter = adapter
}
}
}
}

View file

@ -4,7 +4,6 @@ import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.annotation.UiThread
import androidx.databinding.DataBindingUtil
import androidx.lifecycle.LifecycleOwner
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
@ -12,9 +11,7 @@ import org.linphone.R
import org.linphone.databinding.ChatMessageBottomSheetListCellBinding
import org.linphone.ui.main.chat.model.ChatMessageBottomSheetParticipantModel
class ChatMessageBottomSheetAdapter(
private val viewLifecycleOwner: LifecycleOwner
) : ListAdapter<ChatMessageBottomSheetParticipantModel, RecyclerView.ViewHolder>(
class ChatMessageBottomSheetAdapter : ListAdapter<ChatMessageBottomSheetParticipantModel, RecyclerView.ViewHolder>(
ParticipantDiffCallback()
) {
@ -25,7 +22,6 @@ class ChatMessageBottomSheetAdapter(
parent,
false
)
binding.lifecycleOwner = viewLifecycleOwner
return ViewHolder(binding)
}

View file

@ -4,7 +4,6 @@ import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.annotation.UiThread
import androidx.databinding.DataBindingUtil
import androidx.lifecycle.LifecycleOwner
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
@ -12,9 +11,9 @@ import org.linphone.R
import org.linphone.databinding.ChatParticipantListCellBinding
import org.linphone.ui.main.chat.model.ParticipantModel
class ConversationParticipantsAdapter(
private val viewLifecycleOwner: LifecycleOwner
) : ListAdapter<ParticipantModel, RecyclerView.ViewHolder>(ChatRoomParticipantDiffCallback()) {
class ConversationParticipantsAdapter : ListAdapter<ParticipantModel, RecyclerView.ViewHolder>(
ChatRoomParticipantDiffCallback()
) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val binding: ChatParticipantListCellBinding = DataBindingUtil.inflate(
@ -23,7 +22,6 @@ class ConversationParticipantsAdapter(
parent,
false
)
binding.lifecycleOwner = viewLifecycleOwner
return ViewHolder(binding)
}

View file

@ -158,6 +158,7 @@ class ConversationFragment : GenericFragment() {
super.onCreate(savedInstanceState)
adapter = ConversationEventAdapter()
bottomSheetAdapter = ChatMessageBottomSheetAdapter()
}
override fun onCreateView(
@ -238,10 +239,7 @@ class ConversationFragment : GenericFragment() {
}
}
bottomSheetAdapter = ChatMessageBottomSheetAdapter(viewLifecycleOwner)
binding.messageBottomSheet.bottomSheetList.setHasFixedSize(true)
binding.messageBottomSheet.bottomSheetList.adapter = bottomSheetAdapter
val bottomSheetLayoutManager = LinearLayoutManager(requireContext())
binding.messageBottomSheet.bottomSheetList.layoutManager = bottomSheetLayoutManager
@ -555,6 +553,9 @@ class ConversationFragment : GenericFragment() {
binding.messageBottomSheet.setHandleClickedListener {
deliveryBottomSheetBehavior.state = BottomSheetBehavior.STATE_COLLAPSED
}
if (binding.messageBottomSheet.bottomSheetList.adapter != bottomSheetAdapter) {
binding.messageBottomSheet.bottomSheetList.adapter = bottomSheetAdapter
}
lifecycleScope.launch {
withContext(Dispatchers.IO) {

View file

@ -57,6 +57,16 @@ class ConversationInfoFragment : GenericFragment() {
private val args: ConversationInfoFragmentArgs by navArgs()
override fun goBack(): Boolean {
return findNavController().popBackStack()
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
adapter = ConversationParticipantsAdapter()
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
@ -66,16 +76,12 @@ class ConversationInfoFragment : GenericFragment() {
return binding.root
}
override fun goBack(): Boolean {
return findNavController().popBackStack()
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
// This fragment is displayed in a SlidingPane "child" area
isSlidingPaneChild = true
super.onViewCreated(view, savedInstanceState)
postponeEnterTransition()
super.onViewCreated(view, savedInstanceState)
binding.lifecycleOwner = viewLifecycleOwner
@ -90,10 +96,8 @@ class ConversationInfoFragment : GenericFragment() {
val chatRoom = sharedViewModel.displayedChatRoom
viewModel.findChatRoom(chatRoom, localSipUri, remoteSipUri)
adapter = ConversationParticipantsAdapter(viewLifecycleOwner)
binding.participants.setHasFixedSize(true)
binding.participants.layoutManager = LinearLayoutManager(requireContext())
binding.participants.adapter = adapter
viewModel.chatRoomFoundEvent.observe(viewLifecycleOwner) {
it.consume { found ->
@ -115,6 +119,10 @@ class ConversationInfoFragment : GenericFragment() {
viewModel.participants.observe(viewLifecycleOwner) { items ->
adapter.submitList(items)
Log.i("$TAG Participants list updated with [${items.size}] items")
if (binding.participants.adapter != adapter) {
binding.participants.adapter = adapter
}
}
viewModel.groupLeftEvent.observe(viewLifecycleOwner) {

View file

@ -59,8 +59,8 @@ class StartConversationFragment : GenericAddressPickerFragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
viewModel = ViewModelProvider(this)[StartConversationViewModel::class.java]
super.onViewCreated(view, savedInstanceState)
postponeEnterTransition()
super.onViewCreated(view, savedInstanceState)
binding.lifecycleOwner = viewLifecycleOwner
@ -76,13 +76,12 @@ class StartConversationFragment : GenericAddressPickerFragment() {
viewLifecycleOwner
) {
Log.i("$TAG Contacts & suggestions list is ready with [${it.size}] items")
val count = adapter.itemCount
adapter.submitList(it)
if (count == 0) {
(view.parent as? ViewGroup)?.doOnPreDraw {
startPostponedEnterTransition()
}
attachAdapter()
(view.parent as? ViewGroup)?.doOnPreDraw {
startPostponedEnterTransition()
}
}

View file

@ -82,8 +82,8 @@ class ContactFragment : GenericFragment() {
// This fragment is displayed in a SlidingPane "child" area
isSlidingPaneChild = true
super.onViewCreated(view, savedInstanceState)
postponeEnterTransition()
super.onViewCreated(view, savedInstanceState)
binding.lifecycleOwner = viewLifecycleOwner

View file

@ -100,8 +100,8 @@ class EditContactFragment : GenericFragment() {
// This fragment is displayed in a SlidingPane "child" area
isSlidingPaneChild = true
super.onViewCreated(view, savedInstanceState)
postponeEnterTransition()
super.onViewCreated(view, savedInstanceState)
requireActivity().onBackPressedDispatcher.addCallback(
viewLifecycleOwner,

View file

@ -67,8 +67,8 @@ class AddParticipantsFragment : GenericAddressPickerFragment() {
viewModel = ViewModelProvider(this)[AddParticipantsViewModel::class.java]
super.onViewCreated(view, savedInstanceState)
postponeEnterTransition()
super.onViewCreated(view, savedInstanceState)
binding.lifecycleOwner = viewLifecycleOwner
@ -84,13 +84,12 @@ class AddParticipantsFragment : GenericAddressPickerFragment() {
viewLifecycleOwner
) {
Log.i("$TAG Contacts & suggestions list is ready with [${it.size}] items")
val count = adapter.itemCount
adapter.submitList(it)
if (count == 0) {
(view.parent as? ViewGroup)?.doOnPreDraw {
startPostponedEnterTransition()
}
attachAdapter()
(view.parent as? ViewGroup)?.doOnPreDraw {
startPostponedEnterTransition()
}
}

View file

@ -54,6 +54,8 @@ abstract class GenericAddressPickerFragment : GenericFragment() {
protected abstract val viewModel: AddressSelectionViewModel
private lateinit var recyclerView: RecyclerView
private val listener = object : ContactNumberOrAddressClickListener {
@UiThread
override fun onClicked(model: ContactNumberOrAddressModel) {
@ -79,11 +81,15 @@ abstract class GenericAddressPickerFragment : GenericFragment() {
@WorkerThread
abstract fun onSingleAddressSelected(address: Address, friend: Friend)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
adapter = ContactsAndSuggestionsListAdapter()
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
adapter = ContactsAndSuggestionsListAdapter(viewLifecycleOwner)
adapter.contactClickedEvent.observe(viewLifecycleOwner) {
it.consume { model ->
handleClickOnContactModel(model)
@ -104,14 +110,22 @@ abstract class GenericAddressPickerFragment : GenericFragment() {
}
@UiThread
protected fun setupRecyclerView(recyclerView: RecyclerView) {
protected fun setupRecyclerView(view: RecyclerView) {
recyclerView = view
recyclerView.setHasFixedSize(true)
recyclerView.adapter = adapter
recyclerView.layoutManager = LinearLayoutManager(requireContext())
val headerItemDecoration = RecyclerViewHeaderDecoration(requireContext(), adapter, true)
recyclerView.addItemDecoration(headerItemDecoration)
}
recyclerView.layoutManager = LinearLayoutManager(requireContext())
@UiThread
protected fun attachAdapter() {
if (::recyclerView.isInitialized) {
if (recyclerView.adapter != adapter) {
recyclerView.adapter = adapter
}
}
}
@WorkerThread
@ -127,7 +141,7 @@ abstract class GenericAddressPickerFragment : GenericFragment() {
}
}
protected fun handleClickOnContactModel(model: ContactOrSuggestionModel) {
private fun handleClickOnContactModel(model: ContactOrSuggestionModel) {
coreContext.postOnCoreThread { core ->
val friend = model.friend
if (friend == null) {

View file

@ -23,7 +23,6 @@ import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.annotation.UiThread
import androidx.databinding.DataBindingUtil
import androidx.lifecycle.LifecycleOwner
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
@ -31,9 +30,9 @@ import org.linphone.R
import org.linphone.databinding.HistoryContactListCellBinding
import org.linphone.ui.main.history.model.CallLogHistoryModel
class ContactHistoryListAdapter(
private val viewLifecycleOwner: LifecycleOwner
) : ListAdapter<CallLogHistoryModel, RecyclerView.ViewHolder>(CallHistoryDiffCallback()) {
class ContactHistoryListAdapter : ListAdapter<CallLogHistoryModel, RecyclerView.ViewHolder>(
CallHistoryDiffCallback()
) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val binding: HistoryContactListCellBinding = DataBindingUtil.inflate(
LayoutInflater.from(parent.context),
@ -41,7 +40,7 @@ class ContactHistoryListAdapter(
parent,
false
)
binding.lifecycleOwner = viewLifecycleOwner
// binding.lifecycleOwner = viewLifecycleOwner
return ViewHolder(binding)
}

View file

@ -6,7 +6,6 @@ import android.view.View
import android.view.ViewGroup
import androidx.annotation.UiThread
import androidx.databinding.DataBindingUtil
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.MutableLiveData
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
@ -20,11 +19,10 @@ import org.linphone.utils.AppUtils
import org.linphone.utils.Event
import org.linphone.utils.HeaderAdapter
class ContactsAndSuggestionsListAdapter(
private val viewLifecycleOwner: LifecycleOwner
) : ListAdapter<ContactOrSuggestionModel, RecyclerView.ViewHolder>(
ContactOrSuggestionDiffCallback()
),
class ContactsAndSuggestionsListAdapter :
ListAdapter<ContactOrSuggestionModel, RecyclerView.ViewHolder>(
ContactOrSuggestionDiffCallback()
),
HeaderAdapter {
companion object {
private const val CONTACT_TYPE = 0
@ -75,7 +73,6 @@ class ContactsAndSuggestionsListAdapter(
parent,
false
)
binding.lifecycleOwner = viewLifecycleOwner
ContactViewHolder(binding)
}
else -> {
@ -86,7 +83,6 @@ class ContactsAndSuggestionsListAdapter(
false
)
binding.apply {
lifecycleOwner = viewLifecycleOwner
setOnClickListener {
contactClickedEvent.value = Event(model!!)
}

View file

@ -4,7 +4,6 @@ import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.annotation.UiThread
import androidx.databinding.DataBindingUtil
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.MutableLiveData
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
@ -14,9 +13,7 @@ import org.linphone.databinding.HistoryListCellBinding
import org.linphone.ui.main.history.model.CallLogModel
import org.linphone.utils.Event
class HistoryListAdapter(
private val viewLifecycleOwner: LifecycleOwner
) : ListAdapter<CallLogModel, RecyclerView.ViewHolder>(CallLogDiffCallback()) {
class HistoryListAdapter : ListAdapter<CallLogModel, RecyclerView.ViewHolder>(CallLogDiffCallback()) {
var selectedAdapterPosition = -1
val callLogClickedEvent: MutableLiveData<Event<CallLogModel>> by lazy {
@ -40,8 +37,6 @@ class HistoryListAdapter(
)
val viewHolder = ViewHolder(binding)
binding.apply {
lifecycleOwner = viewLifecycleOwner
setOnClickListener {
callLogClickedEvent.value = Event(model!!)
}

View file

@ -54,32 +54,41 @@ class HistoryContactFragment : GenericFragment() {
}
private lateinit var binding: HistoryContactFragmentBinding
private lateinit var viewModel: ContactHistoryViewModel
private lateinit var adapter: ContactHistoryListAdapter
private val args: HistoryContactFragmentArgs by navArgs()
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
binding = HistoryContactFragmentBinding.inflate(layoutInflater)
return binding.root
}
override fun goBack(): Boolean {
sharedViewModel.closeSlidingPaneEvent.value = Event(true)
// If not done, when going back to CallsFragment this fragment will be created again
return findNavController().popBackStack()
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
adapter = ContactHistoryListAdapter()
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
binding = HistoryContactFragmentBinding.inflate(layoutInflater)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
// This fragment is displayed in a SlidingPane "child" area
isSlidingPaneChild = true
super.onViewCreated(view, savedInstanceState)
postponeEnterTransition()
super.onViewCreated(view, savedInstanceState)
binding.lifecycleOwner = viewLifecycleOwner
@ -90,17 +99,26 @@ class HistoryContactFragment : GenericFragment() {
Log.i("$TAG Looking up for call log with call id [$callId]")
viewModel.findCallLogByCallId(callId)
adapter = ContactHistoryListAdapter(viewLifecycleOwner)
binding.callHistory.setHasFixedSize(true)
binding.callHistory.adapter = adapter
binding.callHistory.layoutManager = LinearLayoutManager(requireContext())
binding.setBackClickListener {
goBack()
}
binding.setMenuClickListener {
showPopupMenu()
viewModel.callLogFoundEvent.observe(viewLifecycleOwner) {
it.consume { found ->
if (found) {
Log.i(
"$TAG Found matching call log for call ID [$callId]"
)
(view.parent as? ViewGroup)?.doOnPreDraw {
startPostponedEnterTransition()
sharedViewModel.openSlidingPaneEvent.value = Event(true)
}
} else {
(view.parent as? ViewGroup)?.doOnPreDraw {
Log.e("$TAG Failed to find call log, going back")
goBack()
}
}
}
}
sharedViewModel.isSlidingPaneSlideable.observe(viewLifecycleOwner) { slideable ->
@ -111,9 +129,8 @@ class HistoryContactFragment : GenericFragment() {
Log.i("$TAG Call history list ready with [${it.size}] items")
adapter.submitList(it)
(view.parent as? ViewGroup)?.doOnPreDraw {
startPostponedEnterTransition()
sharedViewModel.openSlidingPaneEvent.value = Event(true)
if (binding.callHistory.adapter != adapter) {
binding.callHistory.adapter = adapter
}
}
@ -136,6 +153,14 @@ class HistoryContactFragment : GenericFragment() {
sharedViewModel.navigateToConversationsEvent.value = Event(true)
}
}
binding.setBackClickListener {
goBack()
}
binding.setMenuClickListener {
showPopupMenu()
}
}
private fun copyNumberOrAddressToClipboard(value: String) {

View file

@ -78,6 +78,12 @@ class HistoryListFragment : AbstractTopBarFragment() {
return super.onCreateAnimation(transit, enter, nextAnim)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
adapter = HistoryListAdapter()
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
@ -96,9 +102,7 @@ class HistoryListFragment : AbstractTopBarFragment() {
binding.lifecycleOwner = viewLifecycleOwner
binding.viewModel = listViewModel
adapter = HistoryListAdapter(viewLifecycleOwner)
binding.historyList.setHasFixedSize(true)
binding.historyList.adapter = adapter
binding.historyList.layoutManager = LinearLayoutManager(requireContext())
adapter.callLogLongClickedEvent.observe(viewLifecycleOwner) {
@ -173,6 +177,10 @@ class HistoryListFragment : AbstractTopBarFragment() {
adapter.submitList(it)
Log.i("$TAG Call logs ready with [${it.size}] items")
if (binding.historyList.adapter != adapter) {
binding.historyList.adapter = adapter
}
if (currentCount < it.size) {
binding.historyList.scrollToPosition(0)
}

View file

@ -64,8 +64,8 @@ class StartCallFragment : GenericAddressPickerFragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
viewModel = ViewModelProvider(this)[StartCallViewModel::class.java]
super.onViewCreated(view, savedInstanceState)
postponeEnterTransition()
super.onViewCreated(view, savedInstanceState)
binding.lifecycleOwner = viewLifecycleOwner
@ -86,13 +86,12 @@ class StartCallFragment : GenericAddressPickerFragment() {
viewLifecycleOwner
) {
Log.i("$TAG Contacts & suggestions list is ready with [${it.size}] items")
val count = adapter.itemCount
adapter.submitList(it)
if (count == 0) {
(view.parent as? ViewGroup)?.doOnPreDraw {
startPostponedEnterTransition()
}
attachAdapter()
(view.parent as? ViewGroup)?.doOnPreDraw {
startPostponedEnterTransition()
}
}

View file

@ -37,6 +37,8 @@ class ContactHistoryViewModel @UiThread constructor() : ViewModel() {
val operationInProgress = MutableLiveData<Boolean>()
val callLogFoundEvent = MutableLiveData<Event<Boolean>>()
val chatRoomCreationErrorEvent: MutableLiveData<Event<String>> by lazy {
MutableLiveData<Event<String>>()
}
@ -117,6 +119,9 @@ class ContactHistoryViewModel @UiThread constructor() : ViewModel() {
history.add(historyModel)
}
historyCallLogs.postValue(history)
callLogFoundEvent.postValue(Event(true))
} else {
callLogFoundEvent.postValue(Event(false))
}
}
}

View file

@ -6,7 +6,6 @@ import android.view.View
import android.view.ViewGroup
import androidx.annotation.UiThread
import androidx.databinding.DataBindingUtil
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.MutableLiveData
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
@ -18,9 +17,11 @@ import org.linphone.ui.main.meetings.model.MeetingModel
import org.linphone.utils.Event
import org.linphone.utils.HeaderAdapter
class MeetingsListAdapter(
private val viewLifecycleOwner: LifecycleOwner
) : ListAdapter<MeetingModel, RecyclerView.ViewHolder>(MeetingDiffCallback()), HeaderAdapter {
class MeetingsListAdapter :
ListAdapter<MeetingModel, RecyclerView.ViewHolder>(
MeetingDiffCallback()
),
HeaderAdapter {
val meetingClickedEvent: MutableLiveData<Event<MeetingModel>> by lazy {
MutableLiveData<Event<MeetingModel>>()
}
@ -52,8 +53,6 @@ class MeetingsListAdapter(
false
)
binding.apply {
lifecycleOwner = viewLifecycleOwner
setOnClickListener {
meetingClickedEvent.value = Event(model!!)
}

View file

@ -74,8 +74,8 @@ class MeetingFragment : GenericFragment() {
// This fragment is displayed in a SlidingPane "child" area
isSlidingPaneChild = true
super.onViewCreated(view, savedInstanceState)
postponeEnterTransition()
super.onViewCreated(view, savedInstanceState)
binding.lifecycleOwner = viewLifecycleOwner

View file

@ -76,8 +76,8 @@ class MeetingWaitingRoomFragment : GenericFragment() {
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
postponeEnterTransition()
super.onViewCreated(view, savedInstanceState)
binding.lifecycleOwner = viewLifecycleOwner

View file

@ -70,6 +70,12 @@ class MeetingsListFragment : AbstractTopBarFragment() {
return super.onCreateAnimation(transit, enter, nextAnim)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
adapter = MeetingsListAdapter()
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
@ -88,10 +94,7 @@ class MeetingsListFragment : AbstractTopBarFragment() {
binding.lifecycleOwner = viewLifecycleOwner
binding.viewModel = listViewModel
adapter = MeetingsListAdapter(viewLifecycleOwner)
binding.meetingsList.setHasFixedSize(true)
binding.meetingsList.adapter = adapter
val headerItemDecoration = RecyclerViewHeaderDecoration(requireContext(), adapter, true)
binding.meetingsList.addItemDecoration(headerItemDecoration)
binding.meetingsList.layoutManager = LinearLayoutManager(requireContext())
@ -119,13 +122,19 @@ class MeetingsListFragment : AbstractTopBarFragment() {
listViewModel.meetings.observe(viewLifecycleOwner) {
val currentCount = adapter.itemCount
val newCount = it.size
adapter.submitList(it)
Log.i("$TAG Meetings list ready with [${it.size}] items")
Log.i("$TAG Meetings list ready with [$newCount] items")
if (currentCount < it.size) {
(view.parent as? ViewGroup)?.doOnPreDraw {
startPostponedEnterTransition()
sharedViewModel.isFirstFragmentReady = true
if (binding.meetingsList.adapter != adapter) {
binding.meetingsList.adapter = adapter
}
(view.parent as? ViewGroup)?.doOnPreDraw {
startPostponedEnterTransition()
sharedViewModel.isFirstFragmentReady = true
if (currentCount < newCount) {
scrollToToday()
}
}

View file

@ -80,8 +80,8 @@ class AccountProfileFragment : GenericFragment() {
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
postponeEnterTransition()
super.onViewCreated(view, savedInstanceState)
binding.lifecycleOwner = viewLifecycleOwner
binding.viewModel = viewModel

View file

@ -61,8 +61,8 @@ class AccountSettingsFragment : GenericFragment() {
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
postponeEnterTransition()
super.onViewCreated(view, savedInstanceState)
binding.lifecycleOwner = viewLifecycleOwner

View file

@ -70,8 +70,8 @@ class SettingsFragment : GenericFragment() {
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
postponeEnterTransition()
super.onViewCreated(view, savedInstanceState)
binding.lifecycleOwner = viewLifecycleOwner
binding.viewModel = viewModel

View file

@ -345,8 +345,6 @@ fun ShapeableImageView.loadCallAvatarWithCoil(model: AbstractAvatarModel?) {
@UiThread
@BindingAdapter("coilInitials")
fun ShapeableImageView.loadInitialsAvatarWithCoil(initials: String?) {
Log.i("[Data Binding Utils] Displaying initials [$initials] on ImageView")
val imageView = this
(context as AppCompatActivity).lifecycleScope.launch {
withContext(Dispatchers.IO) {
val builder = AvatarGenerator(context)

View file

@ -333,23 +333,16 @@
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="@dimen/screen_bottom_margin"
android:background="@drawable/action_background"
android:text="@string/conversation_info_delete_history_action"
android:drawableStart="@drawable/trash_simple"
app:layout_constraintVertical_bias="0"
app:layout_constraintTop_toBottomOf="@id/action_leave_group"
app:layout_constraintBottom_toTopOf="@id/anchor"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="@id/actions_background"
app:layout_constraintEnd_toEndOf="@id/actions_background"/>
<View
android:id="@+id/anchor"
android:layout_width="wrap_content"
android:layout_height="@dimen/screen_bottom_margin"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>

View file

@ -66,194 +66,202 @@
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/title" />
<View
android:id="@+id/background"
<ScrollView
android:id="@+id/scrollView"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="@color/gray_100"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/title"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
app:layout_constraintBottom_toBottomOf="parent">
<com.google.android.material.imageview.ShapeableImageView
style="@style/avatar_imageview"
android:id="@+id/avatar"
android:layout_width="@dimen/avatar_big_size"
android:layout_height="@dimen/avatar_big_size"
android:layout_marginTop="8dp"
coilBigAvatar="@{viewModel.callLogModel.avatarModel}"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/title" />
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/presence_badge"
android:layout_width="@dimen/avatar_presence_badge_big_size"
android:layout_height="@dimen/avatar_presence_badge_big_size"
android:layout_marginEnd="@dimen/avatar_presence_badge_big_end_margin"
android:background="@drawable/led_background"
android:padding="@dimen/avatar_presence_badge_big_padding"
app:presenceIcon="@{viewModel.callLogModel.avatarModel.presenceStatus}"
android:visibility="@{viewModel.callLogModel.avatarModel.presenceStatus == ConsolidatedPresence.Offline ? View.GONE : View.VISIBLE}"
app:layout_constraintEnd_toEndOf="@id/avatar"
app:layout_constraintBottom_toBottomOf="@id/avatar"/>
<com.google.android.material.imageview.ShapeableImageView
style="@style/avatar_imageview"
android:id="@+id/avatar"
android:layout_width="@dimen/avatar_big_size"
android:layout_height="@dimen/avatar_big_size"
android:layout_marginTop="8dp"
coilBigAvatar="@{viewModel.callLogModel.avatarModel}"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="@+id/trust_badge"
android:layout_width="@dimen/avatar_presence_badge_in_call_size"
android:layout_height="@dimen/avatar_presence_badge_in_call_size"
android:src="@{viewModel.callLogModel.avatarModel.trust == SecurityLevel.Safe ? @drawable/trusted : @drawable/not_trusted, default=@drawable/trusted}"
android:visibility="@{viewModel.callLogModel.avatarModel.trust == SecurityLevel.Safe || viewModel.callLogModel.avatarModel.trust == SecurityLevel.Unsafe ? View.VISIBLE : View.GONE}"
app:layout_constraintStart_toStartOf="@id/avatar"
app:layout_constraintBottom_toBottomOf="@id/avatar"/>
<ImageView
android:id="@+id/presence_badge"
android:layout_width="@dimen/avatar_presence_badge_big_size"
android:layout_height="@dimen/avatar_presence_badge_big_size"
android:layout_marginEnd="@dimen/avatar_presence_badge_big_end_margin"
android:background="@drawable/led_background"
android:padding="@dimen/avatar_presence_badge_big_padding"
app:presenceIcon="@{viewModel.callLogModel.avatarModel.presenceStatus}"
android:visibility="@{viewModel.callLogModel.avatarModel.presenceStatus == ConsolidatedPresence.Offline ? View.GONE : View.VISIBLE}"
app:layout_constraintEnd_toEndOf="@id/avatar"
app:layout_constraintBottom_toBottomOf="@id/avatar"/>
<androidx.appcompat.widget.AppCompatTextView
style="@style/default_text_style"
android:id="@+id/name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:text="@{viewModel.callLogModel.avatarModel.name, default=`John Doe`}"
android:textColor="@color/gray_main2_700"
android:textSize="14sp"
android:maxLines="1"
android:ellipsize="end"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/avatar" />
<ImageView
android:id="@+id/trust_badge"
android:layout_width="@dimen/avatar_presence_badge_in_call_size"
android:layout_height="@dimen/avatar_presence_badge_in_call_size"
android:src="@{viewModel.callLogModel.avatarModel.trust == SecurityLevel.Safe ? @drawable/trusted : @drawable/not_trusted, default=@drawable/trusted}"
android:visibility="@{viewModel.callLogModel.avatarModel.trust == SecurityLevel.Safe || viewModel.callLogModel.avatarModel.trust == SecurityLevel.Unsafe ? View.VISIBLE : View.GONE}"
app:layout_constraintStart_toStartOf="@id/avatar"
app:layout_constraintBottom_toBottomOf="@id/avatar"/>
<androidx.appcompat.widget.AppCompatTextView
style="@style/default_text_style"
android:id="@+id/address"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:text="@{viewModel.callLogModel.displayedAddress, default=`+33601020304`}"
android:textColor="@color/gray_main2_700"
android:textSize="14sp"
android:maxLines="1"
android:ellipsize="end"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/name" />
<androidx.appcompat.widget.AppCompatTextView
style="@style/default_text_style"
android:id="@+id/name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:text="@{viewModel.callLogModel.avatarModel.name, default=`John Doe`}"
android:textColor="@color/gray_main2_700"
android:textSize="14sp"
android:maxLines="1"
android:ellipsize="end"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/avatar" />
<androidx.appcompat.widget.AppCompatTextView
style="@style/default_text_style_300"
android:id="@+id/status"
android:visibility="@{viewModel.callLogModel.avatarModel.presenceStatus == ConsolidatedPresence.Offline ? View.GONE : View.VISIBLE}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{viewModel.callLogModel.avatarModel.lastPresenceInfo, default=@string/friend_presence_status_online}"
android:textColor="@{viewModel.callLogModel.avatarModel.presenceStatus == ConsolidatedPresence.Online ? @color/green_success_500 : @color/orange_warning_600, default=@color/green_success_500}"
android:textSize="14sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/address" />
<androidx.appcompat.widget.AppCompatTextView
style="@style/default_text_style"
android:id="@+id/address"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:text="@{viewModel.callLogModel.displayedAddress, default=`+33601020304`}"
android:textColor="@color/gray_main2_700"
android:textSize="14sp"
android:maxLines="1"
android:ellipsize="end"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/name" />
<ImageView
android:id="@+id/call"
android:onClick="@{() -> viewModel.startAudioCall()}"
android:layout_width="56dp"
android:layout_height="56dp"
android:layout_marginTop="20dp"
android:background="@drawable/circle_light_blue_button_background"
android:padding="16dp"
android:src="@drawable/phone"
app:tint="@color/gray_main2_500"
app:layout_constraintEnd_toStartOf="@id/chat"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/status" />
<androidx.appcompat.widget.AppCompatTextView
style="@style/default_text_style_300"
android:id="@+id/status"
android:visibility="@{viewModel.callLogModel.avatarModel.presenceStatus == ConsolidatedPresence.Offline ? View.GONE : View.VISIBLE}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{viewModel.callLogModel.avatarModel.lastPresenceInfo, default=@string/friend_presence_status_online}"
android:textColor="@{viewModel.callLogModel.avatarModel.presenceStatus == ConsolidatedPresence.Online ? @color/green_success_500 : @color/orange_warning_600, default=@color/green_success_500}"
android:textSize="14sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/address" />
<androidx.appcompat.widget.AppCompatTextView
style="@style/default_text_style"
android:id="@+id/call_label"
android:onClick="@{() -> viewModel.startAudioCall()}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="@string/friend_call_action"
android:textSize="14sp"
app:layout_constraintTop_toBottomOf="@id/call"
app:layout_constraintStart_toStartOf="@id/call"
app:layout_constraintEnd_toEndOf="@id/call"/>
<ImageView
android:id="@+id/call"
android:onClick="@{() -> viewModel.startAudioCall()}"
android:layout_width="56dp"
android:layout_height="56dp"
android:layout_marginTop="20dp"
android:background="@drawable/circle_light_blue_button_background"
android:padding="16dp"
android:src="@drawable/phone"
app:tint="@color/gray_main2_500"
app:layout_constraintEnd_toStartOf="@id/chat"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/status" />
<ImageView
android:id="@+id/chat"
android:onClick="@{() -> viewModel.goToConversation()}"
android:visibility="@{viewModel.chatDisabled ? View.GONE : View.VISIBLE}"
android:layout_width="56dp"
android:layout_height="56dp"
android:layout_marginTop="20dp"
android:background="@drawable/circle_light_blue_button_background"
android:padding="16dp"
android:src="@drawable/chat_teardrop_text"
app:tint="@color/gray_main2_500"
app:layout_constraintEnd_toStartOf="@id/video_call"
app:layout_constraintStart_toEndOf="@id/call"
app:layout_constraintTop_toBottomOf="@id/status" />
<androidx.appcompat.widget.AppCompatTextView
style="@style/default_text_style"
android:id="@+id/call_label"
android:onClick="@{() -> viewModel.startAudioCall()}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="@string/friend_call_action"
android:textSize="14sp"
app:layout_constraintTop_toBottomOf="@id/call"
app:layout_constraintStart_toStartOf="@id/call"
app:layout_constraintEnd_toEndOf="@id/call"/>
<androidx.appcompat.widget.AppCompatTextView
style="@style/default_text_style"
android:id="@+id/chat_label"
android:onClick="@{() -> viewModel.goToConversation()}"
android:visibility="@{viewModel.chatDisabled ? View.GONE : View.VISIBLE}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="@string/friend_message_action"
android:textSize="14sp"
app:layout_constraintTop_toBottomOf="@id/chat"
app:layout_constraintStart_toStartOf="@id/chat"
app:layout_constraintEnd_toEndOf="@id/chat"/>
<ImageView
android:id="@+id/chat"
android:onClick="@{() -> viewModel.goToConversation()}"
android:visibility="@{viewModel.chatDisabled ? View.GONE : View.VISIBLE}"
android:layout_width="56dp"
android:layout_height="56dp"
android:layout_marginTop="20dp"
android:background="@drawable/circle_light_blue_button_background"
android:padding="16dp"
android:src="@drawable/chat_teardrop_text"
app:tint="@color/gray_main2_500"
app:layout_constraintEnd_toStartOf="@id/video_call"
app:layout_constraintStart_toEndOf="@id/call"
app:layout_constraintTop_toBottomOf="@id/status" />
<ImageView
android:id="@+id/video_call"
android:onClick="@{() -> viewModel.startVideoCall()}"
android:visibility="@{viewModel.videoCallDisabled ? View.GONE : View.VISIBLE}"
android:layout_width="56dp"
android:layout_height="56dp"
android:layout_marginTop="20dp"
android:background="@drawable/circle_light_blue_button_background"
android:padding="16dp"
android:src="@drawable/video_camera"
app:tint="@color/gray_main2_500"
app:layout_constraintStart_toEndOf="@id/chat"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/status" />
<androidx.appcompat.widget.AppCompatTextView
style="@style/default_text_style"
android:id="@+id/chat_label"
android:onClick="@{() -> viewModel.goToConversation()}"
android:visibility="@{viewModel.chatDisabled ? View.GONE : View.VISIBLE}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="@string/friend_message_action"
android:textSize="14sp"
app:layout_constraintTop_toBottomOf="@id/chat"
app:layout_constraintStart_toStartOf="@id/chat"
app:layout_constraintEnd_toEndOf="@id/chat"/>
<androidx.appcompat.widget.AppCompatTextView
style="@style/default_text_style"
android:id="@+id/video_call_label"
android:onClick="@{() -> viewModel.startVideoCall()}"
android:visibility="@{viewModel.videoCallDisabled ? View.GONE : View.VISIBLE}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="@string/friend_video_call_action"
android:textSize="14sp"
app:layout_constraintTop_toBottomOf="@id/video_call"
app:layout_constraintStart_toStartOf="@id/video_call"
app:layout_constraintEnd_toEndOf="@id/video_call"/>
<ImageView
android:id="@+id/video_call"
android:onClick="@{() -> viewModel.startVideoCall()}"
android:visibility="@{viewModel.videoCallDisabled ? View.GONE : View.VISIBLE}"
android:layout_width="56dp"
android:layout_height="56dp"
android:layout_marginTop="20dp"
android:background="@drawable/circle_light_blue_button_background"
android:padding="16dp"
android:src="@drawable/video_camera"
app:tint="@color/gray_main2_500"
app:layout_constraintStart_toEndOf="@id/chat"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/status" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/call_history"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginTop="28dp"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="@dimen/screen_bottom_margin"
android:paddingTop="5dp"
android:paddingBottom="5dp"
android:background="@drawable/shape_squircle_white_background"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/call_label"
app:layout_constraintBottom_toBottomOf="parent" />
<androidx.appcompat.widget.AppCompatTextView
style="@style/default_text_style"
android:id="@+id/video_call_label"
android:onClick="@{() -> viewModel.startVideoCall()}"
android:visibility="@{viewModel.videoCallDisabled ? View.GONE : View.VISIBLE}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="@string/friend_video_call_action"
android:textSize="14sp"
app:layout_constraintTop_toBottomOf="@id/video_call"
app:layout_constraintStart_toStartOf="@id/video_call"
app:layout_constraintEnd_toEndOf="@id/video_call"/>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/call_history"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="28dp"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="@dimen/screen_bottom_margin"
android:paddingTop="5dp"
android:paddingBottom="5dp"
android:background="@drawable/shape_squircle_white_background"
android:nestedScrollingEnabled="true"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/call_label" />
</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>
</androidx.constraintlayout.widget.ConstraintLayout>