Merge pull request #3516 from vector-im/feature/fga/dial_pad_improvements

Feature/fga/dial pad improvements
This commit is contained in:
Benoit Marty 2021-06-17 14:31:48 +02:00 committed by GitHub
commit da19992f3f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 122 additions and 111 deletions

1
newsfragment/3516.misc Normal file
View File

@ -0,0 +1 @@
Some improvements on DialPad (cursor edition, paste number, small fixes).

View File

@ -61,6 +61,7 @@ class CallDialPadBottomSheet : VectorBaseBottomSheetDialogFragment<BottomSheetCa
arguments = Bundle().apply { arguments = Bundle().apply {
putBoolean(DialPadFragment.EXTRA_ENABLE_DELETE, showActions) putBoolean(DialPadFragment.EXTRA_ENABLE_DELETE, showActions)
putBoolean(DialPadFragment.EXTRA_ENABLE_OK, showActions) putBoolean(DialPadFragment.EXTRA_ENABLE_OK, showActions)
putBoolean(DialPadFragment.EXTRA_CURSOR_VISIBLE, false)
putString(DialPadFragment.EXTRA_REGION_CODE, VectorLocale.applicationLocale.country) putString(DialPadFragment.EXTRA_REGION_CODE, VectorLocale.applicationLocale.country)
} }
callback = DialPadFragmentCallbackWrapper(this@CallDialPadBottomSheet.callback) callback = DialPadFragmentCallbackWrapper(this@CallDialPadBottomSheet.callback)
@ -88,10 +89,6 @@ class CallDialPadBottomSheet : VectorBaseBottomSheetDialogFragment<BottomSheetCa
private inner class DialPadFragmentCallbackWrapper(val callback: DialPadFragment.Callback?): DialPadFragment.Callback { private inner class DialPadFragmentCallbackWrapper(val callback: DialPadFragment.Callback?): DialPadFragment.Callback {
override fun onDigitAppended(digit: String) {
callback?.onDigitAppended(digit)
}
override fun onOkClicked(formatted: String?, raw: String?) { override fun onOkClicked(formatted: String?, raw: String?) {
callback?.onOkClicked(formatted, raw) callback?.onOkClicked(formatted, raw)
dismiss() dismiss()

View File

@ -16,34 +16,40 @@
package im.vector.app.features.call.dialpad package im.vector.app.features.call.dialpad
import android.content.ClipboardManager
import android.content.res.ColorStateList import android.content.res.ColorStateList
import android.os.Bundle import android.os.Bundle
import android.telephony.PhoneNumberFormattingTextWatcher
import android.telephony.PhoneNumberUtils
import android.text.Editable
import android.text.InputType
import android.text.TextUtils
import android.text.TextWatcher
import android.text.method.DialerKeyListener
import android.view.KeyEvent
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.core.content.getSystemService
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.core.widget.ImageViewCompat import androidx.core.widget.ImageViewCompat
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import com.android.dialer.dialpadview.DialpadView import com.android.dialer.dialpadview.DialpadView
import com.android.dialer.dialpadview.DigitsEditText import com.android.dialer.dialpadview.DigitsEditText
import com.google.i18n.phonenumbers.AsYouTypeFormatter
import com.google.i18n.phonenumbers.PhoneNumberUtil
import im.vector.app.R import im.vector.app.R
import im.vector.app.features.themes.ThemeUtils import im.vector.app.features.themes.ThemeUtils
class DialPadFragment : Fragment() { class DialPadFragment : Fragment(), TextWatcher {
var callback: Callback? = null var callback: Callback? = null
private var digits: DigitsEditText? = null private lateinit var digits: DigitsEditText
private var formatter: AsYouTypeFormatter? = null
private var input = ""
private var regionCode: String = DEFAULT_REGION_CODE private var regionCode: String = DEFAULT_REGION_CODE
private var formatAsYouType = true private var formatAsYouType = true
private var enableStar = true private var enableStar = true
private var enablePound = true private var enablePound = true
private var enablePlus = true private var enablePlus = true
private var cursorVisible = false private var cursorVisible = true
private var enableDelete = true private var enableDelete = true
private var enableFabOk = true private var enableFabOk = true
@ -53,72 +59,80 @@ class DialPadFragment : Fragment() {
savedInstanceState: Bundle?): View { savedInstanceState: Bundle?): View {
initArgs(savedInstanceState) initArgs(savedInstanceState)
val view = inflater.inflate(R.layout.dialpad_fragment, container, false) val view = inflater.inflate(R.layout.dialpad_fragment, container, false)
view.setBackgroundColor(ThemeUtils.getColor(requireContext(), R.attr.backgroundColor))
val dialpadView = view.findViewById<View>(R.id.dialpad_view) as DialpadView val dialpadView = view.findViewById<View>(R.id.dialpad_view) as DialpadView
dialpadView.findViewById<View>(R.id.dialpad_key_voicemail).isVisible = false dialpadView.findViewById<View>(R.id.dialpad_key_voicemail).isVisible = false
digits = dialpadView.digits as? DigitsEditText digits = dialpadView.digits as DigitsEditText
digits?.isCursorVisible = cursorVisible digits.isCursorVisible = cursorVisible
digits?.setTextColor(ThemeUtils.getColor(requireContext(), R.attr.vctr_content_primary)) digits.inputType = InputType.TYPE_CLASS_PHONE
dialpadView.findViewById<View>(R.id.zero).setOnClickListener { append('0') } digits.keyListener = DialerKeyListener.getInstance()
if (enablePlus) { digits.setTextColor(ThemeUtils.getColor(requireContext(), R.attr.vctr_content_primary))
dialpadView.findViewById<View>(R.id.zero).setOnLongClickListener { digits.addTextChangedListener(PhoneNumberFormattingTextWatcher(if (formatAsYouType) regionCode else ""))
append('+') digits.addTextChangedListener(this)
true dialpadView.findViewById<View>(R.id.zero).setOnClickListener { keyPressed(KeyEvent.KEYCODE_0, "0") }
} dialpadView.findViewById<View>(R.id.one).setOnClickListener { keyPressed(KeyEvent.KEYCODE_1, "1") }
} dialpadView.findViewById<View>(R.id.two).setOnClickListener { keyPressed(KeyEvent.KEYCODE_2, "2") }
dialpadView.findViewById<View>(R.id.one).setOnClickListener { append('1') } dialpadView.findViewById<View>(R.id.three).setOnClickListener { keyPressed(KeyEvent.KEYCODE_3, "3") }
dialpadView.findViewById<View>(R.id.two).setOnClickListener { append('2') } dialpadView.findViewById<View>(R.id.four).setOnClickListener { keyPressed(KeyEvent.KEYCODE_4, "4") }
dialpadView.findViewById<View>(R.id.three).setOnClickListener { append('3') } dialpadView.findViewById<View>(R.id.five).setOnClickListener { keyPressed(KeyEvent.KEYCODE_5, "5") }
dialpadView.findViewById<View>(R.id.four).setOnClickListener { append('4') } dialpadView.findViewById<View>(R.id.six).setOnClickListener { keyPressed(KeyEvent.KEYCODE_6, "6") }
dialpadView.findViewById<View>(R.id.four).setOnClickListener { append('4') } dialpadView.findViewById<View>(R.id.seven).setOnClickListener { keyPressed(KeyEvent.KEYCODE_7, "7") }
dialpadView.findViewById<View>(R.id.five).setOnClickListener { append('5') } dialpadView.findViewById<View>(R.id.eight).setOnClickListener { keyPressed(KeyEvent.KEYCODE_8, "8") }
dialpadView.findViewById<View>(R.id.six).setOnClickListener { append('6') } dialpadView.findViewById<View>(R.id.nine).setOnClickListener { keyPressed(KeyEvent.KEYCODE_9, "9") }
dialpadView.findViewById<View>(R.id.seven).setOnClickListener { append('7') }
dialpadView.findViewById<View>(R.id.eight).setOnClickListener { append('8') }
dialpadView.findViewById<View>(R.id.nine).setOnClickListener { append('9') }
if (enableStar) { if (enableStar) {
dialpadView.findViewById<View>(R.id.star).setOnClickListener { append('*') } dialpadView.findViewById<View>(R.id.star).setOnClickListener { keyPressed(KeyEvent.KEYCODE_STAR, "*") }
} else { } else {
dialpadView.findViewById<View>(R.id.star).isVisible = false dialpadView.findViewById<View>(R.id.star).isVisible = false
} }
if (enablePound) { if (enablePound) {
dialpadView.findViewById<View>(R.id.pound).setOnClickListener { append('#') } dialpadView.findViewById<View>(R.id.pound).setOnClickListener { keyPressed(KeyEvent.KEYCODE_POUND, "#") }
} else { } else {
dialpadView.findViewById<View>(R.id.pound).isVisible = false dialpadView.findViewById<View>(R.id.pound).isVisible = false
} }
if (enablePlus) {
dialpadView.findViewById<View>(R.id.zero).setOnLongClickListener {
keyPressed(KeyEvent.KEYCODE_PLUS, "+")
true
}
}
if (enableDelete) { if (enableDelete) {
dialpadView.deleteButton.setOnClickListener { poll() } dialpadView.deleteButton.setOnClickListener { keyPressed(KeyEvent.KEYCODE_DEL, null) }
dialpadView.deleteButton.setOnLongClickListener { dialpadView.deleteButton.setOnLongClickListener {
clear() clear()
true true
} }
val tintColor = ThemeUtils.getColor(requireContext(), im.vector.app.R.attr.vctr_content_secondary) val tintColor = ThemeUtils.getColor(requireContext(), R.attr.vctr_content_secondary)
ImageViewCompat.setImageTintList(dialpadView.deleteButton, ColorStateList.valueOf(tintColor)) ImageViewCompat.setImageTintList(dialpadView.deleteButton, ColorStateList.valueOf(tintColor))
} else { } else {
dialpadView.deleteButton.isVisible = false dialpadView.deleteButton.isVisible = false
} }
// if region code is null, no formatting is performed
formatter = PhoneNumberUtil.getInstance().getAsYouTypeFormatter(if (formatAsYouType) regionCode else "")
val fabOk = view.findViewById<View>(R.id.fab_ok) val fabOk = view.findViewById<View>(R.id.fab_ok)
if (enableFabOk) { if (enableFabOk) {
fabOk.setOnClickListener { fabOk.setOnClickListener { onOkClicked() }
callback?.onOkClicked(digits?.text.toString(), input)
}
} else { } else {
fabOk.isVisible = false fabOk.isVisible = false
} }
digits?.setOnTextContextMenuClickListener {
val string = digits?.text.toString()
clear()
for (element in string) {
append(element)
}
}
return view return view
} }
private fun onOkClicked() {
val rawInput = getRawInput()
if (rawInput.isEmpty()) {
val clipboard = requireContext().getSystemService<ClipboardManager>()
val textToPaste = clipboard?.primaryClip?.getItemAt(0)?.text ?: return
val formatted = formatNumber(textToPaste.toString())
digits.setText(formatted)
digits.setSelection(digits.text!!.length)
} else {
val formatted = digits.text.toString()
callback?.onOkClicked(formatted, rawInput)
}
}
fun getRawInput(): String {
return PhoneNumberUtils.normalizeNumber(digits.text.toString())
}
override fun onSaveInstanceState(outState: Bundle) { override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState) super.onSaveInstanceState(outState)
outState.putString(EXTRA_REGION_CODE, regionCode) outState.putString(EXTRA_REGION_CODE, regionCode)
@ -145,40 +159,35 @@ class DialPadFragment : Fragment() {
} }
} }
private fun poll() { private fun keyPressed(keyCode: Int, digitString: String?) {
if (input.isNotEmpty()) { val event = KeyEvent(KeyEvent.ACTION_DOWN, keyCode)
input = input.substring(0, input.length - 1) // Disable cursor and enable it again after onKeyDown otherwise DigitsEditText force replacing cursor at the end
formatter = PhoneNumberUtil.getInstance().getAsYouTypeFormatter(regionCode) digits.isCursorVisible = false
if (formatAsYouType) { digits.onKeyDown(keyCode, event)
digits?.setText("") digits.isCursorVisible = cursorVisible
for (c in input.toCharArray()) { digitString?.also {
digits?.setText(formatter?.inputDigit(c)) callback?.onDigitAppended(it)
}
} else {
digits?.setText(input)
}
} }
} }
private fun clear() { private fun clear() {
formatter?.clear() digits.setText("")
digits?.setText("")
input = ""
} }
private fun append(c: Char) { private fun formatNumber(dialString: String): String {
callback?.onDigitAppended(c.toString()) val networkPortion = PhoneNumberUtils.extractNetworkPortion(dialString)
input += c if (TextUtils.isEmpty(networkPortion)) {
if (formatAsYouType) { return ""
digits?.setText(formatter?.inputDigit(c))
} else {
digits?.setText(input)
} }
val number = PhoneNumberUtils.formatNumber(networkPortion, null, regionCode) ?: networkPortion
// Also retrieve the post dial portion of the provided data, so that the entire dial string can be reconstituted
val postDial = PhoneNumberUtils.extractPostDialPortion(dialString)
return number + postDial
} }
interface Callback { interface Callback {
fun onOkClicked(formatted: String?, raw: String?) = Unit
fun onDigitAppended(digit: String) = Unit fun onDigitAppended(digit: String) = Unit
fun onOkClicked(formatted: String?, raw: String?) = Unit
} }
companion object { companion object {
@ -193,4 +202,20 @@ class DialPadFragment : Fragment() {
private const val DEFAULT_REGION_CODE = "US" private const val DEFAULT_REGION_CODE = "US"
} }
// Text watcher
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {
// Noop
}
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
// Noop
}
override fun afterTextChanged(s: Editable) {
if (s.isEmpty()) {
digits.clearFocus()
}
}
} }

