Rework to factorize code

This commit is contained in:
Sylvain Berfini 2023-10-26 12:31:08 +02:00
parent fadc6032fb
commit 36f57be6cc
28 changed files with 358 additions and 477 deletions

View file

@ -144,7 +144,7 @@ class ConversationsFragment : GenericFragment() {
}
}
sharedViewModel.navigateToCallsEvent.observe(viewLifecycleOwner) {
sharedViewModel.navigateToHistoryEvent.observe(viewLifecycleOwner) {
it.consume {
if (findNavController().currentDestination?.id == R.id.conversationsFragment) {
// To prevent any previously seen conversation to show up when navigating back to here later

View file

@ -19,6 +19,7 @@
*/
package org.linphone.ui.main.chat.fragment
import android.content.res.Configuration
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
@ -34,8 +35,7 @@ import org.linphone.ui.main.chat.viewmodel.ConversationsListViewModel
import org.linphone.ui.main.fragment.AbstractTopBarFragment
import org.linphone.ui.main.history.fragment.HistoryMenuDialogFragment
import org.linphone.utils.Event
import org.linphone.utils.hideKeyboard
import org.linphone.utils.showKeyboard
import org.linphone.utils.setKeyboardInsetListener
@UiThread
class ConversationsListFragment : AbstractTopBarFragment() {
@ -57,12 +57,11 @@ class ConversationsListFragment : AbstractTopBarFragment() {
binding = ChatListFragmentBinding.inflate(layoutInflater)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
listViewModel = requireActivity().run {
ViewModelProvider(this)[ConversationsListViewModel::class.java]
}
listViewModel = ViewModelProvider(this)[ConversationsListViewModel::class.java]
binding.lifecycleOwner = viewLifecycleOwner
binding.viewModel = listViewModel
@ -129,6 +128,19 @@ class ConversationsListFragment : AbstractTopBarFragment() {
}
}
// TopBarFragment related
setViewModelAndTitle(
binding.topBar.search,
listViewModel,
getString(R.string.bottom_navigation_conversations_label)
)
binding.root.setKeyboardInsetListener { keyboardVisible ->
val portraitOrientation = resources.configuration.orientation != Configuration.ORIENTATION_LANDSCAPE
binding.bottomNavBar.root.visibility = if (!portraitOrientation || !keyboardVisible) View.VISIBLE else View.GONE
}
sharedViewModel.defaultAccountChangedEvent.observe(viewLifecycleOwner) {
it.consume {
Log.i(
@ -137,27 +149,5 @@ class ConversationsListFragment : AbstractTopBarFragment() {
listViewModel.applyFilter()
}
}
// TopBarFragment related
setViewModelAndTitle(
listViewModel,
getString(R.string.bottom_navigation_conversations_label)
)
listViewModel.searchFilter.observe(viewLifecycleOwner) { filter ->
listViewModel.applyFilter(filter.trim())
}
listViewModel.focusSearchBarEvent.observe(viewLifecycleOwner) {
it.consume { show ->
if (show) {
// To automatically open keyboard
binding.topBar.search.showKeyboard()
} else {
binding.topBar.search.hideKeyboard()
}
}
}
}
}

View file

@ -43,8 +43,6 @@ class ConversationsListViewModel @UiThread constructor() : AbstractTopBarViewMod
val fetchInProgress = MutableLiveData<Boolean>()
private var currentFilter = ""
private val coreListener = object : CoreListenerStub() {
@WorkerThread
override fun onChatRoomStateChanged(
@ -94,9 +92,7 @@ class ConversationsListViewModel @UiThread constructor() : AbstractTopBarViewMod
}
@UiThread
fun applyFilter(filter: String = currentFilter) {
currentFilter = filter
override fun filter() {
coreContext.postOnCoreThread {
computeChatRoomsList(currentFilter)
}

View file

@ -119,7 +119,7 @@ class ContactsFragment : GenericFragment() {
}
}
sharedViewModel.navigateToCallsEvent.observe(viewLifecycleOwner) {
sharedViewModel.navigateToHistoryEvent.observe(viewLifecycleOwner) {
it.consume {
if (findNavController().currentDestination?.id == R.id.contactsFragment) {
// To prevent any previously seen contact to show up when navigating back to here later

View file

@ -20,6 +20,7 @@
package org.linphone.ui.main.contacts.fragment
import android.content.Intent
import android.content.res.Configuration
import android.os.Bundle
import android.provider.ContactsContract
import android.view.Gravity
@ -42,8 +43,7 @@ import org.linphone.ui.main.contacts.adapter.ContactsListAdapter
import org.linphone.ui.main.contacts.viewmodel.ContactsListViewModel
import org.linphone.ui.main.fragment.AbstractTopBarFragment
import org.linphone.utils.Event
import org.linphone.utils.hideKeyboard
import org.linphone.utils.showKeyboard
import org.linphone.utils.setKeyboardInsetListener
@UiThread
class ContactsListFragment : AbstractTopBarFragment() {
@ -70,9 +70,7 @@ class ContactsListFragment : AbstractTopBarFragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
listViewModel = requireActivity().run {
ViewModelProvider(this)[ContactsListViewModel::class.java]
}
listViewModel = ViewModelProvider(this)[ContactsListViewModel::class.java]
binding.lifecycleOwner = viewLifecycleOwner
binding.viewModel = listViewModel
@ -131,6 +129,19 @@ class ContactsListFragment : AbstractTopBarFragment() {
showFilterPopupMenu(binding.filter)
}
// TopBarFragment related
setViewModelAndTitle(
binding.topBar.search,
listViewModel,
getString(R.string.bottom_navigation_contacts_label)
)
binding.root.setKeyboardInsetListener { keyboardVisible ->
val portraitOrientation = resources.configuration.orientation != Configuration.ORIENTATION_LANDSCAPE
binding.bottomNavBar.root.visibility = if (!portraitOrientation || !keyboardVisible) View.VISIBLE else View.GONE
}
sharedViewModel.defaultAccountChangedEvent.observe(viewLifecycleOwner) {
it.consume {
Log.i(
@ -140,25 +151,6 @@ class ContactsListFragment : AbstractTopBarFragment() {
listViewModel.applyCurrentDefaultAccountFilter()
}
}
// TopBarFragment related
setViewModelAndTitle(listViewModel, getString(R.string.bottom_navigation_contacts_label))
listViewModel.searchFilter.observe(viewLifecycleOwner) { filter ->
listViewModel.applyFilter(filter.trim())
}
listViewModel.focusSearchBarEvent.observe(viewLifecycleOwner) {
it.consume { show ->
if (show) {
// To automatically open keyboard
binding.topBar.search.showKeyboard()
} else {
binding.topBar.search.hideKeyboard()
}
}
}
}
private fun configureAdapter(adapter: ContactsListAdapter) {

View file

@ -60,7 +60,6 @@ class ContactsListViewModel @UiThread constructor() : AbstractTopBarViewModel()
MutableLiveData<Event<Pair<String, File>>>()
}
private var currentFilter = ""
private var previousFilter = "NotSet"
private var limitSearchToLinphoneAccounts = true
@ -189,11 +188,11 @@ class ContactsListViewModel @UiThread constructor() : AbstractTopBarViewModel()
}
@UiThread
fun applyFilter(filter: String = currentFilter) {
isListFiltered.value = filter.isNotEmpty()
override fun filter() {
isListFiltered.value = currentFilter.isNotEmpty()
coreContext.postOnCoreThread {
applyFilter(
filter,
currentFilter,
if (limitSearchToLinphoneAccounts) corePreferences.defaultDomain else "",
MagicSearch.Source.Friends.toInt() or MagicSearch.Source.LdapServers.toInt()
)

View file

@ -20,18 +20,84 @@
package org.linphone.ui.main.fragment
import androidx.annotation.UiThread
import com.google.android.material.textfield.TextInputLayout
import org.linphone.R
import org.linphone.ui.main.MainActivity
import org.linphone.ui.main.viewmodel.AbstractTopBarViewModel
import org.linphone.utils.Event
import org.linphone.utils.hideKeyboard
import org.linphone.utils.showKeyboard
@UiThread
abstract class AbstractTopBarFragment : GenericFragment() {
fun setViewModelAndTitle(viewModel: AbstractTopBarViewModel, title: String) {
fun setViewModelAndTitle(
searchBar: TextInputLayout,
viewModel: AbstractTopBarViewModel,
title: String
) {
viewModel.title.value = title
viewModel.focusSearchBarEvent.observe(viewLifecycleOwner) {
it.consume { show ->
if (show) {
// To automatically open keyboard
searchBar.showKeyboard()
} else {
searchBar.hideKeyboard()
}
}
}
viewModel.openDrawerMenuEvent.observe(viewLifecycleOwner) {
it.consume {
(requireActivity() as MainActivity).toggleDrawerMenu()
}
}
viewModel.searchFilter.observe(viewLifecycleOwner) { filter ->
viewModel.applyFilter(filter.trim())
}
viewModel.navigateToContactsEvent.observe(viewLifecycleOwner) {
if (sharedViewModel.currentlyDisplayedFragment.value != R.id.contactsFragment) {
sharedViewModel.navigateToContactsEvent.value = Event(true)
}
}
viewModel.navigateToHistoryEvent.observe(viewLifecycleOwner) {
if (sharedViewModel.currentlyDisplayedFragment.value != R.id.historyFragment) {
sharedViewModel.navigateToHistoryEvent.value = Event(true)
}
}
viewModel.navigateToConversationsEvent.observe(viewLifecycleOwner) {
if (sharedViewModel.currentlyDisplayedFragment.value != R.id.conversationsFragment) {
sharedViewModel.navigateToConversationsEvent.value = Event(true)
}
}
viewModel.navigateToMeetingsEvent.observe(viewLifecycleOwner) {
if (sharedViewModel.currentlyDisplayedFragment.value != R.id.meetingsFragment) {
sharedViewModel.navigateToMeetingsEvent.value = Event(true)
}
}
sharedViewModel.currentlyDisplayedFragment.observe(viewLifecycleOwner) {
viewModel.contactsSelected.value = it == R.id.contactsFragment
viewModel.callsSelected.value = it == R.id.historyFragment
viewModel.conversationsSelected.value = it == R.id.conversationsFragment
viewModel.meetingsSelected.value = it == R.id.meetingsFragment
}
sharedViewModel.resetMissedCallsCountEvent.observe(viewLifecycleOwner) {
it.consume {
viewModel.resetMissedCallsCount()
}
}
sharedViewModel.defaultAccountChangedEvent.observe(viewLifecycleOwner) {
// Do not consume it!
viewModel.updateAvailableMenus()
}
}
}

View file

@ -1,115 +0,0 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
package org.linphone.ui.main.fragment
import android.content.res.Configuration
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.annotation.UiThread
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
import org.linphone.R
import org.linphone.databinding.BottomNavBarBinding
import org.linphone.ui.main.viewmodel.BottomNavBarViewModel
import org.linphone.ui.main.viewmodel.SharedMainViewModel
import org.linphone.utils.Event
import org.linphone.utils.setKeyboardInsetListener
@UiThread
class BottomNavBarFragment : Fragment() {
private lateinit var binding: BottomNavBarBinding
private lateinit var viewModel: BottomNavBarViewModel
private lateinit var sharedViewModel: SharedMainViewModel
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
binding = BottomNavBarBinding.inflate(layoutInflater)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.lifecycleOwner = viewLifecycleOwner
viewModel = requireActivity().run {
ViewModelProvider(this)[BottomNavBarViewModel::class.java]
}
binding.viewModel = viewModel
sharedViewModel = requireActivity().run {
ViewModelProvider(this)[SharedMainViewModel::class.java]
}
binding.root.setKeyboardInsetListener { keyboardVisible ->
val portraitOrientation = resources.configuration.orientation != Configuration.ORIENTATION_LANDSCAPE
binding.bottomNavBar.visibility = if (!portraitOrientation || !keyboardVisible) View.VISIBLE else View.GONE
}
binding.setOnContactsClicked {
if (sharedViewModel.currentlyDisplayedFragment.value != R.id.contactsFragment) {
sharedViewModel.navigateToContactsEvent.value = Event(true)
}
}
binding.setOnCallsClicked {
if (sharedViewModel.currentlyDisplayedFragment.value != R.id.historyFragment) {
sharedViewModel.navigateToCallsEvent.value = Event(true)
}
}
binding.setOnConversationsClicked {
if (sharedViewModel.currentlyDisplayedFragment.value != R.id.conversationsFragment) {
sharedViewModel.navigateToConversationsEvent.value = Event(true)
}
}
binding.setOnMeetingsClicked {
if (sharedViewModel.currentlyDisplayedFragment.value != R.id.meetingsFragment) {
sharedViewModel.navigateToMeetingsEvent.value = Event(true)
}
}
sharedViewModel.currentlyDisplayedFragment.observe(viewLifecycleOwner) {
viewModel.contactsSelected.value = it == R.id.contactsFragment
viewModel.callsSelected.value = it == R.id.historyFragment
viewModel.conversationsSelected.value = it == R.id.conversationsFragment
viewModel.meetingsSelected.value = it == R.id.meetingsFragment
}
sharedViewModel.resetMissedCallsCountEvent.observe(viewLifecycleOwner) {
it.consume {
viewModel.resetMissedCallsCount()
}
}
sharedViewModel.defaultAccountChangedEvent.observe(viewLifecycleOwner) {
// Do not consume it!
viewModel.updateAvailableMenus()
}
}
}

View file

@ -22,6 +22,7 @@ package org.linphone.ui.main.history.fragment
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.content.res.Configuration
import android.os.Bundle
import android.view.Gravity
import android.view.LayoutInflater
@ -44,8 +45,7 @@ import org.linphone.ui.main.history.model.ConfirmationDialogModel
import org.linphone.ui.main.history.viewmodel.HistoryListViewModel
import org.linphone.utils.DialogUtils
import org.linphone.utils.Event
import org.linphone.utils.hideKeyboard
import org.linphone.utils.showKeyboard
import org.linphone.utils.setKeyboardInsetListener
@UiThread
class HistoryListFragment : AbstractTopBarFragment() {
@ -67,12 +67,11 @@ class HistoryListFragment : AbstractTopBarFragment() {
binding = HistoryListFragmentBinding.inflate(layoutInflater)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
listViewModel = requireActivity().run {
ViewModelProvider(this)[HistoryListViewModel::class.java]
}
listViewModel = ViewModelProvider(this)[HistoryListViewModel::class.java]
binding.lifecycleOwner = viewLifecycleOwner
binding.viewModel = listViewModel
@ -177,6 +176,19 @@ class HistoryListFragment : AbstractTopBarFragment() {
sharedViewModel.showStartCallEvent.value = Event(true)
}
// TopBarFragment related
setViewModelAndTitle(
binding.topBar.search,
listViewModel,
getString(R.string.bottom_navigation_calls_label)
)
binding.root.setKeyboardInsetListener { keyboardVisible ->
val portraitOrientation = resources.configuration.orientation != Configuration.ORIENTATION_LANDSCAPE
binding.bottomNavBar.root.visibility = if (!portraitOrientation || !keyboardVisible) View.VISIBLE else View.GONE
}
sharedViewModel.defaultAccountChangedEvent.observe(viewLifecycleOwner) {
it.consume {
Log.i(
@ -186,25 +198,6 @@ class HistoryListFragment : AbstractTopBarFragment() {
listViewModel.applyFilter()
}
}
// TopBarFragment related
setViewModelAndTitle(listViewModel, getString(R.string.bottom_navigation_calls_label))
listViewModel.searchFilter.observe(viewLifecycleOwner) { filter ->
listViewModel.applyFilter(filter.trim())
}
listViewModel.focusSearchBarEvent.observe(viewLifecycleOwner) {
it.consume { show ->
if (show) {
// To automatically open keyboard
binding.topBar.search.showKeyboard()
} else {
binding.topBar.search.hideKeyboard()
}
}
}
}
override fun onResume() {

View file

@ -46,8 +46,6 @@ class HistoryListViewModel @UiThread constructor() : AbstractTopBarViewModel() {
MutableLiveData<Event<Boolean>>()
}
private var currentFilter = ""
private val coreListener = object : CoreListenerStub() {
override fun onCallLogUpdated(core: Core, callLog: CallLog) {
computeCallLogsList(currentFilter)
@ -81,15 +79,6 @@ class HistoryListViewModel @UiThread constructor() : AbstractTopBarViewModel() {
}
}
@UiThread
fun applyFilter(filter: String = currentFilter) {
currentFilter = filter
coreContext.postOnCoreThread {
computeCallLogsList(currentFilter)
}
}
@UiThread
fun removeAllCallLogs() {
coreContext.postOnCoreThread { core ->
@ -107,6 +96,13 @@ class HistoryListViewModel @UiThread constructor() : AbstractTopBarViewModel() {
}
}
@UiThread
override fun filter() {
coreContext.postOnCoreThread {
computeCallLogsList(currentFilter)
}
}
@WorkerThread
private fun computeCallLogsList(filter: String) {
if (callLogs.value.orEmpty().isEmpty()) {

View file

@ -134,7 +134,7 @@ class MeetingsFragment : GenericFragment() {
}
}
sharedViewModel.navigateToCallsEvent.observe(viewLifecycleOwner) {
sharedViewModel.navigateToHistoryEvent.observe(viewLifecycleOwner) {
it.consume {
if (findNavController().currentDestination?.id == R.id.meetingsFragment) {
// To prevent any previously seen meeting to show up when navigating back to here later

View file

@ -19,6 +19,7 @@
*/
package org.linphone.ui.main.meetings.fragment
import android.content.res.Configuration
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
@ -36,8 +37,7 @@ 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
import org.linphone.utils.showKeyboard
import org.linphone.utils.setKeyboardInsetListener
@UiThread
class MeetingsListFragment : AbstractTopBarFragment() {
@ -59,13 +59,12 @@ class MeetingsListFragment : AbstractTopBarFragment() {
binding = MeetingsListFragmentBinding.inflate(layoutInflater)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
postponeEnterTransition()
listViewModel = requireActivity().run {
ViewModelProvider(this)[MeetingsListViewModel::class.java]
}
listViewModel = ViewModelProvider(this)[MeetingsListViewModel::class.java]
binding.lifecycleOwner = viewLifecycleOwner
binding.viewModel = listViewModel
@ -98,7 +97,6 @@ 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()
@ -114,6 +112,19 @@ class MeetingsListFragment : AbstractTopBarFragment() {
}
}
// TopBarFragment related
setViewModelAndTitle(
binding.topBar.search,
listViewModel,
getString(R.string.bottom_navigation_meetings_label)
)
binding.root.setKeyboardInsetListener { keyboardVisible ->
val portraitOrientation = resources.configuration.orientation != Configuration.ORIENTATION_LANDSCAPE
binding.bottomNavBar.root.visibility = if (!portraitOrientation || !keyboardVisible) View.VISIBLE else View.GONE
}
sharedViewModel.defaultAccountChangedEvent.observe(viewLifecycleOwner) {
it.consume {
Log.i(
@ -122,28 +133,6 @@ class MeetingsListFragment : AbstractTopBarFragment() {
listViewModel.applyFilter()
}
}
// TopBarFragment related
setViewModelAndTitle(
listViewModel,
getString(R.string.bottom_navigation_meetings_label)
)
listViewModel.searchFilter.observe(viewLifecycleOwner) { filter ->
listViewModel.applyFilter(filter.trim())
}
listViewModel.focusSearchBarEvent.observe(viewLifecycleOwner) {
it.consume { show ->
if (show) {
// To automatically open keyboard
binding.topBar.search.showKeyboard()
} else {
binding.topBar.search.hideKeyboard()
}
}
}
}
private fun scrollToToday() {

View file

@ -67,9 +67,7 @@ class ScheduleMeetingFragment : GenericFragment() {
binding.lifecycleOwner = viewLifecycleOwner
viewModel = requireActivity().run {
ViewModelProvider(this)[ScheduleMeetingViewModel::class.java]
}
viewModel = ViewModelProvider(this)[ScheduleMeetingViewModel::class.java]
binding.viewModel = viewModel
binding.setBackClickListener {

View file

@ -40,8 +40,6 @@ class MeetingsListViewModel @UiThread constructor() : AbstractTopBarViewModel()
val fetchInProgress = MutableLiveData<Boolean>()
private var currentFilter = ""
private val coreListener = object : CoreListenerStub() {
@WorkerThread
override fun onConferenceInfoReceived(core: Core, conferenceInfo: ConferenceInfo) {
@ -68,11 +66,9 @@ class MeetingsListViewModel @UiThread constructor() : AbstractTopBarViewModel()
}
@UiThread
fun applyFilter(filter: String = currentFilter) {
currentFilter = filter
override fun filter() {
coreContext.postOnCoreThread {
computeMeetingsList(filter)
computeMeetingsList(currentFilter)
}
}

View file

@ -66,9 +66,7 @@ class AccountSettingsFragment : GenericFragment() {
binding.lifecycleOwner = viewLifecycleOwner
viewModel = requireActivity().run {
ViewModelProvider(this)[AccountSettingsViewModel::class.java]
}
viewModel = ViewModelProvider(this)[AccountSettingsViewModel::class.java]
binding.viewModel = viewModel
val identity = args.accountIdentity

View file

@ -20,12 +20,20 @@
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.LinphoneApplication.Companion.corePreferences
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.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 +48,22 @@ open class AbstractTopBarViewModel @UiThread constructor() : ViewModel() {
val searchFilter = MutableLiveData<String>()
val contactsSelected = MutableLiveData<Boolean>()
val callsSelected = MutableLiveData<Boolean>()
val conversationsSelected = MutableLiveData<Boolean>()
val meetingsSelected = MutableLiveData<Boolean>()
val hideConversations = MutableLiveData<Boolean>()
val hideMeetings = MutableLiveData<Boolean>()
val missedCallsCount = MutableLiveData<Int>()
val unreadMessages = MutableLiveData<Int>()
val focusSearchBarEvent: MutableLiveData<Event<Boolean>> by lazy {
MutableLiveData<Event<Boolean>>()
}
@ -48,9 +72,62 @@ open class AbstractTopBarViewModel @UiThread constructor() : ViewModel() {
MutableLiveData<Event<Boolean>>()
}
val navigateToHistoryEvent: MutableLiveData<Event<Boolean>> by lazy {
MutableLiveData<Event<Boolean>>()
}
val navigateToContactsEvent: MutableLiveData<Event<Boolean>> by lazy {
MutableLiveData<Event<Boolean>>()
}
val navigateToConversationsEvent: MutableLiveData<Event<Boolean>> by lazy {
MutableLiveData<Event<Boolean>>()
}
val navigateToMeetingsEvent: MutableLiveData<Event<Boolean>> by lazy {
MutableLiveData<Event<Boolean>>()
}
protected var currentFilter = ""
private val coreListener = object : CoreListenerStub() {
@WorkerThread
override fun onCallStateChanged(
core: Core,
call: Call,
state: Call.State?,
message: String
) {
if (state == Call.State.End || state == Call.State.Error) {
updateMissedCallsCount()
}
}
@WorkerThread
override fun onMessagesReceived(
core: Core,
chatRoom: ChatRoom,
messages: Array<out ChatMessage>
) {
updateUnreadMessagesCount()
}
@WorkerThread
override fun onChatRoomRead(core: Core, chatRoom: ChatRoom) {
updateUnreadMessagesCount()
}
}
init {
searchBarVisible.value = false
coreContext.postOnCoreThread { core ->
core.addListener(coreListener)
updateMissedCallsCount()
updateUnreadMessagesCount()
}
updateAvailableMenus()
update()
}
@ -59,6 +136,7 @@ open class AbstractTopBarViewModel @UiThread constructor() : ViewModel() {
super.onCleared()
coreContext.postOnCoreThread { core ->
core.removeListener(coreListener)
account.value?.destroy()
}
}
@ -86,6 +164,17 @@ open class AbstractTopBarViewModel @UiThread constructor() : ViewModel() {
searchFilter.value = ""
}
@UiThread
fun applyFilter(filter: String = currentFilter) {
Log.i("$TAG New filter set by user [$filter]")
currentFilter = filter
filter()
}
@UiThread
open fun filter() {
}
@UiThread
fun update() {
coreContext.postOnCoreThread { core ->
@ -98,4 +187,68 @@ open class AbstractTopBarViewModel @UiThread constructor() : ViewModel() {
}
}
}
@UiThread
fun navigateToContacts() {
navigateToContactsEvent.value = Event(true)
}
@UiThread
fun navigateToHistory() {
navigateToHistoryEvent.value = Event(true)
}
@UiThread
fun navigateToConversations() {
navigateToConversationsEvent.value = Event(true)
}
@UiThread
fun navigateToMeetings() {
navigateToMeetingsEvent.value = Event(true)
}
@WorkerThread
fun updateMissedCallsCount() {
val account = LinphoneUtils.getDefaultAccount()
val count = account?.missedCallsCount ?: coreContext.core.missedCallsCount
val moreThanOne = count > 1
Log.i(
"$TAG There ${if (moreThanOne) "are" else "is"} [$count] missed ${if (moreThanOne) "calls" else "call"}"
)
missedCallsCount.postValue(count)
}
@WorkerThread
fun updateUnreadMessagesCount() {
val account = LinphoneUtils.getDefaultAccount()
val count = account?.unreadChatMessageCount ?: coreContext.core.unreadChatMessageCount
val moreThanOne = count > 1
Log.i(
"$TAG There ${if (moreThanOne) "are" else "is"} [$count] unread ${if (moreThanOne) "messages" else "message"}"
)
unreadMessages.postValue(count)
}
@UiThread
fun resetMissedCallsCount() {
coreContext.postOnCoreThread { core ->
val account = LinphoneUtils.getDefaultAccount()
account?.resetMissedCallsCount() ?: core.resetMissedCallsCount()
updateMissedCallsCount()
}
}
@UiThread
fun updateAvailableMenus() {
coreContext.postOnCoreThread { core ->
hideConversations.postValue(corePreferences.disableChat)
val hideGroupCall =
corePreferences.disableMeetings || !LinphoneUtils.isRemoteConferencingAvailable(
core
)
hideMeetings.postValue(hideGroupCall)
}
}
}

View file

@ -1,147 +0,0 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
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.LinphoneApplication.Companion.corePreferences
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.tools.Log
import org.linphone.utils.LinphoneUtils
class BottomNavBarViewModel @UiThread constructor() : ViewModel() {
companion object {
private const val TAG = "[Bottom Navigation Bar ViewModel]"
}
val contactsSelected = MutableLiveData<Boolean>()
val callsSelected = MutableLiveData<Boolean>()
val conversationsSelected = MutableLiveData<Boolean>()
val meetingsSelected = MutableLiveData<Boolean>()
val hideConversations = MutableLiveData<Boolean>()
val hideMeetings = MutableLiveData<Boolean>()
val missedCallsCount = MutableLiveData<Int>()
val unreadMessages = MutableLiveData<Int>()
private val coreListener = object : CoreListenerStub() {
@WorkerThread
override fun onCallStateChanged(
core: Core,
call: Call,
state: Call.State?,
message: String
) {
if (state == Call.State.End || state == Call.State.Error) {
updateMissedCallsCount()
}
}
@WorkerThread
override fun onMessagesReceived(
core: Core,
chatRoom: ChatRoom,
messages: Array<out ChatMessage>
) {
updateUnreadMessagesCount()
}
@WorkerThread
override fun onChatRoomRead(core: Core, chatRoom: ChatRoom) {
updateUnreadMessagesCount()
}
}
init {
coreContext.postOnCoreThread { core ->
core.addListener(coreListener)
updateMissedCallsCount()
updateUnreadMessagesCount()
}
updateAvailableMenus()
}
@UiThread
override fun onCleared() {
super.onCleared()
coreContext.postOnCoreThread { core ->
core.removeListener(coreListener)
}
}
@WorkerThread
fun updateMissedCallsCount() {
val account = LinphoneUtils.getDefaultAccount()
val count = account?.missedCallsCount ?: coreContext.core.missedCallsCount
val moreThanOne = count > 1
Log.i(
"$TAG There ${if (moreThanOne) "are" else "is"} [$count] missed ${if (moreThanOne) "calls" else "call"}"
)
missedCallsCount.postValue(count)
}
@WorkerThread
fun updateUnreadMessagesCount() {
val account = LinphoneUtils.getDefaultAccount()
val count = account?.unreadChatMessageCount ?: coreContext.core.unreadChatMessageCount
val moreThanOne = count > 1
Log.i(
"$TAG There ${if (moreThanOne) "are" else "is"} [$count] unread ${if (moreThanOne) "messages" else "message"}"
)
unreadMessages.postValue(count)
}
@UiThread
fun resetMissedCallsCount() {
coreContext.postOnCoreThread { core ->
val account = LinphoneUtils.getDefaultAccount()
account?.resetMissedCallsCount() ?: core.resetMissedCallsCount()
updateMissedCallsCount()
}
}
@UiThread
fun updateAvailableMenus() {
coreContext.postOnCoreThread { core ->
hideConversations.postValue(corePreferences.disableChat)
val hideGroupCall =
corePreferences.disableMeetings || !LinphoneUtils.isRemoteConferencingAvailable(
core
)
hideMeetings.postValue(hideGroupCall)
}
}
}

View file

@ -37,7 +37,7 @@ class SharedMainViewModel @UiThread constructor() : ViewModel() {
MutableLiveData<Event<Boolean>>()
}
val navigateToCallsEvent: MutableLiveData<Event<Boolean>> by lazy {
val navigateToHistoryEvent: MutableLiveData<Event<Boolean>> by lazy {
MutableLiveData<Event<Boolean>>()
}

View file

@ -6,21 +6,9 @@
<data>
<import type="android.view.View" />
<import type="android.graphics.Typeface" />
<variable
name="onContactsClicked"
type="View.OnClickListener" />
<variable
name="onCallsClicked"
type="View.OnClickListener" />
<variable
name="onConversationsClicked"
type="View.OnClickListener" />
<variable
name="onMeetingsClicked"
type="View.OnClickListener" />
<variable
name="viewModel"
type="org.linphone.ui.main.viewmodel.BottomNavBarViewModel" />
type="org.linphone.ui.main.viewmodel.AbstractTopBarViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
@ -32,12 +20,12 @@
<androidx.appcompat.widget.AppCompatTextView
style="@style/bottom_nav_bar_label_style"
android:id="@+id/contacts"
android:onClick="@{() -> viewModel.navigateToContacts()}"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:drawableTop="@drawable/address_book"
android:drawablePadding="10dp"
android:drawableTint="@{viewModel.contactsSelected ? @color/orange_main_500 : @color/gray_main2_600, default=@color/gray_main2_600}"
android:onClick="@{onContactsClicked}"
android:text="@string/bottom_navigation_contacts_label"
android:textStyle="@{viewModel.contactsSelected ? Typeface.BOLD : Typeface.NORMAL}"
app:layout_constraintBottom_toTopOf="@id/calls"
@ -47,8 +35,8 @@
<androidx.appcompat.widget.AppCompatTextView
style="@style/bottom_nav_bar_label_style"
android:onClick="@{onCallsClicked}"
android:id="@+id/calls"
android:onClick="@{() -> viewModel.navigateToHistory()}"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:drawableTop="@drawable/phone"
@ -78,15 +66,15 @@
<androidx.appcompat.widget.AppCompatTextView
style="@style/bottom_nav_bar_label_style"
android:id="@+id/conversations"
android:visibility="@{viewModel.hideConversations ? View.GONE : View.VISIBLE}"
android:onClick="@{() -> viewModel.navigateToConversations()}"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:drawableTop="@drawable/chat_teardrop_text"
android:drawablePadding="10dp"
android:drawableTint="@{viewModel.conversationsSelected ? @color/orange_main_500 : @color/gray_main2_600, default=@color/gray_main2_600}"
android:onClick="@{onConversationsClicked}"
android:text="@string/bottom_navigation_conversations_label"
android:textStyle="@{viewModel.conversationsSelected ? Typeface.BOLD : Typeface.NORMAL}"
android:visibility="@{viewModel.hideConversations ? View.GONE : View.VISIBLE}"
app:layout_constraintBottom_toTopOf="@id/meetings"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
@ -109,9 +97,8 @@
<androidx.appcompat.widget.AppCompatTextView
style="@style/bottom_nav_bar_label_style"
android:onClick="@{onMeetingsClicked}"
android:id="@+id/meetings"
android:visibility="@{viewModel.hideMeetings ? View.GONE : View.VISIBLE}"
android:onClick="@{() -> viewModel.navigateToMeetings()}"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:drawableTop="@drawable/users_three"
@ -119,6 +106,7 @@
android:drawableTint="@{viewModel.meetingsSelected ? @color/orange_main_500 : @color/gray_main2_600, default=@color/gray_main2_600}"
android:text="@string/bottom_navigation_meetings_label"
android:textStyle="@{viewModel.meetingsSelected ? Typeface.BOLD : Typeface.NORMAL}"
android:visibility="@{viewModel.hideMeetings ? View.GONE : View.VISIBLE}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"

View file

@ -31,12 +31,12 @@
app:constraint_referenced_ids="no_conversation_image, no_conversation_label"
android:visibility="@{viewModel.conversations.empty ? View.VISIBLE : View.GONE}" />
<androidx.fragment.app.FragmentContainerView
<include
android:id="@+id/bottom_nav_bar"
android:name="org.linphone.ui.main.fragment.BottomNavBarFragment"
android:layout_width="75dp"
android:layout_height="0dp"
bind:layout="@layout/bottom_nav_bar"
android:layout_width="@dimen/landscape_nav_bar_width"
android:layout_height="match_parent"
layout="@layout/bottom_nav_bar"
viewModel="@{viewModel}"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent" />

View file

@ -31,12 +31,12 @@
app:constraint_referenced_ids="no_contacts_image, no_contacts_label"
android:visibility="@{viewModel.contactsList.empty ? View.VISIBLE : View.GONE}" />
<androidx.fragment.app.FragmentContainerView
<include
android:id="@+id/bottom_nav_bar"
android:name="org.linphone.ui.main.fragment.BottomNavBarFragment"
android:layout_width="75dp"
android:layout_height="0dp"
bind:layout="@layout/bottom_nav_bar"
android:layout_width="@dimen/landscape_nav_bar_width"
android:layout_height="match_parent"
layout="@layout/bottom_nav_bar"
viewModel="@{viewModel}"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent" />

View file

@ -37,6 +37,16 @@
app:constraint_referenced_ids="no_calls_image, no_calls_label"
android:visibility="@{viewModel.callLogs.empty ? View.VISIBLE : View.GONE}" />
<include
android:id="@+id/bottom_nav_bar"
android:layout_width="@dimen/landscape_nav_bar_width"
android:layout_height="match_parent"
layout="@layout/bottom_nav_bar"
viewModel="@{viewModel}"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent" />
<include
android:id="@+id/top_bar"
layout="@layout/top_bar"
@ -106,16 +116,6 @@
app:layout_constraintTop_toBottomOf="@id/top_bar"
app:layout_constraintBottom_toBottomOf="parent" />
<androidx.fragment.app.FragmentContainerView
android:id="@+id/bottom_nav_bar"
android:name="org.linphone.ui.main.fragment.BottomNavBarFragment"
android:layout_width="75dp"
android:layout_height="0dp"
bind:layout="@layout/bottom_nav_bar"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:onClick="@{startCallClickListener}"
android:id="@+id/new_call"

View file

@ -34,12 +34,12 @@
app:constraint_referenced_ids="no_meeting_image, no_meeting_label"
android:visibility="@{viewModel.meetings.empty ? View.VISIBLE : View.GONE}" />
<androidx.fragment.app.FragmentContainerView
<include
android:id="@+id/bottom_nav_bar"
android:name="org.linphone.ui.main.fragment.BottomNavBarFragment"
android:layout_width="75dp"
android:layout_height="0dp"
bind:layout="@layout/bottom_nav_bar"
android:layout_width="@dimen/landscape_nav_bar_width"
android:layout_height="match_parent"
layout="@layout/bottom_nav_bar"
viewModel="@{viewModel}"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent" />

View file

@ -6,33 +6,22 @@
<data>
<import type="android.view.View" />
<import type="android.graphics.Typeface" />
<variable
name="onContactsClicked"
type="View.OnClickListener" />
<variable
name="onCallsClicked"
type="View.OnClickListener" />
<variable
name="onConversationsClicked"
type="View.OnClickListener" />
<variable
name="onMeetingsClicked"
type="View.OnClickListener" />
<variable
name="viewModel"
type="org.linphone.ui.main.viewmodel.BottomNavBarViewModel" />
type="org.linphone.ui.main.viewmodel.AbstractTopBarViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/bottom_nav_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:elevation="16dp"
android:background="@color/white">
<androidx.appcompat.widget.AppCompatTextView
style="@style/bottom_nav_bar_label_style"
android:onClick="@{onContactsClicked}"
android:id="@+id/contacts"
android:onClick="@{() -> viewModel.navigateToContacts()}"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:paddingTop="16dp"
@ -49,8 +38,8 @@
<androidx.appcompat.widget.AppCompatTextView
style="@style/bottom_nav_bar_label_style"
android:onClick="@{onCallsClicked}"
android:id="@+id/calls"
android:onClick="@{() -> viewModel.navigateToHistory()}"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="18dp"
@ -86,9 +75,8 @@
<androidx.appcompat.widget.AppCompatTextView
style="@style/bottom_nav_bar_label_style"
android:onClick="@{onConversationsClicked}"
android:visibility="@{viewModel.hideConversations ? View.GONE : View.VISIBLE}"
android:id="@+id/conversations"
android:onClick="@{() -> viewModel.navigateToConversations()}"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="18dp"
@ -99,6 +87,7 @@
android:drawableTint="@{viewModel.conversationsSelected ? @color/orange_main_500 : @color/gray_main2_600, default=@color/gray_main2_600}"
android:text="@string/bottom_navigation_conversations_label"
android:textStyle="@{viewModel.conversationsSelected ? Typeface.BOLD : Typeface.NORMAL}"
android:visibility="@{viewModel.hideConversations ? View.GONE : View.VISIBLE}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/meetings"
app:layout_constraintStart_toEndOf="@id/calls"
@ -124,9 +113,8 @@
<androidx.appcompat.widget.AppCompatTextView
style="@style/bottom_nav_bar_label_style"
android:onClick="@{onMeetingsClicked}"
android:id="@+id/meetings"
android:visibility="@{viewModel.hideMeetings ? View.GONE : View.VISIBLE}"
android:onClick="@{() -> viewModel.navigateToMeetings()}"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="18dp"
@ -137,6 +125,7 @@
android:drawableTint="@{viewModel.meetingsSelected ? @color/orange_main_500 : @color/gray_main2_600, default=@color/gray_main2_600}"
android:text="@string/bottom_navigation_meetings_label"
android:textStyle="@{viewModel.meetingsSelected ? Typeface.BOLD : Typeface.NORMAL}"
android:visibility="@{viewModel.hideMeetings ? View.GONE : View.VISIBLE}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/conversations"

View file

@ -88,12 +88,12 @@
app:layout_constraintTop_toTopOf="@id/background"
app:layout_constraintBottom_toTopOf="@id/bottom_nav_bar" />
<androidx.fragment.app.FragmentContainerView
<include
android:id="@+id/bottom_nav_bar"
android:name="org.linphone.ui.main.fragment.BottomNavBarFragment"
android:layout_width="0dp"
android:layout_height="wrap_content"
bind:layout="@layout/bottom_nav_bar"
layout="@layout/bottom_nav_bar"
viewModel="@{viewModel}"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent" />

View file

@ -144,12 +144,12 @@
app:layout_constraintTop_toBottomOf="@id/all_contacts_label"
app:layout_constraintBottom_toTopOf="@id/bottom_nav_bar" />
<androidx.fragment.app.FragmentContainerView
<include
android:id="@+id/bottom_nav_bar"
android:name="org.linphone.ui.main.fragment.BottomNavBarFragment"
android:layout_width="0dp"
android:layout_height="wrap_content"
bind:layout="@layout/bottom_nav_bar"
layout="@layout/bottom_nav_bar"
viewModel="@{viewModel}"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent" />

View file

@ -106,12 +106,12 @@
app:layout_constraintTop_toBottomOf="@id/top_bar"
app:layout_constraintBottom_toTopOf="@id/bottom_nav_bar" />
<androidx.fragment.app.FragmentContainerView
<include
android:id="@+id/bottom_nav_bar"
android:name="org.linphone.ui.main.fragment.BottomNavBarFragment"
android:layout_width="0dp"
android:layout_height="wrap_content"
bind:layout="@layout/bottom_nav_bar"
layout="@layout/bottom_nav_bar"
viewModel="@{viewModel}"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent" />

View file

@ -103,12 +103,12 @@
app:layout_constraintTop_toTopOf="@id/background"
app:layout_constraintBottom_toTopOf="@id/bottom_nav_bar" />
<androidx.fragment.app.FragmentContainerView
<include
android:id="@+id/bottom_nav_bar"
android:name="org.linphone.ui.main.fragment.BottomNavBarFragment"
android:layout_width="0dp"
android:layout_height="wrap_content"
bind:layout="@layout/bottom_nav_bar"
layout="@layout/bottom_nav_bar"
viewModel="@{viewModel}"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent" />