Reworked MainActivity's top bar, split alerts & in-call notifications, added file sharing dedicated one

This commit is contained in:
Sylvain Berfini 2025-06-16 10:42:14 +02:00
parent b2281430ae
commit c2db82354e
8 changed files with 255 additions and 76 deletions

View file

@ -46,6 +46,7 @@ import androidx.core.view.updatePadding
import androidx.databinding.DataBindingUtil
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.observe
import androidx.navigation.NavController
import androidx.navigation.NavDestination
import androidx.navigation.NavOptions
@ -154,7 +155,8 @@ class MainActivity : GenericActivity() {
binding.lifecycleOwner = this
setUpToastsArea(binding.toastsArea)
ViewCompat.setOnApplyWindowInsetsListener(binding.inCallTopBar.root) { v, windowInsets ->
// Will give the device's status bar background color
ViewCompat.setOnApplyWindowInsetsListener(binding.notificationsArea) { v, windowInsets ->
val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
v.updatePadding(0, insets.top, 0, 0)
windowInsets
@ -248,6 +250,20 @@ class MainActivity : GenericActivity() {
}
}
viewModel.clearFilesPendingSharingEvent.observe(this) {
it.consume {
sharedViewModel.filesToShareFromIntent.value = arrayListOf<String>()
}
}
sharedViewModel.filesToShareFromIntent.observe(this) { list ->
if (list.isNotEmpty()) {
viewModel.addFilesPendingSharing(list)
} else {
viewModel.filesPendingSharingListCleared()
}
}
// Wait for latest visited fragment to be displayed before hiding the splashscreen
binding.root.viewTreeObserver.addOnPreDrawListener(object : ViewTreeObserver.OnPreDrawListener {
override fun onPreDraw(): Boolean {

View file

@ -44,7 +44,6 @@ import org.linphone.ui.main.chat.adapter.ConversationsListAdapter
import org.linphone.ui.main.chat.viewmodel.ConversationsListViewModel
import org.linphone.ui.main.fragment.AbstractMainFragment
import org.linphone.ui.main.history.fragment.HistoryMenuDialogFragment
import org.linphone.utils.AppUtils
import org.linphone.utils.Event
import org.linphone.utils.LinphoneUtils
@ -251,20 +250,6 @@ class ConversationsListFragment : AbstractMainFragment() {
}
}
sharedViewModel.filesToShareFromIntent.observe(viewLifecycleOwner) { filesToShare ->
val count = filesToShare.size
if (count > 0) {
val message = AppUtils.getStringWithPlural(
R.plurals.conversations_files_waiting_to_be_shared_toast,
count,
filesToShare.size.toString()
)
val icon = R.drawable.file
(requireActivity() as GenericActivity).showGreenToast(message, icon)
Log.i("$TAG Found [$count] files waiting to be shared")
}
}
sharedViewModel.textToShareFromIntent.observe(viewLifecycleOwner) { textToShare ->
if (textToShare.isNotEmpty()) {
val message = getString(R.string.conversations_text_waiting_to_be_shared_toast)

View file

@ -62,8 +62,6 @@ class MainViewModel
const val SEND_NOTIFICATIONS_PERMISSION_NOT_GRANTED = 15
const val DEFAULT_ACCOUNT_DISABLED = 18
const val NETWORK_NOT_REACHABLE = 19
const val SINGLE_CALL = 20
const val MULTIPLE_CALLS = 21
}
val showAlert = MutableLiveData<Boolean>()
@ -76,8 +74,16 @@ class MainViewModel
val atLeastOneCall = MutableLiveData<Boolean>()
val callLabel = MutableLiveData<String>()
val callsStatus = MutableLiveData<String>()
val pendingFileSharing = MutableLiveData<Boolean>()
val filesCountPendingSharing = MutableLiveData<Int>()
val filesPendingSharingLabel = MutableLiveData<String>()
val goBackToCallEvent: MutableLiveData<Event<Boolean>> by lazy {
MutableLiveData<Event<Boolean>>()
}
@ -106,6 +112,10 @@ class MainViewModel
MutableLiveData<Event<Boolean>>()
}
val clearFilesPendingSharingEvent: MutableLiveData<Event<Boolean>> by lazy {
MutableLiveData<Event<Boolean>>()
}
private var accountsFound = -1
var mainIntentHandled = false
@ -130,7 +140,6 @@ class MainViewModel
@WorkerThread
override fun onLastCallEnded(core: Core) {
Log.i("$TAG Last call ended, removing in-call 'alert'")
removeAlert(SINGLE_CALL)
atLeastOneCall.postValue(false)
computeNonDefaultAccountNotificationsCount()
}
@ -166,9 +175,6 @@ class MainViewModel
) {
updateCallAlert()
} else if (core.callsNb == 1) {
if (LinphoneUtils.isCallEnding(call.state)) {
removeAlert(MULTIPLE_CALLS)
}
callsStatus.postValue(LinphoneUtils.callStateToString(call.state))
}
}
@ -342,6 +348,11 @@ class MainViewModel
atLeastOneCall.value = false
maxAlertLevel.value = NONE
nonDefaultAccountNotificationsCount = 0
pendingFileSharing.value = false
filesCountPendingSharing.value = 0
filesPendingSharingLabel.value = ""
enableAccountMonitoring(true)
coreContext.postOnCoreThread { core ->
@ -457,6 +468,42 @@ class MainViewModel
}
}
@UiThread
fun onCallTopBarClicked() {
if (atLeastOneCall.value == true) {
goBackToCallEvent.value = Event(true)
}
}
@UiThread
fun addFilesPendingSharing(list: ArrayList<String>) {
val count = list.size
Log.i("$TAG Adding [$count] files to pending sharing files list")
if (count > 0) {
filesCountPendingSharing.value = count
filesPendingSharingLabel.value = AppUtils.getStringWithPlural(
R.plurals.conversations_files_waiting_to_be_shared_toast,
count,
"$count"
)
pendingFileSharing.value = true
}
}
@UiThread
fun filesPendingSharingListCleared() {
pendingFileSharing.value = false
filesCountPendingSharing.value = 0
filesPendingSharingLabel.value = ""
Log.i("$TAG List of files pending sharing has been cleared")
}
@UiThread
fun cancelFileSharing() {
Log.i("$TAG Clearing list of files pending sharing")
clearFilesPendingSharingEvent.value = Event(true)
}
@UiThread
fun enableAccountMonitoring(enable: Boolean) {
if (enable != monitorAccount) {
@ -500,8 +547,6 @@ class MainViewModel
val core = coreContext.core
val callsNb = core.callsNb
if (callsNb == 1) {
removeAlert(MULTIPLE_CALLS)
val currentCall = core.currentCall ?: core.calls.firstOrNull()
if (currentCall != null) {
val address = currentCall.callLog.remoteAddress
@ -513,16 +558,11 @@ class MainViewModel
contact?.name ?: LinphoneUtils.getDisplayName(address)
}
Log.i("$TAG Showing single call alert with label [$label]")
addAlert(SINGLE_CALL, label)
callLabel.postValue(label)
callsStatus.postValue(LinphoneUtils.callStateToString(currentCall.state))
}
} else if (callsNb > 1) {
removeAlert(SINGLE_CALL)
addAlert(
MULTIPLE_CALLS,
AppUtils.getFormattedString(R.string.calls_count_label, callsNb)
)
callLabel.postValue(AppUtils.getFormattedString(R.string.calls_count_label, callsNb))
callsStatus.postValue("") // TODO: improve ?
}
}
@ -589,9 +629,6 @@ class MainViewModel
SEND_NOTIFICATIONS_PERMISSION_NOT_GRANTED, FULL_SCREEN_INTENTS_PERMISSION_NOT_GRANTED -> {
R.drawable.bell_simple_slash
}
SINGLE_CALL, MULTIPLE_CALLS -> {
R.drawable.phone
}
else -> {
R.drawable.bell_simple
}
@ -599,11 +636,8 @@ class MainViewModel
alertIcon.postValue(icon)
alertLabel.postValue(label)
if (type < SINGLE_CALL) {
// Call alert is displayed using atLeastOnCall mutable, not showAlert
Log.i("$TAG Alert top-bar is currently invisible, display it now")
showAlert.postValue(true)
}
Log.i("$TAG Alert top-bar is currently invisible, display it now")
showAlert.postValue(true)
}
}

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="256"
android:viewportHeight="256">
<path
android:pathData="M213.66,66.34l-40,-40A8,8 0,0 0,168 24L88,24A16,16 0,0 0,72 40L72,56L56,56A16,16 0,0 0,40 72L40,216a16,16 0,0 0,16 16L168,232a16,16 0,0 0,16 -16L184,200h16a16,16 0,0 0,16 -16L216,72A8,8 0,0 0,213.66 66.34ZM168,216L56,216L56,72h76.69L168,107.31v84.53c0,0.06 0,0.11 0,0.16s0,0.1 0,0.16L168,216ZM200,184L184,184L184,104a8,8 0,0 0,-2.34 -5.66l-40,-40A8,8 0,0 0,136 56L88,56L88,40h76.69L200,75.31ZM144,152a8,8 0,0 1,-8 8L88,160a8,8 0,0 1,0 -16h48A8,8 0,0 1,144 152ZM144,184a8,8 0,0 1,-8 8L88,192a8,8 0,0 1,0 -16h48A8,8 0,0 1,144 184Z"
android:fillColor="#4e6074"/>
</vector>

