diff --git a/app/src/main/java/org/linphone/ui/main/MainActivity.kt b/app/src/main/java/org/linphone/ui/main/MainActivity.kt index 7b948cec9..3128cb579 100644 --- a/app/src/main/java/org/linphone/ui/main/MainActivity.kt +++ b/app/src/main/java/org/linphone/ui/main/MainActivity.kt @@ -42,12 +42,9 @@ import androidx.lifecycle.lifecycleScope import androidx.navigation.NavController import androidx.navigation.findNavController import kotlinx.coroutines.Deferred -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll -import kotlinx.coroutines.delay import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext import org.linphone.LinphoneApplication.Companion.coreContext import org.linphone.LinphoneApplication.Companion.corePreferences import org.linphone.R @@ -107,20 +104,17 @@ class MainActivity : AppCompatActivity() { viewModel.changeSystemTopBarColorEvent.observe(this) { it.consume { mode -> - val color = when (mode) { - MainViewModel.IN_CALL -> AppUtils.getColor(R.color.green_success_500) - MainViewModel.ACCOUNT_REGISTRATION_FAILURE -> AppUtils.getColor( - R.color.red_danger_500 - ) - else -> AppUtils.getColor(R.color.orange_main_500) - } - lifecycleScope.launch { - withContext(Dispatchers.IO) { - delay(if (mode == MainViewModel.IN_CALL) 1000 else 0) - withContext(Dispatchers.Main) { - window.statusBarColor = color - } + window.statusBarColor = when (mode) { + MainViewModel.SINGLE_CALL, MainViewModel.MULTIPLE_CALLS -> { + AppUtils.getColor(R.color.green_success_500) } + MainViewModel.NETWORK_NOT_REACHABLE, MainViewModel.NON_DEFAULT_ACCOUNT_NOT_CONNECTED -> { + AppUtils.getColor(R.color.red_danger_500) + } + MainViewModel.NON_DEFAULT_ACCOUNT_NOTIFICATIONS -> { + AppUtils.getColor(R.color.gray_main2_500) + } + else -> AppUtils.getColor(R.color.orange_main_500) } } } diff --git a/app/src/main/java/org/linphone/ui/main/viewmodel/MainViewModel.kt b/app/src/main/java/org/linphone/ui/main/viewmodel/MainViewModel.kt index 6b58ece1f..7a8bb947b 100644 --- a/app/src/main/java/org/linphone/ui/main/viewmodel/MainViewModel.kt +++ b/app/src/main/java/org/linphone/ui/main/viewmodel/MainViewModel.kt @@ -23,6 +23,13 @@ import androidx.annotation.UiThread import androidx.annotation.WorkerThread import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.cancelAndJoin +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import org.linphone.LinphoneApplication.Companion.coreContext import org.linphone.R import org.linphone.core.Account @@ -40,16 +47,21 @@ class MainViewModel @UiThread constructor() : ViewModel() { private const val TAG = "[Main ViewModel]" const val NONE = 0 - const val ACCOUNT_REGISTRATION_FAILURE = 1 - const val IN_CALL = 2 + const val NON_DEFAULT_ACCOUNT_NOTIFICATIONS = 5 + const val NON_DEFAULT_ACCOUNT_NOT_CONNECTED = 10 + const val NETWORK_NOT_REACHABLE = 19 + const val SINGLE_CALL = 20 + const val MULTIPLE_CALLS = 21 } - val showTopBar = MutableLiveData() + val showAlert = MutableLiveData() + + val alertLabel = MutableLiveData() + + val alertIcon = MutableLiveData() val atLeastOneCall = MutableLiveData() - val callLabel = MutableLiveData() - val callsStatus = MutableLiveData() val defaultAccountRegistrationErrorEvent: MutableLiveData> by lazy { @@ -68,13 +80,24 @@ class MainViewModel @UiThread constructor() : ViewModel() { MutableLiveData>() } - var defaultAccountRegistrationFailed = false + private var defaultAccountRegistrationFailed = false + + private val alertsList = arrayListOf>() + + private var alertJob: Job? = null private val coreListener = object : CoreListenerStub() { @WorkerThread override fun onLastCallEnded(core: Core) { - Log.i("$TAG Last call ended, asking fragment to change back status bar color") - changeSystemTopBarColorEvent.postValue(Event(NONE)) + Log.i("$TAG Last call ended, removing in-call 'alert'") + removeAlert(SINGLE_CALL) + atLeastOneCall.postValue(false) + } + + override fun onFirstCallStarted(core: Core) { + Log.i("$TAG First call started, adding in-call 'alert'") + updateCallAlert() + atLeastOneCall.postValue(true) } @WorkerThread @@ -84,12 +107,28 @@ class MainViewModel @UiThread constructor() : ViewModel() { state: Call.State?, message: String ) { - if (core.callsNb > 0) { - updateCurrentCallInfo() + if ( + core.callsNb > 1 && ( + LinphoneUtils.isCallEnding(call.state) || + LinphoneUtils.isCallIncoming(call.state) || + LinphoneUtils.isCallOutgoing(call.state) + ) + ) { + updateCallAlert() + } else if (core.callsNb == 1) { + callsStatus.postValue(LinphoneUtils.callStateToString(call.state)) + } + } + + @WorkerThread + override fun onNetworkReachable(core: Core, reachable: Boolean) { + Log.i("$TAG Network is ${if (reachable) "reachable" else "not reachable"}") + if (!reachable) { + val label = AppUtils.getString(R.string.network_not_reachable) + addAlert(NETWORK_NOT_REACHABLE, label) + } else { + removeAlert(NETWORK_NOT_REACHABLE) } - val calls = core.callsNb > 0 - showTopBar.postValue(calls) - atLeastOneCall.postValue(calls) } @WorkerThread @@ -105,17 +144,12 @@ class MainViewModel @UiThread constructor() : ViewModel() { Log.e("$TAG Default account registration failed!") defaultAccountRegistrationFailed = true defaultAccountRegistrationErrorEvent.postValue(Event(true)) - } else { + } else if (core.isNetworkReachable) { Log.e("$TAG Non-default account registration failed!") - // Do not show connection error top bar if there is a call - if (atLeastOneCall.value == false) { - changeSystemTopBarColorEvent.postValue( - Event( - ACCOUNT_REGISTRATION_FAILURE - ) - ) - showTopBar.postValue(true) - } + val label = AppUtils.getString( + R.string.connection_error_for_non_default_account + ) + addAlert(SINGLE_CALL, label) } } RegistrationState.Ok -> { @@ -129,11 +163,7 @@ class MainViewModel @UiThread constructor() : ViewModel() { it.state == RegistrationState.Failed } if (found == null) { - Log.i("$TAG No account in Failed state anymore") - if (atLeastOneCall.value == false) { - changeSystemTopBarColorEvent.postValue(Event(NONE)) - showTopBar.postValue(false) - } + removeAlert(NON_DEFAULT_ACCOUNT_NOT_CONNECTED) } } } @@ -144,18 +174,21 @@ class MainViewModel @UiThread constructor() : ViewModel() { init { defaultAccountRegistrationFailed = false - showTopBar.value = false + showAlert.value = false coreContext.postOnCoreThread { core -> core.addListener(coreListener) - if (core.callsNb > 0) { - updateCurrentCallInfo() + if (!core.isNetworkReachable) { + Log.w("$TAG Network is not reachable!") + val label = AppUtils.getString(R.string.network_not_reachable) + addAlert(NETWORK_NOT_REACHABLE, label) } - val calls = core.callsNb > 0 - showTopBar.postValue(calls) - atLeastOneCall.postValue(calls) + if (core.callsNb > 0) { + updateCallAlert() + } + atLeastOneCall.postValue(core.callsNb > 0) } } @@ -170,7 +203,7 @@ class MainViewModel @UiThread constructor() : ViewModel() { @UiThread fun closeTopBar() { - showTopBar.value = false + showAlert.value = false } @UiThread @@ -183,38 +216,121 @@ class MainViewModel @UiThread constructor() : ViewModel() { } @WorkerThread - private fun updateCurrentCallInfo() { + private fun updateCallAlert() { val core = coreContext.core - if (core.callsNb == 1) { + val callsNb = core.callsNb + if (callsNb == 1) { + removeAlert(MULTIPLE_CALLS) + val currentCall = core.currentCall ?: core.calls.firstOrNull() if (currentCall != null) { val contact = coreContext.contactsManager.findContactByAddress( currentCall.remoteAddress ) - if (contact != null) { - callLabel.postValue( - contact.name ?: LinphoneUtils.getDisplayName(currentCall.remoteAddress) - ) + val label = if (contact != null) { + contact.name ?: LinphoneUtils.getDisplayName(currentCall.remoteAddress) } else { val conferenceInfo = coreContext.core.findConferenceInformationFromUri( currentCall.remoteAddress ) - callLabel.postValue( - conferenceInfo?.subject ?: LinphoneUtils.getDisplayName( - currentCall.remoteAddress - ) + conferenceInfo?.subject ?: LinphoneUtils.getDisplayName( + currentCall.remoteAddress ) } + addAlert(SINGLE_CALL, label) callsStatus.postValue(LinphoneUtils.callStateToString(currentCall.state)) } + } else if (callsNb > 1) { + removeAlert(SINGLE_CALL) - Log.i("$TAG At least a call, asking fragment to change status bar color") - changeSystemTopBarColorEvent.postValue(Event(IN_CALL)) - } else { - callLabel.postValue( - AppUtils.getFormattedString(R.string.calls_count_label, core.callsNb) + addAlert( + MULTIPLE_CALLS, + AppUtils.getFormattedString(R.string.calls_count_label, callsNb) ) callsStatus.postValue("") // TODO: improve ? } } + + @WorkerThread + private fun addAlert(type: Int, label: String) { + val found = alertsList.find { + it.first == type + } + if (found == null) { + cancelAlertJob() + val alert = Pair(type, label) + alertsList.add(alert) + updateDisplayedAlert() + } else { + Log.w("$TAG There is already an alert with type [$type], skipping...") + } + } + + @WorkerThread + private fun removeAlert(type: Int) { + val found = alertsList.find { + it.first == type + } + if (found != null) { + cancelAlertJob() + alertsList.remove(found) + updateDisplayedAlert() + } else { + Log.w("$TAG Failed to remove alert with type [$type], not found in current alerts list") + } + } + + @WorkerThread + private fun cancelAlertJob() { + viewModelScope.launch { + withContext(Dispatchers.Main) { + alertJob?.cancelAndJoin() + alertJob = null + } + } + } + + @WorkerThread + private fun updateDisplayedAlert() { + // Sort alerts by priority + alertsList.sortByDescending { + it.first + } + + val maxedPriorityAlert = alertsList.firstOrNull() + if (maxedPriorityAlert == null) { + Log.i("$TAG No alert to display") + showAlert.postValue(false) + changeSystemTopBarColorEvent.postValue(Event(NONE)) + } else { + val type = maxedPriorityAlert.first + val label = maxedPriorityAlert.second + Log.i("$TAG Max priority alert right now is [$type]") + when (type) { + NON_DEFAULT_ACCOUNT_NOTIFICATIONS, NON_DEFAULT_ACCOUNT_NOT_CONNECTED -> { + alertIcon.postValue(R.drawable.bell_simple) + } + NETWORK_NOT_REACHABLE -> { + alertIcon.postValue(R.drawable.wifi_slash) + } + SINGLE_CALL, MULTIPLE_CALLS -> { + alertIcon.postValue(R.drawable.phone) + } + } + 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) + } + } + } + } + } + } } diff --git a/app/src/main/res/drawable/wifi_slash.xml b/app/src/main/res/drawable/wifi_slash.xml new file mode 100644 index 000000000..07c8e0062 --- /dev/null +++ b/app/src/main/res/drawable/wifi_slash.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/layout/main_activity.xml b/app/src/main/res/layout/main_activity.xml index 194877d04..199430259 100644 --- a/app/src/main/res/layout/main_activity.xml +++ b/app/src/main/res/layout/main_activity.xml @@ -24,10 +24,10 @@ + + - + app:barrierDirection="start" + app:constraint_referenced_ids="call_time, close_notif" /> diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 784d14fc1..fc6c2324c 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -457,6 +457,7 @@ %s participants Account connection error + You aren\'t connected to internet Skip