View File

@ -86,17 +86,13 @@ class CallTransferActivity : VectorBaseActivity<ActivityCallTransferBinding>(),
} }
} }
sectionsPagerAdapter = CallTransferPagerAdapter(this).register() sectionsPagerAdapter = CallTransferPagerAdapter(this)
views.callTransferViewPager.adapter = sectionsPagerAdapter views.callTransferViewPager.adapter = sectionsPagerAdapter
sectionsPagerAdapter.onDialPadOkClicked = { phoneNumber ->
val action = CallTransferAction.ConnectWithPhoneNumber(views.callTransferConsultCheckBox.isChecked, phoneNumber)
callTransferViewModel.handle(action)
}
TabLayoutMediator(views.callTransferTabLayout, views.callTransferViewPager) { tab, position -> TabLayoutMediator(views.callTransferTabLayout, views.callTransferViewPager) { tab, position ->
when (position) { when (position) {
0 -> tab.text = getString(R.string.call_transfer_users_tab_title) CallTransferPagerAdapter.USER_LIST_INDEX -> tab.text = getString(R.string.call_transfer_users_tab_title)
1 -> tab.text = getString(R.string.call_dial_pad_title) CallTransferPagerAdapter.DIAL_PAD_INDEX -> tab.text = getString(R.string.call_dial_pad_title)
} }
}.attach() }.attach()
configureToolbar(views.callTransferToolbar) configureToolbar(views.callTransferToolbar)
@ -106,11 +102,18 @@ class CallTransferActivity : VectorBaseActivity<ActivityCallTransferBinding>(),
private fun setupConnectAction() { private fun setupConnectAction() {
views.callTransferConnectAction.debouncedClicks { views.callTransferConnectAction.debouncedClicks {
val selectedUser = sectionsPagerAdapter.userListFragment?.getCurrentState()?.getSelectedMatrixId()?.firstOrNull() when (views.callTransferTabLayout.selectedTabPosition) {
if (selectedUser != null) { CallTransferPagerAdapter.USER_LIST_INDEX -> {
val selectedUser = sectionsPagerAdapter.userListFragment?.getCurrentState()?.getSelectedMatrixId()?.firstOrNull() ?: return@debouncedClicks
val action = CallTransferAction.ConnectWithUserId(views.callTransferConsultCheckBox.isChecked, selectedUser) val action = CallTransferAction.ConnectWithUserId(views.callTransferConsultCheckBox.isChecked, selectedUser)
callTransferViewModel.handle(action) callTransferViewModel.handle(action)
} }
CallTransferPagerAdapter.DIAL_PAD_INDEX -> {
val phoneNumber = sectionsPagerAdapter.dialPadFragment?.getRawInput() ?: return@debouncedClicks
val action = CallTransferAction.ConnectWithPhoneNumber(views.callTransferConsultCheckBox.isChecked, phoneNumber)
callTransferViewModel.handle(action)
}
}
} }
} }

View File

@ -21,7 +21,6 @@ import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
import androidx.viewpager2.adapter.FragmentStateAdapter import androidx.viewpager2.adapter.FragmentStateAdapter
import im.vector.app.core.extensions.toMvRxBundle import im.vector.app.core.extensions.toMvRxBundle
import im.vector.app.core.platform.Restorable
import im.vector.app.features.call.dialpad.DialPadFragment import im.vector.app.features.call.dialpad.DialPadFragment
import im.vector.app.features.settings.VectorLocale import im.vector.app.features.settings.VectorLocale
import im.vector.app.features.userdirectory.UserListFragment import im.vector.app.features.userdirectory.UserListFragment
@ -29,14 +28,17 @@ import im.vector.app.features.userdirectory.UserListFragmentArgs
class CallTransferPagerAdapter( class CallTransferPagerAdapter(
private val fragmentActivity: FragmentActivity private val fragmentActivity: FragmentActivity
) : FragmentStateAdapter(fragmentActivity), Restorable { ) : FragmentStateAdapter(fragmentActivity) {
companion object {
const val USER_LIST_INDEX = 0
const val DIAL_PAD_INDEX = 1
}
val userListFragment: UserListFragment? val userListFragment: UserListFragment?
get() = findFragmentAtPosition(0) as? UserListFragment get() = findFragmentAtPosition(USER_LIST_INDEX) as? UserListFragment
val dialPadFragment: DialPadFragment? val dialPadFragment: DialPadFragment?
get() = findFragmentAtPosition(1) as? DialPadFragment get() = findFragmentAtPosition(DIAL_PAD_INDEX) as? DialPadFragment
var onDialPadOkClicked: ((String) -> Unit)? = null
override fun getItemCount() = 2 override fun getItemCount() = 2
@ -57,10 +59,9 @@ class CallTransferPagerAdapter(
(fragment as DialPadFragment).apply { (fragment as DialPadFragment).apply {
arguments = Bundle().apply { arguments = Bundle().apply {
putBoolean(DialPadFragment.EXTRA_ENABLE_DELETE, true) putBoolean(DialPadFragment.EXTRA_ENABLE_DELETE, true)
putBoolean(DialPadFragment.EXTRA_ENABLE_OK, true) putBoolean(DialPadFragment.EXTRA_ENABLE_OK, false)
putString(DialPadFragment.EXTRA_REGION_CODE, VectorLocale.applicationLocale.country) putString(DialPadFragment.EXTRA_REGION_CODE, VectorLocale.applicationLocale.country)
} }
applyCallback()
} }
} }
return fragment return fragment
@ -69,20 +70,4 @@ class CallTransferPagerAdapter(
private fun findFragmentAtPosition(position: Int): Fragment? { private fun findFragmentAtPosition(position: Int): Fragment? {
return fragmentActivity.supportFragmentManager.findFragmentByTag("f$position") return fragmentActivity.supportFragmentManager.findFragmentByTag("f$position")
} }
override fun onSaveInstanceState(outState: Bundle) = Unit
override fun onRestoreInstanceState(savedInstanceState: Bundle?) {
dialPadFragment?.applyCallback()
}
private fun DialPadFragment.applyCallback(): DialPadFragment {
callback = object : DialPadFragment.Callback {
override fun onOkClicked(formatted: String?, raw: String?) {
if (raw.isNullOrEmpty()) return
onDialPadOkClicked?.invoke(raw)
}
}
return this
}
} }

View File

@ -22,6 +22,7 @@ import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.core.view.get
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.core.view.iterator import androidx.core.view.iterator
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
@ -122,7 +123,6 @@ class HomeDetailFragment @Inject constructor(
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
sharedActionViewModel = activityViewModelProvider.get(HomeSharedActionViewModel::class.java) sharedActionViewModel = activityViewModelProvider.get(HomeSharedActionViewModel::class.java)
sharedCallActionViewModel = activityViewModelProvider.get(SharedKnownCallsViewModel::class.java) sharedCallActionViewModel = activityViewModelProvider.get(SharedKnownCallsViewModel::class.java)
setupBottomNavigationView() setupBottomNavigationView()
setupToolbar() setupToolbar()
setupKeysBackupBanner() setupKeysBackupBanner()
@ -351,6 +351,7 @@ class HomeDetailFragment @Inject constructor(
} }
private fun updateUIForTab(tab: HomeTab) { private fun updateUIForTab(tab: HomeTab) {
views.bottomNavigationView.menu.findItem(tab.toMenuId()).isChecked = true
views.groupToolbarTitleView.setText(tab.titleRes) views.groupToolbarTitleView.setText(tab.titleRes)
updateSelectedFragment(tab) updateSelectedFragment(tab)
invalidateOptionsMenu() invalidateOptionsMenu()

View File

@ -140,7 +140,6 @@
android:id="@+id/roomListContainer" android:id="@+id/roomListContainer"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="0dp" android:layout_height="0dp"
android:background="?vctr_list_separator_system"
app:layout_constraintBottom_toTopOf="@+id/bottomNavigationView" app:layout_constraintBottom_toTopOf="@+id/bottomNavigationView"
app:layout_constraintTop_toBottomOf="@+id/activeCallView" /> app:layout_constraintTop_toBottomOf="@+id/activeCallView" />