View file

@ -22,15 +22,39 @@
android:layout_width="match_parent"
android:layout_height="match_parent">
<include
android:id="@+id/in_call_top_bar"
layout="@layout/main_activity_notification_top_bar"
android:layout_width="0dp"
<LinearLayout
android:id="@+id/notifications_area"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:viewModel="@{viewModel}"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
android:orientation="vertical"
android:background="@{viewModel.showAlert ? (viewModel.maxAlertLevel >= 10 ? @drawable/color_danger_500 : @drawable/color_main_activity_top_bar) : (viewModel.pendingFileSharing ? @drawable/color_main_activity_top_bar : (viewModel.atLeastOneCall ? @drawable/color_success_500 : @drawable/color_main1_500)), default=@drawable/color_main1_500}"
app:layout_constraintTop_toTopOf="parent">
<include
android:id="@+id/alerts_top_bar"
layout="@layout/main_activity_alert_top_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="@{viewModel.showAlert ? View.VISIBLE : View.GONE, default=gone}"
app:viewModel="@{viewModel}"/>
<include
android:id="@+id/file_sharing_top_bar"
layout="@layout/main_activity_pending_file_sharing_top_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="@{viewModel.pendingFileSharing ? View.VISIBLE : View.GONE, default=gone}"
app:viewModel="@{viewModel}"/>
<include
android:id="@+id/in_call_top_bar"
layout="@layout/main_activity_in_call_top_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="@{viewModel.atLeastOneCall ? View.VISIBLE : View.GONE, default=gone}"
app:viewModel="@{viewModel}"/>
</LinearLayout>
<androidx.fragment.app.FragmentContainerView
android:id="@+id/main_nav_container"
@ -41,7 +65,7 @@
app:navGraph="@navigation/main_nav_graph"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/in_call_top_bar"
app:layout_constraintTop_toBottomOf="@id/notifications_area"
app:layout_constraintBottom_toBottomOf="parent"/>
<LinearLayout

