Added multi-accounts notifications top bar

This commit is contained in:
Sylvain Berfini 2024-05-10 10:11:11 +02:00
parent 63b06ed1fa
commit 405596d291
7 changed files with 102 additions and 33 deletions

View file

@ -35,8 +35,11 @@ import org.linphone.LinphoneApplication.Companion.corePreferences
import org.linphone.R
import org.linphone.core.Account
import org.linphone.core.Call
import org.linphone.core.ChatMessage
import org.linphone.core.ChatRoom
import org.linphone.core.Core
import org.linphone.core.CoreListenerStub
import org.linphone.core.GlobalState
import org.linphone.core.RegistrationState
import org.linphone.core.VFS
import org.linphone.core.tools.Log
@ -59,6 +62,8 @@ class MainViewModel @UiThread constructor() : ViewModel() {
val showAlert = MutableLiveData<Boolean>()
val maxAlertLevel = MutableLiveData<Int>()
val alertLabel = MutableLiveData<String>()
val alertIcon = MutableLiveData<Int>()
@ -104,11 +109,21 @@ class MainViewModel @UiThread constructor() : ViewModel() {
private var firstAccountRegistered: Boolean = false
private val coreListener = object : CoreListenerStub() {
@WorkerThread
override fun onGlobalStateChanged(core: Core, state: GlobalState?, message: String) {
Log.i("$TAG Core's global state is now [${core.globalState}]")
if (core.globalState == GlobalState.On) {
computeNonDefaultAccountNotificationsCount()
}
}
@WorkerThread
override fun onLastCallEnded(core: Core) {
Log.i("$TAG Last call ended, removing in-call 'alert'")
removeAlert(SINGLE_CALL)
atLeastOneCall.postValue(false)
computeNonDefaultAccountNotificationsCount()
}
@WorkerThread
@ -125,6 +140,7 @@ class MainViewModel @UiThread constructor() : ViewModel() {
state: Call.State?,
message: String
) {
Log.i("$TAG A call's state changed, updating alerts if needed")
if (
core.callsNb > 1 && (
LinphoneUtils.isCallEnding(call.state) ||
@ -141,6 +157,16 @@ class MainViewModel @UiThread constructor() : ViewModel() {
}
}
@WorkerThread
override fun onMessagesReceived(
core: Core,
chatRoom: ChatRoom,
messages: Array<out ChatMessage>
) {
Log.i("$TAG Message(s) received, updating notifications count if needed")
computeNonDefaultAccountNotificationsCount()
}
@WorkerThread
override fun onNetworkReachable(core: Core, reachable: Boolean) {
Log.i("$TAG Network is ${if (reachable) "reachable" else "not reachable"}")
@ -213,9 +239,7 @@ class MainViewModel @UiThread constructor() : ViewModel() {
core.refreshRegisters()
}
removeAlert(NON_DEFAULT_ACCOUNT_NOTIFICATIONS)
// TODO FIXME: compute other accounts notifications count
computeNonDefaultAccountNotificationsCount()
}
@WorkerThread
@ -234,6 +258,7 @@ class MainViewModel @UiThread constructor() : ViewModel() {
init {
defaultAccountRegistrationFailed = false
showAlert.value = false
maxAlertLevel.value = NONE
coreContext.postOnCoreThread { core ->
accountsFound = core.accountList.size
@ -311,6 +336,28 @@ class MainViewModel @UiThread constructor() : ViewModel() {
}
}
@WorkerThread
private fun computeNonDefaultAccountNotificationsCount() {
var count = 0
for (account in coreContext.core.accountList) {
if (account == coreContext.core.defaultAccount) continue
count += account.unreadChatMessageCount + account.missedCallsCount
}
if (count > 0) {
val label = AppUtils.getStringWithPlural(
R.plurals.pending_notification_for_other_accounts,
count,
count.toString()
)
addAlert(NON_DEFAULT_ACCOUNT_NOTIFICATIONS, label, forceUpdate = true)
Log.i("$TAG Found [$count] pending notifications for other account(s)")
} else {
Log.i("$TAG No pending notification found for other account(s), clearing alert")
removeAlert(NON_DEFAULT_ACCOUNT_NOTIFICATIONS)
}
}
@WorkerThread
private fun updateCallAlert() {
val core = coreContext.core
@ -347,13 +394,18 @@ class MainViewModel @UiThread constructor() : ViewModel() {
}
@WorkerThread
private fun addAlert(type: Int, label: String) {
private fun addAlert(type: Int, label: String, forceUpdate: Boolean = false) {
val found = alertsList.find {
it.first == type
}
if (found == null) {
if (found == null || forceUpdate) {
cancelAlertJob()
if (found != null) {
alertsList.remove(found)
}
val alert = Pair(type, label)
Log.i("$TAG Adding alert with type [$type]")
alertsList.add(alert)
updateDisplayedAlert()
} else {
@ -368,6 +420,7 @@ class MainViewModel @UiThread constructor() : ViewModel() {
}
if (found != null) {
cancelAlertJob()
Log.i("$TAG Removing alert with type [$type]")
alertsList.remove(found)
updateDisplayedAlert()
} else {
@ -397,10 +450,12 @@ class MainViewModel @UiThread constructor() : ViewModel() {
Log.i("$TAG No alert to display")
showAlert.postValue(false)
changeSystemTopBarColorEvent.postValue(Event(NONE))
maxAlertLevel.postValue(NONE)
} else {
val type = maxedPriorityAlert.first
val label = maxedPriorityAlert.second
Log.i("$TAG Max priority alert right now is [$type]")
maxAlertLevel.postValue(type)
when (type) {
NON_DEFAULT_ACCOUNT_NOTIFICATIONS, NON_DEFAULT_ACCOUNT_NOT_CONNECTED -> {
alertIcon.postValue(R.drawable.bell_simple)
@ -414,14 +469,20 @@ class MainViewModel @UiThread constructor() : ViewModel() {
}
alertLabel.postValue(label)
coreContext.postOnMainThread {
val delayMs = if (type == SINGLE_CALL) 1000L else 0L
alertJob = viewModelScope.launch {
withContext(Dispatchers.IO) {
delay(delayMs)
withContext(Dispatchers.Main) {
showAlert.value = true
changeSystemTopBarColorEvent.value = Event(type)
if (showAlert.value == true) {
Log.i("$TAG Alert top-bar is already visible, updating color if needed")
changeSystemTopBarColorEvent.postValue(Event(type))
} else {
Log.i("$TAG Alert top-bar is currently invisible, starting job to display it")
coreContext.postOnMainThread {
val delayMs = if (type == SINGLE_CALL) 1000L else 0L
alertJob = viewModelScope.launch {
withContext(Dispatchers.IO) {
delay(delayMs)
withContext(Dispatchers.Main) {
showAlert.value = true
changeSystemTopBarColorEvent.value = Event(type)
}
}
}
}

View file

@ -36,8 +36,8 @@
android:layout_height="wrap_content">
<ImageView
android:onClick="@{backClickListener}"
android:id="@+id/back"
android:onClick="@{backClickListener}"
android:layout_width="@dimen/top_bar_height"
android:layout_height="@dimen/top_bar_height"
android:padding="15dp"
@ -82,7 +82,7 @@
android:layout_height="wrap_content"
android:layout_marginTop="18dp"
android:layout_marginEnd="16dp"
android:text="@{@string/sip_address + `*`, default=@string/sip_address}"
android:text="@{@string/sip_address + `*`, default=`SIP Address*`}"
app:layout_constraintTop_toBottomOf="@id/title"
app:layout_constraintStart_toStartOf="@id/sip_identity"/>
@ -113,7 +113,7 @@
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:layout_marginTop="16dp"
android:text="@{@string/password + `*`, default=@string/password}"
android:text="@{@string/password + `*`, default=`Password*`}"
app:layout_constraintWidth_max="@dimen/text_input_max_width"
app:layout_constraintTop_toBottomOf="@id/sip_identity"
app:layout_constraintStart_toStartOf="@id/password"/>
@ -139,8 +139,8 @@
app:layout_constraintEnd_toEndOf="parent"/>
<ImageView
android:onClick="@{() -> viewModel.toggleShowPassword()}"
android:id="@+id/eye"
android:onClick="@{() -> viewModel.toggleShowPassword()}"
android:layout_width="@dimen/icon_size"
android:layout_height="0dp"
android:padding="4dp"
@ -153,10 +153,10 @@
app:layout_constraintBottom_toBottomOf="@id/password" />
<androidx.appcompat.widget.AppCompatTextView
android:onClick="@{() -> viewModel.login()}"
android:enabled="@{viewModel.loginEnabled &amp;&amp; !viewModel.registrationInProgress, default=false}"
style="@style/primary_button_label_style"
android:id="@+id/login"
android:onClick="@{() -> viewModel.login()}"
android:enabled="@{viewModel.loginEnabled &amp;&amp; !viewModel.registrationInProgress, default=false}"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
@ -171,9 +171,9 @@
app:layout_constraintEnd_toEndOf="parent" />
<androidx.appcompat.widget.AppCompatTextView
android:onClick="@{forgottenPasswordClickListener}"
style="@style/default_text_style_600"
android:id="@+id/forgotten_password"
android:onClick="@{forgottenPasswordClickListener}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
@ -224,9 +224,9 @@
app:layout_constraintBottom_toBottomOf="@id/or"/>
<androidx.appcompat.widget.AppCompatTextView
android:onClick="@{qrCodeClickListener}"
style="@style/secondary_button_label_style"
android:id="@+id/scan_qr_code"
android:onClick="@{qrCodeClickListener}"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="22dp"
@ -245,9 +245,9 @@
app:layout_constraintTop_toBottomOf="@id/or" />
<androidx.appcompat.widget.AppCompatTextView
android:onClick="@{thirdPartySipAccountLoginClickListener}"
style="@style/secondary_button_label_style"
android:id="@+id/third_party_sip_account"
android:onClick="@{thirdPartySipAccountLoginClickListener}"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
@ -279,9 +279,9 @@
app:layout_constraintBottom_toBottomOf="@id/register"/>
<androidx.appcompat.widget.AppCompatTextView
android:onClick="@{registerClickListener}"
style="@style/primary_button_label_style"
android:id="@+id/register"
android:onClick="@{registerClickListener}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="40dp"

View file

@ -27,8 +27,8 @@
android:layout_height="wrap_content">
<ImageView
android:onClick="@{backClickListener}"
android:id="@+id/back"
android:onClick="@{backClickListener}"
android:layout_width="@dimen/top_bar_height"
android:layout_height="@dimen/top_bar_height"
android:padding="15dp"
@ -129,8 +129,8 @@
app:layout_constraintEnd_toEndOf="parent"/>
<ImageView
android:onClick="@{() -> viewModel.toggleShowPassword()}"
android:id="@+id/eye"
android:onClick="@{() -> viewModel.toggleShowPassword()}"
android:layout_width="@dimen/icon_size"
android:layout_height="0dp"
android:padding="4dp"
@ -143,10 +143,10 @@
app:layout_constraintBottom_toBottomOf="@id/password" />
<androidx.appcompat.widget.AppCompatTextView
android:onClick="@{() -> viewModel.login()}"
android:enabled="@{viewModel.loginEnabled &amp;&amp; !viewModel.registrationInProgress, default=false}"
style="@style/primary_button_label_style"
android:id="@+id/login"
android:onClick="@{() -> viewModel.login()}"
android:enabled="@{viewModel.loginEnabled &amp;&amp; !viewModel.registrationInProgress, default=false}"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
@ -161,9 +161,9 @@
app:layout_constraintEnd_toEndOf="parent" />
<androidx.appcompat.widget.AppCompatTextView
android:onClick="@{forgottenPasswordClickListener}"
style="@style/default_text_style_600"
android:id="@+id/forgotten_password"
android:onClick="@{forgottenPasswordClickListener}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"

View file

@ -70,7 +70,7 @@
android:layout_marginTop="38dp"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:text="@{@string/username + `*`}"
android:text="@{@string/username + `*`, default=`Username*`}"
app:layout_constraintTop_toBottomOf="@id/title"
app:layout_constraintStart_toStartOf="@id/username"/>
@ -102,7 +102,7 @@
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:layout_marginTop="16dp"
android:text="@{@string/password + `*`}"
android:text="@string/password"
app:layout_constraintTop_toBottomOf="@id/username"
app:layout_constraintStart_toStartOf="@id/password"/>
@ -148,7 +148,7 @@
android:layout_marginTop="18dp"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:text="@{@string/sip_address_domain + `*`}"
android:text="@{@string/sip_address_domain + `*`, default=`Domain*`}"
app:layout_constraintTop_toBottomOf="@id/password"
app:layout_constraintStart_toStartOf="@id/domain"/>
@ -212,7 +212,7 @@
android:layout_marginTop="18dp"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:text="@{@string/assistant_sip_account_transport_protocol + `*`}"
android:text="@string/assistant_sip_account_transport_protocol"
app:layout_constraintTop_toBottomOf="@id/display_name"
app:layout_constraintStart_toStartOf="@id/transport"/>

View file

@ -13,7 +13,7 @@
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@{viewModel.atLeastOneCall ? @color/success_500 : @color/danger_500, default=@color/danger_500}"
android:background="@{viewModel.maxAlertLevel >= 20 ? @color/success_500 : viewModel.maxAlertLevel >= 10 ? @color/danger_500 : @color/main2_500, default=@color/main2_500}"
android:onClick="@{() -> viewModel.onTopBarClicked()}">
<ImageView

View file

@ -627,6 +627,10 @@
<!-- Misc -->
<string name="multiple_participants_selection_placeholder">Les participants selectionnés apparaîtront ici</string>
<string name="connection_error_for_non_default_account">Compte(s) non connecté(s)</string>
<plurals name="pending_notification_for_other_accounts" tools:ignore="MissingQuantity">
<item quantity="one">%s notification en attente</item>
<item quantity="other">%s notifications en attente</item>
</plurals>
<string name="network_not_reachable">Vous n\'êtes pas connecté à internet</string>
<string name="operation_in_progress_overlay">Opération en cours, merci de patienter…</string>

View file

@ -663,6 +663,10 @@
<!-- Misc -->
<string name="multiple_participants_selection_placeholder">Selected participants will appear here</string>
<string name="connection_error_for_non_default_account">Account(s) connection error</string>
<plurals name="pending_notification_for_other_accounts">
<item quantity="one">%s notification for other account(s)</item>
<item quantity="other">%s notifications for other account(s)</item>
</plurals>
<string name="network_not_reachable">You aren\'t connected to internet</string>
<string name="operation_in_progress_overlay">Operation in progress, please wait</string>