From a48cc91680c11e99a3ea72b9d198a6e5d30a04e7 Mon Sep 17 00:00:00 2001 From: n0tst3v3 Date: Wed, 19 Aug 2026 00:56:47 -0600 Subject: [PATCH] feat(list): sort tracked items by last seen with reverse toggle --- .../java/com/wytehat/btlogger/DeviceSort.java | 121 ++++++++++++++++++ .../com/wytehat/btlogger/MainActivity.java | 29 ++++- app/src/main/res/layout/main.xml | 5 +- 3 files changed, 153 insertions(+), 2 deletions(-) create mode 100644 app/src/main/java/com/wytehat/btlogger/DeviceSort.java diff --git a/app/src/main/java/com/wytehat/btlogger/DeviceSort.java b/app/src/main/java/com/wytehat/btlogger/DeviceSort.java new file mode 100644 index 0000000..f48162d --- /dev/null +++ b/app/src/main/java/com/wytehat/btlogger/DeviceSort.java @@ -0,0 +1,121 @@ +package com.wytehat.btlogger; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +/** + * Ordering for the tracked item list. + * + * Default order is newest sighting first. Items that have not been seen for + * a long time - or that carry no sighting timestamp at all - are pushed to + * the bottom and stay there in both sort directions, so the reverse toggle + * never buries a live item under a pile of stale ones. + */ +final class DeviceSort { + + /** A device unseen for longer than this sinks to the bottom. */ + static final long STALE_AFTER_MS = 24L * 60L * 60L * 1000L; + + private DeviceSort() { + } + + /** + * Last moment the device was actually observed. Deliberately ignores + * updatedAt, which also moves when the user edits a name or colour. + */ + static long lastSeen(DeviceRecord record) { + + if (record == null) { + return 0L; + } + + long seen = record.lastRssiAt; + + if (record.connectedAt > seen) { + seen = record.connectedAt; + } + + if (record.disconnectedAt > seen) { + seen = record.disconnectedAt; + } + + return seen; + } + + static boolean isStale(DeviceRecord record, long now) { + + long seen = lastSeen(record); + + return seen <= 0L || + now - seen > STALE_AFTER_MS; + } + + /** + * @param reverse false = newest first (default), true = oldest first. + */ + static List sorted(List rows, boolean reverse) { + + ArrayList fresh = new ArrayList(); + ArrayList stale = new ArrayList(); + + if (rows == null) { + return fresh; + } + + long now = System.currentTimeMillis(); + + for (int i = 0; i < rows.size(); i++) { + + DeviceRecord record = rows.get(i); + + if (record == null) { + continue; + } + + if (isStale(record, now)) { + stale.add(record); + } else { + fresh.add(record); + } + } + + Comparator newestFirst = + new Comparator() { + + public int compare(DeviceRecord left, DeviceRecord right) { + + long a = lastSeen(left); + long b = lastSeen(right); + + if (a == b) { + return left.displayName() + .compareToIgnoreCase(right.displayName()); + } + + return a > b ? -1 : 1; + } + }; + + Collections.sort(fresh, newestFirst); + Collections.sort(stale, newestFirst); + + if (reverse) { + Collections.reverse(fresh); + Collections.reverse(stale); + } + + ArrayList result = + new ArrayList(fresh.size() + stale.size()); + + result.addAll(fresh); + + /* + * Stale entries are appended last in both directions. + */ + result.addAll(stale); + + return result; + } +} diff --git a/app/src/main/java/com/wytehat/btlogger/MainActivity.java b/app/src/main/java/com/wytehat/btlogger/MainActivity.java index e254c03..7eebb2a 100644 --- a/app/src/main/java/com/wytehat/btlogger/MainActivity.java +++ b/app/src/main/java/com/wytehat/btlogger/MainActivity.java @@ -17,6 +17,7 @@ public class MainActivity extends Activity implements ItemActionListener { private static final int CAMERA = 200; private TrackerDatabase db; private DeviceListAdapter adapter; + private boolean reverseSort; private boolean registered; private DeviceRecord editing; private final Handler refreshHandler = new Handler(); @@ -46,6 +47,18 @@ public class MainActivity extends Activity implements ItemActionListener { public void onClick(View v) { startActivity(new Intent(MainActivity.this, MapActivity.class)); } }); + reverseSort = getSharedPreferences("list_settings", 0) + .getBoolean("reverse_sort", false); + updateSortButton(); + findViewById(R.id.sort_button).setOnClickListener(new View.OnClickListener() { + public void onClick(View v) { + reverseSort = !reverseSort; + getSharedPreferences("list_settings", 0).edit() + .putBoolean("reverse_sort", reverseSort).apply(); + updateSortButton(); + reload(); + } + }); findViewById(R.id.settings_button).setOnClickListener(new View.OnClickListener() { public void onClick(View v) { showSettings(); } }); @@ -347,7 +360,21 @@ public class MainActivity extends Activity implements ItemActionListener { PackageManager.PERMISSION_GRANTED) list.add(permission); } - private void reload() { if (adapter != null) adapter.setRows(db.tracked()); } + private void reload() { + if (adapter == null) return; + adapter.setRows(DeviceSort.sorted(db.tracked(), reverseSort)); + } + + /* + * Stale items stay pinned to the bottom in both directions, so the + * label only describes how the recently seen items are ordered. + */ + private void updateSortButton() { + android.widget.Button button = + (android.widget.Button) findViewById(R.id.sort_button); + if (button == null) return; + button.setText(reverseSort ? "Oldest first" : "Newest first"); + } private void startTracker() { Intent intent = new Intent(this, BluetoothTrackingService.class); diff --git a/app/src/main/res/layout/main.xml b/app/src/main/res/layout/main.xml index 015ed02..acb3b40 100644 --- a/app/src/main/res/layout/main.xml +++ b/app/src/main/res/layout/main.xml @@ -6,7 +6,10 @@