Skip to content

File share - #254

Draft
pappz wants to merge 183 commits into
mainfrom
file-share
Draft

pappz wants to merge 183 commits into
mainfrom
file-share

Conversation

@pappz

@pappz pappz commented Sep 1, 2026

Copy link
Copy Markdown
Collaborator

No description provided.

pappz and others added 30 commits May 10, 2026 16:15
Replace the side drawer + collapsible bottom-sheet pattern with a
bottom navigation bar (4 tabs: Home, Peers, Resources, Settings) to
mirror the iOS client's TabView. Sub-screens (Advanced, Profiles,
Change Server, Troubleshoot, About) are reached from the Settings
tab and use an iOS-style sectioned list layout.

- New SettingsFragment (sectioned list mirroring iOSSettingsView)
- Promote PeersFragment / NetworksFragment to top-level destinations;
  drop the modal BottomDialogFragment + PagerAdapter
- Profile chip on Home opens a ProfilePickerSheet (one-tap switch +
  Manage profiles link), echoing the iOS ProfileBadge
- Restyle AdvancedFragment + TroubleshootFragment as sectioned lists;
  Theme Mode now opens a bottom-sheet picker
- Refresh menu icons to thinner outlined Material Symbols
- NavigationRailView via layout-w960dp for large screens / TV
- Toolbar hidden on top-level destinations; visible on sub-screens
- Profile cards adopt PR #137 dark-mode contrast fix
- Treat the empty-profile-state JSON read as a normal first-launch
  case in ProfileManagerWrapper instead of logging at error level
The destination listener was hiding the bottom nav on the first-launch
fragment but still falling through to the top-level toolbar logic, so
the toolbar stayed visible. Treat firstInstallFragment as a full-screen
take-over and bypass the rest of the listener.
ProfilePickerSheet validated the input via sanitizeProfileName() but
still wrote the raw user string, so disallowed characters (anything
outside [A-Za-z0-9_-]) made it into the engine. Pass the sanitized
value to addProfile() and the success/duplicate toasts so what the
user sees matches what was stored.
On a TV or stripped-down build there may not be an Activity that
handles ACTION_VIEW for an https URL. Catch ActivityNotFoundException
and surface a toast instead of crashing the app.
Hardcoded #FFFFFFFF prevents these icons from adapting to theme
overlays (e.g. dark mode, focus inversions on TV). Switch to
?attr/colorControlNormal so each icon picks up the correct tint
from its host theme.

Affects ic_nav_home, ic_nav_peers, ic_nav_networks, ic_nav_settings,
ic_add, ic_check, ic_chevron_right, ic_arrow_drop_down,
ic_open_in_new — all custom icons introduced in this branch.
Theme picker rows now expose:
- contentDescription with the theme label
- stateDescription "Selected" on the active row (Android R+)
- isSelected=true on the active row for accessibility services
After picking, announce "<theme> theme selected" on the sheet root
so TalkBack confirms the change before the sheet dismisses.
On large screens the bottom navigation moves to a side rail
(layout-w960dp/activity_main.xml). The 80dp bottom padding fragments
add to clear the bottom bar is therefore wasted vertical space — set
the dimen to 0dp at the same qualifier so layouts pick the right
value automatically without per-layout overrides.
A long profile name was wrapping and pushing the picker row taller
than the others, breaking the row rhythm. maxLines=1 + ellipsize=end
truncates the overflow with "…" so picker rows stay uniform.
list_item_setting_section.xml duplicated the same padding / text size
/ caps attributes already defined in @style/SettingsSectionHeader,
which the section headers inside fragment_settings.xml etc. already
apply via the style. Replace the inline attributes with a single
style= reference so the section template is the same source of truth.
CodeRabbit suggested catching a typed exception or pre-checking the
state file length. The gomobile binding flattens the Go error chain
into go.Universe$proxyerror without surfacing the underlying type,
and the state-file path is a Go-side constant not exposed to Java,
so neither approach is available without a gomobile API change.
Expand the comment to make that trade-off explicit so the next
reader doesn't try the same refactor.
Squash-merge the four commits from PR #189 (profile-id branch) onto the
redesign branch, adapting the profile-id migration to the new bottom-nav
UI instead of the old drawer/NavigationView layout:

