Skip to content

fix(mobile): width and swipe - #80

Merged
keyldev merged 2 commits into
mainfrom
fix/mobile-width-and-swipe
Oct 1, 2026
Merged

keyldev merged 2 commits into
mainfrom
fix/mobile-width-and-swipe

Conversation

@keyldev

@keyldev keyldev commented Oct 1, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Related

Type

  • Bug fix
  • Feature
  • Refactor / cleanup
  • Docs / CI
  • Other:

Checklist

  • Builds with 0 warnings (TreatWarningsAsErrors=true).
  • Tests pass (dotnet test); new Core logic has unit tests.
  • No layering violation — App references Core only (Infrastructure / Video / Devices wired via DI in a head).
  • Scope stays within one phase (didn't pull work from a later phase's "Не входит").
  • README / docs updated if public commands, options, or setup changed.

Platforms tested

  • Windows
  • Linux
  • macOS
  • Android
  • iOS
  • CI build only

Screenshots / notes

… the hierarchy

- Insets move off ScrollViewer.Padding onto the content (settings panes, camera
  page Majestic/ISP/metrics, player event list): the padding shrank the viewport
  but not the content, so it hung past the right edge on a phone
- Health Center sheet drops its 420px minimum, wider than most phones
- Android Back / edge swipe (and the mouse back button) closes the top sheet,
  leaves fullscreen, returns from a camera or recording, a settings section to
  its list, then to the start page; only at the root does the app go to background
- A sheet closed by Back completes like Cancel, so the awaiting command and the
  bottom nav are not left disabled
- IBackNavigable: a page or overlay dialog handles Back first and only leaves
  or closes once it is at its own top
- file manager climbs to the parent folder, closing at "/"
- settings section returns to the list; an expanded month calendar folds back
  to its week strip before Back leaves the recordings page
@keyldev
keyldev merged commit e77ea7c into main Oct 1, 2026
5 checks passed
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

PR Summary by Qodo

Fix mobile page overflow and make Back unwind navigation levels

🐞 Bug fix ✨ Enhancement 🕐 20-40 Minutes

Grey Divider

AI Description

• Move scroll insets to content and narrow the Health Center minimum to prevent phone-width
 overflow.
• Handle system Back through overlays, fullscreen modes, detail pages, and nested page levels.
• Complete Back-dismissed dialogs as cancellations so awaiting commands and navigation recover.
Diagram

graph TD
  Back["Back request"] --> Overlay{"Overlay open?"} -- "no" --> Fullscreen{"Explicit fullscreen?"} -- "no" --> Detail{"Detail page?"} -- "no" --> Internal{"Inner level?"} -- "no" --> Other{"Other page?"} -- "no" --> Platform["Platform back"]
  Overlay -- "yes: step or cancel" --> Handled["Back handled"]
  Fullscreen -- "yes: exit" --> Handled
  Detail -- "yes: return" --> Handled
  Internal -- "yes: step up" --> Handled
  Other -- "yes: start page" --> Handled
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Use a unified navigation stack
  • ➕ Could model page history and nested navigation with one mechanism.
  • ➖ Requires a broader navigation refactor for a limited Back-handling fix.
  • ➖ Dialog cancellation and fullscreen still need distinct handling.

Recommendation: Keep the shell-level priority order and IBackNavigable contract: they fit the existing page model without replacing navigation. In review, verify Back-dismissed dialogs release their awaiting callers and exercise the hierarchy on a phone.

Files changed (11) +167 / -25

Enhancement (6) +114 / -5
IBackNavigable.csDefine a contract for nested Back navigation +12/-0

Define a contract for nested Back navigation

• Adds TryGoBack so pages and overlay content can consume Back while they have an internal level to leave.

src/OpenIPC.Viewer.App/Services/IBackNavigable.cs

FileManagerViewModel.csMake Back climb remote folders +10/-1

Make Back climb remote folders

• Implements IBackNavigable so Back opens the parent folder until the file manager reaches the remote root, where the overlay can close.

src/OpenIPC.Viewer.App/ViewModels/FileManagerViewModel.cs

MainWindowViewModel.csCoordinate Back across the application hierarchy +51/-1

Coordinate Back across the application hierarchy

• Records the configured start page and handles Back in priority order: overlay, explicit fullscreen, camera or player, page-internal level, then start page. Returns false at the root for platform handling.

src/OpenIPC.Viewer.App/ViewModels/MainWindowViewModel.cs

RecordingsPageViewModel.csCollapse the archive calendar before leaving recordings +11/-1

Collapse the archive calendar before leaving recordings

• Consumes Back to fold an expanded month calendar to its week strip when the recordings tab is shown.

