feat: redesign Profiles pane following design philosophy#492
Conversation
Replaces the list-view + flyout + modal-dialog workflow with a unified dark Profiles pane: card tiles with ambient status stripes, and a single inline settings drawer for both editing and creating profiles. New files: - ProfilesPaneViewModel – owns drawer state, activation logic (two-pass device matching), inline error messages, and new-profile creation form - ProfilesPane.xaml / .xaml.cs – dark surface with profile cards and a right-side 400 px inline drawer (edit mode + new-profile mode) MainWindow.xaml: replaces the legacy ListView + FAB + flyout content in the Profiles tab with a single <local:ProfilesPane/> element. The legacy UpdateProfileFlyout, AddprofileDialog, and AddProfileConfirmationDialog are kept in place but no longer reachable from the new pane. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Double-check follow-ups on the Profiles pane redesign:
- Logging-active lockout: block OpenEditDrawer, OpenNewDrawer, and
ActivateProfile when logging is running; dim tiles with a "no"
cursor and disable "+ ADD PROFILE" in both populated and empty
states. Ordered the IsLoggingActive trigger last so hover can't
un-dim a locked tile.
- Replaced instructional status bar text with ambient state:
"{N} SAVED · {name} ACTIVE" plus an amber "LOGGING · LOCKED"
badge bound to IsLoggingActive.
- SaveCurrentSettings now uses NewProfileName when non-empty; the
date-stamped name is just the fallback.
- Guard empty-name in CloseDrawer before UpdateProfileInXml.
- Drawer width 400 → 380 to match the Channels pane.
- ShowError no longer delegates to OpenEditDrawer, so clearing and
setting DrawerError aren't order-dependent.
- ActiveProfileName property with proper per-profile PropertyChanged
subscribe/unsubscribe in Cleanup.
Deleted code made dead by the redesign:
- UpdateProfileFlyout, AddprofileDialog, AddProfileConfirmationDialog
(views, code-behind, and view models).
- DaqifiViewModel: ShowAddProfileDialog, ShowAddProfileConfirmation,
GetUpdateProfileAvailableDevice, RemoveProfile, OpenProfileSettings,
GetAvailableChannels, SaveExistingSetting, ActivateProfile (all old
RelayCommands) plus SelectedProfile, IsProfileSettingsOpen,
UpdateProfileSelectedDevice, AvailableDevices, AvailableChannels
fields/properties; removed the flyout declaration from MainWindow
and the IsProfileSettingsOpen clear in CloseFlyouts.
- NewProfileChannelItem.TypeLabel (unused).
Net -1250 lines.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…n-ce793b # Conflicts: # Daqifi.Desktop/MainWindow.xaml # Daqifi.Desktop/ViewModels/DaqifiViewModel.cs
…evice LoadProfilesFromXml used `?.ToList()` for ProfileDevice.Channels, so when the XML has no <Channels> element (which the writer intentionally omits for devices with zero active channels — see UpdateProfileInXml:247-250 and AddAndRemoveProfileXml:298-306), Channels ends up null on the loaded profile. That nulled any caller of pd.Channels.Where/.Select. Repro: activate profile A → deactivate → click tile for profile B → ArgumentNullException at ProfilesPaneViewModel.ActivateProfile line 250 (`pd.Channels.Where(c => c.IsChannelActive)`). Normalize to an empty list at load time so every downstream caller can trust the invariant. Also fixes two latent crashes on the same field in UpdateProfileInXml:239 and AddAndRemoveProfileXml:299. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
… profile
Two unrelated fixes to the Profiles pane activate flow.
1. Frequency slider cap
The new-profile and edit-profile sliders allowed up to 10000Hz, but the
device (matching the Devices pane's own cap at
DevicesPanePrototype.xaml:678) only accepts up to 1000Hz. Profiles saved
above 1000 would silently fail when pushed to the device. Align both
ProfilesPane sliders to Maximum="1000".
2. Activating a different profile now offers to switch
Previously, clicking a second profile while one was active opened the
drawer with "Deactivate the current active profile first." — forcing
two clicks for the common case. Now the user gets a yes/no dialog
("'A' is currently active. Switch to 'B'?") and on confirm we
deactivate A and activate B in one action. We still validate that B has
matching devices *before* asking, so canceling or a mismatch never
leaves the user with nothing active.
Refactored the matching + apply logic into MatchProfileToConnected and
ApplyProfileToDevices helpers so the switch path reuses the same two-pass
(serial → model) matching as a fresh activate. ActivateProfileCommand is
now an AsyncRelayCommand<Profile> so the confirm dialog can await.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The MahApps MessageDialog (white card, blue theme) doesn't match the
dark, tile-based design system — it reads as a browser alert in the
middle of a carefully designed dark surface. Replace it with an
in-pane overlay styled with the shared design tokens:
- Scrim over everything (same #A0000000 as the drawer scrim;
click-to-dismiss behaves like a Cancel)
- 420px centered card on SurfaceRaised with a BorderDim stroke
- Title in TextPrimary, message in TextSecondary
- Cancel (PillButton) + affirmative (AccentPillButton) right-aligned
- Panel.ZIndex=20 so it sits above the profile drawer (ZIndex=10)
ViewModel now owns the dialog state (IsConfirmOpen, ConfirmTitle,
ConfirmMessage, ConfirmAffirmativeLabel) and exposes affirmative /
negative commands. A private TaskCompletionSource<bool> bridges the
async await surface: ShowConfirm returns Task<bool>, the two button
commands call CompleteConfirm which closes the dialog and resolves
the TCS. Cleanup resolves a pending TCS defensively so no awaiter is
leaked on unload.
Removes the MahApps.Metro.Controls / Dialogs usings — the VM no longer
needs to look up the MainWindow to show a dialog.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Review Summary by QodoRedesign Profiles pane with dark tiles, inline drawer, and unified workflow
WalkthroughsDescription• Redesign Profiles pane with dark tile-based UI, inline 380px drawer, and ambient status bar • Replace modal dialogs and flyouts with unified in-pane workflow for profile creation, editing, and activation • Add two-pass device matching (serial number then model) and atomic profile switching with confirmation • Fix null Channels crash when profile device has zero active channels; cap frequency slider at 1000Hz • Remove ~1250 lines of dead code (UpdateProfileFlyout, AddprofileDialog, AddProfileConfirmationDialog VMs) • Block profile operations and dim tiles with "no" cursor when logging is active; show "LOGGING · LOCKED" badge Diagramflowchart LR
A["Old UI<br/>ListView + Flyout<br/>+ Modal Dialogs"] -->|Replace| B["New ProfilesPane<br/>Dark Tiles<br/>+ Inline Drawer"]
B -->|Owns| C["ProfilesPaneViewModel<br/>Activation Logic<br/>Device Matching<br/>Profile CRUD"]
C -->|Two-Pass Match| D["Profile to Device<br/>Serial Number<br/>then Model"]
C -->|Atomic Switch| E["Confirm Dialog<br/>Deactivate Old<br/>Activate New"]
F["LoggingManager"] -->|Null Channels| G["Fix: Default to<br/>Empty List"]
H["Frequency Slider"] -->|Cap| I["1000 Hz Max"]
File Changes1. Daqifi.Desktop/Loggers/LoggingManager.cs
|
Code Review by Qodo
1.
|
- Fix deactivate-order bug in ApplyProfileToDevices: call LoggingManager.Unsubscribe BEFORE AbstractStreamingDevice.RemoveAllChannels. Unsubscribe filters its lookup by IChannel.IsActive, and RemoveAllChannels clears IsActive on every channel, so the previous order caused Unsubscribe to silently no-op and leave stale subscriptions behind. - Split NewProfileDeviceItem and NewProfileChannelItem into their own files so ProfilesPaneViewModel.cs hosts a single public class. - Add #region grouping throughout ProfilesPaneViewModel (fields, properties, commands, constructor, handlers, drawer lifecycle, activation, confirm, CRUD, helpers, cleanup). - Fix two Allman-brace violations in MatchProfileToConnected. - Add XML docs on every public property, command, and method. - Add XML docs on the ProfilesPane code-behind class and constructor. - Replace [ObservableProperty] _hasDrawerError + OnDrawerErrorChanged partial with [NotifyPropertyChangedFor(nameof(HasDrawerError))] on _drawerError and a computed HasDrawerError property — single source of truth. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
All 6 inline Qodo items addressed in 2386e3d (threaded replies posted on each). Also fixed the summary-only item from the Qodo review comment ("Missing
Net effect: single source of truth ( |
📊 Code Coverage ReportSummarySummary
CoverageDAQiFi - 17.7%
Daqifi.Desktop.Common - 30.8%
Daqifi.Desktop.IO - 100%
Coverage report generated by ReportGenerator • View full report in build artifacts |
Summary
Redesigns the Profiles pane to match the design philosophy used by the Channels pane (#479) and Devices pane (#484) — dark, tile-based, inline drawer instead of a flyout, ambient status instead of modal errors. Also sweeps out the pre-redesign code that the new UI replaces, and addresses follow-up bugs and polish found during testing.
LOGGING · LOCKEDbadge when a session is running. Tiles dim with a no-cursor and the+ ADD PROFILEbutton is disabled while logging is active.UpdateProfileFlyout,AddprofileDialog,AddProfileConfirmationDialogand their VMs (~1250 lines).DaqifiViewModelno longer owns any profile state or commands.Channelscrash when activating a profile whose device has zero active channels (?.ToList()inLoadProfilesFromXmlleftProfileDevice.Channelsnull when the writer had intentionally omitted the<Channels>element). Normalized to empty list at load time.Maximum="1000".ProfilesPane.xamlto use the shared tokens fromResources/DesignTokens.xaml(merged from main) — brushes are no longer duplicated locally, and the hardcoded amber on the LOGGING · LOCKED badge usesStatusAmber.ShowMessageAsyncwith an in-pane overlay (scrim +SurfaceRaisedcard + pill buttons) wired to the VM via aTaskCompletionSource<bool>bridge.Test plan
{N} SAVED · {name} ACTIVE+ ADD PROFILEdisables,LOGGING · LOCKEDbadge appears, clicking a tile or gear is a silent no-op🤖 Generated with Claude Code