Start port forwards on the phone, listed in a sheet off its terminal
ci / build and test (pull_request) Successful in 3m32s
ci / desktop nightly (pull_request) Skipped
ci / android head (pull_request) Successful in 5m17s
ci / api image (pull_request) Failing after 13s

The Android head skipped a host's forwards because it had nowhere to show
what was listening. Its terminal bar now draws a ⇄ for a tab that has
forwards, carrying the listening count in green and turning the danger
colour when one failed, and it raises a sheet over the same rows the
desktop sidebar lists: notation, route, state, failure reason and a tap
to switch each off or on. MainWindowViewModel.StartForwards no longer
returns early on Android.

The sheet is shell state like the connect sheet: it collapses the
renderer while up, is lowered by leaving the terminal, and the back
gesture closes it first.

Forwards ride the shell's keep-alive rather than holding the foreground
service themselves, since a forward cannot outlive its shell. They only
change the notification, which now counts ports forwarded beside shells
connected, fed by a new ForwardsChanged event and ListeningForwardCount.

The phone still has no editor for forwards; the More screen card, README
and design-import-gaps now say they run here and are edited on the
desktop.
This commit is contained in:
jaap-jan committed 2026-09-28 21:06:05 +02:00
1 parent f05deb4fbc
commit c3dfa42108
13 files changed
+415 -56

No files matched your search

@@ -342,6 +342,20 @@ internal sealed partial class TerminalTabViewModel : ObservableObject
/// <summary>How many of <see cref="Forwards"/> are listening now.</summary>
internal int ListeningForwardCount => Forwards.Count(forward => forward.IsListening);
/// <summary>Whether any of <see cref="Forwards"/> is listening now.</summary>
internal bool HasListeningForward => ListeningForwardCount > 0;
/// <summary>Whether this tab carries any forwards at all, running or not.</summary>
/// <remarks>The phone's terminal bar draws its FORWARDS button only for a tab that has some.</remarks>
internal bool HasForwards => Forwards.Count > 0;
/// <summary>Whether any of <see cref="Forwards"/> could not be started.</summary>
/// <remarks>
/// The phone's list is a sheet, not a sidebar that is always drawn, so a refusal there would be invisible
/// until somebody happened to open it. This is what lets the button that opens it say so first.
/// </remarks>
internal bool HasFailedForward => Forwards.Any(forward => forward.IsFailed);
/// <summary>
/// The status bar's forward count, or null when nothing is listening and there is nothing to say.
/// </summary>
@@ -394,6 +408,9 @@ internal sealed partial class TerminalTabViewModel : ObservableObject
{
OnPropertyChanged(nameof(ListeningForwardCount));
OnPropertyChanged(nameof(ForwardsSummary));
OnPropertyChanged(nameof(HasListeningForward));
OnPropertyChanged(nameof(HasForwards));
OnPropertyChanged(nameof(HasFailedForward));
}
/// <inheritdoc cref="TerminalTabState" />