View file

@ -13,7 +13,7 @@
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@{viewModel.maxAlertLevel >= 20 ? @drawable/color_success_500 : viewModel.maxAlertLevel >= 10 ? @drawable/color_danger_500 : viewModel.maxAlertLevel > 0 ? @drawable/color_main_activity_top_bar : @drawable/color_main1_500, default=@drawable/color_main1_500}"
android:background="@{viewModel.maxAlertLevel >= 10 ? @drawable/color_danger_500 : @drawable/color_main_activity_top_bar, default=@drawable/color_danger_500}"
android:onClick="@{() -> viewModel.onTopBarClicked()}">
<ImageView
@ -23,7 +23,6 @@
android:layout_marginStart="16dp"
android:layout_marginTop="5dp"
android:layout_marginBottom="5dp"
android:visibility="@{viewModel.showAlert || viewModel.atLeastOneCall ? View.VISIBLE : View.GONE, default=gone}"
android:src="@{viewModel.alertIcon, default=@drawable/bell_simple}"
android:contentDescription="@null"
app:layout_constraintStart_toStartOf="parent"
@ -46,35 +45,11 @@
android:textSize="16sp"
android:maxLines="1"
android:ellipsize="end"
android:visibility="@{viewModel.showAlert || viewModel.atLeastOneCall ? View.VISIBLE : View.GONE, default=gone}"
app:layout_constraintEnd_toStartOf="@id/end_barrier"
app:layout_constraintEnd_toStartOf="@id/close_notif"
app:layout_constraintStart_toEndOf="@id/icon"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"/>
<androidx.constraintlayout.widget.Barrier
android:id="@+id/end_barrier"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:barrierDirection="start"
app:constraint_referenced_ids="call_time, close_notif" />
<androidx.appcompat.widget.AppCompatTextView
style="@style/default_text_style"
android:id="@+id/call_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:gravity="center_vertical"
android:text="@{viewModel.callsStatus, default=`Paused`}"
android:textColor="@color/bc_white"
android:textSize="14sp"
android:visibility="@{viewModel.atLeastOneCall ? View.VISIBLE : View.GONE, default=gone}"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/label"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"/>
<ImageView
android:id="@+id/close_notif"
android:onClick="@{() -> viewModel.closeTopBar()}"
@ -84,7 +59,6 @@
android:src="@drawable/x"
app:tint="@color/bc_white"
android:contentDescription="@string/content_description_dismiss_notification"
android:visibility="@{!viewModel.showAlert || viewModel.atLeastOneCall ? View.GONE : View.VISIBLE, default=gone}"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"/>

