From 4368e1a5f7c02c11429642f29b84c2abf25ea0a4 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Wed, 25 Oct 2023 14:26:17 +0200 Subject: [PATCH] Indicator in meetings list for today --- .../meetings/adapter/MeetingsListAdapter.kt | 18 --------- .../meetings/fragment/MeetingsListFragment.kt | 21 ++++------- .../ui/main/meetings/model/MeetingModel.kt | 6 ++- .../viewmodel/MeetingsListViewModel.kt | 27 ++++++++++++++ .../java/org/linphone/utils/TimestampUtils.kt | 11 ++++++ app/src/main/res/layout/meeting_list_cell.xml | 37 ++++++++++++++----- app/src/main/res/values/dimen.xml | 2 + app/src/main/res/values/strings.xml | 2 + 8 files changed, 82 insertions(+), 42 deletions(-) diff --git a/app/src/main/java/org/linphone/ui/main/meetings/adapter/MeetingsListAdapter.kt b/app/src/main/java/org/linphone/ui/main/meetings/adapter/MeetingsListAdapter.kt index 794b67381..4999501af 100644 --- a/app/src/main/java/org/linphone/ui/main/meetings/adapter/MeetingsListAdapter.kt +++ b/app/src/main/java/org/linphone/ui/main/meetings/adapter/MeetingsListAdapter.kt @@ -21,8 +21,6 @@ import org.linphone.utils.HeaderAdapter class MeetingsListAdapter( private val viewLifecycleOwner: LifecycleOwner ) : ListAdapter(MeetingDiffCallback()), HeaderAdapter { - var selectedAdapterPosition = -1 - val meetingClickedEvent: MutableLiveData> by lazy { MutableLiveData>() } @@ -60,11 +58,6 @@ class MeetingsListAdapter( (holder as ViewHolder).bind(getItem(position)) } - fun resetSelection() { - notifyItemChanged(selectedAdapterPosition) - selectedAdapterPosition = -1 - } - inner class ViewHolder( val binding: MeetingListCellBinding ) : RecyclerView.ViewHolder(binding.root) { @@ -73,24 +66,13 @@ class MeetingsListAdapter( with(binding) { model = meetingModel - val hasPrevious = bindingAdapterPosition > 0 - firstMeetingOfTheDay = if (hasPrevious) { - val previous = getItem(bindingAdapterPosition - 1) - previous.day != meetingModel.day || previous.dayNumber != meetingModel.dayNumber - } else { - true - } - lifecycleOwner = viewLifecycleOwner - binding.cardview.isSelected = bindingAdapterPosition == selectedAdapterPosition - binding.setOnClickListener { meetingClickedEvent.value = Event(meetingModel) } binding.setOnLongClickListener { - selectedAdapterPosition = bindingAdapterPosition binding.root.isSelected = true meetingLongClickedEvent.value = Event(meetingModel) true diff --git a/app/src/main/java/org/linphone/ui/main/meetings/fragment/MeetingsListFragment.kt b/app/src/main/java/org/linphone/ui/main/meetings/fragment/MeetingsListFragment.kt index b097aebb7..8b9196f75 100644 --- a/app/src/main/java/org/linphone/ui/main/meetings/fragment/MeetingsListFragment.kt +++ b/app/src/main/java/org/linphone/ui/main/meetings/fragment/MeetingsListFragment.kt @@ -33,6 +33,7 @@ import org.linphone.databinding.MeetingsListFragmentBinding import org.linphone.ui.main.fragment.AbstractTopBarFragment import org.linphone.ui.main.meetings.adapter.MeetingsListAdapter import org.linphone.ui.main.meetings.viewmodel.MeetingsListViewModel +import org.linphone.utils.AppUtils import org.linphone.utils.Event import org.linphone.utils.RecyclerViewHeaderDecoration import org.linphone.utils.hideKeyboard @@ -97,6 +98,7 @@ class MeetingsListFragment : AbstractTopBarFragment() { adapter.submitList(it) Log.i("$TAG Meetings list ready with [${it.size}] items") + val newCount = it.size if (currentCount < it.size) { (view.parent as? ViewGroup)?.doOnPreDraw { startPostponedEnterTransition() @@ -147,19 +149,12 @@ class MeetingsListFragment : AbstractTopBarFragment() { private fun scrollToToday() { Log.i("$TAG Scrolling to today's meeting (if any)") val todayMeeting = listViewModel.meetings.value.orEmpty().find { - it.isToday + it.displayTodayIndicator.value == true } - val position = if (todayMeeting != null) { - val index = listViewModel.meetings.value.orEmpty().indexOf(todayMeeting) - Log.i( - "$TAG Found (at least) a meeting for today [${todayMeeting.subject.value}] at index [$index]" - ) - // Return the element before so today's event will be properly displayed (due to header) - if (index > 0) index - 1 else index - } else { - Log.i("$TAG No meeting found for today") - 0 // TODO FIXME: improve by getting closest meeting - } - binding.meetingsList.smoothScrollToPosition(position) + val index = listViewModel.meetings.value.orEmpty().indexOf(todayMeeting) + (binding.meetingsList.layoutManager as LinearLayoutManager).scrollToPositionWithOffset( + index, + AppUtils.getDimension(R.dimen.meeting_list_decoration_height).toInt() + ) } } diff --git a/app/src/main/java/org/linphone/ui/main/meetings/model/MeetingModel.kt b/app/src/main/java/org/linphone/ui/main/meetings/model/MeetingModel.kt index 5f4e03142..1ac045953 100644 --- a/app/src/main/java/org/linphone/ui/main/meetings/model/MeetingModel.kt +++ b/app/src/main/java/org/linphone/ui/main/meetings/model/MeetingModel.kt @@ -28,7 +28,7 @@ import org.linphone.utils.TimestampUtils class MeetingModel @WorkerThread constructor(conferenceInfo: ConferenceInfo) { val id = conferenceInfo.uri?.asStringUriOnly() ?: "" - private val timestamp = conferenceInfo.dateTime + val timestamp = conferenceInfo.dateTime val day = TimestampUtils.dayOfWeek(timestamp) @@ -48,6 +48,10 @@ class MeetingModel @WorkerThread constructor(conferenceInfo: ConferenceInfo) { val subject = MutableLiveData() + val firstMeetingOfTheDay = MutableLiveData() + + val displayTodayIndicator = MutableLiveData() + init { subject.postValue(conferenceInfo.subject) diff --git a/app/src/main/java/org/linphone/ui/main/meetings/viewmodel/MeetingsListViewModel.kt b/app/src/main/java/org/linphone/ui/main/meetings/viewmodel/MeetingsListViewModel.kt index 48ba5d672..c18b42e8c 100644 --- a/app/src/main/java/org/linphone/ui/main/meetings/viewmodel/MeetingsListViewModel.kt +++ b/app/src/main/java/org/linphone/ui/main/meetings/viewmodel/MeetingsListViewModel.kt @@ -29,6 +29,7 @@ import org.linphone.core.CoreListenerStub import org.linphone.core.tools.Log import org.linphone.ui.main.meetings.model.MeetingModel import org.linphone.ui.main.viewmodel.AbstractTopBarViewModel +import org.linphone.utils.TimestampUtils class MeetingsListViewModel @UiThread constructor() : AbstractTopBarViewModel() { companion object { @@ -86,6 +87,9 @@ class MeetingsListViewModel @UiThread constructor() : AbstractTopBarViewModel() ) source = coreContext.core.conferenceInformationList } + + var previousModel: MeetingModel? = null + var firstMeetingOfTodayFound = false for (info: ConferenceInfo in source) { val add = if (filter.isNotEmpty()) { val organizerCheck = info.organizer?.asStringUriOnly()?.contains( @@ -103,7 +107,30 @@ class MeetingsListViewModel @UiThread constructor() : AbstractTopBarViewModel() } if (add) { val model = MeetingModel(info) + val firstMeetingOfTheDay = if (previousModel != null) { + previousModel.day != model.day || previousModel.dayNumber != model.dayNumber + } else { + true + } + model.firstMeetingOfTheDay.postValue(firstMeetingOfTheDay) + + if (firstMeetingOfTheDay && model.isToday) { + firstMeetingOfTodayFound = true + model.displayTodayIndicator.postValue(true) + } + list.add(model) + previousModel = model + } + } + + if (!firstMeetingOfTodayFound) { + val firstMeetingAfterToday = list.find { + TimestampUtils.isAfterToday(it.timestamp) + } + Log.i("$TAG $firstMeetingAfterToday") + if (firstMeetingAfterToday != null) { + firstMeetingAfterToday.displayTodayIndicator.postValue(true) } } diff --git a/app/src/main/java/org/linphone/utils/TimestampUtils.kt b/app/src/main/java/org/linphone/utils/TimestampUtils.kt index e2be2138d..4f0b045fc 100644 --- a/app/src/main/java/org/linphone/utils/TimestampUtils.kt +++ b/app/src/main/java/org/linphone/utils/TimestampUtils.kt @@ -41,6 +41,17 @@ class TimestampUtils { return isSameDay(cal, Calendar.getInstance()) } + @AnyThread + fun isAfterToday(timestamp: Long, timestampInSecs: Boolean = true): Boolean { + val cal = Calendar.getInstance() + cal.timeInMillis = if (timestampInSecs) timestamp * 1000 else timestamp + + val tomorrow = Calendar.getInstance() + return cal[Calendar.ERA] >= tomorrow[Calendar.ERA] && + cal[Calendar.YEAR] >= tomorrow[Calendar.YEAR] && + cal[Calendar.DAY_OF_YEAR] >= tomorrow[Calendar.DAY_OF_YEAR] + } + @AnyThread fun isYesterday(timestamp: Long, timestampInSecs: Boolean = true): Boolean { val yesterday = Calendar.getInstance() diff --git a/app/src/main/res/layout/meeting_list_cell.xml b/app/src/main/res/layout/meeting_list_cell.xml index 34a07c9a8..64e5d67e0 100644 --- a/app/src/main/res/layout/meeting_list_cell.xml +++ b/app/src/main/res/layout/meeting_list_cell.xml @@ -14,29 +14,46 @@ - + + + + + app:layout_constraintTop_toBottomOf="@id/today_separator"/> @@ -54,7 +71,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{model.dayNumber, default=`19`}" - android:visibility="@{firstMeetingOfTheDay ? View.VISIBLE : View.INVISIBLE}" + android:visibility="@{model.firstMeetingOfTheDay ? View.VISIBLE : View.INVISIBLE}" android:textColor="@{model.isToday ? @color/white : @color/gray_main2_500, default=@color/gray_main2_500}" android:textSize="20sp" android:paddingBottom="4dp" @@ -71,14 +88,14 @@ android:layout_height="wrap_content" android:layout_marginStart="24dp" android:layout_marginEnd="5dp" - android:layout_marginTop="@{firstMeetingOfTheDay ? @dimen/meeting_margin : @dimen/zero, default=@dimen/zero}" + android:layout_marginTop="@{model.firstMeetingOfTheDay ? @dimen/meeting_margin : @dimen/zero, default=@dimen/zero}" android:layout_marginBottom="8dp" app:cardBackgroundColor="@color/list_cell_background_color" app:cardElevation="5dp" app:cardCornerRadius="10dp" app:layout_constraintStart_toEndOf="@id/header_day" app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintTop_toTopOf="parent" + app:layout_constraintTop_toBottomOf="@id/today_separator" app:layout_constraintBottom_toBottomOf="parent"> 15dp 30dp + 66dp + 400dp 300dp 400dp diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 6f4c0e9fc..c243b4e2d 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -373,6 +373,8 @@ %s %s No meeting for the moment… + Today + New meeting Meeting Broadcast