src/OpenIPC.Viewer.App/ViewModels/RecordingsPageViewModel.cs

SettingsPageViewModel.csReturn from a narrow settings section to its list +11/-1

Return from a narrow settings section to its list

• Consumes Back when a settings section is open in the narrow layout; wide layouts retain their existing two-pane behavior.

src/OpenIPC.Viewer.App/ViewModels/SettingsPageViewModel.cs

MainView.axaml.csForward platform Back requests to the shell +19/-1

Forward platform Back requests to the shell

• Subscribes to TopLevel.BackRequested while attached and marks the event handled only when the view model performs a Back step.

src/OpenIPC.Viewer.App/Views/MainView.axaml.cs

Bug fix (5) +53 / -20
OverlayDialogPresenter.csDismiss the topmost mobile dialog through Back +34/-7

Dismiss the topmost mobile dialog through Back

• Tracks open overlays in order and gives the topmost dialog the first chance to navigate internally. Otherwise, Back resolves its dismissal task and returns a cancellation result to the awaiting caller.

src/OpenIPC.Viewer.App/Services/OverlayDialogPresenter.cs

HealthCenterContent.axamlReduce the Health Center sheet minimum width +1/-1

Reduce the Health Center sheet minimum width

• Lowers the content grid's minimum width from 420 to 300 pixels so it fits narrower phones.

src/OpenIPC.Viewer.App/Views/Dialogs/HealthCenterContent.axaml

RecordingPlayerPage.axamlKeep player event-list insets within scroll content +2/-2

Keep player event-list insets within scroll content

• Moves padding from the ScrollViewer to the event ItemsControl margin to prevent content extending past the phone viewport.

src/OpenIPC.Viewer.App/Views/Pages/RecordingPlayerPage.axaml

SettingsPage.axamlConstrain both settings panes to mobile width +8/-4

Constrain both settings panes to mobile width

• Moves master- and detail-pane insets from their ScrollViewers to content margins, keeping controls and text within the visible width.

src/OpenIPC.Viewer.App/Views/Pages/SettingsPage.axaml

SingleCameraPage.axamlFix camera panel horizontal overflow +8/-6

Fix camera panel horizontal overflow

• Moves ISP, Majestic configuration, and metrics-list insets onto their ItemsControls instead of their ScrollViewers.

src/OpenIPC.Viewer.App/Views/Pages/SingleCameraPage.axaml

@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (4) 📘 Rule violations (0) 📜 Skill insights (0)

Grey Divider


Remediation recommended

1. Discovery continues after its sheet closes 🐞 Bug ☼ Reliability
Description
TryDismissTopmost completes only the presenter’s dismissal task, bypassing the dialog content’s
Finish method and its cancellation call. When Back closes an active discovery sheet,
DiscoveryDialogViewModel.Cancel() is never called, so its scan and fingerprint work can continue
after the sheet disappears.
Code

src/OpenIPC.Viewer.App/Services/OverlayDialogPresenter.cs[R75-76]

+        if (inner?.TryGoBack() != true)
+            top.Dismissed.TrySetResult();
Evidence
The presenter returns when its private dismissal task wins, without completing the content task.
Discovery’s normal close path calls Cancel, which cancels both its scan and lifetime tokens; the
camera editor likewise cancels pending connection work only through Finish.

src/OpenIPC.Viewer.App/Services/OverlayDialogPresenter.cs[69-76]
src/OpenIPC.Viewer.App/Services/OverlayDialogPresenter.cs[207-219]
src/OpenIPC.Viewer.App/Views/Dialogs/DiscoveryDialogContent.axaml.cs[35-49]
src/OpenIPC.Viewer.App/ViewModels/Dialogs/DiscoveryDialogViewModel.cs[413-418]
src/OpenIPC.Viewer.App/Views/Dialogs/CameraEditorContent.axaml.cs[75-79]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Back removes mobile dialogs without invoking their cancellation paths, leaving background work active.
## Fix Focus Areas
- src/OpenIPC.Viewer.App/Services/OverlayDialogPresenter.cs[69-80]
- src/OpenIPC.Viewer.App/Views/Dialogs/DiscoveryDialogContent.axaml.cs[43-49]
- src/OpenIPC.Viewer.App/Views/Dialogs/CameraEditorContent.axaml.cs[75-80]
## Recommended Fix
Give the presenter a way to invoke each content’s normal cancel operation before dismissing it. Ensure dismissal still completes the awaiting command and runs cleanup exactly once.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Folder Back can mix directory listings 🐞 Bug ≡ Correctness
Description
FileManagerViewModel.TryGoBack starts NavigateUpAsync without awaiting or serializing the
resulting directory load. Repeated Back presses, or Back during an existing load, let asynchronous
enumerations clear and append to the same Entries collection after RemotePath has changed.
Code

