feat(list): sort tracked items by last seen with reverse toggle
This commit is contained in:
@@ -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 static final int CAMERA = 200;
|
||||||
private TrackerDatabase db;
|
private TrackerDatabase db;
|
||||||
private DeviceListAdapter adapter;
|
private DeviceListAdapter adapter;
|
||||||
|
private boolean reverseSort;
|
||||||
private boolean registered;
|
private boolean registered;
|
||||||
private DeviceRecord editing;
|
private DeviceRecord editing;
|
||||||
private final Handler refreshHandler = new Handler();
|
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,
|
public void onClick(View v) { startActivity(new Intent(MainActivity.this,
|
||||||
MapActivity.class)); }
|
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() {
|
findViewById(R.id.settings_button).setOnClickListener(new View.OnClickListener() {
|
||||||
public void onClick(View v) { showSettings(); }
|
public void onClick(View v) { showSettings(); }
|
||||||
});
|
});
|
||||||
@@ -347,7 +360,21 @@ public class MainActivity extends Activity implements ItemActionListener {
|
|||||||
PackageManager.PERMISSION_GRANTED) list.add(permission);
|
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() {
|
private void startTracker() {
|
||||||
Intent intent = new Intent(this, BluetoothTrackingService.class);
|
Intent intent = new Intent(this, BluetoothTrackingService.class);
|
||||||
|
|||||||
@@ -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/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"/>
|
<Button android:id="@+id/settings_button" android:layout_width="0dp" android:layout_weight="1" android:layout_height="wrap_content" android:text="SETTINGS"/>
|
||||||
</LinearLayout>
|
</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"/>
|
<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"/>
|
<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>
|
</LinearLayout>
|
||||||
|
|||||||
Reference in New Issue
Block a user