diff --git a/.filenesting.json b/.filenesting.json new file mode 100644 index 0000000..7a09e65 --- /dev/null +++ b/.filenesting.json @@ -0,0 +1,14 @@ +{ + "help": "https://go.microsoft.com/fwlink/?linkid=866610", + "dependentFileProviders": { + "add": { + "pathSegment": { + "add": { + ".*": [ + ".cs" + ] + } + } + } + } +} diff --git a/CoreAppUWP/Controls/Setting/Setting.Properties.cs b/CoreAppUWP/Controls/Setting/Setting.Properties.cs deleted file mode 100644 index 0adf19f..0000000 --- a/CoreAppUWP/Controls/Setting/Setting.Properties.cs +++ /dev/null @@ -1,281 +0,0 @@ -using Microsoft.UI.Xaml; -using System; -using System.ComponentModel; - -namespace CoreAppUWP.Controls -{ - public partial class Setting - { - /// - /// Gets or sets the content of a ContentControl. - /// - /// An object that contains the control's content. The default is . - [Obsolete("Use Content instead of ActionContent.")] - public object ActionContent => Content; - - #region Header - - /// - /// Identifies the dependency property. - /// - public static readonly DependencyProperty HeaderProperty = - DependencyProperty.Register( - nameof(Header), - typeof(object), - typeof(Setting), - new PropertyMetadata(null, OnHeaderPropertyChanged)); - - /// - /// Gets or sets the Header. - /// - [Localizable(true)] - public object Header - { - get => GetValue(HeaderProperty); - set => SetValue(HeaderProperty, value); - } - - private static void OnHeaderPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) - { - if (e.NewValue != e.OldValue) - { - ((Setting)d).OnHeaderChanged(); - } - } - - #endregion - - #region Description - - /// - /// Identifies the dependency property. - /// - public static readonly DependencyProperty DescriptionProperty = - DependencyProperty.Register( - nameof(Description), - typeof(object), - typeof(Setting), - new PropertyMetadata(null, OnDescriptionPropertyChanged)); - - /// - /// Gets or sets the description. - /// - [Localizable(true)] - public object Description - { - get => GetValue(DescriptionProperty); - set => SetValue(DescriptionProperty, value); - } - - private static void OnDescriptionPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) - { - if (e.NewValue != e.OldValue) - { - ((Setting)d).OnDescriptionChanged(); - } - } - - #endregion - - #region Icon - - /// - /// Identifies the dependency property. - /// - public static readonly DependencyProperty IconProperty = - DependencyProperty.Register( - nameof(Icon), - typeof(object), - typeof(Setting), - new PropertyMetadata(null, OnIconPropertyChanged)); - - /// - /// Gets or sets the icon on the left. - /// - public object Icon - { - get => GetValue(IconProperty); - set => SetValue(IconProperty, value); - } - - private static void OnIconPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) - { - if (e.NewValue != e.OldValue) - { - ((Setting)d).OnHeaderIconChanged(); - } - } - - #endregion - - #region ActionIcon - - /// - /// Identifies the dependency property. - /// - public static readonly DependencyProperty ActionIconProperty = - DependencyProperty.Register( - nameof(ActionIcon), - typeof(object), - typeof(Setting), - new PropertyMetadata(null, OnActionIconPropertyChanged)); - - /// - /// Gets or sets the icon that is shown when IsClickEnabled is set to true. - /// - public object ActionIcon - { - get => GetValue(ActionIconProperty); - set => SetValue(ActionIconProperty, value); - } - - private static void OnActionIconPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) - { - if (e.NewValue != e.OldValue) - { - ((Setting)d).OnButtonIconChanged(); - } - } - - #endregion - - #region ActionIconToolTip - - /// - /// Identifies the dependency property. - /// - public static readonly DependencyProperty ActionIconToolTipProperty = - DependencyProperty.Register( - nameof(ActionIconToolTip), - typeof(string), - typeof(Setting), - new PropertyMetadata("More")); - - /// - /// Gets or sets the tooltip of the ActionIcon. - /// - public string ActionIconToolTip - { - get => (string)GetValue(ActionIconToolTipProperty); - set => SetValue(ActionIconToolTipProperty, value); - } - - #endregion - - #region IsClickEnabled - - /// - /// Identifies the dependency property. - /// - public static readonly DependencyProperty IsClickEnabledProperty = - DependencyProperty.Register( - nameof(IsClickEnabled), - typeof(bool), - typeof(Setting), - new PropertyMetadata(false, OnIsClickEnabledPropertyChanged)); - - /// - /// Gets or sets if the card can be clicked. - /// - public bool IsClickEnabled - { - get => (bool)GetValue(IsClickEnabledProperty); - set => SetValue(IsClickEnabledProperty, value); - } - - private static void OnIsClickEnabledPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) - { - if (e.NewValue != e.OldValue) - { - ((Setting)d).OnIsClickEnabledChanged(); - } - } - - #endregion - - #region ContentAlignment - - /// - /// Identifies the dependency property. - /// - public static readonly DependencyProperty ContentAlignmentProperty = - DependencyProperty.Register( - nameof(ContentAlignment), - typeof(ContentAlignment), - typeof(Setting), - new PropertyMetadata(ContentAlignment.Right)); - - /// - /// Gets or sets the alignment of the Content. - /// - public ContentAlignment ContentAlignment - { - get => (ContentAlignment)GetValue(ContentAlignmentProperty); - set => SetValue(ContentAlignmentProperty, value); - } - - #endregion - - #region WrapThreshold - - /// - /// Identifies the dependency property. - /// - public static readonly DependencyProperty WrapThresholdProperty = - DependencyProperty.Register( - nameof(WrapThreshold), - typeof(double), - typeof(Setting), - new PropertyMetadata(476)); - - /// - /// Gets or sets the threshold of wrap. - /// - public double WrapThreshold - { - get => (double)GetValue(WrapThresholdProperty); - set => SetValue(WrapThresholdProperty, value); - } - - #endregion - - #region WrapNoIconThreshold - - /// - /// Identifies the dependency property. - /// - public static readonly DependencyProperty WrapNoIconThresholdProperty = - DependencyProperty.Register( - nameof(WrapNoIconThreshold), - typeof(double), - typeof(Setting), - new PropertyMetadata(286)); - - /// - /// Gets or sets the threshold of wrap with no icon. - /// - public double WrapNoIconThreshold - { - get => (double)GetValue(WrapNoIconThresholdProperty); - set => SetValue(WrapNoIconThresholdProperty, value); - } - - #endregion - } - - public enum ContentAlignment - { - /// - /// The Content is aligned to the right. Default state. - /// - Right, - /// - /// The Content is left-aligned while the Header, HeaderIcon and Description are collapsed. This is commonly used for Content types such as CheckBoxes, RadioButtons and custom layouts. - /// - Left, - /// - /// The Content is vertically aligned. - /// - Vertical - } -} diff --git a/CoreAppUWP/Controls/Setting/Setting.ThemeResources.xaml b/CoreAppUWP/Controls/Setting/Setting.ThemeResources.xaml deleted file mode 100644 index 0a7b2e2..0000000 --- a/CoreAppUWP/Controls/Setting/Setting.ThemeResources.xaml +++ /dev/null @@ -1,890 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 1 - 16,16,16,16 - 2,0,18,0 - 148 - 32 - 32 - 32 - 12 - 20 - 13 - 0 - 240 - 2,0,20,0 - 0,8,0,0 - 476 - 286 - - - - 0,0,4,0 - - - - - - - - diff --git a/CoreAppUWP/Controls/Setting/Setting.cs b/CoreAppUWP/Controls/Setting/Setting.cs deleted file mode 100644 index 612b34c..0000000 --- a/CoreAppUWP/Controls/Setting/Setting.cs +++ /dev/null @@ -1,216 +0,0 @@ -using Microsoft.UI.Xaml; -using Microsoft.UI.Xaml.Automation; -using Microsoft.UI.Xaml.Automation.Peers; -using Microsoft.UI.Xaml.Controls; -using Microsoft.UI.Xaml.Controls.Primitives; -using Microsoft.UI.Xaml.Input; - -// To learn more about WinUI, the WinUI project structure, -// and more about our project templates, see: http://aka.ms/winui-project-info. - -namespace CoreAppUWP.Controls -{ - /// - /// This is the base control to create consistent settings experiences, inline with the Windows 11 design language. - /// A Setting can also be hosted within a SettingExpander. - /// - public partial class Setting : ButtonBase - { - internal const string NormalState = "Normal"; - internal const string PointerOverState = "PointerOver"; - internal const string PressedState = "Pressed"; - internal const string DisabledState = "Disabled"; - - internal const string ContentPresenter = "PART_ContentPresenter"; - internal const string HeaderIconPresenterHolder = "PART_HeaderIconPresenterHolder"; - - /// - /// Creates a new instance of the class. - /// - public Setting() - { - DefaultStyleKey = typeof(Setting); - } - - /// - protected override void OnApplyTemplate() - { - base.OnApplyTemplate(); - IsEnabledChanged -= OnIsEnabledChanged; - OnButtonIconChanged(); - OnHeaderChanged(); - OnHeaderIconChanged(); - OnDescriptionChanged(); - OnContentChanged(); - OnIsClickEnabledChanged(); - _ = VisualStateManager.GoToState(this, IsEnabled ? NormalState : DisabledState, true); - RegisterAutomation(); - IsEnabledChanged += OnIsEnabledChanged; - } - - private void RegisterAutomation() - { - if (Header is string headerString && headerString != string.Empty) - { - AutomationProperties.SetName(this, headerString); - // We don't want to override an AutomationProperties.Name that is manually set, or if the Content basetype is of type ButtonBase (the ButtonBase.Content will be used then) - if (Content is UIElement element - && string.IsNullOrEmpty(AutomationProperties.GetName(element)) - && element is not ButtonBase or TextBlock) - { - AutomationProperties.SetName(element, headerString); - } - } - } - - private void EnableButtonInteraction() - { - DisableButtonInteraction(); - - PointerEntered += Control_PointerEntered; - PointerExited += Control_PointerExited; - PointerCaptureLost += Control_PointerCaptureLost; - PointerCanceled += Control_PointerCanceled; - PreviewKeyDown += Control_PreviewKeyDown; - PreviewKeyUp += Control_PreviewKeyUp; - } - - private void DisableButtonInteraction() - { - PointerEntered -= Control_PointerEntered; - PointerExited -= Control_PointerExited; - PointerCaptureLost -= Control_PointerCaptureLost; - PointerCanceled -= Control_PointerCanceled; - PreviewKeyDown -= Control_PreviewKeyDown; - PreviewKeyUp -= Control_PreviewKeyUp; - } - - private void Control_PreviewKeyUp(object sender, KeyRoutedEventArgs e) - { - if (e.Key is Windows.System.VirtualKey.Enter or Windows.System.VirtualKey.Space or Windows.System.VirtualKey.GamepadA) - { - _ = VisualStateManager.GoToState(this, NormalState, true); - } - } - - private void Control_PreviewKeyDown(object sender, KeyRoutedEventArgs e) - { - if (e.Key is Windows.System.VirtualKey.Enter or Windows.System.VirtualKey.Space or Windows.System.VirtualKey.GamepadA) - { - _ = VisualStateManager.GoToState(this, PressedState, true); - } - } - - public void Control_PointerEntered(object sender, PointerRoutedEventArgs e) - { - base.OnPointerEntered(e); - _ = VisualStateManager.GoToState(this, PointerOverState, true); - } - - public void Control_PointerExited(object sender, PointerRoutedEventArgs e) - { - base.OnPointerExited(e); - _ = VisualStateManager.GoToState(this, NormalState, true); - } - - private void Control_PointerCaptureLost(object sender, PointerRoutedEventArgs e) - { - base.OnPointerCaptureLost(e); - _ = VisualStateManager.GoToState(this, NormalState, true); - } - - private void Control_PointerCanceled(object sender, PointerRoutedEventArgs e) - { - base.OnPointerCanceled(e); - _ = VisualStateManager.GoToState(this, NormalState, true); - } - - protected override void OnPointerPressed(PointerRoutedEventArgs e) - { - // e.Handled = true; - if (IsClickEnabled) - { - base.OnPointerPressed(e); - _ = VisualStateManager.GoToState(this, PressedState, true); - } - } - - protected override void OnPointerReleased(PointerRoutedEventArgs e) - { - if (IsClickEnabled) - { - base.OnPointerReleased(e); - _ = VisualStateManager.GoToState(this, NormalState, true); - } - } - - protected override void OnContentChanged(object oldContent, object newContent) - { - base.OnContentChanged(oldContent, newContent); - OnContentChanged(); - } - - /// - /// Creates AutomationPeer - /// - /// An automation peer for . - protected override AutomationPeer OnCreateAutomationPeer() - { - return new SettingAutomationPeer(this); - } - - private void OnIsClickEnabledChanged() - { - OnButtonIconChanged(); - if (IsClickEnabled) - { - EnableButtonInteraction(); - } - else - { - DisableButtonInteraction(); - } - } - - private void OnIsEnabledChanged(object sender, DependencyPropertyChangedEventArgs e) - { - _ = VisualStateManager.GoToState(this, IsEnabled ? NormalState : DisabledState, true); - } - - - public void OnButtonIconChanged() - { - _ = VisualStateManager.GoToState(this, IsClickEnabled && ActionIcon != null ? "ActionIconVisible" : "ActionIconCollapsed", false); - } - - public void OnHeaderIconChanged() - { - if (GetTemplateChild(HeaderIconPresenterHolder) is FrameworkElement headerIconPresenter) - { - headerIconPresenter.Visibility = Icon != null - ? Visibility.Visible - : Visibility.Collapsed; - } - } - - public void OnContentChanged() - { - if (GetTemplateChild(ContentPresenter) is FrameworkElement contentPresenter) - { - contentPresenter.Visibility = Content != null - ? Visibility.Visible - : Visibility.Collapsed; - } - } - - public void OnDescriptionChanged() - { - _ = VisualStateManager.GoToState(this, Description == null ? "DescriptionCollapsed" : "DescriptionVisible", false); - } - - public void OnHeaderChanged() - { - _ = VisualStateManager.GoToState(this, Header == null ? "HeaderCollapsed" : "HeaderVisible", false); - } - } -} diff --git a/CoreAppUWP/Controls/Setting/Setting.xaml b/CoreAppUWP/Controls/Setting/Setting.xaml deleted file mode 100644 index 218f619..0000000 --- a/CoreAppUWP/Controls/Setting/Setting.xaml +++ /dev/null @@ -1,319 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/CoreAppUWP/Controls/Setting/SettingAutomationPeer.cs b/CoreAppUWP/Controls/Setting/SettingAutomationPeer.cs deleted file mode 100644 index 444418d..0000000 --- a/CoreAppUWP/Controls/Setting/SettingAutomationPeer.cs +++ /dev/null @@ -1,34 +0,0 @@ -using Microsoft.UI.Xaml.Automation.Peers; - -namespace CoreAppUWP.Controls -{ - /// - /// AutomationPeer for SettingsCard - /// - /// Setting - public class SettingAutomationPeer(Setting owner) : ButtonBaseAutomationPeer(owner) - { - /// - /// Gets the control type for the element that is associated with the UI Automation peer. - /// - /// The control type. - protected override AutomationControlType GetAutomationControlTypeCore() - { - return AutomationControlType.Group; - } - - /// - /// Called by GetClassName that gets a human readable name that, in addition to AutomationControlType, - /// differentiates the control represented by this AutomationPeer. - /// - /// The string that contains the name. - protected override string GetClassNameCore() - { - string classNameCore = Owner.GetType().Name; -#if DEBUG_AUTOMATION - System.Diagnostics.Debug.WriteLine("SettingsCardAutomationPeer.GetClassNameCore returns " + classNameCore); -#endif - return classNameCore; - } - } -} diff --git a/CoreAppUWP/Controls/SettingExpander/SettingExpander.Properties.cs b/CoreAppUWP/Controls/SettingExpander/SettingExpander.Properties.cs deleted file mode 100644 index 71f77eb..0000000 --- a/CoreAppUWP/Controls/SettingExpander/SettingExpander.Properties.cs +++ /dev/null @@ -1,195 +0,0 @@ -using Microsoft.UI.Xaml; -using System.ComponentModel; - -namespace CoreAppUWP.Controls -{ - public partial class SettingExpander - { - #region Header - - /// - /// Identifies the dependency property. - /// - public static readonly DependencyProperty HeaderProperty = - DependencyProperty.Register( - nameof(Header), - typeof(object), - typeof(SettingExpander), - new PropertyMetadata(null)); - - /// - /// Gets or sets the Header. - /// - [Localizable(true)] - public object Header - { - get => GetValue(HeaderProperty); - set => SetValue(HeaderProperty, value); - } - - #endregion - - #region Description - - /// - /// Identifies the dependency property. - /// - public static readonly DependencyProperty DescriptionProperty = - DependencyProperty.Register( - nameof(Description), - typeof(object), - typeof(SettingExpander), - new PropertyMetadata(null)); - - /// - /// Gets or sets the Description. - /// - [Localizable(true)] - public object Description - { - get => GetValue(DescriptionProperty); - set => SetValue(DescriptionProperty, value); - } - - #endregion - - #region ActionContent - - /// - /// Identifies the dependency property. - /// - public static readonly DependencyProperty ActionContentProperty = - DependencyProperty.Register( - nameof(ActionContent), - typeof(object), - typeof(SettingExpander), - new PropertyMetadata(null)); - - /// - /// Gets or sets the content of a Setting. - /// - /// An object that contains the setting's content. The default is . - public object ActionContent - { - get => GetValue(ActionContentProperty); - set => SetValue(ActionContentProperty, value); - } - - #endregion - - #region Icon - - /// - /// Identifies the dependency property. - /// - public static readonly DependencyProperty IconProperty = - DependencyProperty.Register( - nameof(Icon), - typeof(object), - typeof(SettingExpander), - new PropertyMetadata(default(string))); - - /// - /// Gets or sets the icon on the left. - /// - public object Icon - { - get => GetValue(IconProperty); - set => SetValue(IconProperty, value); - } - - #endregion - - #region ContentAlignment - - /// - /// Identifies the dependency property. - /// - public static readonly DependencyProperty ContentAlignmentProperty = - DependencyProperty.Register( - nameof(ContentAlignment), - typeof(ContentAlignment), - typeof(SettingExpander), - new PropertyMetadata(ContentAlignment.Right)); - - /// - /// Gets or sets the alignment of the Content - /// - public ContentAlignment ContentAlignment - { - get => (ContentAlignment)GetValue(ContentAlignmentProperty); - set => SetValue(ContentAlignmentProperty, value); - } - - #endregion - - #region ItemsHeader - - /// - /// Identifies the dependency property. - /// - public static readonly DependencyProperty ItemsHeaderProperty = - DependencyProperty.Register( - nameof(ItemsHeader), - typeof(UIElement), - typeof(SettingExpander), - new PropertyMetadata(null)); - - /// - /// Gets or sets the ItemsFooter. - /// - public UIElement ItemsHeader - { - get => (UIElement)GetValue(ItemsHeaderProperty); - set => SetValue(ItemsHeaderProperty, value); - } - - #endregion - - #region ItemsFooter - - /// - /// Identifies the dependency property. - /// - public static readonly DependencyProperty ItemsFooterProperty = - DependencyProperty.Register( - nameof(ItemsFooter), - typeof(UIElement), - typeof(SettingExpander), - new PropertyMetadata(null)); - - /// - /// Gets or sets the ItemsFooter. - /// - public UIElement ItemsFooter - { - get => (UIElement)GetValue(ItemsFooterProperty); - set => SetValue(ItemsFooterProperty, value); - } - - #endregion - - #region IsExpanded - - /// - /// Identifies the dependency property. - /// - public static readonly DependencyProperty IsExpandedProperty = - DependencyProperty.Register( - nameof(IsExpanded), - typeof(bool), - typeof(SettingExpander), - new PropertyMetadata(false)); - - /// - /// Gets or sets the IsExpanded state. - /// - public bool IsExpanded - { - get => (bool)GetValue(IsExpandedProperty); - set => SetValue(IsExpandedProperty, value); - } - - #endregion - } -} diff --git a/CoreAppUWP/Controls/SettingExpander/SettingExpander.ThemeResources.xaml b/CoreAppUWP/Controls/SettingExpander/SettingExpander.ThemeResources.xaml deleted file mode 100644 index 4853eed..0000000 --- a/CoreAppUWP/Controls/SettingExpander/SettingExpander.ThemeResources.xaml +++ /dev/null @@ -1,84 +0,0 @@ - - - 0 - 8,0 - - Show all settings - 0,16,0,16 - 40,16,0,16 - 58,8,44,8 - 0,1,0,0 - 58,8,16,8 - - 410 - 220 - - 474 - 284 - - - - - - - - - - - - - - - - diff --git a/CoreAppUWP/Controls/SettingExpander/SettingExpander.cs b/CoreAppUWP/Controls/SettingExpander/SettingExpander.cs deleted file mode 100644 index c49123e..0000000 --- a/CoreAppUWP/Controls/SettingExpander/SettingExpander.cs +++ /dev/null @@ -1,50 +0,0 @@ -using Microsoft.UI.Xaml.Automation; -using Microsoft.UI.Xaml.Automation.Peers; -using Microsoft.UI.Xaml.Controls; - -// To learn more about WinUI, the WinUI project structure, -// and more about our project templates, see: http://aka.ms/winui-project-info. - -namespace CoreAppUWP.Controls -{ - /// - /// The SettingExpander is a collapsable control to host multiple SettingsCards. - /// - public partial class SettingExpander : ItemsControl - { - /// - /// Creates a new instance of the class. - /// - public SettingExpander() - { - DefaultStyleKey = typeof(SettingExpander); - } - - /// - protected override void OnApplyTemplate() - { - base.OnApplyTemplate(); - RegisterAutomation(); - } - - private void RegisterAutomation() - { - if (Header is string headerString && headerString != string.Empty) - { - if (!string.IsNullOrEmpty(headerString) && string.IsNullOrEmpty(AutomationProperties.GetName(this))) - { - AutomationProperties.SetName(this, headerString); - } - } - } - - /// - /// Creates AutomationPeer - /// - /// An automation peer for . - protected override AutomationPeer OnCreateAutomationPeer() - { - return new SettingExpanderAutomationPeer(this); - } - } -} diff --git a/CoreAppUWP/Controls/SettingExpander/SettingExpander.xaml b/CoreAppUWP/Controls/SettingExpander/SettingExpander.xaml deleted file mode 100644 index 0b60c9d..0000000 --- a/CoreAppUWP/Controls/SettingExpander/SettingExpander.xaml +++ /dev/null @@ -1,72 +0,0 @@ - - - - - - - - diff --git a/CoreAppUWP/Controls/SettingExpander/SettingExpanderAutomationPeer.cs b/CoreAppUWP/Controls/SettingExpander/SettingExpanderAutomationPeer.cs deleted file mode 100644 index ce47426..0000000 --- a/CoreAppUWP/Controls/SettingExpander/SettingExpanderAutomationPeer.cs +++ /dev/null @@ -1,41 +0,0 @@ -using Microsoft.UI.Xaml.Automation.Peers; - -namespace CoreAppUWP.Controls -{ - /// - /// AutomationPeer for SettingExpander - /// - public class SettingExpanderAutomationPeer : ItemsControlAutomationPeer - { - /// - /// Initializes a new instance of the class. - /// - /// SettingExpander - public SettingExpanderAutomationPeer(SettingExpander owner) : base(owner) - { - } - - /// - /// Gets the control type for the element that is associated with the UI Automation peer. - /// - /// The control type. - protected override AutomationControlType GetAutomationControlTypeCore() - { - return AutomationControlType.Button; - } - - /// - /// Called by GetClassName that gets a human readable name that, in addition to AutomationControlType, - /// differentiates the control represented by this AutomationPeer. - /// - /// The string that contains the name. - protected override string GetClassNameCore() - { - string classNameCore = Owner.GetType().Name; -#if DEBUG_AUTOMATION - System.Diagnostics.Debug.WriteLine("SettingsCardAutomationPeer.GetClassNameCore returns " + classNameCore); -#endif - return classNameCore; - } - } -} diff --git a/CoreAppUWP/Controls/SettingsExpanderItemStyleSelector.cs b/CoreAppUWP/Controls/SettingsExpanderItemStyleSelector.cs new file mode 100644 index 0000000..e7c2d74 --- /dev/null +++ b/CoreAppUWP/Controls/SettingsExpanderItemStyleSelector.cs @@ -0,0 +1,24 @@ +using CommunityToolkit.WinUI.Controls; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; + +namespace CoreAppUWP.Controls +{ + public class SettingsExpanderItemStyleSelector : CommunityToolkit.WinUI.Controls.SettingsExpanderItemStyleSelector + { + public Style GridStyle { get; set; } + public Style BorderStyle { get; set; } + public Style StackPanelStyle { get; set; } + + protected override Style SelectStyleCore(object item, DependencyObject container) => + container switch + { + SettingsCard card => card.IsClickEnabled ? ClickableStyle : DefaultStyle, + Grid => GridStyle, + Border => BorderStyle, + StackPanel => StackPanelStyle, + FrameworkElement element => element.Style, + _ => null + }; + } +} diff --git a/CoreAppUWP/CoreAppUWP.csproj b/CoreAppUWP/CoreAppUWP.csproj index 5057f90..e89e406 100644 --- a/CoreAppUWP/CoreAppUWP.csproj +++ b/CoreAppUWP/CoreAppUWP.csproj @@ -19,19 +19,18 @@ - - - - + + + + - runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/CoreAppUWP/Helpers/ApplicationDataStorageHelper.CacheFolder.cs b/CoreAppUWP/Helpers/ApplicationDataStorageHelper.CacheFolder.cs new file mode 100644 index 0000000..ca3b260 --- /dev/null +++ b/CoreAppUWP/Helpers/ApplicationDataStorageHelper.CacheFolder.cs @@ -0,0 +1,87 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using CommunityToolkit.Common.Helpers; +using System.Collections.Generic; +using System.Threading.Tasks; +using Windows.Storage; + +namespace CoreAppUWP.Helpers +{ + /// + /// An extension of ApplicationDataStorageHelper with additional features for interop with the LocalCacheFolder. + /// + public partial class ApplicationDataStorageHelper + { + /// + /// Gets the local cache folder. + /// + public StorageFolder CacheFolder => AppData.LocalCacheFolder; + + /// + /// Retrieves an object from a file in the LocalCacheFolder. + /// + /// Type of object retrieved. + /// Path to the file that contains the object. + /// Default value of the object. + /// Waiting task until completion with the object in the file. + public Task ReadCacheFileAsync(string filePath, T @default = default) + { + return ReadFileAsync(CacheFolder, filePath, @default); + } + + /// + /// Retrieves the listings for a folder and the item types in the LocalCacheFolder. + /// + /// The path to the target folder. + /// A list of file types and names in the target folder. + public Task> ReadCacheFolderAsync(string folderPath) + { + return ReadFolderAsync(CacheFolder, folderPath); + } + + /// + /// Saves an object inside a file in the LocalCacheFolder. + /// + /// Type of object saved. + /// Path to the file that will contain the object. + /// Object to save. + /// Waiting task until completion. + public Task CreateCacheFileAsync(string filePath, T value) + { + return CreateFileAsync(CacheFolder, filePath, value); + } + + /// + /// Ensure a folder exists at the folder path specified in the LocalCacheFolder. + /// + /// The path and name of the target folder. + /// Waiting task until completion. + public Task CreateCacheFolderAsync(string folderPath) + { + return CreateFolderAsync(CacheFolder, folderPath); + } + + /// + /// Deletes a file or folder item in the LocalCacheFolder. + /// + /// The path to the item for deletion. + /// Waiting task until completion. + public Task TryDeleteCacheItemAsync(string itemPath) + { + return TryDeleteItemAsync(CacheFolder, itemPath); + } + + /// + /// Rename an item in the LocalCacheFolder. + /// + /// The path to the target item. + /// The new nam for the target item. + /// Waiting task until completion. + public Task TryRenameCacheItemAsync(string itemPath, string newName) + { + return TryRenameItemAsync(CacheFolder, itemPath, newName); + } + } +} diff --git a/CoreAppUWP/Helpers/ApplicationDataStorageHelper.cs b/CoreAppUWP/Helpers/ApplicationDataStorageHelper.cs new file mode 100644 index 0000000..ce631eb --- /dev/null +++ b/CoreAppUWP/Helpers/ApplicationDataStorageHelper.cs @@ -0,0 +1,333 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using CommunityToolkit.Common.Helpers; +using CommunityToolkit.Helpers; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Windows.Storage; +using Windows.System; + +namespace CoreAppUWP.Helpers +{ + /// + /// Storage helper for files and folders living in Windows.Storage.ApplicationData storage endpoints. + /// + public partial class ApplicationDataStorageHelper : IFileStorageHelper, ISettingsStorageHelper + { + /// + /// Initializes a new instance of the class. + /// + /// The data store to interact with. + /// Serializer for converting stored values. Defaults to . + public ApplicationDataStorageHelper(ApplicationData appData, IObjectSerializer objectSerializer = null) + { + AppData = appData ?? throw new ArgumentNullException(nameof(appData)); + Serializer = objectSerializer ?? new SystemSerializer(); + } + + /// + /// Gets the settings container. + /// + public ApplicationDataContainer Settings => AppData.LocalSettings; + + /// + /// Gets the storage folder. + /// + public StorageFolder Folder => AppData.LocalFolder; + + /// + /// Gets the storage host. + /// + protected ApplicationData AppData { get; } + + /// + /// Gets the serializer for converting stored values. + /// + protected IObjectSerializer Serializer { get; } + + /// + /// Get a new instance using ApplicationData.Current and the provided serializer. + /// + /// Serializer for converting stored values. Defaults to . + /// A new instance of ApplicationDataStorageHelper. + public static ApplicationDataStorageHelper GetCurrent(IObjectSerializer objectSerializer = null) + { + ApplicationData appData = ApplicationData.Current; + return new ApplicationDataStorageHelper(appData, objectSerializer); + } + + /// + /// Get a new instance using the ApplicationData for the provided user and serializer. + /// + /// App data user owner. + /// Serializer for converting stored values. Defaults to . + /// A new instance of ApplicationDataStorageHelper. + public static async Task GetForUserAsync(User user, IObjectSerializer objectSerializer = null) + { + ApplicationData appData = await ApplicationData.GetForUserAsync(user); + return new ApplicationDataStorageHelper(appData, objectSerializer); + } + + /// + /// Determines whether a setting already exists. + /// + /// Key of the setting (that contains object). + /// True if a value exists. + public bool KeyExists(string key) + { + return Settings.Values.ContainsKey(key); + } + + /// + /// Retrieves a single item by its key. + /// + /// Type of object retrieved. + /// Key of the object. + /// Default value of the object. + /// The TValue object. + public T Read(string key, T @default = default) + { + return Settings.Values.TryGetValue(key, out object valueObj) && valueObj is string valueString + ? Serializer.Deserialize(valueString) + : @default; + } + + /// + public bool TryRead(string key, out T value) + { + if (Settings.Values.TryGetValue(key, out object valueObj) && valueObj is string valueString) + { + value = Serializer.Deserialize(valueString); + return true; + } + + value = default; + return false; + } + + /// + public void Save(string key, T value) + { + Settings.Values[key] = Serializer.Serialize(value); + } + + /// + public bool TryDelete(string key) + { + return Settings.Values.Remove(key); + } + + /// + public void Clear() + { + Settings.Values.Clear(); + } + + /// + /// Determines whether a setting already exists in composite. + /// + /// Key of the composite (that contains settings). + /// Key of the setting (that contains object). + /// True if a value exists. + public bool KeyExists(string compositeKey, string key) + { + return TryRead(compositeKey, out ApplicationDataCompositeValue composite) && composite != null && composite.ContainsKey(key); + } + + /// + /// Attempts to retrieve a single item by its key in composite. + /// + /// Type of object retrieved. + /// Key of the composite (that contains settings). + /// Key of the object. + /// The value of the object retrieved. + /// The T object. + public bool TryRead(string compositeKey, string key, out T value) + { + if (TryRead(compositeKey, out ApplicationDataCompositeValue composite) && composite != null) + { + string compositeValue = (string)composite[key]; + if (compositeValue != null) + { + value = Serializer.Deserialize(compositeValue); + return true; + } + } + + value = default; + return false; + } + + /// + /// Retrieves a single item by its key in composite. + /// + /// Type of object retrieved. + /// Key of the composite (that contains settings). + /// Key of the object. + /// Default value of the object. + /// The T object. + public T Read(string compositeKey, string key, T @default = default) + { + if (TryRead(compositeKey, out ApplicationDataCompositeValue composite) && composite != null) + { + if (composite.TryGetValue(key, out object valueObj) && valueObj is string value) + { + return Serializer.Deserialize(value); + } + } + + return @default; + } + + /// + /// Saves a group of items by its key in a composite. + /// This method should be considered for objects that do not exceed 8k bytes during the lifetime of the application + /// and for groups of settings which need to be treated in an atomic way. + /// + /// Type of object saved. + /// Key of the composite (that contains settings). + /// Objects to save. + public void Save(string compositeKey, IDictionary values) + { + if (TryRead(compositeKey, out ApplicationDataCompositeValue composite) && composite != null) + { + foreach (KeyValuePair setting in values) + { + if (composite.ContainsKey(setting.Key)) + { + composite[setting.Key] = Serializer.Serialize(setting.Value); + } + else + { + composite.Add(setting.Key, Serializer.Serialize(setting.Value)); + } + } + } + else + { + composite = []; + foreach (KeyValuePair setting in values) + { + composite.Add(setting.Key, Serializer.Serialize(setting.Value)); + } + + Settings.Values[compositeKey] = composite; + } + } + + /// + /// Deletes a single item by its key in composite. + /// + /// Key of the composite (that contains settings). + /// Key of the object. + /// A boolean indicator of success. + public bool TryDelete(string compositeKey, string key) + { + return TryRead(compositeKey, out ApplicationDataCompositeValue composite) && composite != null && composite.Remove(key); + } + + /// + public Task ReadFileAsync(string filePath, T @default = default) + { + return ReadFileAsync(Folder, filePath, @default); + } + + /// + public Task> ReadFolderAsync(string folderPath) + { + return ReadFolderAsync(Folder, folderPath); + } + + /// + public Task CreateFileAsync(string filePath, T value) + { + return CreateFileAsync(Folder, filePath, value); + } + + /// + public Task CreateFolderAsync(string folderPath) + { + return CreateFolderAsync(Folder, folderPath); + } + + /// + public Task TryDeleteItemAsync(string itemPath) + { + return TryDeleteItemAsync(Folder, itemPath); + } + + /// + public Task TryRenameItemAsync(string itemPath, string newName) + { + return TryRenameItemAsync(Folder, itemPath, newName); + } + + private async Task ReadFileAsync(StorageFolder folder, string filePath, T @default = default) + { + string value = await StorageFileHelper.ReadTextFromFileAsync(folder, NormalizePath(filePath)); + return (value != null) ? Serializer.Deserialize(value) : @default; + } + + private async Task> ReadFolderAsync(StorageFolder folder, string folderPath) + { + StorageFolder targetFolder = await folder.GetFolderAsync(NormalizePath(folderPath)); + IReadOnlyList items = await targetFolder.GetItemsAsync(); + + return items.Select((item) => + { + DirectoryItemType itemType = item.IsOfType(StorageItemTypes.File) ? DirectoryItemType.File + : item.IsOfType(StorageItemTypes.Folder) ? DirectoryItemType.Folder + : DirectoryItemType.None; + return (itemType, item.Name); + }); + } + + private async Task CreateFileAsync(StorageFolder folder, string filePath, T value) + { + return await StorageFileHelper.WriteTextToFileAsync(folder, Serializer.Serialize(value)?.ToString(), NormalizePath(filePath), CreationCollisionOption.ReplaceExisting); + } + + private async Task CreateFolderAsync(StorageFolder folder, string folderPath) + { + await folder.CreateFolderAsync(NormalizePath(folderPath), CreationCollisionOption.OpenIfExists); + } + + private async Task TryDeleteItemAsync(StorageFolder folder, string itemPath) + { + try + { + IStorageItem item = await folder.GetItemAsync(NormalizePath(itemPath)); + await item.DeleteAsync(); + return true; + } + catch + { + return false; + } + } + + private async Task TryRenameItemAsync(StorageFolder folder, string itemPath, string newName) + { + try + { + IStorageItem item = await folder.GetItemAsync(NormalizePath(itemPath)); + await item.RenameAsync(newName, NameCollisionOption.FailIfExists); + return true; + } + catch + { + return false; + } + } + + private string NormalizePath(string path) + { + return Path.Combine(Path.GetDirectoryName(path), Path.GetFileName(path)); + } + } +} diff --git a/CoreAppUWP/Helpers/BackdropHelper.cs b/CoreAppUWP/Helpers/BackdropHelper.cs index e0a8d64..eb627e4 100644 --- a/CoreAppUWP/Helpers/BackdropHelper.cs +++ b/CoreAppUWP/Helpers/BackdropHelper.cs @@ -6,6 +6,7 @@ using Microsoft.UI.Xaml; using System; using System.Collections.Generic; +using System.Runtime.InteropServices; using Windows.UI; using WinRT; // required to support Window.As() @@ -346,4 +347,59 @@ public static void RemoveBackdropTypeChanged(this DesktopWindow window, Action> ActiveWindows { get; } = []; public static Dictionary> ActiveDesktopWindows { get; } = []; } + + public partial class WindowsSystemDispatcherQueueHelper + { + /// + /// Specifies the threading and apartment type for a new DispatcherQueueController. + /// + /// Introduced in Windows 10, version 1709. + [StructLayout(LayoutKind.Sequential)] + private struct DispatcherQueueOptions + { + /// + /// Size of this structure. + /// + public int DWSize; + + /// + /// Thread affinity for the created DispatcherQueueController. + /// + public int ThreadType; + + /// + /// Specifies whether to initialize COM apartment on the new thread as an application single-threaded apartment (ASTA) + /// or single-threaded apartment (STA). This field is only relevant if threadType is DQTYPE_THREAD_DEDICATED. + /// Use DQTAT_COM_NONE when DispatcherQueueOptions.threadType is DQTYPE_THREAD_CURRENT. + /// + public int ApartmentType; + } + + [LibraryImport("CoreMessaging.dll")] + private static unsafe partial int CreateDispatcherQueueController(DispatcherQueueOptions options, out nint instance); + + private nint m_dispatcherQueueController = 0; + public void EnsureWindowsSystemDispatcherQueueController() + { + if (Windows.System.DispatcherQueue.GetForCurrentThread() != null) + { + // one already exists, so we'll just use it. + return; + } + + if (m_dispatcherQueueController == 0) + { + DispatcherQueueOptions options; + options.DWSize = Marshal.SizeOf(typeof(DispatcherQueueOptions)); + options.ThreadType = 2; // DQTYPE_THREAD_CURRENT + options.ApartmentType = 2; // DQTAT_COM_STA + + unsafe + { + _ = CreateDispatcherQueueController(options, out nint dispatcherQueueController); + m_dispatcherQueueController = dispatcherQueueController; + } + } + } + } } diff --git a/CoreAppUWP/Helpers/Controls/SettingsCardHelper.cs b/CoreAppUWP/Helpers/Controls/SettingsCardHelper.cs new file mode 100644 index 0000000..ce974c8 --- /dev/null +++ b/CoreAppUWP/Helpers/Controls/SettingsCardHelper.cs @@ -0,0 +1,69 @@ +using CommunityToolkit.WinUI; +using CommunityToolkit.WinUI.Controls; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; + +namespace CoreAppUWP.Helpers +{ + public static class SettingsCardHelper + { + #region HeaderIcon + + /// + /// Identifies the HeaderIcon dependency property. + /// + public static readonly DependencyProperty HeaderIconProperty = + DependencyProperty.RegisterAttached( + "HeaderIcon", + typeof(object), + typeof(SettingsExpanderHelper), + new PropertyMetadata(null, OnHeaderIconChanged)); + + /// + /// Gets the HeaderIcon. + /// + /// The element from which to read the property value. + /// The HeaderIcon. + public static object GetHeaderIcon(SettingsCard control) + { + return control.GetValue(HeaderIconProperty); + } + + /// + /// Sets the HeaderIcon. + /// + /// The element on which to set the attached property. + /// The property value to set. + public static void SetHeaderIcon(SettingsCard control, object value) + { + control.SetValue(HeaderIconProperty, value); + } + + private static void OnHeaderIconChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + if (d is not SettingsCard element) { return; } + if (element.IsLoaded) + { + OnElementLoaded(element, null); + } + else + { + element.Loaded -= OnElementLoaded; + element.Loaded += OnElementLoaded; + } + } + + private static void OnElementLoaded(object sender, RoutedEventArgs e) + { + if (sender is not SettingsCard element) { return; } + object content = GetHeaderIcon(element); + element.HeaderIcon = content == null ? null : new SymbolIcon(); + if (element.FindDescendant("PART_HeaderIconPresenter") is ContentPresenter presenter) + { + presenter.Content = content; + } + } + + #endregion + } +} diff --git a/CoreAppUWP/Helpers/Controls/SettingsExpanderHelper.cs b/CoreAppUWP/Helpers/Controls/SettingsExpanderHelper.cs new file mode 100644 index 0000000..27276f7 --- /dev/null +++ b/CoreAppUWP/Helpers/Controls/SettingsExpanderHelper.cs @@ -0,0 +1,67 @@ +using CommunityToolkit.WinUI; +using CommunityToolkit.WinUI.Controls; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; + +namespace CoreAppUWP.Helpers +{ + public static class SettingsExpanderHelper + { + #region HeaderIcon + + /// + /// Identifies the HeaderIcon dependency property. + /// + public static readonly DependencyProperty HeaderIconProperty = + DependencyProperty.RegisterAttached( + "HeaderIcon", + typeof(object), + typeof(SettingsExpanderHelper), + new PropertyMetadata(null, OnHeaderIconChanged)); + + /// + /// Gets the HeaderIcon. + /// + /// The element from which to read the property value. + /// The HeaderIcon. + public static object GetHeaderIcon(SettingsExpander control) + { + return control.GetValue(HeaderIconProperty); + } + + /// + /// Sets the HeaderIcon. + /// + /// The element on which to set the attached property. + /// The property value to set. + public static void SetHeaderIcon(SettingsExpander control, object value) + { + control.SetValue(HeaderIconProperty, value); + } + + private static void OnHeaderIconChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + if (d is not FrameworkElement element) { return; } + if (element.IsLoaded) + { + OnElementLoaded(element, null); + } + else + { + element.Loaded -= OnElementLoaded; + element.Loaded += OnElementLoaded; + } + } + + private static void OnElementLoaded(object sender, RoutedEventArgs e) + { + if (sender is not SettingsExpander element) { return; } + if (element.FindDescendant() is Expander expander && expander.Header is SettingsCard settings) + { + SettingsCardHelper.SetHeaderIcon(settings, GetHeaderIcon(element)); + } + } + + #endregion + } +} diff --git a/CoreAppUWP/Helpers/DispatcherQueueHelper.cs b/CoreAppUWP/Helpers/DispatcherQueueHelper.cs deleted file mode 100644 index 6328e71..0000000 --- a/CoreAppUWP/Helpers/DispatcherQueueHelper.cs +++ /dev/null @@ -1,61 +0,0 @@ -using System.Runtime.InteropServices; // For DllImport -using Windows.System; - -namespace CoreAppUWP.Helpers -{ - public partial class WindowsSystemDispatcherQueueHelper - { - /// - /// Specifies the threading and apartment type for a new DispatcherQueueController. - /// - /// Introduced in Windows 10, version 1709. - [StructLayout(LayoutKind.Sequential)] - private struct DispatcherQueueOptions - { - /// - /// Size of this structure. - /// - public int DWSize; - - /// - /// Thread affinity for the created DispatcherQueueController. - /// - public int ThreadType; - - /// - /// Specifies whether to initialize COM apartment on the new thread as an application single-threaded apartment (ASTA) - /// or single-threaded apartment (STA). This field is only relevant if threadType is DQTYPE_THREAD_DEDICATED. - /// Use DQTAT_COM_NONE when DispatcherQueueOptions.threadType is DQTYPE_THREAD_CURRENT. - /// - public int ApartmentType; - } - - [LibraryImport("CoreMessaging.dll")] - private static unsafe partial int CreateDispatcherQueueController(DispatcherQueueOptions options, nint* instance); - - private nint m_dispatcherQueueController = nint.Zero; - public void EnsureWindowsSystemDispatcherQueueController() - { - if (DispatcherQueue.GetForCurrentThread() != null) - { - // one already exists, so we'll just use it. - return; - } - - if (m_dispatcherQueueController == nint.Zero) - { - DispatcherQueueOptions options; - options.DWSize = Marshal.SizeOf(typeof(DispatcherQueueOptions)); - options.ThreadType = 2; // DQTYPE_THREAD_CURRENT - options.ApartmentType = 2; // DQTAT_COM_STA - - unsafe - { - nint dispatcherQueueController; - _ = CreateDispatcherQueueController(options, &dispatcherQueueController); - m_dispatcherQueueController = dispatcherQueueController; - } - } - } - } -} diff --git a/CoreAppUWP/Helpers/SettingsHelper.cs b/CoreAppUWP/Helpers/SettingsHelper.cs index 7d396f1..277316d 100644 --- a/CoreAppUWP/Helpers/SettingsHelper.cs +++ b/CoreAppUWP/Helpers/SettingsHelper.cs @@ -1,5 +1,4 @@ -using CommunityToolkit.WinUI.Helpers; -using MetroLog; +using MetroLog; using MetroLog.Targets; using Microsoft.UI.Xaml; using System; @@ -8,7 +7,6 @@ using System.Text.Json.Serialization; using System.Threading.Tasks; using Windows.Storage; -using Windows.UI.ViewManagement; using IObjectSerializer = CommunityToolkit.Common.Helpers.IObjectSerializer; namespace CoreAppUWP.Helpers @@ -43,9 +41,7 @@ public static void SetDefaultSettings() public static partial class SettingsHelper { - public static UISettings UISettings { get; } = new(); public static ILogManager LogManager { get; private set; } - public static OSVersion OperatingSystemVersion => SystemInformation.Instance.OperatingSystemVersion; public static ApplicationDataStorageHelper LocalObject { get; } = ApplicationDataStorageHelper.GetCurrent(new SystemTextJsonObjectSerializer()); static SettingsHelper() => SetDefaultSettings(); diff --git a/CoreAppUWP/Helpers/StorageFileHelper.cs b/CoreAppUWP/Helpers/StorageFileHelper.cs new file mode 100644 index 0000000..8ff38a7 --- /dev/null +++ b/CoreAppUWP/Helpers/StorageFileHelper.cs @@ -0,0 +1,697 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Windows.ApplicationModel; +using Windows.Storage; +using Windows.Storage.Search; +using Windows.Storage.Streams; + +namespace CoreAppUWP.Helpers +{ + /// + /// This class provides static helper methods for . + /// + public static class StorageFileHelper + { + /// + /// Saves a string value to a in application local folder/>. + /// + /// + /// The value to save to the file. + /// + /// + /// The name for the file. + /// + /// + /// The creation collision options. Default is ReplaceExisting. + /// + /// + /// The saved containing the text. + /// + /// + /// Exception thrown if the file location or file name are null or empty. + /// + public static Task WriteTextToLocalFileAsync( + string text, + string fileName, + CreationCollisionOption options = CreationCollisionOption.ReplaceExisting) + { + if (string.IsNullOrWhiteSpace(fileName)) + { + throw new ArgumentNullException(nameof(fileName)); + } + + StorageFolder folder = ApplicationData.Current.LocalFolder; + return folder.WriteTextToFileAsync(text, fileName, options); + } + + /// + /// Saves a string value to a in application local cache folder/>. + /// + /// + /// The value to save to the file. + /// + /// + /// The name for the file. + /// + /// + /// The creation collision options. Default is ReplaceExisting. + /// + /// + /// The saved containing the text. + /// + /// + /// Exception thrown if the file location or file name are null or empty. + /// + public static Task WriteTextToLocalCacheFileAsync( + string text, + string fileName, + CreationCollisionOption options = CreationCollisionOption.ReplaceExisting) + { + if (string.IsNullOrWhiteSpace(fileName)) + { + throw new ArgumentNullException(nameof(fileName)); + } + + StorageFolder folder = ApplicationData.Current.LocalCacheFolder; + return folder.WriteTextToFileAsync(text, fileName, options); + } + + /// + /// Saves a string value to a in well known folder/>. + /// + /// + /// The well known folder ID to use. + /// + /// + /// The value to save to the file. + /// + /// + /// The name for the file. + /// + /// + /// The creation collision options. Default is ReplaceExisting. + /// + /// + /// The saved containing the text. + /// + /// + /// Exception thrown if the file location or file name are null or empty. + /// + public static Task WriteTextToKnownFolderFileAsync( + KnownFolderId knownFolderId, + string text, + string fileName, + CreationCollisionOption options = CreationCollisionOption.ReplaceExisting) + { + if (string.IsNullOrWhiteSpace(fileName)) + { + throw new ArgumentNullException(nameof(fileName)); + } + + StorageFolder folder = GetFolderFromKnownFolderId(knownFolderId); + return folder.WriteTextToFileAsync(text, fileName, options); + } + + /// + /// Saves a string value to a in the given . + /// + /// + /// The to save the file in. + /// + /// + /// The value to save to the file. + /// + /// + /// The name for the file. + /// + /// + /// The creation collision options. Default is ReplaceExisting. + /// + /// + /// The saved containing the text. + /// + /// + /// Exception thrown if the file location or file name are null or empty. + /// + public static async Task WriteTextToFileAsync( + this StorageFolder fileLocation, + string text, + string fileName, + CreationCollisionOption options = CreationCollisionOption.ReplaceExisting) + { + if (fileLocation == null) + { + throw new ArgumentNullException(nameof(fileLocation)); + } + + if (string.IsNullOrWhiteSpace(fileName)) + { + throw new ArgumentNullException(nameof(fileName)); + } + + StorageFile storageFile = await fileLocation.CreateFileAsync(fileName, options); + await FileIO.WriteTextAsync(storageFile, text); + + return storageFile; + } + + /// + /// Saves an array of bytes to a to application local folder/>. + /// + /// + /// The array to save to the file. + /// + /// + /// The name for the file. + /// + /// + /// The creation collision options. Default is ReplaceExisting. + /// + /// + /// The saved containing the bytes. + /// + /// + /// Exception thrown if the file location or file name are null or empty. + /// + public static Task WriteBytesToLocalFileAsync( + byte[] bytes, + string fileName, + CreationCollisionOption options = CreationCollisionOption.ReplaceExisting) + { + if (string.IsNullOrWhiteSpace(fileName)) + { + throw new ArgumentNullException(nameof(fileName)); + } + + StorageFolder folder = ApplicationData.Current.LocalFolder; + return folder.WriteBytesToFileAsync(bytes, fileName, options); + } + + /// + /// Saves an array of bytes to a to application local cache folder/>. + /// + /// + /// The array to save to the file. + /// + /// + /// The name for the file. + /// + /// + /// The creation collision options. Default is ReplaceExisting. + /// + /// + /// The saved containing the bytes. + /// + /// + /// Exception thrown if the file location or file name are null or empty. + /// + public static Task WriteBytesToLocalCacheFileAsync( + byte[] bytes, + string fileName, + CreationCollisionOption options = CreationCollisionOption.ReplaceExisting) + { + if (string.IsNullOrWhiteSpace(fileName)) + { + throw new ArgumentNullException(nameof(fileName)); + } + + StorageFolder folder = ApplicationData.Current.LocalCacheFolder; + return folder.WriteBytesToFileAsync(bytes, fileName, options); + } + + /// + /// Saves an array of bytes to a to well known folder/>. + /// + /// + /// The well known folder ID to use. + /// + /// + /// The array to save to the file. + /// + /// + /// The name for the file. + /// + /// + /// The creation collision options. Default is ReplaceExisting. + /// + /// + /// The saved containing the bytes. + /// + /// + /// Exception thrown if the file location or file name are null or empty. + /// + public static Task WriteBytesToKnownFolderFileAsync( + KnownFolderId knownFolderId, + byte[] bytes, + string fileName, + CreationCollisionOption options = CreationCollisionOption.ReplaceExisting) + { + if (string.IsNullOrWhiteSpace(fileName)) + { + throw new ArgumentNullException(nameof(fileName)); + } + + StorageFolder folder = GetFolderFromKnownFolderId(knownFolderId); + return folder.WriteBytesToFileAsync(bytes, fileName, options); + } + + /// + /// Saves an array of bytes to a in the given . + /// + /// + /// The to save the file in. + /// + /// + /// The array to save to the file. + /// + /// + /// The name for the file. + /// + /// + /// The creation collision options. Default is ReplaceExisting. + /// + /// + /// The saved containing the bytes. + /// + /// + /// Exception thrown if the file location or file name are null or empty. + /// + public static async Task WriteBytesToFileAsync( + this StorageFolder fileLocation, + byte[] bytes, + string fileName, + CreationCollisionOption options = CreationCollisionOption.ReplaceExisting) + { + if (fileLocation == null) + { + throw new ArgumentNullException(nameof(fileLocation)); + } + + if (string.IsNullOrWhiteSpace(fileName)) + { + throw new ArgumentNullException(nameof(fileName)); + } + + StorageFile storageFile = await fileLocation.CreateFileAsync(fileName, options); + await FileIO.WriteBytesAsync(storageFile, bytes); + + return storageFile; + } + + /// + /// Gets a string value from a located in the application installation folder. + /// + /// + /// The relative file path. + /// + /// + /// The stored value. + /// + /// + /// Exception thrown if the is null or empty. + /// + public static Task ReadTextFromPackagedFileAsync(string fileName) + { + if (string.IsNullOrWhiteSpace(fileName)) + { + throw new ArgumentNullException(nameof(fileName)); + } + + StorageFolder folder = Package.Current.InstalledLocation; + return folder.ReadTextFromFileAsync(fileName); + } + + /// + /// Gets a string value from a located in the application local cache folder. + /// + /// + /// The relative file path. + /// + /// + /// The stored value. + /// + /// + /// Exception thrown if the is null or empty. + /// + public static Task ReadTextFromLocalCacheFileAsync(string fileName) + { + if (string.IsNullOrWhiteSpace(fileName)) + { + throw new ArgumentNullException(nameof(fileName)); + } + + StorageFolder folder = ApplicationData.Current.LocalCacheFolder; + return folder.ReadTextFromFileAsync(fileName); + } + + /// + /// Gets a string value from a located in the application local folder. + /// + /// + /// The relative file path. + /// + /// + /// The stored value. + /// + /// + /// Exception thrown if the is null or empty. + /// + public static Task ReadTextFromLocalFileAsync(string fileName) + { + if (string.IsNullOrWhiteSpace(fileName)) + { + throw new ArgumentNullException(nameof(fileName)); + } + + StorageFolder folder = ApplicationData.Current.LocalFolder; + return folder.ReadTextFromFileAsync(fileName); + } + + /// + /// Gets a string value from a located in a well known folder. + /// + /// + /// The well known folder ID to use. + /// + /// + /// The relative file path. + /// + /// + /// The stored value. + /// + /// + /// Exception thrown if the is null or empty. + /// + public static Task ReadTextFromKnownFoldersFileAsync( + KnownFolderId knownFolderId, + string fileName) + { + if (string.IsNullOrWhiteSpace(fileName)) + { + throw new ArgumentNullException(nameof(fileName)); + } + + StorageFolder folder = GetFolderFromKnownFolderId(knownFolderId); + return folder.ReadTextFromFileAsync(fileName); + } + + /// + /// Gets a string value from a located in the given . + /// + /// + /// The to save the file in. + /// + /// + /// The relative file path. + /// + /// + /// The stored value. + /// + /// + /// Exception thrown if the is null or empty. + /// + public static async Task ReadTextFromFileAsync( + this StorageFolder fileLocation, + string fileName) + { + if (string.IsNullOrWhiteSpace(fileName)) + { + throw new ArgumentNullException(nameof(fileName)); + } + + StorageFile file = await fileLocation.GetFileAsync(fileName); + return await FileIO.ReadTextAsync(file); + } + + /// + /// Gets an array of bytes from a located in the application installation folder. + /// + /// + /// The relative file path. + /// + /// + /// The stored array. + /// + /// + /// Exception thrown if the is null or empty. + /// + public static Task ReadBytesFromPackagedFileAsync(string fileName) + { + if (string.IsNullOrWhiteSpace(fileName)) + { + throw new ArgumentNullException(nameof(fileName)); + } + + StorageFolder folder = Package.Current.InstalledLocation; + return folder.ReadBytesFromFileAsync(fileName); + } + + /// + /// Gets an array of bytes from a located in the application local cache folder. + /// + /// + /// The relative file path. + /// + /// + /// The stored array. + /// + /// + /// Exception thrown if the is null or empty. + /// + public static Task ReadBytesFromLocalCacheFileAsync(string fileName) + { + if (string.IsNullOrWhiteSpace(fileName)) + { + throw new ArgumentNullException(nameof(fileName)); + } + + StorageFolder folder = ApplicationData.Current.LocalCacheFolder; + return folder.ReadBytesFromFileAsync(fileName); + } + + /// + /// Gets an array of bytes from a located in the application local folder. + /// + /// + /// The relative file path. + /// + /// + /// The stored array. + /// + /// + /// Exception thrown if the is null or empty. + /// + public static Task ReadBytesFromLocalFileAsync(string fileName) + { + if (string.IsNullOrWhiteSpace(fileName)) + { + throw new ArgumentNullException(nameof(fileName)); + } + + StorageFolder folder = ApplicationData.Current.LocalFolder; + return folder.ReadBytesFromFileAsync(fileName); + } + + /// + /// Gets an array of bytes from a located in a well known folder. + /// + /// + /// The well known folder ID to use. + /// + /// + /// The relative file path. + /// + /// + /// The stored array. + /// + /// + /// Exception thrown if the is null or empty. + /// + public static Task ReadBytesFromKnownFoldersFileAsync( + KnownFolderId knownFolderId, + string fileName) + { + if (string.IsNullOrWhiteSpace(fileName)) + { + throw new ArgumentNullException(nameof(fileName)); + } + + StorageFolder folder = GetFolderFromKnownFolderId(knownFolderId); + return folder.ReadBytesFromFileAsync(fileName); + } + + /// + /// Gets an array of bytes from a located in the given . + /// + /// + /// The to save the file in. + /// + /// + /// The relative file path. + /// + /// + /// The stored array. + /// + /// + /// Exception thrown if the is null or empty. + /// + public static async Task ReadBytesFromFileAsync( + this StorageFolder fileLocation, + string fileName) + { + if (string.IsNullOrWhiteSpace(fileName)) + { + throw new ArgumentNullException(nameof(fileName)); + } + + StorageFile file = await fileLocation.GetFileAsync(fileName).AsTask().ConfigureAwait(false); + return await file.ReadBytesAsync(); + } + + /// + /// Gets an array of bytes from a . + /// + /// + /// The . + /// + /// + /// The stored array. + /// + public static async Task ReadBytesAsync(this StorageFile file) + { + if (file == null) + { + return null; + } + + using IRandomAccessStream stream = await file.OpenReadAsync(); + using DataReader reader = new(stream.GetInputStreamAt(0)); + await reader.LoadAsync((uint)stream.Size); + byte[] bytes = new byte[stream.Size]; + reader.ReadBytes(bytes); + return bytes; + } + + /// + /// Gets a value indicating whether a file exists in the current folder. + /// + /// + /// The to look for the file in. + /// + /// + /// The filename of the file to search for. Must include the file extension and is not case-sensitive. + /// + /// + /// The , indicating if the subfolders should also be searched through. + /// + /// + /// true if the file exists; otherwise, false. + /// + public static Task FileExistsAsync(this StorageFolder folder, string fileName, bool isRecursive = false) + => isRecursive + ? FileExistsInSubtreeAsync(folder, fileName) + : FileExistsInFolderAsync(folder, fileName); + + /// + /// Gets a value indicating whether a filename is correct or not using the Storage feature. + /// + /// The filename to test. Must include the file extension and is not case-sensitive. + /// true if the filename is valid; otherwise, false. + public static bool IsFileNameValid(string fileName) + { + char[] illegalChars = Path.GetInvalidFileNameChars(); + return fileName.All(c => !illegalChars.Contains(c)); + } + + /// + /// Gets a value indicating whether a file path is correct or not using the Storage feature. + /// + /// The file path to test. Must include the file extension and is not case-sensitive. + /// true if the file path is valid; otherwise, false. + public static bool IsFilePathValid(string filePath) + { + char[] illegalChars = Path.GetInvalidPathChars(); + return filePath.All(c => !illegalChars.Contains(c)); + } + + /// + /// Gets a value indicating whether a file exists in the current folder. + /// + /// + /// The to look for the file in. + /// + /// + /// The filename of the file to search for. Must include the file extension and is not case-sensitive. + /// + /// + /// true if the file exists; otherwise, false. + /// + internal static async Task FileExistsInFolderAsync(StorageFolder folder, string fileName) + { + IStorageItem item = await folder.TryGetItemAsync(fileName).AsTask().ConfigureAwait(false); + return (item != null) && item.IsOfType(StorageItemTypes.File); + } + + /// + /// Gets a value indicating whether a file exists in the current folder or in one of its subfolders. + /// + /// + /// The to look for the file in. + /// + /// + /// The filename of the file to search for. Must include the file extension and is not case-sensitive. + /// + /// + /// true if the file exists; otherwise, false. + /// + /// + /// Exception thrown if the contains a quotation mark. + /// + internal static async Task FileExistsInSubtreeAsync(StorageFolder rootFolder, string fileName) + { + if (fileName.IndexOf('"') >= 0) + { + throw new ArgumentException(nameof(fileName)); + } + + QueryOptions options = new() + { + FolderDepth = FolderDepth.Deep, + UserSearchFilter = $"filename:=\"{fileName}\"" // “:=” is the exact-match operator + }; + + IReadOnlyList files = await rootFolder.CreateFileQueryWithOptions(options).GetFilesAsync().AsTask().ConfigureAwait(false); + return files.Count > 0; + } + + /// + /// Returns a from a + /// + /// Folder Id + /// The + internal static StorageFolder GetFolderFromKnownFolderId(KnownFolderId knownFolderId) + { + StorageFolder workingFolder = knownFolderId switch + { + KnownFolderId.AppCaptures => KnownFolders.AppCaptures, + KnownFolderId.CameraRoll => KnownFolders.CameraRoll, + KnownFolderId.DocumentsLibrary => KnownFolders.DocumentsLibrary, + KnownFolderId.HomeGroup => KnownFolders.HomeGroup, + KnownFolderId.MediaServerDevices => KnownFolders.MediaServerDevices, + KnownFolderId.MusicLibrary => KnownFolders.MusicLibrary, + KnownFolderId.Objects3D => KnownFolders.Objects3D, + KnownFolderId.PicturesLibrary => KnownFolders.PicturesLibrary, + KnownFolderId.Playlists => KnownFolders.Playlists, + KnownFolderId.RecordedCalls => KnownFolders.RecordedCalls, + KnownFolderId.RemovableDevices => KnownFolders.RemovableDevices, + KnownFolderId.SavedPictures => KnownFolders.SavedPictures, + KnownFolderId.VideosLibrary => KnownFolders.VideosLibrary, + _ => throw new ArgumentOutOfRangeException(nameof(knownFolderId), knownFolderId, null), + }; + return workingFolder; + } + } +} diff --git a/CoreAppUWP/Pages/HomePage.xaml b/CoreAppUWP/Pages/HomePage.xaml index 924f55a..21d51cd 100644 --- a/CoreAppUWP/Pages/HomePage.xaml +++ b/CoreAppUWP/Pages/HomePage.xaml @@ -2,7 +2,7 @@ x:Class="CoreAppUWP.Pages.HomePage" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" - xmlns:cwuc="using:CommunityToolkit.WinUI.UI.Controls" + xmlns:cwuc="using:CommunityToolkit.WinUI.Controls" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" NavigationCacheMode="Enabled" diff --git a/CoreAppUWP/Pages/SettingsPages/SettingsPage.xaml b/CoreAppUWP/Pages/SettingsPages/SettingsPage.xaml index c96e9aa..3007a43 100644 --- a/CoreAppUWP/Pages/SettingsPages/SettingsPage.xaml +++ b/CoreAppUWP/Pages/SettingsPages/SettingsPage.xaml @@ -3,11 +3,13 @@ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:animatedvisuals="using:Microsoft.UI.Xaml.Controls.AnimatedVisuals" + xmlns:clwm="using:CommunityToolkit.Labs.WinUI.MarkdownTextBlock" xmlns:common="using:CoreAppUWP.Common" xmlns:controls="using:CoreAppUWP.Controls" - xmlns:converters="using:CommunityToolkit.WinUI.UI.Converters" - xmlns:cwuc="using:CommunityToolkit.WinUI.UI.Controls" + xmlns:converters="using:CommunityToolkit.WinUI.Converters" + xmlns:cwuc="using:CommunityToolkit.WinUI.Controls" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" + xmlns:helpers="using:CoreAppUWP.Helpers" xmlns:interopservices="using:System.Runtime.InteropServices" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:settingspages="using:CoreAppUWP.ViewModels.SettingsPages" @@ -41,43 +43,46 @@ - - + + + + + - + - - + + + + + Mica MicaAlt Acrylic DefaultColor - - - - + + + + + + + - + + + + - - + + + + + - - - - - - + + + + + + + + + + + + + + + + + + - + @@ -150,9 +161,9 @@ - - - + + - + @@ -170,14 +181,14 @@ - - + + - + + + + - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - + - - + + - - + + - - - - - - + + + + + + + + + + + + diff --git a/CoreAppUWP/Pages/SettingsPages/SettingsPage.xaml.cs b/CoreAppUWP/Pages/SettingsPages/SettingsPage.xaml.cs index fc74009..616f59b 100644 --- a/CoreAppUWP/Pages/SettingsPages/SettingsPage.xaml.cs +++ b/CoreAppUWP/Pages/SettingsPages/SettingsPage.xaml.cs @@ -1,4 +1,3 @@ -using CommunityToolkit.WinUI.UI.Controls; using CoreAppUWP.Common; using CoreAppUWP.Controls; using CoreAppUWP.Helpers; @@ -173,7 +172,5 @@ private async void HyperlinkButton_Click(object sender, RoutedEventArgs e) } public Task Refresh(bool reset = false) => Provider.Refresh(reset); - - private void MarkdownText_LinkClicked(object sender, LinkClickedEventArgs e) => _ = Launcher.LaunchUriAsync(new Uri(e.Link)); } } diff --git a/CoreAppUWP/Program.cs b/CoreAppUWP/Program.cs index ec4a4c8..ab99616 100644 --- a/CoreAppUWP/Program.cs +++ b/CoreAppUWP/Program.cs @@ -44,7 +44,7 @@ private static bool IsSupportCoreWindow try { RegistryKey registryKey = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\WinUI\Xaml"); - return registryKey?.GetValue("EnableUWPWindow") is int value && value > 0; + return registryKey?.GetValue("EnableUWPWindow") is > 0; } catch { diff --git a/CoreAppUWP/Styles/SettingsCard.xaml b/CoreAppUWP/Styles/SettingsCard.xaml new file mode 100644 index 0000000..ad32294 --- /dev/null +++ b/CoreAppUWP/Styles/SettingsCard.xaml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + diff --git a/CoreAppUWP/Themes/CustomStyles.xaml b/CoreAppUWP/Themes/CustomStyles.xaml index 4690d0e..7741fa9 100644 --- a/CoreAppUWP/Themes/CustomStyles.xaml +++ b/CoreAppUWP/Themes/CustomStyles.xaml @@ -1,7 +1,6 @@  - - + diff --git a/CoreAppUWP/Themes/Generic.xaml b/CoreAppUWP/Themes/Generic.xaml index 3d2cae4..3138497 100644 --- a/CoreAppUWP/Themes/Generic.xaml +++ b/CoreAppUWP/Themes/Generic.xaml @@ -1,8 +1,6 @@ - - diff --git a/nuget.config b/nuget.config new file mode 100644 index 0000000..8bf43a4 --- /dev/null +++ b/nuget.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file