Implement bluetooth device list bottom sheet.
This commit is contained in:
parent
706f513baf
commit
b3b5a5bfe6
@ -3247,6 +3247,11 @@
|
|||||||
<!-- Element Call Widget - Push to Talk -->
|
<!-- Element Call Widget - Push to Talk -->
|
||||||
<string name="push_to_talk_notification_title">${app_name} Push to Talk</string>
|
<string name="push_to_talk_notification_title">${app_name} Push to Talk</string>
|
||||||
<string name="push_to_talk_notification_description">A service is running to communicate with BLE device</string>
|
<string name="push_to_talk_notification_description">A service is running to communicate with BLE device</string>
|
||||||
|
<string name="push_to_talk_activity_title">Walkie-Talkie Call</string>
|
||||||
|
<string name="action_push_to_talk_configure_device">Configure push to talk device</string>
|
||||||
|
<string name="push_to_talk_bottom_sheet_title">Bluetooth</string>
|
||||||
|
<string name="push_to_talk_device_connected">Connected</string>
|
||||||
|
<string name="push_to_talk_device_disconnected">Disconnected</string>
|
||||||
|
|
||||||
<plurals name="room_removed_messages">
|
<plurals name="room_removed_messages">
|
||||||
<item quantity="one">%d message removed</item>
|
<item quantity="one">%d message removed</item>
|
||||||
|
|||||||
@ -16,7 +16,6 @@
|
|||||||
|
|
||||||
package im.vector.app.features.widgets
|
package im.vector.app.features.widgets
|
||||||
|
|
||||||
import android.bluetooth.BluetoothDevice
|
|
||||||
import im.vector.app.core.platform.VectorViewModelAction
|
import im.vector.app.core.platform.VectorViewModelAction
|
||||||
|
|
||||||
sealed class WidgetAction : VectorViewModelAction {
|
sealed class WidgetAction : VectorViewModelAction {
|
||||||
@ -27,7 +26,7 @@ sealed class WidgetAction : VectorViewModelAction {
|
|||||||
object DeleteWidget : WidgetAction()
|
object DeleteWidget : WidgetAction()
|
||||||
object RevokeWidget : WidgetAction()
|
object RevokeWidget : WidgetAction()
|
||||||
object OnTermsReviewed : WidgetAction()
|
object OnTermsReviewed : WidgetAction()
|
||||||
data class ConnectToBluetoothDevice(val device: BluetoothDevice) : WidgetAction()
|
data class ConnectToBluetoothDevice(val deviceAddress: String) : WidgetAction()
|
||||||
object HangupElementCall : WidgetAction()
|
object HangupElementCall : WidgetAction()
|
||||||
object CloseWidget : WidgetAction()
|
object CloseWidget : WidgetAction()
|
||||||
}
|
}
|
||||||
|
|||||||
@ -35,7 +35,6 @@ import android.webkit.PermissionRequest
|
|||||||
import android.webkit.WebMessage
|
import android.webkit.WebMessage
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.annotation.RequiresApi
|
import androidx.annotation.RequiresApi
|
||||||
import androidx.appcompat.app.AlertDialog
|
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.content.getSystemService
|
import androidx.core.content.getSystemService
|
||||||
import androidx.core.view.isInvisible
|
import androidx.core.view.isInvisible
|
||||||
@ -47,30 +46,34 @@ import com.airbnb.mvrx.Uninitialized
|
|||||||
import com.airbnb.mvrx.activityViewModel
|
import com.airbnb.mvrx.activityViewModel
|
||||||
import com.airbnb.mvrx.args
|
import com.airbnb.mvrx.args
|
||||||
import com.airbnb.mvrx.withState
|
import com.airbnb.mvrx.withState
|
||||||
|
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
|
import im.vector.app.core.extensions.cleanup
|
||||||
|
import im.vector.app.core.extensions.configureWith
|
||||||
import im.vector.app.core.extensions.registerStartForActivityResult
|
import im.vector.app.core.extensions.registerStartForActivityResult
|
||||||
import im.vector.app.core.platform.OnBackPressed
|
import im.vector.app.core.platform.OnBackPressed
|
||||||
import im.vector.app.core.platform.VectorBaseFragment
|
import im.vector.app.core.platform.VectorBaseFragment
|
||||||
|
import im.vector.app.core.platform.VectorMenuProvider
|
||||||
|
import im.vector.app.core.utils.CheckWebViewPermissionsUseCase
|
||||||
import im.vector.app.core.utils.PERMISSIONS_FOR_BLUETOOTH
|
import im.vector.app.core.utils.PERMISSIONS_FOR_BLUETOOTH
|
||||||
import im.vector.app.core.utils.checkPermissions
|
import im.vector.app.core.utils.checkPermissions
|
||||||
import im.vector.app.core.utils.onPermissionDeniedDialog
|
import im.vector.app.core.utils.onPermissionDeniedDialog
|
||||||
import im.vector.app.core.platform.VectorMenuProvider
|
|
||||||
import im.vector.app.core.utils.CheckWebViewPermissionsUseCase
|
|
||||||
import im.vector.app.core.utils.openUrlInExternalBrowser
|
import im.vector.app.core.utils.openUrlInExternalBrowser
|
||||||
import im.vector.app.core.utils.registerForPermissionsResult
|
import im.vector.app.core.utils.registerForPermissionsResult
|
||||||
import im.vector.app.databinding.FragmentRoomWidgetBinding
|
import im.vector.app.databinding.FragmentRoomWidgetBinding
|
||||||
import im.vector.app.features.settings.VectorPreferences
|
import im.vector.app.features.settings.VectorPreferences
|
||||||
import im.vector.app.features.webview.WebEventListener
|
import im.vector.app.features.webview.WebEventListener
|
||||||
|
import im.vector.app.features.widgets.ptt.BluetoothLowEnergyDevice
|
||||||
import im.vector.app.features.widgets.ptt.BluetoothLowEnergyDeviceScanner
|
import im.vector.app.features.widgets.ptt.BluetoothLowEnergyDeviceScanner
|
||||||
|
import im.vector.app.features.widgets.ptt.BluetoothLowEnergyDevicesBottomSheetController
|
||||||
import im.vector.app.features.widgets.ptt.BluetoothLowEnergyService
|
import im.vector.app.features.widgets.ptt.BluetoothLowEnergyService
|
||||||
import im.vector.app.features.widgets.webview.WebviewPermissionUtils
|
import im.vector.app.features.widgets.webview.WebviewPermissionUtils
|
||||||
import im.vector.app.features.widgets.webview.clearAfterWidget
|
import im.vector.app.features.widgets.webview.clearAfterWidget
|
||||||
import im.vector.app.features.widgets.webview.setupForWidget
|
import im.vector.app.features.widgets.webview.setupForWidget
|
||||||
import im.vector.lib.core.utils.compat.resolveActivityCompat
|
import im.vector.lib.core.utils.compat.resolveActivityCompat
|
||||||
import kotlinx.parcelize.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
import org.matrix.android.sdk.api.extensions.orFalse
|
|
||||||
import org.matrix.android.sdk.api.session.terms.TermsService
|
import org.matrix.android.sdk.api.session.terms.TermsService
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.net.URISyntaxException
|
import java.net.URISyntaxException
|
||||||
@ -96,6 +99,7 @@ class WidgetFragment :
|
|||||||
@Inject lateinit var checkWebViewPermissionsUseCase: CheckWebViewPermissionsUseCase
|
@Inject lateinit var checkWebViewPermissionsUseCase: CheckWebViewPermissionsUseCase
|
||||||
@Inject lateinit var vectorPreferences: VectorPreferences
|
@Inject lateinit var vectorPreferences: VectorPreferences
|
||||||
@Inject lateinit var bluetoothLowEnergyDeviceScanner: BluetoothLowEnergyDeviceScanner
|
@Inject lateinit var bluetoothLowEnergyDeviceScanner: BluetoothLowEnergyDeviceScanner
|
||||||
|
@Inject lateinit var bluetoothLowEnergyDevicesBottomSheetController: BluetoothLowEnergyDevicesBottomSheetController
|
||||||
|
|
||||||
private val fragmentArgs: WidgetArgs by args()
|
private val fragmentArgs: WidgetArgs by args()
|
||||||
private val viewModel: WidgetViewModel by activityViewModel()
|
private val viewModel: WidgetViewModel by activityViewModel()
|
||||||
@ -127,6 +131,12 @@ class WidgetFragment :
|
|||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||||
configureAudioDevice()
|
configureAudioDevice()
|
||||||
}
|
}
|
||||||
|
views.widgetBluetoothListRecyclerView.configureWith(bluetoothLowEnergyDevicesBottomSheetController, hasFixedSize = false)
|
||||||
|
bluetoothLowEnergyDevicesBottomSheetController.callback = object : BluetoothLowEnergyDevicesBottomSheetController.Callback {
|
||||||
|
override fun onItemSelected(deviceAddress: String) {
|
||||||
|
onBluetoothDeviceSelected(deviceAddress)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
viewModel.observeViewEvents {
|
viewModel.observeViewEvents {
|
||||||
@ -175,6 +185,7 @@ class WidgetFragment :
|
|||||||
viewModel.getPostAPIMediator().clearWebView()
|
viewModel.getPostAPIMediator().clearWebView()
|
||||||
}
|
}
|
||||||
views.widgetWebView.clearAfterWidget()
|
views.widgetWebView.clearAfterWidget()
|
||||||
|
views.widgetBluetoothListRecyclerView.cleanup()
|
||||||
super.onDestroyView()
|
super.onDestroyView()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -201,7 +212,8 @@ class WidgetFragment :
|
|||||||
override fun handlePrepareMenu(menu: Menu) {
|
override fun handlePrepareMenu(menu: Menu) {
|
||||||
withState(viewModel) { state ->
|
withState(viewModel) { state ->
|
||||||
val widget = state.asyncWidget()
|
val widget = state.asyncWidget()
|
||||||
menu.findItem(R.id.action_edit)?.isVisible = state.widgetKind != WidgetKind.INTEGRATION_MANAGER
|
menu.findItem(R.id.action_edit)?.isVisible = state.widgetKind !in listOf(WidgetKind.INTEGRATION_MANAGER, WidgetKind.ELEMENT_CALL)
|
||||||
|
menu.findItem(R.id.action_push_to_talk)?.isVisible = state.widgetKind == WidgetKind.ELEMENT_CALL
|
||||||
if (widget == null) {
|
if (widget == null) {
|
||||||
menu.findItem(R.id.action_refresh)?.isVisible = false
|
menu.findItem(R.id.action_refresh)?.isVisible = false
|
||||||
menu.findItem(R.id.action_widget_open_ext)?.isVisible = false
|
menu.findItem(R.id.action_widget_open_ext)?.isVisible = false
|
||||||
@ -251,6 +263,10 @@ class WidgetFragment :
|
|||||||
}
|
}
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
R.id.action_push_to_talk -> {
|
||||||
|
showBluetoothLowEnergyDevicesBottomSheet()
|
||||||
|
true
|
||||||
|
}
|
||||||
else -> false
|
else -> false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -413,15 +429,12 @@ class WidgetFragment :
|
|||||||
viewModel.handle(WidgetAction.RevokeWidget)
|
viewModel.handle(WidgetAction.RevokeWidget)
|
||||||
}
|
}
|
||||||
|
|
||||||
private var deviceListDialog: AlertDialog? = null
|
|
||||||
|
|
||||||
private fun startBluetoothScanning() {
|
private fun startBluetoothScanning() {
|
||||||
val deviceListDialogBuilder = MaterialAlertDialogBuilder(requireContext())
|
|
||||||
val bluetoothDevices = mutableListOf<BluetoothDevice>()
|
val bluetoothDevices = mutableListOf<BluetoothDevice>()
|
||||||
|
|
||||||
bluetoothLowEnergyDeviceScanner.callback = object : BluetoothLowEnergyDeviceScanner.Callback {
|
bluetoothLowEnergyDeviceScanner.callback = object : BluetoothLowEnergyDeviceScanner.Callback {
|
||||||
override fun onPairedDeviceFound(device: BluetoothDevice) {
|
override fun onPairedDeviceFound(device: BluetoothDevice) {
|
||||||
onBluetoothDeviceSelected(device)
|
onBluetoothDeviceSelected(device.address)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onScanResult(device: BluetoothDevice) {
|
override fun onScanResult(device: BluetoothDevice) {
|
||||||
@ -432,24 +445,28 @@ class WidgetFragment :
|
|||||||
|
|
||||||
bluetoothDevices.add(device)
|
bluetoothDevices.add(device)
|
||||||
|
|
||||||
deviceListDialogBuilder.setItems(
|
bluetoothLowEnergyDevicesBottomSheetController.setData(
|
||||||
bluetoothDevices.map { it.name + " " + it.address }.toTypedArray()
|
bluetoothDevices.map {
|
||||||
) { _, which ->
|
BluetoothLowEnergyDevice(
|
||||||
Timber.d("### WidgetFragment. $which selected")
|
name = it.name,
|
||||||
onBluetoothDeviceSelected(bluetoothDevices[which])
|
macAddress = it.address,
|
||||||
}
|
isConnected = it.bondState == BluetoothDevice.BOND_BONDED
|
||||||
|
)
|
||||||
if (deviceListDialog?.isShowing.orFalse()) {
|
}
|
||||||
deviceListDialog?.dismiss()
|
)
|
||||||
}
|
|
||||||
deviceListDialog = deviceListDialogBuilder.show()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
bluetoothLowEnergyDeviceScanner.startScanning()
|
bluetoothLowEnergyDeviceScanner.startScanning()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onBluetoothDeviceSelected(device: BluetoothDevice) {
|
private fun showBluetoothLowEnergyDevicesBottomSheet() {
|
||||||
viewModel.handle(WidgetAction.ConnectToBluetoothDevice(device))
|
bluetoothLowEnergyDeviceScanner.startScanning()
|
||||||
|
views.bottomSheet.isVisible = true
|
||||||
|
BottomSheetBehavior.from(views.bottomSheet).state = BottomSheetBehavior.STATE_HALF_EXPANDED
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onBluetoothDeviceSelected(deviceAddress: String) {
|
||||||
|
viewModel.handle(WidgetAction.ConnectToBluetoothDevice(deviceAddress))
|
||||||
|
|
||||||
Intent(requireContext(), BluetoothLowEnergyService::class.java).also {
|
Intent(requireContext(), BluetoothLowEnergyService::class.java).also {
|
||||||
ContextCompat.startForegroundService(requireContext(), it)
|
ContextCompat.startForegroundService(requireContext(), it)
|
||||||
|
|||||||
@ -161,7 +161,7 @@ class WidgetViewModel @AssistedInject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun handleConnectToBluetoothDevice(action: WidgetAction.ConnectToBluetoothDevice) {
|
private fun handleConnectToBluetoothDevice(action: WidgetAction.ConnectToBluetoothDevice) {
|
||||||
bluetoothLowEnergyServiceConnection.bind(action.device, this)
|
bluetoothLowEnergyServiceConnection.bind(action.deviceAddress, this)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleCloseWidget() {
|
private fun handleCloseWidget() {
|
||||||
|
|||||||
@ -34,7 +34,7 @@ enum class WidgetKind(@StringRes val nameRes: Int, val screenId: String?) {
|
|||||||
ROOM(R.string.room_widget_activity_title, null),
|
ROOM(R.string.room_widget_activity_title, null),
|
||||||
STICKER_PICKER(R.string.title_activity_choose_sticker, WidgetType.StickerPicker.preferred),
|
STICKER_PICKER(R.string.title_activity_choose_sticker, WidgetType.StickerPicker.preferred),
|
||||||
INTEGRATION_MANAGER(0, null),
|
INTEGRATION_MANAGER(0, null),
|
||||||
ELEMENT_CALL(0, null);
|
ELEMENT_CALL(R.string.push_to_talk_activity_title, null);
|
||||||
|
|
||||||
fun isAdmin(): Boolean {
|
fun isAdmin(): Boolean {
|
||||||
return this == STICKER_PICKER || this == INTEGRATION_MANAGER
|
return this == STICKER_PICKER || this == INTEGRATION_MANAGER
|
||||||
|
|||||||
@ -0,0 +1,23 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2022 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.app.features.widgets.ptt
|
||||||
|
|
||||||
|
data class BluetoothLowEnergyDevice(
|
||||||
|
val name: String,
|
||||||
|
val macAddress: String?,
|
||||||
|
val isConnected: Boolean,
|
||||||
|
)
|
||||||
@ -0,0 +1,76 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2022 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.app.features.widgets.ptt
|
||||||
|
|
||||||
|
import android.widget.TextView
|
||||||
|
import androidx.annotation.ColorInt
|
||||||
|
import com.airbnb.epoxy.EpoxyAttribute
|
||||||
|
import com.airbnb.epoxy.EpoxyModelClass
|
||||||
|
import im.vector.app.R
|
||||||
|
import im.vector.app.core.epoxy.VectorEpoxyHolder
|
||||||
|
import im.vector.app.core.epoxy.VectorEpoxyModel
|
||||||
|
import im.vector.app.core.extensions.setTextOrHide
|
||||||
|
import im.vector.app.features.themes.ThemeUtils
|
||||||
|
|
||||||
|
@EpoxyModelClass
|
||||||
|
abstract class BluetoothLowEnergyDeviceItem : VectorEpoxyModel<BluetoothLowEnergyDeviceItem.Holder>(R.layout.item_bluetooth_device) {
|
||||||
|
|
||||||
|
interface Callback {
|
||||||
|
fun onItemSelected(deviceAddress: String)
|
||||||
|
}
|
||||||
|
|
||||||
|
@EpoxyAttribute
|
||||||
|
var deviceName: String? = null
|
||||||
|
|
||||||
|
@EpoxyAttribute
|
||||||
|
var deviceMacAddress: String? = null
|
||||||
|
|
||||||
|
@EpoxyAttribute
|
||||||
|
var deviceConnectionStatusText: String? = null
|
||||||
|
|
||||||
|
@EpoxyAttribute
|
||||||
|
@ColorInt
|
||||||
|
var deviceConnectionStatusTextColor: Int? = null
|
||||||
|
|
||||||
|
@EpoxyAttribute
|
||||||
|
var callback: Callback? = null
|
||||||
|
|
||||||
|
override fun bind(holder: Holder) {
|
||||||
|
super.bind(holder)
|
||||||
|
holder.bluetoothDeviceNameTextView.setTextOrHide(deviceName)
|
||||||
|
holder.bluetoothDeviceMacAddressTextView.setTextOrHide(deviceMacAddress)
|
||||||
|
holder.bluetoothDeviceConnectionStatusTextView.setTextOrHide(deviceConnectionStatusText)
|
||||||
|
|
||||||
|
deviceConnectionStatusTextColor?.let {
|
||||||
|
holder.bluetoothDeviceConnectionStatusTextView.setTextColor(it)
|
||||||
|
} ?: run {
|
||||||
|
holder.bluetoothDeviceConnectionStatusTextView.setTextColor(ThemeUtils.getColor(holder.view.context, R.attr.vctr_content_primary))
|
||||||
|
}
|
||||||
|
|
||||||
|
holder.view.setOnClickListener {
|
||||||
|
deviceMacAddress?.let {
|
||||||
|
callback?.onItemSelected(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Holder : VectorEpoxyHolder() {
|
||||||
|
val bluetoothDeviceNameTextView by bind<TextView>(R.id.bluetoothDeviceNameTextView)
|
||||||
|
val bluetoothDeviceMacAddressTextView by bind<TextView>(R.id.bluetoothDeviceMacAddressTextView)
|
||||||
|
val bluetoothDeviceConnectionStatusTextView by bind<TextView>(R.id.bluetoothDeviceConnectionStatusTextView)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -48,6 +48,7 @@ class BluetoothLowEnergyDeviceScanner @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun startScanning() {
|
fun startScanning() {
|
||||||
|
stopScanning()
|
||||||
bluetoothManager
|
bluetoothManager
|
||||||
?.adapter
|
?.adapter
|
||||||
?.bondedDevices
|
?.bondedDevices
|
||||||
|
|||||||
@ -0,0 +1,70 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2022 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.app.features.widgets.ptt
|
||||||
|
|
||||||
|
import com.airbnb.epoxy.EpoxyController
|
||||||
|
import im.vector.app.R
|
||||||
|
import im.vector.app.core.resources.ColorProvider
|
||||||
|
import im.vector.app.core.resources.StringProvider
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class BluetoothLowEnergyDevicesBottomSheetController @Inject constructor(
|
||||||
|
private val stringProvider: StringProvider,
|
||||||
|
private val colorProvider: ColorProvider,
|
||||||
|
) : EpoxyController() {
|
||||||
|
|
||||||
|
interface Callback {
|
||||||
|
fun onItemSelected(deviceAddress: String)
|
||||||
|
}
|
||||||
|
|
||||||
|
private var deviceList: List<BluetoothLowEnergyDevice>? = null
|
||||||
|
var callback: Callback? = null
|
||||||
|
|
||||||
|
fun setData(deviceList: List<BluetoothLowEnergyDevice>) {
|
||||||
|
this.deviceList = deviceList
|
||||||
|
requestModelBuild()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun buildModels() {
|
||||||
|
val currentDeviceList = deviceList ?: return
|
||||||
|
val host = this
|
||||||
|
|
||||||
|
currentDeviceList.forEach { device ->
|
||||||
|
val deviceConnectionStatus = host.stringProvider.getString(
|
||||||
|
if (device.isConnected) R.string.push_to_talk_device_connected else R.string.push_to_talk_device_disconnected
|
||||||
|
)
|
||||||
|
val deviceConnectionStatusColor = host.colorProvider.getColorFromAttribute(
|
||||||
|
if (device.isConnected) R.attr.colorPrimary else R.attr.colorError
|
||||||
|
)
|
||||||
|
|
||||||
|
val deviceItemCallback = object : BluetoothLowEnergyDeviceItem.Callback {
|
||||||
|
override fun onItemSelected(deviceAddress: String) {
|
||||||
|
host.callback?.onItemSelected(deviceAddress)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bluetoothLowEnergyDeviceItem {
|
||||||
|
id(device.hashCode())
|
||||||
|
deviceName(device.name)
|
||||||
|
deviceMacAddress(device.macAddress)
|
||||||
|
deviceConnectionStatusText(deviceConnectionStatus)
|
||||||
|
deviceConnectionStatusTextColor(deviceConnectionStatusColor)
|
||||||
|
callback(deviceItemCallback)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -16,7 +16,6 @@
|
|||||||
|
|
||||||
package im.vector.app.features.widgets.ptt
|
package im.vector.app.features.widgets.ptt
|
||||||
|
|
||||||
import android.bluetooth.BluetoothDevice
|
|
||||||
import android.content.ComponentName
|
import android.content.ComponentName
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
@ -34,12 +33,12 @@ class BluetoothLowEnergyServiceConnection @Inject constructor(
|
|||||||
|
|
||||||
private var isBound = false
|
private var isBound = false
|
||||||
private var bluetoothLowEnergyService: BluetoothLowEnergyService? = null
|
private var bluetoothLowEnergyService: BluetoothLowEnergyService? = null
|
||||||
private var bluetoothDevice: BluetoothDevice? = null
|
private var deviceAddress: String? = null
|
||||||
|
|
||||||
var callback: Callback? = null
|
var callback: Callback? = null
|
||||||
|
|
||||||
fun bind(device: BluetoothDevice, callback: Callback) {
|
fun bind(deviceAddress: String, callback: Callback) {
|
||||||
this.bluetoothDevice = device
|
this.deviceAddress = deviceAddress
|
||||||
this.callback = callback
|
this.callback = callback
|
||||||
|
|
||||||
if (!isBound) {
|
if (!isBound) {
|
||||||
@ -54,7 +53,7 @@ class BluetoothLowEnergyServiceConnection @Inject constructor(
|
|||||||
it.callback = this
|
it.callback = this
|
||||||
}
|
}
|
||||||
|
|
||||||
bluetoothDevice?.address?.let {
|
deviceAddress?.let {
|
||||||
bluetoothLowEnergyService?.connect(it)
|
bluetoothLowEnergyService?.connect(it)
|
||||||
}
|
}
|
||||||
isBound = true
|
isBound = true
|
||||||
|
|||||||
@ -1,53 +1,91 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
<WebView
|
<RelativeLayout
|
||||||
android:id="@+id/widgetWebView"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent">
|
||||||
android:layout_alignParentBottom="true"
|
|
||||||
android:layout_marginBottom="0dp"
|
|
||||||
android:background="@android:color/transparent" />
|
|
||||||
|
|
||||||
<ProgressBar
|
<WebView
|
||||||
android:id="@+id/widgetProgressBar"
|
android:id="@+id/widgetWebView"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="match_parent"
|
||||||
android:layout_centerInParent="true"
|
android:layout_alignParentBottom="true"
|
||||||
android:indeterminate="true" />
|
android:layout_marginBottom="0dp"
|
||||||
|
android:background="@android:color/transparent" />
|
||||||
|
|
||||||
<LinearLayout
|
<ProgressBar
|
||||||
android:id="@+id/widgetErrorLayout"
|
android:id="@+id/widgetProgressBar"
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_alignParentTop="true"
|
|
||||||
android:background="?colorSurface"
|
|
||||||
android:orientation="horizontal"
|
|
||||||
android:padding="16dp"
|
|
||||||
android:visibility="gone"
|
|
||||||
tools:visibility="visible">
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:layout_width="40dp"
|
|
||||||
android:layout_height="40dp"
|
|
||||||
android:layout_gravity="center"
|
|
||||||
android:importantForAccessibility="no"
|
|
||||||
android:src="@drawable/error" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/widgetErrorText"
|
|
||||||
style="@style/Widget.Vector.TextView.Subtitle"
|
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="center"
|
android:layout_centerInParent="true"
|
||||||
android:layout_marginStart="@dimen/layout_horizontal_margin"
|
android:indeterminate="true" />
|
||||||
android:layout_marginEnd="@dimen/layout_horizontal_margin"
|
|
||||||
android:textColor="?vctr_content_primary"
|
<LinearLayout
|
||||||
android:textStyle="bold"
|
android:id="@+id/widgetErrorLayout"
|
||||||
tools:text="Fail to load widget " />
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_alignParentTop="true"
|
||||||
|
android:background="?colorSurface"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:padding="16dp"
|
||||||
|
android:visibility="gone"
|
||||||
|
tools:visibility="visible">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="40dp"
|
||||||
|
android:layout_height="40dp"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:importantForAccessibility="no"
|
||||||
|
android:src="@drawable/error" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/widgetErrorText"
|
||||||
|
style="@style/Widget.Vector.TextView.Subtitle"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:layout_marginStart="@dimen/layout_horizontal_margin"
|
||||||
|
android:layout_marginEnd="@dimen/layout_horizontal_margin"
|
||||||
|
android:textColor="?vctr_content_primary"
|
||||||
|
android:textStyle="bold"
|
||||||
|
tools:text="Fail to load widget " />
|
||||||
|
</LinearLayout>
|
||||||
|
</RelativeLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/bottomSheet"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:background="?vctr_system"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:behavior_hideable="true"
|
||||||
|
app:behavior_peekHeight="200dp"
|
||||||
|
app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
style="@style/TextAppearance.Vector.Headline.Medium"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginVertical="30dp"
|
||||||
|
android:paddingHorizontal="16dp"
|
||||||
|
android:text="@string/push_to_talk_bottom_sheet_title" />
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="1dp"
|
||||||
|
android:background="?vctr_list_separator" />
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/widgetBluetoothListRecyclerView"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
tools:listitem="@layout/item_bluetooth_device" />
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
</RelativeLayout>
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||||
|
|||||||
40
vector/src/main/res/layout/item_bluetooth_device.xml
Normal file
40
vector/src/main/res/layout/item_bluetooth_device.xml
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/bluetoothDeviceNameTextView"
|
||||||
|
style="@style/TextAppearance.Vector.Body"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginHorizontal="16dp"
|
||||||
|
android:layout_marginTop="30dp"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
tools:text="Device 1" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/bluetoothDeviceMacAddressTextView"
|
||||||
|
style="@style/TextAppearance.Vector.Caption"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:layout_constraintStart_toStartOf="@id/bluetoothDeviceNameTextView"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/bluetoothDeviceNameTextView"
|
||||||
|
tools:text="00:1B:44:11:3A:B7" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/bluetoothDeviceConnectionStatusTextView"
|
||||||
|
style="@style/TextAppearance.Vector.Body"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginEnd="16dp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@id/bluetoothDeviceMacAddressTextView"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="@id/bluetoothDeviceNameTextView"
|
||||||
|
tools:text="Disconnected"
|
||||||
|
tools:textColor="?colorError" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
@ -27,4 +27,10 @@
|
|||||||
android:title="@string/room_widget_revoke_access"
|
android:title="@string/room_widget_revoke_access"
|
||||||
app:showAsAction="never" />
|
app:showAsAction="never" />
|
||||||
|
|
||||||
</menu>
|
<item
|
||||||
|
android:id="@+id/action_push_to_talk"
|
||||||
|
android:title="@string/action_push_to_talk_configure_device"
|
||||||
|
android:icon="@drawable/quantum_ic_bluetooth_audio_grey600_24"
|
||||||
|
app:showAsAction="ifRoom" />
|
||||||
|
|
||||||
|
</menu>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user