Commit acc93db9 authored by Adimote's avatar Adimote

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
......@@ -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
......@@ -34,7 +34,7 @@ data class Device(val btMac: String, val name: String?, val paired: Boolean) {
return true
}
override fun hashCode(): Int{
override fun hashCode(): Int {
return Objects.hashCode(btMac)
}
}
......@@ -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.add(device)
}
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_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
android:layout_height="match_parent"/>
<FrameLayout
android:id="@+id/scanner_modal_scrim"
android:layout_width="match_parent"
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"
android:layout_width="match_parent"
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:foreground="?android:attr/selectableItemBackground"
android:padding="16dp"
android:orientation="horizontal">
<LinearLayout
android:layout_width="0px"
android:layout_weight="1"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_gravity="center_horizontal"
android:padding="16dp"
android:foreground="?android:attr/selectableItemBackground">
android:orientation="vertical">
<TextView
<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:textStyle="bold"/>
android:layout_gravity="start"
android:paddingStart="16dp"
android:textStyle="bold" />
<TextView
<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>
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment