From 5daf7ce97f1d0ba9f1210fb98573dfb69f2be69d Mon Sep 17 00:00:00 2001 From: Andy Howard Date: Tue, 6 Oct 2026 14:13:28 +0100 Subject: [PATCH] Improve shared MAUI controls and tests --- SaCommonVS.sln | 12 + SacsMobileMaui/Maui/App/Sam.cs | 96 ++-- SacsMobileMaui/Maui/App/SamApplication.cs | 43 +- SacsMobileMaui/Maui/Base/SamComponents.cs | 4 +- .../Maui/Consoles/SamWebServiceConsole.cs | 2 +- SacsMobileMaui/Maui/Controls/ISamControl.cs | 10 + .../Maui/Controls/ISamNativeControlStyle.cs | 19 + .../Maui/Controls/Panels/SamCheckBoxPanel.cs | 83 +--- .../Maui/Controls/Panels/SamControlPanel.cs | 48 +- .../Maui/Controls/Panels/SamEntryPanel.cs | 85 +--- SacsMobileMaui/Maui/Controls/SamButton.cs | 35 +- SacsMobileMaui/Maui/Controls/SamCheckBox.cs | 29 +- SacsMobileMaui/Maui/Controls/SamDatePicker.cs | 65 +-- .../Maui/Controls/SamDateTimePicker.cs | 55 +-- SacsMobileMaui/Maui/Controls/SamEditor.cs | 36 +- SacsMobileMaui/Maui/Controls/SamEntry.cs | 85 ++-- .../Maui/Controls/SamImageButton.cs | 33 +- SacsMobileMaui/Maui/Controls/SamLabel.cs | 63 +-- SacsMobileMaui/Maui/Controls/SamPicker.cs | 95 ++-- SacsMobileMaui/Maui/Controls/SamTabControl.cs | 70 +-- SacsMobileMaui/Maui/Controls/SamTimePicker.cs | 9 +- SacsMobileMaui/Maui/Controls/SamWebView.cs | 53 +-- .../Maui/Handlers/SamFormHandler.cs | 59 +-- .../Maui/Layouts/SamAbsoluteLayout.cs | 2 +- SacsMobileMaui/Maui/Layouts/SamFlexLayout.cs | 2 +- SacsMobileMaui/Maui/Layouts/SamGrid.cs | 2 +- SacsMobileMaui/Maui/Layouts/SamStackLayout.cs | 2 +- SacsMobileMaui/Maui/Layouts/SamWrapLayout.cs | 20 +- .../Maui/Logic/SamDateValueState.cs | 44 ++ .../Maui/Logic/SamEntryTextFormatOptions.cs | 18 + .../Maui/Logic/SamEntryTextFormatter.cs | 48 ++ .../Maui/Logic/SamPickerItemCollection.cs | 61 +++ SacsMobileMaui/Maui/Logic/SamViewTreeUtil.cs | 36 ++ SacsMobileMaui/Maui/Pages/SamContentPage.cs | 69 +-- SacsMobileMaui/Maui/Panels/SamPanel.cs | 6 +- .../Maui/Panels/SamPanel_ItemFilter.cs | 2 +- .../Panels/SamPanel_ItemFilterProperty.cs | 2 +- .../Maui/Panels/SamPanel_ItemListHeader.cs | 2 +- .../Maui/Panels/SamPanel_ItemListSummary.cs | 2 +- .../Maui/Panels/SamPanel_Messages.cs | 2 +- .../Maui/Panels/SamPanel_Page_Footer.cs | 2 +- .../Maui/Panels/SamPanel_Page_Header.cs | 2 +- .../Maui/Panels/SamPanel_Progress.cs | 2 +- .../Maui/Panels/SamPanel_PropertySummary.cs | 2 +- SacsMobileMaui/Maui/Panels/SamPanel_Tab.cs | 6 +- SacsMobileMaui/Maui/Panels/SamPanel_Tabbed.cs | 6 +- SacsMobileMaui/Maui/Panels/SamTabPanel.cs | 2 +- SacsMobileMaui/Maui/Utils/SamElementUtil.cs | 2 +- SacsMobileMaui/Maui/Utils/SamFormUtil.cs | 40 +- SacsMobileMaui/Maui/Utils/SamImageUtil.cs | 2 +- SacsMobileMaui/Maui/Utils/SamPageUtil.cs | 2 +- SacsMobileMaui/Maui/Utils/SamResourceUtil.cs | 45 +- SacsMobileMaui/Maui/Utils/SamThreadUtil.cs | 61 +-- SacsMobileMaui/Maui/Utils/SamTimerUtil.cs | 2 +- SacsMobileMaui/Maui/Views/ISamView.cs | 4 +- SacsMobileMaui/Maui/Views/SamBorder.cs | 7 +- SacsMobileMaui/Maui/Views/SamBoxView.cs | 5 +- SacsMobileMaui/Maui/Views/SamContentView.cs | 9 +- SacsMobileMaui/Maui/Views/SamFrame.cs | 5 +- SacsMobileMaui/Maui/Views/SamView.cs | 5 +- .../Platforms/Android/PlatformClass1.cs | 2 +- .../Platforms/MacCatalyst/PlatformClass1.cs | 2 +- .../Platforms/Tizen/PlatformClass1.cs | 2 +- .../Platforms/Windows/PlatformClass1.cs | 2 +- .../Platforms/iOS/PlatformClass1.cs | 2 +- SacsMobileMaui/docs/component-reviews.md | 10 +- SacsMobileMaui/docs/topics.md | 12 +- SacsMobileMauiTestHost/App.xaml | 14 + SacsMobileMauiTestHost/App.xaml.cs | 16 + SacsMobileMauiTestHost/AppShell.xaml | 14 + SacsMobileMauiTestHost/AppShell.xaml.cs | 9 + SacsMobileMauiTestHost/MainPage.xaml | 31 ++ SacsMobileMauiTestHost/MainPage.xaml.cs | 144 ++++++ SacsMobileMauiTestHost/MauiProgram.cs | 24 + .../Platforms/Android/AndroidManifest.xml | 6 + .../Platforms/Android/MainActivity.cs | 10 + .../Platforms/Android/MainApplication.cs | 15 + .../Android/Resources/values/colors.xml | 6 + .../Platforms/MacCatalyst/AppDelegate.cs | 9 + .../Platforms/MacCatalyst/Entitlements.plist | 13 + .../Platforms/MacCatalyst/Info.plist | 40 ++ .../Platforms/MacCatalyst/Program.cs | 15 + .../Platforms/Windows/App.xaml | 8 + .../Platforms/Windows/App.xaml.cs | 23 + .../Platforms/Windows/Package.appxmanifest | 46 ++ .../Platforms/Windows/app.manifest | 17 + .../Platforms/iOS/AppDelegate.cs | 9 + .../Platforms/iOS/Info.plist | 32 ++ .../Platforms/iOS/Program.cs | 15 + .../iOS/Resources/PrivacyInfo.xcprivacy | 51 ++ .../Properties/launchSettings.json | 8 + SacsMobileMauiTestHost/README.md | 13 + .../Resources/AppIcon/appicon.svg | 4 + .../Resources/AppIcon/appiconfg.svg | 8 + .../Resources/Fonts/OpenSans-Regular.ttf | Bin 0 -> 107280 bytes .../Resources/Fonts/OpenSans-Semibold.ttf | Bin 0 -> 111204 bytes .../Resources/Images/dotnet_bot.png | Bin 0 -> 92532 bytes .../Resources/Raw/AboutAssets.txt | 15 + .../Resources/Splash/splash.svg | 8 + .../Resources/Styles/Colors.xaml | 44 ++ .../Resources/Styles/Styles.xaml | 434 ++++++++++++++++++ .../SacsMobileMauiTestHost.csproj | 75 +++ .../Maui/Controls/SamDatePicker_Tester.cs | 67 +++ .../Maui/Logic/SamLogic_Tester.cs | 78 ++++ .../Maui/Utils/SamThreadUtil_Tester.cs | 103 +++++ .../SacsMobileMauiTests.csproj | 28 ++ 106 files changed, 2299 insertions(+), 805 deletions(-) create mode 100644 SacsMobileMaui/Maui/Controls/ISamControl.cs create mode 100644 SacsMobileMaui/Maui/Controls/ISamNativeControlStyle.cs create mode 100644 SacsMobileMaui/Maui/Logic/SamDateValueState.cs create mode 100644 SacsMobileMaui/Maui/Logic/SamEntryTextFormatOptions.cs create mode 100644 SacsMobileMaui/Maui/Logic/SamEntryTextFormatter.cs create mode 100644 SacsMobileMaui/Maui/Logic/SamPickerItemCollection.cs create mode 100644 SacsMobileMaui/Maui/Logic/SamViewTreeUtil.cs create mode 100644 SacsMobileMauiTestHost/App.xaml create mode 100644 SacsMobileMauiTestHost/App.xaml.cs create mode 100644 SacsMobileMauiTestHost/AppShell.xaml create mode 100644 SacsMobileMauiTestHost/AppShell.xaml.cs create mode 100644 SacsMobileMauiTestHost/MainPage.xaml create mode 100644 SacsMobileMauiTestHost/MainPage.xaml.cs create mode 100644 SacsMobileMauiTestHost/MauiProgram.cs create mode 100644 SacsMobileMauiTestHost/Platforms/Android/AndroidManifest.xml create mode 100644 SacsMobileMauiTestHost/Platforms/Android/MainActivity.cs create mode 100644 SacsMobileMauiTestHost/Platforms/Android/MainApplication.cs create mode 100644 SacsMobileMauiTestHost/Platforms/Android/Resources/values/colors.xml create mode 100644 SacsMobileMauiTestHost/Platforms/MacCatalyst/AppDelegate.cs create mode 100644 SacsMobileMauiTestHost/Platforms/MacCatalyst/Entitlements.plist create mode 100644 SacsMobileMauiTestHost/Platforms/MacCatalyst/Info.plist create mode 100644 SacsMobileMauiTestHost/Platforms/MacCatalyst/Program.cs create mode 100644 SacsMobileMauiTestHost/Platforms/Windows/App.xaml create mode 100644 SacsMobileMauiTestHost/Platforms/Windows/App.xaml.cs create mode 100644 SacsMobileMauiTestHost/Platforms/Windows/Package.appxmanifest create mode 100644 SacsMobileMauiTestHost/Platforms/Windows/app.manifest create mode 100644 SacsMobileMauiTestHost/Platforms/iOS/AppDelegate.cs create mode 100644 SacsMobileMauiTestHost/Platforms/iOS/Info.plist create mode 100644 SacsMobileMauiTestHost/Platforms/iOS/Program.cs create mode 100644 SacsMobileMauiTestHost/Platforms/iOS/Resources/PrivacyInfo.xcprivacy create mode 100644 SacsMobileMauiTestHost/Properties/launchSettings.json create mode 100644 SacsMobileMauiTestHost/README.md create mode 100644 SacsMobileMauiTestHost/Resources/AppIcon/appicon.svg create mode 100644 SacsMobileMauiTestHost/Resources/AppIcon/appiconfg.svg create mode 100644 SacsMobileMauiTestHost/Resources/Fonts/OpenSans-Regular.ttf create mode 100644 SacsMobileMauiTestHost/Resources/Fonts/OpenSans-Semibold.ttf create mode 100644 SacsMobileMauiTestHost/Resources/Images/dotnet_bot.png create mode 100644 SacsMobileMauiTestHost/Resources/Raw/AboutAssets.txt create mode 100644 SacsMobileMauiTestHost/Resources/Splash/splash.svg create mode 100644 SacsMobileMauiTestHost/Resources/Styles/Colors.xaml create mode 100644 SacsMobileMauiTestHost/Resources/Styles/Styles.xaml create mode 100644 SacsMobileMauiTestHost/SacsMobileMauiTestHost.csproj create mode 100644 SacsMobileMauiTests/Maui/Controls/SamDatePicker_Tester.cs create mode 100644 SacsMobileMauiTests/Maui/Logic/SamLogic_Tester.cs create mode 100644 SacsMobileMauiTests/Maui/Utils/SamThreadUtil_Tester.cs create mode 100644 SacsMobileMauiTests/SacsMobileMauiTests.csproj diff --git a/SaCommonVS.sln b/SaCommonVS.sln index b64ba15..d899eee 100644 --- a/SaCommonVS.sln +++ b/SaCommonVS.sln @@ -22,6 +22,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SacsStandardProc", "SacsSta EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SacsMobileMaui", "SacsMobileMaui\SacsMobileMaui.csproj", "{F167BCF8-8B99-4103-A710-D4B8FD5518A7}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SacsMobileMauiTests", "SacsMobileMauiTests\SacsMobileMauiTests.csproj", "{D7B6206D-6D7B-4A67-9F32-B09FCD6E8EAA}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SacsMobileMauiTestHost", "SacsMobileMauiTestHost\SacsMobileMauiTestHost.csproj", "{52B726C9-13B2-49DF-9352-90563709259B}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -56,6 +60,14 @@ Global {F167BCF8-8B99-4103-A710-D4B8FD5518A7}.Debug|Any CPU.Build.0 = Debug|Any CPU {F167BCF8-8B99-4103-A710-D4B8FD5518A7}.Release|Any CPU.ActiveCfg = Release|Any CPU {F167BCF8-8B99-4103-A710-D4B8FD5518A7}.Release|Any CPU.Build.0 = Release|Any CPU + {D7B6206D-6D7B-4A67-9F32-B09FCD6E8EAA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D7B6206D-6D7B-4A67-9F32-B09FCD6E8EAA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D7B6206D-6D7B-4A67-9F32-B09FCD6E8EAA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D7B6206D-6D7B-4A67-9F32-B09FCD6E8EAA}.Release|Any CPU.Build.0 = Release|Any CPU + {52B726C9-13B2-49DF-9352-90563709259B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {52B726C9-13B2-49DF-9352-90563709259B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {52B726C9-13B2-49DF-9352-90563709259B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {52B726C9-13B2-49DF-9352-90563709259B}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/SacsMobileMaui/Maui/App/Sam.cs b/SacsMobileMaui/Maui/App/Sam.cs index 938b6ea..21ac75c 100644 --- a/SacsMobileMaui/Maui/App/Sam.cs +++ b/SacsMobileMaui/Maui/App/Sam.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Globalization; using System.Linq; @@ -8,15 +8,18 @@ using System.Threading.Tasks; namespace Sarand.Net.Mobile.Maui.App { - //======================================================================= // Colour references: // http://www.html-color-names.com/skyblue.php // http://www.html-color-names.com/lightsteelblue.php - //======================================================================= + public static class Sam { - //------------------------------------------------------------------- - // Definitions. + // --- Configuration --- + // Values in this class are intended to be configured during application + // startup. Controls normally copy visual defaults when they are created; + // changing a value later does not automatically update existing controls. + + // --- Definitions --- public enum Platform { Unknown = 0, @@ -25,26 +28,30 @@ namespace Sarand.Net.Mobile.Maui.App MacCatalyst, Windows } - // Utilities. + /// Shared random-number generator for common MAUI code. public static Random Random { get; set; } = new Random(); - //------------------------------------------------------------------- - // Localisation. + // --- Localisation --- + /// Application-wide culture settings used by consumers that opt in. public static class Locale { + /// The culture used for application-level formatting. public static CultureInfo CultureInfo { get; set; } = CultureInfo.CurrentCulture; + /// Text rules associated with . public static TextInfo TextInfo { get; set; } = CultureInfo.TextInfo; } - //------------------------------------------------------------------- - // Visuals. + // --- Visuals --- + /// Supported positions for optional page content. public enum DisplayPosition { None, Left, Middle, Right } + + /// Shared visual defaults for common MAUI components. public static class Visuals { - // Application styling. + // --- Application styling --- public static class Application { - // Application bar. + // --- Application bar --- public static class Bar { public static double Height { get; set; } = 28; @@ -54,7 +61,7 @@ namespace Sarand.Net.Mobile.Maui.App public static Color BorderColor { get; set; } = Colors.DarkSlateBlue; public static double BorderBottom { get; set; } = 1.0; } - // Menu. + // --- Menu --- public static class Menu { public static Color BorderColor { get; set; } = Colors.DarkSlateBlue; @@ -63,28 +70,28 @@ namespace Sarand.Net.Mobile.Maui.App public static Color HeaderBackgroundColor { get; set; } = Color.FromArgb("#E0E0E0"); } } - // Page layout. + // --- Page layout --- public static class Page { public static Color BaseColor { get; set; } = Color.FromArgb("#c8e8f5"); public static Color BackgroundColor { get; set; } = Color.FromArgb("#e9f6fb"); - // Header. + // --- Header --- public static class Header { - // Text. + // --- Text --- public static String TextLeft { get; set; } = ""; public static String TextRight { get; set; } = ""; - // Toolbar. + // --- Toolbar --- public static DisplayPosition ToolbarDisplay { get; set; } = Sam.DisplayPosition.None; public static bool Display_ToolbarItem_Back { get; set; } = false; - // Styling. + // --- Styling --- public static Color BackgroundColor { get; set; } = Colors.Honeydew; public static Color BorderColor { get; set; } = Colors.LightSlateGray; public static double BorderStrokeWidthTop { get; set; } = 2.0; public static double BorderStrokeWidthBottom { get; set; } = 1.0; public static Thickness Padding { get; set; } = new Thickness(8, 4); } - // Caption. + // --- Caption --- public static class Caption { public static Color BackgroundColor { get; set; } = Color.FromArgb("#558395"); @@ -93,7 +100,7 @@ namespace Sarand.Net.Mobile.Maui.App public static Color TextColor { get; set; } = Color.FromArgb("#f8f8f8"); public static double TextSizeRatio { get; set; } = 1.25; } - // Navigation. + // --- Navigation --- public static class Navigation { public static double BorderTopWidth { get; set; } = 0.75; @@ -101,13 +108,13 @@ namespace Sarand.Net.Mobile.Maui.App public static Color BorderColor { get; set; } = Color.FromArgb("#808080"); public static Color BackgroundColor { get; set; } = Color.FromArgb("#e9f6fb"); } - // Body. + // --- Body --- public static class Body { public static double PaddingVerticalMin { get; set; } = 4.0; public static double PaddingHorizontalMin { get; set; } = 4.0; } - // Footer. + // --- Footer --- public static class Footer { public static String TextLeft { get; set; } = ""; @@ -122,18 +129,20 @@ namespace Sarand.Net.Mobile.Maui.App public static Thickness Padding { get; set; } = new Thickness(8, 4); } } - // Fonts. + // --- Fonts --- public static class Fonts { - // Dynamic font sizing. + // Values are calculated from the current device dimensions and are not + // copied from a mutable setting. + // --- Dynamic font sizing --- public static string HtmlFontSize { get { - if (SamApplication.MinDeviceDimension > 2000) return "14pt"; - if (SamApplication.MinDeviceDimension > 1600) return "13pt"; - if (SamApplication.MinDeviceDimension > 1200) return "12pt"; - if (SamApplication.MinDeviceDimension > 1000) return "11pt"; + if (SamApplication.MinDeviceDimensionPx > 2000) return "14pt"; + if (SamApplication.MinDeviceDimensionPx > 1600) return "13pt"; + if (SamApplication.MinDeviceDimensionPx > 1200) return "12pt"; + if (SamApplication.MinDeviceDimensionPx > 1000) return "11pt"; return "10pt"; } } @@ -141,25 +150,25 @@ namespace Sarand.Net.Mobile.Maui.App { get { - if (SamApplication.MinDeviceDimension > 2000) return 1.75; - if (SamApplication.MinDeviceDimension > 1600) return 1.50; - if (SamApplication.MinDeviceDimension > 1200) return 1.25; + if (SamApplication.MinDeviceDimensionPx > 2000) return 1.75; + if (SamApplication.MinDeviceDimensionPx > 1600) return 1.50; + if (SamApplication.MinDeviceDimensionPx > 1200) return 1.25; return 1.0; } } } - // Data display visuals. + // --- Data display visuals --- public static class Data { - // Item list. + // --- Item list --- public static class ItemList { public static double HeaderBorderWidth { get; set; } = 2.0; - public static float HeaderBorderRadius = 6.0f; - public static Color HeaderBorderColor = Sam.Visuals.Application.Bar.BackgroundColor; - public static Color HeaderBackgroundColor = Color.FromArgb("#F0F0F0"); + public static float HeaderBorderRadius { get; set; } = 6.0f; + public static Color HeaderBorderColor { get; set; } = Sam.Visuals.Application.Bar.BackgroundColor; + public static Color HeaderBackgroundColor { get; set; } = Color.FromArgb("#F0F0F0"); } - // Associated items panel. + // --- Associated items panel --- public static class AssociatedItems { public static Color CaptionBackgroundColor { get; set; } = Color.FromArgb("#becee4"); @@ -168,30 +177,30 @@ namespace Sarand.Net.Mobile.Maui.App public static Color BackgroundColor { get; set; } = Color.FromArgb("#dbe4f0"); } } - // Form. + // --- Form --- public static class Form { public static Color BackgroundColor { get; set; } = Color.FromArgb("#e9f6fb"); public static double GridRowSpacing { get; set; } = 4.0; public static double GridColumnSpacing { get; set; } = 4.0; - // Label. + // --- Label --- public static class Label { public static Thickness Margin { get; set; } = new Thickness(8.0, 4.0, 8.0, 2.0); } - // Controls. + // --- Controls --- public static class Controls { public static Thickness Padding { get; set; } = new Thickness(4.0, 2.0, 4.0, 1.0); public static Color BackgroundColor { get; set; } = Color.FromArgb("#F8F8F8"); public static Color BorderColor { get; set; } = Color.FromArgb("#E0E0E0"); public static double BorderWidth { get; set; } = 0.5; - // Native behaviour modified by SamHandler. + // --- Native behaviour modified by SamHandler --- public static bool NativeNoBorder { get; set; } = false; public static bool NativeNoUnderline { get; set; } = false; public static bool NativeBackgroundClear { get; set; } = false; } - // Tab controls. + // --- Tab controls --- public static class TabControl { public static Color HeaderBackgroundColor { get; set; } = Colors.Transparent; @@ -202,7 +211,6 @@ namespace Sarand.Net.Mobile.Maui.App } } - //------------------------------------------------------------------- + // --- The End --- } - //======================================================================= } diff --git a/SacsMobileMaui/Maui/App/SamApplication.cs b/SacsMobileMaui/Maui/App/SamApplication.cs index 947bc94..9491c4d 100644 --- a/SacsMobileMaui/Maui/App/SamApplication.cs +++ b/SacsMobileMaui/Maui/App/SamApplication.cs @@ -1,4 +1,4 @@ -using Sarand.Net.Standard.Base.App; +using Sarand.Net.Standard.Base.App; using System; using System.Collections.Generic; using System.Linq; @@ -7,20 +7,19 @@ using System.Threading.Tasks; namespace Sarand.Net.Mobile.Maui.App { - //======================================================================= + // --- Shared MAUI application base --- public class SamApplication : Application { - //------------------------------------------------------------------- - // Definitions. + // --- Data Members --- - //------------------------------------------------------------------- - // Data members. - private Sam.Platform platform; + private readonly Sam.Platform platform; + + // --- Construction --- - //------------------------------------------------------------------- protected SamApplication() { - // Determine platform. + // --- Platform --- + #if ANDROID platform = Sam.Platform.Android; #elif IOS @@ -31,30 +30,30 @@ namespace Sarand.Net.Mobile.Maui.App platform = Sam.Platform.Windows; #endif - // Log environment. + // --- Startup diagnostics --- + SaLogger.Debug("Platform: " + platform.ToString()); SaLogger.Debug("AppInfo.AppName: " + AppInfo.Name); SaLogger.Debug("AppInfo.PackageName: " + AppInfo.PackageName); SaLogger.Debug("AppInfo.Version: " + AppInfo.VersionString); SaLogger.Debug("AppInfo.Build: " + AppInfo.BuildString); - SaLogger.Debug("DeviceWidth: " + DeviceWidth); - SaLogger.Debug("DeviceHeight: " + DeviceHeight); + SaLogger.Debug("DeviceWidth: " + DeviceWidthPx); + SaLogger.Debug("DeviceHeight: " + DeviceHeightPx); SaLogger.Debug("DeviceDensity: " + DeviceDisplay.MainDisplayInfo.Density); } - //------------------------------------------------------------------- - // Properties. + // --- Properties --- public Sam.Platform Platform { get { return platform; } } - // Device screen size and orientation. - public static bool Landscape => DeviceWidth > DeviceHeight; - public static double DeviceWidth => DeviceDisplay.MainDisplayInfo.Width; - public static double DeviceHeight => DeviceDisplay.MainDisplayInfo.Height; - public static double MinDeviceDimension => Math.Min(DeviceWidth, DeviceHeight); - public static double MaxDeviceDimension => Math.Max(DeviceWidth, DeviceHeight); + // --- Display --- - //------------------------------------------------------------------- + public static bool Landscape => DeviceWidthPx > DeviceHeightPx; + public static double DeviceWidthPx => DeviceDisplay.MainDisplayInfo.Width; + public static double DeviceHeightPx => DeviceDisplay.MainDisplayInfo.Height; + public static double MinDeviceDimensionPx => Math.Min(DeviceWidthPx, DeviceHeightPx); + public static double MaxDeviceDimensionPx => Math.Max(DeviceWidthPx, DeviceHeightPx); + + // --- The End --- } - //======================================================================= } diff --git a/SacsMobileMaui/Maui/Base/SamComponents.cs b/SacsMobileMaui/Maui/Base/SamComponents.cs index 868951a..d24bc25 100644 --- a/SacsMobileMaui/Maui/Base/SamComponents.cs +++ b/SacsMobileMaui/Maui/Base/SamComponents.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Text; @@ -41,7 +41,7 @@ namespace Sarand.Net.Mobile.Maui.Base //------------------------------------------------------------------- // C o n t r o l A c t i v i t y //------------------------------------------------------------------- - protected virtual void SetControlActivity() + protected virtual void SetControlActivity(bool enabled) { } protected virtual void SetControlsReadOnly() diff --git a/SacsMobileMaui/Maui/Consoles/SamWebServiceConsole.cs b/SacsMobileMaui/Maui/Consoles/SamWebServiceConsole.cs index eee48d3..b84fec6 100644 --- a/SacsMobileMaui/Maui/Consoles/SamWebServiceConsole.cs +++ b/SacsMobileMaui/Maui/Consoles/SamWebServiceConsole.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Text; diff --git a/SacsMobileMaui/Maui/Controls/ISamControl.cs b/SacsMobileMaui/Maui/Controls/ISamControl.cs new file mode 100644 index 0000000..4179f57 --- /dev/null +++ b/SacsMobileMaui/Maui/Controls/ISamControl.cs @@ -0,0 +1,10 @@ +namespace Sarand.Net.Mobile.Maui.Controls +{ + // --- Definitions --- + public interface ISamControl + { + void SetControlActivity(bool enabled); + + // --- The End --- + } +} diff --git a/SacsMobileMaui/Maui/Controls/ISamNativeControlStyle.cs b/SacsMobileMaui/Maui/Controls/ISamNativeControlStyle.cs new file mode 100644 index 0000000..3b33b85 --- /dev/null +++ b/SacsMobileMaui/Maui/Controls/ISamNativeControlStyle.cs @@ -0,0 +1,19 @@ +namespace Sarand.Net.Mobile.Maui.Controls +{ + // --- Native control styling --- + /// + /// Exposes native styles applied when a MAUI handler is mapped. These plain properties + /// are read during handler mapping and changes made afterward do not automatically reapply them. + /// + public interface ISamNativeControlStyle + { + /// Removes the native control border where supported. + bool NativeNoBorder { get; set; } + /// Removes an Android-style underline where supported; other platforms may have no equivalent. + bool NativeNoUnderline { get; set; } + /// Makes the native control background transparent where supported. + bool NativeBackgroundClear { get; set; } + + // --- The End --- + } +} diff --git a/SacsMobileMaui/Maui/Controls/Panels/SamCheckBoxPanel.cs b/SacsMobileMaui/Maui/Controls/Panels/SamCheckBoxPanel.cs index 48210cc..ccbab0b 100644 --- a/SacsMobileMaui/Maui/Controls/Panels/SamCheckBoxPanel.cs +++ b/SacsMobileMaui/Maui/Controls/Panels/SamCheckBoxPanel.cs @@ -1,4 +1,4 @@ -using Sarand.Net.Mobile.Maui.Controls.Views; +using Sarand.Net.Mobile.Maui.Controls.Views; using Sarand.Net.Standard.Base.Utils; using System; using System.Collections.Generic; @@ -9,19 +9,15 @@ using System.Threading.Tasks; namespace Sarand.Net.Mobile.Maui.Controls.Panels { - //======================================================================= public class SamCheckBoxPanel : SamControlPanel { - //------------------------------------------------------------------- - // Definitions. + // --- Data Members --- - //------------------------------------------------------------------- - // Data members. private readonly SamCheckBox checkbox = new SamCheckBox() { }; private readonly TapGestureRecognizer gestureSuffixTap = new TapGestureRecognizer() { }; - //------------------------------------------------------------------- - // Construction. + // --- Construction --- + public SamCheckBoxPanel() { // View configuration. @@ -40,11 +36,11 @@ namespace Sarand.Net.Mobile.Maui.Controls.Panels LabelSuffix.GestureRecognizers.Add(gestureSuffixTap); ConfigureComponents(); - SetControlActivity(); + SetControlActivity(IsEnabled); } - //------------------------------------------------------------------- - // Properties. + // --- Properties --- + protected override View ControlView { get { return checkbox; } } public bool IsChecked { @@ -57,41 +53,16 @@ namespace Sarand.Net.Mobile.Maui.Controls.Panels set { checkbox.HorizontalOptions = value; ReadOnlyLabel.HorizontalOptions = checkbox.HorizontalOptions; } } - //------------------------------------------------------------------- - // C o n f i g u r e C o m p o n e n t s - //------------------------------------------------------------------- - /// Control configuration that can be changed dynamically. - public override void ConfigureComponents() - { - base.ConfigureComponents(); - } + // --- Control events --- - //------------------------------------------------------------------- - // C o n t r o l A c t i v i t y - //------------------------------------------------------------------- - public override void SetControlActivity() - { - base.SetControlActivity(); - SetControlsReadOnly(); - } - public override void SetControlsReadOnly() - { - base.SetControlsReadOnly(); - } - - //------------------------------------------------------------------- - // C o n t r o l E v e n t s - //------------------------------------------------------------------- private void Entry_Focused(object? sender, FocusEventArgs e) { Control_Focused(sender, e); } - //------------------------------------------------------------------- private void Entry_Unfocused(object? sender, FocusEventArgs e) { Control_Unfocused(sender, e); } - //------------------------------------------------------------------- // Checked changed. public event EventHandler? CheckedChanged; private void Checkbox_CheckedChanged(object? sender, CheckedChangedEventArgs e) @@ -106,42 +77,6 @@ namespace Sarand.Net.Mobile.Maui.Controls.Panels checkbox.IsChecked = !checkbox.IsChecked; } - //------------------------------------------------------------------- - // V i e w E v e n t s - //------------------------------------------------------------------- - protected override void OnParentSet() - { - base.OnParentSet(); - } - - //------------------------------------------------------------------- - protected override void OnPropertyChanging([CallerMemberName] string propertyName = "") - { - base.OnPropertyChanging(propertyName); - } - protected override void OnPropertyChanged([CallerMemberName] string propertyName = "") - { - base.OnPropertyChanged(propertyName); - //Logger.Debug(GetType().Name + ".OnPropertyChanged Name=" + propertyName); - } - - //------------------------------------------------------------------- - protected override void OnChildAdded(Element child) - { - base.OnChildAdded(child); - } - protected override void OnChildRemoved(Element child, int oldLogicalIndex) - { - base.OnChildRemoved(child, oldLogicalIndex); - } - - //------------------------------------------------------------------- - protected override void OnSizeAllocated(double width, double height) - { - base.OnSizeAllocated(width, height); - } - - //------------------------------------------------------------------- + // --- The End --- } - //======================================================================= } diff --git a/SacsMobileMaui/Maui/Controls/Panels/SamControlPanel.cs b/SacsMobileMaui/Maui/Controls/Panels/SamControlPanel.cs index 7d780a0..c9575c6 100644 --- a/SacsMobileMaui/Maui/Controls/Panels/SamControlPanel.cs +++ b/SacsMobileMaui/Maui/Controls/Panels/SamControlPanel.cs @@ -1,4 +1,4 @@ -using Sarand.Net.Mobile.Maui.App; +using Sarand.Net.Mobile.Maui.App; using Sarand.Net.Mobile.Maui.Views; using Sarand.Net.Standard.Base.Utils; using System; @@ -10,14 +10,10 @@ using System.Threading.Tasks; namespace Sarand.Net.Mobile.Maui.Controls.Views { - //======================================================================= public abstract class SamControlPanel : SamContentView { - //------------------------------------------------------------------- - // Definitions. + // --- Data Members --- - //------------------------------------------------------------------- - // Data members. // Components. private HorizontalStackLayout controlLayout = new HorizontalStackLayout(); private SamContentView controlBorder = new SamContentView(); @@ -27,8 +23,8 @@ namespace Sarand.Net.Mobile.Maui.Controls.Views private Color controlBorderColor = Sam.Visuals.Form.Controls.BorderColor; private Color controlBackgroundColor = Sam.Visuals.Form.Controls.BackgroundColor; - //------------------------------------------------------------------- - // Construction. + // --- Construction --- + protected SamControlPanel() { // Control layout: [Border[Panel]] [Suffix]. @@ -36,14 +32,12 @@ namespace Sarand.Net.Mobile.Maui.Controls.Views controlLayout.Add(controlBorder); controlLayout.Add(LabelSuffix); - // Main layout. + // --- Main layout --- Content = controlLayout; - - SetControlActivity(); } - //------------------------------------------------------------------- - // Properties. + // --- Properties --- + // Label. public SamLabel Label { get; } = new SamLabel() { Margin = Sam.Visuals.Form.Label.Margin }; @@ -92,9 +86,8 @@ namespace Sarand.Net.Mobile.Maui.Controls.Views set { if (ReadOnlyLabel.Text.Equals(value)) return; ReadOnlyLabel.Text = value; } } - //------------------------------------------------------------------- - // C o n f i g u r e C o m p o n e n t s - //------------------------------------------------------------------- + // --- Component configuration --- + /// Control configuration that can be changed dynamically. public override void ConfigureComponents() { @@ -104,13 +97,8 @@ namespace Sarand.Net.Mobile.Maui.Controls.Views controlPanel.BackgroundColor = controlBackgroundColor; } - //------------------------------------------------------------------- - // C o n t r o l A c t i v i t y - //------------------------------------------------------------------- - public override void SetControlActivity() - { - base.SetControlActivity(); - } + // --- Control activity --- + public override void SetControlsReadOnly() { base.SetControlsReadOnly(); @@ -118,24 +106,21 @@ namespace Sarand.Net.Mobile.Maui.Controls.Views else controlPanel.Content = ControlView; } - //------------------------------------------------------------------- - // C o n t r o l E v e n t s - //------------------------------------------------------------------- + // --- Control events --- + public new event EventHandler? Focused; protected void Control_Focused(object? sender, FocusEventArgs e) { if (Focused != null) Focused.Invoke(sender, e); } - //------------------------------------------------------------------- public new event EventHandler? Unfocused; protected void Control_Unfocused(object? sender, FocusEventArgs e) { if (Unfocused != null) Unfocused.Invoke(sender, e); } - //------------------------------------------------------------------- - // V i e w E v e n t s - //------------------------------------------------------------------- + // --- View events --- + protected override void OnPropertyChanged([CallerMemberName] string propertyName = "") { base.OnPropertyChanged(propertyName); @@ -156,7 +141,6 @@ namespace Sarand.Net.Mobile.Maui.Controls.Views } } - //------------------------------------------------------------------- + // --- The End --- } - //======================================================================= } diff --git a/SacsMobileMaui/Maui/Controls/Panels/SamEntryPanel.cs b/SacsMobileMaui/Maui/Controls/Panels/SamEntryPanel.cs index 6a6ee41..35b50c1 100644 --- a/SacsMobileMaui/Maui/Controls/Panels/SamEntryPanel.cs +++ b/SacsMobileMaui/Maui/Controls/Panels/SamEntryPanel.cs @@ -1,4 +1,4 @@ -using Sarand.Net.Standard.Base.Utils; +using Sarand.Net.Standard.Base.Utils; using System; using System.Collections.Generic; using System.Linq; @@ -8,18 +8,14 @@ using System.Threading.Tasks; namespace Sarand.Net.Mobile.Maui.Controls.Views { - //======================================================================= public class SamEntryPanel : SamControlPanel { - //------------------------------------------------------------------- - // Definitions. + // --- Data Members --- - //------------------------------------------------------------------- - // Data members. private readonly SamEntry entry = new SamEntry() { HorizontalOptions = LayoutOptions.Fill }; - //------------------------------------------------------------------- - // Construction. + // --- Construction --- + public SamEntryPanel() { // View configuration. @@ -34,11 +30,12 @@ namespace Sarand.Net.Mobile.Maui.Controls.Views entry.TextChanged += Entry_TextChanged; entry.Completed += Entry_Completed; - SetControlActivity(); + ConfigureComponents(); + SetControlActivity(IsEnabled); } - //------------------------------------------------------------------- - // Properties. + // --- Properties --- + protected override View ControlView { get { return entry; } } public int MaxLength { @@ -86,41 +83,16 @@ namespace Sarand.Net.Mobile.Maui.Controls.Views set { entry.HorizontalTextAlignment = value; ReadOnlyLabel.HorizontalTextAlignment = entry.HorizontalTextAlignment; } } - //------------------------------------------------------------------- - // C o n f i g u r e C o m p o n e n t s - //------------------------------------------------------------------- - /// Control configuration that can be changed dynamically. - public override void ConfigureComponents() - { - base.ConfigureComponents(); - } + // --- Control events --- - //------------------------------------------------------------------- - // C o n t r o l A c t i v i t y - //------------------------------------------------------------------- - public override void SetControlActivity() - { - base.SetControlActivity(); - SetControlsReadOnly(); - } - public override void SetControlsReadOnly() - { - base.SetControlsReadOnly(); - } - - //------------------------------------------------------------------- - // C o n t r o l E v e n t s - //------------------------------------------------------------------- private void Entry_Focused(object? sender, FocusEventArgs e) { Control_Focused(sender, e); } - //------------------------------------------------------------------- private void Entry_Unfocused(object? sender, FocusEventArgs e) { Control_Unfocused(sender, e); } - //------------------------------------------------------------------- // Text changed. public event EventHandler? TextChanged; private void Entry_TextChanged(object? sender, TextChangedEventArgs e) @@ -129,7 +101,6 @@ namespace Sarand.Net.Mobile.Maui.Controls.Views if (ReadOnly) return; if (TextChanged != null) TextChanged.Invoke(sender, e); } - //------------------------------------------------------------------- // Completed. public event EventHandler? Completed; private void Entry_Completed(object? sender, EventArgs e) @@ -138,42 +109,6 @@ namespace Sarand.Net.Mobile.Maui.Controls.Views if (Completed != null) Completed.Invoke(sender, e); } - //------------------------------------------------------------------- - // V i e w E v e n t s - //------------------------------------------------------------------- - protected override void OnParentSet() - { - base.OnParentSet(); - } - - //------------------------------------------------------------------- - protected override void OnPropertyChanging([CallerMemberName] string propertyName = "") - { - base.OnPropertyChanging(propertyName); - } - protected override void OnPropertyChanged([CallerMemberName] string propertyName = "") - { - base.OnPropertyChanged(propertyName); - //Logger.Debug(GetType().Name + ".OnPropertyChanged Name=" + propertyName); - } - - //------------------------------------------------------------------- - protected override void OnChildAdded(Element child) - { - base.OnChildAdded(child); - } - protected override void OnChildRemoved(Element child, int oldLogicalIndex) - { - base.OnChildRemoved(child, oldLogicalIndex); - } - - //------------------------------------------------------------------- - protected override void OnSizeAllocated(double width, double height) - { - base.OnSizeAllocated(width, height); - } - - //------------------------------------------------------------------- + // --- The End --- } - //======================================================================= } diff --git a/SacsMobileMaui/Maui/Controls/SamButton.cs b/SacsMobileMaui/Maui/Controls/SamButton.cs index a6ef2f1..71031c4 100644 --- a/SacsMobileMaui/Maui/Controls/SamButton.cs +++ b/SacsMobileMaui/Maui/Controls/SamButton.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Text; @@ -6,31 +6,30 @@ using System.Threading.Tasks; namespace Sarand.Net.Mobile.Maui.Controls { - //======================================================================= - public class SamButton : Button + /// + /// Application button extension point. It currently adds no behavior beyond + /// the MAUI contract. + /// + public class SamButton : Button, ISamControl { - //------------------------------------------------------------------- - // Definitions. + // --- Construction --- - //------------------------------------------------------------------- - // Data members. - - //------------------------------------------------------------------- - // Construction. public SamButton() { DefaultFontSize = FontSize; } - //------------------------------------------------------------------- - // Properties. + // --- Component state --- + + public void SetControlActivity(bool enabled) + { + if (IsEnabled != enabled) IsEnabled = enabled; + } + + // --- Properties --- + public double DefaultFontSize { get; } - //------------------------------------------------------------------- - // C o n t r o l E v e n t s - //------------------------------------------------------------------- - - //------------------------------------------------------------------- + // --- The End --- } - //======================================================================= } diff --git a/SacsMobileMaui/Maui/Controls/SamCheckBox.cs b/SacsMobileMaui/Maui/Controls/SamCheckBox.cs index 04a4b93..9ca6ae8 100644 --- a/SacsMobileMaui/Maui/Controls/SamCheckBox.cs +++ b/SacsMobileMaui/Maui/Controls/SamCheckBox.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Text; @@ -6,28 +6,25 @@ using System.Threading.Tasks; namespace Sarand.Net.Mobile.Maui.Controls { - //====================================================================== - public class SamCheckBox : CheckBox + /// + /// Application check-box extension point. It currently adds no behavior + /// beyond the MAUI contract. + /// + public class SamCheckBox : CheckBox, ISamControl { - //------------------------------------------------------------------- - // Definitions. + // --- Construction --- - //------------------------------------------------------------------- - // Data members. - - //------------------------------------------------------------------- - // Construction. public SamCheckBox() { } - //------------------------------------------------------------------- - // Properties. + // --- Component state --- - //------------------------------------------------------------------- - // Control events. + public void SetControlActivity(bool enabled) + { + if (IsEnabled != enabled) IsEnabled = enabled; + } - //------------------------------------------------------------------- + // --- The End --- } - //====================================================================== } diff --git a/SacsMobileMaui/Maui/Controls/SamDatePicker.cs b/SacsMobileMaui/Maui/Controls/SamDatePicker.cs index bea61e6..f76987a 100644 --- a/SacsMobileMaui/Maui/Controls/SamDatePicker.cs +++ b/SacsMobileMaui/Maui/Controls/SamDatePicker.cs @@ -1,4 +1,5 @@ using Sarand.Net.Mobile.Maui.Layouts; +using Sarand.Net.Mobile.Maui.Logic; using Sarand.Net.Mobile.Maui.Views; using Sarand.Net.Standard.Base.Utils; using System; @@ -9,24 +10,17 @@ using System.Threading.Tasks; namespace Sarand.Net.Mobile.Maui.Controls { - //======================================================================= public class SamDatePicker : SamContentView { - //------------------------------------------------------------------- - // Definitions. - - //------------------------------------------------------------------- - // Data members. + // --- Data Members --- private bool allowInvalidDate = false; - private bool invalidDate = true; - private DateTime previousDate = DateTime.MinValue; + private SamDateValueState dateState = new SamDateValueState(); private SamStackLayout controlLayout = new SamStackLayout() { Orientation = StackOrientation.Horizontal, HorizontalOptions = LayoutOptions.Fill }; private DatePicker pickerDate = new DatePicker() { IsVisible = false }; private SamEntry entryDate = new SamEntry() { HorizontalOptions = LayoutOptions.FillAndExpand, VerticalOptions = LayoutOptions.Fill }; private SamLabel labelResetDate = new SamLabel() { Text = "X", FontAttributes = FontAttributes.Bold, TextColor = Colors.Red, HorizontalOptions = LayoutOptions.Center, VerticalTextAlignment = TextAlignment.Center }; - //------------------------------------------------------------------- - // Construction. + // --- Construction --- public SamDatePicker() { // Initialise controls. @@ -58,17 +52,16 @@ namespace Sarand.Net.Mobile.Maui.Controls RefreshDateDisplay(); } - //------------------------------------------------------------------- - // Properties. - // Configuration. + // --- Properties --- public Boolean AllowInvalid { get { return allowInvalidDate; } set { - if (allowInvalidDate = value) return; + if (allowInvalidDate == value) return; allowInvalidDate = value; - if (!allowInvalidDate) invalidDate = false; + if (!allowInvalidDate && dateState.IsInvalid) + dateState.SetDate(pickerDate.Date ?? DateTime.Today); RefreshDateDisplay(); } } @@ -91,34 +84,29 @@ namespace Sarand.Net.Mobile.Maui.Controls get { return pickerDate.MaximumDate ?? DateTime.MaxValue; } set { pickerDate.MaximumDate = value; } } - //------------------------------------------------------------------- - // Date value. + // --- Date value --- public DateTime Date { get { - if (invalidDate) return DateTime.MinValue; - return pickerDate.Date ?? DateTime.MinValue; + return dateState.Date; } set { - if (Date.Equals(value)) return; - if (!DateUtil.IsValid(value)) + if (!dateState.SetDate(value)) return; + if (dateState.IsInvalid) { - invalidDate = true; pickerDate.Date = DateTime.Today; } else { - invalidDate = false; pickerDate.Date = value; } - previousDate = value; + RefreshDateDisplay(); } } - public String FormattedDate { get { return !invalidDate ? DateUtil.Format(Date, pickerDate.Format) : ""; } } - //------------------------------------------------------------------- - // Font. + public String FormattedDate { get { return !dateState.IsInvalid ? DateUtil.Format(Date, pickerDate.Format) : ""; } } + // --- Font --- public double DefaultFontSize { get; } public double FontSize { @@ -161,8 +149,7 @@ namespace Sarand.Net.Mobile.Maui.Controls } } - //------------------------------------------------------------------- - // Utilities. + // --- Utilities --- private void RefreshDateDisplay() { if (allowInvalidDate) @@ -178,44 +165,36 @@ namespace Sarand.Net.Mobile.Maui.Controls labelResetDate.IsVisible = false; } String dateValue = ""; - if (!invalidDate) + if (!dateState.IsInvalid) dateValue = DateUtil.Format(Date, pickerDate.Format); entryDate.Text = dateValue; } - //------------------------------------------------------------------- - // C o n t r o l E v e n t s - //------------------------------------------------------------------- + // --- Control events --- private void PickerDate_DateSelected(object? sender, DateChangedEventArgs e) { // Date has changed in control. - invalidDate = !DateUtil.IsValid(e.NewDate); + dateState.SetDate(e.NewDate ?? DateTime.MinValue); RefreshDateDisplay(); if (DateSelected != null) DateSelected.Invoke(sender, e); } public event EventHandler? DateSelected; - //------------------------------------------------------------------- private void EntryDate_Focused(object? sender, FocusEventArgs e) { // Control has gained focus. entryDate.Unfocus(); pickerDate.Focus(); - invalidDate = false; + dateState.SetDate(pickerDate.Date ?? DateTime.Today); RefreshDateDisplay(); } - //------------------------------------------------------------------- private void LabelValidDate_Tapped(object? sender, EventArgs e) { // 'Valid' label tapped. - if (invalidDate) return; - invalidDate = true; - DateTime oldDate = previousDate; + if (!dateState.TryInvalidate(out DateTime oldDate)) return; RefreshDateDisplay(); if (DateSelected != null) DateSelected.Invoke(sender, new DateChangedEventArgs(oldDate, Date)); - previousDate = Date; } - //------------------------------------------------------------------- + // --- The End --- } - //======================================================================= } diff --git a/SacsMobileMaui/Maui/Controls/SamDateTimePicker.cs b/SacsMobileMaui/Maui/Controls/SamDateTimePicker.cs index 443cdb9..c6cf769 100644 --- a/SacsMobileMaui/Maui/Controls/SamDateTimePicker.cs +++ b/SacsMobileMaui/Maui/Controls/SamDateTimePicker.cs @@ -9,22 +9,18 @@ using System.Threading.Tasks; namespace Sarand.Net.Mobile.Maui.Controls { - //======================================================================= public class SamDateTimePicker : SamContentView { - //------------------------------------------------------------------- - // Definitions. + // --- Data Members --- + // Control layout. private SamStackLayout layoutControl = new SamStackLayout() { Orientation = StackOrientation.Horizontal }; private SamDatePicker pickerDate = new SamDatePicker(); private SamLabel labelTimePrefix = new SamLabel(); private SamTimePicker pickerTime = new SamTimePicker(); - //------------------------------------------------------------------- - // Data members. + // --- Construction --- - //------------------------------------------------------------------- - // Construction. public SamDateTimePicker() { DefaultFontSize = pickerDate.FontSize; @@ -39,8 +35,8 @@ namespace Sarand.Net.Mobile.Maui.Controls pickerTime.TimeChanged += PickerTime_TimeChanged; } - //------------------------------------------------------------------- - // Properties. + // --- Properties --- + public string DateFormat { get { return pickerDate.Format; } @@ -50,6 +46,11 @@ namespace Sarand.Net.Mobile.Maui.Controls pickerDate.Format = value; } } + public bool AllowInvalid + { + get { return pickerDate.AllowInvalid; } + set { pickerDate.AllowInvalid = value; } + } public DateTime MinimumDate { get { return pickerDate.MinimumDate; } @@ -69,7 +70,7 @@ namespace Sarand.Net.Mobile.Maui.Controls pickerTime.Format = value; } } - // Control value. + // --- Control value --- public DateTime DateTime { get { return pickerDate.Date.Date + (pickerTime.Time ?? TimeSpan.Zero); } @@ -81,8 +82,15 @@ namespace Sarand.Net.Mobile.Maui.Controls } } public String DateTimeDelim { get; set; } = " "; - public String FormattedDateTime { get { return pickerDate.FormattedDate + DateTimeDelim + pickerTime.FormattedTime; } } - // Styling. + public String FormattedDateTime + { + get + { + if (StringUtil.IsEmpty(pickerDate.FormattedDate)) return ""; + return pickerDate.FormattedDate + DateTimeDelim + pickerTime.FormattedTime; + } + } + // --- Styling --- public double DefaultFontSize { get; } public double FontSize { @@ -101,9 +109,8 @@ namespace Sarand.Net.Mobile.Maui.Controls set { value = StringUtil.TrimAll(value); if (labelTimePrefix.Text.Equals(value)) return; labelTimePrefix.Text = value; ConfigureComponents(); } } - //------------------------------------------------------------------- - // C o n f i g u r e C o m p o n e n t s - //------------------------------------------------------------------- + // --- Component configuration --- + public override void ConfigureComponents() { base.ConfigureComponents(); @@ -127,34 +134,30 @@ namespace Sarand.Net.Mobile.Maui.Controls } } - //------------------------------------------------------------------- - // C o n t r o l A c t i v i t y - //------------------------------------------------------------------- - public override void SetControlActivity() + // --- Control activity --- + + public override void SetControlActivity(bool enabled) { - base.SetControlActivity(); + base.SetControlActivity(enabled); } public override void SetControlsReadOnly() { base.SetControlsReadOnly(); } - //------------------------------------------------------------------- - // C o n t r o l E v e n t s - //------------------------------------------------------------------- + // --- Control events --- + private void PickerDate_DateSelected(object? sender, DateChangedEventArgs e) { if (DateSelected != null) DateSelected.Invoke(sender, e); } public event EventHandler? DateSelected; - //------------------------------------------------------------------- private void PickerTime_TimeChanged(object? sender, SamTimePicker.TimeChangedEventArgs e) { if (TimeChanged != null) TimeChanged.Invoke(sender, e); } public event EventHandler? TimeChanged; - //------------------------------------------------------------------- + // --- The End --- } - //======================================================================= } diff --git a/SacsMobileMaui/Maui/Controls/SamEditor.cs b/SacsMobileMaui/Maui/Controls/SamEditor.cs index 7a83494..893e0ac 100644 --- a/SacsMobileMaui/Maui/Controls/SamEditor.cs +++ b/SacsMobileMaui/Maui/Controls/SamEditor.cs @@ -1,4 +1,4 @@ -using Sarand.Net.Mobile.Maui.App; +using Sarand.Net.Mobile.Maui.App; using Sarand.Net.Standard.Base.Utils; using System; using System.Collections.Generic; @@ -8,19 +8,15 @@ using System.Threading.Tasks; namespace Sarand.Net.Mobile.Maui.Controls { - //======================================================================= - public class SamEditor : Editor + public class SamEditor : Editor, ISamNativeControlStyle, ISamControl { - //------------------------------------------------------------------- - // Definitions. + // --- Data Members --- - //------------------------------------------------------------------- - // Data members. private bool trimTextOnCompletion = true; private int NumberOfLinesPrevious = 0; - //------------------------------------------------------------------- - // Construction. + // --- Construction --- + public SamEditor() { DefaultFontSize = FontSize; @@ -29,8 +25,15 @@ namespace Sarand.Net.Mobile.Maui.Controls Completed += SamEditor_Completed; } - //------------------------------------------------------------------- - // Properties. + // --- Component state --- + + public void SetControlActivity(bool enabled) + { + if (IsEnabled != enabled) IsEnabled = enabled; + } + + // --- Properties --- + public double DefaultFontSize { get; } @@ -44,14 +47,13 @@ namespace Sarand.Net.Mobile.Maui.Controls } } - // Native behaviour modified by SamHandler. + // --- Native styling --- public bool NativeNoBorder { get; set; } = Sam.Visuals.Form.Controls.NativeNoBorder; public bool NativeNoUnderline { get; set; } = Sam.Visuals.Form.Controls.NativeNoUnderline; public bool NativeBackgroundClear { get; set; } = Sam.Visuals.Form.Controls.NativeBackgroundClear; - //------------------------------------------------------------------- - // C o n t r o l E v e n t s - //------------------------------------------------------------------- + // --- Control events --- + // Key Press event is not supported directly in Xamarin. // Requires O/S specific implementation. // See: Sarand.LogisticsMgr.Mobile.Droid.Forms.Renderers.SlmEditorRenderer @@ -60,7 +62,6 @@ namespace Sarand.Net.Mobile.Maui.Controls // Resize control based on entered text. InvalidateMeasure(); } - //------------------------------------------------------------------- private void SamEditor_TextChanged(object? sender, TextChangedEventArgs e) { // If the number of lines of text has changed, resize the control. @@ -78,7 +79,6 @@ namespace Sarand.Net.Mobile.Maui.Controls InvalidateMeasure(); } - //------------------------------------------------------------------- + // --- The End --- } - //======================================================================= } diff --git a/SacsMobileMaui/Maui/Controls/SamEntry.cs b/SacsMobileMaui/Maui/Controls/SamEntry.cs index 2738565..9ffb6d9 100644 --- a/SacsMobileMaui/Maui/Controls/SamEntry.cs +++ b/SacsMobileMaui/Maui/Controls/SamEntry.cs @@ -1,4 +1,5 @@ -using Sarand.Net.Mobile.Maui.App; +using Sarand.Net.Mobile.Maui.App; +using Sarand.Net.Mobile.Maui.Logic; using Sarand.Net.Standard.Base.App; using Sarand.Net.Standard.Base.Utils; using System; @@ -10,11 +11,10 @@ using System.Threading.Tasks; namespace Sarand.Net.Mobile.Maui.Controls { - //======================================================================= - public class SamEntry : Entry + public class SamEntry : Entry, ISamNativeControlStyle, ISamControl { - //------------------------------------------------------------------- - // Definitions. + // --- Definitions --- + public enum TextCaseType { Normal, @@ -23,16 +23,16 @@ namespace Sarand.Net.Mobile.Maui.Controls Title } - //------------------------------------------------------------------- - // Data members. + // --- Data Members --- + private bool trimTextOnCompletion = true; private TextInfo textInfo = CultureInfo.CurrentCulture.TextInfo; private TextCaseType textCase = TextCaseType.Normal; private bool numericOnly = false; private byte decimals = 0; - //------------------------------------------------------------------- - // Construction. + // --- Construction --- + public SamEntry() { DefaultFontSize = FontSize; @@ -40,8 +40,15 @@ namespace Sarand.Net.Mobile.Maui.Controls Completed += SamEntry_Completed; } - //------------------------------------------------------------------- - // Properties. + // --- Component state --- + + public void SetControlActivity(bool enabled) + { + if (IsEnabled != enabled) IsEnabled = enabled; + } + + // --- Properties --- + public bool TrimText { get { return trimTextOnCompletion; } @@ -95,52 +102,26 @@ namespace Sarand.Net.Mobile.Maui.Controls } } - // Native behaviour modified by SamHandler. + // --- Native styling --- public bool NativeNoBorder { get; set; } = Sam.Visuals.Form.Controls.NativeNoBorder; public bool NativeNoUnderline { get; set; } = Sam.Visuals.Form.Controls.NativeNoUnderline; public bool NativeBackgroundClear { get; set; } = Sam.Visuals.Form.Controls.NativeBackgroundClear; - //------------------------------------------------------------------- - // F o r m a t T e x t - //------------------------------------------------------------------- + // --- Format text --- + private void FormatText(bool trimText = false) { String fn = GetType().Name + ".FormatText"; String originalText = Text; - String formattedText = originalText; - if (StringUtil.IsEmpty(formattedText)) return; - if (trimText) - formattedText = StringUtil.TrimAll(formattedText); - - if (numericOnly) + SamEntryTextFormatOptions options = new SamEntryTextFormatOptions() { - if (decimals > 0) - { - decimal value = StringUtil.ToDecimal(formattedText); - if (value != 0.0m) - formattedText = value.ToString("N" + decimals); - else if (!formattedText.Equals("0")) - formattedText = ""; - } - else - { - long value = StringUtil.ToLong(formattedText); - if (value != 0) - formattedText = value.ToString(); - else if (!formattedText.Equals("0")) - formattedText = ""; - } - } - else - { - if (textCase == TextCaseType.Upper) - formattedText = textInfo.ToUpper(formattedText); - else if (textCase == TextCaseType.Lower) - formattedText = textInfo.ToLower(formattedText); - else if (textCase == TextCaseType.Title) - formattedText = - StringUtil.ToTitleCase(formattedText, textInfo); - } + TextCase = textCase, + NumericOnly = numericOnly, + Decimals = decimals, + TrimText = trimText, + TextInfo = textInfo + }; + String formattedText = SamEntryTextFormatter.Format(originalText, options); if (!originalText.Equals(formattedText)) { @@ -149,9 +130,8 @@ namespace Sarand.Net.Mobile.Maui.Controls } } - //------------------------------------------------------------------- - // C o n t r o l E v e n t s - //------------------------------------------------------------------- + // --- Control events --- + private void SamEntry_TextChanged(object? sender, TextChangedEventArgs e) { if (FormatTextOnChange) FormatText(); @@ -161,7 +141,6 @@ namespace Sarand.Net.Mobile.Maui.Controls FormatText(trimTextOnCompletion); } - //------------------------------------------------------------------- + // --- The End --- } - //======================================================================= } diff --git a/SacsMobileMaui/Maui/Controls/SamImageButton.cs b/SacsMobileMaui/Maui/Controls/SamImageButton.cs index 2ee174f..c163442 100644 --- a/SacsMobileMaui/Maui/Controls/SamImageButton.cs +++ b/SacsMobileMaui/Maui/Controls/SamImageButton.cs @@ -1,4 +1,4 @@ -using Sarand.Net.Standard.Base.App; +using Sarand.Net.Standard.Base.App; using Sarand.Net.Standard.Base.Utils; using System; using System.Collections.Generic; @@ -8,33 +8,32 @@ using System.Threading.Tasks; namespace Sarand.Net.Mobile.Maui.Controls { - //======================================================================= - public class SamImageButton : ImageButton + /// + /// Application image-button extension point. It currently adds no active + /// behavior beyond the MAUI contract. + /// + public class SamImageButton : ImageButton, ISamControl { - //------------------------------------------------------------------- - // Definitions. + // --- Construction --- - //------------------------------------------------------------------- - // Data members. - - //------------------------------------------------------------------- - // Construction. public SamImageButton() { - //Clicked += Button_Clicked; } - //------------------------------------------------------------------- - // Properties. + // --- Component state --- + + public void SetControlActivity(bool enabled) + { + if (IsEnabled != enabled) IsEnabled = enabled; + } + + // --- Events --- - //------------------------------------------------------------------- - // Events. protected virtual void Button_Clicked(object? sender, EventArgs e) { SaLogger.Info(SysUtil.Fn + " Button clicked."); } - //------------------------------------------------------------------- + // --- The End --- } - //======================================================================= } diff --git a/SacsMobileMaui/Maui/Controls/SamLabel.cs b/SacsMobileMaui/Maui/Controls/SamLabel.cs index ec1eb24..ada292a 100644 --- a/SacsMobileMaui/Maui/Controls/SamLabel.cs +++ b/SacsMobileMaui/Maui/Controls/SamLabel.cs @@ -1,4 +1,4 @@ -using Sarand.Net.Standard.Base.Utils; +using Sarand.Net.Standard.Base.Utils; using System; using System.Collections.Generic; using System.Linq; @@ -8,19 +8,16 @@ using System.Threading.Tasks; namespace Sarand.Net.Mobile.Maui.Controls { - //======================================================================= - public class SamLabel : Label + public class SamLabel : Label, ISamControl { - //------------------------------------------------------------------- - // Definitions. + // --- Data Members --- - //------------------------------------------------------------------- - // Data members. private double fontSize; private double fontSizeProportion = 1.0; + private bool applyingFontSize; + + // --- Construction --- - //------------------------------------------------------------------- - // Construction. public SamLabel() { fontSize = base.FontSize; @@ -28,8 +25,8 @@ namespace Sarand.Net.Mobile.Maui.Controls Text = ""; } - //------------------------------------------------------------------- - // Properties. + // --- Properties --- + /// Default (initial) font size. public double FontSizeDefault { get; private set; } @@ -37,42 +34,54 @@ namespace Sarand.Net.Mobile.Maui.Controls public new double FontSize { get { return fontSize; } - set { fontSize = value; base.FontSize = fontSize * fontSizeProportion; } + set + { + if (fontSize == value) return; + fontSize = value; + ApplyFontSize(); + } } /// Font size proportion. public double FontSizeProportion { get { return fontSizeProportion; } - set { fontSizeProportion = value; base.FontSize = fontSize * fontSizeProportion; } + set + { + if (fontSizeProportion == value) return; + fontSizeProportion = value; + ApplyFontSize(); + } } - //------------------------------------------------------------------- - // Control events. + // --- Font sizing --- - //------------------------------------------------------------------- - // View events. - - protected override void OnPropertyChanging([CallerMemberName] string propertyName = "") + private void ApplyFontSize() { - base.OnPropertyChanging(propertyName); - if (propertyName.EqualsCI("FontSize")) - { - } + applyingFontSize = true; + base.FontSize = fontSize * fontSizeProportion; + applyingFontSize = false; } protected override void OnPropertyChanged([CallerMemberName] string propertyName = "") { base.OnPropertyChanged(propertyName); - if (propertyName.EqualsCI("FontSize")) + if (propertyName.EqualsCI("FontSize") && !applyingFontSize) { if (FontSizeDefault <= 0) FontSizeDefault = base.FontSize; - fontSize = base.FontSize; + if (fontSizeProportion != 0) + fontSize = base.FontSize / fontSizeProportion; } } - //------------------------------------------------------------------- + // --- Component state --- + + public void SetControlActivity(bool enabled) + { + if (IsEnabled != enabled) IsEnabled = enabled; + } + + // --- The End --- } - //======================================================================= } diff --git a/SacsMobileMaui/Maui/Controls/SamPicker.cs b/SacsMobileMaui/Maui/Controls/SamPicker.cs index 2481afe..d7a75e0 100644 --- a/SacsMobileMaui/Maui/Controls/SamPicker.cs +++ b/SacsMobileMaui/Maui/Controls/SamPicker.cs @@ -1,4 +1,5 @@ -using Sarand.Net.Mobile.Maui.App; +using Sarand.Net.Mobile.Maui.App; +using Sarand.Net.Mobile.Maui.Logic; using Sarand.Net.Standard.Base.Bus; using Sarand.Net.Standard.Base.Utils; using System; @@ -9,11 +10,10 @@ using System.Threading.Tasks; namespace Sarand.Net.Mobile.Maui.Controls { - //======================================================================= - public class SamPicker : Picker + public class SamPicker : Picker, ISamNativeControlStyle, ISamControl { - //------------------------------------------------------------------- - // Definitions. + // --- Definitions --- + public class SamPickerItem { public SysId Id { get; set; } = new SysId(); @@ -26,38 +26,37 @@ namespace Sarand.Net.Mobile.Maui.Controls } } - //------------------------------------------------------------------- - // Data members. - private readonly List pickerItems = new List(); + // --- Data Members --- + + private readonly SamPickerItemCollection pickerItems = new SamPickerItemCollection(); + + // --- Construction --- - //------------------------------------------------------------------- - // Construction. public SamPicker() { DefaultFontSize = FontSize; } - //------------------------------------------------------------------- - // Properties. + // --- Properties --- public double DefaultFontSize { get; } + /// Whether the picker is currently replacing its item list. public bool BeingPopulated { get; protected set; } = false; - // Native behaviour modified by SamHandler. + // --- Native styling --- public bool NativeNoBorder { get; set; } = Sam.Visuals.Form.Controls.NativeNoBorder; public bool NativeNoUnderline { get; set; } = Sam.Visuals.Form.Controls.NativeNoUnderline; public bool NativeBackgroundClear { get; set; } = Sam.Visuals.Form.Controls.NativeBackgroundClear; - //------------------------------------------------------------------- - // P o p u l a t e I t e m s - //------------------------------------------------------------------- + // --- Item population --- + /// /// Does the list contain an item with the same Id. /// NB Invalid Id values are matched. /// public bool ContainsItemId(SysId id) { - return pickerItems.Exists(item => SysId.ToInt(item.Id) == SysId.ToInt(id)); + return pickerItems.ContainsId(id); } /// /// Does the list contain an item with the same code. @@ -65,9 +64,8 @@ namespace Sarand.Net.Mobile.Maui.Controls /// public bool ContainsItemCode(String code) { - return pickerItems.Exists(item => item.Code.EqualsCI(code)); + return pickerItems.ContainsCode(code); } - //------------------------------------------------------------------- /// Remove all items from the picker. public void ClearItems() { @@ -75,22 +73,31 @@ namespace Sarand.Net.Mobile.Maui.Controls ItemsSource = null; pickerItems.Clear(); } - /// Replace existing picker item list. + /// Replace existing picker item list and raise . public void SetItems(List pickerItems) { - ClearItems(); - this.pickerItems.AddRange(pickerItems); - RefreshPickerItems(); + BeingPopulated = true; + bool populated = false; + try + { + ClearItems(); + this.pickerItems.Replace(pickerItems); + RefreshPickerItems(); + populated = true; + } + finally + { + BeingPopulated = false; + if (populated) InvokePopulatedEvent(); + } } - //------------------------------------------------------------------- /// /// Add an item identified by a SysId value. /// NB Fails if an item already exists with the same id. /// public bool AddItem(SysId id, String caption) { - if (ContainsItemId(id)) return false; - pickerItems.Add(new SamPickerItem() { Id = id, Caption = caption }); + if (!pickerItems.AddById(new SamPickerItem() { Id = id, Caption = caption })) return false; RefreshPickerItems(); return ContainsItemId(id); } @@ -100,25 +107,20 @@ namespace Sarand.Net.Mobile.Maui.Controls /// public bool AddItem(String code, String caption) { - if (ContainsItemCode(code)) return false; - pickerItems.Add(new SamPickerItem() { Code = code, Caption = caption }); + if (!pickerItems.AddByCode(new SamPickerItem() { Code = code, Caption = caption })) return false; RefreshPickerItems(); return ContainsItemCode(code); } - //------------------------------------------------------------------- private void RefreshPickerItems() { - //SetBinding(Picker.ItemsSourceProperty, BindableProperty.Create()); - //ItemDisplayBinding = new Binding("Caption"); - // Picker items doesn't automatically keep up to date with teh ItemsSource list. - // So clear it and reset to force the refresh. + // Picker items do not automatically keep the ItemsSource display in sync. + // Clear and reset the source to force a refresh. ItemsSource = null; - ItemsSource = pickerItems; + ItemsSource = pickerItems.Items; } - //------------------------------------------------------------------- - // I t e m S e l e c t i o n - //------------------------------------------------------------------- + // --- Item selection --- + public SamPickerItem? SelectedPickerItem { get @@ -143,7 +145,7 @@ namespace Sarand.Net.Mobile.Maui.Controls } set { - SelectedPickerItem = pickerItems.Find(item => item.Id.Equals(value)); + SelectedPickerItem = pickerItems.FindById(value); } } public String SelectedItemCode @@ -155,13 +157,12 @@ namespace Sarand.Net.Mobile.Maui.Controls } set { - SelectedPickerItem = pickerItems.Find(item => item.Code.EqualsCI(value)); + SelectedPickerItem = pickerItems.FindByCode(value); } } - //------------------------------------------------------------------- - // C o n t r o l E v e n t s - //------------------------------------------------------------------- + // --- Control events --- + // Picker population completed. public class PopulatedEventArgs : EventArgs { } public delegate void PopulatedEventHandler(object? sender, PopulatedEventArgs e); @@ -172,7 +173,13 @@ namespace Sarand.Net.Mobile.Maui.Controls if (Populated != null) Populated.Invoke(this, new PopulatedEventArgs() { }); } - //------------------------------------------------------------------- + // --- Component state --- + + public void SetControlActivity(bool enabled) + { + if (IsEnabled != enabled) IsEnabled = enabled; + } + + // --- The End --- } - //======================================================================= } diff --git a/SacsMobileMaui/Maui/Controls/SamTabControl.cs b/SacsMobileMaui/Maui/Controls/SamTabControl.cs index 29a2841..e9a8ad8 100644 --- a/SacsMobileMaui/Maui/Controls/SamTabControl.cs +++ b/SacsMobileMaui/Maui/Controls/SamTabControl.cs @@ -1,5 +1,6 @@ -using Microsoft.Maui; +using Microsoft.Maui; using Microsoft.Maui.Layouts; +using Sarand.Net.Mobile.Maui.App; using Sarand.Net.Mobile.Maui.Layouts; using Sarand.Net.Mobile.Maui.Panels; using Sarand.Net.Mobile.Maui.Views; @@ -11,14 +12,10 @@ using System.Threading.Tasks; namespace Sarand.Net.Mobile.Maui.Controls { - //======================================================================= - public class SamTabControl : ContentView + public class SamTabControl : ContentView, ISamView { - //------------------------------------------------------------------- - // Definitions. + // --- Data Members --- - //------------------------------------------------------------------- - // Data members. private readonly List tabPanels = new List(); private short currentTabIdx = -1; // Main layout to host header and body. @@ -28,39 +25,40 @@ namespace Sarand.Net.Mobile.Maui.Controls // Body - Content view to host tab panel. private SamContentView bodyPanel = new SamContentView(); - //------------------------------------------------------------------- - // Construction. + // --- Construction --- + public SamTabControl() { - // Main layout - vertical stack with two rows. - // 1. Header - tab panel captions. - // 2. Body - current tab panel. + // --- Main layout --- + VerticalOptions = LayoutOptions.FillAndExpand; - //BackgroundColor = Color.LightSkyBlue; mainLayout.VerticalOptions = LayoutOptions.FillAndExpand; mainLayout.Spacing = 2.0; Content = mainLayout; - // Header panel - tab captions. - //headerPanel.HorizontalOptions = LayoutOptions.Fill; + // --- Header panel --- + + headerPanel.BackgroundColor = Sam.Visuals.Form.TabControl.HeaderBackgroundColor; mainLayout.Children.Add(headerPanel); - // Default label font size. + // --- Default font size --- + DefaultHeaderFontSize = new Label().FontSize; HeaderFontSize = DefaultHeaderFontSize; - // Body panel. + // --- Body panel --- + bodyPanel.HorizontalOptions = LayoutOptions.Fill; bodyPanel.VerticalOptions = LayoutOptions.FillAndExpand; - bodyPanel.BackgroundColor = Colors.LightYellow; + bodyPanel.BackgroundColor = Sam.Visuals.Form.TabControl.BodyBackgroundColor; mainLayout.Children.Add(bodyPanel); } - //------------------------------------------------------------------- - // Properties. + // --- Properties --- + // Header. public Color HeaderBackgroundColor { get { return headerPanel.BackgroundColor; } set { headerPanel.BackgroundColor = value; } } - public Color CaptionBackgroundColor { get; set; } = Colors.Transparent; + public Color CaptionBackgroundColor { get; set; } = Sam.Visuals.Form.TabControl.CaptionBackgroundColor; public double DefaultHeaderFontSize { get; } public double HeaderFontSize { get; set; } public double HeaderMarginBottom @@ -84,9 +82,8 @@ namespace Sarand.Net.Mobile.Maui.Controls set { ChangeTabPanel(value); } } - //------------------------------------------------------------------- - // T a b P a n e l s - //------------------------------------------------------------------- + // --- Tab panels --- + public void ClearPanels() { tabPanels.Clear(); @@ -106,9 +103,8 @@ namespace Sarand.Net.Mobile.Maui.Controls RefreshLayout(); } - //------------------------------------------------------------------- - // R e f r e s h L a y o u t - //------------------------------------------------------------------- + // --- Refresh layout --- + private void RefreshLayout() { // Clear existing components. @@ -152,9 +148,8 @@ namespace Sarand.Net.Mobile.Maui.Controls ChangeTabPanel(currentTabIdx); } - //------------------------------------------------------------------- - // C h a n g e T a b P a n e l - //------------------------------------------------------------------- + // --- Change tab panel --- + private void ChangeTabPanel(short tabPanelIdx) { // Validate requested panel index. @@ -183,11 +178,16 @@ namespace Sarand.Net.Mobile.Maui.Controls currentTabIdx = tabPanelIdx; } - //------------------------------------------------------------------- - // C o n t r o l E v e n t s - //------------------------------------------------------------------- + // --- Component state --- - //------------------------------------------------------------------- + public void ConfigureComponents() { } + public void SetControlActivity(bool enabled) + { + if (IsEnabled != enabled) IsEnabled = enabled; + SetControlsReadOnly(); + } + public void SetControlsReadOnly() { } + + // --- The End --- } - //======================================================================= } diff --git a/SacsMobileMaui/Maui/Controls/SamTimePicker.cs b/SacsMobileMaui/Maui/Controls/SamTimePicker.cs index 3ebaade..8e6da4a 100644 --- a/SacsMobileMaui/Maui/Controls/SamTimePicker.cs +++ b/SacsMobileMaui/Maui/Controls/SamTimePicker.cs @@ -9,7 +9,7 @@ using System.Threading.Tasks; namespace Sarand.Net.Mobile.Maui.Controls { //======================================================================= - public class SamTimePicker : TimePicker + public class SamTimePicker : TimePicker, ISamControl { //------------------------------------------------------------------- // Definitions. @@ -47,6 +47,13 @@ namespace Sarand.Net.Mobile.Maui.Controls base.OnPropertyChanged(propertyName); } + // --- Component state --- + + public void SetControlActivity(bool enabled) + { + if (IsEnabled != enabled) IsEnabled = enabled; + } + //------------------------------------------------------------------- } //======================================================================= diff --git a/SacsMobileMaui/Maui/Controls/SamWebView.cs b/SacsMobileMaui/Maui/Controls/SamWebView.cs index cedb4f1..4bb561d 100644 --- a/SacsMobileMaui/Maui/Controls/SamWebView.cs +++ b/SacsMobileMaui/Maui/Controls/SamWebView.cs @@ -1,4 +1,4 @@ -using Sarand.Net.Standard.Base.App; +using Sarand.Net.Standard.Base.App; using Sarand.Net.Standard.Base.Utils; using System; using System.Collections.Generic; @@ -8,36 +8,33 @@ using System.Threading.Tasks; namespace Sarand.Net.Mobile.Maui.Controls { - //======================================================================= - public class SamWebView : WebView + public class SamWebView : WebView, ISamControl { - //------------------------------------------------------------------- - // Definitions. + // --- Data Members --- - //------------------------------------------------------------------- - // Data members. public String html = ""; - private HtmlWebViewSource htmlSource; - private UrlWebViewSource aboutBlankSource = new UrlWebViewSource() { Url = "about:blank" }; - // Managers. + private readonly HtmlWebViewSource htmlSource; + private readonly UrlWebViewSource aboutBlankSource = new UrlWebViewSource() { Url = "about:blank" }; + + // --- Construction --- - //------------------------------------------------------------------- - // Construction. public SamWebView() { - // Events. + // --- Events --- + Navigating += WebView_Navigating; Navigated += WebView_Navigated; - // HTML source. + // --- HTML source --- + htmlSource = new HtmlWebViewSource() { Html = html }; Source = aboutBlankSource; } - //------------------------------------------------------------------- - // Properties. - // HTML Panel. + // --- Properties --- + public static String AppActionPrefix => "App:"; + /// Gets or sets the HTML displayed by the web view. public String Html { get { return html; } @@ -51,9 +48,8 @@ namespace Sarand.Net.Mobile.Maui.Controls } } - //------------------------------------------------------------------- - // N a v i g a t i o n - //------------------------------------------------------------------- + // --- Navigation --- + public class AppNavigatingEventArgs : EventArgs { public AppNavigatingEventArgs(String appAction) @@ -70,13 +66,12 @@ namespace Sarand.Net.Mobile.Maui.Controls } private String GetAppAction(String location) { - if (!location.StartsWithCI(AppActionPrefix)) return ""; + if (String.IsNullOrEmpty(location) || !location.StartsWithCI(AppActionPrefix)) return ""; return location.Right(location.Length - AppActionPrefix.Length); } - //------------------------------------------------------------------- - // C o n t r o l E v e n t s - //------------------------------------------------------------------- + // --- Control events --- + private void WebView_Navigating(object? sender, WebNavigatingEventArgs e) { String appAction = GetAppAction(e.Url); @@ -97,7 +92,13 @@ namespace Sarand.Net.Mobile.Maui.Controls } } - //------------------------------------------------------------------- + // --- Component state --- + + public void SetControlActivity(bool enabled) + { + if (IsEnabled != enabled) IsEnabled = enabled; + } + + // --- The End --- } - //======================================================================= } diff --git a/SacsMobileMaui/Maui/Handlers/SamFormHandler.cs b/SacsMobileMaui/Maui/Handlers/SamFormHandler.cs index 433fdca..8b34bae 100644 --- a/SacsMobileMaui/Maui/Handlers/SamFormHandler.cs +++ b/SacsMobileMaui/Maui/Handlers/SamFormHandler.cs @@ -1,4 +1,4 @@ -#if ANDROID +#if ANDROID using Microsoft.Maui.Controls.Compatibility.Platform.Android; #endif using Sarand.Net.Mobile.Maui.Controls; @@ -10,26 +10,25 @@ using System.Threading.Tasks; namespace Sarand.Net.Mobile.Maui.Handlers { - //======================================================================== // Ref. https://dev.to/vhugogarcia/remove-entry-and-picker-borders-in-net-maui-2pk2 // Ref. https://github.com/dotnet/maui/issues/7906 + public static class SamFormHandler { - //-------------------------------------------------------------------- - // Definitions. + // --- Control handler registration --- - //-------------------------------------------------------------------- /// - /// Remove borders around entry and picker controls. + /// Apply configured native styling to Sam entry, editor, and picker controls. /// Also make background transparent. /// public static void AssignControlHandlers() { - //--------------------------------------------------------------- - // Entry handler. + // --- Entry handler --- + Microsoft.Maui.Handlers.EntryHandler.Mapper.AppendToMapping("SamEntryHandler", (handler, view) => { - if ((view as SamEntry)?.NativeNoBorder ?? false) + if (view is not ISamNativeControlStyle style) return; + if (style.NativeNoBorder) { #if ANDROID handler.PlatformView.BackgroundTintList = Android.Content.Res.ColorStateList.ValueOf(Colors.Transparent.ToAndroid()); @@ -40,15 +39,17 @@ namespace Sarand.Net.Mobile.Maui.Handlers handler.PlatformView.BorderThickness = new Microsoft.UI.Xaml.Thickness() { Bottom = 0, Top = 0, Left = 0, Right = 0, }; #endif } - if ((view as SamEntry)?.NativeNoUnderline ?? false) + if (style.NativeNoUnderline) { #if ANDROID handler.PlatformView.BackgroundTintList = Android.Content.Res.ColorStateList.ValueOf(Colors.Transparent.ToAndroid()); #elif IOS || MACCATALYST + // UITextField/UITextView do not expose a separate Android-style underline. #elif WINDOWS + // WinUI text controls use a border, not a separate underline; use NativeNoBorder to remove it. #endif } - if ((view as SamEntry)?.NativeBackgroundClear ?? false) + if (style.NativeBackgroundClear) { #if ANDROID handler.PlatformView.Background = null; @@ -56,34 +57,37 @@ namespace Sarand.Net.Mobile.Maui.Handlers #elif IOS || MACCATALYST handler.PlatformView.BackgroundColor = UIKit.UIColor.Clear; #elif WINDOWS + handler.PlatformView.Background = new Microsoft.UI.Xaml.Media.SolidColorBrush(Microsoft.UI.Colors.Transparent); #endif } }); - //--------------------------------------------------------------- - // Editor handler. + // --- Editor handler --- + Microsoft.Maui.Handlers.EditorHandler.Mapper.AppendToMapping("SamEditorHandler", (handler, view) => { - if ((view as SamEditor)?.NativeNoBorder ?? false) + if (view is not ISamNativeControlStyle style) return; + if (style.NativeNoBorder) { #if ANDROID handler.PlatformView.BackgroundTintList = Android.Content.Res.ColorStateList.ValueOf(Colors.Transparent.ToAndroid()); #elif IOS || MACCATALYST handler.PlatformView.Layer.BorderWidth = 0; - //handler.PlatformView.BorderStyle = UIKit.UITextViewBorderStyle.None; #elif WINDOWS handler.PlatformView.BorderThickness = new Microsoft.UI.Xaml.Thickness() { Bottom = 0, Top = 0, Left = 0, Right = 0, }; #endif } - if ((view as SamEntry)?.NativeNoUnderline ?? false) + if (style.NativeNoUnderline) { #if ANDROID handler.PlatformView.BackgroundTintList = Android.Content.Res.ColorStateList.ValueOf(Colors.Transparent.ToAndroid()); #elif IOS || MACCATALYST + // UITextView has no separate underline to remove. #elif WINDOWS + // WinUI text controls use a border, not a separate underline; use NativeNoBorder to remove it. #endif } - if ((view as SamEntry)?.NativeBackgroundClear ?? false) + if (style.NativeBackgroundClear) { #if ANDROID handler.PlatformView.Background = null; @@ -91,15 +95,17 @@ namespace Sarand.Net.Mobile.Maui.Handlers #elif IOS || MACCATALYST handler.PlatformView.BackgroundColor = UIKit.UIColor.Clear; #elif WINDOWS + handler.PlatformView.Background = new Microsoft.UI.Xaml.Media.SolidColorBrush(Microsoft.UI.Colors.Transparent); #endif } }); - //--------------------------------------------------------------- - // Picker handler. - Microsoft.Maui.Handlers.PickerHandler.Mapper.AppendToMapping("Borderless", (handler, view) => + // --- Picker handler --- + + Microsoft.Maui.Handlers.PickerHandler.Mapper.AppendToMapping("SamPickerHandler", (handler, view) => { - if ((view as SamEditor)?.NativeNoBorder ?? false) + if (view is not ISamNativeControlStyle style) return; + if (style.NativeNoBorder) { #if ANDROID handler.PlatformView.BackgroundTintList = Android.Content.Res.ColorStateList.ValueOf(Colors.Transparent.ToAndroid()); @@ -110,15 +116,17 @@ namespace Sarand.Net.Mobile.Maui.Handlers handler.PlatformView.BorderThickness = new Microsoft.UI.Xaml.Thickness() { Bottom = 0, Top = 0, Left = 0, Right = 0, }; #endif } - if ((view as SamEntry)?.NativeNoUnderline ?? false) + if (style.NativeNoUnderline) { #if ANDROID handler.PlatformView.BackgroundTintList = Android.Content.Res.ColorStateList.ValueOf(Colors.Transparent.ToAndroid()); #elif IOS || MACCATALYST + // The native picker has a full border, not a separate underline; use NativeNoBorder to remove it. #elif WINDOWS + // The WinUI picker has a full border, not a separate underline; use NativeNoBorder to remove it. #endif } - if ((view as SamEntry)?.NativeBackgroundClear ?? false) + if (style.NativeBackgroundClear) { #if ANDROID handler.PlatformView.Background = null; @@ -126,14 +134,13 @@ namespace Sarand.Net.Mobile.Maui.Handlers #elif IOS || MACCATALYST handler.PlatformView.BackgroundColor = UIKit.UIColor.Clear; #elif WINDOWS + handler.PlatformView.Background = new Microsoft.UI.Xaml.Media.SolidColorBrush(Microsoft.UI.Colors.Transparent); #endif } }); - //--------------------------------------------------------------- } - //-------------------------------------------------------------------- + // --- The End --- } - //======================================================================== } diff --git a/SacsMobileMaui/Maui/Layouts/SamAbsoluteLayout.cs b/SacsMobileMaui/Maui/Layouts/SamAbsoluteLayout.cs index 6b6b6b2..4b3bb1e 100644 --- a/SacsMobileMaui/Maui/Layouts/SamAbsoluteLayout.cs +++ b/SacsMobileMaui/Maui/Layouts/SamAbsoluteLayout.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Text; diff --git a/SacsMobileMaui/Maui/Layouts/SamFlexLayout.cs b/SacsMobileMaui/Maui/Layouts/SamFlexLayout.cs index 8b8ba34..38f6a69 100644 --- a/SacsMobileMaui/Maui/Layouts/SamFlexLayout.cs +++ b/SacsMobileMaui/Maui/Layouts/SamFlexLayout.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Text; diff --git a/SacsMobileMaui/Maui/Layouts/SamGrid.cs b/SacsMobileMaui/Maui/Layouts/SamGrid.cs index 7935ffb..086c73c 100644 --- a/SacsMobileMaui/Maui/Layouts/SamGrid.cs +++ b/SacsMobileMaui/Maui/Layouts/SamGrid.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Runtime.CompilerServices; diff --git a/SacsMobileMaui/Maui/Layouts/SamStackLayout.cs b/SacsMobileMaui/Maui/Layouts/SamStackLayout.cs index b1aea8b..b533186 100644 --- a/SacsMobileMaui/Maui/Layouts/SamStackLayout.cs +++ b/SacsMobileMaui/Maui/Layouts/SamStackLayout.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Text; diff --git a/SacsMobileMaui/Maui/Layouts/SamWrapLayout.cs b/SacsMobileMaui/Maui/Layouts/SamWrapLayout.cs index b1903d6..bf006ec 100644 --- a/SacsMobileMaui/Maui/Layouts/SamWrapLayout.cs +++ b/SacsMobileMaui/Maui/Layouts/SamWrapLayout.cs @@ -1,4 +1,4 @@ -using Microsoft.Maui.Controls.Compatibility; +using Microsoft.Maui.Controls.Compatibility; namespace Sarand.Net.Mobile.Maui.Layouts { @@ -184,17 +184,13 @@ namespace Sarand.Net.Mobile.Maui.Layouts { child = c, size = c.Measure(widthConstraint, heightConstraint) - }); + }).ToList(); - var nextChildren = visibleChildren.Skip(1).ToList(); - nextChildren.Add(null); //make element count same - - var zipChildren = visibleChildren.Zip(nextChildren, (c, n) => new { current = c, next = n }); - - foreach (var childBlock in zipChildren) + for (var childIndex = 0; childIndex < visibleChildren.Count; childIndex++) { - var child = childBlock.current.child; - var size = childBlock.current.size; + var childBlock = visibleChildren[childIndex]; + var child = childBlock.child; + var size = childBlock.size; var itemWidth = size.Width; var itemHeight = size.Height; @@ -210,7 +206,7 @@ namespace Sarand.Net.Mobile.Maui.Layouts LayoutChildIntoBoundingRegion(child, region); } - if (childBlock.next == null) + if (childIndex == visibleChildren.Count - 1) { totalHeight += rowHeight; totalWidth = Math.Max(totalWidth, rowWidth); @@ -218,7 +214,7 @@ namespace Sarand.Net.Mobile.Maui.Layouts } xPos += itemWidth + SpacingX; - var nextWitdh = childBlock.next.size.Width; + var nextWitdh = visibleChildren[childIndex + 1].size.Width; if (xPos + nextWitdh - x > widthConstraint) { diff --git a/SacsMobileMaui/Maui/Logic/SamDateValueState.cs b/SacsMobileMaui/Maui/Logic/SamDateValueState.cs new file mode 100644 index 0000000..cd15ff7 --- /dev/null +++ b/SacsMobileMaui/Maui/Logic/SamDateValueState.cs @@ -0,0 +1,44 @@ +using Sarand.Net.Standard.Base.Utils; +using System; + +namespace Sarand.Net.Mobile.Maui.Logic +{ + // --- Date value state --- + public sealed class SamDateValueState + { + private DateTime date = DateTime.MinValue; + private bool invalid = true; + + public DateTime Date { get { return invalid ? DateTime.MinValue : date; } } + public bool IsInvalid { get { return invalid; } } + + public bool SetDate(DateTime value) + { + if (Date.Equals(value)) return false; + if (DateUtil.IsValid(value)) + { + date = value; + invalid = false; + } + else + { + date = DateTime.MinValue; + invalid = true; + } + return true; + } + + public bool TryInvalidate(out DateTime previousDate) + { + if (invalid) + { + previousDate = DateTime.MinValue; + return false; + } + previousDate = date; + date = DateTime.MinValue; + invalid = true; + return true; + } + } +} diff --git a/SacsMobileMaui/Maui/Logic/SamEntryTextFormatOptions.cs b/SacsMobileMaui/Maui/Logic/SamEntryTextFormatOptions.cs new file mode 100644 index 0000000..daa9b54 --- /dev/null +++ b/SacsMobileMaui/Maui/Logic/SamEntryTextFormatOptions.cs @@ -0,0 +1,18 @@ +using Sarand.Net.Mobile.Maui.Controls; +using System; +using System.Globalization; + +namespace Sarand.Net.Mobile.Maui.Logic +{ + // --- Entry text format options --- + public sealed class SamEntryTextFormatOptions + { + public SamEntry.TextCaseType TextCase { get; init; } = SamEntry.TextCaseType.Normal; + public bool NumericOnly { get; init; } + public byte Decimals { get; init; } + public bool TrimText { get; init; } + public TextInfo TextInfo { get; init; } = CultureInfo.CurrentCulture.TextInfo; + + // --- The End --- + } +} diff --git a/SacsMobileMaui/Maui/Logic/SamEntryTextFormatter.cs b/SacsMobileMaui/Maui/Logic/SamEntryTextFormatter.cs new file mode 100644 index 0000000..f4942c8 --- /dev/null +++ b/SacsMobileMaui/Maui/Logic/SamEntryTextFormatter.cs @@ -0,0 +1,48 @@ +using Sarand.Net.Standard.Base.Utils; +using System; + +namespace Sarand.Net.Mobile.Maui.Logic +{ + // --- Entry text formatting --- + public static class SamEntryTextFormatter + { + public static String Format(String? text, SamEntryTextFormatOptions options) + { + String originalText = text ?? ""; + String formattedText = originalText; + if (StringUtil.IsEmpty(formattedText)) return formattedText; + if (options.TrimText) + formattedText = StringUtil.TrimAll(formattedText); + + if (options.NumericOnly) + { + if (options.Decimals > 0) + { + decimal value = StringUtil.ToDecimal(formattedText); + if (value != 0.0m) + formattedText = value.ToString("N" + options.Decimals); + else if (!formattedText.Equals("0")) + formattedText = ""; + } + else + { + long value = StringUtil.ToLong(formattedText); + if (value != 0) + formattedText = value.ToString(); + else if (!formattedText.Equals("0")) + formattedText = ""; + } + } + else if (options.TextCase == Controls.SamEntry.TextCaseType.Upper) + formattedText = options.TextInfo.ToUpper(formattedText); + else if (options.TextCase == Controls.SamEntry.TextCaseType.Lower) + formattedText = options.TextInfo.ToLower(formattedText); + else if (options.TextCase == Controls.SamEntry.TextCaseType.Title) + formattedText = StringUtil.ToTitleCase(formattedText, options.TextInfo); + + return formattedText; + } + + // --- The End --- + } +} diff --git a/SacsMobileMaui/Maui/Logic/SamPickerItemCollection.cs b/SacsMobileMaui/Maui/Logic/SamPickerItemCollection.cs new file mode 100644 index 0000000..f90330a --- /dev/null +++ b/SacsMobileMaui/Maui/Logic/SamPickerItemCollection.cs @@ -0,0 +1,61 @@ +using Sarand.Net.Mobile.Maui.Controls; +using Sarand.Net.Standard.Base.Bus; +using Sarand.Net.Standard.Base.Utils; +using System; +using System.Collections.Generic; + +namespace Sarand.Net.Mobile.Maui.Logic +{ + // --- Picker item collection --- + public sealed class SamPickerItemCollection + { + private readonly List items = new List(); + + public List Items { get { return items; } } + + public void Clear() + { + items.Clear(); + } + + public void Replace(IEnumerable replacementItems) + { + items.Clear(); + items.AddRange(replacementItems); + } + + public bool ContainsId(SysId id) + { + return items.Exists(item => SysId.ToInt(item.Id) == SysId.ToInt(id)); + } + + public bool ContainsCode(String code) + { + return items.Exists(item => item.Code.EqualsCI(code)); + } + + public bool AddById(SamPicker.SamPickerItem item) + { + if (ContainsId(item.Id)) return false; + items.Add(item); + return true; + } + + public bool AddByCode(SamPicker.SamPickerItem item) + { + if (ContainsCode(item.Code)) return false; + items.Add(item); + return true; + } + + public SamPicker.SamPickerItem? FindById(SysId id) + { + return items.Find(item => item.Id.Equals(id)); + } + + public SamPicker.SamPickerItem? FindByCode(String code) + { + return items.Find(item => item.Code.EqualsCI(code)); + } + } +} diff --git a/SacsMobileMaui/Maui/Logic/SamViewTreeUtil.cs b/SacsMobileMaui/Maui/Logic/SamViewTreeUtil.cs new file mode 100644 index 0000000..a8270a1 --- /dev/null +++ b/SacsMobileMaui/Maui/Logic/SamViewTreeUtil.cs @@ -0,0 +1,36 @@ +using Sarand.Net.Mobile.Maui.Views; +using Sarand.Net.Mobile.Maui.Controls; +using System.Collections.Generic; + +namespace Sarand.Net.Mobile.Maui.Logic +{ + // --- Sam view tree operations --- + public static class SamViewTreeUtil + { + public static void ConfigureChildren(Element? root) + { + foreach (Element element in GetElements(root)) + if (element is ISamView samView) + samView.ConfigureComponents(); + } + + public static void SetChildControlActivity(Element? root, bool enabled) + { + foreach (Element element in GetElements(root)) + { + if (element is ISamView samView) + samView.SetControlActivity(enabled); + if (element is ISamControl samControl) + samControl.SetControlActivity(enabled); + } + } + + private static IEnumerable GetElements(Element? root) + { + if (root == null) yield break; + yield return root; + foreach (Element descendant in root.GetVisualTreeDescendants()) + yield return descendant; + } + } +} diff --git a/SacsMobileMaui/Maui/Pages/SamContentPage.cs b/SacsMobileMaui/Maui/Pages/SamContentPage.cs index 57a2f15..db6bb49 100644 --- a/SacsMobileMaui/Maui/Pages/SamContentPage.cs +++ b/SacsMobileMaui/Maui/Pages/SamContentPage.cs @@ -1,4 +1,5 @@ -using Microsoft.Maui.Controls; +using Microsoft.Maui.Controls; +using Sarand.Net.Mobile.Maui.Logic; using Sarand.Net.Mobile.Maui.Views; using Sarand.Net.Standard.Base.Utils; using System; @@ -11,7 +12,7 @@ using System.Threading.Tasks; namespace Sarand.Net.Mobile.Maui.Pages { //======================================================================= - public abstract class SamContentPage : ContentPage + public abstract class SamContentPage : ContentPage, ISamView { //------------------------------------------------------------------- // Definitions. @@ -38,61 +39,35 @@ namespace Sarand.Net.Mobile.Maui.Pages protected virtual void ConfigureChildComponents() { - // Configure child components. - if (Content != null) - { - ConfigureChildComponent(Content); - foreach (var descendent in Content.GetVisualTreeDescendants()) - { - View? childView = descendent as View; - if (childView != null) - ConfigureChildComponent(childView); - } - } - } - - private void ConfigureChildComponent(View view) - { - if (view is ISamView) - { - ISamView? samView = view as ISamView; - if (samView != null) - samView.ConfigureComponents(); - } + SamViewTreeUtil.ConfigureChildren(Content); } //------------------------------------------------------------------- // C o n t r o l A c t i v i t y //------------------------------------------------------------------- - protected virtual void SetControlActivity() + protected virtual void SetControlActivity(bool enabled) { + if (IsEnabled != enabled) IsEnabled = enabled; SetControlsReadOnly(); - - // Child component control activity. - if (Content != null) - { - SetChildControlActivity(Content); - foreach (var descendent in Content.GetVisualTreeDescendants()) - { - View? childView = descendent as View; - if (childView != null) - SetChildControlActivity(childView); - } - } - } - private void SetChildControlActivity(View view) - { - if (view is ISamView) - { - ISamView? samView = view as ISamView; - if (samView != null) - samView.SetControlActivity(); - } + SamViewTreeUtil.SetChildControlActivity(Content, enabled); } protected virtual void SetControlsReadOnly() { } + void ISamView.ConfigureComponents() + { + ConfigureComponents(); + } + void ISamView.SetControlActivity(bool enabled) + { + SetControlActivity(enabled); + } + void ISamView.SetControlsReadOnly() + { + SetControlsReadOnly(); + } + //------------------------------------------------------------------- // P o p u p M e s s a g e s //------------------------------------------------------------------- @@ -107,7 +82,7 @@ namespace Sarand.Net.Mobile.Maui.Pages String cancel = "OK"; if (alertMode == AlertMode.Close) cancel = "Close"; - return DisplayAlert(title, message, cancel); + return DisplayAlertAsync(title, message, cancel); } public enum ConfirmMode { @@ -124,7 +99,7 @@ namespace Sarand.Net.Mobile.Maui.Pages accept = "OK"; cancel = "Cancel"; } - return await DisplayAlert(title, message, accept, cancel); + return await DisplayAlertAsync(title, message, accept, cancel); } //------------------------------------------------------------------- diff --git a/SacsMobileMaui/Maui/Panels/SamPanel.cs b/SacsMobileMaui/Maui/Panels/SamPanel.cs index 7bda81f..b341212 100644 --- a/SacsMobileMaui/Maui/Panels/SamPanel.cs +++ b/SacsMobileMaui/Maui/Panels/SamPanel.cs @@ -1,4 +1,4 @@ -using Sarand.Net.Mobile.Maui.Views; +using Sarand.Net.Mobile.Maui.Views; using System; using System.Collections.Generic; using System.Linq; @@ -37,11 +37,13 @@ namespace Sarand.Net.Mobile.Maui.Panels //------------------------------------------------------------------- // C o n t r o l A c t i v i t y //------------------------------------------------------------------- - public override void SetControlActivity() + public override void SetControlActivity(bool enabled) { + base.SetControlActivity(enabled); } public override void SetControlsReadOnly() { + base.SetControlsReadOnly(); } //------------------------------------------------------------------- diff --git a/SacsMobileMaui/Maui/Panels/SamPanel_ItemFilter.cs b/SacsMobileMaui/Maui/Panels/SamPanel_ItemFilter.cs index 9f3904f..e7b2466 100644 --- a/SacsMobileMaui/Maui/Panels/SamPanel_ItemFilter.cs +++ b/SacsMobileMaui/Maui/Panels/SamPanel_ItemFilter.cs @@ -1,4 +1,4 @@ -using Sarand.Net.Mobile.Maui.Layouts; +using Sarand.Net.Mobile.Maui.Layouts; using System; using System.Collections.Generic; using System.Linq; diff --git a/SacsMobileMaui/Maui/Panels/SamPanel_ItemFilterProperty.cs b/SacsMobileMaui/Maui/Panels/SamPanel_ItemFilterProperty.cs index 539e703..9722b12 100644 --- a/SacsMobileMaui/Maui/Panels/SamPanel_ItemFilterProperty.cs +++ b/SacsMobileMaui/Maui/Panels/SamPanel_ItemFilterProperty.cs @@ -1,4 +1,4 @@ -using Sarand.Net.Mobile.Maui.Controls; +using Sarand.Net.Mobile.Maui.Controls; using Sarand.Net.Mobile.Maui.Layouts; using Sarand.Net.Standard.Base.Utils; using System; diff --git a/SacsMobileMaui/Maui/Panels/SamPanel_ItemListHeader.cs b/SacsMobileMaui/Maui/Panels/SamPanel_ItemListHeader.cs index a3083ae..90dc57e 100644 --- a/SacsMobileMaui/Maui/Panels/SamPanel_ItemListHeader.cs +++ b/SacsMobileMaui/Maui/Panels/SamPanel_ItemListHeader.cs @@ -1,4 +1,4 @@ -using Sarand.Net.Mobile.Maui.Controls; +using Sarand.Net.Mobile.Maui.Controls; using Sarand.Net.Mobile.Maui.Layouts; using Sarand.Net.Mobile.Maui.Utils; using Sarand.Net.Mobile.Maui.Views; diff --git a/SacsMobileMaui/Maui/Panels/SamPanel_ItemListSummary.cs b/SacsMobileMaui/Maui/Panels/SamPanel_ItemListSummary.cs index ffb4111..b29f156 100644 --- a/SacsMobileMaui/Maui/Panels/SamPanel_ItemListSummary.cs +++ b/SacsMobileMaui/Maui/Panels/SamPanel_ItemListSummary.cs @@ -1,4 +1,4 @@ -using Sarand.Net.Mobile.Maui.Controls; +using Sarand.Net.Mobile.Maui.Controls; using Sarand.Net.Mobile.Maui.Layouts; using Sarand.Net.Mobile.Maui.Utils; using Sarand.Net.Mobile.Maui.Views; diff --git a/SacsMobileMaui/Maui/Panels/SamPanel_Messages.cs b/SacsMobileMaui/Maui/Panels/SamPanel_Messages.cs index 99c1985..72f77ae 100644 --- a/SacsMobileMaui/Maui/Panels/SamPanel_Messages.cs +++ b/SacsMobileMaui/Maui/Panels/SamPanel_Messages.cs @@ -1,4 +1,4 @@ -using Sarand.Net.Mobile.Maui.Controls; +using Sarand.Net.Mobile.Maui.Controls; using Sarand.Net.Mobile.Maui.Layouts; using Sarand.Net.Standard.Base.Core; using System; diff --git a/SacsMobileMaui/Maui/Panels/SamPanel_Page_Footer.cs b/SacsMobileMaui/Maui/Panels/SamPanel_Page_Footer.cs index fced510..70d8590 100644 --- a/SacsMobileMaui/Maui/Panels/SamPanel_Page_Footer.cs +++ b/SacsMobileMaui/Maui/Panels/SamPanel_Page_Footer.cs @@ -1,4 +1,4 @@ -using Sarand.Net.Mobile.Maui.App; +using Sarand.Net.Mobile.Maui.App; using Sarand.Net.Mobile.Maui.Controls; using Sarand.Net.Mobile.Maui.Layouts; using Sarand.Net.Mobile.Maui.Utils; diff --git a/SacsMobileMaui/Maui/Panels/SamPanel_Page_Header.cs b/SacsMobileMaui/Maui/Panels/SamPanel_Page_Header.cs index 9cc4c8b..a7f84cc 100644 --- a/SacsMobileMaui/Maui/Panels/SamPanel_Page_Header.cs +++ b/SacsMobileMaui/Maui/Panels/SamPanel_Page_Header.cs @@ -1,4 +1,4 @@ -using Microsoft.Maui.Controls.Internals; +using Microsoft.Maui.Controls.Internals; using Sarand.Net.Mobile.Maui.App; using Sarand.Net.Mobile.Maui.Controls; using Sarand.Net.Mobile.Maui.Layouts; diff --git a/SacsMobileMaui/Maui/Panels/SamPanel_Progress.cs b/SacsMobileMaui/Maui/Panels/SamPanel_Progress.cs index bf36767..5a71b3b 100644 --- a/SacsMobileMaui/Maui/Panels/SamPanel_Progress.cs +++ b/SacsMobileMaui/Maui/Panels/SamPanel_Progress.cs @@ -1,4 +1,4 @@ -using Sarand.Net.Mobile.Maui.Controls; +using Sarand.Net.Mobile.Maui.Controls; using Sarand.Net.Mobile.Maui.Layouts; using Sarand.Net.Mobile.Maui.Views; using System; diff --git a/SacsMobileMaui/Maui/Panels/SamPanel_PropertySummary.cs b/SacsMobileMaui/Maui/Panels/SamPanel_PropertySummary.cs index 631e691..a514d84 100644 --- a/SacsMobileMaui/Maui/Panels/SamPanel_PropertySummary.cs +++ b/SacsMobileMaui/Maui/Panels/SamPanel_PropertySummary.cs @@ -1,4 +1,4 @@ -using Sarand.Net.Mobile.Maui.Controls; +using Sarand.Net.Mobile.Maui.Controls; using Sarand.Net.Mobile.Maui.Layouts; using Sarand.Net.Mobile.Maui.Views; using System; diff --git a/SacsMobileMaui/Maui/Panels/SamPanel_Tab.cs b/SacsMobileMaui/Maui/Panels/SamPanel_Tab.cs index f1a906d..58dfa38 100644 --- a/SacsMobileMaui/Maui/Panels/SamPanel_Tab.cs +++ b/SacsMobileMaui/Maui/Panels/SamPanel_Tab.cs @@ -1,4 +1,4 @@ - + using Sarand.Net.Mobile.Maui.Views; using System; using System.Collections.Generic; @@ -38,11 +38,13 @@ namespace Sarand.Net.Mobile.Maui.Panels //------------------------------------------------------------------- // C o n t r o l A c t i v i t y //------------------------------------------------------------------- - public override void SetControlActivity() + public override void SetControlActivity(bool enabled) { + base.SetControlActivity(enabled); } public override void SetControlsReadOnly() { + base.SetControlsReadOnly(); } //------------------------------------------------------------------- diff --git a/SacsMobileMaui/Maui/Panels/SamPanel_Tabbed.cs b/SacsMobileMaui/Maui/Panels/SamPanel_Tabbed.cs index 912f923..28b9d38 100644 --- a/SacsMobileMaui/Maui/Panels/SamPanel_Tabbed.cs +++ b/SacsMobileMaui/Maui/Panels/SamPanel_Tabbed.cs @@ -1,4 +1,4 @@ -using Sarand.Net.Mobile.Maui.Views; +using Sarand.Net.Mobile.Maui.Views; using Sarand.Net.Standard.Base.Utils; using System; using System.Collections.Generic; @@ -165,11 +165,13 @@ namespace Sarand.Net.Mobile.Maui.Panels //------------------------------------------------------------------- // C o n t r o l A c t i v i t y //------------------------------------------------------------------- - public override void SetControlActivity() + public override void SetControlActivity(bool enabled) { + base.SetControlActivity(enabled); } public override void SetControlsReadOnly() { + base.SetControlsReadOnly(); } //------------------------------------------------------------------- diff --git a/SacsMobileMaui/Maui/Panels/SamTabPanel.cs b/SacsMobileMaui/Maui/Panels/SamTabPanel.cs index 0235740..f7348dc 100644 --- a/SacsMobileMaui/Maui/Panels/SamTabPanel.cs +++ b/SacsMobileMaui/Maui/Panels/SamTabPanel.cs @@ -1,4 +1,4 @@ -using Sarand.Net.Mobile.Maui.Views; +using Sarand.Net.Mobile.Maui.Views; using System; using System.Collections.Generic; using System.Linq; diff --git a/SacsMobileMaui/Maui/Utils/SamElementUtil.cs b/SacsMobileMaui/Maui/Utils/SamElementUtil.cs index c5b69aa..c94790d 100644 --- a/SacsMobileMaui/Maui/Utils/SamElementUtil.cs +++ b/SacsMobileMaui/Maui/Utils/SamElementUtil.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Text; diff --git a/SacsMobileMaui/Maui/Utils/SamFormUtil.cs b/SacsMobileMaui/Maui/Utils/SamFormUtil.cs index bc07216..fd686c6 100644 --- a/SacsMobileMaui/Maui/Utils/SamFormUtil.cs +++ b/SacsMobileMaui/Maui/Utils/SamFormUtil.cs @@ -1,9 +1,6 @@ -using Sarand.Net.Standard.Base.App; +using Sarand.Net.Standard.Base.App; using Sarand.Net.Standard.Base.Utils; using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; using System.Threading.Tasks; namespace Sarand.Net.Mobile.Maui.Utils @@ -11,39 +8,28 @@ namespace Sarand.Net.Mobile.Maui.Utils //======================================================================= public static class SamFormsUtil { - //------------------------------------------------------------------- - // D i s p l a y A l e r t a n d W a i t - //------------------------------------------------------------------- - public static void DisplayAlertSync(String title, String message, String cancel = "OK") + // --- Display dialogs --- + public static Task DisplayAlertAsync(String title, String message, String cancel = "OK") { - if (Application.Current?.MainPage == null) + Page? mainPage = Application.Current?.Windows.FirstOrDefault()?.Page; + if (mainPage == null) { SaLogger.Warn(SysUtil.Fn + " Null main page. Message: " + message); - return; + return Task.CompletedTask; } - SamThreadUtil.InvokeOnMainThreadSync(async () => await Application.Current.MainPage.DisplayAlert(title, message, cancel)); + return SamThreadUtil.InvokeOnMainThreadAsync(() => mainPage.DisplayAlertAsync(title, message, cancel)); } - public static bool DisplayConfirmSync(String title, String message, String accept = "Yes", String cancel = "No") + public static Task DisplayConfirmAsync(String title, String message, String accept = "Yes", String cancel = "No") { - if (Application.Current?.MainPage == null) + Page? mainPage = Application.Current?.Windows.FirstOrDefault()?.Page; + if (mainPage == null) { SaLogger.Warn(SysUtil.Fn + " Null main page. Message: " + message); - return false; + return Task.FromResult(false); } - var tcs = new TaskCompletionSource(); - MainThread.BeginInvokeOnMainThread(async () => - { - try - { - bool result = await Application.Current.MainPage.DisplayAlert(title, message, accept, cancel); - tcs.SetResult(result); - } - catch (Exception ex) { tcs.SetException(ex); } - }); - return tcs.Task.Result; + return SamThreadUtil.InvokeOnMainThreadAsync(() => mainPage.DisplayAlertAsync(title, message, accept, cancel)); } - //------------------------------------------------------------------- + // --- The End --- } - //======================================================================= } diff --git a/SacsMobileMaui/Maui/Utils/SamImageUtil.cs b/SacsMobileMaui/Maui/Utils/SamImageUtil.cs index cb78529..fa6aa39 100644 --- a/SacsMobileMaui/Maui/Utils/SamImageUtil.cs +++ b/SacsMobileMaui/Maui/Utils/SamImageUtil.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Text; diff --git a/SacsMobileMaui/Maui/Utils/SamPageUtil.cs b/SacsMobileMaui/Maui/Utils/SamPageUtil.cs index b2fbd2d..522a5ac 100644 --- a/SacsMobileMaui/Maui/Utils/SamPageUtil.cs +++ b/SacsMobileMaui/Maui/Utils/SamPageUtil.cs @@ -1,4 +1,4 @@ -using Microsoft.Maui.Graphics; +using Microsoft.Maui.Graphics; using System; using System.Collections.Generic; using System.Linq; diff --git a/SacsMobileMaui/Maui/Utils/SamResourceUtil.cs b/SacsMobileMaui/Maui/Utils/SamResourceUtil.cs index 3985cbe..a9ad411 100644 --- a/SacsMobileMaui/Maui/Utils/SamResourceUtil.cs +++ b/SacsMobileMaui/Maui/Utils/SamResourceUtil.cs @@ -1,61 +1,42 @@ -using Sarand.Net.Standard.Base.App; +using Sarand.Net.Standard.Base.App; using Sarand.Net.Standard.Base.Utils; using System; -using System.Collections.Generic; -using System.Linq; +using System.IO; using System.Reflection; -using System.Text; -using System.Threading.Tasks; namespace Sarand.Net.Mobile.Maui.Utils { - //======================================================================= public class SamResourceUtil { - //------------------------------------------------------------------- - // Resource as Stream. - + // --- Resource stream --- public static Stream? GetResourceStream(String resourcePath) { return GetResourceStream(Assembly.GetExecutingAssembly(), resourcePath); } - //------------------------------------------------------------------- public static Stream? GetResourceStream(Assembly assembly, String resourcePath) { + if (String.IsNullOrWhiteSpace(resourcePath)) return null; + Stream? stream = assembly.GetManifestResourceStream(resourcePath); if (stream != null) return stream; - foreach (var res in assembly.GetManifestResourceNames()) + foreach (String resourceName in assembly.GetManifestResourceNames()) { - //SaLogger.Info(SysUtil.Fn + $" Resource: {res.ToString()}"); - - // Read source file - stream = assembly.GetManifestResourceStream(res); - //using var reader = new StreamReader(stream); - //var content = reader.ReadToEnd(); - - // generate target path inside data folder - //string targetFile = System.IO.Path.Combine(FileSystem.Current.AppDataDirectory, res); - - // write file into data folder - //using FileStream outputStream = System.IO.File.OpenWrite(targetFile); - //using StreamWriter streamWriter = new StreamWriter(outputStream); - - //streamWriter.Write(content); + if (!resourceName.EndsWith(resourcePath, StringComparison.OrdinalIgnoreCase)) continue; + return assembly.GetManifestResourceStream(resourceName); } + return null; } - //------------------------------------------------------------------- - // Log resources. + // --- Resource diagnostics --- public static void LogResources(Assembly assembly) { - foreach (var res in assembly.GetManifestResourceNames()) - SaLogger.Info(SysUtil.Fn + $" Resource: {res.ToString()}"); + foreach (String resourceName in assembly.GetManifestResourceNames()) + SaLogger.Info(SysUtil.Fn + $" Resource: {resourceName}"); } - //------------------------------------------------------------------- + // --- The End --- } - //======================================================================= } diff --git a/SacsMobileMaui/Maui/Utils/SamThreadUtil.cs b/SacsMobileMaui/Maui/Utils/SamThreadUtil.cs index 2502347..5b9ee9f 100644 --- a/SacsMobileMaui/Maui/Utils/SamThreadUtil.cs +++ b/SacsMobileMaui/Maui/Utils/SamThreadUtil.cs @@ -1,7 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; +using System; using System.Threading.Tasks; namespace Sarand.Net.Mobile.Maui.Utils @@ -19,12 +16,10 @@ namespace Sarand.Net.Mobile.Maui.Utils return MainThread.IsMainThread; } - //------------------------------------------------------------------- - // I n v o k e o n M a i n T h r e a d a n d W a i t - //------------------------------------------------------------------- + // --- Fire-and-forget dispatch --- - /// Invoke task on main thread. - public static void InvokeOnMainThread(Action taskFn) + /// Invoke an action on the main thread without waiting for completion. + public static void PostToMainThread(Action taskFn) { MainThread.BeginInvokeOnMainThread(() => { @@ -32,38 +27,32 @@ namespace Sarand.Net.Mobile.Maui.Utils }); } - /// Invoke task on main thread and wait for completion. - public static void InvokeOnMainThreadSync(Action taskFn) + // --- Awaitable dispatch --- + + /// Invoke an action on the main thread and await its completion. + public static Task InvokeOnMainThreadAsync(Action taskFn) { - var tcs = new TaskCompletionSource(); - MainThread.BeginInvokeOnMainThread(() => - { - try - { - taskFn(); - tcs.SetResult(true); - } - catch (Exception ex) { tcs.SetException(ex); } - }); + return MainThread.InvokeOnMainThreadAsync(taskFn); } - /// Invoke task on main thread and wait for completion. - public static T InvokeOnMainThreadSync(Func taskFn) + /// Invoke an asynchronous function on the main thread and await its completion. + public static Task InvokeOnMainThreadAsync(Func taskFn) { - var tcs = new TaskCompletionSource(); - MainThread.BeginInvokeOnMainThread(() => - { - try - { - var result = taskFn(); - tcs.SetResult(result); - } - catch (Exception ex) { tcs.SetException(ex); } - }); - return tcs.Task.Result; + return MainThread.InvokeOnMainThreadAsync(taskFn); } - //------------------------------------------------------------------- + /// Invoke a function on the main thread and await its result. + public static Task InvokeOnMainThreadAsync(Func taskFn) + { + return MainThread.InvokeOnMainThreadAsync(taskFn); + } + + /// Invoke an asynchronous function on the main thread and await its result. + public static Task InvokeOnMainThreadAsync(Func> taskFn) + { + return MainThread.InvokeOnMainThreadAsync(taskFn); + } + + // --- The End --- } - //======================================================================= } diff --git a/SacsMobileMaui/Maui/Utils/SamTimerUtil.cs b/SacsMobileMaui/Maui/Utils/SamTimerUtil.cs index 3669ebe..8464070 100644 --- a/SacsMobileMaui/Maui/Utils/SamTimerUtil.cs +++ b/SacsMobileMaui/Maui/Utils/SamTimerUtil.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Text; diff --git a/SacsMobileMaui/Maui/Views/ISamView.cs b/SacsMobileMaui/Maui/Views/ISamView.cs index a4e62b8..e8ed9f2 100644 --- a/SacsMobileMaui/Maui/Views/ISamView.cs +++ b/SacsMobileMaui/Maui/Views/ISamView.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Text; @@ -18,7 +18,7 @@ namespace Sarand.Net.Mobile.Maui.Views /// void ConfigureComponents(); - void SetControlActivity(); + void SetControlActivity(bool enabled); void SetControlsReadOnly(); diff --git a/SacsMobileMaui/Maui/Views/SamBorder.cs b/SacsMobileMaui/Maui/Views/SamBorder.cs index cd45971..4f85bf6 100644 --- a/SacsMobileMaui/Maui/Views/SamBorder.cs +++ b/SacsMobileMaui/Maui/Views/SamBorder.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Text; @@ -7,7 +7,7 @@ using System.Threading.Tasks; namespace Sarand.Net.Mobile.Maui.Views { //======================================================================= - public class SamBorder : Border + public class SamBorder : Border, ISamView { //------------------------------------------------------------------- // Definitions. @@ -32,8 +32,9 @@ namespace Sarand.Net.Mobile.Maui.Views //------------------------------------------------------------------- // Control activity. - public virtual void SetControlActivity() + public virtual void SetControlActivity(bool enabled) { + if (IsEnabled != enabled) IsEnabled = enabled; SetControlsReadOnly(); } public virtual void SetControlsReadOnly() diff --git a/SacsMobileMaui/Maui/Views/SamBoxView.cs b/SacsMobileMaui/Maui/Views/SamBoxView.cs index 1eb952b..199911f 100644 --- a/SacsMobileMaui/Maui/Views/SamBoxView.cs +++ b/SacsMobileMaui/Maui/Views/SamBoxView.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Text; @@ -32,8 +32,9 @@ namespace Sarand.Net.Mobile.Maui.Views //------------------------------------------------------------------- // Control activity. - public virtual void SetControlActivity() + public virtual void SetControlActivity(bool enabled) { + if (IsEnabled != enabled) IsEnabled = enabled; SetControlsReadOnly(); } public virtual void SetControlsReadOnly() diff --git a/SacsMobileMaui/Maui/Views/SamContentView.cs b/SacsMobileMaui/Maui/Views/SamContentView.cs index 45fbf2c..efc664c 100644 --- a/SacsMobileMaui/Maui/Views/SamContentView.cs +++ b/SacsMobileMaui/Maui/Views/SamContentView.cs @@ -1,4 +1,4 @@ -using Sarand.Net.Mobile.Maui.Utils; +using Sarand.Net.Mobile.Maui.Utils; using Sarand.Net.Standard.Base.Utils; using System; using System.Collections.Generic; @@ -33,7 +33,7 @@ namespace Sarand.Net.Mobile.Maui.Views public bool ReadOnly { get { return readOnly; } - set { if (readOnly == value) return; readOnly = value; SetControlActivity(); } + set { if (readOnly == value) return; readOnly = value; SetControlActivity(IsEnabled); } } //------------------------------------------------------------------- @@ -44,8 +44,9 @@ namespace Sarand.Net.Mobile.Maui.Views //------------------------------------------------------------------- // Control activity. - public virtual void SetControlActivity() + public virtual void SetControlActivity(bool enabled) { + if (IsEnabled != enabled) IsEnabled = enabled; SetControlsReadOnly(); } public virtual void SetControlsReadOnly() @@ -60,7 +61,7 @@ namespace Sarand.Net.Mobile.Maui.Views base.OnPropertyChanged(propertyName); if (propertyName.EqualsCI(IsEnabledProperty.PropertyName)) { - SetControlActivity(); + SetControlActivity(IsEnabled); } } diff --git a/SacsMobileMaui/Maui/Views/SamFrame.cs b/SacsMobileMaui/Maui/Views/SamFrame.cs index 49b9c59..b742a6d 100644 --- a/SacsMobileMaui/Maui/Views/SamFrame.cs +++ b/SacsMobileMaui/Maui/Views/SamFrame.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Text; @@ -32,8 +32,9 @@ namespace Sarand.Net.Mobile.Maui.Views //------------------------------------------------------------------- // Control activity. - public virtual void SetControlActivity() + public virtual void SetControlActivity(bool enabled) { + if (IsEnabled != enabled) IsEnabled = enabled; SetControlsReadOnly(); } public virtual void SetControlsReadOnly() diff --git a/SacsMobileMaui/Maui/Views/SamView.cs b/SacsMobileMaui/Maui/Views/SamView.cs index 11ef025..7940fd7 100644 --- a/SacsMobileMaui/Maui/Views/SamView.cs +++ b/SacsMobileMaui/Maui/Views/SamView.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Text; @@ -32,8 +32,9 @@ namespace Sarand.Net.Mobile.Maui.Views //------------------------------------------------------------------- // Control activity. - public virtual void SetControlActivity() + public virtual void SetControlActivity(bool enabled) { + if (IsEnabled != enabled) IsEnabled = enabled; SetControlsReadOnly(); } public virtual void SetControlsReadOnly() diff --git a/SacsMobileMaui/Platforms/Android/PlatformClass1.cs b/SacsMobileMaui/Platforms/Android/PlatformClass1.cs index 217dd04..59a1b46 100644 --- a/SacsMobileMaui/Platforms/Android/PlatformClass1.cs +++ b/SacsMobileMaui/Platforms/Android/PlatformClass1.cs @@ -1,4 +1,4 @@ -namespace SacsMobileMaui +namespace SacsMobileMaui { // All the code in this file is only included on Android. public class PlatformClass1 diff --git a/SacsMobileMaui/Platforms/MacCatalyst/PlatformClass1.cs b/SacsMobileMaui/Platforms/MacCatalyst/PlatformClass1.cs index caa7f97..b8200f0 100644 --- a/SacsMobileMaui/Platforms/MacCatalyst/PlatformClass1.cs +++ b/SacsMobileMaui/Platforms/MacCatalyst/PlatformClass1.cs @@ -1,4 +1,4 @@ -namespace SacsMobileMaui +namespace SacsMobileMaui { // All the code in this file is only included on Mac Catalyst. public class PlatformClass1 diff --git a/SacsMobileMaui/Platforms/Tizen/PlatformClass1.cs b/SacsMobileMaui/Platforms/Tizen/PlatformClass1.cs index 1ba08e1..0fc8083 100644 --- a/SacsMobileMaui/Platforms/Tizen/PlatformClass1.cs +++ b/SacsMobileMaui/Platforms/Tizen/PlatformClass1.cs @@ -1,4 +1,4 @@ -using System; +using System; namespace SacsMobileMaui { diff --git a/SacsMobileMaui/Platforms/Windows/PlatformClass1.cs b/SacsMobileMaui/Platforms/Windows/PlatformClass1.cs index 48ff4d1..182a6dc 100644 --- a/SacsMobileMaui/Platforms/Windows/PlatformClass1.cs +++ b/SacsMobileMaui/Platforms/Windows/PlatformClass1.cs @@ -1,4 +1,4 @@ -namespace SacsMobileMaui +namespace SacsMobileMaui { // All the code in this file is only included on Windows. public class PlatformClass1 diff --git a/SacsMobileMaui/Platforms/iOS/PlatformClass1.cs b/SacsMobileMaui/Platforms/iOS/PlatformClass1.cs index c3df60d..ef78570 100644 --- a/SacsMobileMaui/Platforms/iOS/PlatformClass1.cs +++ b/SacsMobileMaui/Platforms/iOS/PlatformClass1.cs @@ -1,4 +1,4 @@ -namespace SacsMobileMaui +namespace SacsMobileMaui { // All the code in this file is only included on iOS. public class PlatformClass1 diff --git a/SacsMobileMaui/docs/component-reviews.md b/SacsMobileMaui/docs/component-reviews.md index 42201ab..69b7158 100644 --- a/SacsMobileMaui/docs/component-reviews.md +++ b/SacsMobileMaui/docs/component-reviews.md @@ -7,8 +7,14 @@ table td { } -Last updated: 2026-09-11 +Last updated: 2026-10-06 | Component | Scope | Status | Date reviewed | Next steps | Notes | |---|---|---|---|---|---| -| `SacsMobileMaui` | Project | .NET 10 migration complete; follow-up required | 2026-09-11 | Validate Apple targets on a Mac, then address the compatibility APIs and focused defects recorded in `topics.md`. | The project and both active consumers target .NET 10 with MAUI 10.0.20. Windows runtime smoke tests pass, and Windows and Android Debug builds complete with zero errors against Common’s final .NET 10-only libraries. Compatibility API, UI-thread synchronization, handler-cast, `AllowInvalid`, and resource-stream work remains tracked. | +| `SacsMobileMaui` | Project and project documentation review | .NET 10 migration complete; follow-up required | 2026-09-17 | Validate Apple targets on a Mac, then address the compatibility APIs, handler registration, and focused tests recorded in `topics.md`. | Reviewed the MAUI project README, topic tracker, component register, and shared Common trackers. The project and both active consumers target .NET 10 with MAUI 10.0.20. Windows runtime smoke tests pass, and Windows and Android Debug builds complete with zero errors against Common’s final .NET 10-only libraries. Async/dialog implementation and date-picker behavior are implemented; compatibility APIs, handler registration, and resource-stream tests remain. | +| `Maui/Utils/SamThreadUtil.cs`, `Maui/Utils/SamFormUtil.cs`, `Maui/Pages/SamContentPage.cs`, `SacsMobileMauiTests`, `SacsMobileMauiTestHost` | Async UI-thread dispatch and dialogs | Common behavior implemented and host-tested | 2026-10-06 | Resolve the separately scoped Sandtrap API call-site and Apple runtime-pack build failures before claiming consumer compatibility. | All six dispatcher tests and all three interactive dialog tests pass in the Windows MAUI host; all four non-interactive NUnit tests pass. The host builds with zero warnings/errors. Sandtrap's build check exposed removed-API call sites and Apple-target runtime-pack errors; no consumer files were changed. | +| `Maui/Controls/SamDatePicker.cs`, `SamDatePicker_Tester` | `AllowInvalid` transitions, date display, and reset behavior | Implemented and host-verified | 2026-10-06 | Retain the host test for future date-picker changes. | The `Date` setter now refreshes the formatted entry/reset display after changing state. The explicit host test verifies both mode transitions, valid-date display, invalid-date clearing, reset visibility, and restoration of the native picker date when invalid values are disabled; the user confirmed it passes. | +| `Maui/App/Sam.cs` | Shared MAUI configuration and visual defaults | Retained and clarified | 2026-09-29 | Classify remaining unused settings and decide whether localisation and font defaults should be wired into consumers. | Preserved all public settings, documented the startup/default configuration semantics, and made the remaining item-list visual fields consistent properties. | +| `Maui/Logic/SamEntryTextFormatter.cs`, `SamEntryTextFormatOptions.cs`, `SamPickerItemCollection.cs`, `SamDateValueState.cs`, `SamViewTreeUtil.cs` | Reusable control logic extraction | Implemented and covered where platform-independent | 2026-09-29 | Use these seams when adding further `Sam*` controls; add MAUI-host tests for visual-tree behavior. | `SamEntry`, `SamPicker`, `SamDatePicker`, and `SamContentPage` now delegate reusable formatting, item, date-state, and child-view traversal behavior. `SamEntryTextFormatter` receives an immutable formatting-options snapshot rather than positional settings. Four ordinary NUnit tests pass without a MAUI host. | +| `Maui/Controls/*` and `Maui/Controls/Panels/*` | MAUI control coverage and consistency review | Activity contracts separated for controls and composite views | 2026-10-06 | Add MAUI-host tests, assess `BindableProperty` support, and review the remaining platform-specific handler branches and no-op overrides. | Added the single-method `ISamControl` contract for leaf controls. `SamViewTreeUtil` now applies activity to child `ISamControl` and `ISamView` elements; `ISamView` remains the contract for composite views and layout configuration. Earlier work removed the `SamControlPanel` constructor’s virtual call, simplified redundant panel lifecycle overrides, corrected `SamLabel` proportional font handling, added `ISamNativeControlStyle`, and improved `SamWebView`, `SamTabControl`, `SamDateTimePicker`, and `SamPicker`. | +| `Maui/Handlers/SamFormHandler.cs`, `Maui/Controls/ISamNativeControlStyle.cs` | Native form-control handler mappings | Platform behavior clarified; registration still unverified | 2026-10-06 | Verify or add the startup call to `SamFormHandler.AssignControlHandlers()`; then host-test mapping behavior and visually check transparent backgrounds and borders. Consider bindable properties only if styling must change after handler creation. | All mappings guard with `ISamNativeControlStyle`. Windows background clearing now uses a transparent brush. `NativeNoUnderline` intentionally has no iOS/macOS or Windows implementation because those controls expose a full border rather than the Android-style underline; `NativeNoBorder` removes that border. A Common-project search found the registration method definition but no call site, so mapping activation is not yet established. | diff --git a/SacsMobileMaui/docs/topics.md b/SacsMobileMaui/docs/topics.md index 7ad9146..2ec92b8 100644 --- a/SacsMobileMaui/docs/topics.md +++ b/SacsMobileMaui/docs/topics.md @@ -7,17 +7,15 @@ table td { } -Last updated: 2026-09-11 +Last updated: 2026-10-06 ## Prioritised Topics to Be Actioned | Priority | Topic | Date noted | Next step | Notes | |---|---|---|---|---| | P2 | Replace obsolete MAUI compatibility APIs | 2026-09-10 | Replace compatibility layouts, `Frame`, `MainPage`, synchronous popup calls, and deprecated expansion options; then remove `Microsoft.Maui.Controls.Compatibility`. | The .NET 10 Windows and Android builds succeed, but retain compatibility references and report the existing API-obsolescence warnings. | -| P1 | Make `SamThreadUtil` and form dialogs genuinely asynchronous and deadlock-safe | 2026-09-10 | Replace synchronous UI-thread bridges with `Task`-returning APIs, update the Sandtrap caller, and add focused tests. | The void `InvokeOnMainThreadSync` overload never waits; the generic overload and `DisplayConfirmSync` block on `Task.Result` and can deadlock on the UI thread. `DisplayAlertSync` also passes an async lambda to `Action`, producing `async void` behavior. | -| P2 | Correct MAUI native handler type checks | 2026-09-10 | Use `SamEditor` in editor mappings and `SamPicker` in picker mappings; validate each platform-specific option. | Several editor and every picker option currently cast to the wrong custom control type, so configured native styling is silently skipped. | -| P2 | Fix `SamDatePicker.AllowInvalid` state transition | 2026-09-10 | Change the assignment in the equality guard, verify both transitions, and add tests for display and reset behavior. | Setting `AllowInvalid` to `true` returns before refreshing the control; the guard uses `allowInvalidDate = value`. | -| P3 | Remove unused resource-stream enumeration | 2026-09-10 | Return `null` immediately after a failed named lookup or implement an intentional lookup strategy that disposes rejected streams. | `GetResourceStream` opens every manifest resource, discards each stream, and returns `null`. | +| P2 | Verify native form-handler registration | 2026-10-06 | Find or add the startup call to `SamFormHandler.AssignControlHandlers()`, then cover registration and configured-control guards in host tests. | A search of `SacsMobileMaui` currently finds the method definition but no call site. Handler mappings will have no effect unless this registration method runs during app startup. | +| P3 | Replace unused resource-stream enumeration | 2026-09-10 | Add focused tests for exact, case-insensitive suffix, and missing-resource lookups. | `SamResourceUtil.GetResourceStream` now performs an exact lookup followed by a case-insensitive manifest-name suffix lookup, opening a stream only for the selected resource. | ## .NET 10 Dependencies and Sequence @@ -39,7 +37,11 @@ Last updated: 2026-09-11 | Topic | Date noted | Date started | Date completed | Outcome | |---|---|---|---|---| +| Fix `SamDatePicker.AllowInvalid` state transition | 2026-09-10 | 2026-10-06 | 2026-10-06 | The equality guard compares rather than assigns; changing `Date` refreshes the formatted entry/reset display. The explicit MAUI-host test covers both `AllowInvalid` transitions, valid-date display, invalid-date clearing, reset visibility, and fallback to the native picker date. The host build succeeds and the user confirmed the test passes. | +| Correct MAUI native handler type checks and complete supported styling branches | 2026-09-10 | 2026-10-06 | 2026-10-06 | Each mapping checks `ISamNativeControlStyle`. Windows background clearing now assigns a transparent WinUI brush. `NativeNoUnderline` is intentionally a no-op on iOS/macOS and Windows because those controls have a full border rather than a separate Android-style underline; comments direct callers to `NativeNoBorder`. `SamEditor` and `SamPicker` derive from different MAUI controls, so no common concrete parent is appropriate. | +| Make `SamThreadUtil` and form dialogs genuinely asynchronous and deadlock-safe | 2026-09-10 | 2026-09-17 | 2026-10-06 | Added a Windows MAUI host for the explicit NUnit integration tests. All six dispatcher tests and all three interactive dialog tests pass; the four non-interactive NUnit tests also pass. The Sandtrap build check was performed but remains red because the consumer still calls the removed `InvokeOnMainThread` API and its iOS/Mac Catalyst targets lack ASP.NET Core runtime packs. Per scope, no Sandtrap files were changed; resolve that consumer fallout separately. | | Establish authoritative source control for Sandtrap and Sandalwood | 2026-09-10 | 2026-09-10 | 2026-09-10 | Separate repositories were initialized, given baseline guidance and tracking files, committed, and pushed to their remotes. | | Repair the MAUI workload and validate local targets | 2026-09-10 | 2026-09-11 | 2026-09-11 | SDK 10.0.401 reports coherent .NET 10 Android, iOS, Mac Catalyst, and MAUI Windows workloads. The upgraded shared project restores and builds successfully for Windows and Android; Apple builds still require compatible Mac/Xcode validation. | | Upgrade `SacsMobileMaui` to .NET MAUI 10 | 2026-09-10 | 2026-09-11 | 2026-09-11 | Retargeted Android, iOS, Mac Catalyst, and Windows to .NET 10; aligned MAUI packages to 10.0.20; and adapted date/time controls to MAUI 10 nullable picker values. Windows and Android Debug builds complete with zero errors. | | Upgrade Sandtrap and Sandalwood to .NET MAUI 10 | 2026-09-10 | 2026-09-11 | 2026-09-11 | Both applications now target .NET 10 with MAUI 10.0.20. Windows runtime smoke tests pass, and Windows and Android Debug builds complete with zero errors against the final Common .NET 10-only libraries. Apple validation remains separately tracked in each application repository. | +| Extract reusable logic from core MAUI controls | 2026-09-29 | 2026-09-29 | 2026-09-29 | Continue adding `Sam*` components using the extracted logic seams, keeping platform-specific behavior in the controls. | Added `SamEntryTextFormatter`, `SamEntryTextFormatOptions`, `SamPickerItemCollection`, `SamDateValueState`, and `SamViewTreeUtil`. `SamEntry` now passes an immutable formatting-options snapshot to its formatter. The Common MAUI project builds successfully; four pure-logic tests pass in `SacsMobileMauiTests`. The Windows MAUI host now runs the six dispatcher and three dialog integration tests; control-specific host tests are added as needed. | diff --git a/SacsMobileMauiTestHost/App.xaml b/SacsMobileMauiTestHost/App.xaml new file mode 100644 index 0000000..4d2accb --- /dev/null +++ b/SacsMobileMauiTestHost/App.xaml @@ -0,0 +1,14 @@ + + + + + + + + + + + diff --git a/SacsMobileMauiTestHost/App.xaml.cs b/SacsMobileMauiTestHost/App.xaml.cs new file mode 100644 index 0000000..99542a1 --- /dev/null +++ b/SacsMobileMauiTestHost/App.xaml.cs @@ -0,0 +1,16 @@ +using Microsoft.Extensions.DependencyInjection; + +namespace SacsMobileMauiTestHost; + +public partial class App : Application +{ + public App() + { + InitializeComponent(); + } + + protected override Window CreateWindow(IActivationState? activationState) + { + return new Window(new AppShell()); + } +} \ No newline at end of file diff --git a/SacsMobileMauiTestHost/AppShell.xaml b/SacsMobileMauiTestHost/AppShell.xaml new file mode 100644 index 0000000..a14294b --- /dev/null +++ b/SacsMobileMauiTestHost/AppShell.xaml @@ -0,0 +1,14 @@ + + + + + + diff --git a/SacsMobileMauiTestHost/AppShell.xaml.cs b/SacsMobileMauiTestHost/AppShell.xaml.cs new file mode 100644 index 0000000..7cf1464 --- /dev/null +++ b/SacsMobileMauiTestHost/AppShell.xaml.cs @@ -0,0 +1,9 @@ +namespace SacsMobileMauiTestHost; + +public partial class AppShell : Shell +{ + public AppShell() + { + InitializeComponent(); + } +} diff --git a/SacsMobileMauiTestHost/MainPage.xaml b/SacsMobileMauiTestHost/MainPage.xaml new file mode 100644 index 0000000..1e8a1ad --- /dev/null +++ b/SacsMobileMauiTestHost/MainPage.xaml @@ -0,0 +1,31 @@ + + + + +