diff --git a/src/PostIt/PostIt/App.axaml.cs b/src/PostIt/PostIt/App.axaml.cs index b474b37b..84312064 100644 --- a/src/PostIt/PostIt/App.axaml.cs +++ b/src/PostIt/PostIt/App.axaml.cs @@ -69,7 +69,6 @@ public partial class App : Application services.AddSingleton(api); services.AddSingleton(client); services.AddTransient(); - services.AddTransient(); services.AddTransient(); services.AddTransient(); diff --git a/src/PostIt/PostIt/Settings/AuthenticationSettings.cs b/src/PostIt/PostIt/Settings/AuthenticationSettings.cs index 9ece1e45..ad71063b 100644 --- a/src/PostIt/PostIt/Settings/AuthenticationSettings.cs +++ b/src/PostIt/PostIt/Settings/AuthenticationSettings.cs @@ -1,5 +1,6 @@ using CommunityToolkit.Mvvm.ComponentModel; using System; +using System.Text.Json.Serialization; public partial class AuthenticationSettings : ObservableObject { @@ -40,4 +41,82 @@ public partial class AuthenticationSettings : ObservableObject [ObservableProperty] public partial string RedirectUri { get; set; } = DefaultDesktopRedirectUri; + /// + /// Space-separated view of . Exists for the + /// SettingsPage TextBox binding — a string[] does not + /// round-trip through XAML binding to TextBox.Text, so we + /// expose the array as a string here and re-parse on assignment. + /// + /// [JsonIgnore] on purpose: is the + /// persisted shape (matches the on-disk format in + /// postit-settings.json and the runtime contract in + /// ). + /// Writing this property back to disk would duplicate the + /// information and confuse the deserializer. + /// + /// + [JsonIgnore] + [ObservableProperty] + public partial string ScopeListText { get; set; } = string.Empty; + + /// + /// Refresh from so + /// the TextBox shows the current persisted state after a Load(). + /// Called from Settings.ApplyJson on each disk / embedded + /// hydration; the source generator's OnScopesChanged partial + /// below keeps the two in sync in the other direction (edits made + /// in the TextBox). + /// + public void RefreshScopeListText() + { + ScopeListText = Scopes is null ? string.Empty : string.Join(' ', Scopes); + } + + partial void OnScopeListTextChanged(string value) + { + if (Scopes is null) + { + Scopes = Array.Empty(); + } + // Split on any whitespace, drop empties. Matches what + // string.Join(' ', Scopes) produces when Scopes is null-free, + // so a round-trip (Display → Edit → Display) is lossless + // for sane inputs. + var parts = value?.Split( + new[] { ' ', '\t', '\n', '\r' }, + StringSplitOptions.RemoveEmptyEntries) ?? Array.Empty(); + + // Skip the write if the parsed array is equal to the current + // one — avoids a PropertyChanged loop between OnScopesChanged + // and OnScopeListTextChanged when RefreshScopeListText runs. + if (Scopes is not null && Scopes.Length == parts.Length) + { + var same = true; + for (var i = 0; i < parts.Length; i++) + { + if (!string.Equals(Scopes[i], parts[i], StringComparison.Ordinal)) + { + same = false; + break; + } + } + if (same) return; + } + Scopes = parts; + } + + partial void OnScopesChanged(string[] value) + { + // Keep ScopeListText in sync when Scopes is reassigned from + // outside (JSON hydration, MergeScopes, programmatic + // updates). Compute the new value and only fire if it + // differs from what's already shown, otherwise the TextBox + // would briefly flicker / re-set the caret on every load. + var newText = value is null ? string.Empty : string.Join(' ', value); + if (!string.Equals(ScopeListText, newText, StringComparison.Ordinal)) + { + ScopeListText = newText; + } + } + } diff --git a/src/PostIt/PostIt/ViewModels/Settings.cs b/src/PostIt/PostIt/ViewModels/Settings.cs index 98085f0c..5bd3a844 100644 --- a/src/PostIt/PostIt/ViewModels/Settings.cs +++ b/src/PostIt/PostIt/ViewModels/Settings.cs @@ -361,6 +361,15 @@ public partial class Settings : ViewModelBase // triggered by the assignments above doesn't leave it // stuck at true. IsDirty = false; + // Refresh the space-separated ScopeListText view after + // hydration so the SettingsPage TextBox reflects the + // loaded scopes (and not the default empty string the + // ObservableProperty was constructed with). OnScopesChanged + // already tries to do this, but it skips when the new + // array parses to the same text — calling explicitly + // forces a re-sync and normalises any whitespace the + // JSON might have introduced. + this.Authentication?.RefreshScopeListText(); // Re-notify the command in case the button was bound // before Load finished and the CanExecute cache is // stale. diff --git a/src/PostIt/PostIt/Views/SettingsPage.axaml b/src/PostIt/PostIt/Views/SettingsPage.axaml index aeba19f0..5d18f8ad 100644 --- a/src/PostIt/PostIt/Views/SettingsPage.axaml +++ b/src/PostIt/PostIt/Views/SettingsPage.axaml @@ -20,6 +20,8 @@ + + @@ -30,25 +32,29 @@ - - + + + + - - + - - + + -