Fixed more leaks

This commit is contained in:
Sylvain Berfini 2020-08-31 10:57:15 +02:00
parent ebda69e739
commit 2433921f22
22 changed files with 290 additions and 261 deletions

View file

@ -27,8 +27,10 @@ import android.view.MenuItem
import android.view.ViewGroup import android.view.ViewGroup
import androidx.appcompat.widget.PopupMenu import androidx.appcompat.widget.PopupMenu
import androidx.databinding.DataBindingUtil import androidx.databinding.DataBindingUtil
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView
import org.linphone.LinphoneApplication.Companion.coreContext import org.linphone.LinphoneApplication.Companion.coreContext
import org.linphone.LinphoneApplication.Companion.corePreferences import org.linphone.LinphoneApplication.Companion.corePreferences
import org.linphone.R import org.linphone.R
@ -42,10 +44,12 @@ import org.linphone.core.EventLog
import org.linphone.databinding.ChatEventListCellBinding import org.linphone.databinding.ChatEventListCellBinding
import org.linphone.databinding.ChatMessageListCellBinding import org.linphone.databinding.ChatMessageListCellBinding
import org.linphone.utils.Event import org.linphone.utils.Event
import org.linphone.utils.LifecycleListAdapter import org.linphone.utils.SelectionListAdapter
import org.linphone.utils.LifecycleViewHolder
class ChatMessagesListAdapter(val selectionViewModel: ListTopBarViewModel) : LifecycleListAdapter<EventLog, LifecycleViewHolder>(ChatMessageDiffCallback()) { class ChatMessagesListAdapter(
selectionVM: ListTopBarViewModel,
private val viewLifecycleOwner: LifecycleOwner
) : SelectionListAdapter<EventLog, RecyclerView.ViewHolder>(selectionVM, ChatMessageDiffCallback()) {
companion object { companion object {
const val MAX_TIME_TO_GROUP_MESSAGES = 60 // 1 minute const val MAX_TIME_TO_GROUP_MESSAGES = 60 // 1 minute
} }
@ -80,7 +84,7 @@ class ChatMessagesListAdapter(val selectionViewModel: ListTopBarViewModel) : Lif
} }
} }
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): LifecycleViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return when (viewType) { return when (viewType) {
EventLog.Type.ConferenceChatMessage.toInt() -> createChatMessageViewHolder(parent) EventLog.Type.ConferenceChatMessage.toInt() -> createChatMessageViewHolder(parent)
else -> createEventViewHolder(parent) else -> createEventViewHolder(parent)
@ -92,9 +96,7 @@ class ChatMessagesListAdapter(val selectionViewModel: ListTopBarViewModel) : Lif
LayoutInflater.from(parent.context), LayoutInflater.from(parent.context),
R.layout.chat_message_list_cell, parent, false R.layout.chat_message_list_cell, parent, false
) )
val viewHolder = ChatMessageViewHolder(binding) return ChatMessageViewHolder(binding)
binding.lifecycleOwner = viewHolder
return viewHolder
} }
private fun createEventViewHolder(parent: ViewGroup): EventViewHolder { private fun createEventViewHolder(parent: ViewGroup): EventViewHolder {
@ -102,12 +104,10 @@ class ChatMessagesListAdapter(val selectionViewModel: ListTopBarViewModel) : Lif
LayoutInflater.from(parent.context), LayoutInflater.from(parent.context),
R.layout.chat_event_list_cell, parent, false R.layout.chat_event_list_cell, parent, false
) )
val viewHolder = EventViewHolder(binding) return EventViewHolder(binding)
binding.lifecycleOwner = viewHolder
return viewHolder
} }
override fun onBindViewHolder(holder: LifecycleViewHolder, position: Int) { override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val eventLog = getItem(position) val eventLog = getItem(position)
when (holder) { when (holder) {
is ChatMessageViewHolder -> holder.bind(eventLog) is ChatMessageViewHolder -> holder.bind(eventLog)
@ -122,7 +122,7 @@ class ChatMessagesListAdapter(val selectionViewModel: ListTopBarViewModel) : Lif
inner class ChatMessageViewHolder( inner class ChatMessageViewHolder(
private val binding: ChatMessageListCellBinding private val binding: ChatMessageListCellBinding
) : LifecycleViewHolder(binding), PopupMenu.OnMenuItemClickListener { ) : RecyclerView.ViewHolder(binding.root), PopupMenu.OnMenuItemClickListener {
fun bind(eventLog: EventLog) { fun bind(eventLog: EventLog) {
with(binding) { with(binding) {
if (eventLog.type == EventLog.Type.ConferenceChatMessage) { if (eventLog.type == EventLog.Type.ConferenceChatMessage) {
@ -131,9 +131,11 @@ class ChatMessagesListAdapter(val selectionViewModel: ListTopBarViewModel) : Lif
val chatMessageViewModel = ChatMessageViewModel(chatMessage, contentClickedListener) val chatMessageViewModel = ChatMessageViewModel(chatMessage, contentClickedListener)
viewModel = chatMessageViewModel viewModel = chatMessageViewModel
binding.lifecycleOwner = viewLifecycleOwner
// This is for item selection through ListTopBarFragment // This is for item selection through ListTopBarFragment
selectionListViewModel = selectionViewModel selectionListViewModel = selectionViewModel
selectionViewModel.isEditionEnabled.observe(this@ChatMessageViewHolder, { selectionViewModel.isEditionEnabled.observe(viewLifecycleOwner, {
position = adapterPosition position = adapterPosition
}) })
@ -284,15 +286,17 @@ class ChatMessagesListAdapter(val selectionViewModel: ListTopBarViewModel) : Lif
inner class EventViewHolder( inner class EventViewHolder(
private val binding: ChatEventListCellBinding private val binding: ChatEventListCellBinding
) : LifecycleViewHolder(binding) { ) : RecyclerView.ViewHolder(binding.root) {
fun bind(eventLog: EventLog) { fun bind(eventLog: EventLog) {
with(binding) { with(binding) {
val eventViewModel = EventViewModel(eventLog) val eventViewModel = EventViewModel(eventLog)
viewModel = eventViewModel viewModel = eventViewModel
binding.lifecycleOwner = viewLifecycleOwner
// This is for item selection through ListTopBarFragment // This is for item selection through ListTopBarFragment
selectionListViewModel = selectionViewModel selectionListViewModel = selectionViewModel
selectionViewModel.isEditionEnabled.observe(this@EventViewHolder, { selectionViewModel.isEditionEnabled.observe(viewLifecycleOwner, {
position = adapterPosition position = adapterPosition
}) })

View file

@ -22,8 +22,11 @@ package org.linphone.activities.main.chat.adapters
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.ViewGroup import android.view.ViewGroup
import androidx.databinding.DataBindingUtil import androidx.databinding.DataBindingUtil
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import org.linphone.LinphoneApplication.Companion.coreContext import org.linphone.LinphoneApplication.Companion.coreContext
import org.linphone.R import org.linphone.R
import org.linphone.activities.main.chat.viewmodels.ChatRoomCreationContactViewModel import org.linphone.activities.main.chat.viewmodels.ChatRoomCreationContactViewModel
@ -32,51 +35,57 @@ import org.linphone.core.FriendCapability
import org.linphone.core.SearchResult import org.linphone.core.SearchResult
import org.linphone.databinding.ChatRoomCreationContactCellBinding import org.linphone.databinding.ChatRoomCreationContactCellBinding
import org.linphone.utils.Event import org.linphone.utils.Event
import org.linphone.utils.LifecycleListAdapter
import org.linphone.utils.LifecycleViewHolder
class ChatRoomCreationContactsAdapter : LifecycleListAdapter<SearchResult, ChatRoomCreationContactsAdapter.ViewHolder>(SearchResultDiffCallback()) { class ChatRoomCreationContactsAdapter(
private val viewLifecycleOwner: LifecycleOwner
) : ListAdapter<SearchResult, RecyclerView.ViewHolder>(SearchResultDiffCallback()) {
val selectedContact = MutableLiveData<Event<SearchResult>>() val selectedContact = MutableLiveData<Event<SearchResult>>()
val selectedAddresses = MutableLiveData<ArrayList<Address>>()
var groupChatEnabled: Boolean = false var groupChatEnabled: Boolean = false
val securityEnabled = MutableLiveData<Boolean>() private var selectedAddresses = ArrayList<Address>()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { private var securityEnabled: Boolean = false
fun updateSelectedAddresses(selection: ArrayList<Address>) {
selectedAddresses = selection
notifyDataSetChanged()
}
fun updateSecurity(enabled: Boolean) {
securityEnabled = enabled
notifyDataSetChanged()
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val binding: ChatRoomCreationContactCellBinding = DataBindingUtil.inflate( val binding: ChatRoomCreationContactCellBinding = DataBindingUtil.inflate(
LayoutInflater.from(parent.context), LayoutInflater.from(parent.context),
R.layout.chat_room_creation_contact_cell, parent, false R.layout.chat_room_creation_contact_cell, parent, false
) )
val viewHolder = ViewHolder(binding) return ViewHolder(binding)
binding.lifecycleOwner = viewHolder
return viewHolder
} }
override fun onBindViewHolder(holder: ViewHolder, position: Int) { override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
holder.bind(getItem(position)) (holder as ViewHolder).bind(getItem(position))
} }
inner class ViewHolder( inner class ViewHolder(
private val binding: ChatRoomCreationContactCellBinding private val binding: ChatRoomCreationContactCellBinding
) : LifecycleViewHolder(binding) { ) : RecyclerView.ViewHolder(binding.root) {
fun bind(searchResult: SearchResult) { fun bind(searchResult: SearchResult) {
with(binding) { with(binding) {
val searchResultViewModel = ChatRoomCreationContactViewModel(searchResult) val searchResultViewModel = ChatRoomCreationContactViewModel(searchResult)
viewModel = searchResultViewModel viewModel = searchResultViewModel
securityEnabled.observe(this@ViewHolder, { binding.lifecycleOwner = viewLifecycleOwner
updateSecurity(searchResult, searchResultViewModel, it)
})
selectedAddresses.observe(this@ViewHolder, { updateSecurity(searchResult, searchResultViewModel, securityEnabled)
val selected = it.find { address ->
val selected = selectedAddresses.find { address ->
val searchAddress = searchResult.address val searchAddress = searchResult.address
if (searchAddress != null) address.weakEqual(searchAddress) else false if (searchAddress != null) address.weakEqual(searchAddress) else false
} }
searchResultViewModel.isSelected.value = selected != null searchResultViewModel.isSelected.value = selected != null
})
setClickListener { setClickListener {
selectedContact.value = Event(searchResult) selectedContact.value = Event(searchResult)

View file

@ -22,47 +22,55 @@ package org.linphone.activities.main.chat.adapters
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.ViewGroup import android.view.ViewGroup
import androidx.databinding.DataBindingUtil import androidx.databinding.DataBindingUtil
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView
import org.linphone.R import org.linphone.R
import org.linphone.activities.main.chat.viewmodels.ChatRoomViewModel import org.linphone.activities.main.chat.viewmodels.ChatRoomViewModel
import org.linphone.activities.main.viewmodels.ListTopBarViewModel import org.linphone.activities.main.viewmodels.ListTopBarViewModel
import org.linphone.core.ChatRoom import org.linphone.core.ChatRoom
import org.linphone.databinding.ChatRoomListCellBinding import org.linphone.databinding.ChatRoomListCellBinding
import org.linphone.utils.Event import org.linphone.utils.Event
import org.linphone.utils.LifecycleListAdapter import org.linphone.utils.SelectionListAdapter
import org.linphone.utils.LifecycleViewHolder
class ChatRoomsListAdapter(val selectionViewModel: ListTopBarViewModel) : LifecycleListAdapter<ChatRoom, ChatRoomsListAdapter.ViewHolder>(ChatRoomDiffCallback()) { class ChatRoomsListAdapter(
selectionVM: ListTopBarViewModel,
private val viewLifecycleOwner: LifecycleOwner
) : SelectionListAdapter<ChatRoom, RecyclerView.ViewHolder>(selectionVM, ChatRoomDiffCallback()) {
val selectedChatRoomEvent: MutableLiveData<Event<ChatRoom>> by lazy { val selectedChatRoomEvent: MutableLiveData<Event<ChatRoom>> by lazy {
MutableLiveData<Event<ChatRoom>>() MutableLiveData<Event<ChatRoom>>()
} }
val toggledPositionForSelectionEvent: MutableLiveData<Event<Int>> by lazy {
MutableLiveData<Event<Int>>()
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val binding: ChatRoomListCellBinding = DataBindingUtil.inflate( val binding: ChatRoomListCellBinding = DataBindingUtil.inflate(
LayoutInflater.from(parent.context), LayoutInflater.from(parent.context),
R.layout.chat_room_list_cell, parent, false R.layout.chat_room_list_cell, parent, false
) )
val viewHolder = ViewHolder(binding) return ViewHolder(binding)
binding.lifecycleOwner = viewHolder
return viewHolder
} }
override fun onBindViewHolder(holder: ViewHolder, position: Int) { override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
holder.bind(getItem(position)) (holder as ViewHolder).bind(getItem(position))
} }
inner class ViewHolder( inner class ViewHolder(
private val binding: ChatRoomListCellBinding private val binding: ChatRoomListCellBinding
) : LifecycleViewHolder(binding) { ) : RecyclerView.ViewHolder(binding.root) {
fun bind(chatRoom: ChatRoom) { fun bind(chatRoom: ChatRoom) {
with(binding) { with(binding) {
val chatRoomViewModel = ChatRoomViewModel(chatRoom) val chatRoomViewModel = ChatRoomViewModel(chatRoom)
viewModel = chatRoomViewModel viewModel = chatRoomViewModel
binding.lifecycleOwner = viewLifecycleOwner
// This is for item selection through ListTopBarFragment // This is for item selection through ListTopBarFragment
selectionListViewModel = selectionViewModel selectionListViewModel = selectionViewModel
selectionViewModel.isEditionEnabled.observe(this@ViewHolder, { selectionViewModel.isEditionEnabled.observe(viewLifecycleOwner, {
position = adapterPosition position = adapterPosition
}) })

View file

@ -22,18 +22,21 @@ package org.linphone.activities.main.chat.adapters
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.ViewGroup import android.view.ViewGroup
import androidx.databinding.DataBindingUtil import androidx.databinding.DataBindingUtil
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import org.linphone.R import org.linphone.R
import org.linphone.activities.main.chat.GroupChatRoomMember import org.linphone.activities.main.chat.GroupChatRoomMember
import org.linphone.activities.main.chat.viewmodels.GroupInfoParticipantViewModel import org.linphone.activities.main.chat.viewmodels.GroupInfoParticipantViewModel
import org.linphone.databinding.ChatRoomGroupInfoParticipantCellBinding import org.linphone.databinding.ChatRoomGroupInfoParticipantCellBinding
import org.linphone.utils.Event import org.linphone.utils.Event
import org.linphone.utils.LifecycleListAdapter
import org.linphone.utils.LifecycleViewHolder
class GroupInfoParticipantsAdapter(private val isEncryptionEnabled: Boolean) : LifecycleListAdapter<GroupChatRoomMember, class GroupInfoParticipantsAdapter(
GroupInfoParticipantsAdapter.ViewHolder>(ParticipantDiffCallback()) { private val viewLifecycleOwner: LifecycleOwner,
private val isEncryptionEnabled: Boolean
) : ListAdapter<GroupChatRoomMember, RecyclerView.ViewHolder>(ParticipantDiffCallback()) {
private var showAdmin: Boolean = false private var showAdmin: Boolean = false
val participantRemovedEvent: MutableLiveData<Event<GroupChatRoomMember>> by lazy { val participantRemovedEvent: MutableLiveData<Event<GroupChatRoomMember>> by lazy {
@ -45,13 +48,11 @@ class GroupInfoParticipantsAdapter(private val isEncryptionEnabled: Boolean) : L
LayoutInflater.from(parent.context), LayoutInflater.from(parent.context),
R.layout.chat_room_group_info_participant_cell, parent, false R.layout.chat_room_group_info_participant_cell, parent, false
) )
val viewHolder = ViewHolder(binding) return ViewHolder(binding)
binding.lifecycleOwner = viewHolder
return viewHolder
} }
override fun onBindViewHolder(holder: ViewHolder, position: Int) { override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
holder.bind(getItem(position)) (holder as ViewHolder).bind(getItem(position))
} }
fun showAdminControls(show: Boolean) { fun showAdminControls(show: Boolean) {
@ -61,13 +62,15 @@ class GroupInfoParticipantsAdapter(private val isEncryptionEnabled: Boolean) : L
inner class ViewHolder( inner class ViewHolder(
private val binding: ChatRoomGroupInfoParticipantCellBinding private val binding: ChatRoomGroupInfoParticipantCellBinding
) : LifecycleViewHolder(binding) { ) : RecyclerView.ViewHolder(binding.root) {
fun bind(participant: GroupChatRoomMember) { fun bind(participant: GroupChatRoomMember) {
with(binding) { with(binding) {
val participantViewModel = GroupInfoParticipantViewModel(participant) val participantViewModel = GroupInfoParticipantViewModel(participant)
participantViewModel.showAdminControls.value = showAdmin participantViewModel.showAdminControls.value = showAdmin
viewModel = participantViewModel viewModel = participantViewModel
binding.lifecycleOwner = viewLifecycleOwner
setRemoveClickListener { setRemoveClickListener {
participantRemovedEvent.value = Event(participant) participantRemovedEvent.value = Event(participant)
} }

View file

@ -24,8 +24,10 @@ import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.databinding.DataBindingUtil import androidx.databinding.DataBindingUtil
import androidx.lifecycle.LifecycleOwner
import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import org.linphone.R import org.linphone.R
import org.linphone.activities.main.chat.viewmodels.ImdnParticipantViewModel import org.linphone.activities.main.chat.viewmodels.ImdnParticipantViewModel
import org.linphone.core.ChatMessage import org.linphone.core.ChatMessage
@ -33,31 +35,30 @@ import org.linphone.core.ParticipantImdnState
import org.linphone.databinding.ChatRoomImdnParticipantCellBinding import org.linphone.databinding.ChatRoomImdnParticipantCellBinding
import org.linphone.databinding.ImdnListHeaderBinding import org.linphone.databinding.ImdnListHeaderBinding
import org.linphone.utils.HeaderAdapter import org.linphone.utils.HeaderAdapter
import org.linphone.utils.LifecycleViewHolder
class ImdnAdapter : ListAdapter<ParticipantImdnState, class ImdnAdapter(
ImdnAdapter.ViewHolder>(ParticipantImdnStateDiffCallback()), HeaderAdapter { private val viewLifecycleOwner: LifecycleOwner
) : ListAdapter<ParticipantImdnState, RecyclerView.ViewHolder>(ParticipantImdnStateDiffCallback()), HeaderAdapter {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val binding: ChatRoomImdnParticipantCellBinding = DataBindingUtil.inflate( val binding: ChatRoomImdnParticipantCellBinding = DataBindingUtil.inflate(
LayoutInflater.from(parent.context), LayoutInflater.from(parent.context),
R.layout.chat_room_imdn_participant_cell, parent, false R.layout.chat_room_imdn_participant_cell, parent, false
) )
val viewHolder = ViewHolder(binding) return ViewHolder(binding)
binding.lifecycleOwner = viewHolder
return viewHolder
} }
override fun onBindViewHolder(holder: ViewHolder, position: Int) { override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
holder.bind(getItem(position)) (holder as ViewHolder).bind(getItem(position))
} }
class ViewHolder( inner class ViewHolder(
private val binding: ChatRoomImdnParticipantCellBinding private val binding: ChatRoomImdnParticipantCellBinding
) : LifecycleViewHolder(binding) { ) : RecyclerView.ViewHolder(binding.root) {
fun bind(participantImdnState: ParticipantImdnState) { fun bind(participantImdnState: ParticipantImdnState) {
with(binding) { with(binding) {
val imdnViewModel = ImdnParticipantViewModel(participantImdnState) viewModel = ImdnParticipantViewModel(participantImdnState)
viewModel = imdnViewModel
binding.lifecycleOwner = viewLifecycleOwner
executePendingBindings() executePendingBindings()
} }

View file

@ -59,9 +59,9 @@ class ChatRoomCreationFragment : GenericFragment<ChatRoomCreationFragmentBinding
binding.viewModel = viewModel binding.viewModel = viewModel
adapter = ChatRoomCreationContactsAdapter() adapter = ChatRoomCreationContactsAdapter(viewLifecycleOwner)
adapter.groupChatEnabled = viewModel.createGroupChat.value == true adapter.groupChatEnabled = viewModel.createGroupChat.value == true
adapter.securityEnabled.value = viewModel.isEncrypted.value == true adapter.updateSecurity(viewModel.isEncrypted.value == true)
binding.contactsList.adapter = adapter binding.contactsList.adapter = adapter
val layoutManager = LinearLayoutManager(activity) val layoutManager = LinearLayoutManager(activity)
@ -90,7 +90,7 @@ class ChatRoomCreationFragment : GenericFragment<ChatRoomCreationFragmentBinding
}) })
viewModel.isEncrypted.observe(viewLifecycleOwner, { viewModel.isEncrypted.observe(viewLifecycleOwner, {
adapter.securityEnabled.value = it adapter.updateSecurity(it)
}) })
viewModel.sipContactsSelected.observe(viewLifecycleOwner, { viewModel.sipContactsSelected.observe(viewLifecycleOwner, {
@ -98,7 +98,7 @@ class ChatRoomCreationFragment : GenericFragment<ChatRoomCreationFragmentBinding
}) })
viewModel.selectedAddresses.observe(viewLifecycleOwner, { viewModel.selectedAddresses.observe(viewLifecycleOwner, {
adapter.selectedAddresses.value = it adapter.updateSelectedAddresses(it)
}) })
viewModel.chatRoomCreatedEvent.observe(viewLifecycleOwner, { viewModel.chatRoomCreatedEvent.observe(viewLifecycleOwner, {

View file

@ -56,18 +56,33 @@ import org.linphone.databinding.ChatRoomDetailFragmentBinding
import org.linphone.utils.* import org.linphone.utils.*
import org.linphone.utils.Event import org.linphone.utils.Event
class DetailChatRoomFragment : MasterFragment<ChatRoomDetailFragmentBinding>() { class DetailChatRoomFragment : MasterFragment<ChatRoomDetailFragmentBinding, ChatMessagesListAdapter>() {
private lateinit var viewModel: ChatRoomViewModel private lateinit var viewModel: ChatRoomViewModel
private lateinit var chatSendingViewModel: ChatMessageSendingViewModel private lateinit var chatSendingViewModel: ChatMessageSendingViewModel
private lateinit var listViewModel: ChatMessagesListViewModel private lateinit var listViewModel: ChatMessagesListViewModel
private lateinit var adapter: ChatMessagesListAdapter
private lateinit var sharedViewModel: SharedMainViewModel private lateinit var sharedViewModel: SharedMainViewModel
private val observer = object : RecyclerView.AdapterDataObserver() {
override fun onItemRangeInserted(positionStart: Int, itemCount: Int) {
if (positionStart == adapter.itemCount - 1) {
adapter.notifyItemChanged(positionStart - 1) // For grouping purposes
scrollToBottom()
}
}
}
private var chatRoomAddress: String? = null private var chatRoomAddress: String? = null
override fun getLayoutId(): Int { override fun getLayoutId(): Int {
return R.layout.chat_room_detail_fragment return R.layout.chat_room_detail_fragment
} }
override fun onDestroyView() {
binding.chatMessagesList.adapter = null
adapter.unregisterAdapterDataObserver(observer)
super.onDestroyView()
}
override fun onActivityCreated(savedInstanceState: Bundle?) { override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState) super.onActivityCreated(savedInstanceState)
@ -98,18 +113,11 @@ class DetailChatRoomFragment : MasterFragment<ChatRoomDetailFragmentBinding>() {
ChatMessagesListViewModelFactory(chatRoom) ChatMessagesListViewModelFactory(chatRoom)
)[ChatMessagesListViewModel::class.java] )[ChatMessagesListViewModel::class.java]
adapter = ChatMessagesListAdapter(listSelectionViewModel) _adapter = ChatMessagesListAdapter(listSelectionViewModel, viewLifecycleOwner)
// SubmitList is done on a background thread // SubmitList is done on a background thread
// We need this adapter data observer to know when to scroll // We need this adapter data observer to know when to scroll
adapter.registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() {
override fun onItemRangeInserted(positionStart: Int, itemCount: Int) {
if (positionStart == adapter.itemCount - 1) {
adapter.notifyItemChanged(positionStart - 1) // For grouping purposes
scrollToBottom()
}
}
})
binding.chatMessagesList.adapter = adapter binding.chatMessagesList.adapter = adapter
adapter.registerAdapterDataObserver(observer)
val layoutManager = LinearLayoutManager(activity) val layoutManager = LinearLayoutManager(activity)
layoutManager.stackFromEnd = true layoutManager.stackFromEnd = true
@ -265,7 +273,7 @@ class DetailChatRoomFragment : MasterFragment<ChatRoomDetailFragmentBinding>() {
override fun deleteItems(indexesOfItemToDelete: ArrayList<Int>) { override fun deleteItems(indexesOfItemToDelete: ArrayList<Int>) {
val list = ArrayList<EventLog>() val list = ArrayList<EventLog>()
for (index in indexesOfItemToDelete) { for (index in indexesOfItemToDelete) {
val eventLog = adapter.getItemAt(index) val eventLog = adapter.currentList[index]
list.add(eventLog) list.add(eventLog)
} }
listViewModel.deleteEventLogs(list) listViewModel.deleteEventLogs(list)

View file

@ -67,7 +67,10 @@ class GroupInfoFragment : GenericFragment<ChatRoomGroupInfoFragmentBinding>() {
viewModel.isEncrypted.value = sharedViewModel.createEncryptedChatRoom viewModel.isEncrypted.value = sharedViewModel.createEncryptedChatRoom
adapter = GroupInfoParticipantsAdapter(chatRoom?.hasCapability(ChatRoomCapabilities.Encrypted.toInt()) ?: viewModel.isEncrypted.value == true) adapter = GroupInfoParticipantsAdapter(
viewLifecycleOwner,
chatRoom?.hasCapability(ChatRoomCapabilities.Encrypted.toInt()) ?: viewModel.isEncrypted.value == true
)
binding.participants.adapter = adapter binding.participants.adapter = adapter
val layoutManager = LinearLayoutManager(activity) val layoutManager = LinearLayoutManager(activity)

View file

@ -76,7 +76,7 @@ class ImdnFragment : GenericFragment<ChatRoomImdnFragmentBinding>() {
return return
} }
adapter = ImdnAdapter() adapter = ImdnAdapter(viewLifecycleOwner)
binding.participantsList.adapter = adapter binding.participantsList.adapter = adapter
val layoutManager = LinearLayoutManager(activity) val layoutManager = LinearLayoutManager(activity)

View file

@ -43,14 +43,31 @@ import org.linphone.core.tools.Log
import org.linphone.databinding.ChatRoomMasterFragmentBinding import org.linphone.databinding.ChatRoomMasterFragmentBinding
import org.linphone.utils.* import org.linphone.utils.*
class MasterChatRoomsFragment : MasterFragment<ChatRoomMasterFragmentBinding>() { class MasterChatRoomsFragment : MasterFragment<ChatRoomMasterFragmentBinding, ChatRoomsListAdapter>() {
override val dialogConfirmationMessageBeforeRemoval = R.plurals.chat_room_delete_dialog override val dialogConfirmationMessageBeforeRemoval = R.plurals.chat_room_delete_dialog
private lateinit var listViewModel: ChatRoomsListViewModel private lateinit var listViewModel: ChatRoomsListViewModel
private lateinit var adapter: ChatRoomsListAdapter
private lateinit var sharedViewModel: SharedMainViewModel private lateinit var sharedViewModel: SharedMainViewModel
private val observer = object : RecyclerView.AdapterDataObserver() {
override fun onChanged() {
scrollToTop()
}
override fun onItemRangeInserted(positionStart: Int, itemCount: Int) {
scrollToTop()
}
override fun onItemRangeMoved(fromPosition: Int, toPosition: Int, itemCount: Int) {
scrollToTop()
}
}
override fun getLayoutId(): Int = R.layout.chat_room_master_fragment override fun getLayoutId(): Int = R.layout.chat_room_master_fragment
override fun onDestroyView() {
binding.chatList.adapter = null
adapter.unregisterAdapterDataObserver(observer)
super.onDestroyView()
}
override fun onActivityCreated(savedInstanceState: Bundle?) { override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState) super.onActivityCreated(savedInstanceState)
@ -63,20 +80,10 @@ class MasterChatRoomsFragment : MasterFragment<ChatRoomMasterFragmentBinding>()
ViewModelProvider(this).get(SharedMainViewModel::class.java) ViewModelProvider(this).get(SharedMainViewModel::class.java)
} ?: throw Exception("Invalid Activity") } ?: throw Exception("Invalid Activity")
adapter = ChatRoomsListAdapter(listSelectionViewModel) _adapter = ChatRoomsListAdapter(listSelectionViewModel, viewLifecycleOwner)
// SubmitList is done on a background thread // SubmitList is done on a background thread
// We need this adapter data observer to know when to scroll // We need this adapter data observer to know when to scroll
adapter.registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() { adapter.registerAdapterDataObserver(observer)
override fun onChanged() {
scrollToTop()
}
override fun onItemRangeInserted(positionStart: Int, itemCount: Int) {
scrollToTop()
}
override fun onItemRangeMoved(fromPosition: Int, toPosition: Int, itemCount: Int) {
scrollToTop()
}
})
binding.chatList.adapter = adapter binding.chatList.adapter = adapter
val layoutManager = LinearLayoutManager(activity) val layoutManager = LinearLayoutManager(activity)
@ -100,7 +107,7 @@ class MasterChatRoomsFragment : MasterFragment<ChatRoomMasterFragmentBinding>()
} }
viewModel.showDeleteButton({ viewModel.showDeleteButton({
listViewModel.deleteChatRoom(adapter.getItemAt(viewHolder.adapterPosition)) listViewModel.deleteChatRoom(adapter.currentList[viewHolder.adapterPosition])
dialog.dismiss() dialog.dismiss()
}, getString(R.string.dialog_delete)) }, getString(R.string.dialog_delete))
@ -136,6 +143,12 @@ class MasterChatRoomsFragment : MasterFragment<ChatRoomMasterFragmentBinding>()
} }
}) })
adapter.toggledPositionForSelectionEvent.observe(viewLifecycleOwner, {
it.consume { position ->
listSelectionViewModel.onToggleSelect(position)
}
})
binding.setEditClickListener { binding.setEditClickListener {
listSelectionViewModel.isEditionEnabled.value = true listSelectionViewModel.isEditionEnabled.value = true
} }
@ -201,7 +214,7 @@ class MasterChatRoomsFragment : MasterFragment<ChatRoomMasterFragmentBinding>()
override fun deleteItems(indexesOfItemToDelete: ArrayList<Int>) { override fun deleteItems(indexesOfItemToDelete: ArrayList<Int>) {
val list = ArrayList<ChatRoom>() val list = ArrayList<ChatRoom>()
for (index in indexesOfItemToDelete) { for (index in indexesOfItemToDelete) {
val chatRoom = adapter.getItemAt(index) val chatRoom = adapter.currentList[index]
list.add(chatRoom) list.add(chatRoom)
} }
listViewModel.deleteChatRooms(list) listViewModel.deleteChatRooms(list)

View file

@ -40,7 +40,7 @@ import org.linphone.utils.TimestampUtils
class ChatMessageViewModel( class ChatMessageViewModel(
val chatMessage: ChatMessage, val chatMessage: ChatMessage,
private val contentListener: OnContentClickedListener? = null private var contentListener: OnContentClickedListener? = null
) : GenericContactViewModel(chatMessage.fromAddress) { ) : GenericContactViewModel(chatMessage.fromAddress) {
val sendInProgress = MutableLiveData<Boolean>() val sendInProgress = MutableLiveData<Boolean>()
@ -114,6 +114,7 @@ class ChatMessageViewModel(
override fun onCleared() { override fun onCleared() {
chatMessage.removeListener(listener) chatMessage.removeListener(listener)
contentListener = null
super.onCleared() super.onCleared()
} }

View file

@ -24,8 +24,10 @@ import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.databinding.DataBindingUtil import androidx.databinding.DataBindingUtil
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView
import org.linphone.R import org.linphone.R
import org.linphone.activities.main.contact.viewmodels.ContactViewModel import org.linphone.activities.main.contact.viewmodels.ContactViewModel
import org.linphone.activities.main.viewmodels.ListTopBarViewModel import org.linphone.activities.main.viewmodels.ListTopBarViewModel
@ -34,10 +36,12 @@ import org.linphone.databinding.ContactListCellBinding
import org.linphone.databinding.GenericListHeaderBinding import org.linphone.databinding.GenericListHeaderBinding
import org.linphone.utils.Event import org.linphone.utils.Event
import org.linphone.utils.HeaderAdapter import org.linphone.utils.HeaderAdapter
import org.linphone.utils.LifecycleListAdapter import org.linphone.utils.SelectionListAdapter
import org.linphone.utils.LifecycleViewHolder
class ContactsListAdapter(val selectionViewModel: ListTopBarViewModel) : LifecycleListAdapter<Contact, ContactsListAdapter.ViewHolder>(ContactDiffCallback()), HeaderAdapter { class ContactsListAdapter(
selectionVM: ListTopBarViewModel,
private val viewLifecycleOwner: LifecycleOwner
) : SelectionListAdapter<Contact, RecyclerView.ViewHolder>(selectionVM, ContactDiffCallback()), HeaderAdapter {
val selectedContactEvent: MutableLiveData<Event<Contact>> by lazy { val selectedContactEvent: MutableLiveData<Event<Contact>> by lazy {
MutableLiveData<Event<Contact>>() MutableLiveData<Event<Contact>>()
} }
@ -47,26 +51,26 @@ class ContactsListAdapter(val selectionViewModel: ListTopBarViewModel) : Lifecyc
LayoutInflater.from(parent.context), LayoutInflater.from(parent.context),
R.layout.contact_list_cell, parent, false R.layout.contact_list_cell, parent, false
) )
val viewHolder = ViewHolder(binding) return ViewHolder(binding)
binding.lifecycleOwner = viewHolder
return viewHolder
} }
override fun onBindViewHolder(holder: ViewHolder, position: Int) { override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
holder.bind(getItem(position)) (holder as ViewHolder).bind(getItem(position))
} }
inner class ViewHolder( inner class ViewHolder(
private val binding: ContactListCellBinding private val binding: ContactListCellBinding
) : LifecycleViewHolder(binding) { ) : RecyclerView.ViewHolder(binding.root) {
fun bind(contact: Contact) { fun bind(contact: Contact) {
with(binding) { with(binding) {
val contactViewModel = ContactViewModel(contact) val contactViewModel = ContactViewModel(contact)
viewModel = contactViewModel viewModel = contactViewModel
binding.lifecycleOwner = viewLifecycleOwner
// This is for item selection through ListTopBarFragment // This is for item selection through ListTopBarFragment
selectionListViewModel = selectionViewModel selectionListViewModel = selectionViewModel
selectionViewModel.isEditionEnabled.observe(this@ViewHolder, { selectionViewModel.isEditionEnabled.observe(viewLifecycleOwner, {
position = adapterPosition position = adapterPosition
}) })

View file

@ -44,10 +44,9 @@ import org.linphone.core.tools.Log
import org.linphone.databinding.ContactMasterFragmentBinding import org.linphone.databinding.ContactMasterFragmentBinding
import org.linphone.utils.* import org.linphone.utils.*
class MasterContactsFragment : MasterFragment<ContactMasterFragmentBinding>() { class MasterContactsFragment : MasterFragment<ContactMasterFragmentBinding, ContactsListAdapter>() {
override val dialogConfirmationMessageBeforeRemoval = R.plurals.contact_delete_dialog override val dialogConfirmationMessageBeforeRemoval = R.plurals.contact_delete_dialog
private lateinit var listViewModel: ContactsListViewModel private lateinit var listViewModel: ContactsListViewModel
private lateinit var adapter: ContactsListAdapter
private lateinit var sharedViewModel: SharedMainViewModel private lateinit var sharedViewModel: SharedMainViewModel
private var sipUriToAdd: String? = null private var sipUriToAdd: String? = null
@ -55,6 +54,11 @@ class MasterContactsFragment : MasterFragment<ContactMasterFragmentBinding>() {
override fun getLayoutId(): Int = R.layout.contact_master_fragment override fun getLayoutId(): Int = R.layout.contact_master_fragment
override fun onDestroyView() {
binding.contactsList.adapter = null
super.onDestroyView()
}
override fun onActivityCreated(savedInstanceState: Bundle?) { override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState) super.onActivityCreated(savedInstanceState)
@ -67,7 +71,7 @@ class MasterContactsFragment : MasterFragment<ContactMasterFragmentBinding>() {
ViewModelProvider(this).get(SharedMainViewModel::class.java) ViewModelProvider(this).get(SharedMainViewModel::class.java)
} ?: throw Exception("Invalid Activity") } ?: throw Exception("Invalid Activity")
adapter = ContactsListAdapter(listSelectionViewModel) _adapter = ContactsListAdapter(listSelectionViewModel, viewLifecycleOwner)
binding.contactsList.adapter = adapter binding.contactsList.adapter = adapter
binding.setEditClickListener { binding.setEditClickListener {
@ -100,7 +104,7 @@ class MasterContactsFragment : MasterFragment<ContactMasterFragmentBinding>() {
} }
viewModel.showDeleteButton({ viewModel.showDeleteButton({
listViewModel.deleteContact(adapter.getItemAt(viewHolder.adapterPosition)) listViewModel.deleteContact(adapter.currentList[viewHolder.adapterPosition])
dialog.dismiss() dialog.dismiss()
}, getString(R.string.dialog_delete)) }, getString(R.string.dialog_delete))
@ -211,7 +215,7 @@ class MasterContactsFragment : MasterFragment<ContactMasterFragmentBinding>() {
override fun deleteItems(indexesOfItemToDelete: ArrayList<Int>) { override fun deleteItems(indexesOfItemToDelete: ArrayList<Int>) {
val list = ArrayList<Contact>() val list = ArrayList<Contact>()
for (index in indexesOfItemToDelete) { for (index in indexesOfItemToDelete) {
val contact = adapter.getItemAt(index) val contact = adapter.currentList[index]
list.add(contact) list.add(contact)
} }
listViewModel.deleteContacts(list) listViewModel.deleteContacts(list)

View file

@ -29,12 +29,16 @@ import org.linphone.activities.main.viewmodels.DialogViewModel
import org.linphone.activities.main.viewmodels.ListTopBarViewModel import org.linphone.activities.main.viewmodels.ListTopBarViewModel
import org.linphone.utils.AppUtils import org.linphone.utils.AppUtils
import org.linphone.utils.DialogUtils import org.linphone.utils.DialogUtils
import org.linphone.utils.SelectionListAdapter
/** /**
* This fragment can be inherited by all fragments that will display a list * This fragment can be inherited by all fragments that will display a list
* where items can be selected for removal through the ListTopBarFragment * where items can be selected for removal through the ListTopBarFragment
*/ */
abstract class MasterFragment<T : ViewDataBinding> : GenericFragment<T>() { abstract class MasterFragment<T : ViewDataBinding, U : SelectionListAdapter<*, *>> : GenericFragment<T>() {
protected var _adapter: U? = null
protected val adapter get() = _adapter!!
protected lateinit var listSelectionViewModel: ListTopBarViewModel protected lateinit var listSelectionViewModel: ListTopBarViewModel
protected open val dialogConfirmationMessageBeforeRemoval: Int = R.plurals.dialog_default_delete protected open val dialogConfirmationMessageBeforeRemoval: Int = R.plurals.dialog_default_delete

View file

@ -24,8 +24,10 @@ import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.databinding.DataBindingUtil import androidx.databinding.DataBindingUtil
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView
import org.linphone.R import org.linphone.R
import org.linphone.activities.main.history.viewmodels.CallLogViewModel import org.linphone.activities.main.history.viewmodels.CallLogViewModel
import org.linphone.activities.main.history.viewmodels.GroupedCallLogViewModel import org.linphone.activities.main.history.viewmodels.GroupedCallLogViewModel
@ -35,7 +37,10 @@ import org.linphone.databinding.GenericListHeaderBinding
import org.linphone.databinding.HistoryListCellBinding import org.linphone.databinding.HistoryListCellBinding
import org.linphone.utils.* import org.linphone.utils.*
class CallLogsListAdapter(val selectionViewModel: ListTopBarViewModel) : LifecycleListAdapter<GroupedCallLogViewModel, CallLogsListAdapter.ViewHolder>(CallLogDiffCallback()), HeaderAdapter { class CallLogsListAdapter(
selectionVM: ListTopBarViewModel,
private val viewLifecycleOwner: LifecycleOwner
) : SelectionListAdapter<GroupedCallLogViewModel, RecyclerView.ViewHolder>(selectionVM, CallLogDiffCallback()), HeaderAdapter {
val selectedCallLogEvent: MutableLiveData<Event<GroupedCallLogViewModel>> by lazy { val selectedCallLogEvent: MutableLiveData<Event<GroupedCallLogViewModel>> by lazy {
MutableLiveData<Event<GroupedCallLogViewModel>>() MutableLiveData<Event<GroupedCallLogViewModel>>()
} }
@ -44,31 +49,31 @@ class CallLogsListAdapter(val selectionViewModel: ListTopBarViewModel) : Lifecyc
MutableLiveData<Event<Address>>() MutableLiveData<Event<Address>>()
} }
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val binding: HistoryListCellBinding = DataBindingUtil.inflate( val binding: HistoryListCellBinding = DataBindingUtil.inflate(
LayoutInflater.from(parent.context), LayoutInflater.from(parent.context),
R.layout.history_list_cell, parent, false R.layout.history_list_cell, parent, false
) )
val viewHolder = ViewHolder(binding) return ViewHolder(binding)
binding.lifecycleOwner = viewHolder
return viewHolder
} }
override fun onBindViewHolder(holder: ViewHolder, position: Int) { override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
holder.bind(getItem(position)) (holder as ViewHolder).bind(getItem(position))
} }
inner class ViewHolder( inner class ViewHolder(
private val binding: HistoryListCellBinding private val binding: HistoryListCellBinding
) : LifecycleViewHolder(binding) { ) : RecyclerView.ViewHolder(binding.root) {
fun bind(callLogGroup: GroupedCallLogViewModel) { fun bind(callLogGroup: GroupedCallLogViewModel) {
with(binding) { with(binding) {
val callLogViewModel = CallLogViewModel(callLogGroup.lastCallLog) val callLogViewModel = CallLogViewModel(callLogGroup.lastCallLog)
viewModel = callLogViewModel viewModel = callLogViewModel
binding.lifecycleOwner = viewLifecycleOwner
// This is for item selection through ListTopBarFragment // This is for item selection through ListTopBarFragment
selectionListViewModel = selectionViewModel selectionListViewModel = selectionViewModel
selectionViewModel.isEditionEnabled.observe(this@ViewHolder, { selectionViewModel.isEditionEnabled.observe(viewLifecycleOwner, {
position = adapterPosition position = adapterPosition
}) })

View file

@ -42,14 +42,25 @@ import org.linphone.core.tools.Log
import org.linphone.databinding.HistoryMasterFragmentBinding import org.linphone.databinding.HistoryMasterFragmentBinding
import org.linphone.utils.* import org.linphone.utils.*
class MasterCallLogsFragment : MasterFragment<HistoryMasterFragmentBinding>() { class MasterCallLogsFragment : MasterFragment<HistoryMasterFragmentBinding, CallLogsListAdapter>() {
override val dialogConfirmationMessageBeforeRemoval = R.plurals.history_delete_dialog override val dialogConfirmationMessageBeforeRemoval = R.plurals.history_delete_dialog
private lateinit var listViewModel: CallLogsListViewModel private lateinit var listViewModel: CallLogsListViewModel
private lateinit var adapter: CallLogsListAdapter
private lateinit var sharedViewModel: SharedMainViewModel private lateinit var sharedViewModel: SharedMainViewModel
private val observer = object : RecyclerView.AdapterDataObserver() {
override fun onItemRangeInserted(positionStart: Int, itemCount: Int) {
scrollToTop()
}
}
override fun getLayoutId(): Int = R.layout.history_master_fragment override fun getLayoutId(): Int = R.layout.history_master_fragment
override fun onDestroyView() {
binding.callLogsList.adapter = null
adapter.unregisterAdapterDataObserver(observer)
super.onDestroyView()
}
override fun onActivityCreated(savedInstanceState: Bundle?) { override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState) super.onActivityCreated(savedInstanceState)
@ -62,14 +73,10 @@ class MasterCallLogsFragment : MasterFragment<HistoryMasterFragmentBinding>() {
ViewModelProvider(this).get(SharedMainViewModel::class.java) ViewModelProvider(this).get(SharedMainViewModel::class.java)
} ?: throw Exception("Invalid Activity") } ?: throw Exception("Invalid Activity")
adapter = CallLogsListAdapter(listSelectionViewModel) _adapter = CallLogsListAdapter(listSelectionViewModel, viewLifecycleOwner)
// SubmitList is done on a background thread // SubmitList is done on a background thread
// We need this adapter data observer to know when to scroll // We need this adapter data observer to know when to scroll
adapter.registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() { adapter.registerAdapterDataObserver(observer)
override fun onItemRangeInserted(positionStart: Int, itemCount: Int) {
scrollToTop()
}
})
binding.callLogsList.adapter = adapter binding.callLogsList.adapter = adapter
binding.setEditClickListener { binding.setEditClickListener {
@ -97,7 +104,7 @@ class MasterCallLogsFragment : MasterFragment<HistoryMasterFragmentBinding>() {
} }
viewModel.showDeleteButton({ viewModel.showDeleteButton({
listViewModel.deleteCallLogGroup(adapter.getItemAt(viewHolder.adapterPosition)) listViewModel.deleteCallLogGroup(adapter.currentList[viewHolder.adapterPosition])
dialog.dismiss() dialog.dismiss()
}, getString(R.string.dialog_delete)) }, getString(R.string.dialog_delete))
@ -194,7 +201,7 @@ class MasterCallLogsFragment : MasterFragment<HistoryMasterFragmentBinding>() {
override fun deleteItems(indexesOfItemToDelete: ArrayList<Int>) { override fun deleteItems(indexesOfItemToDelete: ArrayList<Int>) {
val list = ArrayList<GroupedCallLogViewModel>() val list = ArrayList<GroupedCallLogViewModel>()
for (index in indexesOfItemToDelete) { for (index in indexesOfItemToDelete) {
val callLogGroup = adapter.getItemAt(index) val callLogGroup = adapter.currentList[index]
list.add(callLogGroup) list.add(callLogGroup)
} }
listViewModel.deleteCallLogGroups(list) listViewModel.deleteCallLogGroups(list)

View file

@ -25,8 +25,10 @@ import android.view.TextureView
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.databinding.DataBindingUtil import androidx.databinding.DataBindingUtil
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView
import org.linphone.R import org.linphone.R
import org.linphone.activities.main.recordings.viewmodels.RecordingViewModel import org.linphone.activities.main.recordings.viewmodels.RecordingViewModel
import org.linphone.activities.main.viewmodels.ListTopBarViewModel import org.linphone.activities.main.viewmodels.ListTopBarViewModel
@ -34,10 +36,10 @@ import org.linphone.databinding.GenericListHeaderBinding
import org.linphone.databinding.RecordingListCellBinding import org.linphone.databinding.RecordingListCellBinding
import org.linphone.utils.* import org.linphone.utils.*
class RecordingsListAdapter(val selectionViewModel: ListTopBarViewModel) : LifecycleListAdapter<RecordingViewModel, RecordingsListAdapter.ViewHolder>( class RecordingsListAdapter(
RecordingDiffCallback() selectionVM: ListTopBarViewModel,
), HeaderAdapter { private val viewLifecycleOwner: LifecycleOwner
) : SelectionListAdapter<RecordingViewModel, RecyclerView.ViewHolder>(selectionVM, RecordingDiffCallback()), HeaderAdapter {
val isVideoRecordingPlayingEvent: MutableLiveData<Event<Boolean>> by lazy { val isVideoRecordingPlayingEvent: MutableLiveData<Event<Boolean>> by lazy {
MutableLiveData<Event<Boolean>>() MutableLiveData<Event<Boolean>>()
} }
@ -48,32 +50,30 @@ class RecordingsListAdapter(val selectionViewModel: ListTopBarViewModel) : Lifec
videoSurface = textureView videoSurface = textureView
} }
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val binding: RecordingListCellBinding = DataBindingUtil.inflate( val binding: RecordingListCellBinding = DataBindingUtil.inflate(
LayoutInflater.from(parent.context), LayoutInflater.from(parent.context),
R.layout.recording_list_cell, parent, false R.layout.recording_list_cell, parent, false
) )
val viewHolder = ViewHolder(binding) return ViewHolder(binding)
binding.lifecycleOwner = viewHolder
return viewHolder
} }
override fun onBindViewHolder(holder: ViewHolder, position: Int) { override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
holder.bind(getItem(position)) (holder as ViewHolder).bind(getItem(position))
} }
inner class ViewHolder( inner class ViewHolder(
private val binding: RecordingListCellBinding private val binding: RecordingListCellBinding
) : LifecycleViewHolder(binding) { ) : RecyclerView.ViewHolder(binding.root) {
fun bind(recording: RecordingViewModel) { fun bind(recording: RecordingViewModel) {
with(binding) { with(binding) {
viewModel = recording viewModel = recording
binding.lifecycleOwner = viewLifecycleOwner
// This is for item selection through ListTopBarFragment // This is for item selection through ListTopBarFragment
selectionListViewModel = selectionViewModel
selectionViewModel.isEditionEnabled.observe(this@ViewHolder, {
position = adapterPosition position = adapterPosition
}) selectionListViewModel = selectionViewModel
setClickListener { setClickListener {
if (selectionViewModel.isEditionEnabled.value == true) { if (selectionViewModel.isEditionEnabled.value == true) {
@ -81,14 +81,18 @@ class RecordingsListAdapter(val selectionViewModel: ListTopBarViewModel) : Lifec
} }
} }
recording.isVideoRecordingPlayingEvent.observe(this@ViewHolder, { setPlayListener {
it.consume { value -> if (recording.isPlaying.value == true) {
if (value) { recording.pause()
isVideoRecordingPlayingEvent.value = Event(false)
} else {
recording.play()
if (recording.isVideoAvailable()) {
recording.setTextureView(videoSurface) recording.setTextureView(videoSurface)
isVideoRecordingPlayingEvent.value = Event(true)
}
} }
isVideoRecordingPlayingEvent.value = Event(value)
} }
})
executePendingBindings() executePendingBindings()
} }

View file

@ -33,15 +33,19 @@ import org.linphone.activities.main.recordings.viewmodels.RecordingsViewModel
import org.linphone.databinding.RecordingsFragmentBinding import org.linphone.databinding.RecordingsFragmentBinding
import org.linphone.utils.RecyclerViewHeaderDecoration import org.linphone.utils.RecyclerViewHeaderDecoration
class RecordingsFragment : MasterFragment<RecordingsFragmentBinding>() { class RecordingsFragment : MasterFragment<RecordingsFragmentBinding, RecordingsListAdapter>() {
private lateinit var viewModel: RecordingsViewModel private lateinit var viewModel: RecordingsViewModel
private lateinit var adapter: RecordingsListAdapter
private var videoX: Float = 0f private var videoX: Float = 0f
private var videoY: Float = 0f private var videoY: Float = 0f
override fun getLayoutId(): Int = R.layout.recordings_fragment override fun getLayoutId(): Int = R.layout.recordings_fragment
override fun onDestroyView() {
binding.recordingsList.adapter = null
super.onDestroyView()
}
override fun onActivityCreated(savedInstanceState: Bundle?) { override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState) super.onActivityCreated(savedInstanceState)
@ -50,10 +54,7 @@ class RecordingsFragment : MasterFragment<RecordingsFragmentBinding>() {
viewModel = ViewModelProvider(this).get(RecordingsViewModel::class.java) viewModel = ViewModelProvider(this).get(RecordingsViewModel::class.java)
binding.viewModel = viewModel binding.viewModel = viewModel
adapter = _adapter = RecordingsListAdapter(listSelectionViewModel, viewLifecycleOwner)
RecordingsListAdapter(
listSelectionViewModel
)
binding.recordingsList.adapter = adapter binding.recordingsList.adapter = adapter
val layoutManager = LinearLayoutManager(activity) val layoutManager = LinearLayoutManager(activity)
@ -109,7 +110,7 @@ class RecordingsFragment : MasterFragment<RecordingsFragmentBinding>() {
override fun deleteItems(indexesOfItemToDelete: ArrayList<Int>) { override fun deleteItems(indexesOfItemToDelete: ArrayList<Int>) {
val list = ArrayList<RecordingViewModel>() val list = ArrayList<RecordingViewModel>()
for (index in indexesOfItemToDelete) { for (index in indexesOfItemToDelete) {
val recording = adapter.getItemAt(index) val recording = adapter.currentList[index]
list.add(recording) list.add(recording)
} }
viewModel.deleteRecordings(list) viewModel.deleteRecordings(list)

View file

@ -37,7 +37,6 @@ import org.linphone.core.AudioDevice
import org.linphone.core.Player import org.linphone.core.Player
import org.linphone.core.PlayerListener import org.linphone.core.PlayerListener
import org.linphone.core.tools.Log import org.linphone.core.tools.Log
import org.linphone.utils.Event
import org.linphone.utils.LinphoneUtils import org.linphone.utils.LinphoneUtils
class RecordingViewModel(val path: String) : ViewModel(), Comparable<RecordingViewModel> { class RecordingViewModel(val path: String) : ViewModel(), Comparable<RecordingViewModel> {
@ -56,10 +55,6 @@ class RecordingViewModel(val path: String) : ViewModel(), Comparable<RecordingVi
val formattedPosition = MutableLiveData<String>() val formattedPosition = MutableLiveData<String>()
val isPlaying = MutableLiveData<Boolean>() val isPlaying = MutableLiveData<Boolean>()
val isVideoRecordingPlayingEvent: MutableLiveData<Event<Boolean>> by lazy {
MutableLiveData<Event<Boolean>>()
}
private val tickerChannel = ticker(1000, 1000) private val tickerChannel = ticker(1000, 1000)
private lateinit var player: Player private lateinit var player: Player
@ -68,6 +63,30 @@ class RecordingViewModel(val path: String) : ViewModel(), Comparable<RecordingVi
stop() stop()
} }
private val textureViewListener = object : TextureView.SurfaceTextureListener {
override fun onSurfaceTextureSizeChanged(
surface: SurfaceTexture,
width: Int,
height: Int
) { }
override fun onSurfaceTextureUpdated(surface: SurfaceTexture) { }
override fun onSurfaceTextureDestroyed(surface: SurfaceTexture): Boolean {
player.setWindowId(null)
return true
}
override fun onSurfaceTextureAvailable(
surface: SurfaceTexture,
width: Int,
height: Int
) {
Log.i("[Recording VM] Surface texture should be available now")
player.setWindowId(surface)
}
}
init { init {
val m = RECORD_PATTERN.matcher(path) val m = RECORD_PATTERN.matcher(path)
if (m.matches() && m.groupCount() >= 2) { if (m.matches() && m.groupCount() >= 2) {
@ -84,6 +103,7 @@ class RecordingViewModel(val path: String) : ViewModel(), Comparable<RecordingVi
override fun onCleared() { override fun onCleared() {
tickerChannel.cancel() tickerChannel.cancel()
player.setWindowId(null)
if (!isClosed()) player.close() if (!isClosed()) player.close()
player.removeListener(listener) player.removeListener(listener)
@ -111,8 +131,10 @@ class RecordingViewModel(val path: String) : ViewModel(), Comparable<RecordingVi
} }
} }
} }
}
isVideoRecordingPlayingEvent.value = Event(player.isVideoAvailable) fun isVideoAvailable(): Boolean {
return player.isVideoAvailable
} }
fun pause() { fun pause() {
@ -135,28 +157,7 @@ class RecordingViewModel(val path: String) : ViewModel(), Comparable<RecordingVi
if (textureView.isAvailable) { if (textureView.isAvailable) {
player.setWindowId(textureView.surfaceTexture) player.setWindowId(textureView.surfaceTexture)
} else { } else {
textureView.surfaceTextureListener = object : TextureView.SurfaceTextureListener { textureView.surfaceTextureListener = textureViewListener
override fun onSurfaceTextureSizeChanged(
surface: SurfaceTexture,
width: Int,
height: Int
) { }
override fun onSurfaceTextureUpdated(surface: SurfaceTexture) { }
override fun onSurfaceTextureDestroyed(surface: SurfaceTexture): Boolean {
return true
}
override fun onSurfaceTextureAvailable(
surface: SurfaceTexture,
width: Int,
height: Int
) {
Log.i("[Recording VM] Surface texture should be available now")
player.setWindowId(textureView.surfaceTexture)
}
}
} }
} }
@ -197,7 +198,6 @@ class RecordingViewModel(val path: String) : ViewModel(), Comparable<RecordingVi
player.seek(0) player.seek(0)
updatePosition() updatePosition()
player.close() player.close()
isVideoRecordingPlayingEvent.value = Event(false)
} }
private fun isClosed(): Boolean { private fun isClosed(): Boolean {

View file

@ -1,50 +0,0 @@
/*
* Copyright (c) 2010-2020 Belledonne Communications SARL.
*
* This file is part of linphone-android
* (see https://www.linphone.org).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.linphone.utils
import androidx.databinding.ViewDataBinding
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.LifecycleRegistry
import androidx.recyclerview.widget.RecyclerView
/**
* This class allows us to use ViewHolder as lifecycle owner so items in lists can refresh when
* a live data value changes without having to notify the adapter that the item as changed.
*/
abstract class LifecycleViewHolder(viewBinding: ViewDataBinding) : RecyclerView.ViewHolder(viewBinding.root), LifecycleOwner {
private val lifecycleRegistry = LifecycleRegistry(this) // TODO FIXME leak ?
init {
lifecycleRegistry.currentState = Lifecycle.State.INITIALIZED
}
override fun getLifecycle(): Lifecycle {
return lifecycleRegistry
}
fun attach() {
lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START)
}
fun detach() {
lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_STOP)
}
}

View file

@ -21,23 +21,20 @@ package org.linphone.utils
import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import org.linphone.activities.main.viewmodels.ListTopBarViewModel
/** abstract class SelectionListAdapter<T, VH : RecyclerView.ViewHolder>(
* This class prevents having to do in each adapter the viewHolder.attach/detach calls selectionVM: ListTopBarViewModel,
* to create lifecycle events that are required for the data binding to work correctly diff: DiffUtil.ItemCallback<T>
*/ ) :
abstract class LifecycleListAdapter<T, VH : LifecycleViewHolder>(diff: DiffUtil.ItemCallback<T>) : ListAdapter<T, VH>(diff) { ListAdapter<T, VH>(diff) {
override fun onViewAttachedToWindow(holder: VH) {
super.onViewAttachedToWindow(holder)
holder.attach()
}
override fun onViewDetachedFromWindow(holder: VH) { private var _selectionViewModel: ListTopBarViewModel? = selectionVM
super.onViewDetachedFromWindow(holder) protected val selectionViewModel get() = _selectionViewModel!!
holder.detach()
}
fun getItemAt(position: Int): T { override fun onDetachedFromRecyclerView(recyclerView: RecyclerView) {
return getItem(position) super.onDetachedFromRecyclerView(recyclerView)
_selectionViewModel = null
} }
} }

View file

@ -6,6 +6,9 @@
<variable <variable
name="clickListener" name="clickListener"
type="android.view.View.OnClickListener"/> type="android.view.View.OnClickListener"/>
<variable
name="playListener"
type="android.view.View.OnClickListener"/>
<variable <variable
name="position" name="position"
type="Integer"/> type="Integer"/>
@ -28,7 +31,7 @@
<ImageView <ImageView
android:id="@+id/record_play_pause" android:id="@+id/record_play_pause"
android:onClick="@{() -> viewModel.isPlaying ? viewModel.pause() : viewModel.play()}" android:onClick="@{playListener}"
android:selected="@{viewModel.isPlaying}" android:selected="@{viewModel.isPlaying}"
android:contentDescription="@string/content_description_recording_toggle_play" android:contentDescription="@string/content_description_recording_toggle_play"
android:layout_width="wrap_content" android:layout_width="wrap_content"