From 5eb323c6624ba7eeac1d6a01a759432ad3f0366a Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Thu, 14 Sep 2023 13:58:32 +0200 Subject: [PATCH] Added in-call top bar --- .../java/org/linphone/core/CoreContext.kt | 2 +- .../main/calls/fragment/CallsListFragment.kt | 14 +-- .../contacts/fragment/ContactsListFragment.kt | 15 +-- .../main/fragment/AbstractTopBarFragment.kt | 63 ++++++++++++ .../main/viewmodel/AbstractTopBarViewModel.kt | 99 +++++++++++++++++-- ...shape_circle_green_disabled_background.xml | 5 + .../res/layout-land/calls_list_fragment.xml | 13 ++- .../layout-land/contacts_list_fragment.xml | 13 ++- .../res/layout/account_profile_fragment.xml | 2 +- .../account_profile_secure_mode_fragment.xml | 2 +- app/src/main/res/layout/call_fragment.xml | 1 + .../main/res/layout/call_start_fragment.xml | 2 +- .../main/res/layout/calls_list_fragment.xml | 13 ++- app/src/main/res/layout/contact_fragment.xml | 1 + .../layout/contact_new_or_edit_fragment.xml | 6 +- .../res/layout/contacts_list_fragment.xml | 13 ++- .../main/res/layout/help_debug_fragment.xml | 2 +- app/src/main/res/layout/help_fragment.xml | 2 +- app/src/main/res/layout/main_activity.xml | 1 + .../layout/main_activity_in_call_top_bar.xml | 53 ++++++++++ ...arch_bar.xml => main_activity_top_bar.xml} | 0 .../main/res/layout/recordings_fragment.xml | 2 +- app/src/main/res/layout/settings_fragment.xml | 2 +- app/src/main/res/values/colors.xml | 1 + 24 files changed, 285 insertions(+), 42 deletions(-) create mode 100644 app/src/main/java/org/linphone/ui/main/fragment/AbstractTopBarFragment.kt create mode 100644 app/src/main/res/drawable/shape_circle_green_disabled_background.xml create mode 100644 app/src/main/res/layout/main_activity_in_call_top_bar.xml rename app/src/main/res/layout/{top_search_bar.xml => main_activity_top_bar.xml} (100%) diff --git a/app/src/main/java/org/linphone/core/CoreContext.kt b/app/src/main/java/org/linphone/core/CoreContext.kt index ec0273b32..c4ff862c5 100644 --- a/app/src/main/java/org/linphone/core/CoreContext.kt +++ b/app/src/main/java/org/linphone/core/CoreContext.kt @@ -319,7 +319,7 @@ class CoreContext @UiThread constructor(val context: Context) : HandlerThread("C } @UiThread - private fun showCallActivity() { + fun showCallActivity() { Log.i("$TAG Starting VoIP activity") val intent = Intent(context, VoipActivity::class.java) // This flag is required to start an Activity from a Service context diff --git a/app/src/main/java/org/linphone/ui/main/calls/fragment/CallsListFragment.kt b/app/src/main/java/org/linphone/ui/main/calls/fragment/CallsListFragment.kt index 1f1ae4c2a..318546c75 100644 --- a/app/src/main/java/org/linphone/ui/main/calls/fragment/CallsListFragment.kt +++ b/app/src/main/java/org/linphone/ui/main/calls/fragment/CallsListFragment.kt @@ -42,14 +42,14 @@ import org.linphone.ui.main.MainActivity import org.linphone.ui.main.calls.adapter.CallsListAdapter import org.linphone.ui.main.calls.model.ConfirmationDialogModel import org.linphone.ui.main.calls.viewmodel.CallsListViewModel -import org.linphone.ui.main.fragment.GenericFragment +import org.linphone.ui.main.fragment.AbstractTopBarFragment import org.linphone.utils.DialogUtils import org.linphone.utils.Event import org.linphone.utils.hideKeyboard import org.linphone.utils.showKeyboard @UiThread -class CallsListFragment : GenericFragment() { +class CallsListFragment : AbstractTopBarFragment() { companion object { private const val TAG = "[Calls List Fragment]" } @@ -187,18 +187,14 @@ class CallsListFragment : GenericFragment() { Log.i( "$TAG Default account changed, updating avatar in top bar & re-computing call logs" ) - listViewModel.updateDefaultAccount() + listViewModel.update() listViewModel.applyFilter() } } // TopBarFragment related - listViewModel.openDrawerMenuEvent.observe(viewLifecycleOwner) { - it.consume { - (requireActivity() as MainActivity).toggleDrawerMenu() - } - } + setViewModelAndTitle(listViewModel, "Calls") listViewModel.searchFilter.observe(viewLifecycleOwner) { filter -> listViewModel.applyFilter(filter.trim()) @@ -214,8 +210,6 @@ class CallsListFragment : GenericFragment() { } } } - - listViewModel.title.value = "Calls" } override fun onResume() { diff --git a/app/src/main/java/org/linphone/ui/main/contacts/fragment/ContactsListFragment.kt b/app/src/main/java/org/linphone/ui/main/contacts/fragment/ContactsListFragment.kt index 97c3ab0bf..d2420f61e 100644 --- a/app/src/main/java/org/linphone/ui/main/contacts/fragment/ContactsListFragment.kt +++ b/app/src/main/java/org/linphone/ui/main/contacts/fragment/ContactsListFragment.kt @@ -35,16 +35,15 @@ import org.linphone.LinphoneApplication.Companion.coreContext import org.linphone.R import org.linphone.core.tools.Log import org.linphone.databinding.ContactsListFragmentBinding -import org.linphone.ui.main.MainActivity import org.linphone.ui.main.contacts.adapter.ContactsListAdapter import org.linphone.ui.main.contacts.viewmodel.ContactsListViewModel -import org.linphone.ui.main.fragment.GenericFragment +import org.linphone.ui.main.fragment.AbstractTopBarFragment import org.linphone.utils.Event import org.linphone.utils.hideKeyboard import org.linphone.utils.showKeyboard @UiThread -class ContactsListFragment : GenericFragment() { +class ContactsListFragment : AbstractTopBarFragment() { companion object { private const val TAG = "[Contacts List Fragment]" } @@ -143,18 +142,14 @@ class ContactsListFragment : GenericFragment() { Log.i( "$TAG Default account changed, updating avatar in top bar & refreshing contacts list" ) - listViewModel.updateDefaultAccount() + listViewModel.update() listViewModel.applyCurrentDefaultAccountFilter() } } // TopBarFragment related - listViewModel.openDrawerMenuEvent.observe(viewLifecycleOwner) { - it.consume { - (requireActivity() as MainActivity).toggleDrawerMenu() - } - } + setViewModelAndTitle(listViewModel, "Contacts") listViewModel.searchFilter.observe(viewLifecycleOwner) { filter -> listViewModel.applyFilter(filter.trim()) @@ -170,8 +165,6 @@ class ContactsListFragment : GenericFragment() { } } } - - listViewModel.title.value = "Contacts" } private fun configureAdapter(adapter: ContactsListAdapter) { diff --git a/app/src/main/java/org/linphone/ui/main/fragment/AbstractTopBarFragment.kt b/app/src/main/java/org/linphone/ui/main/fragment/AbstractTopBarFragment.kt new file mode 100644 index 000000000..da7e8c544 --- /dev/null +++ b/app/src/main/java/org/linphone/ui/main/fragment/AbstractTopBarFragment.kt @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2010-2023 Belledonne Communications SARL. + * + * This file is part of linphone-android + * (see https://www.linphone.org). + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.linphone.ui.main.fragment + +import androidx.annotation.UiThread +import androidx.core.content.ContextCompat +import org.linphone.LinphoneApplication +import org.linphone.R +import org.linphone.ui.main.MainActivity +import org.linphone.ui.main.viewmodel.AbstractTopBarViewModel + +@UiThread +abstract class AbstractTopBarFragment : GenericFragment() { + fun setViewModelAndTitle(viewModel: AbstractTopBarViewModel, title: String) { + viewModel.title.value = title + + viewModel.openDrawerMenuEvent.observe(viewLifecycleOwner) { + it.consume { + (requireActivity() as MainActivity).toggleDrawerMenu() + } + } + + viewModel.changeSystemTopBarColorToInCallEvent.observe(viewLifecycleOwner) { + it.consume { useInCallColor -> + val color = if (useInCallColor) { + ContextCompat.getColor( + requireContext(), + R.color.green_online + ) + } else { + ContextCompat.getColor( + requireContext(), + R.color.primary_color + ) + } + requireActivity().window.statusBarColor = color + } + } + + viewModel.goBackToCallEvent.observe(viewLifecycleOwner) { + it.consume { + LinphoneApplication.coreContext.showCallActivity() + } + } + } +} diff --git a/app/src/main/java/org/linphone/ui/main/viewmodel/AbstractTopBarViewModel.kt b/app/src/main/java/org/linphone/ui/main/viewmodel/AbstractTopBarViewModel.kt index c64d6b727..4ea83961b 100644 --- a/app/src/main/java/org/linphone/ui/main/viewmodel/AbstractTopBarViewModel.kt +++ b/app/src/main/java/org/linphone/ui/main/viewmodel/AbstractTopBarViewModel.kt @@ -20,12 +20,17 @@ package org.linphone.ui.main.viewmodel import androidx.annotation.UiThread +import androidx.annotation.WorkerThread import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import org.linphone.LinphoneApplication.Companion.coreContext +import org.linphone.core.Call +import org.linphone.core.Core +import org.linphone.core.CoreListenerStub import org.linphone.core.tools.Log import org.linphone.ui.main.model.AccountModel import org.linphone.utils.Event +import org.linphone.utils.LinphoneUtils open class AbstractTopBarViewModel @UiThread constructor() : ViewModel() { companion object { @@ -40,6 +45,12 @@ open class AbstractTopBarViewModel @UiThread constructor() : ViewModel() { val searchFilter = MutableLiveData() + val atLastOneCall = MutableLiveData() + + val callDisplayName = MutableLiveData() + + val callStatus = MutableLiveData() + val focusSearchBarEvent: MutableLiveData> by lazy { MutableLiveData>() } @@ -48,17 +59,55 @@ open class AbstractTopBarViewModel @UiThread constructor() : ViewModel() { MutableLiveData>() } + val changeSystemTopBarColorToInCallEvent: MutableLiveData> by lazy { + MutableLiveData>() + } + + val goBackToCallEvent: MutableLiveData> by lazy { + MutableLiveData>() + } + + 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") + changeSystemTopBarColorToInCallEvent.postValue(Event(false)) + } + + @WorkerThread + override fun onCallStateChanged( + core: Core, + call: Call, + state: Call.State?, + message: String + ) { + if (core.callsNb > 0) { + updateCurrentCallInfo() + } + atLastOneCall.postValue(core.callsNb > 0) + } + } + init { searchBarVisible.value = false - updateDefaultAccount() + coreContext.postOnCoreThread { core -> + core.addListener(coreListener) + if (core.callsNb > 0) { + updateCurrentCallInfo() + } + atLastOneCall.postValue(core.callsNb > 0) + } + + update() } @UiThread override fun onCleared() { super.onCleared() - coreContext.postOnCoreThread { + coreContext.postOnCoreThread { core -> + core.removeListener(coreListener) account.value?.destroy() } } @@ -87,15 +136,51 @@ open class AbstractTopBarViewModel @UiThread constructor() : ViewModel() { } @UiThread - fun updateDefaultAccount() { - coreContext.postOnCoreThread { - Log.i("$TAG Updating displayed default account") - - val core = coreContext.core + fun update() { + coreContext.postOnCoreThread { core -> if (core.accountList.isNotEmpty()) { + Log.i("$TAG Updating displayed default account") val defaultAccount = core.defaultAccount ?: core.accountList.first() account.postValue(AccountModel(defaultAccount)) } + + if (core.callsNb > 0) { + updateCurrentCallInfo() + } + atLastOneCall.postValue(core.callsNb > 0) } } + + @UiThread + fun goBackToCall() { + goBackToCallEvent.value = Event(true) + } + + @WorkerThread + private fun updateCurrentCallInfo() { + val core = coreContext.core + val currentCall = core.currentCall + if (currentCall != null) { + val contact = coreContext.contactsManager.findContactByAddress( + currentCall.remoteAddress + ) + callDisplayName.postValue( + contact?.name ?: LinphoneUtils.getDisplayName(currentCall.remoteAddress) + ) + callStatus.postValue(currentCall.state.name) // TODO: improve text + } else { + val firstCall = core.calls.firstOrNull() + if (firstCall != null) { + val contact = coreContext.contactsManager.findContactByAddress( + firstCall.remoteAddress + ) + callDisplayName.postValue( + contact?.name ?: LinphoneUtils.getDisplayName(firstCall.remoteAddress) + ) + callStatus.postValue(firstCall.state.name) // TODO: improve text + } + } + Log.i("$TAG At least a call, asking fragment to change status bar color") + changeSystemTopBarColorToInCallEvent.postValue(Event(true)) + } } diff --git a/app/src/main/res/drawable/shape_circle_green_disabled_background.xml b/app/src/main/res/drawable/shape_circle_green_disabled_background.xml new file mode 100644 index 000000000..9cf045c2f --- /dev/null +++ b/app/src/main/res/drawable/shape_circle_green_disabled_background.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout-land/calls_list_fragment.xml b/app/src/main/res/layout-land/calls_list_fragment.xml index 214e30b6f..4d65854b5 100644 --- a/app/src/main/res/layout-land/calls_list_fragment.xml +++ b/app/src/main/res/layout-land/calls_list_fragment.xml @@ -35,7 +35,7 @@ + + + + diff --git a/app/src/main/res/layout/account_profile_secure_mode_fragment.xml b/app/src/main/res/layout/account_profile_secure_mode_fragment.xml index 19a510e25..a4f529753 100644 --- a/app/src/main/res/layout/account_profile_secure_mode_fragment.xml +++ b/app/src/main/res/layout/account_profile_secure_mode_fragment.xml @@ -33,7 +33,7 @@ android:adjustViewBounds="true" android:padding="5dp" android:src="@drawable/caret_left" - android:drawableTint="@color/primary_color" + app:tint="@color/gray_1" app:layout_constraintBottom_toBottomOf="@id/title" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="@id/title" /> diff --git a/app/src/main/res/layout/call_fragment.xml b/app/src/main/res/layout/call_fragment.xml index 822b92719..ef09b0c3d 100644 --- a/app/src/main/res/layout/call_fragment.xml +++ b/app/src/main/res/layout/call_fragment.xml @@ -31,6 +31,7 @@ android:onClick="@{backClickListener}" android:visibility="@{viewModel.showBackButton ? View.VISIBLE : View.GONE}" android:src="@drawable/caret_left" + app:tint="@color/gray_1" app:layout_constraintBottom_toBottomOf="@id/title" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="@id/title"/> diff --git a/app/src/main/res/layout/call_start_fragment.xml b/app/src/main/res/layout/call_start_fragment.xml index 400b529ba..f77515d86 100644 --- a/app/src/main/res/layout/call_start_fragment.xml +++ b/app/src/main/res/layout/call_start_fragment.xml @@ -31,10 +31,10 @@ android:layout_height="35dp" android:layout_marginStart="10dp" android:adjustViewBounds="true" - android:drawableTint="@color/primary_color" android:onClick="@{backClickListener}" android:padding="5dp" android:src="@drawable/caret_left" + app:tint="@color/gray_1" app:layout_constraintBottom_toBottomOf="@id/title" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="@id/title" /> diff --git a/app/src/main/res/layout/calls_list_fragment.xml b/app/src/main/res/layout/calls_list_fragment.xml index 0ecc2ea30..9b442f6e8 100644 --- a/app/src/main/res/layout/calls_list_fragment.xml +++ b/app/src/main/res/layout/calls_list_fragment.xml @@ -35,7 +35,7 @@ + + diff --git a/app/src/main/res/layout/contact_new_or_edit_fragment.xml b/app/src/main/res/layout/contact_new_or_edit_fragment.xml index d20e5674f..aeb58b62c 100644 --- a/app/src/main/res/layout/contact_new_or_edit_fragment.xml +++ b/app/src/main/res/layout/contact_new_or_edit_fragment.xml @@ -30,7 +30,7 @@ android:adjustViewBounds="true" android:padding="5dp" android:src="@drawable/caret_left" - android:drawableTint="@color/primary_color" + app:tint="@color/gray_1" app:layout_constraintBottom_toBottomOf="@id/title" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="@id/title" /> @@ -262,9 +262,9 @@ android:layout_marginStart="16dp" android:layout_marginEnd="16dp" android:layout_marginTop="5dp" - android:layout_marginBottom="24dp" android:paddingStart="20dp" android:paddingEnd="20dp" + android:layout_marginBottom="32dp" android:text="@={viewModel.jobTitle, default=`Android dev`}" android:textSize="14sp" android:textColor="@color/gray_9" @@ -272,8 +272,10 @@ android:maxLines="1" android:background="@drawable/edit_text_background" android:inputType="text|textCapWords" + app:layout_constraintVertical_bias="0" app:layout_constraintWidth_max="@dimen/text_input_max_width" app:layout_constraintTop_toBottomOf="@id/job_title_label" + app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent"/> diff --git a/app/src/main/res/layout/contacts_list_fragment.xml b/app/src/main/res/layout/contacts_list_fragment.xml index 44d156fae..c7cfc2c08 100644 --- a/app/src/main/res/layout/contacts_list_fragment.xml +++ b/app/src/main/res/layout/contacts_list_fragment.xml @@ -29,7 +29,7 @@ + + diff --git a/app/src/main/res/layout/help_fragment.xml b/app/src/main/res/layout/help_fragment.xml index 8f13c1695..f4adcd78a 100644 --- a/app/src/main/res/layout/help_fragment.xml +++ b/app/src/main/res/layout/help_fragment.xml @@ -44,7 +44,7 @@ android:adjustViewBounds="true" android:padding="5dp" android:src="@drawable/caret_left" - android:drawableTint="@color/primary_color" + app:tint="@color/gray_1" app:layout_constraintBottom_toBottomOf="@id/title" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="@id/title" /> diff --git a/app/src/main/res/layout/main_activity.xml b/app/src/main/res/layout/main_activity.xml index 41553a9bf..8a9a2a44f 100644 --- a/app/src/main/res/layout/main_activity.xml +++ b/app/src/main/res/layout/main_activity.xml @@ -39,6 +39,7 @@ android:layout_marginTop="@dimen/toast_top_margin" android:layout_marginStart="15dp" android:layout_marginEnd="15dp" + app:layout_constraintWidth_max="@dimen/toast_max_width" app:layout_constraintTop_toTopOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" /> diff --git a/app/src/main/res/layout/main_activity_in_call_top_bar.xml b/app/src/main/res/layout/main_activity_in_call_top_bar.xml new file mode 100644 index 000000000..ee7ee6ef0 --- /dev/null +++ b/app/src/main/res/layout/main_activity_in_call_top_bar.xml @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/top_search_bar.xml b/app/src/main/res/layout/main_activity_top_bar.xml similarity index 100% rename from app/src/main/res/layout/top_search_bar.xml rename to app/src/main/res/layout/main_activity_top_bar.xml diff --git a/app/src/main/res/layout/recordings_fragment.xml b/app/src/main/res/layout/recordings_fragment.xml index e3ec7f622..3c844ccb1 100644 --- a/app/src/main/res/layout/recordings_fragment.xml +++ b/app/src/main/res/layout/recordings_fragment.xml @@ -24,7 +24,7 @@ android:adjustViewBounds="true" android:padding="5dp" android:src="@drawable/caret_left" - android:drawableTint="@color/primary_color" + app:tint="@color/gray_1" app:layout_constraintBottom_toBottomOf="@id/title" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="@id/title" /> diff --git a/app/src/main/res/layout/settings_fragment.xml b/app/src/main/res/layout/settings_fragment.xml index 235db486c..5aae8ff42 100644 --- a/app/src/main/res/layout/settings_fragment.xml +++ b/app/src/main/res/layout/settings_fragment.xml @@ -26,7 +26,7 @@ android:adjustViewBounds="true" android:padding="5dp" android:src="@drawable/caret_left" - android:drawableTint="@color/primary_color" + app:tint="@color/gray_1" app:layout_constraintBottom_toBottomOf="@id/title" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="@id/title" /> diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index c56828f37..d4d06b9e6 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -13,6 +13,7 @@ #F5CCBE #4FAE80 #377D71 + #4FAE80 #DFECF2 #C0D1D9 #4AA8FF