diff --git a/app/src/main/java/de/ccc/events/badge/card10/scanner/Device.kt b/app/src/main/java/de/ccc/events/badge/card10/scanner/Device.kt index e75fa3e26b22e5fccd1a291b7e8b965dff4eb5a2..3f5be1b52920ed2ecf4f9508f0e43a72fc88b520 100644 --- a/app/src/main/java/de/ccc/events/badge/card10/scanner/Device.kt +++ b/app/src/main/java/de/ccc/events/badge/card10/scanner/Device.kt @@ -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) } } diff --git a/app/src/main/java/de/ccc/events/badge/card10/scanner/DeviceViewHolder.kt b/app/src/main/java/de/ccc/events/badge/card10/scanner/DeviceViewHolder.kt index c43e0c8e661765ccdf267754d42d516a42418cf7..ed9b70cdcbdf877a2bfc51165fd16fed45af14f2 100644 --- a/app/src/main/java/de/ccc/events/badge/card10/scanner/DeviceViewHolder.kt +++ b/app/src/main/java/de/ccc/events/badge/card10/scanner/DeviceViewHolder.kt @@ -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) } diff --git a/app/src/main/java/de/ccc/events/badge/card10/scanner/ScannerFragment.kt b/app/src/main/java/de/ccc/events/badge/card10/scanner/ScannerFragment.kt index 6cf743f8a98ef98caae815936d0871b2c3bd77ff..962a9f769cf0e215f218258ca16f6c37d2055db6 100644 --- a/app/src/main/java/de/ccc/events/badge/card10/scanner/ScannerFragment.kt +++ b/app/src/main/java/de/ccc/events/badge/card10/scanner/ScannerFragment.kt @@ -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) } diff --git a/app/src/main/java/de/ccc/events/badge/card10/scanner/ScannerListAdapter.kt b/app/src/main/java/de/ccc/events/badge/card10/scanner/ScannerListAdapter.kt index b9deeaf9f3f44cf4d3791c21ba6400e3fc810276..0b5b391bef0388074db69d1a71e2822a23d00bd2 100644 --- a/app/src/main/java/de/ccc/events/badge/card10/scanner/ScannerListAdapter.kt +++ b/app/src/main/java/de/ccc/events/badge/card10/scanner/ScannerListAdapter.kt @@ -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() } } diff --git a/app/src/main/java/de/ccc/events/badge/card10/scanner/ScannerViewModel.kt b/app/src/main/java/de/ccc/events/badge/card10/scanner/ScannerViewModel.kt index 8e54e05c8d78d9f50cdabd15184e3709f06ccabc..87d293f039d5a8c5a28827c6ff10b33d1c146f33 100644 --- a/app/src/main/java/de/ccc/events/badge/card10/scanner/ScannerViewModel.kt +++ b/app/src/main/java/de/ccc/events/badge/card10/scanner/ScannerViewModel.kt @@ -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 diff --git a/app/src/main/res/layout/scanner_fragment.xml b/app/src/main/res/layout/scanner_fragment.xml index 60c06373b2adf1b9beabdc01bda5d798b2d8bb94..858c8e3cfc778f0adb7d942d3579fab158117c2b 100644 --- a/app/src/main/res/layout/scanner_fragment.xml +++ b/app/src/main/res/layout/scanner_fragment.xml @@ -1,18 +1,30 @@ <?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> diff --git a/app/src/main/res/layout/scanner_list_item.xml b/app/src/main/res/layout/scanner_list_item.xml index 24af45b3a7b1b786b3c7a103129240e0eeeb06ad..d6a776015a39cdc4fb2b533f7bd9a5c47dac6bf7 100644 --- a/app/src/main/res/layout/scanner_list_item.xml +++ b/app/src/main/res/layout/scanner_list_item.xml @@ -1,26 +1,39 @@ <?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> diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 315755b4d63c6ae563f9472947e7309e470e325d..8bc6760f710f6c9c8def722fd9a8f0609c6aeb3e 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -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> diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index d789f84e375b0f57adbc40d64da93bd5b7a76dd0..f93509009701983f07924266a9a59a2d2628d075 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -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>