src/OpenIPC.Viewer.App/ViewModels/FileManagerViewModel.cs[R99-102]

+        if ((RemotePath ?? "/").Trim('/').Length == 0)
+            return false;
+        _ = NavigateUpAsync();
+        return true;
Evidence
Back immediately permits another event after starting an asynchronous parent navigation.
LoadEntriesAsync clears the shared collection, awaits remote entries, and appends them without
cancellation, synchronization, or a check that the requested path remains current.

src/OpenIPC.Viewer.App/ViewModels/FileManagerViewModel.cs[85-103]
src/OpenIPC.Viewer.App/ViewModels/FileManagerViewModel.cs[219-239]
src/OpenIPC.Viewer.App/Services/OverlayDialogPresenter.cs[69-77]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Back can overlap file-manager directory loads, allowing results from different paths into one listing.
## Fix Focus Areas
- src/OpenIPC.Viewer.App/ViewModels/FileManagerViewModel.cs[96-103]
- src/OpenIPC.Viewer.App/ViewModels/FileManagerViewModel.cs[219-239]
## Recommended Fix
Cancel or version outstanding loads when navigating, and apply entries and busy state only for the current path. Prevent an older enumeration from appending results after a newer navigation starts.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Back from an event opens the wrong page 🐞 Bug ≡ Correctness
Description
MainWindowViewModel.TryGoBack sends GoBackToRecordingsMessage for every recording player instead
of using the page that opened it. A recording opened from Events therefore returns to Recordings on
system Back, while recordings opened from the Recordings page take the same route.
Code

src/OpenIPC.Viewer.App/ViewModels/MainWindowViewModel.cs[R221-223]

+            case RecordingPlayerPageViewModel:
+                Receive(new GoBackToRecordingsMessage());
+                return true;
Evidence
Both Events and Recordings send OpenRecordingMessage, but opening the player records no origin. The
new system-Back branch invokes a receiver that always selects Recordings.

src/OpenIPC.Viewer.App/ViewModels/EventsPageViewModel.cs[347-352]
src/OpenIPC.Viewer.App/ViewModels/RecordingsPageViewModel.cs[346-351]
src/OpenIPC.Viewer.App/ViewModels/MainWindowViewModel.cs[219-225]
src/OpenIPC.Viewer.App/ViewModels/MainWindowViewModel.cs[304-326]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
System Back from a recording opened in Events navigates to Recordings instead of its origin.
## Fix Focus Areas
- src/OpenIPC.Viewer.App/ViewModels/MainWindowViewModel.cs[219-225]
- src/OpenIPC.Viewer.App/ViewModels/MainWindowViewModel.cs[304-326]
## Recommended Fix
Store the originating page when opening a recording from a non-player page, preserve it across previous/next recording changes, and return to it on system Back after disposing the player.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View review recommended (1)
4. Health Center still overflows narrow phones 🐞 Bug ≡ Correctness
Description
HealthCenterContent replaces its grid’s 420-pixel minimum width with a fixed 300-pixel minimum
while retaining a 24-pixel margin on each side. On a 320-pixel-wide viewport, the grid requires at
least 348 pixels, so the sheet still cannot fit its content within the screen.
Code

src/OpenIPC.Viewer.App/Views/Dialogs/HealthCenterContent.axaml[14]

+  <Grid RowDefinitions="Auto,Auto,*,Auto" Margin="24" MinWidth="300">
Evidence
The changed line imposes a 300-pixel minimum on a grid with 48 pixels of horizontal margins. Mobile
dialogs place the content in a full-width card whose horizontal scrolling is disabled, so the
required 348 pixels cannot fit a 320-pixel-wide sheet.

src/OpenIPC.Viewer.App/Views/Dialogs/HealthCenterContent.axaml[1-14]
src/OpenIPC.Viewer.App/Services/OverlayDialogPresenter.cs[109-145]
src/OpenIPC.Viewer.App/Services/OverlayDialogPresenter.cs[172-182]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The Health Center grid’s minimum width plus its margins still exceeds a narrow mobile viewport.
## Fix Focus Areas
- src/OpenIPC.Viewer.App/Views/Dialogs/HealthCenterContent.axaml[14-14]
## Recommended Fix
Remove the fixed minimum width or make the grid and its margins responsive to available width. Verify that the sheet fits a 320-pixel-wide viewport.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Context sources
Review mode: ⚖️ Balanced: Downgraded extended -> standard: change is below the extended eligibility bar (hunks 27/18, lines 192/200; both must reach the floor). Router rationale: This mobile navigation change adds cross-cutting back handling across overlays, view models, and platform events, with many independent logic paths and UI layout adjustments that could hide multiple subtle defects.

