Indicator in meetings list for today

This commit is contained in:
Sylvain Berfini 2023-10-25 14:26:17 +02:00
parent a129cc5f95
commit 4368e1a5f7
8 changed files with 82 additions and 42 deletions

View file

@ -21,8 +21,6 @@ import org.linphone.utils.HeaderAdapter
class MeetingsListAdapter(
private val viewLifecycleOwner: LifecycleOwner
) : ListAdapter<MeetingModel, RecyclerView.ViewHolder>(MeetingDiffCallback()), HeaderAdapter {
var selectedAdapterPosition = -1
val meetingClickedEvent: MutableLiveData<Event<MeetingModel>> by lazy {
MutableLiveData<Event<MeetingModel>>()
}
@ -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

View file

@ -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()
)
}
}

View file

@ -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<String>()
val firstMeetingOfTheDay = MutableLiveData<Boolean>()
val displayTodayIndicator = MutableLiveData<Boolean>()
init {
subject.postValue(conferenceInfo.subject)

View file

@ -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)
}
}

View file

@ -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()

View file

@ -14,29 +14,46 @@
<variable
name="onLongClickListener"
type="View.OnLongClickListener" />
<variable
name="firstMeetingOfTheDay"
type="Boolean" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.appcompat.widget.AppCompatTextView
style="@style/default_text_style_300"
android:id="@+id/today_indicator"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:text="@string/meetings_list_today_indicator"
android:textSize="12sp"
android:textColor="@color/orange_main_500"
android:visibility="@{model.displayTodayIndicator ? View.VISIBLE : View.GONE, default=gone}"
app:layout_constraintTop_toTopOf="parent" />
<View
android:id="@+id/today_separator"
android:layout_width="match_parent"
android:layout_height="2dp"
android:background="@color/orange_main_500"
android:visibility="@{model.displayTodayIndicator ? View.VISIBLE : View.GONE, default=gone}"
app:layout_constraintTop_toBottomOf="@id/today_indicator" />
<androidx.appcompat.widget.AppCompatTextView
style="@style/default_text_style"
android:id="@+id/header_day"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
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_marginStart="5dp"
android:text="@{model.day, default=`Mon.`}"
android:visibility="@{firstMeetingOfTheDay ? View.VISIBLE : View.INVISIBLE}"
android:visibility="@{model.firstMeetingOfTheDay ? View.VISIBLE : View.INVISIBLE}"
android:textColor="@color/gray_main2_500"
android:textSize="14sp"
app:layout_constraintStart_toStartOf="@id/header_day_number"
app:layout_constraintEnd_toEndOf="@id/header_day_number"
app:layout_constraintTop_toTopOf="parent"/>
app:layout_constraintTop_toBottomOf="@id/today_separator"/>
<ImageView
android:id="@+id/today_background"
@ -44,7 +61,7 @@
android:layout_height="32dp"
android:layout_marginStart="5dp"
android:src="@drawable/shape_circle_primary_background"
android:visibility="@{model.isToday &amp;&amp; firstMeetingOfTheDay ? View.VISIBLE : View.INVISIBLE}"
android:visibility="@{model.isToday &amp;&amp; model.firstMeetingOfTheDay ? View.VISIBLE : View.INVISIBLE}"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/header_day" />
@ -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">
<androidx.constraintlayout.widget.ConstraintLayout

View file

@ -50,6 +50,8 @@
<dimen name="call_button_icon_padding">15dp</dimen>
<dimen name="call_extra_button_top_margin">30dp</dimen>
<dimen name="meeting_list_decoration_height">66dp</dimen>
<dimen name="toast_max_width">400dp</dimen>
<dimen name="toast_text_max_width">300dp</dimen>
<dimen name="button_max_width">400dp</dimen>

View file

@ -373,6 +373,8 @@
<string name="message_reactions_info_emoji_title">%s %s</string>
<string name="meetings_list_empty">No meeting for the moment…</string>
<string name="meetings_list_today_indicator">Today</string>
<string name="meeting_schedule_title">New meeting</string>
<string name="meeting_schedule_meeting_label">Meeting</string>
<string name="meeting_schedule_broadcast_label">Broadcast</string>