Reworked GenericAddressPicker related code, updated CorePreferences getters to be @AnyThread instead of @WorkerThread

This commit is contained in:
Sylvain Berfini 2025-08-19 11:01:19 +02:00
parent 77d744d020
commit 881e2c217b
12 changed files with 219 additions and 243 deletions

View file

@ -40,84 +40,84 @@ class CorePreferences
private var _config: Config? = null
@get:WorkerThread @set:WorkerThread
@get:AnyThread @set:WorkerThread
var config: Config
get() = _config ?: coreContext.core.config
set(value) {
_config = value
}
@get:WorkerThread @set:WorkerThread
@get:AnyThread @set:WorkerThread
var printLogsInLogcat: Boolean
get() = config.getBool("app", "debug", BuildConfig.DEBUG)
set(value) {
config.setBool("app", "debug", value)
}
@get:WorkerThread @set:WorkerThread
@get:AnyThread @set:WorkerThread
var sendLogsToCrashlytics: Boolean
get() = config.getBool("app", "send_logs_to_crashlytics", BuildConfig.CRASHLYTICS_ENABLED)
set(value) {
config.setBool("app", "send_logs_to_crashlytics", value)
}
@get:WorkerThread @set:WorkerThread
@get:AnyThread @set:WorkerThread
var firstLaunch: Boolean
get() = config.getBool("app", "first_6.0_launch", true)
set(value) {
config.setBool("app", "first_6.0_launch", value)
}
@get:WorkerThread @set:WorkerThread
@get:AnyThread @set:WorkerThread
var linphoneConfigurationVersion: Int
get() = config.getInt("app", "config_version", 52005)
set(value) {
config.setInt("app", "config_version", value)
}
@get:WorkerThread @set:WorkerThread
@get:AnyThread @set:WorkerThread
var autoStart: Boolean
get() = config.getBool("app", "auto_start", true)
set(value) {
config.setBool("app", "auto_start", value)
}
@get:WorkerThread @set:WorkerThread
@get:AnyThread @set:WorkerThread
var checkForUpdateServerUrl: String
get() = config.getString("misc", "version_check_url_root", "").orEmpty()
set(value) {
config.setString("misc", "version_check_url_root", value)
}
@get:WorkerThread @set:WorkerThread
@get:AnyThread @set:WorkerThread
var conditionsAndPrivacyPolicyAccepted: Boolean
get() = config.getBool("app", "read_and_agree_terms_and_privacy", false)
set(value) {
config.setBool("app", "read_and_agree_terms_and_privacy", value)
}
@get:WorkerThread @set:WorkerThread
@get:AnyThread @set:WorkerThread
var publishPresence: Boolean
get() = config.getBool("app", "publish_presence", true)
set(value) {
config.setBool("app", "publish_presence", value)
}
@get:WorkerThread @set:WorkerThread
@get:AnyThread @set:WorkerThread
var keepServiceAlive: Boolean
get() = config.getBool("app", "keep_service_alive", false)
set(value) {
config.setBool("app", "keep_service_alive", value)
}
@get:WorkerThread @set:WorkerThread
@get:AnyThread @set:WorkerThread
var deviceName: String
get() = config.getString("app", "device", "").orEmpty().trim()
set(value) {
config.setString("app", "device", value.trim())
}
@get:WorkerThread @set:WorkerThread
@get:AnyThread @set:WorkerThread
var showDeveloperSettings: Boolean
get() = config.getBool("ui", "show_developer_settings", false)
set(value) {
@ -127,63 +127,63 @@ class CorePreferences
// Call settings
// This won't be done if bluetooth or wired headset is used
@get:WorkerThread @set:WorkerThread
@get:AnyThread @set:WorkerThread
var routeAudioToSpeakerWhenVideoIsEnabled: Boolean
get() = config.getBool("app", "route_audio_to_speaker_when_video_enabled", true)
set(value) {
config.setBool("app", "route_audio_to_speaker_when_video_enabled", value)
}
@get:WorkerThread @set:WorkerThread
@get:AnyThread @set:WorkerThread
var callRecordingUseSmffFormat: Boolean
get() = config.getBool("app", "use_smff_for_call_recording", false)
set(value) {
config.setBool("app", "use_smff_for_call_recording", value)
}
@get:WorkerThread @set:WorkerThread
@get:AnyThread @set:WorkerThread
var automaticallyStartCallRecording: Boolean
get() = config.getBool("app", "auto_start_call_record", false)
set(value) {
config.setBool("app", "auto_start_call_record", value)
}
@get:WorkerThread @set:WorkerThread
@get:AnyThread @set:WorkerThread
var showDialogWhenCallingDeviceUuidDirectly: Boolean
get() = config.getBool("app", "show_confirmation_dialog_zrtp_trust_call", true)
set(value) {
config.setBool("app", "show_confirmation_dialog_zrtp_trust_call", value)
}
@get:WorkerThread @set:WorkerThread
@get:AnyThread @set:WorkerThread
var acceptEarlyMedia: Boolean
get() = config.getBool("sip", "incoming_calls_early_media", false)
set(value) {
config.setBool("sip", "incoming_calls_early_media", value)
}
@get:WorkerThread @set:WorkerThread
@get:AnyThread @set:WorkerThread
var allowOutgoingEarlyMedia: Boolean
get() = config.getBool("misc", "real_early_media", false)
set(value) {
config.setBool("misc", "real_early_media", value)
}
@get:WorkerThread @set:WorkerThread
@get:AnyThread @set:WorkerThread
var autoAnswerEnabled: Boolean
get() = config.getBool("app", "auto_answer", false)
set(value) {
config.setBool("app", "auto_answer", value)
}
@get:WorkerThread @set:WorkerThread
@get:AnyThread @set:WorkerThread
var autoAnswerDelay: Int
get() = config.getInt("app", "auto_answer_delay", 0)
set(value) {
config.setInt("app", "auto_answer_delay", value)
}
@get:WorkerThread @set:WorkerThread
@get:AnyThread @set:WorkerThread
var autoAnswerVideoCallsWithVideoDirectionSendReceive: Boolean
get() = config.getBool("app", "auto_answer_video_send_receive", false)
set(value) {
@ -192,13 +192,14 @@ class CorePreferences
// Conversation related
@get:WorkerThread @set:WorkerThread
@get:AnyThread @set:WorkerThread
var markConversationAsReadWhenDismissingMessageNotification: Boolean
get() = config.getBool("app", "mark_as_read_notif_dismissal", false)
set(value) {
config.setBool("app", "mark_as_read_notif_dismissal", value)
}
@get:AnyThread @set:WorkerThread
var makePublicMediaFilesDownloaded: Boolean
// Keep old name for backward compatibility
get() = config.getBool("app", "make_downloaded_images_public_in_gallery", false)
@ -208,7 +209,7 @@ class CorePreferences
// Conference related
@get:WorkerThread @set:WorkerThread
@get:AnyThread @set:WorkerThread
var createEndToEndEncryptedMeetingsAndGroupCalls: Boolean
get() = config.getBool("app", "create_e2e_encrypted_conferences", false)
set(value) {
@ -217,35 +218,35 @@ class CorePreferences
// Contacts related
@get:WorkerThread @set:WorkerThread
@get:AnyThread @set:WorkerThread
var sortContactsByFirstName: Boolean
get() = config.getBool("ui", "sort_contacts_by_first_name", true) // If disabled, last name will be used
set(value) {
config.setBool("ui", "sort_contacts_by_first_name", value)
}
@get:WorkerThread
@get:AnyThread
var hideContactsWithoutPhoneNumberOrSipAddress: Boolean
get() = config.getBool("ui", "hide_contacts_without_phone_number_or_sip_address", false)
set(value) {
config.setBool("ui", "hide_contacts_without_phone_number_or_sip_address", value)
}
@get:WorkerThread @set:WorkerThread
@get:AnyThread @set:WorkerThread
var contactsFilter: String
get() = config.getString("ui", "contacts_filter", "")!! // Default value must be empty!
set(value) {
config.setString("ui", "contacts_filter", value)
}
@get:WorkerThread @set:WorkerThread
@get:AnyThread @set:WorkerThread
var showFavoriteContacts: Boolean
get() = config.getBool("ui", "show_favorites_contacts", true)
set(value) {
config.setBool("ui", "show_favorites_contacts", value)
}
@get:WorkerThread @set:WorkerThread
@get:AnyThread @set:WorkerThread
var friendListInWhichStoreNewlyCreatedFriends: String
get() = config.getString(
"app",
@ -258,7 +259,7 @@ class CorePreferences
// Voice recordings related
@get:WorkerThread @set:WorkerThread
@get:AnyThread @set:WorkerThread
var voiceRecordingMaxDuration: Int
get() = config.getInt("app", "voice_recording_max_duration", 600000) // in ms
set(value) = config.setInt("app", "voice_recording_max_duration", value)
@ -266,7 +267,7 @@ class CorePreferences
// User interface related
// -1 means auto, 0 no, 1 yes
@get:WorkerThread @set:WorkerThread
@get:AnyThread @set:WorkerThread
var darkMode: Int
get() {
if (!darkModeAllowed) return 0
@ -277,21 +278,21 @@ class CorePreferences
}
// Allows to make screenshots
@get:WorkerThread @set:WorkerThread
@get:AnyThread @set:WorkerThread
var enableSecureMode: Boolean
get() = config.getBool("ui", "enable_secure_mode", true)
set(value) {
config.setBool("ui", "enable_secure_mode", value)
}
@get:WorkerThread @set:WorkerThread
@get:AnyThread @set:WorkerThread
var automaticallyShowDialpad: Boolean
get() = config.getBool("ui", "automatically_show_dialpad", false)
set(value) {
config.setBool("ui", "automatically_show_dialpad", value)
}
@get:WorkerThread @set:WorkerThread
@get:AnyThread @set:WorkerThread
var themeMainColor: String
get() = config.getString("ui", "theme_main_color", "orange")!!
set(value) {
@ -300,109 +301,109 @@ class CorePreferences
// Customization options
@get:WorkerThread @set:WorkerThread
@get:AnyThread @set:WorkerThread
var showMicrophoneAndSpeakerVuMeters: Boolean
get() = config.getBool("ui", "show_mic_speaker_vu_meter", false)
set(value) {
config.setBool("ui", "show_mic_speaker_vu_meter", value)
}
@get:WorkerThread @set:WorkerThread
@get:AnyThread @set:WorkerThread
var pushNotificationCompatibleDomains: Array<String>
get() = config.getStringList("app", "push_notification_domains", arrayOf("sip.linphone.org"))
set(value) {
config.setStringList("app", "push_notification_domains", value)
}
@get:WorkerThread
@get:AnyThread
val defaultDomain: String
get() = config.getString("app", "default_domain", "sip.linphone.org")!!
@get:WorkerThread
@get:AnyThread
val darkModeAllowed: Boolean
get() = config.getBool("ui", "dark_mode_allowed", true)
@get:WorkerThread
@get:AnyThread
val changeMainColorAllowed: Boolean
get() = config.getBool("ui", "change_main_color_allowed", false)
@get:WorkerThread
@get:AnyThread
val onlyDisplaySipUriUsername: Boolean
get() = config.getBool("ui", "only_display_sip_uri_username", false)
@get:WorkerThread
@get:AnyThread
val hideSipAddresses: Boolean
get() = config.getBool("ui", "hide_sip_addresses", false)
@get:WorkerThread
@get:AnyThread
val disableChat: Boolean
get() = config.getBool("ui", "disable_chat_feature", false)
@get:WorkerThread
@get:AnyThread
val disableMeetings: Boolean
get() = config.getBool("ui", "disable_meetings_feature", false)
@get:WorkerThread
@get:AnyThread
val disableBroadcasts: Boolean
get() = config.getBool("ui", "disable_broadcast_feature", true) // TODO FIXME: not implemented yet
@get:WorkerThread
@get:AnyThread
val disableCallRecordings: Boolean
get() = config.getBool("ui", "disable_call_recordings_feature", false)
@get:WorkerThread
@get:AnyThread
val maxAccountsCount: Int
get() = config.getInt("ui", "max_account", 0) // 0 means no max
@get:WorkerThread
@get:AnyThread
val hidePhoneNumbers: Boolean
get() = config.getBool("ui", "hide_phone_numbers", false)
@get:WorkerThread
@get:AnyThread
val hideSettings: Boolean
get() = config.getBool("ui", "hide_settings", false)
@get:WorkerThread
@get:AnyThread
val hideAccountSettings: Boolean
get() = config.getBool("ui", "hide_account_settings", false)
@get:WorkerThread
@get:AnyThread
val hideAdvancedSettings: Boolean
get() = config.getBool("ui", "hide_advanced_settings", false)
@get:WorkerThread
@get:AnyThread
val hideAssistantCreateAccount: Boolean
get() = config.getBool("ui", "assistant_hide_create_account", false)
@get:WorkerThread
@get:AnyThread
val hideAssistantScanQrCode: Boolean
get() = config.getBool("ui", "assistant_disable_qr_code", false)
@get:WorkerThread
@get:AnyThread
val hideAssistantThirdPartySipAccount: Boolean
get() = config.getBool("ui", "assistant_hide_third_party_account", false)
@get:WorkerThread
@get:AnyThread
val magicSearchResultsLimit: Int
get() = config.getInt("ui", "max_number_of_magic_search_results", 300)
@get:WorkerThread
@get:AnyThread
val singleSignOnClientId: String
get() = config.getString("app", "oidc_client_id", "linphone")!!
@get:WorkerThread
@get:AnyThread
val useUsernameAsSingleSignOnLoginHint: Boolean
get() = config.getBool("ui", "use_username_as_sso_login_hint", false)
@get:WorkerThread
@get:AnyThread
val thirdPartySipAccountDefaultTransport: String
get() = config.getString("ui", "assistant_third_party_sip_account_transport", "tls")!!
@get:WorkerThread
@get:AnyThread
val thirdPartySipAccountDefaultDomain: String
get() = config.getString("ui", "assistant_third_party_sip_account_domain", "")!!
@get:WorkerThread
@get:AnyThread
val assistantDirectlyGoToThirdPartySipAccountLogin: Boolean
get() = config.getBool(
"ui",
@ -410,11 +411,11 @@ class CorePreferences
false
)
@get:WorkerThread
@get:AnyThread
val fetchContactsFromDefaultDirectory: Boolean
get() = config.getBool("app", "fetch_contacts_from_default_directory", true)
@get:WorkerThread
@get:AnyThread
val showLettersOnDialpad: Boolean
get() = config.getBool("ui", "show_letters_on_dialpad", true)

View file

@ -24,12 +24,9 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.annotation.UiThread
import androidx.annotation.WorkerThread
import androidx.core.view.doOnPreDraw
import androidx.lifecycle.ViewModelProvider
import androidx.navigation.fragment.findNavController
import org.linphone.core.Address
import org.linphone.core.Friend
import org.linphone.core.tools.Log
import org.linphone.databinding.GenericAddParticipantsFragmentBinding
import org.linphone.ui.call.viewmodel.CurrentCallViewModel
@ -66,11 +63,6 @@ class ConferenceAddParticipantsFragment : GenericAddressPickerFragment() {
return false
}
@WorkerThread
override fun onSingleAddressSelected(address: Address, friend: Friend) {
Log.e("$TAG This shouldn't happen as we should always be in multiple selection mode here!")
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
viewModel = ViewModelProvider(this)[AddParticipantsViewModel::class.java]

View file

@ -149,7 +149,7 @@ class ConversationForwardMessageFragment : SlidingPaneChildFragment() {
}
}
viewModel.hideNumberOrAddressPickerDialogEvent.observe(viewLifecycleOwner) {
viewModel.dismissNumberOrAddressPickerDialogEvent.observe(viewLifecycleOwner) {
it.consume {
numberOrAddressPickerDialog?.dismiss()
numberOrAddressPickerDialog = null
@ -172,7 +172,7 @@ class ConversationForwardMessageFragment : SlidingPaneChildFragment() {
}
}
private fun showNumberOrAddressPickerDialog(list: ArrayList<ContactNumberOrAddressModel>) {
private fun showNumberOrAddressPickerDialog(list: List<ContactNumberOrAddressModel>) {
val numberOrAddressModel = NumberOrAddressPickerDialogModel(list)
val dialog =
DialogUtils.getNumberOrAddressPickerDialog(

View file

@ -24,12 +24,9 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.annotation.UiThread
import androidx.annotation.WorkerThread
import androidx.core.view.doOnPreDraw
import androidx.lifecycle.ViewModelProvider
import org.linphone.R
import org.linphone.core.Address
import org.linphone.core.Friend
import org.linphone.core.tools.Log
import org.linphone.databinding.StartChatFragmentBinding
import org.linphone.ui.GenericActivity
@ -119,11 +116,6 @@ class StartConversationFragment : GenericAddressPickerFragment() {
}
}
@WorkerThread
override fun onSingleAddressSelected(address: Address, friend: Friend) {
viewModel.createOneToOneChatRoomWith(address)
}
private fun showGroupConversationSubjectDialog() {
val model = GroupSetOrEditSubjectDialogModel("", isGroupConversation = true)

View file

@ -30,9 +30,8 @@ import org.linphone.core.Address
import org.linphone.core.ChatRoom
import org.linphone.core.ChatRoomListenerStub
import org.linphone.core.Conference
import org.linphone.core.Friend
import org.linphone.core.tools.Log
import org.linphone.ui.main.contacts.model.ContactNumberOrAddressClickListener
import org.linphone.ui.main.contacts.model.ContactNumberOrAddressModel
import org.linphone.ui.main.model.ConversationContactOrSuggestionModel
import org.linphone.ui.main.viewmodel.AddressSelectionViewModel
import org.linphone.utils.AppUtils
@ -52,31 +51,6 @@ class ConversationForwardMessageViewModel
MutableLiveData<Event<String>>()
}
val showNumberOrAddressPickerDialogEvent: MutableLiveData<Event<ArrayList<ContactNumberOrAddressModel>>> by lazy {
MutableLiveData<Event<ArrayList<ContactNumberOrAddressModel>>>()
}
val hideNumberOrAddressPickerDialogEvent: MutableLiveData<Event<Boolean>> by lazy {
MutableLiveData<Event<Boolean>>()
}
private val listener = object : ContactNumberOrAddressClickListener {
@UiThread
override fun onClicked(model: ContactNumberOrAddressModel) {
val address = model.address
coreContext.postOnCoreThread {
if (address != null) {
Log.i("$TAG Selected address is [${model.address.asStringUriOnly()}]")
onAddressSelected(model.address)
}
}
}
@UiThread
override fun onLongPress(model: ContactNumberOrAddressModel) {
}
}
private val chatRoomListener = object : ChatRoomListenerStub() {
@WorkerThread
override fun onStateChanged(chatRoom: ChatRoom, newState: ChatRoom.State?) {
@ -105,8 +79,8 @@ class ConversationForwardMessageViewModel
}
@WorkerThread
private fun onAddressSelected(address: Address) {
hideNumberOrAddressPickerDialogEvent.postValue(Event(true))
override fun onSingleAddressSelected(address: Address, friend: Friend?) {
dismissNumberOrAddressPickerDialogEvent.postValue(Event(true))
createOneToOneChatRoomWith(address)
@ -136,7 +110,7 @@ class ConversationForwardMessageViewModel
val friend = model.friend
if (friend == null) {
Log.i("$TAG Friend is null, using address [${model.address.asStringUriOnly()}]")
onAddressSelected(model.address)
onSingleAddressSelected(model.address, null)
return@postOnCoreThread
}
@ -145,9 +119,9 @@ class ConversationForwardMessageViewModel
Log.i(
"$TAG Only 1 SIP address or phone number found for contact [${friend.name}], using it"
)
onAddressSelected(singleAvailableAddress)
onSingleAddressSelected(singleAvailableAddress, friend)
} else {
val list = friend.getListOfSipAddressesAndPhoneNumbers(listener)
val list = friend.getListOfSipAddressesAndPhoneNumbers(numberOrAddressClickListener)
Log.i(
"$TAG [${list.size}] numbers or addresses found for contact [${friend.name}], showing selection dialog"
)

View file

@ -30,6 +30,7 @@ import org.linphone.core.Address
import org.linphone.core.ChatRoom
import org.linphone.core.ChatRoomListenerStub
import org.linphone.core.Conference
import org.linphone.core.Friend
import org.linphone.core.tools.Log
import org.linphone.ui.main.viewmodel.AddressSelectionViewModel
import org.linphone.utils.AppUtils
@ -93,6 +94,11 @@ class StartConversationViewModel
updateGroupChatButtonVisibility()
}
@WorkerThread
override fun onSingleAddressSelected(address: Address, friend: Friend?) {
createOneToOneChatRoomWith(address)
}
@UiThread
fun createGroupChatRoom() {
coreContext.postOnCoreThread { core ->

View file

@ -25,13 +25,10 @@ import android.view.View
import android.view.ViewGroup
import androidx.activity.OnBackPressedCallback
import androidx.annotation.UiThread
import androidx.annotation.WorkerThread
import androidx.core.view.doOnPreDraw
import androidx.lifecycle.ViewModelProvider
import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs
import org.linphone.core.Address
import org.linphone.core.Friend
import org.linphone.core.tools.Log
import org.linphone.databinding.GenericAddParticipantsFragmentBinding
import org.linphone.ui.main.viewmodel.AddParticipantsViewModel
@ -92,11 +89,6 @@ class AddParticipantsFragment : GenericAddressPickerFragment() {
return false
}
@WorkerThread
override fun onSingleAddressSelected(address: Address, friend: Friend) {
Log.e("$TAG This shouldn't happen as we should always be in multiple selection mode here!")
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
viewModel = ViewModelProvider(this)[AddParticipantsViewModel::class.java]

View file

@ -23,23 +23,13 @@ import android.app.Dialog
import android.os.Bundle
import android.view.View
import androidx.annotation.UiThread
import androidx.annotation.WorkerThread
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import org.linphone.LinphoneApplication.Companion.coreContext
import org.linphone.contacts.getListOfSipAddressesAndPhoneNumbers
import org.linphone.core.Address
import org.linphone.core.Friend
import org.linphone.core.tools.Log
import org.linphone.ui.main.adapter.ConversationsContactsAndSuggestionsListAdapter
import org.linphone.ui.main.contacts.model.ContactNumberOrAddressClickListener
import org.linphone.ui.main.contacts.model.ContactNumberOrAddressModel
import org.linphone.ui.main.contacts.model.NumberOrAddressPickerDialogModel
import org.linphone.ui.main.model.ConversationContactOrSuggestionModel
import org.linphone.ui.main.model.SelectedAddressModel
import org.linphone.ui.main.viewmodel.AddressSelectionViewModel
import org.linphone.utils.DialogUtils
import org.linphone.utils.LinphoneUtils
import org.linphone.utils.RecyclerViewHeaderDecoration
@UiThread
@ -56,31 +46,6 @@ abstract class GenericAddressPickerFragment : GenericMainFragment() {
private lateinit var recyclerView: RecyclerView
private val listener = object : ContactNumberOrAddressClickListener {
@UiThread
override fun onClicked(model: ContactNumberOrAddressModel) {
val address = model.address
coreContext.postOnCoreThread {
if (address != null) {
Log.i(
"$TAG Selected address [${model.address.asStringUriOnly()}] from friend [${model.friend.name}]"
)
onAddressSelected(model.address, model.friend)
}
}
numberOrAddressPickerDialog?.dismiss()
numberOrAddressPickerDialog = null
}
@UiThread
override fun onLongPress(model: ContactNumberOrAddressModel) {
}
}
@WorkerThread
abstract fun onSingleAddressSelected(address: Address, friend: Friend)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@ -92,7 +57,7 @@ abstract class GenericAddressPickerFragment : GenericMainFragment() {
adapter.onClickedEvent.observe(viewLifecycleOwner) {
it.consume { model ->
handleClickOnContactModel(model)
viewModel.handleClickOnContactModel(model)
}
}
@ -100,6 +65,19 @@ abstract class GenericAddressPickerFragment : GenericMainFragment() {
val trimmed = filter.trim()
viewModel.applyFilter(trimmed)
}
viewModel.showNumberOrAddressPickerDialogEvent.observe(viewLifecycleOwner) {
it.consume { list ->
showNumbersOrAddressesDialog(list)
}
}
viewModel.dismissNumberOrAddressPickerDialogEvent.observe(viewLifecycleOwner) {
it.consume {
numberOrAddressPickerDialog?.dismiss()
numberOrAddressPickerDialog = null
}
}
}
override fun onPause() {
@ -109,7 +87,6 @@ abstract class GenericAddressPickerFragment : GenericMainFragment() {
numberOrAddressPickerDialog = null
}
@UiThread
protected fun setupRecyclerView(view: RecyclerView) {
recyclerView = view
recyclerView.setHasFixedSize(true)
@ -119,7 +96,6 @@ abstract class GenericAddressPickerFragment : GenericMainFragment() {
recyclerView.addItemDecoration(headerItemDecoration)
}
@UiThread
protected fun attachAdapter() {
if (::recyclerView.isInitialized) {
if (recyclerView.adapter != adapter) {
@ -128,82 +104,21 @@ abstract class GenericAddressPickerFragment : GenericMainFragment() {
}
}
@WorkerThread
fun onAddressSelected(address: Address, friend: Friend) {
if (viewModel.multipleSelectionMode.value == true) {
val avatarModel = coreContext.contactsManager.getContactAvatarModelForAddress(address)
val model = SelectedAddressModel(address, avatarModel) {
viewModel.removeAddressModelFromSelection(it)
}
viewModel.addAddressModelToSelection(model)
} else {
onSingleAddressSelected(address, friend)
}
// Clear filter after it was used
coreContext.postOnMainThread {
viewModel.clearFilter()
}
}
private fun handleClickOnContactModel(model: ConversationContactOrSuggestionModel) {
if (model.selected.value == true) {
Log.i(
"$TAG User clicked on already selected item [${model.name}], removing it from selection"
private fun showNumbersOrAddressesDialog(list: List<ContactNumberOrAddressModel>) {
val numberOrAddressModel = NumberOrAddressPickerDialogModel(list)
val dialog =
DialogUtils.getNumberOrAddressPickerDialog(
requireActivity(),
numberOrAddressModel
)
val found = viewModel.selection.value.orEmpty().find {
it.address.weakEqual(model.address) || it.avatarModel?.friend == model.friend
}
if (found != null) {
coreContext.postOnCoreThread {
viewModel.removeAddressModelFromSelection(found)
}
return
} else {
Log.e("$TAG Failed to find already selected entry matching the one clicked")
numberOrAddressPickerDialog = dialog
numberOrAddressModel.dismissEvent.observe(viewLifecycleOwner) { event ->
event.consume {
dialog.dismiss()
}
}
coreContext.postOnCoreThread { core ->
val friend = model.friend
if (friend == null) {
Log.i("$TAG Friend is null, using address [${model.address.asStringUriOnly()}]")
val fakeFriend = core.createFriend()
fakeFriend.addAddress(model.address)
onAddressSelected(model.address, fakeFriend)
return@postOnCoreThread
}
val singleAvailableAddress = LinphoneUtils.getSingleAvailableAddressForFriend(friend)
if (singleAvailableAddress != null) {
Log.i(
"$TAG Only 1 SIP address or phone number found for contact [${friend.name}], using it"
)
onAddressSelected(singleAvailableAddress, friend)
} else {
val list = friend.getListOfSipAddressesAndPhoneNumbers(listener)
Log.i(
"$TAG [${list.size}] numbers or addresses found for contact [${friend.name}], showing selection dialog"
)
coreContext.postOnMainThread {
val numberOrAddressModel = NumberOrAddressPickerDialogModel(list)
val dialog =
DialogUtils.getNumberOrAddressPickerDialog(
requireActivity(),
numberOrAddressModel
)
numberOrAddressPickerDialog = dialog
numberOrAddressModel.dismissEvent.observe(viewLifecycleOwner) { event ->
event.consume {
dialog.dismiss()
}
}
dialog.show()
}
}
}
dialog.show()
}
}

View file

@ -26,15 +26,12 @@ import android.view.View
import android.view.ViewGroup
import androidx.activity.OnBackPressedCallback
import androidx.annotation.UiThread
import androidx.annotation.WorkerThread
import androidx.core.view.doOnPreDraw
import androidx.lifecycle.ViewModelProvider
import com.google.android.material.bottomsheet.BottomSheetBehavior
import org.linphone.LinphoneApplication.Companion.coreContext
import org.linphone.LinphoneApplication.Companion.corePreferences
import org.linphone.R
import org.linphone.core.Address
import org.linphone.core.Friend
import org.linphone.core.tools.Log
import org.linphone.databinding.StartCallFragmentBinding
import org.linphone.ui.GenericActivity
@ -42,7 +39,6 @@ import org.linphone.ui.main.fragment.GenericAddressPickerFragment
import org.linphone.ui.main.history.viewmodel.StartCallViewModel
import org.linphone.ui.main.model.GroupSetOrEditSubjectDialogModel
import org.linphone.utils.DialogUtils
import org.linphone.utils.Event
import org.linphone.utils.addCharacterAtPosition
import org.linphone.utils.hideKeyboard
import org.linphone.utils.removeCharacterAtPosition
@ -209,19 +205,11 @@ class StartCallFragment : GenericAddressPickerFragment() {
)
}
@WorkerThread
override fun onSingleAddressSelected(address: Address, friend: Friend) {
coreContext.startAudioCall(address)
viewModel.leaveFragmentEvent.postValue(Event(true))
}
override fun onResume() {
super.onResume()
coreContext.postOnCoreThread {
if (corePreferences.automaticallyShowDialpad) {
viewModel.isNumpadVisible.postValue(true)
}
if (corePreferences.automaticallyShowDialpad) {
viewModel.isNumpadVisible.value = true
}
}

View file

@ -32,6 +32,7 @@ import org.linphone.R
import org.linphone.core.Address
import org.linphone.core.Conference
import org.linphone.core.ConferenceListenerStub
import org.linphone.core.Friend
import org.linphone.core.MediaDirection
import org.linphone.core.tools.Log
import org.linphone.ui.main.history.model.NumpadModel
@ -174,6 +175,12 @@ class StartCallViewModel
updateGroupCallButtonVisibility()
}
@WorkerThread
override fun onSingleAddressSelected(address: Address, friend: Friend?) {
coreContext.startAudioCall(address)
leaveFragmentEvent.postValue(Event(true))
}
@UiThread
fun updateGroupCallButtonVisibility() {
coreContext.postOnCoreThread { core ->

View file

@ -20,10 +20,12 @@
package org.linphone.ui.main.viewmodel
import androidx.annotation.UiThread
import androidx.annotation.WorkerThread
import androidx.lifecycle.MutableLiveData
import org.linphone.LinphoneApplication.Companion.coreContext
import org.linphone.R
import org.linphone.core.Address
import org.linphone.core.Friend
import org.linphone.core.tools.Log
import org.linphone.ui.main.model.SelectedAddressModel
import org.linphone.utils.AppUtils
@ -43,6 +45,11 @@ class AddParticipantsViewModel
switchToMultipleSelectionMode()
}
@WorkerThread
override fun onSingleAddressSelected(address: Address, friend: Friend?) {
Log.e("$TAG This shouldn't happen as we should always be in multiple selection mode here!")
}
@UiThread
fun isSelectionEmpty(): Boolean {
return selection.value.orEmpty().isEmpty()

View file

@ -28,6 +28,7 @@ import org.linphone.LinphoneApplication.Companion.coreContext
import org.linphone.LinphoneApplication.Companion.corePreferences
import org.linphone.R
import org.linphone.contacts.ContactsManager
import org.linphone.contacts.getListOfSipAddressesAndPhoneNumbers
import org.linphone.core.Address
import org.linphone.core.ChatRoom
import org.linphone.core.Friend
@ -36,10 +37,15 @@ import org.linphone.core.MagicSearchListenerStub
import org.linphone.core.SearchResult
import org.linphone.mediastream.Log
import org.linphone.ui.main.contacts.model.ContactAvatarModel
import org.linphone.ui.main.contacts.model.ContactNumberOrAddressClickListener
import org.linphone.ui.main.contacts.model.ContactNumberOrAddressModel
import org.linphone.ui.main.model.ConversationContactOrSuggestionModel
import org.linphone.ui.main.model.SelectedAddressModel
import org.linphone.utils.AppUtils
import org.linphone.utils.Event
import org.linphone.utils.LinphoneUtils
import kotlin.collections.find
import kotlin.collections.orEmpty
abstract class AddressSelectionViewModel
@UiThread
@ -64,6 +70,14 @@ abstract class AddressSelectionViewModel
val showResultsLimitReached = MutableLiveData<Boolean>()
val showNumberOrAddressPickerDialogEvent: MutableLiveData<Event<List<ContactNumberOrAddressModel>>> by lazy {
MutableLiveData<Event<List<ContactNumberOrAddressModel>>>()
}
val dismissNumberOrAddressPickerDialogEvent: MutableLiveData<Event<Boolean>> by lazy {
MutableLiveData<Event<Boolean>>()
}
protected var magicSearchSourceFlags = MagicSearch.Source.All.toInt()
protected var skipConversation: Boolean = true
@ -75,6 +89,27 @@ abstract class AddressSelectionViewModel
private lateinit var favouritesMagicSearch: MagicSearch
protected val numberOrAddressClickListener = object : ContactNumberOrAddressClickListener {
@UiThread
override fun onClicked(model: ContactNumberOrAddressModel) {
val address = model.address
coreContext.postOnCoreThread {
if (address != null) {
Log.i(
"$TAG Selected address [${model.address.asStringUriOnly()}] from friend [${model.friend.name}]"
)
onAddressSelected(model.address, model.friend)
}
}
dismissNumberOrAddressPickerDialogEvent.postValue(Event(true))
}
@UiThread
override fun onLongPress(model: ContactNumberOrAddressModel) {
}
}
private val magicSearchListener = object : MagicSearchListenerStub() {
@WorkerThread
override fun onSearchResultsReceived(magicSearch: MagicSearch) {
@ -108,6 +143,9 @@ abstract class AddressSelectionViewModel
override fun onContactFoundInRemoteDirectory(friend: Friend) { }
}
@WorkerThread
abstract fun onSingleAddressSelected(address: Address, friend: Friend?)
init {
multipleSelectionMode.value = false
isEmpty.value = true
@ -471,4 +509,68 @@ abstract class AddressSelectionViewModel
}
return conversationsList
}
@UiThread
fun handleClickOnContactModel(model: ConversationContactOrSuggestionModel) {
if (model.selected.value == true) {
org.linphone.core.tools.Log.i(
"$TAG User clicked on already selected item [${model.name}], removing it from selection"
)
val found = selection.value.orEmpty().find {
it.address.weakEqual(model.address) || it.avatarModel?.friend == model.friend
}
if (found != null) {
coreContext.postOnCoreThread {
removeAddressModelFromSelection(found)
}
return
} else {
org.linphone.core.tools.Log.e("$TAG Failed to find already selected entry matching the one clicked")
}
}
coreContext.postOnCoreThread { core ->
val friend = model.friend
if (friend == null) {
org.linphone.core.tools.Log.i("$TAG Friend is null, using address [${model.address.asStringUriOnly()}]")
val fakeFriend = core.createFriend()
fakeFriend.addAddress(model.address)
onAddressSelected(model.address, fakeFriend)
return@postOnCoreThread
}
val singleAvailableAddress = LinphoneUtils.getSingleAvailableAddressForFriend(friend)
if (singleAvailableAddress != null) {
org.linphone.core.tools.Log.i(
"$TAG Only 1 SIP address or phone number found for contact [${friend.name}], using it"
)
onAddressSelected(singleAvailableAddress, friend)
} else {
val list = friend.getListOfSipAddressesAndPhoneNumbers(numberOrAddressClickListener)
org.linphone.core.tools.Log.i(
"$TAG [${list.size}] numbers or addresses found for contact [${friend.name}], showing selection dialog"
)
showNumberOrAddressPickerDialogEvent.postValue(Event(list))
}
}
}
@WorkerThread
private fun onAddressSelected(address: Address, friend: Friend) {
if (multipleSelectionMode.value == true) {
val avatarModel = coreContext.contactsManager.getContactAvatarModelForAddress(address)
val model = SelectedAddressModel(address, avatarModel) {
removeAddressModelFromSelection(it)
}
addAddressModelToSelection(model)
} else {
onSingleAddressSelected(address, friend)
}
// Clear filter after it was used
coreContext.postOnMainThread {
clearFilter()
}
}
}