Commit 58b61cf
Make timeline default view (#572)
* Make Timeline live-update and give it a guiding empty state
New users landed on an empty Timeline with no feedback: the screen only
refreshed on onResume, and the empty text said "Enable TrackerControl"
even when it was already on. They had no way to know they needed to
open an app to generate traffic.
- ActivityTimeline subscribes to DatabaseHelper.AccessChangedListener
(same pattern as ActivityLog) with a 500ms debounce so entries stream
in while the screen is open.
- Wrap the RecyclerView in a SwipeRefreshLayout as a manual fallback.
- Replace the single tvEmpty label with a context-aware empty state:
"TrackerControl is off" when disabled, or "Watching for trackers…"
plus an "Open an app" button (launches the system launcher) when
enabled.
- Drop the obsolete msg_private_dns Toast in ActivityMain — private
DNS is blocked by default now, so the nudge no longer applies.
- Remove the dead llUsage / hint_usage banner plumbing (the layout
was already visibility="gone"; the flag was only gating other hints).
* Add bottom nav with Timeline and Apps tabs
Onboarding dropped new users on the app list with no guidance. With the
Timeline now live-updating and owning a guiding empty state, promote it
to a peer destination via a bottom navigation bar, landing users there
by default while keeping the filterable app list and SearchView a tap
away.
- Extract TimelineFragment from ActivityTimeline (same live refresh,
swipe refresh, and context-aware empty state with "Open an app" CTA)
and reuse it in both ActivityMain and ActivityTimeline.
- Add BottomNavigationView to main.xml with Timeline and Apps items;
ActivityMain swaps visibility of the Apps content vs. the Timeline
fragment container and adjusts the toolbar (custom on/off switch
visible only on the Apps tab; screen title shown on Timeline).
- onPrepareOptionsMenu hides search/filter/sort on the Timeline tab.
- Remove the now-redundant "Tracker activity" overflow menu item and
the ActivityMain->ActivityTimeline launcher intent. The standalone
ActivityTimeline still exists as a thin fragment host so the
Insights hero card's "timeline" shortcut keeps working (it now
switches tabs in-place when launched from ActivityMain).
- Back from Timeline returns to Apps instead of exiting.
* Move insights hero card from Apps tab to Timeline
The insights card (total trackers blocked, companies, block %) used to
sit above the app list on the Apps tab. With Timeline as the default
home, it makes more sense as the top card there — an at-a-glance
dashboard that frames whatever tracker activity follows.
- TimelineFragment now owns a ConcatAdapter of
(InsightsHeaderAdapter, TimelineEmptyAdapter, TimelineAdapter). The
insights card is always present; the empty-state card appears only
when there are no timeline entries so the hero stays visible.
- Move loadInsightsData() into TimelineFragment so insights refresh
on the same debounced access-change callback as the timeline.
- Strip InsightsHeaderAdapter, ConcatAdapter, and loadInsightsData
from ActivityMain. The Apps tab is now a plain AdapterRule list.
- Hide the now-redundant "open timeline" shortcut inside the insights
card (the card already lives on Timeline).
- Delete ActivityTimeline and its layout/manifest entry — no longer
reachable now that the tab + in-card action route traffic directly.
- Replace the overlay-style empty view in fragment_timeline.xml with
an in-list TimelineEmptyAdapter so the insights card remains visible
alongside the empty state.
* Keep top bar identical across tabs and fix insights clipping
Two fixes from on-device feedback:
- The insights hero card was being clipped under the AppBar. The cause
was wrapping the tab contents in an extra FrameLayout that broke the
CoordinatorLayout's appbar_scrolling_view_behavior offset. Drop the
wrapper and put llApps and timelineContainer back as direct children
of the CoordinatorLayout (the original llApps pattern), each with
the scrolling behavior + dodgeInsetEdges so they sit below the
AppBar and above the bottom nav.
- The toolbar was swapping its custom view (on/off switch) and title
between tabs, which felt jarring. Keep the on/off switch always
visible — the global VPN toggle now works on Timeline too — and
drop the per-tab title swap. Filter and sort still hide on Timeline
since they're meaningless without the app list, but Search stays
visible: tapping it on Timeline jumps to the Apps tab and expands
the search field.
* Fix insights clipping by switching root to LinearLayout
The clipping was real and caused by app:layout_dodgeInsetEdges="bottom"
on the content child interacting with the BottomNavigationView's
app:layout_insetEdge="bottom". CoordinatorLayout's dodge translates the
dodging child upward by the insetting sibling's height, and combined
with appbar_scrolling_view_behavior the result was the content's top
ending up above the AppBar — so the insights card's top rows
(Last 7 days header + first ~30dp of the big numbers) rendered behind
the toolbar.
We don't need any of CoordinatorLayout's behavior magic here: the
AppBar is pinned (liftOnScroll="false"), the bottom nav is always
visible, and there are no scroll-dependent transitions. Switch the
root to a vertical LinearLayout — AppBar / content FrameLayout
(weight=1) / BottomNavigationView — which gives a predictable static
layout. Also swap timelineContainer from FragmentContainerView to a
plain FrameLayout to avoid FragmentContainerView's window-insets
interception, which was a second potential source of layout drift.
* Fix doubled AppBar padding in Settings and Details
Latent bug from commit 7e6bccb (the M3 edge-to-edge migration). Both
activity_settings.xml and activity_details.xml have:
CoordinatorLayout android:fitsSystemWindows="true"
AppBarLayout
MaterialToolbar
Combined with the OnApplyWindowInsetsListener in ActivitySettings.java
and DetailsActivity.java that pads the AppBar by sysBars.top, the
status-bar inset was applied twice: once by the CoordinatorLayout
(fitsSystemWindows pads the parent) and once by the listener (pads the
AppBar) — yielding 2 × statusBarHeight of red space above the toolbar.
ActivityMain's CoordinatorLayout never had fitsSystemWindows, which is
why this branch's screenshots only showed it after looking at Settings.
Drop fitsSystemWindows from both layouts so the listener is the single
source of truth, matching how main.xml has always behaved.
* Clean up dead divider + fix search-from-Timeline focus
Two follow-ups from the bottom-nav move:
Insights card: the white horizontal divider and the "View recent tracker
activity" row used to separate the stats from the link to the Timeline
activity. With the card now living on the Timeline tab itself the
forwarding row was already hidden — drop the divider and the row from
item_insights_header.xml entirely, and remove the matching field/wiring
in InsightsHeaderAdapter.
Search from Timeline: tapping the toolbar search icon already jumped to
the Apps tab, but the SearchView never received focus (so the keyboard
did not pop up and the user could not type). Two fixes:
1. selectTab no longer calls invalidateOptionsMenu. Filter/sort live
in the overflow, so they re-prepare lazily next time it opens; we
avoid rebuilding the SearchView mid-expand and dropping its focus.
Also only collapse the SearchView on Apps→Timeline, not in the
Timeline→Apps direction triggered by expanding it.
2. The bottom-nav switch in onMenuItemActionExpand is posted so the
SearchView finishes expanding and the IME shows on the current
frame; the layout toggle then runs without pre-empting focus.
* Fix empty Timeline on open and add periodic refresh
Two issues on the Timeline tab:
1. Opening the app went to "Watching for trackers…" even when there
was recent activity; only pulling to refresh populated the list.
Root cause: TimelineFragment.buildTimeline() calls the static
TrackerList.findTracker(daddr), which reads a static
hostname → Tracker map populated lazily by
TrackerList.getInstance(context). InsightsDataProvider initializes
it (kt:50, "Ensure TrackerList is initialized") but the timeline
path never did. loadTimeline() and loadInsights() race on
different executors; if the timeline ran first the map was empty,
every entry was dropped, and the empty state showed. Pull-to-
refresh worked because by then insights had won the race. Call
TrackerList.getInstance() at the top of buildTimeline.
2. The screen relied solely on AccessChangedListener for updates,
so relative timestamps drifted and any missed callback left the
list stale. Add a 30-second periodic tick (Handler.postDelayed
loop) that re-runs refreshAll while the fragment is resumed, and
cancel it in onPause.
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 9f94d80 commit 58b61cf
16 files changed
Lines changed: 508 additions & 379 deletions
File tree
- app/src/main
- java
- eu/faircode/netguard
- net/kollnig/missioncontrol
- res
- drawable
- layout
- values
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
169 | 169 | | |
170 | 170 | | |
171 | 171 | | |
172 | | - | |
173 | | - | |
174 | | - | |
175 | | - | |
176 | | - | |
177 | | - | |
178 | | - | |
179 | | - | |
180 | | - | |
181 | 172 | | |
182 | 173 | | |
183 | 174 | | |
| |||
Lines changed: 85 additions & 56 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
75 | 75 | | |
76 | 76 | | |
77 | 77 | | |
78 | | - | |
79 | 78 | | |
80 | 79 | | |
81 | 80 | | |
82 | 81 | | |
| 82 | + | |
83 | 83 | | |
84 | 84 | | |
85 | 85 | | |
86 | 86 | | |
87 | | - | |
88 | 87 | | |
89 | | - | |
90 | | - | |
| 88 | + | |
91 | 89 | | |
92 | 90 | | |
93 | 91 | | |
| |||
99 | 97 | | |
100 | 98 | | |
101 | 99 | | |
102 | | - | |
103 | | - | |
104 | 100 | | |
105 | 101 | | |
106 | 102 | | |
| |||
119 | 115 | | |
120 | 116 | | |
121 | 117 | | |
122 | | - | |
123 | 118 | | |
124 | 119 | | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
125 | 124 | | |
126 | 125 | | |
127 | 126 | | |
| |||
310 | 309 | | |
311 | 310 | | |
312 | 311 | | |
313 | | - | |
314 | | - | |
315 | | - | |
316 | | - | |
317 | 312 | | |
318 | 313 | | |
319 | 314 | | |
| |||
381 | 376 | | |
382 | 377 | | |
383 | 378 | | |
| 379 | + | |
| 380 | + | |
| 381 | + | |
| 382 | + | |
| 383 | + | |
| 384 | + | |
| 385 | + | |
| 386 | + | |
| 387 | + | |
| 388 | + | |
| 389 | + | |
| 390 | + | |
| 391 | + | |
| 392 | + | |
| 393 | + | |
| 394 | + | |
| 395 | + | |
| 396 | + | |
| 397 | + | |
| 398 | + | |
| 399 | + | |
| 400 | + | |
| 401 | + | |
| 402 | + | |
| 403 | + | |
| 404 | + | |
| 405 | + | |
| 406 | + | |
| 407 | + | |
| 408 | + | |
384 | 409 | | |
385 | 410 | | |
386 | 411 | | |
| |||
402 | 427 | | |
403 | 428 | | |
404 | 429 | | |
405 | | - | |
406 | 430 | | |
407 | | - | |
408 | | - | |
409 | | - | |
410 | | - | |
| 431 | + | |
411 | 432 | | |
412 | 433 | | |
413 | 434 | | |
| |||
417 | 438 | | |
418 | 439 | | |
419 | 440 | | |
420 | | - | |
421 | | - | |
422 | | - | |
423 | | - | |
424 | | - | |
425 | | - | |
426 | | - | |
427 | | - | |
428 | | - | |
429 | | - | |
430 | | - | |
431 | | - | |
432 | | - | |
433 | | - | |
434 | | - | |
435 | 441 | | |
436 | 442 | | |
437 | 443 | | |
| |||
499 | 505 | | |
500 | 506 | | |
501 | 507 | | |
502 | | - | |
503 | | - | |
504 | 508 | | |
505 | 509 | | |
506 | 510 | | |
| |||
821 | 825 | | |
822 | 826 | | |
823 | 827 | | |
824 | | - | |
825 | 828 | | |
826 | 829 | | |
827 | 830 | | |
| |||
863 | 866 | | |
864 | 867 | | |
865 | 868 | | |
| 869 | + | |
| 870 | + | |
| 871 | + | |
| 872 | + | |
| 873 | + | |
| 874 | + | |
| 875 | + | |
| 876 | + | |
| 877 | + | |
866 | 878 | | |
867 | 879 | | |
868 | 880 | | |
| |||
895 | 907 | | |
896 | 908 | | |
897 | 909 | | |
898 | | - | |
899 | | - | |
900 | 910 | | |
901 | 911 | | |
902 | 912 | | |
| |||
911 | 921 | | |
912 | 922 | | |
913 | 923 | | |
914 | | - | |
915 | | - | |
916 | 924 | | |
917 | 925 | | |
918 | 926 | | |
| |||
937 | 945 | | |
938 | 946 | | |
939 | 947 | | |
| 948 | + | |
| 949 | + | |
| 950 | + | |
| 951 | + | |
| 952 | + | |
| 953 | + | |
| 954 | + | |
| 955 | + | |
| 956 | + | |
940 | 957 | | |
941 | 958 | | |
942 | 959 | | |
| |||
1012 | 1029 | | |
1013 | 1030 | | |
1014 | 1031 | | |
1015 | | - | |
1016 | | - | |
1017 | | - | |
1018 | 1032 | | |
1019 | 1033 | | |
1020 | 1034 | | |
| |||
1059 | 1073 | | |
1060 | 1074 | | |
1061 | 1075 | | |
| 1076 | + | |
| 1077 | + | |
| 1078 | + | |
| 1079 | + | |
| 1080 | + | |
| 1081 | + | |
| 1082 | + | |
| 1083 | + | |
| 1084 | + | |
| 1085 | + | |
| 1086 | + | |
| 1087 | + | |
| 1088 | + | |
| 1089 | + | |
| 1090 | + | |
| 1091 | + | |
| 1092 | + | |
| 1093 | + | |
| 1094 | + | |
| 1095 | + | |
| 1096 | + | |
| 1097 | + | |
| 1098 | + | |
| 1099 | + | |
| 1100 | + | |
| 1101 | + | |
| 1102 | + | |
| 1103 | + | |
1062 | 1104 | | |
1063 | 1105 | | |
1064 | | - | |
1065 | 1106 | | |
1066 | 1107 | | |
1067 | 1108 | | |
| |||
1070 | 1111 | | |
1071 | 1112 | | |
1072 | 1113 | | |
1073 | | - | |
| 1114 | + | |
1074 | 1115 | | |
1075 | 1116 | | |
1076 | 1117 | | |
| |||
1083 | 1124 | | |
1084 | 1125 | | |
1085 | 1126 | | |
1086 | | - | |
| 1127 | + | |
1087 | 1128 | | |
1088 | 1129 | | |
1089 | 1130 | | |
| |||
1364 | 1405 | | |
1365 | 1406 | | |
1366 | 1407 | | |
1367 | | - | |
1368 | | - | |
1369 | | - | |
1370 | | - | |
1371 | | - | |
1372 | | - | |
1373 | | - | |
1374 | | - | |
1375 | | - | |
1376 | | - | |
1377 | | - | |
1378 | | - | |
1379 | 1408 | | |
Lines changed: 0 additions & 7 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
49 | 49 | | |
50 | 50 | | |
51 | 51 | | |
52 | | - | |
53 | | - | |
54 | 52 | | |
55 | 53 | | |
56 | 54 | | |
| |||
129 | 127 | | |
130 | 128 | | |
131 | 129 | | |
132 | | - | |
133 | | - | |
134 | | - | |
135 | 130 | | |
136 | 131 | | |
137 | 132 | | |
| |||
282 | 277 | | |
283 | 278 | | |
284 | 279 | | |
285 | | - | |
286 | 280 | | |
287 | 281 | | |
288 | 282 | | |
| |||
295 | 289 | | |
296 | 290 | | |
297 | 291 | | |
298 | | - | |
299 | 292 | | |
300 | 293 | | |
301 | 294 | | |
| |||
0 commit comments