View file

@ -0,0 +1,70 @@
<?xml version="1.0" encoding="utf-8"?>
<layout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<import type="android.view.View" />
<variable
name="viewModel"
type="org.linphone.ui.main.viewmodel.MainViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/color_success_500"
android:onClick="@{() -> viewModel.onCallTopBarClicked()}">
<ImageView
android:id="@+id/icon"
android:layout_width="@dimen/icon_size"
android:layout_height="@dimen/icon_size"
android:layout_marginStart="16dp"
android:layout_marginTop="5dp"
android:layout_marginBottom="5dp"
android:src="@drawable/phone"
android:contentDescription="@null"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:tint="@color/bc_white" />
<androidx.appcompat.widget.AppCompatTextView
style="@style/default_text_style_300"
android:id="@+id/label"
android:layout_width="0dp"
android:layout_height="30dp"
android:layout_marginStart="5dp"
android:layout_marginEnd="10dp"
android:layout_marginTop="5dp"
android:layout_marginBottom="5dp"
android:gravity="center_vertical"
android:text="@{viewModel.callLabel, default=`John Doe`}"
android:textColor="@color/bc_white"
android:textSize="16sp"
android:maxLines="1"
android:ellipsize="end"
app:layout_constraintEnd_toStartOf="@id/call_time"
app:layout_constraintStart_toEndOf="@id/icon"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"/>
<androidx.appcompat.widget.AppCompatTextView
style="@style/default_text_style"
android:id="@+id/call_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:gravity="center_vertical"
android:text="@{viewModel.callsStatus, default=`Paused`}"
android:textColor="@color/bc_white"
android:textSize="14sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/label"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

View file

@ -0,0 +1,67 @@
<?xml version="1.0" encoding="utf-8"?>
<layout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<import type="android.view.View" />
<variable
name="viewModel"
type="org.linphone.ui.main.viewmodel.MainViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/color_main_activity_top_bar">
<ImageView
android:id="@+id/icon"
android:layout_width="@dimen/icon_size"
android:layout_height="@dimen/icon_size"
android:layout_marginStart="16dp"
android:layout_marginTop="5dp"
android:layout_marginBottom="5dp"
android:src="@{viewModel.filesCountPendingSharing > 1 ? @drawable/files : @drawable/file, default=@drawable/file}"
android:contentDescription="@null"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:tint="@color/bc_white" />
<androidx.appcompat.widget.AppCompatTextView
style="@style/default_text_style_300"
android:id="@+id/label"
android:layout_width="0dp"
android:layout_height="30dp"
android:layout_marginStart="5dp"
android:layout_marginEnd="10dp"
android:layout_marginTop="5dp"
android:layout_marginBottom="5dp"
android:gravity="center_vertical"
android:text="@{viewModel.filesPendingSharingLabel, default=@plurals/conversations_files_waiting_to_be_shared_toast}"
android:textColor="@color/bc_white"
android:textSize="16sp"
android:maxLines="1"
android:ellipsize="end"
app:layout_constraintEnd_toStartOf="@id/close_notif"
app:layout_constraintStart_toEndOf="@id/icon"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"/>
<ImageView
android:id="@+id/close_notif"
android:onClick="@{() -> viewModel.cancelFileSharing()}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:src="@drawable/x"
app:tint="@color/bc_white"
android:contentDescription="@string/content_description_dismiss_notification"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>