diff --git a/app/build.gradle b/app/build.gradle index 0fc4319..92a3e42 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -46,4 +46,5 @@ dependencies { testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test.ext:junit:1.1.3' androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' + implementation 'androidx.recyclerview:recyclerview:1.1.0' } \ No newline at end of file diff --git a/app/src/androidTest/java/com/example/sensortestingapp/ExampleInstrumentedTest.kt b/app/src/androidTest/java/com/example/sensortestingapp/ExampleInstrumentedTest.kt index f6bbbe5..2d81b84 100644 --- a/app/src/androidTest/java/com/example/sensortestingapp/ExampleInstrumentedTest.kt +++ b/app/src/androidTest/java/com/example/sensortestingapp/ExampleInstrumentedTest.kt @@ -1,13 +1,11 @@ package com.example.sensortestingapp -import androidx.test.platform.app.InstrumentationRegistry import androidx.test.ext.junit.runners.AndroidJUnit4 - +import androidx.test.platform.app.InstrumentationRegistry +import org.junit.Assert.* import org.junit.Test import org.junit.runner.RunWith -import org.junit.Assert.* - /** * Instrumented test, which will execute on an Android device. * diff --git a/app/src/main/java/com/example/sensortestingapp/MainActivity.kt b/app/src/main/java/com/example/sensortestingapp/MainActivity.kt index 6c24755..6517ff8 100644 --- a/app/src/main/java/com/example/sensortestingapp/MainActivity.kt +++ b/app/src/main/java/com/example/sensortestingapp/MainActivity.kt @@ -22,9 +22,11 @@ import androidx.appcompat.app.AppCompatActivity import androidx.core.app.ActivityCompat import androidx.core.content.ContextCompat import androidx.core.view.WindowCompat +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import androidx.recyclerview.widget.SimpleItemAnimator import com.example.sensortestingapp.databinding.ActivityMainBinding -import java.util.stream.Collectors -import java.util.stream.Collectors.toList + private const val ENABLE_BLUETOOTH_REQUEST_CODE = 1 @@ -70,7 +72,13 @@ class MainActivity : AppCompatActivity() { } } - private var scanResults = HashMap(); + private var kirbyScanResults = ArrayList(); + + private val scanResultAdapter: ScanResultAdapter by lazy { + ScanResultAdapter(kirbyScanResults) { + // TODO: Implement + } + } override fun onCreate(savedInstanceState: Bundle?) { WindowCompat.setDecorFitsSystemWindows(window, false) @@ -82,6 +90,7 @@ class MainActivity : AppCompatActivity() { setSupportActionBar(binding.toolbar) + binding.fab.setOnClickListener { view -> if (!isScanning) { startBleScan() @@ -92,6 +101,26 @@ class MainActivity : AppCompatActivity() { // .setAnchorView(R.id.fab) // .setAction("Action", null).show() } + + setupRecyclerView() + + } + + private fun setupRecyclerView() { + binding.scanResultsRecyclerView.apply { + adapter = scanResultAdapter + layoutManager = LinearLayoutManager( + this@MainActivity, + RecyclerView.VERTICAL, + false + ) + isNestedScrollingEnabled = false + } + + val animator = binding.scanResultsRecyclerView.itemAnimator + if (animator is SimpleItemAnimator) { + animator.supportsChangeAnimations = false + } } override fun onCreateOptionsMenu(menu: Menu): Boolean { @@ -178,18 +207,25 @@ class MainActivity : AppCompatActivity() { @SuppressLint("MissingPermission") val scanCallback = object : ScanCallback() { override fun onScanResult(callbackType: Int, result: ScanResult) { - - scanResults.merge(result.device.address, result, fun(o, n): ScanResult { - return if (n.rssi >= o.rssi) n else o; - }) - val displayScanResults = getDisplayScanResults(); - Log.i( + Log.d( "ScanCallback", - "Found BLE device ${result.device.name ?: result.device.address} (rssi: ${result.rssi}). Current scan results (${displayScanResults.size}): ${ - toDebugScanResults(displayScanResults) - }" + "Found BLE device with address ${result.device.address} (name: ${result.device.name}, rssi: ${result.rssi})" ) - + if ((result.device.name ?: "").lowercase().contains("kirby")) { + Log.i( + "ScanCallback", + "Found Kirby device with name ${result.device.name} (address: ${result.device.address}, rssi: ${result.rssi})" + ) + val index = + kirbyScanResults.indexOfFirst { r -> r.device.address == result.device.address } + if (index == -1) { + kirbyScanResults.add(result); + scanResultAdapter.notifyItemInserted(index) + } else { + kirbyScanResults[index] = result + scanResultAdapter.notifyItemChanged(index) + } + } } override fun onScanFailed(errorCode: Int) { @@ -197,25 +233,12 @@ class MainActivity : AppCompatActivity() { } } - private fun getDisplayScanResults(): List { - return scanResults.values.stream() - .filter { r -> r.rssi > -80 } - .sorted { r1, r2 -> Integer.compare(r2.rssi, r1.rssi) } - .collect(toList()) - } - - private fun toDebugScanResults(scanResults: List): String { - return scanResults.stream() - .map { r -> "${r.device.name ?: r.device.address} (${r.rssi})" } - .collect(Collectors.joining(", ")) - } - - private fun startBleScan() { if (!hasRequiredRuntimePermissions()) { requestRelevantRuntimePermissions() } else { - scanResults.clear() + kirbyScanResults.clear() + scanResultAdapter.notifyDataSetChanged() bleScanner.startScan(null, scanSettings, scanCallback) isScanning = true } diff --git a/app/src/main/java/com/example/sensortestingapp/ScanResultAdapter.kt b/app/src/main/java/com/example/sensortestingapp/ScanResultAdapter.kt new file mode 100644 index 0000000..65bce61 --- /dev/null +++ b/app/src/main/java/com/example/sensortestingapp/ScanResultAdapter.kt @@ -0,0 +1,64 @@ +package com.example.sensortestingapp + +import android.annotation.SuppressLint +import android.bluetooth.le.ScanResult +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.TextView +import androidx.recyclerview.widget.RecyclerView + +@SuppressLint("MissingPermission") +class ScanResultAdapter( + private val items: List, + private val onClickListener: ((device: ScanResult) -> Unit) +) : RecyclerView.Adapter() { + + /** + * Provide a reference to the type of views that you are using + * (custom ViewHolder) + */ + class ViewHolder( + private val view: View, + private val onClickListener: ((device: ScanResult) -> Unit) + ) : RecyclerView.ViewHolder(view) { + + val deviceNameView: TextView + val macAddressView: TextView + val signalStrengthView: TextView + + init { + deviceNameView = view.findViewById(R.id.device_name) + macAddressView = view.findViewById(R.id.mac_address) + signalStrengthView = view.findViewById(R.id.signal_strength) + } + + fun bind(result: ScanResult) { + deviceNameView.text = result.device.name ?: "Unnamed" + macAddressView.text = result.device.address + signalStrengthView.text = "${result.rssi} dBm" + view.setOnClickListener { onClickListener.invoke(result) } + } + } + + override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): ViewHolder { + // Create a new view, which defines the UI of the list item + val view = LayoutInflater.from(viewGroup.context) + .inflate(R.layout.row_scan_result, viewGroup, false) + + return ViewHolder(view, onClickListener) + } + + override fun getItemCount() = items.size + + // Replace the contents of a view (invoked by the layout manager) + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + + // Get element from your dataset at this position and replace the + // contents of the view with that element + val item = items[position] + holder.bind(item) + } + + +} \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 4a9d98d..5d660b1 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -17,8 +17,21 @@ android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" /> + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/row_scan_result.xml b/app/src/main/res/layout/row_scan_result.xml new file mode 100644 index 0000000..c978ac4 --- /dev/null +++ b/app/src/main/res/layout/row_scan_result.xml @@ -0,0 +1,39 @@ + + + + + + + + + + \ No newline at end of file