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

View file

@ -22,8 +22,11 @@ package org.linphone.activities.main.chat.adapters
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.databinding.DataBindingUtil
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.MutableLiveData
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import org.linphone.LinphoneApplication.Companion.coreContext
import org.linphone.R
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.databinding.ChatRoomCreationContactCellBinding
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 selectedAddresses = MutableLiveData<ArrayList<Address>>()
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(
LayoutInflater.from(parent.context),
R.layout.chat_room_creation_contact_cell, parent, false
)
val viewHolder = ViewHolder(binding)
binding.lifecycleOwner = viewHolder
return viewHolder
return ViewHolder(binding)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind(getItem(position))
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
(holder as ViewHolder).bind(getItem(position))
}
inner class ViewHolder(
private val binding: ChatRoomCreationContactCellBinding
) : LifecycleViewHolder(binding) {
) : RecyclerView.ViewHolder(binding.root) {
fun bind(searchResult: SearchResult) {
with(binding) {
val searchResultViewModel = ChatRoomCreationContactViewModel(searchResult)
viewModel = searchResultViewModel
securityEnabled.observe(this@ViewHolder, {
updateSecurity(searchResult, searchResultViewModel, it)
})
binding.lifecycleOwner = viewLifecycleOwner
selectedAddresses.observe(this@ViewHolder, {
val selected = it.find { address ->
val searchAddress = searchResult.address
if (searchAddress != null) address.weakEqual(searchAddress) else false
}
searchResultViewModel.isSelected.value = selected != null
})
updateSecurity(searchResult, searchResultViewModel, securityEnabled)
val selected = selectedAddresses.find { address ->
val searchAddress = searchResult.address
if (searchAddress != null) address.weakEqual(searchAddress) else false
}
searchResultViewModel.isSelected.value = selected != null
setClickListener {
selectedContact.value = Event(searchResult)

View file

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

View file

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

View file

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

View file

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

View file

@ -56,18 +56,33 @@ import org.linphone.databinding.ChatRoomDetailFragmentBinding
import org.linphone.utils.*
import org.linphone.utils.Event
class DetailChatRoomFragment : MasterFragment<ChatRoomDetailFragmentBinding>() {
class DetailChatRoomFragment : MasterFragment<ChatRoomDetailFragmentBinding, ChatMessagesListAdapter>() {
private lateinit var viewModel: ChatRoomViewModel
private lateinit var chatSendingViewModel: ChatMessageSendingViewModel
private lateinit var listViewModel: ChatMessagesListViewModel
private lateinit var adapter: ChatMessagesListAdapter
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
override fun getLayoutId(): Int {
return R.layout.chat_room_detail_fragment
}
override fun onDestroyView() {
binding.chatMessagesList.adapter = null
adapter.unregisterAdapterDataObserver(observer)
super.onDestroyView()
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
@ -98,18 +113,11 @@ class DetailChatRoomFragment : MasterFragment<ChatRoomDetailFragmentBinding>() {
ChatMessagesListViewModelFactory(chatRoom)
)[ChatMessagesListViewModel::class.java]
adapter = ChatMessagesListAdapter(listSelectionViewModel)
_adapter = ChatMessagesListAdapter(listSelectionViewModel, viewLifecycleOwner)
// SubmitList is done on a background thread
// 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
adapter.registerAdapterDataObserver(observer)
val layoutManager = LinearLayoutManager(activity)
layoutManager.stackFromEnd = true
@ -265,7 +273,7 @@ class DetailChatRoomFragment : MasterFragment<ChatRoomDetailFragmentBinding>() {
override fun deleteItems(indexesOfItemToDelete: ArrayList<Int>) {
val list = ArrayList<EventLog>()
for (index in indexesOfItemToDelete) {
val eventLog = adapter.getItemAt(index)
val eventLog = adapter.currentList[index]
list.add(eventLog)
}
listViewModel.deleteEventLogs(list)

View file

@ -67,7 +67,10 @@ class GroupInfoFragment : GenericFragment<ChatRoomGroupInfoFragmentBinding>() {
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
val layoutManager = LinearLayoutManager(activity)

View file

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

View file

@ -43,14 +43,31 @@ import org.linphone.core.tools.Log
import org.linphone.databinding.ChatRoomMasterFragmentBinding
import org.linphone.utils.*
class MasterChatRoomsFragment : MasterFragment<ChatRoomMasterFragmentBinding>() {
class MasterChatRoomsFragment : MasterFragment<ChatRoomMasterFragmentBinding, ChatRoomsListAdapter>() {
override val dialogConfirmationMessageBeforeRemoval = R.plurals.chat_room_delete_dialog
private lateinit var listViewModel: ChatRoomsListViewModel
private lateinit var adapter: ChatRoomsListAdapter
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 onDestroyView() {
binding.chatList.adapter = null
adapter.unregisterAdapterDataObserver(observer)
super.onDestroyView()
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
@ -63,20 +80,10 @@ class MasterChatRoomsFragment : MasterFragment<ChatRoomMasterFragmentBinding>()
ViewModelProvider(this).get(SharedMainViewModel::class.java)
} ?: throw Exception("Invalid Activity")
adapter = ChatRoomsListAdapter(listSelectionViewModel)
_adapter = ChatRoomsListAdapter(listSelectionViewModel, viewLifecycleOwner)
// SubmitList is done on a background thread
// We need this adapter data observer to know when to scroll
adapter.registerAdapterDataObserver(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()
}
})
adapter.registerAdapterDataObserver(observer)
binding.chatList.adapter = adapter
val layoutManager = LinearLayoutManager(activity)
@ -100,7 +107,7 @@ class MasterChatRoomsFragment : MasterFragment<ChatRoomMasterFragmentBinding>()
}
viewModel.showDeleteButton({
listViewModel.deleteChatRoom(adapter.getItemAt(viewHolder.adapterPosition))
listViewModel.deleteChatRoom(adapter.currentList[viewHolder.adapterPosition])
dialog.dismiss()
}, 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 {
listSelectionViewModel.isEditionEnabled.value = true
}
@ -201,7 +214,7 @@ class MasterChatRoomsFragment : MasterFragment<ChatRoomMasterFragmentBinding>()
override fun deleteItems(indexesOfItemToDelete: ArrayList<Int>) {
val list = ArrayList<ChatRoom>()
for (index in indexesOfItemToDelete) {
val chatRoom = adapter.getItemAt(index)
val chatRoom = adapter.currentList[index]
list.add(chatRoom)
}
listViewModel.deleteChatRooms(list)

View file

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

View file

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

View file

@ -44,10 +44,9 @@ import org.linphone.core.tools.Log
import org.linphone.databinding.ContactMasterFragmentBinding
import org.linphone.utils.*
class MasterContactsFragment : MasterFragment<ContactMasterFragmentBinding>() {
class MasterContactsFragment : MasterFragment<ContactMasterFragmentBinding, ContactsListAdapter>() {
override val dialogConfirmationMessageBeforeRemoval = R.plurals.contact_delete_dialog
private lateinit var listViewModel: ContactsListViewModel
private lateinit var adapter: ContactsListAdapter
private lateinit var sharedViewModel: SharedMainViewModel
private var sipUriToAdd: String? = null
@ -55,6 +54,11 @@ class MasterContactsFragment : MasterFragment<ContactMasterFragmentBinding>() {
override fun getLayoutId(): Int = R.layout.contact_master_fragment
override fun onDestroyView() {
binding.contactsList.adapter = null
super.onDestroyView()
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
@ -67,7 +71,7 @@ class MasterContactsFragment : MasterFragment<ContactMasterFragmentBinding>() {
ViewModelProvider(this).get(SharedMainViewModel::class.java)
} ?: throw Exception("Invalid Activity")
adapter = ContactsListAdapter(listSelectionViewModel)
_adapter = ContactsListAdapter(listSelectionViewModel, viewLifecycleOwner)
binding.contactsList.adapter = adapter
binding.setEditClickListener {
@ -100,7 +104,7 @@ class MasterContactsFragment : MasterFragment<ContactMasterFragmentBinding>() {
}
viewModel.showDeleteButton({
listViewModel.deleteContact(adapter.getItemAt(viewHolder.adapterPosition))
listViewModel.deleteContact(adapter.currentList[viewHolder.adapterPosition])
dialog.dismiss()
}, getString(R.string.dialog_delete))
@ -211,7 +215,7 @@ class MasterContactsFragment : MasterFragment<ContactMasterFragmentBinding>() {
override fun deleteItems(indexesOfItemToDelete: ArrayList<Int>) {
val list = ArrayList<Contact>()
for (index in indexesOfItemToDelete) {
val contact = adapter.getItemAt(index)
val contact = adapter.currentList[index]
list.add(contact)
}
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.utils.AppUtils
import org.linphone.utils.DialogUtils
import org.linphone.utils.SelectionListAdapter
/**
* This fragment can be inherited by all fragments that will display a list
* 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 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.ViewGroup
import androidx.databinding.DataBindingUtil
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.MutableLiveData
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView
import org.linphone.R
import org.linphone.activities.main.history.viewmodels.CallLogViewModel
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.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 {
MutableLiveData<Event<GroupedCallLogViewModel>>()
}
@ -44,31 +49,31 @@ class CallLogsListAdapter(val selectionViewModel: ListTopBarViewModel) : Lifecyc
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(
LayoutInflater.from(parent.context),
R.layout.history_list_cell, parent, false
)
val viewHolder = ViewHolder(binding)
binding.lifecycleOwner = viewHolder
return viewHolder
return ViewHolder(binding)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind(getItem(position))
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
(holder as ViewHolder).bind(getItem(position))
}
inner class ViewHolder(
private val binding: HistoryListCellBinding
) : LifecycleViewHolder(binding) {
) : RecyclerView.ViewHolder(binding.root) {
fun bind(callLogGroup: GroupedCallLogViewModel) {
with(binding) {
val callLogViewModel = CallLogViewModel(callLogGroup.lastCallLog)
viewModel = callLogViewModel
binding.lifecycleOwner = viewLifecycleOwner
// This is for item selection through ListTopBarFragment
selectionListViewModel = selectionViewModel
selectionViewModel.isEditionEnabled.observe(this@ViewHolder, {
selectionViewModel.isEditionEnabled.observe(viewLifecycleOwner, {
position = adapterPosition
})

View file

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

View file

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

View file

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

View file

@ -37,7 +37,6 @@ import org.linphone.core.AudioDevice
import org.linphone.core.Player
import org.linphone.core.PlayerListener
import org.linphone.core.tools.Log
import org.linphone.utils.Event
import org.linphone.utils.LinphoneUtils
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 isPlaying = MutableLiveData<Boolean>()
val isVideoRecordingPlayingEvent: MutableLiveData<Event<Boolean>> by lazy {
MutableLiveData<Event<Boolean>>()
}
private val tickerChannel = ticker(1000, 1000)
private lateinit var player: Player
@ -68,6 +63,30 @@ class RecordingViewModel(val path: String) : ViewModel(), Comparable<RecordingVi
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 {
val m = RECORD_PATTERN.matcher(path)
if (m.matches() && m.groupCount() >= 2) {
@ -84,6 +103,7 @@ class RecordingViewModel(val path: String) : ViewModel(), Comparable<RecordingVi
override fun onCleared() {
tickerChannel.cancel()
player.setWindowId(null)
if (!isClosed()) player.close()
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() {
@ -135,28 +157,7 @@ class RecordingViewModel(val path: String) : ViewModel(), Comparable<RecordingVi
if (textureView.isAvailable) {
player.setWindowId(textureView.surfaceTexture)
} else {
textureView.surfaceTextureListener = object : TextureView.SurfaceTextureListener {
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)
}
}
textureView.surfaceTextureListener = textureViewListener
}
}
@ -197,7 +198,6 @@ class RecordingViewModel(val path: String) : ViewModel(), Comparable<RecordingVi
player.seek(0)
updatePosition()
player.close()
isVideoRecordingPlayingEvent.value = Event(false)
}
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.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import org.linphone.activities.main.viewmodels.ListTopBarViewModel
/**
* This class prevents having to do in each adapter the viewHolder.attach/detach calls
* to create lifecycle events that are required for the data binding to work correctly
*/
abstract class LifecycleListAdapter<T, VH : LifecycleViewHolder>(diff: DiffUtil.ItemCallback<T>) : ListAdapter<T, VH>(diff) {
override fun onViewAttachedToWindow(holder: VH) {
super.onViewAttachedToWindow(holder)
holder.attach()
}
abstract class SelectionListAdapter<T, VH : RecyclerView.ViewHolder>(
selectionVM: ListTopBarViewModel,
diff: DiffUtil.ItemCallback<T>
) :
ListAdapter<T, VH>(diff) {
override fun onViewDetachedFromWindow(holder: VH) {
super.onViewDetachedFromWindow(holder)
holder.detach()
}
private var _selectionViewModel: ListTopBarViewModel? = selectionVM
protected val selectionViewModel get() = _selectionViewModel!!
fun getItemAt(position: Int): T {
return getItem(position)
override fun onDetachedFromRecyclerView(recyclerView: RecyclerView) {
super.onDetachedFromRecyclerView(recyclerView)
_selectionViewModel = null
}
}

View file

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