- getActiveProfile() now returns a Profile (with ID) instead of a String;
  update SettingsFragment and HomeFragment callers to use getName().
- Drop the PR's drawer-specific MainActivity changes (updateProfileMenuItem,
  drawer onKeyDown) — the redesign replaced the drawer with bottom nav.
- Graft the new disable-IPv6 switch listener into AdvancedFragment and add
  the IPv6 settings row to fragment_advanced.xml in the redesign row style.
- Bump netbird submodule to 62afff6 (adds Profile.ID to the gomobile binding).
Replace the Lottie connect button and background mask with a custom
pill-shaped SwitchMaterial toggle (white thumb, orange track when
connected), and restructure the home layout to match the iOS client:
centered profile chip, NetBird logo, status text, hostname with a
tappable IP/IPv6 detail section.

- Remove Lottie dependency, ButtonAnimation and unused JSON assets
- Add Inter and JetBrains Mono fonts; logo from the iOS client
- Hostname shown emphasized, IPv4 in the muted summary (with chevron)
- Info rows (IPv4 + IPv6) expand on tapping the summary; IPv6 row only
  shown when an IPv6 address is available; copy-to-clipboard buttons
- Append ellipsis to Connecting/Disconnecting status
- Make bottom nav unselected items white in night mode
- Drop the 'profile created'/'switched to profile' success toasts
* Add profile id migration

* Check if ID is set on Profile

* Bump netbird

* Update profile-id-name branch

* Fix active profile errors, bump netbird

* Bump netbird to v0.74.0
Resolve conflicts in favor of the iOS-style bottom-nav redesign:
- MainActivity: keep bottom-nav AppBarConfiguration; drop drawer setup,
  updateProfileMenuItem and drawer-based onKeyDown (drawer no longer exists)
- AdvancedFragment: keep compact row-click listeners; drop redundant main
  block and a duplicate layoutDisableIpv6 listener
- fragment_advanced.xml: keep settings_row_bg IPv6/firewall rows; drop main's
  ConstraintLayout variant that duplicated @+id/layout_force_relay_connection
- ProfilesFragment: keep no success toast on profile switch
- ProfileManagerWrapper: take main's getActiveProfile (getIsActive + throw);
  fresh-install empty state is now handled inside the engine
- netbird submodule: advance to current main pointer (3aa6c02)

Note: gomobile netbird.aar must be regenerated from the new submodule so the
Profile.getIsActive() binding is available.
- Rework the connect toggle thumb as a centred layer so the white circle
  size is independent of the track height
- Give the disconnected track a light grey fill with a thin darker border
  and disable Material auto-tint so the custom per-state colours render
- Make the bottom nav background white and drive item icon/text colour off
  state_checked; use opaque icon fills so itemIconTint applies at full strength
- Lighten the home background
- Give the connect toggle thumb a soft drop shadow (baked into the drawable;
  drop thumbTint so the shadow is not tinted white)
- Merge the two info rows (IP / secondary value) into a single bordered box
  with a middle divider; showDividers hides the separator when the secondary
  row is gone
Revert to a plain white circle for the connect toggle thumb.
The recent white-surface tweaks hardcoded @color/white and #4A4A4A, which
have no night variant, breaking dark mode (white bottom nav, white profile
chip, invisible unselected icons).

Introduce theme-aware semantic colours instead:
- nb_bottom_nav_bg / nb_chip_bg: white in light, dark surface in night
- tab_icon_unselected: #4A4A4A in light, white in night
- Reduce row height (52->40dp), text (15->12sp) and copy buttons (40->34dp)
- Middle-ellipsize the IPv6 row so a long address is truncated in the centre
  while the copy button still copies the full value
