mirror of
https://gitlab.linphone.org/BC/public/linphone-android.git
synced 2026-04-20 18:28:32 +00:00
Started blind call transfer
This commit is contained in:
parent
a60c66ad33
commit
027e5dd61b
14 changed files with 502 additions and 245 deletions
|
|
@ -168,6 +168,11 @@ class ContactsManager @UiThread constructor(context: Context) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@WorkerThread
|
||||||
|
fun findDisplayName(address: Address): String {
|
||||||
|
return findContactByAddress(address)?.name ?: LinphoneUtils.getDisplayName(address)
|
||||||
|
}
|
||||||
|
|
||||||
@WorkerThread
|
@WorkerThread
|
||||||
fun onCoreStarted(core: Core) {
|
fun onCoreStarted(core: Core) {
|
||||||
core.addListener(coreListener)
|
core.addListener(coreListener)
|
||||||
|
|
|
||||||
|
|
@ -30,8 +30,10 @@ import androidx.annotation.AnyThread
|
||||||
import androidx.annotation.UiThread
|
import androidx.annotation.UiThread
|
||||||
import androidx.annotation.WorkerThread
|
import androidx.annotation.WorkerThread
|
||||||
import androidx.emoji2.text.EmojiCompat
|
import androidx.emoji2.text.EmojiCompat
|
||||||
|
import androidx.lifecycle.MutableLiveData
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import org.linphone.BuildConfig
|
import org.linphone.BuildConfig
|
||||||
|
import org.linphone.LinphoneApplication
|
||||||
import org.linphone.LinphoneApplication.Companion.corePreferences
|
import org.linphone.LinphoneApplication.Companion.corePreferences
|
||||||
import org.linphone.contacts.ContactsManager
|
import org.linphone.contacts.ContactsManager
|
||||||
import org.linphone.core.tools.Log
|
import org.linphone.core.tools.Log
|
||||||
|
|
@ -39,6 +41,7 @@ import org.linphone.notifications.NotificationsManager
|
||||||
import org.linphone.telecom.TelecomManager
|
import org.linphone.telecom.TelecomManager
|
||||||
import org.linphone.ui.call.CallActivity
|
import org.linphone.ui.call.CallActivity
|
||||||
import org.linphone.utils.ActivityMonitor
|
import org.linphone.utils.ActivityMonitor
|
||||||
|
import org.linphone.utils.Event
|
||||||
import org.linphone.utils.LinphoneUtils
|
import org.linphone.utils.LinphoneUtils
|
||||||
|
|
||||||
class CoreContext @UiThread constructor(val context: Context) : HandlerThread("Core Thread") {
|
class CoreContext @UiThread constructor(val context: Context) : HandlerThread("Core Thread") {
|
||||||
|
|
@ -68,6 +71,10 @@ class CoreContext @UiThread constructor(val context: Context) : HandlerThread("C
|
||||||
|
|
||||||
private val mainThread = Handler(Looper.getMainLooper())
|
private val mainThread = Handler(Looper.getMainLooper())
|
||||||
|
|
||||||
|
val greenToastToShowEvent: MutableLiveData<Event<Pair<String, Int>>> by lazy {
|
||||||
|
MutableLiveData<Event<Pair<String, Int>>>()
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressLint("HandlerLeak")
|
@SuppressLint("HandlerLeak")
|
||||||
private lateinit var coreThread: Handler
|
private lateinit var coreThread: Handler
|
||||||
|
|
||||||
|
|
@ -77,6 +84,7 @@ class CoreContext @UiThread constructor(val context: Context) : HandlerThread("C
|
||||||
Log.i("$TAG Global state changed [$state]")
|
Log.i("$TAG Global state changed [$state]")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@WorkerThread
|
||||||
override fun onConfiguringStatus(
|
override fun onConfiguringStatus(
|
||||||
core: Core,
|
core: Core,
|
||||||
status: Config.ConfiguringState?,
|
status: Config.ConfiguringState?,
|
||||||
|
|
@ -103,6 +111,25 @@ class CoreContext @UiThread constructor(val context: Context) : HandlerThread("C
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@WorkerThread
|
||||||
|
override fun onTransferStateChanged(core: Core, transfered: Call, state: Call.State) {
|
||||||
|
Log.i(
|
||||||
|
"$TAG Transferred call [${transfered.remoteAddress.asStringUriOnly()}] state changed [$state]"
|
||||||
|
)
|
||||||
|
if (state == Call.State.Connected) {
|
||||||
|
// TODO FIXME: Remote is call being transferred, not transferee !
|
||||||
|
val displayName = contactsManager.findDisplayName(transfered.remoteAddress)
|
||||||
|
|
||||||
|
val message = context.getString(
|
||||||
|
org.linphone.R.string.toast_call_transfer_successful,
|
||||||
|
displayName
|
||||||
|
)
|
||||||
|
val icon = org.linphone.R.drawable.transfer
|
||||||
|
|
||||||
|
greenToastToShowEvent.postValue(Event(Pair(message, icon)))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@ import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.core.view.WindowCompat
|
import androidx.core.view.WindowCompat
|
||||||
import androidx.core.view.WindowInsetsCompat
|
import androidx.core.view.WindowInsetsCompat
|
||||||
import androidx.core.view.WindowInsetsControllerCompat
|
import androidx.core.view.WindowInsetsControllerCompat
|
||||||
|
import androidx.core.view.children
|
||||||
import androidx.databinding.DataBindingUtil
|
import androidx.databinding.DataBindingUtil
|
||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
|
@ -58,6 +59,8 @@ import org.linphone.utils.slideInToastFromTopForDuration
|
||||||
class CallActivity : AppCompatActivity() {
|
class CallActivity : AppCompatActivity() {
|
||||||
companion object {
|
companion object {
|
||||||
private const val TAG = "[Call Activity]"
|
private const val TAG = "[Call Activity]"
|
||||||
|
|
||||||
|
private const val PERSISTENT_TOAST_TAG = "PERSISTENT"
|
||||||
}
|
}
|
||||||
|
|
||||||
private lateinit var binding: CallActivityBinding
|
private lateinit var binding: CallActivityBinding
|
||||||
|
|
@ -108,15 +111,16 @@ class CallActivity : AppCompatActivity() {
|
||||||
callViewModel.showLowWifiSignalEvent.observe(this) {
|
callViewModel.showLowWifiSignalEvent.observe(this) {
|
||||||
it.consume { show ->
|
it.consume { show ->
|
||||||
if (show) {
|
if (show) {
|
||||||
showRedToast(
|
showPersistentRedToast(
|
||||||
getString(R.string.toast_alert_low_wifi_signal),
|
getString(R.string.toast_alert_low_wifi_signal),
|
||||||
R.drawable.wifi_low
|
R.drawable.wifi_low
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
hideRedToast()
|
removePersistentRedToasts()
|
||||||
showGreenToast(
|
showGreenToast(
|
||||||
getString(R.string.toast_alert_low_wifi_signal_cleared),
|
getString(R.string.toast_alert_low_wifi_signal_cleared),
|
||||||
R.drawable.wifi_high
|
R.drawable.wifi_high,
|
||||||
|
2000
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -125,20 +129,39 @@ class CallActivity : AppCompatActivity() {
|
||||||
callViewModel.showLowCellularSignalEvent.observe(this) {
|
callViewModel.showLowCellularSignalEvent.observe(this) {
|
||||||
it.consume { show ->
|
it.consume { show ->
|
||||||
if (show) {
|
if (show) {
|
||||||
showRedToast(
|
showPersistentRedToast(
|
||||||
getString(R.string.toast_alert_low_cellular_signal),
|
getString(R.string.toast_alert_low_cellular_signal),
|
||||||
R.drawable.cell_signal_low
|
R.drawable.cell_signal_low
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
hideRedToast()
|
removePersistentRedToasts()
|
||||||
showGreenToast(
|
showGreenToast(
|
||||||
getString(R.string.toast_alert_low_cellular_signal_cleared),
|
getString(R.string.toast_alert_low_cellular_signal_cleared),
|
||||||
R.drawable.cell_signal_full
|
R.drawable.cell_signal_full,
|
||||||
|
2000
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
callViewModel.transferInProgressEvent.observe(this) {
|
||||||
|
it.consume { remote ->
|
||||||
|
showGreenToast(
|
||||||
|
getString(R.string.toast_call_transfer_in_progress, remote),
|
||||||
|
R.drawable.transfer
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
callViewModel.transferFailedEvent.observe(this) {
|
||||||
|
it.consume { remote ->
|
||||||
|
showRedToast(
|
||||||
|
getString(R.string.toast_call_transfer_failed, remote),
|
||||||
|
R.drawable.warning_circle
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
callsViewModel.showIncomingCallEvent.observe(this) {
|
callsViewModel.showIncomingCallEvent.observe(this) {
|
||||||
it.consume {
|
it.consume {
|
||||||
val action = IncomingCallFragmentDirections.actionGlobalIncomingCallFragment()
|
val action = IncomingCallFragmentDirections.actionGlobalIncomingCallFragment()
|
||||||
|
|
@ -245,29 +268,44 @@ class CallActivity : AppCompatActivity() {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun showRedToast(message: String, @DrawableRes icon: Int) {
|
private fun showRedToast(message: String, @DrawableRes icon: Int, duration: Long = 4000) {
|
||||||
val redToast = AppUtils.getRedToast(this, binding.toastsArea, message, icon)
|
val redToast = AppUtils.getRedToast(this, binding.toastsArea, message, icon)
|
||||||
binding.toastsArea.addView(redToast.root)
|
binding.toastsArea.addView(redToast.root)
|
||||||
|
|
||||||
|
redToast.root.slideInToastFromTopForDuration(
|
||||||
|
binding.toastsArea as ViewGroup,
|
||||||
|
lifecycleScope,
|
||||||
|
duration
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showPersistentRedToast(message: String, @DrawableRes icon: Int) {
|
||||||
|
val redToast = AppUtils.getRedToast(this, binding.toastsArea, message, icon)
|
||||||
|
redToast.root.tag = PERSISTENT_TOAST_TAG
|
||||||
|
binding.toastsArea.addView(redToast.root)
|
||||||
|
|
||||||
redToast.root.slideInToastFromTop(
|
redToast.root.slideInToastFromTop(
|
||||||
binding.toastsArea as ViewGroup,
|
binding.toastsArea as ViewGroup,
|
||||||
true
|
true
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun hideRedToast() {
|
private fun removePersistentRedToasts() {
|
||||||
// TODO: improve
|
for (child in binding.toastsArea.children) {
|
||||||
binding.toastsArea.removeAllViews()
|
if (child.tag == PERSISTENT_TOAST_TAG) {
|
||||||
|
binding.toastsArea.removeView(child)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun showGreenToast(message: String, @DrawableRes icon: Int) {
|
private fun showGreenToast(message: String, @DrawableRes icon: Int, duration: Long = 4000) {
|
||||||
val greenToast = AppUtils.getGreenToast(this, binding.toastsArea, message, icon)
|
val greenToast = AppUtils.getGreenToast(this, binding.toastsArea, message, icon)
|
||||||
binding.toastsArea.addView(greenToast.root)
|
binding.toastsArea.addView(greenToast.root)
|
||||||
|
|
||||||
greenToast.root.slideInToastFromTopForDuration(
|
greenToast.root.slideInToastFromTopForDuration(
|
||||||
binding.toastsArea as ViewGroup,
|
binding.toastsArea as ViewGroup,
|
||||||
lifecycleScope,
|
lifecycleScope,
|
||||||
2000
|
duration
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,266 @@
|
||||||
|
/*
|
||||||
|
* 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.call.fragment
|
||||||
|
|
||||||
|
import android.app.Dialog
|
||||||
|
import android.os.Bundle
|
||||||
|
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.navigation.fragment.findNavController
|
||||||
|
import androidx.navigation.navGraphViewModels
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
|
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||||
|
import org.linphone.LinphoneApplication.Companion.coreContext
|
||||||
|
import org.linphone.R
|
||||||
|
import org.linphone.contacts.getListOfSipAddressesAndPhoneNumbers
|
||||||
|
import org.linphone.core.Address
|
||||||
|
import org.linphone.core.tools.Log
|
||||||
|
import org.linphone.databinding.StartCallFragmentBinding
|
||||||
|
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.history.adapter.ContactsAndSuggestionsListAdapter
|
||||||
|
import org.linphone.ui.main.history.model.ContactOrSuggestionModel
|
||||||
|
import org.linphone.ui.main.history.viewmodel.StartCallViewModel
|
||||||
|
import org.linphone.ui.main.model.isInSecureMode
|
||||||
|
import org.linphone.utils.DialogUtils
|
||||||
|
import org.linphone.utils.RecyclerViewHeaderDecoration
|
||||||
|
import org.linphone.utils.hideKeyboard
|
||||||
|
import org.linphone.utils.setKeyboardInsetListener
|
||||||
|
import org.linphone.utils.showKeyboard
|
||||||
|
|
||||||
|
abstract class AbstractNewTransferCallFragment : GenericCallFragment() {
|
||||||
|
companion object {
|
||||||
|
private const val TAG = "[New/Transfer Call Fragment]"
|
||||||
|
}
|
||||||
|
|
||||||
|
private lateinit var binding: StartCallFragmentBinding
|
||||||
|
|
||||||
|
private val viewModel: StartCallViewModel by navGraphViewModels(
|
||||||
|
R.id.call_nav_graph
|
||||||
|
)
|
||||||
|
|
||||||
|
private lateinit var adapter: ContactsAndSuggestionsListAdapter
|
||||||
|
|
||||||
|
private val listener = object : ContactNumberOrAddressClickListener {
|
||||||
|
@UiThread
|
||||||
|
override fun onClicked(model: ContactNumberOrAddressModel) {
|
||||||
|
val address = model.address
|
||||||
|
if (address != null) {
|
||||||
|
coreContext.postOnCoreThread {
|
||||||
|
action(address)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@UiThread
|
||||||
|
override fun onLongPress(model: ContactNumberOrAddressModel) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var numberOrAddressPickerDialog: Dialog? = null
|
||||||
|
|
||||||
|
abstract val title: String
|
||||||
|
|
||||||
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater,
|
||||||
|
container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): View {
|
||||||
|
binding = StartCallFragmentBinding.inflate(layoutInflater)
|
||||||
|
return binding.root
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
postponeEnterTransition()
|
||||||
|
|
||||||
|
binding.lifecycleOwner = viewLifecycleOwner
|
||||||
|
|
||||||
|
viewModel.title.value = title
|
||||||
|
binding.viewModel = viewModel
|
||||||
|
|
||||||
|
binding.hideGroupChatButton = true
|
||||||
|
|
||||||
|
binding.setBackClickListener {
|
||||||
|
findNavController().popBackStack()
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.setHideNumpadClickListener {
|
||||||
|
viewModel.hideNumpad()
|
||||||
|
}
|
||||||
|
|
||||||
|
adapter = ContactsAndSuggestionsListAdapter(viewLifecycleOwner)
|
||||||
|
binding.contactsAndSuggestionsList.setHasFixedSize(true)
|
||||||
|
binding.contactsAndSuggestionsList.adapter = adapter
|
||||||
|
|
||||||
|
val headerItemDecoration = RecyclerViewHeaderDecoration(requireContext(), adapter, true)
|
||||||
|
binding.contactsAndSuggestionsList.addItemDecoration(headerItemDecoration)
|
||||||
|
|
||||||
|
adapter.contactClickedEvent.observe(viewLifecycleOwner) {
|
||||||
|
it.consume { model ->
|
||||||
|
startCall(model)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.contactsAndSuggestionsList.layoutManager = LinearLayoutManager(requireContext())
|
||||||
|
|
||||||
|
viewModel.contactsAndSuggestionsList.observe(
|
||||||
|
viewLifecycleOwner
|
||||||
|
) {
|
||||||
|
Log.i("$TAG Contacts & suggestions list is ready with [${it.size}] items")
|
||||||
|
val count = adapter.itemCount
|
||||||
|
adapter.submitList(it)
|
||||||
|
|
||||||
|
if (count == 0 && it.isNotEmpty()) {
|
||||||
|
(view.parent as? ViewGroup)?.doOnPreDraw {
|
||||||
|
startPostponedEnterTransition()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
viewModel.searchFilter.observe(viewLifecycleOwner) { filter ->
|
||||||
|
val trimmed = filter.trim()
|
||||||
|
viewModel.applyFilter(trimmed)
|
||||||
|
}
|
||||||
|
|
||||||
|
viewModel.removedCharacterAtCurrentPositionEvent.observe(viewLifecycleOwner) {
|
||||||
|
it.consume {
|
||||||
|
val selectionStart = binding.searchBar.selectionStart
|
||||||
|
val selectionEnd = binding.searchBar.selectionEnd
|
||||||
|
if (selectionStart > 0) {
|
||||||
|
binding.searchBar.text =
|
||||||
|
binding.searchBar.text?.delete(
|
||||||
|
selectionStart - 1,
|
||||||
|
selectionEnd
|
||||||
|
)
|
||||||
|
binding.searchBar.setSelection(selectionStart - 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
viewModel.appendDigitToSearchBarEvent.observe(viewLifecycleOwner) {
|
||||||
|
it.consume { digit ->
|
||||||
|
val newValue = "${binding.searchBar.text}$digit"
|
||||||
|
binding.searchBar.setText(newValue)
|
||||||
|
binding.searchBar.setSelection(newValue.length)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
viewModel.requestKeyboardVisibilityChangedEvent.observe(viewLifecycleOwner) {
|
||||||
|
it.consume { show ->
|
||||||
|
if (show) {
|
||||||
|
// To automatically open keyboard
|
||||||
|
binding.searchBar.showKeyboard(requireActivity().window)
|
||||||
|
} else {
|
||||||
|
binding.searchBar.requestFocus()
|
||||||
|
binding.searchBar.hideKeyboard()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
viewModel.isNumpadVisible.observe(viewLifecycleOwner) { visible ->
|
||||||
|
val standardBottomSheetBehavior = BottomSheetBehavior.from(binding.numpadLayout.root)
|
||||||
|
if (visible) {
|
||||||
|
standardBottomSheetBehavior.state = BottomSheetBehavior.STATE_EXPANDED
|
||||||
|
} else {
|
||||||
|
standardBottomSheetBehavior.state = BottomSheetBehavior.STATE_COLLAPSED
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.root.setKeyboardInsetListener { keyboardVisible ->
|
||||||
|
if (keyboardVisible) {
|
||||||
|
viewModel.isNumpadVisible.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPause() {
|
||||||
|
super.onPause()
|
||||||
|
|
||||||
|
numberOrAddressPickerDialog?.dismiss()
|
||||||
|
numberOrAddressPickerDialog = null
|
||||||
|
}
|
||||||
|
|
||||||
|
@WorkerThread
|
||||||
|
abstract fun action(address: Address)
|
||||||
|
|
||||||
|
private fun startCall(model: ContactOrSuggestionModel) {
|
||||||
|
coreContext.postOnCoreThread { core ->
|
||||||
|
val friend = model.friend
|
||||||
|
if (friend == null) {
|
||||||
|
action(model.address)
|
||||||
|
return@postOnCoreThread
|
||||||
|
}
|
||||||
|
|
||||||
|
val addressesCount = friend.addresses.size
|
||||||
|
val numbersCount = friend.phoneNumbers.size
|
||||||
|
|
||||||
|
// Do not consider phone numbers if default account is in secure mode
|
||||||
|
val enablePhoneNumbers = core.defaultAccount?.isInSecureMode() != true
|
||||||
|
|
||||||
|
if (addressesCount == 1 && (numbersCount == 0 || !enablePhoneNumbers)) {
|
||||||
|
Log.i(
|
||||||
|
"$TAG Only 1 SIP address found for contact [${friend.name}], starting call directly"
|
||||||
|
)
|
||||||
|
val address = friend.addresses.first()
|
||||||
|
action(address)
|
||||||
|
} else if (addressesCount == 0 && numbersCount == 1 && enablePhoneNumbers) {
|
||||||
|
val number = friend.phoneNumbers.first()
|
||||||
|
val address = core.interpretUrl(number, true)
|
||||||
|
if (address != null) {
|
||||||
|
Log.i(
|
||||||
|
"$TAG Only 1 phone number found for contact [${friend.name}], starting call directly"
|
||||||
|
)
|
||||||
|
action(address)
|
||||||
|
} else {
|
||||||
|
Log.e("$TAG Failed to interpret phone number [$number] as SIP address")
|
||||||
|
}
|
||||||
|
} 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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -135,6 +135,11 @@ class ActiveCallFragment : GenericCallFragment() {
|
||||||
findNavController().navigate(action)
|
findNavController().navigate(action)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
binding.setTransferClickListener {
|
||||||
|
val action = ActiveCallFragmentDirections.actionActiveCallFragmentToTransferCallFragment()
|
||||||
|
findNavController().navigate(action)
|
||||||
|
}
|
||||||
|
|
||||||
binding.setCallsListClickListener {
|
binding.setCallsListClickListener {
|
||||||
val action = ActiveCallFragmentDirections.actionActiveCallFragmentToCallsListFragment()
|
val action = ActiveCallFragmentDirections.actionActiveCallFragmentToCallsListFragment()
|
||||||
findNavController().navigate(action)
|
findNavController().navigate(action)
|
||||||
|
|
|
||||||
|
|
@ -19,250 +19,30 @@
|
||||||
*/
|
*/
|
||||||
package org.linphone.ui.call.fragment
|
package org.linphone.ui.call.fragment
|
||||||
|
|
||||||
import android.app.Dialog
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.View
|
|
||||||
import android.view.ViewGroup
|
|
||||||
import androidx.annotation.UiThread
|
import androidx.annotation.UiThread
|
||||||
import androidx.core.view.doOnPreDraw
|
import androidx.annotation.WorkerThread
|
||||||
import androidx.navigation.fragment.findNavController
|
import androidx.navigation.fragment.findNavController
|
||||||
import androidx.navigation.navGraphViewModels
|
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
|
||||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
|
||||||
import org.linphone.LinphoneApplication.Companion.coreContext
|
import org.linphone.LinphoneApplication.Companion.coreContext
|
||||||
import org.linphone.R
|
import org.linphone.R
|
||||||
import org.linphone.contacts.getListOfSipAddressesAndPhoneNumbers
|
import org.linphone.core.Address
|
||||||
import org.linphone.core.tools.Log
|
import org.linphone.core.tools.Log
|
||||||
import org.linphone.databinding.StartCallFragmentBinding
|
|
||||||
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.fragment.GenericFragment
|
|
||||||
import org.linphone.ui.main.history.adapter.ContactsAndSuggestionsListAdapter
|
|
||||||
import org.linphone.ui.main.history.model.ContactOrSuggestionModel
|
|
||||||
import org.linphone.ui.main.history.viewmodel.StartCallViewModel
|
|
||||||
import org.linphone.ui.main.model.isInSecureMode
|
|
||||||
import org.linphone.utils.DialogUtils
|
|
||||||
import org.linphone.utils.RecyclerViewHeaderDecoration
|
|
||||||
import org.linphone.utils.hideKeyboard
|
|
||||||
import org.linphone.utils.setKeyboardInsetListener
|
|
||||||
import org.linphone.utils.showKeyboard
|
|
||||||
|
|
||||||
@UiThread
|
@UiThread
|
||||||
class NewCallFragment : GenericFragment() {
|
class NewCallFragment : AbstractNewTransferCallFragment() {
|
||||||
companion object {
|
companion object {
|
||||||
private const val TAG = "[New Call Fragment]"
|
private const val TAG = "[New Call Fragment]"
|
||||||
}
|
}
|
||||||
|
|
||||||
private lateinit var binding: StartCallFragmentBinding
|
override val title: String
|
||||||
|
get() = getString(R.string.call_action_start_new_call)
|
||||||
|
|
||||||
private val viewModel: StartCallViewModel by navGraphViewModels(
|
@WorkerThread
|
||||||
R.id.call_nav_graph
|
override fun action(address: Address) {
|
||||||
)
|
Log.i("$TAG Calling [${address.asStringUriOnly()}]")
|
||||||
|
|
||||||
private lateinit var adapter: ContactsAndSuggestionsListAdapter
|
|
||||||
|
|
||||||
private val listener = object : ContactNumberOrAddressClickListener {
|
|
||||||
@UiThread
|
|
||||||
override fun onClicked(model: ContactNumberOrAddressModel) {
|
|
||||||
val address = model.address
|
|
||||||
if (address != null) {
|
|
||||||
coreContext.postOnCoreThread {
|
|
||||||
coreContext.startCall(address)
|
coreContext.startCall(address)
|
||||||
}
|
|
||||||
findNavController().popBackStack()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@UiThread
|
|
||||||
override fun onLongPress(model: ContactNumberOrAddressModel) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private var numberOrAddressPickerDialog: Dialog? = null
|
|
||||||
|
|
||||||
override fun onCreateView(
|
|
||||||
inflater: LayoutInflater,
|
|
||||||
container: ViewGroup?,
|
|
||||||
savedInstanceState: Bundle?
|
|
||||||
): View {
|
|
||||||
binding = StartCallFragmentBinding.inflate(layoutInflater)
|
|
||||||
return binding.root
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
|
||||||
super.onViewCreated(view, savedInstanceState)
|
|
||||||
postponeEnterTransition()
|
|
||||||
|
|
||||||
binding.lifecycleOwner = viewLifecycleOwner
|
|
||||||
binding.viewModel = viewModel
|
|
||||||
binding.hideGroupChatButton = true
|
|
||||||
|
|
||||||
binding.setBackClickListener {
|
|
||||||
goBack()
|
|
||||||
}
|
|
||||||
|
|
||||||
binding.setHideNumpadClickListener {
|
|
||||||
viewModel.hideNumpad()
|
|
||||||
}
|
|
||||||
|
|
||||||
adapter = ContactsAndSuggestionsListAdapter(viewLifecycleOwner)
|
|
||||||
binding.contactsAndSuggestionsList.setHasFixedSize(true)
|
|
||||||
binding.contactsAndSuggestionsList.adapter = adapter
|
|
||||||
|
|
||||||
val headerItemDecoration = RecyclerViewHeaderDecoration(requireContext(), adapter, true)
|
|
||||||
binding.contactsAndSuggestionsList.addItemDecoration(headerItemDecoration)
|
|
||||||
|
|
||||||
adapter.contactClickedEvent.observe(viewLifecycleOwner) {
|
|
||||||
it.consume { model ->
|
|
||||||
startCall(model)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
binding.contactsAndSuggestionsList.layoutManager = LinearLayoutManager(requireContext())
|
|
||||||
|
|
||||||
viewModel.contactsAndSuggestionsList.observe(
|
|
||||||
viewLifecycleOwner
|
|
||||||
) {
|
|
||||||
Log.i("$TAG Contacts & suggestions list is ready with [${it.size}] items")
|
|
||||||
val count = adapter.itemCount
|
|
||||||
adapter.submitList(it)
|
|
||||||
|
|
||||||
if (count == 0 && it.isNotEmpty()) {
|
|
||||||
(view.parent as? ViewGroup)?.doOnPreDraw {
|
|
||||||
startPostponedEnterTransition()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
viewModel.searchFilter.observe(viewLifecycleOwner) { filter ->
|
|
||||||
val trimmed = filter.trim()
|
|
||||||
viewModel.applyFilter(trimmed)
|
|
||||||
}
|
|
||||||
|
|
||||||
viewModel.removedCharacterAtCurrentPositionEvent.observe(viewLifecycleOwner) {
|
|
||||||
it.consume {
|
|
||||||
val selectionStart = binding.searchBar.selectionStart
|
|
||||||
val selectionEnd = binding.searchBar.selectionEnd
|
|
||||||
if (selectionStart > 0) {
|
|
||||||
binding.searchBar.text =
|
|
||||||
binding.searchBar.text?.delete(
|
|
||||||
selectionStart - 1,
|
|
||||||
selectionEnd
|
|
||||||
)
|
|
||||||
binding.searchBar.setSelection(selectionStart - 1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
viewModel.appendDigitToSearchBarEvent.observe(viewLifecycleOwner) {
|
|
||||||
it.consume { digit ->
|
|
||||||
val newValue = "${binding.searchBar.text}$digit"
|
|
||||||
binding.searchBar.setText(newValue)
|
|
||||||
binding.searchBar.setSelection(newValue.length)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
viewModel.requestKeyboardVisibilityChangedEvent.observe(viewLifecycleOwner) {
|
|
||||||
it.consume { show ->
|
|
||||||
if (show) {
|
|
||||||
// To automatically open keyboard
|
|
||||||
binding.searchBar.showKeyboard(requireActivity().window)
|
|
||||||
} else {
|
|
||||||
binding.searchBar.requestFocus()
|
|
||||||
binding.searchBar.hideKeyboard()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
viewModel.isNumpadVisible.observe(viewLifecycleOwner) { visible ->
|
|
||||||
val standardBottomSheetBehavior = BottomSheetBehavior.from(binding.numpadLayout.root)
|
|
||||||
if (visible) {
|
|
||||||
standardBottomSheetBehavior.state = BottomSheetBehavior.STATE_EXPANDED
|
|
||||||
} else {
|
|
||||||
standardBottomSheetBehavior.state = BottomSheetBehavior.STATE_COLLAPSED
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
binding.root.setKeyboardInsetListener { keyboardVisible ->
|
|
||||||
if (keyboardVisible) {
|
|
||||||
viewModel.isNumpadVisible.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onPause() {
|
|
||||||
super.onPause()
|
|
||||||
|
|
||||||
numberOrAddressPickerDialog?.dismiss()
|
|
||||||
numberOrAddressPickerDialog = null
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun startCall(model: ContactOrSuggestionModel) {
|
|
||||||
coreContext.postOnCoreThread { core ->
|
|
||||||
val friend = model.friend
|
|
||||||
if (friend == null) {
|
|
||||||
coreContext.startCall(model.address)
|
|
||||||
coreContext.postOnMainThread {
|
|
||||||
findNavController().popBackStack()
|
|
||||||
}
|
|
||||||
return@postOnCoreThread
|
|
||||||
}
|
|
||||||
|
|
||||||
val addressesCount = friend.addresses.size
|
|
||||||
val numbersCount = friend.phoneNumbers.size
|
|
||||||
|
|
||||||
// Do not consider phone numbers if default account is in secure mode
|
|
||||||
val enablePhoneNumbers = core.defaultAccount?.isInSecureMode() != true
|
|
||||||
|
|
||||||
if (addressesCount == 1 && (numbersCount == 0 || !enablePhoneNumbers)) {
|
|
||||||
Log.i(
|
|
||||||
"$TAG Only 1 SIP address found for contact [${friend.name}], starting call directly"
|
|
||||||
)
|
|
||||||
val address = friend.addresses.first()
|
|
||||||
coreContext.startCall(address)
|
|
||||||
coreContext.postOnMainThread {
|
|
||||||
findNavController().popBackStack()
|
|
||||||
}
|
|
||||||
} else if (addressesCount == 0 && numbersCount == 1 && enablePhoneNumbers) {
|
|
||||||
val number = friend.phoneNumbers.first()
|
|
||||||
val address = core.interpretUrl(number, true)
|
|
||||||
if (address != null) {
|
|
||||||
Log.i(
|
|
||||||
"$TAG Only 1 phone number found for contact [${friend.name}], starting call directly"
|
|
||||||
)
|
|
||||||
coreContext.startCall(address)
|
|
||||||
coreContext.postOnMainThread {
|
|
||||||
findNavController().popBackStack()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Log.e("$TAG Failed to interpret phone number [$number] as SIP address")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
val list = friend.getListOfSipAddressesAndPhoneNumbers(listener)
|
|
||||||
Log.i(
|
|
||||||
"$TAG [${list.size}] numbers or addresses found for contact [${friend.name}], showing selection dialog"
|
|
||||||
)
|
|
||||||
|
|
||||||
coreContext.postOnMainThread {
|
coreContext.postOnMainThread {
|
||||||
val numberOrAddressModel = NumberOrAddressPickerDialogModel(list)
|
findNavController().popBackStack()
|
||||||
val dialog =
|
|
||||||
DialogUtils.getNumberOrAddressPickerDialog(
|
|
||||||
requireActivity(),
|
|
||||||
numberOrAddressModel
|
|
||||||
)
|
|
||||||
numberOrAddressPickerDialog = dialog
|
|
||||||
|
|
||||||
numberOrAddressModel.dismissEvent.observe(viewLifecycleOwner) { event ->
|
|
||||||
event.consume {
|
|
||||||
dialog.dismiss()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
dialog.show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,62 @@
|
||||||
|
/*
|
||||||
|
* 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.call.fragment
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.View
|
||||||
|
import androidx.annotation.UiThread
|
||||||
|
import androidx.annotation.WorkerThread
|
||||||
|
import androidx.lifecycle.ViewModelProvider
|
||||||
|
import androidx.navigation.fragment.findNavController
|
||||||
|
import org.linphone.LinphoneApplication.Companion.coreContext
|
||||||
|
import org.linphone.R
|
||||||
|
import org.linphone.core.Address
|
||||||
|
import org.linphone.core.tools.Log
|
||||||
|
import org.linphone.ui.call.viewmodel.CurrentCallViewModel
|
||||||
|
|
||||||
|
@UiThread
|
||||||
|
class TransferCallFragment : AbstractNewTransferCallFragment() {
|
||||||
|
companion object {
|
||||||
|
private const val TAG = "[Transfer Call Fragment]"
|
||||||
|
}
|
||||||
|
|
||||||
|
override val title: String
|
||||||
|
get() = getString(R.string.call_transfer_title)
|
||||||
|
|
||||||
|
private lateinit var callViewModel: CurrentCallViewModel
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
|
callViewModel = requireActivity().run {
|
||||||
|
ViewModelProvider(this)[CurrentCallViewModel::class.java]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@WorkerThread
|
||||||
|
override fun action(address: Address) {
|
||||||
|
Log.i("$TAG Transferring current call to [${address.asStringUriOnly()}]")
|
||||||
|
callViewModel.blindTransferCallTo(address)
|
||||||
|
|
||||||
|
coreContext.postOnMainThread {
|
||||||
|
findNavController().popBackStack()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -30,6 +30,7 @@ import java.util.Locale
|
||||||
import org.linphone.LinphoneApplication.Companion.coreContext
|
import org.linphone.LinphoneApplication.Companion.coreContext
|
||||||
import org.linphone.LinphoneApplication.Companion.corePreferences
|
import org.linphone.LinphoneApplication.Companion.corePreferences
|
||||||
import org.linphone.R
|
import org.linphone.R
|
||||||
|
import org.linphone.core.Address
|
||||||
import org.linphone.core.Alert
|
import org.linphone.core.Alert
|
||||||
import org.linphone.core.AlertListenerStub
|
import org.linphone.core.AlertListenerStub
|
||||||
import org.linphone.core.AudioDevice
|
import org.linphone.core.AudioDevice
|
||||||
|
|
@ -115,6 +116,14 @@ class CurrentCallViewModel @UiThread constructor() : ViewModel() {
|
||||||
MutableLiveData<Event<Boolean>>()
|
MutableLiveData<Event<Boolean>>()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val transferInProgressEvent: MutableLiveData<Event<String>> by lazy {
|
||||||
|
MutableLiveData<Event<String>>()
|
||||||
|
}
|
||||||
|
|
||||||
|
val transferFailedEvent: MutableLiveData<Event<String>> by lazy {
|
||||||
|
MutableLiveData<Event<String>>()
|
||||||
|
}
|
||||||
|
|
||||||
val numpadModel: NumpadModel
|
val numpadModel: NumpadModel
|
||||||
|
|
||||||
val appendDigitToSearchBarEvent: MutableLiveData<Event<String>> by lazy {
|
val appendDigitToSearchBarEvent: MutableLiveData<Event<String>> by lazy {
|
||||||
|
|
@ -247,6 +256,22 @@ class CurrentCallViewModel @UiThread constructor() : ViewModel() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@WorkerThread
|
||||||
|
override fun onTransferStateChanged(core: Core, transfered: Call, state: Call.State) {
|
||||||
|
Log.i(
|
||||||
|
"$TAG Transferred call [${transfered.remoteAddress.asStringUriOnly()}] state changed [$state]"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TODO FIXME: Remote is call being transferred, not transferee !
|
||||||
|
if (state == Call.State.OutgoingProgress) {
|
||||||
|
val displayName = coreContext.contactsManager.findDisplayName(transfered.remoteAddress)
|
||||||
|
transferInProgressEvent.postValue(Event(displayName))
|
||||||
|
} else if (LinphoneUtils.isCallEnding(state)) {
|
||||||
|
val displayName = coreContext.contactsManager.findDisplayName(transfered.remoteAddress)
|
||||||
|
transferFailedEvent.postValue(Event(displayName))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val alertListener = object : AlertListenerStub() {
|
private val alertListener = object : AlertListenerStub() {
|
||||||
|
|
@ -489,6 +514,22 @@ class CurrentCallViewModel @UiThread constructor() : ViewModel() {
|
||||||
showNumpadBottomSheetEvent.value = Event(true)
|
showNumpadBottomSheetEvent.value = Event(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@WorkerThread
|
||||||
|
fun blindTransferCallTo(to: Address) {
|
||||||
|
if (::currentCall.isInitialized) {
|
||||||
|
Log.i(
|
||||||
|
"$TAG Call [${currentCall.remoteAddress.asStringUriOnly()}] is being blindly transferred to [${to.asStringUriOnly()}]"
|
||||||
|
)
|
||||||
|
if (currentCall.transferTo(to) == 0) {
|
||||||
|
Log.i("$TAG Blind call transfer is successful")
|
||||||
|
} else {
|
||||||
|
Log.e("$TAG Failed to make blind call transfer!")
|
||||||
|
val displayName = coreContext.contactsManager.findDisplayName(to)
|
||||||
|
transferFailedEvent.postValue(Event(displayName))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@WorkerThread
|
@WorkerThread
|
||||||
private fun showZrtpSasDialog(authToken: String) {
|
private fun showZrtpSasDialog(authToken: String) {
|
||||||
val toRead: String
|
val toRead: String
|
||||||
|
|
|
||||||
|
|
@ -106,6 +106,14 @@ class MainActivity : AppCompatActivity() {
|
||||||
POST_NOTIFICATIONS_PERMISSION_REQUEST
|
POST_NOTIFICATIONS_PERMISSION_REQUEST
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
coreContext.greenToastToShowEvent.observe(this) {
|
||||||
|
it.consume { pair ->
|
||||||
|
val message = pair.first
|
||||||
|
val icon = pair.second
|
||||||
|
showGreenToast(message, icon)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onRequestPermissionsResult(
|
override fun onRequestPermissionsResult(
|
||||||
|
|
|
||||||
|
|
@ -96,6 +96,8 @@ class StartCallFragment : GenericFragment() {
|
||||||
postponeEnterTransition()
|
postponeEnterTransition()
|
||||||
|
|
||||||
binding.lifecycleOwner = viewLifecycleOwner
|
binding.lifecycleOwner = viewLifecycleOwner
|
||||||
|
|
||||||
|
viewModel.title.value = getString(R.string.history_call_start_title)
|
||||||
binding.viewModel = viewModel
|
binding.viewModel = viewModel
|
||||||
|
|
||||||
binding.setBackClickListener {
|
binding.setBackClickListener {
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,8 @@ class StartCallViewModel @UiThread constructor() : ViewModel() {
|
||||||
private const val TAG = "[Start Call ViewModel]"
|
private const val TAG = "[Start Call ViewModel]"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val title = MutableLiveData<String>()
|
||||||
|
|
||||||
val searchFilter = MutableLiveData<String>()
|
val searchFilter = MutableLiveData<String>()
|
||||||
|
|
||||||
val contactsAndSuggestionsList = MutableLiveData<ArrayList<ContactOrSuggestionModel>>()
|
val contactsAndSuggestionsList = MutableLiveData<ArrayList<ContactOrSuggestionModel>>()
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,9 @@
|
||||||
<variable
|
<variable
|
||||||
name="hideGroupChatButton"
|
name="hideGroupChatButton"
|
||||||
type="Boolean" />
|
type="Boolean" />
|
||||||
|
<variable
|
||||||
|
name="title"
|
||||||
|
type="String" />
|
||||||
<variable
|
<variable
|
||||||
name="viewModel"
|
name="viewModel"
|
||||||
type="org.linphone.ui.main.history.viewmodel.StartCallViewModel" />
|
type="org.linphone.ui.main.history.viewmodel.StartCallViewModel" />
|
||||||
|
|
@ -48,7 +51,7 @@
|
||||||
android:layout_height="@dimen/top_bar_height"
|
android:layout_height="@dimen/top_bar_height"
|
||||||
android:layout_marginStart="10dp"
|
android:layout_marginStart="10dp"
|
||||||
android:layout_marginEnd="10dp"
|
android:layout_marginEnd="10dp"
|
||||||
android:text="@string/history_call_start_title"
|
android:text="@{viewModel.title, default=@string/history_call_start_title}"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toEndOf="@id/back"
|
app:layout_constraintStart_toEndOf="@id/back"
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
|
||||||
|
|
@ -54,6 +54,12 @@
|
||||||
app:enterAnim="@anim/slide_in"
|
app:enterAnim="@anim/slide_in"
|
||||||
app:popExitAnim="@anim/slide_out"
|
app:popExitAnim="@anim/slide_out"
|
||||||
app:launchSingleTop="true" />
|
app:launchSingleTop="true" />
|
||||||
|
<action
|
||||||
|
android:id="@+id/action_activeCallFragment_to_transferCallFragment"
|
||||||
|
app:destination="@id/transferCallFragment"
|
||||||
|
app:enterAnim="@anim/slide_in"
|
||||||
|
app:popExitAnim="@anim/slide_out"
|
||||||
|
app:launchSingleTop="true" />
|
||||||
<action
|
<action
|
||||||
android:id="@+id/action_activeCallFragment_to_callsListFragment"
|
android:id="@+id/action_activeCallFragment_to_callsListFragment"
|
||||||
app:destination="@id/callsListFragment"
|
app:destination="@id/callsListFragment"
|
||||||
|
|
@ -72,7 +78,15 @@
|
||||||
android:id="@+id/newCallFragment"
|
android:id="@+id/newCallFragment"
|
||||||
android:name="org.linphone.ui.call.fragment.NewCallFragment"
|
android:name="org.linphone.ui.call.fragment.NewCallFragment"
|
||||||
android:label="NewCallFragment"
|
android:label="NewCallFragment"
|
||||||
tools:layout="@layout/start_call_fragment" />
|
tools:layout="@layout/start_call_fragment" >
|
||||||
|
</fragment>
|
||||||
|
|
||||||
|
<fragment
|
||||||
|
android:id="@+id/transferCallFragment"
|
||||||
|
android:name="org.linphone.ui.call.fragment.TransferCallFragment"
|
||||||
|
android:label="TransferCallFragment"
|
||||||
|
tools:layout="@layout/start_call_fragment" >
|
||||||
|
</fragment>
|
||||||
|
|
||||||
<fragment
|
<fragment
|
||||||
android:id="@+id/callsListFragment"
|
android:id="@+id/callsListFragment"
|
||||||
|
|
|
||||||
|
|
@ -290,6 +290,10 @@
|
||||||
<string name="call_state_paused">Paused</string>
|
<string name="call_state_paused">Paused</string>
|
||||||
<string name="call_state_ended">Ended</string>
|
<string name="call_state_ended">Ended</string>
|
||||||
<string name="calls_list_title">Calls list</string>
|
<string name="calls_list_title">Calls list</string>
|
||||||
|
<string name="call_transfer_title">Transfer call to</string>
|
||||||
|
<string name="toast_call_transfer_in_progress">Call is being transferred to %s</string>
|
||||||
|
<string name="toast_call_transfer_successful">Call has been transferred to %s</string>
|
||||||
|
<string name="toast_call_transfer_failed">Call transfer to %s failed!</string>
|
||||||
|
|
||||||
<!-- Keep <u></u> in following strings translations! -->
|
<!-- Keep <u></u> in following strings translations! -->
|
||||||
<string name="welcome_carousel_skip"><u>Skip</u></string>
|
<string name="welcome_carousel_skip"><u>Skip</u></string>
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue