diff --git a/app/build.gradle b/app/build.gradle index 5892549a2..a6d041406 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -100,7 +100,7 @@ dependencies { // https://github.com/tommybuonomo/dotsindicator/blob/master/LICENSE Apache v2.0 implementation("com.tbuonomo:dotsindicator:5.0") - implementation platform('com.google.firebase:firebase-bom:30.3.2') + implementation platform('com.google.firebase:firebase-bom:32.2.3') implementation 'com.google.firebase:firebase-messaging' implementation 'org.linphone:linphone-sdk-android:5.3+' diff --git a/app/src/main/java/org/linphone/ui/main/calls/fragment/StartCallFragment.kt b/app/src/main/java/org/linphone/ui/main/calls/fragment/StartCallFragment.kt index 3b9f3bc8c..d99360c6e 100644 --- a/app/src/main/java/org/linphone/ui/main/calls/fragment/StartCallFragment.kt +++ b/app/src/main/java/org/linphone/ui/main/calls/fragment/StartCallFragment.kt @@ -43,6 +43,9 @@ import org.linphone.ui.main.contacts.viewmodel.ContactsListViewModel import org.linphone.ui.main.fragment.GenericFragment import org.linphone.ui.main.model.isInSecureMode import org.linphone.utils.DialogUtils +import org.linphone.utils.hideKeyboard +import org.linphone.utils.setKeyboardInsetListener +import org.linphone.utils.showKeyboard @UiThread class StartCallFragment : GenericFragment() { @@ -103,6 +106,10 @@ class StartCallFragment : GenericFragment() { goBack() } + binding.setHideNumpadClickListener { + viewModel.hideNumpad() + } + contactsAdapter = ContactsListAdapter(viewLifecycleOwner, disableLongClick = true) binding.contactsList.setHasFixedSize(true) binding.contactsList.adapter = contactsAdapter @@ -140,6 +147,32 @@ class StartCallFragment : GenericFragment() { contactsListViewModel.applyFilter(filter) viewModel.applyFilter(filter) } + + 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() + } + } + } + + binding.root.setKeyboardInsetListener { keyboardVisible -> + if (keyboardVisible) { + viewModel.isNumpadVisible.value = false + } + } } private fun startCall(model: ContactAvatarModel) { diff --git a/app/src/main/java/org/linphone/ui/main/calls/model/NumpadModel.kt b/app/src/main/java/org/linphone/ui/main/calls/model/NumpadModel.kt new file mode 100644 index 000000000..f39be1540 --- /dev/null +++ b/app/src/main/java/org/linphone/ui/main/calls/model/NumpadModel.kt @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2010-2023 Belledonne Communications SARL. + * + * This file is part of linphone-android + * (see https://www.linphone.org). + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.linphone.ui.main.calls.model + +import androidx.annotation.UiThread +import org.linphone.core.tools.Log + +class NumpadModel @UiThread constructor( + private val onDigitClicked: (value: String) -> (Unit), + private val onCallClicked: () -> (Unit) +) { + companion object { + private const val TAG = "[Numpad Model]" + } + + @UiThread + fun onDigitClicked(value: String) { + Log.i("$TAG Clicked on digit [$value]") + onDigitClicked.invoke(value) + } + + fun onDigitLongClicked(value: String): Boolean { + Log.i("$TAG Long clicked on digit [$value]") + onDigitClicked.invoke(value) + return true + } + + @UiThread + fun onCallClicked() { + Log.i("$TAG Starting call") + onCallClicked.invoke() + } +} diff --git a/app/src/main/java/org/linphone/ui/main/calls/viewmodel/StartCallViewModel.kt b/app/src/main/java/org/linphone/ui/main/calls/viewmodel/StartCallViewModel.kt index 6705a9033..67a34baa6 100644 --- a/app/src/main/java/org/linphone/ui/main/calls/viewmodel/StartCallViewModel.kt +++ b/app/src/main/java/org/linphone/ui/main/calls/viewmodel/StartCallViewModel.kt @@ -33,6 +33,7 @@ import org.linphone.core.MagicSearch import org.linphone.core.MagicSearchListenerStub import org.linphone.core.SearchResult import org.linphone.core.tools.Log +import org.linphone.ui.main.calls.model.NumpadModel import org.linphone.ui.main.calls.model.SuggestionModel import org.linphone.ui.main.model.isInSecureMode import org.linphone.utils.Event @@ -49,6 +50,18 @@ class StartCallViewModel @UiThread constructor() : ViewModel() { val suggestionsList = MutableLiveData>() + val numpadModel: NumpadModel + + val isNumpadVisible = MutableLiveData() + + val appendDigitToSearchBarEvent: MutableLiveData> by lazy { + MutableLiveData>() + } + + val requestKeyboardVisibilityChangedEvent: MutableLiveData> by lazy { + MutableLiveData>() + } + val onSuggestionClickedEvent: MutableLiveData> by lazy { MutableLiveData>() } @@ -81,6 +94,31 @@ class StartCallViewModel @UiThread constructor() : ViewModel() { } init { + isNumpadVisible.value = false + numpadModel = NumpadModel( + { digit -> + // onDigitClicked + appendDigitToSearchBarEvent.value = Event(digit) + // Don't do that, cursor will stay at start + // searchFilter.value = "${searchFilter.value.orEmpty()}$digit" + }, + { // OnCallClicked + val suggestion = searchFilter.value.orEmpty() + if (suggestion.isNotEmpty()) { + Log.i("$TAG Using numpad dial button to call [$suggestion]") + coreContext.postOnCoreThread { core -> + val address = core.interpretUrl(suggestion, true) + if (address != null) { + Log.i("$TAG Calling [${address.asStringUriOnly()}]") + onSuggestionClickedEvent.postValue(Event(address)) + } else { + Log.e("$TAG Failed to parse [$suggestion] as SIP address") + } + } + } + } + ) + coreContext.postOnCoreThread { core -> val defaultAccount = core.defaultAccount limitSearchToLinphoneAccounts = defaultAccount?.isInSecureMode() ?: false @@ -108,6 +146,17 @@ class StartCallViewModel @UiThread constructor() : ViewModel() { searchFilter.value = "" } + @UiThread + fun switchBetweenKeyboardAndNumpad() { + requestKeyboardVisibilityChangedEvent.value = Event(isNumpadVisible.value == true) + isNumpadVisible.value = isNumpadVisible.value == false + } + + @UiThread + fun hideNumpad() { + isNumpadVisible.value = false + } + @WorkerThread fun processMagicSearchResults(results: Array) { Log.i("$TAG Processing [${results.size}] results") diff --git a/app/src/main/java/org/linphone/utils/DataBindingUtils.kt b/app/src/main/java/org/linphone/utils/DataBindingUtils.kt index 053b13087..110544c37 100644 --- a/app/src/main/java/org/linphone/utils/DataBindingUtils.kt +++ b/app/src/main/java/org/linphone/utils/DataBindingUtils.kt @@ -40,6 +40,7 @@ import androidx.core.view.doOnLayout import androidx.databinding.BindingAdapter import androidx.databinding.DataBindingUtil import androidx.databinding.ViewDataBinding +import androidx.lifecycle.LifecycleOwner import coil.load import coil.transform.CircleCropTransformation import io.getstream.avatarview.AvatarView @@ -120,7 +121,7 @@ fun View.setKeyboardInsetListener(lambda: (visible: Boolean) -> Unit) { lambda(isKeyboardVisible) } catch (ise: IllegalStateException) { Log.e( - "[Databinding Utils] Failed to called lambda after keyboard visibility changed: $ise" + "[Data Binding Utils] Failed to called lambda after keyboard visibility changed: $ise" ) } @@ -137,7 +138,7 @@ fun View.setKeyboardInsetListener(lambda: (visible: Boolean) -> Unit) { lambda(isKeyboardVisible) } catch (ise: IllegalStateException) { Log.e( - "[Databinding Utils] Failed to called lambda after keyboard visibility changed: $ise" + "[Data Binding Utils] Failed to called lambda after keyboard visibility changed: $ise" ) } } @@ -284,3 +285,12 @@ fun setConstraintLayoutBottomMargin(view: View, margins: Float) { params.setMargins(params.leftMargin, params.topMargin, params.rightMargin, m) view.layoutParams = params } + +@BindingAdapter("inflatedLifecycleOwner") +fun setInflatedViewStubLifecycleOwner(view: View, enable: Boolean) { + val binding = DataBindingUtil.bind(view) + // This is a bit hacky... + if (view.context is LifecycleOwner) { + binding?.lifecycleOwner = view.context as? LifecycleOwner + } +} diff --git a/app/src/main/res/drawable/shape_circle_green_background.xml b/app/src/main/res/drawable/shape_circle_green_background.xml new file mode 100644 index 000000000..d36f85dd7 --- /dev/null +++ b/app/src/main/res/drawable/shape_circle_green_background.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_drawer_handle.xml b/app/src/main/res/drawable/shape_drawer_handle.xml new file mode 100644 index 000000000..7946b3661 --- /dev/null +++ b/app/src/main/res/drawable/shape_drawer_handle.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_numpad_background.xml b/app/src/main/res/drawable/shape_numpad_background.xml new file mode 100644 index 000000000..334cf259c --- /dev/null +++ b/app/src/main/res/drawable/shape_numpad_background.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout-land/calls_list_fragment.xml b/app/src/main/res/layout-land/calls_list_fragment.xml index 659da509d..5b4e3a2c5 100644 --- a/app/src/main/res/layout-land/calls_list_fragment.xml +++ b/app/src/main/res/layout-land/calls_list_fragment.xml @@ -47,7 +47,7 @@ - + + @@ -16,7 +19,7 @@ + android:background="@color/gray_7"> + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/call_start_numpad.xml b/app/src/main/res/layout/call_start_numpad.xml new file mode 100644 index 000000000..5c3eb9ada --- /dev/null +++ b/app/src/main/res/layout/call_start_numpad.xml @@ -0,0 +1,191 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/calls_list_fragment.xml b/app/src/main/res/layout/calls_list_fragment.xml index 0a245880b..63dc048b6 100644 --- a/app/src/main/res/layout/calls_list_fragment.xml +++ b/app/src/main/res/layout/calls_list_fragment.xml @@ -47,7 +47,7 @@ #FFEACB #FFB266 #22334D + #C0D1D9 #EEF7F8 diff --git a/app/src/main/res/values/dimen.xml b/app/src/main/res/values/dimen.xml index a2e74c5c8..612d669be 100644 --- a/app/src/main/res/values/dimen.xml +++ b/app/src/main/res/values/dimen.xml @@ -8,6 +8,8 @@ 355dp 300dp + 24dp + 45dp 50dp 100dp diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 1b7939621..4ba503000 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -50,6 +50,14 @@ @color/red_danger 8dp +