Skip to content
Snippets Groups Projects
Commit acc93db9 authored by Adimote's avatar Adimote
Browse files

Update the UI for the device scanner

- Add a modal message for 0 devices showing
- Show the state of the connection next to every device

New 'scanning' screen, which shows when 0 devices are seen:
https://i.imgur.com/qgkox1A.jpg

Status indicators for each device:
https://i.imgur.com/dUI0iCq.jpg
(either 'NOT BONDED', 'BONDING...' or 'BONDED', The lattermost is bold and has green text)
parent ed9053bc
No related branches found
No related tags found
No related merge requests found
Pipeline #3414 passed
......@@ -20,11 +20,11 @@
* SOFTWARE.
*/
package de.ccc.events.badge.card10.scanner;
package de.ccc.events.badge.card10.scanner
import java.util.*
data class Device(val btMac: String, val name: String?, val paired: Boolean) {
data class Device(val btMac: String, val name: String?, val state: Int) {
override fun equals(other: Any?): Boolean{
if (this === other) return true
if (other?.javaClass != javaClass) return false
......
......@@ -22,10 +22,12 @@
package de.ccc.events.badge.card10.scanner
import android.bluetooth.BluetoothDevice
import android.graphics.Typeface
import android.view.LayoutInflater
import android.view.ViewGroup
import android.widget.TextView
import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.RecyclerView
import de.ccc.events.badge.card10.R
......@@ -34,16 +36,22 @@ class DeviceViewHolder(inflater: LayoutInflater, parent: ViewGroup) :
private var nameView: TextView? = null
private var btMacView: TextView? = null
private var statusView: TextView? = null
init {
nameView = itemView.findViewById(R.id.scanner_list_item_name)
btMacView = itemView.findViewById(R.id.scanner_list_item_btmac)
statusView = itemView.findViewById(R.id.scanner_list_item_status)
}
fun bind(device: Device, clickListener: (Device) -> Unit) {
nameView?.text = device.name
btMacView?.text = device.btMac
btMacView?.typeface = if (device.paired) Typeface.DEFAULT_BOLD else Typeface.DEFAULT
val typeface = if (device.state == BluetoothDevice.BOND_BONDED) Typeface.DEFAULT_BOLD else Typeface.DEFAULT
btMacView?.typeface = typeface
statusView?.text = if (device.state == BluetoothDevice.BOND_BONDING) "BONDING..." else if (device.state == BluetoothDevice.BOND_BONDED) "BONDED" else "NOT BONDED"
statusView?.setTextColor( if (device.state == BluetoothDevice.BOND_BONDED) ContextCompat.getColor(itemView.context, R.color.colorStatusConnected) else ContextCompat.getColor(itemView.context, R.color.colorStatusDisconnected))
btMacView?.typeface = typeface
itemView.setOnClickListener {
clickListener(device)
}
......
......@@ -38,9 +38,11 @@ import androidx.fragment.app.Fragment
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProviders
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.transition.Fade
import de.ccc.events.badge.card10.CARD10_BLUETOOTH_MAC_PREFIX
import de.ccc.events.badge.card10.R
import kotlinx.android.synthetic.main.scanner_fragment.*
import androidx.transition.TransitionManager
import de.ccc.events.badge.card10.R
class ScannerFragment : Fragment() {
......@@ -52,45 +54,66 @@ class ScannerFragment : Fragment() {
override fun onScanResult(callbackType: Int, result: ScanResult) {
val device = result.device
if (device.address.startsWith(CARD10_BLUETOOTH_MAC_PREFIX, true))
putToListAdapter(device)
upsertToListAdapter(device)
}
}
private fun putToListAdapter(device: BluetoothDevice) {
listAdapter.put(
private fun upsertToListAdapter(device: BluetoothDevice) {
if (listAdapter.itemCount == 0) {
viewModel.showModal.value = false
}
listAdapter.upsert(
Device(
btMac = device.address,
name = device.name,
paired = device.bondState == BluetoothDevice.BOND_BONDED
state = device.bondState
)
)
}
// TODO: Remove devices if we lose their connection.
val receiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val bondState = intent.extras!!.getInt(BluetoothDevice.EXTRA_BOND_STATE)
val device = intent.extras!!.getParcelable(BluetoothDevice.EXTRA_DEVICE) as BluetoothDevice?
viewModel.status.value =
if (bondState == BluetoothDevice.BOND_NONE) "NONE" else if (bondState == BluetoothDevice.BOND_BONDING) "BONDING" else if (bondState == BluetoothDevice.BOND_BONDED) "BONDED" else null
System.out.println("=== onReceive ${intent.action} ${viewModel.status.value} ${device}")
?: return
val status =
if (device.bondState == BluetoothDevice.BOND_NONE) "NONE" else if (device.bondState == BluetoothDevice.BOND_BONDING) "BONDING" else if (device.bondState == BluetoothDevice.BOND_BONDED) "BONDED" else "NULL"
System.out.println("=== onReceive ${intent.action} ${status} ${device}")
upsertToListAdapter(device)
listAdapter.notifyDataSetChanged()
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewModel.status.observe(
viewModel.modalMessage.observe(
this,
Observer { text -> scanner_status.text = text })
Observer { text -> scanner_modal.text = text })
viewModel.showModal.observe(this, Observer { show ->
val transition = Fade()
transition.duration = 1000
transition.targets.add(scanner_modal_scrim)
TransitionManager.beginDelayedTransition(scanner_device_list, transition)
scanner_modal_scrim.visibility = if (show) View.VISIBLE else View.GONE
scanner_modal_scrim.isClickable = show
scanner_modal_scrim.isFocusable = show
})
activity?.registerReceiver(receiver, IntentFilter(BluetoothDevice.ACTION_BOND_STATE_CHANGED))
bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
for (device in bluetoothAdapter.bondedDevices.filter {
bluetoothAdapter = BluetoothAdapter.getDefaultAdapter()
val devices = bluetoothAdapter.bondedDevices.filter {
it.address.startsWith(
CARD10_BLUETOOTH_MAC_PREFIX,
true
)
}) {
putToListAdapter(device)
}
for (device in devices) {
upsertToListAdapter(device)
}
// TODO: If we the ability to remove devices, show this modal if the list is empty.
if (devices.isEmpty()) {
viewModel.modalMessage.value = context?.resources?.getString(R.string.scan_no_devices)
viewModel.showModal.value = true
}
bluetoothAdapter.bluetoothLeScanner.startScan(callback)
}
......
......@@ -31,7 +31,6 @@ import kotlin.collections.HashSet
class ScannerListAdapter(val clickListener: (Device) -> Unit) : RecyclerView.Adapter<DeviceViewHolder>() {
private val list = LinkedList<Device>()
private val foundDevices = mutableSetOf<String>()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DeviceViewHolder {
val inflater = LayoutInflater.from(parent.context)
......@@ -45,15 +44,12 @@ class ScannerListAdapter(val clickListener: (Device) -> Unit) : RecyclerView.Ada
override fun getItemCount(): Int = list.size
fun put(device: Device) {
if (foundDevices.contains(device.btMac)) {
return
fun upsert(device: Device) {
if (list.contains(device)) {
list[list.indexOf(device)] = device
} else {
foundDevices.add(device.btMac)
}
list.remove(device)
list.add(device)
}
notifyDataSetChanged()
}
}
......@@ -26,5 +26,6 @@ import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
class ScannerViewModel : ViewModel() {
val status = MutableLiveData<String>()
val modalMessage = MutableLiveData<String>()
val showModal = MutableLiveData<Boolean>()
}
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:layout_gravity="center_horizontal"
android:padding="16dp">
>
<androidx.recyclerview.widget.RecyclerView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_margin="16dp"
android:id="@+id/scanner_device_list"
android:layout_weight="1"
android:layout_width="match_parent"
android:layout_height="0px"/>
<TextView android:id="@+id/scanner_status"
android:layout_height="match_parent"/>
<FrameLayout
android:id="@+id/scanner_modal_scrim"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
android:layout_height="match_parent"
android:clickable="true"
android:focusable="true"
android:visibility="gone"
android:background="@color/colorScannerTint">
<TextView android:id="@+id/scanner_modal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:padding="10dp"
android:background="@color/colorScannerBG"
/>
</FrameLayout>
</FrameLayout>
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_gravity="center_horizontal"
android:layout_gravity="center_vertical"
android:foreground="?android:attr/selectableItemBackground"
android:padding="16dp"
android:foreground="?android:attr/selectableItemBackground">
android:orientation="horizontal">
<LinearLayout
android:layout_width="0px"
android:layout_weight="1"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:orientation="vertical">
<TextView
android:id="@+id/scanner_list_item_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="left"
android:paddingLeft="16dp"
android:layout_gravity="start"
android:paddingStart="16dp"
android:textStyle="bold" />
<TextView
android:id="@+id/scanner_list_item_btmac"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="left"
android:paddingLeft="16dp"/>
android:layout_gravity="start"
android:paddingStart="16dp" />
</LinearLayout>
<TextView
android:id="@+id/scanner_list_item_status"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
/>
</LinearLayout>
......@@ -3,4 +3,8 @@
<color name="colorPrimary">#0076ba</color>
<color name="colorPrimaryDark">#005383</color>
<color name="colorAccent">#99ba00</color>
<color name="colorStatusDisconnected">#333333</color>
<color name="colorStatusConnected">#179317</color>
<color name="colorScannerTint">#55000000</color>
<color name="colorScannerBG">#ffffff</color>
</resources>
......@@ -50,4 +50,5 @@
<string name="camp">Camp</string>
<string name="no_contact">No Contact</string>
<string name="off">Off</string>
<string name="scan_no_devices">Scanning for devices…</string>
</resources>
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment