feat(list): sort tracked items by last seen with reverse toggle

This commit is contained in:
n0tst3v3
2026-08-19 00:56:47 -06:00
parent a5f5d415e2
commit a48cc91680
3 changed files with 153 additions and 2 deletions
@@ -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<DeviceRecord> sorted(List<DeviceRecord> rows, boolean reverse) {
ArrayList<DeviceRecord> fresh = new ArrayList<DeviceRecord>();
ArrayList<DeviceRecord> stale = new ArrayList<DeviceRecord>();
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<DeviceRecord> newestFirst =
new Comparator<DeviceRecord>() {
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<DeviceRecord> result =
new ArrayList<DeviceRecord>(fresh.size() + stale.size());
result.addAll(fresh);
/*
* Stale entries are appended last in both directions.
*/
result.addAll(stale);
return result;
}
}
@@ -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);
+4 -1
View File
@@ -6,7 +6,10 @@
<Button android:id="@+id/map_all_button" android:layout_width="0dp" android:layout_weight="1" android:layout_height="wrap_content" android:text="MAP"/>
<Button android:id="@+id/settings_button" android:layout_width="0dp" android:layout_weight="1" android:layout_height="wrap_content" android:text="SETTINGS"/>
</LinearLayout>
<TextView android:id="@+id/tracking_status" android:text="@string/tracking_status" android:layout_width="match_parent" android:layout_height="wrap_content" android:paddingLeft="12dp" android:paddingBottom="8dp" android:textColor="#2878ff"/>
<LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:gravity="center_vertical">
<TextView android:id="@+id/tracking_status" android:text="@string/tracking_status" android:layout_width="0dp" android:layout_weight="1" android:layout_height="wrap_content" android:paddingLeft="12dp" android:paddingBottom="8dp" android:textColor="#2878ff"/>
<Button android:id="@+id/sort_button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Newest first" android:textSize="10sp" android:layout_marginRight="8dp"/>
</LinearLayout>
<ListView android:id="@+id/device_list" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1" android:dividerHeight="10dp" android:padding="8dp"/>
<TextView android:id="@+id/empty_view" android:text="No tracked items yet. Use ADD / PAIR to choose an item." android:gravity="center" android:layout_width="match_parent" android:layout_height="match_parent"/>
</LinearLayout>