Use opaque fillColor so app:tint renders the profile picker + and
check icons at full nb_orange intensity, matching the manage icon.
Replace the default circular ripple mask on bottom nav / navigation rail
items with a rounded-rect (8dp) mask.
Navigating back to home re-inflates the fragment, so the hardcoded
android:text default painted "Disconnected" before the real state
arrived — and the state update was deferred by two nested post() calls
even though registration replays it on the main thread.

Move the layout default to tools:text, and apply view updates inline
when already on the main thread (engine callbacks, which arrive off the
main thread, still post). Snap the toggle thumb after setChecked so it
doesn't animate into place on a fresh view.
The sheet listed every profile in a wrap_content RecyclerView, so past a
handful of profiles the list grew past the screen and pushed the "Add
profile" and "Manage profiles" rows out of reach.

Cap the list height, show only the five most recently used profiles, and
surface a "Show all profiles (N)" row into the manage screen when there
are more. Expand the sheet on open so a long list no longer parks at peek
height.

Recency is tracked in SharedPreferences since the gomobile Profile has no
last-used field. The store prunes entries for profiles that no longer
exist on every read, so deleting a profile needs no bookkeeping from the
caller and the store cannot outgrow the profile count.

Also fix the sheet passing a profile name where switchProfile expects an
ID, which made switching from the sheet fail whenever the two differed.
Picks up 78 upstream commits, including the client-side profile work
("Always connect on profile selection except in manage profiles") that
this branch's picker sheet sits on top of.
Adds an edit affordance next to each profile name, opening a dialog
prefilled with the current name. Renaming only rewrites the "name" field
in the profile config; the ID and its on-disk filename stay put, so the
default profile can be renamed too.

The picker sheet is unaffected: it reads the name fresh and its
most-recently-used ordering is keyed on ID.

Because the default profile is now renameable, the adapter's "can this be
removed" check moves from name to ID, which no longer identifies it.

Renaming and adding also enforce different limits, matching the Go core:
a new profile's name doubles as its filename (64 chars), while a rename
sets a display name only (128 chars).

Requires the submodule bump, which exposes RenameProfile on the Android
gomobile binding.
The profiles list reserved bottom_nav_inset, which covers the nav bar but
not the add button sitting above it, so the final card stayed partly
hidden. Reserve the nav bar, the button, and a gap instead.

Left bottom_nav_inset alone since seven other fragments share it and none
of them have a FAB.
Tapping a peer opens a full-screen view of everything the client knows
about it, following the desktop app's panel: the same rows in the same
order, icons per row, latency coloured by band, and transfer counters
split into received and sent.

Rows are hidden rather than filled with a meaningless value, as on
desktop. That matters most for the connection type: "relayed" is a plain
bool with no not-connected state, so an idle peer would otherwise read as
P2P. Latency, transfer and the ICE rows drop out the same way when there
is nothing to report.

The screen re-renders from each peer-list snapshot instead of a copy taken
when the row was tapped, so it tracks status changes live and closes
itself if the peer disappears. Transfer counters and latency move without
the list firing a change event, so it also polls once a second while in
the foreground, and Peer gains value equality to skip rebuilding rows on
the ticks that bring identical data.

Requires the submodule bump: PeersList now refreshes WireGuard stats
before reading the recorder, without which the counters read as zero.
pappz and others added 23 commits August 25, 2026 19:44
Network changes no longer restart the engine; they are handled as events.

