Grouped call logs in history for a shorter list

This commit is contained in:
Sylvain Berfini 2020-06-24 15:27:53 +02:00
parent 5e540dce71
commit cadd5a84b2
7 changed files with 116 additions and 59 deletions

View file

@ -113,7 +113,7 @@ class MasterChatRoomsFragment : MasterFragment() {
} }
viewModel.showDeleteButton({ viewModel.showDeleteButton({
listViewModel.deleteChatRoom(listViewModel.chatRooms.value?.get(viewHolder.adapterPosition)) listViewModel.deleteChatRoom(adapter.getItemAt(viewHolder.adapterPosition))
dialog.dismiss() dialog.dismiss()
}, getString(R.string.dialog_delete)) }, getString(R.string.dialog_delete))

View file

@ -112,7 +112,7 @@ class MasterContactsFragment : MasterFragment() {
} }
viewModel.showDeleteButton({ viewModel.showDeleteButton({
listViewModel.deleteContact(listViewModel.contactsList.value?.get(viewHolder.adapterPosition)) listViewModel.deleteContact(adapter.getItemAt(viewHolder.adapterPosition))
dialog.dismiss() dialog.dismiss()
}, getString(R.string.dialog_delete)) }, getString(R.string.dialog_delete))

View file

@ -29,16 +29,16 @@ import androidx.lifecycle.Observer
import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.DiffUtil
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.viewmodels.ListTopBarViewModel import org.linphone.activities.main.viewmodels.ListTopBarViewModel
import org.linphone.core.Address import org.linphone.core.Address
import org.linphone.core.CallLog
import org.linphone.databinding.GenericListHeaderBinding 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<CallLog, CallLogsListAdapter.ViewHolder>(CallLogDiffCallback()), HeaderAdapter { class CallLogsListAdapter(val selectionViewModel: ListTopBarViewModel) : LifecycleListAdapter<GroupedCallLogViewModel, CallLogsListAdapter.ViewHolder>(CallLogDiffCallback()), HeaderAdapter {
val selectedCallLogEvent: MutableLiveData<Event<CallLog>> by lazy { val selectedCallLogEvent: MutableLiveData<Event<GroupedCallLogViewModel>> by lazy {
MutableLiveData<Event<CallLog>>() MutableLiveData<Event<GroupedCallLogViewModel>>()
} }
val startCallToEvent: MutableLiveData<Event<Address>> by lazy { val startCallToEvent: MutableLiveData<Event<Address>> by lazy {
@ -62,9 +62,9 @@ class CallLogsListAdapter(val selectionViewModel: ListTopBarViewModel) : Lifecyc
inner class ViewHolder( inner class ViewHolder(
private val binding: HistoryListCellBinding private val binding: HistoryListCellBinding
) : LifecycleViewHolder(binding) { ) : LifecycleViewHolder(binding) {
fun bind(callLog: CallLog) { fun bind(callLogGroup: GroupedCallLogViewModel) {
with(binding) { with(binding) {
val callLogViewModel = CallLogViewModel(callLog) val callLogViewModel = CallLogViewModel(callLogGroup.lastCallLog)
viewModel = callLogViewModel viewModel = callLogViewModel
// This is for item selection through ListTopBarFragment // This is for item selection through ListTopBarFragment
@ -77,15 +77,17 @@ class CallLogsListAdapter(val selectionViewModel: ListTopBarViewModel) : Lifecyc
if (selectionViewModel.isEditionEnabled.value == true) { if (selectionViewModel.isEditionEnabled.value == true) {
selectionViewModel.onToggleSelect(adapterPosition) selectionViewModel.onToggleSelect(adapterPosition)
} else { } else {
startCallToEvent.value = Event(callLog.remoteAddress) startCallToEvent.value = Event(callLogGroup.lastCallLog.remoteAddress)
} }
} }
// This listener is disabled when in edition mode // This listener is disabled when in edition mode
setDetailsClickListener { setDetailsClickListener {
selectedCallLogEvent.value = Event(callLog) selectedCallLogEvent.value = Event(callLogGroup)
} }
groupCount = callLogGroup.callLogs.size
executePendingBindings() executePendingBindings()
} }
} }
@ -93,18 +95,18 @@ class CallLogsListAdapter(val selectionViewModel: ListTopBarViewModel) : Lifecyc
override fun displayHeaderForPosition(position: Int): Boolean { override fun displayHeaderForPosition(position: Int): Boolean {
if (position >= itemCount) return false if (position >= itemCount) return false
val callLog = getItem(position) val callLogGroup = getItem(position)
val date = callLog.startDate val date = callLogGroup.lastCallLog.startDate
val previousPosition = position - 1 val previousPosition = position - 1
return if (previousPosition >= 0) { return if (previousPosition >= 0) {
val previousItemDate = getItem(previousPosition).startDate val previousItemDate = getItem(previousPosition).lastCallLog.startDate
!TimestampUtils.isSameDay(date, previousItemDate) !TimestampUtils.isSameDay(date, previousItemDate)
} else true } else true
} }
override fun getHeaderViewForPosition(context: Context, position: Int): View { override fun getHeaderViewForPosition(context: Context, position: Int): View {
val callLog = getItem(position) val callLog = getItem(position)
val date = formatDate(context, callLog.startDate) val date = formatDate(context, callLog.lastCallLog.startDate)
val binding: GenericListHeaderBinding = DataBindingUtil.inflate( val binding: GenericListHeaderBinding = DataBindingUtil.inflate(
LayoutInflater.from(context), LayoutInflater.from(context),
R.layout.generic_list_header, null, false R.layout.generic_list_header, null, false
@ -124,17 +126,17 @@ class CallLogsListAdapter(val selectionViewModel: ListTopBarViewModel) : Lifecyc
} }
} }
private class CallLogDiffCallback : DiffUtil.ItemCallback<CallLog>() { private class CallLogDiffCallback : DiffUtil.ItemCallback<GroupedCallLogViewModel>() {
override fun areItemsTheSame( override fun areItemsTheSame(
oldItem: CallLog, oldItem: GroupedCallLogViewModel,
newItem: CallLog newItem: GroupedCallLogViewModel
): Boolean { ): Boolean {
return oldItem.callId == newItem.callId return oldItem.lastCallLog.callId == newItem.lastCallLog.callId
} }
override fun areContentsTheSame( override fun areContentsTheSame(
oldItem: CallLog, oldItem: GroupedCallLogViewModel,
newItem: CallLog newItem: GroupedCallLogViewModel
): Boolean { ): Boolean {
return false // For headers return false // For headers
} }

View file

@ -38,10 +38,10 @@ import org.linphone.R
import org.linphone.activities.main.fragments.MasterFragment import org.linphone.activities.main.fragments.MasterFragment
import org.linphone.activities.main.history.adapters.CallLogsListAdapter import org.linphone.activities.main.history.adapters.CallLogsListAdapter
import org.linphone.activities.main.history.viewmodels.CallLogsListViewModel import org.linphone.activities.main.history.viewmodels.CallLogsListViewModel
import org.linphone.activities.main.history.viewmodels.GroupedCallLogViewModel
import org.linphone.activities.main.viewmodels.DialogViewModel import org.linphone.activities.main.viewmodels.DialogViewModel
import org.linphone.activities.main.viewmodels.SharedMainViewModel import org.linphone.activities.main.viewmodels.SharedMainViewModel
import org.linphone.activities.main.viewmodels.TabsViewModel import org.linphone.activities.main.viewmodels.TabsViewModel
import org.linphone.core.CallLog
import org.linphone.core.tools.Log import org.linphone.core.tools.Log
import org.linphone.databinding.HistoryMasterFragmentBinding import org.linphone.databinding.HistoryMasterFragmentBinding
import org.linphone.utils.* import org.linphone.utils.*
@ -109,7 +109,7 @@ class MasterCallLogsFragment : MasterFragment() {
} }
viewModel.showDeleteButton({ viewModel.showDeleteButton({
listViewModel.deleteCallLog(listViewModel.callLogs.value?.get(viewHolder.adapterPosition)) listViewModel.deleteCallLogGroup(adapter.getItemAt(viewHolder.adapterPosition))
dialog.dismiss() dialog.dismiss()
}, getString(R.string.dialog_delete)) }, getString(R.string.dialog_delete))
@ -156,7 +156,7 @@ class MasterCallLogsFragment : MasterFragment() {
adapter.selectedCallLogEvent.observe(viewLifecycleOwner, Observer { adapter.selectedCallLogEvent.observe(viewLifecycleOwner, Observer {
it.consume { callLog -> it.consume { callLog ->
sharedViewModel.selectedCallLog.value = callLog sharedViewModel.selectedCallLog.value = callLog.lastCallLog
if (!resources.getBoolean(R.bool.isTablet)) { if (!resources.getBoolean(R.bool.isTablet)) {
if (findNavController().currentDestination?.id == R.id.masterCallLogsFragment) { if (findNavController().currentDestination?.id == R.id.masterCallLogsFragment) {
findNavController().navigate(R.id.action_masterCallLogsFragment_to_detailCallLogFragment) findNavController().navigate(R.id.action_masterCallLogsFragment_to_detailCallLogFragment)
@ -212,12 +212,12 @@ class MasterCallLogsFragment : MasterFragment() {
} }
override fun deleteItems(indexesOfItemToDelete: ArrayList<Int>) { override fun deleteItems(indexesOfItemToDelete: ArrayList<Int>) {
val list = ArrayList<CallLog>() val list = ArrayList<GroupedCallLogViewModel>()
for (index in indexesOfItemToDelete) { for (index in indexesOfItemToDelete) {
val callLog = adapter.getItemAt(index) val callLogGroup = adapter.getItemAt(index)
list.add(callLog) list.add(callLogGroup)
} }
listViewModel.deleteCallLogs(list) listViewModel.deleteCallLogGroups(list)
} }
private fun scrollToTop() { private fun scrollToTop() {

View file

@ -26,10 +26,11 @@ import org.linphone.contact.ContactsUpdatedListenerStub
import org.linphone.core.* import org.linphone.core.*
import org.linphone.core.tools.Log import org.linphone.core.tools.Log
import org.linphone.utils.Event import org.linphone.utils.Event
import org.linphone.utils.TimestampUtils
class CallLogsListViewModel : ViewModel() { class CallLogsListViewModel : ViewModel() {
val callLogs = MutableLiveData<ArrayList<CallLog>>() val callLogs = MutableLiveData<ArrayList<GroupedCallLogViewModel>>()
val missedCallLogs = MutableLiveData<ArrayList<CallLog>>() val missedCallLogs = MutableLiveData<ArrayList<GroupedCallLogViewModel>>()
val missedCallLogsSelected = MutableLiveData<Boolean>() val missedCallLogsSelected = MutableLiveData<Boolean>()
@ -70,47 +71,71 @@ class CallLogsListViewModel : ViewModel() {
super.onCleared() super.onCleared()
} }
fun deleteCallLog(callLog: CallLog?) { fun deleteCallLogGroup(callLog: GroupedCallLogViewModel?) {
val list = arrayListOf<CallLog>()
list.addAll(callLogs.value.orEmpty())
val missedList = arrayListOf<CallLog>()
missedList.addAll(missedCallLogs.value.orEmpty())
if (callLog != null) { if (callLog != null) {
coreContext.core.removeCallLog(callLog) for (log in callLog.callLogs) {
list.remove(callLog) coreContext.core.removeCallLog(log)
missedList.remove(callLog) }
} }
callLogs.value = list updateCallLogs()
missedCallLogs.value = missedList
} }
fun deleteCallLogs(listToDelete: ArrayList<CallLog>) { fun deleteCallLogGroups(listToDelete: ArrayList<GroupedCallLogViewModel>) {
val list = arrayListOf<CallLog>()
list.addAll(callLogs.value.orEmpty())
val missedList = arrayListOf<CallLog>()
missedList.addAll(missedCallLogs.value.orEmpty())
for (callLog in listToDelete) { for (callLog in listToDelete) {
coreContext.core.removeCallLog(callLog) for (log in callLog.callLogs) {
list.remove(callLog) coreContext.core.removeCallLog(log)
missedList.remove(callLog) }
} }
callLogs.value = list updateCallLogs()
missedCallLogs.value = missedList
} }
private fun updateCallLogs() { private fun updateCallLogs() {
val list = arrayListOf<CallLog>() val list = arrayListOf<GroupedCallLogViewModel>()
val missedList = arrayListOf<CallLog>() val missedList = arrayListOf<GroupedCallLogViewModel>()
var previousCallLogGroup: GroupedCallLogViewModel? = null
var previousMissedCallLogGroup: GroupedCallLogViewModel? = null
for (callLog in coreContext.core.callLogs) { for (callLog in coreContext.core.callLogs) {
list.add(callLog) if (previousCallLogGroup == null) {
if (callLog.status == Call.Status.Missed) missedList.add(callLog) previousCallLogGroup = GroupedCallLogViewModel(callLog)
} else if (previousCallLogGroup.lastCallLog.localAddress.weakEqual(callLog.localAddress) && previousCallLogGroup.lastCallLog.remoteAddress.weakEqual(callLog.remoteAddress)) {
if (TimestampUtils.isSameDay(previousCallLogGroup.lastCallLog.startDate, callLog.startDate)) {
previousCallLogGroup.callLogs.add(callLog)
previousCallLogGroup.lastCallLog = callLog
} else {
list.add(previousCallLogGroup)
previousCallLogGroup = GroupedCallLogViewModel(callLog)
}
} else {
list.add(previousCallLogGroup)
previousCallLogGroup = GroupedCallLogViewModel(callLog)
}
if (callLog.status == Call.Status.Missed) {
if (previousMissedCallLogGroup == null) {
previousMissedCallLogGroup = GroupedCallLogViewModel(callLog)
} else if (previousMissedCallLogGroup.lastCallLog.localAddress.weakEqual(callLog.localAddress) && previousMissedCallLogGroup.lastCallLog.remoteAddress.weakEqual(callLog.remoteAddress)) {
if (TimestampUtils.isSameDay(previousMissedCallLogGroup.lastCallLog.startDate, callLog.startDate)) {
previousMissedCallLogGroup.callLogs.add(callLog)
previousMissedCallLogGroup.lastCallLog = callLog
} else {
missedList.add(previousMissedCallLogGroup)
previousMissedCallLogGroup = GroupedCallLogViewModel(callLog)
}
} else {
missedList.add(previousMissedCallLogGroup)
previousMissedCallLogGroup = GroupedCallLogViewModel(callLog)
}
}
}
if (previousCallLogGroup != null && !list.contains(previousCallLogGroup)) {
list.add(previousCallLogGroup)
}
if (previousMissedCallLogGroup != null && !missedList.contains(previousMissedCallLogGroup)) {
missedList.add(previousMissedCallLogGroup)
} }
callLogs.value = list callLogs.value = list

View file

@ -0,0 +1,27 @@
/*
* 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.activities.main.history.viewmodels
import org.linphone.core.CallLog
class GroupedCallLogViewModel(callLog: CallLog) {
var lastCallLog: CallLog = callLog
val callLogs = arrayListOf(callLog)
}

View file

@ -20,6 +20,9 @@
<variable <variable
name="selectionListViewModel" name="selectionListViewModel"
type="org.linphone.activities.main.viewmodels.ListTopBarViewModel" /> type="org.linphone.activities.main.viewmodels.ListTopBarViewModel" />
<variable
name="groupCount"
type="Integer"/>
</data> </data>
<RelativeLayout <RelativeLayout
@ -53,7 +56,7 @@
<CheckBox <CheckBox
android:onClick="@{() -> selectionListViewModel.onToggleSelect(position)}" android:onClick="@{() -> selectionListViewModel.onToggleSelect(position)}"
android:visibility="@{selectionListViewModel.isEditionEnabled ? View.VISIBLE : View.GONE}" android:visibility="@{selectionListViewModel.isEditionEnabled ? View.VISIBLE : View.GONE, default=gone}"
android:checked="@{selectionListViewModel.selectedItems.contains(position)}" android:checked="@{selectionListViewModel.selectedItems.contains(position)}"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="match_parent" android:layout_height="match_parent"
@ -90,7 +93,7 @@
<org.linphone.views.MarqueeTextView <org.linphone.views.MarqueeTextView
android:id="@+id/sip_uri" android:id="@+id/sip_uri"
android:text="@{viewModel.contact.fullName ?? viewModel.displayName}" android:text="@{(viewModel.contact.fullName ?? viewModel.displayName) + (groupCount > 1 ? ` (` + groupCount + `)` : ``)}"
style="@style/contact_name_list_cell_font" style="@style/contact_name_list_cell_font"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="match_parent" android:layout_height="match_parent"