Grey Divider

Tip of the day
💡 Did you know, you can keep summaries lean with Findings visible per group, which tucks the rest behind a View link

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment on lines +75 to +76
if (inner?.TryGoBack() != true)
top.Dismissed.TrySetResult();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remediation recommended

1. Discovery continues after its sheet closes 🐞 Bug ☼ Reliability

TryDismissTopmost completes only the presenter’s dismissal task, bypassing the dialog content’s
Finish method and its cancellation call. When Back closes an active discovery sheet,
DiscoveryDialogViewModel.Cancel() is never called, so its scan and fingerprint work can continue
after the sheet disappears.
Agent Prompt
## Issue description
Back removes mobile dialogs without invoking their cancellation paths, leaving background work active.
## Fix Focus Areas
- src/OpenIPC.Viewer.App/Services/OverlayDialogPresenter.cs[69-80]
- src/OpenIPC.Viewer.App/Views/Dialogs/DiscoveryDialogContent.axaml.cs[43-49]
- src/OpenIPC.Viewer.App/Views/Dialogs/CameraEditorContent.axaml.cs[75-80]
## Recommended Fix
Give the presenter a way to invoke each content’s normal cancel operation before dismissing it. Ensure dismissal still completes the awaiting command and runs cleanup exactly once.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +99 to +102
if ((RemotePath ?? "/").Trim('/').Length == 0)
return false;
_ = NavigateUpAsync();
return true;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remediation recommended

2. Folder back can mix directory listings 🐞 Bug ≡ Correctness

FileManagerViewModel.TryGoBack starts NavigateUpAsync without awaiting or serializing the
resulting directory load. Repeated Back presses, or Back during an existing load, let asynchronous
enumerations clear and append to the same Entries collection after RemotePath has changed.
Agent Prompt
## Issue description
Back can overlap file-manager directory loads, allowing results from different paths into one listing.
## Fix Focus Areas
- src/OpenIPC.Viewer.App/ViewModels/FileManagerViewModel.cs[96-103]
- src/OpenIPC.Viewer.App/ViewModels/FileManagerViewModel.cs[219-239]
## Recommended Fix
Cancel or version outstanding loads when navigating, and apply entries and busy state only for the current path. Prevent an older enumeration from appending results after a newer navigation starts.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +221 to +223
case RecordingPlayerPageViewModel:
Receive(new GoBackToRecordingsMessage());
return true;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remediation recommended

3. Back from an event opens the wrong page 🐞 Bug ≡ Correctness

MainWindowViewModel.TryGoBack sends GoBackToRecordingsMessage for every recording player instead
of using the page that opened it. A recording opened from Events therefore returns to Recordings on
system Back, while recordings opened from the Recordings page take the same route.
Agent Prompt
## Issue description
System Back from a recording opened in Events navigates to Recordings instead of its origin.
## Fix Focus Areas
- src/OpenIPC.Viewer.App/ViewModels/MainWindowViewModel.cs[219-225]
- src/OpenIPC.Viewer.App/ViewModels/MainWindowViewModel.cs[304-326]
## Recommended Fix
Store the originating page when opening a recording from a non-player page, preserve it across previous/next recording changes, and return to it on system Back after disposing the player.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Foreground="{StaticResource TextPrimaryBrush}">

<Grid RowDefinitions="Auto,Auto,*,Auto" Margin="24" MinWidth="420">
<Grid RowDefinitions="Auto,Auto,*,Auto" Margin="24" MinWidth="300">

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remediation recommended

4. Health center still overflows narrow phones 🐞 Bug ≡ Correctness

HealthCenterContent replaces its grid’s 420-pixel minimum width with a fixed 300-pixel minimum
while retaining a 24-pixel margin on each side. On a 320-pixel-wide viewport, the grid requires at
least 348 pixels, so the sheet still cannot fit its content within the screen.
Agent Prompt
## Issue description
The Health Center grid’s minimum width plus its margins still exceeds a narrow mobile viewport.
## Fix Focus Areas
- src/OpenIPC.Viewer.App/Views/Dialogs/HealthCenterContent.axaml[14-14]
## Recommended Fix
Remove the fixed minimum width or make the grid and its margins responsive to available width. Verify that the sheet fits a 320-pixel-wide viewport.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

@keyldev
keyldev deleted the fix/mobile-width-and-swipe branch October 1, 2026 23:07
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.

1 participant