No network: the reconnect loops suspend instead of retrying against an unreachable network, and the UI and the notification show "No network available". The loops resume with a fresh backoff when connectivity returns.
Network switch: stale management, signal and relay connections are swept and re-dialed immediately on the new network (cross-transport handover fast path: fix: notify Go core on seamless network handover (cellular → WiFi) while VPN is active #243).
WiFi-to-WiFi handovers are detected by network identity, so a same-transport switch triggers the same reconnect path.
Availability is seeded from non-VPN internet networks, so an up tunnel cannot mask a missing underlying network.
New instrumented e2e suites (NetworkTransitionTest, ExitNodeNetworkTransitionTest) cover blackout, transport-switch and handover scenarios with outage budgets.
---------

Co-authored-by: hchengting <74168694+hchengting@users.noreply.github.com>
# Conflicts:
#	app/src/main/java/io/netbird/client/MainActivity.java
#	app/src/main/java/io/netbird/client/ServiceAccessor.java
#	app/src/main/java/io/netbird/client/ui/ssh/SshSessionsFragment.java
#	app/src/main/res/navigation/mobile_navigation.xml
#	app/src/main/res/values-de/strings.xml
#	app/src/main/res/values-es/strings.xml
#	app/src/main/res/values-fr/strings.xml
#	app/src/main/res/values-hu/strings.xml
#	app/src/main/res/values-it/strings.xml
#	app/src/main/res/values-ja/strings.xml
#	app/src/main/res/values-pt/strings.xml
#	app/src/main/res/values-ru/strings.xml
#	app/src/main/res/values-zh-rCN/strings.xml
#	app/src/main/res/values/dimens.xml
#	app/src/main/res/values/strings.xml
#	netbird
#	tool/src/main/java/io/netbird/client/tool/EngineRunner.java
#	tool/src/main/java/io/netbird/client/tool/VPNService.java
#	tool/src/main/res/values/strings.xml
The Resources view rendered its own connected counter below the
segmented control while the header still showed the peers counter.
NetworksFragment now reports its counts to the hosting PeersFragment,
which swaps the single header line between the peers and resources
counters with the toggle.
…l it

A theme change relaunches MainActivity; the old instance's unbindService
reaches the service after the new instance has already bound, so the
service's stopSelf destroys it under the fresh binding. Without
BIND_AUTO_CREATE the binding neither keeps the service alive nor brings
it back, and onServiceDisconnected silently nulls mBinder — every action
afterwards fails with "VPN binder is null" until a full stop/start
cycle. AUTO_CREATE keeps the service alive while bound and rebinds it
after any real death; onServiceDisconnected now logs a warning too.
# Conflicts:
#	app/src/main/res/layout/fragment_peer_detail.xml
@coderabbitai

coderabbitai Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

…the desktop

The outcome column marked a completed send green but left a received
file grey, so green meant one thing on the icon, incoming, and a
different, asymmetric thing on the label. Completed is now green in
either direction; the label's legend is green arrived, red refused or
broke, grey everything else, a transfer in flight included.

The row's icon moves from a bare arrow to the desktop's three glyphs: a
document carrying the arrow for a file, up or down by direction, and a
quote mark for a text snippet. Drawn as 2px round strokes like the Files
menu icon. The bare arrows stay where they still mean network traffic,
on the peer detail row.
Both carried the paper plane, which says "send" and nothing else: the
feature receives as well, and the settings row it sat on is about the
receiving policy. Each icon now follows its own label. The segment gets a
folder at the 16dp the other segment icons use, since a compound drawable
shows at its intrinsic size; the settings row gets the three-node share
glyph Android itself uses for sharing. The plane stays on the send button
and the share target, where it means what it says.
Base automatically changed from ux/ios-style-redesign to main September 8, 2026 08:19
main received the redesign as a single squashed commit (#179) while this
branch carries the same work as individual commits plus file sharing, so
git could not match them up and flagged nearly every shared file.

Kept from this branch: the file sharing feature, the four-item bottom
navigation (Networks folded into the Peers segmented switch, Files and
SSH merged into Apps) and the activity-scoped nav lookup the SSH list
needs inside the Apps screen.

Taken from main: split tunneling and its wiring, the SSH terminal fixes
(application cursor keys, IME composition, modifier dimming, no-peek
password), IPv6 and cleartext handling in ManagementUrl, the forced TUN
rebuild, the remote debug bundle row and the tablet toolbar divider.

Translations were union-merged; the unused menu_ssh string was dropped.
The submodule pointer stays on this branch's commit.
NewAuth takes a PolicyFetcher for MDM enforcement; a nil one disables it,
which is what the client does until it grows a managed configuration
source. Preferences.getPreSharedKey is gone in favour of
hasPreSharedKey, which also counts a key the MDM policy supplies.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants