diff --git a/SaCommonVS.sln b/SaCommonVS.sln
index b611761..af93996 100644
--- a/SaCommonVS.sln
+++ b/SaCommonVS.sln
@@ -7,8 +7,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SacsStandardBase", "SacsSta
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SacsStandardDbas", "SacsStandardDbas\SacsStandardDbas.csproj", "{14768EB5-6FA5-4F62-9347-44A667A760C1}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SacsStandardMob", "SacsStandardMob\SacsStandardMob.csproj", "{E507AB63-28E3-4FF4-9EA8-2E87C42FFC20}"
-EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SacsCoreWeb", "SacsCoreWeb\SacsCoreWeb.csproj", "{AE8FCC39-D13D-4E32-995E-C69352F9F95E}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SacsCoreUnitTests", "SacsCoreUnitTests\SacsCoreUnitTests.csproj", "{70F1302F-E8F2-49CB-A09D-889DA59681DD}"
@@ -22,6 +20,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SacsStandardAccounts", "Sac
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SacsStandardProc", "SacsStandardProc\SacsStandardProc.csproj", "{3988EE9F-FFCD-481A-AF5E-AA7E5DB5A415}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SacsMobileXamarin", "SacsMobileXamarin\SacsMobileXamarin.csproj", "{8DB58994-2E78-4F87-8630-63A3C7F5D8BD}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SacsMobileMaui", "SacsMobileMaui\SacsMobileMaui.csproj", "{F167BCF8-8B99-4103-A710-D4B8FD5518A7}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -36,10 +38,6 @@ Global
{14768EB5-6FA5-4F62-9347-44A667A760C1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{14768EB5-6FA5-4F62-9347-44A667A760C1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{14768EB5-6FA5-4F62-9347-44A667A760C1}.Release|Any CPU.Build.0 = Release|Any CPU
- {E507AB63-28E3-4FF4-9EA8-2E87C42FFC20}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {E507AB63-28E3-4FF4-9EA8-2E87C42FFC20}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {E507AB63-28E3-4FF4-9EA8-2E87C42FFC20}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {E507AB63-28E3-4FF4-9EA8-2E87C42FFC20}.Release|Any CPU.Build.0 = Release|Any CPU
{AE8FCC39-D13D-4E32-995E-C69352F9F95E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{AE8FCC39-D13D-4E32-995E-C69352F9F95E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AE8FCC39-D13D-4E32-995E-C69352F9F95E}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -56,6 +54,14 @@ Global
{3988EE9F-FFCD-481A-AF5E-AA7E5DB5A415}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3988EE9F-FFCD-481A-AF5E-AA7E5DB5A415}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3988EE9F-FFCD-481A-AF5E-AA7E5DB5A415}.Release|Any CPU.Build.0 = Release|Any CPU
+ {8DB58994-2E78-4F87-8630-63A3C7F5D8BD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {8DB58994-2E78-4F87-8630-63A3C7F5D8BD}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {8DB58994-2E78-4F87-8630-63A3C7F5D8BD}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {8DB58994-2E78-4F87-8630-63A3C7F5D8BD}.Release|Any CPU.Build.0 = Release|Any CPU
+ {F167BCF8-8B99-4103-A710-D4B8FD5518A7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {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
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/SacsCoreUnitTests/SacsCoreUnitTests.csproj b/SacsCoreUnitTests/SacsCoreUnitTests.csproj
index 68a8b4a..8d897ad 100644
--- a/SacsCoreUnitTests/SacsCoreUnitTests.csproj
+++ b/SacsCoreUnitTests/SacsCoreUnitTests.csproj
@@ -19,7 +19,6 @@
-
diff --git a/SacsCoreUnitTests/Standard/Base/Generic/Regex_Tester.cs b/SacsCoreUnitTests/Standard/Base/Generic/Regex_Tester.cs
index 38e5cff..98e565b 100644
--- a/SacsCoreUnitTests/Standard/Base/Generic/Regex_Tester.cs
+++ b/SacsCoreUnitTests/Standard/Base/Generic/Regex_Tester.cs
@@ -8,12 +8,51 @@ namespace Sacs.UnitTests.Net.Standard.Base.Generic
{
//-------------------------------------------------------------------
// Data members.
+
//-------------------------------------------------------------------
[TestMethod]
- public void Regex_FolderPaths ()
+ public void Regex_FolderPaths()
{
Assert.AreEqual(true, Regex.IsMatch("abcabcabc\\.metadata\\.plugins\\abcabcabc", ".*?\\\\\\.metadata\\\\\\.plugins\\\\.*?"));
+
+ // Path ends in.
+ Assert.AreEqual(true, Regex.IsMatch("D:\\Home\\Data\\Logs\\App", "[\\\\/]app$", RegexOptions.IgnoreCase));
+ Assert.AreEqual(true, Regex.IsMatch("/home/data/logs/app", "[\\\\/]app$", RegexOptions.IgnoreCase));
+
+ // Path doesn't end in.
+ Assert.AreEqual(false, Regex.IsMatch("D:\\Home\\Data\\Logs", "[\\\\/]app$", RegexOptions.IgnoreCase));
+ Assert.AreEqual(false, Regex.IsMatch("/home/data/logs", "[\\\\/]app$", RegexOptions.IgnoreCase));
}
+
+ //-------------------------------------------------------------------
+ [TestMethod]
+ public void Regex_IsMatch()
+ {
+ string input = "abcdefghi";
+
+ Assert.AreEqual(true, Regex.IsMatch(input, "a"));
+ Assert.AreEqual(true, Regex.IsMatch(input, "^a"));
+ Assert.AreEqual(true, Regex.IsMatch(input, "d"));
+ Assert.AreEqual(true, Regex.IsMatch(input, "i$"));
+
+ // Combination characters.
+ Assert.AreEqual(true, Regex.IsMatch(input, "[adg]"));
+ Assert.AreEqual(true, Regex.IsMatch(input, "[cba]"));
+
+ // Blank matches input.
+ Assert.AreEqual(true, Regex.IsMatch(input, ""));
+
+ // Characters at incorrect position.
+ Assert.AreEqual(false, Regex.IsMatch(input, "a$"));
+ Assert.AreEqual(false, Regex.IsMatch(input, "^d"));
+ Assert.AreEqual(false, Regex.IsMatch(input, "d$"));
+ Assert.AreEqual(false, Regex.IsMatch(input, "^i"));
+
+ // Incorrect characters.
+ Assert.AreEqual(false, Regex.IsMatch(input, "j"));
+ Assert.AreEqual(false, Regex.IsMatch(input, " "));
+ }
+
//-------------------------------------------------------------------
}
}
diff --git a/SacsCoreWeb/Web/Components/SaController.cs b/SacsCoreWeb/Web/Components/SaController.cs
index f4bf7c8..a31853e 100644
--- a/SacsCoreWeb/Web/Components/SaController.cs
+++ b/SacsCoreWeb/Web/Components/SaController.cs
@@ -41,7 +41,6 @@ namespace Sacs.Web.Components
//-------------------------------------------------------------------
// Properties.
- private static String ClassName { get; } = nameof(SaController);
public DateTime CreatedAt { get; } = DateTime.Now;
public bool DebugMode { get; } = SacsSystem.DebugMode;
///
@@ -125,7 +124,6 @@ namespace Sacs.Web.Components
//-------------------------------------------------------------------
public static String EncryptParameters(NameValueCollection parameters, String base64IV, String base64Key)
{
- String fn = ClassName + ".EncryptParameters";
try
{
// Copy the list of parameters and sort the keys.
@@ -140,22 +138,21 @@ namespace Sacs.Web.Components
// Encrypt data.
String encrypted = SacsEncryptionHelper.Encrypt(queryString, base64IV, base64Key);
- //if (SacsSystem.DebugMode) SaLogger.Debug(fn + "\n\tData: " + queryString + " \n\tEncrypted: " + encrypted);
+ //if (SacsSystem.DebugMode) SaLogger.Debug(SysUtil.Fn + "\n\tData: " + queryString + " \n\tEncrypted: " + encrypted);
return encrypted;
}
- catch (Exception exc) { SaLogger.Warn(fn + " " + ExceptionUtil.GetErrorStateMessage(exc)); }
+ catch (Exception exc) { SaLogger.Warn(SysUtil.Fn + " " + ExceptionUtil.GetErrorStateMessage(exc)); }
return "";
}
public static NameValueCollection DecryptParameters(String encrypted, String base64IV, String base64Key)
{
- String fn = ClassName + ".DecryptParameters";
try
{
String queryString = SacsEncryptionHelper.Decrypt(encrypted, base64IV, base64Key);
- //if (SacsSystem.DebugMode) SaLogger.Debug(fn + " \n\tEncrypted: " + encrypted + "\n\tData: " + queryString);
+ //if (SacsSystem.DebugMode) SaLogger.Debug(SysUtil.Fn + " \n\tEncrypted: " + encrypted + "\n\tData: " + queryString);
return HttpUtility.ParseQueryString(queryString);
}
- catch (Exception exc) { SaLogger.Warn(fn + " " + ExceptionUtil.GetErrorStateMessage(exc)); }
+ catch (Exception exc) { SaLogger.Warn(SysUtil.Fn + " " + ExceptionUtil.GetErrorStateMessage(exc)); }
return new NameValueCollection();
}
@@ -236,7 +233,7 @@ namespace Sacs.Web.Components
public MessageData Message { get; private set; }
public AjaxResponse_Base SetMessage(String title, String text)
{
- if (!StringUtil.IsEmpty(text))
+ if (!StringUtil.IsBlank(text))
Message = new MessageData() { Title = title, Text = text };
return this;
}
@@ -368,7 +365,7 @@ namespace Sacs.Web.Components
{
foreach (FormControlValue controlValue in controlValues.Values)
{
- if (!StringUtil.IsEmpty(controlValue.Error)) return true;
+ if (!StringUtil.IsBlank(controlValue.Error)) return true;
}
return false;
}
diff --git a/SacsMobileMaui/Maui/App/Sam.cs b/SacsMobileMaui/Maui/App/Sam.cs
new file mode 100644
index 0000000..938b6ea
--- /dev/null
+++ b/SacsMobileMaui/Maui/App/Sam.cs
@@ -0,0 +1,208 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using System.Runtime.InteropServices;
+using System.Text;
+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.
+ public enum Platform
+ {
+ Unknown = 0,
+ Android,
+ IOS,
+ MacCatalyst,
+ Windows
+ }
+ // Utilities.
+ public static Random Random { get; set; } = new Random();
+
+ //-------------------------------------------------------------------
+ // Localisation.
+ public static class Locale
+ {
+ public static CultureInfo CultureInfo { get; set; } = CultureInfo.CurrentCulture;
+ public static TextInfo TextInfo { get; set; } = CultureInfo.TextInfo;
+ }
+
+ //-------------------------------------------------------------------
+ // Visuals.
+ public enum DisplayPosition { None, Left, Middle, Right }
+ public static class Visuals
+ {
+ // Application styling.
+ public static class Application
+ {
+ // Application bar.
+ public static class Bar
+ {
+ public static double Height { get; set; } = 28;
+ public static Color BackgroundColor { get; set; } = Color.FromArgb("#558395");
+ public static Color TextColor { get; set; } = Color.FromArgb("#f8f8f8");
+ public static double TextSizeRatio { get; set; } = 1.5;
+ public static Color BorderColor { get; set; } = Colors.DarkSlateBlue;
+ public static double BorderBottom { get; set; } = 1.0;
+ }
+ // Menu.
+ public static class Menu
+ {
+ public static Color BorderColor { get; set; } = Colors.DarkSlateBlue;
+ public static double Border { get; set; } = 1.0;
+ public static Color BackgroundColor { get; set; } = Color.FromArgb("#F0F0F0");
+ public static Color HeaderBackgroundColor { get; set; } = Color.FromArgb("#E0E0E0");
+ }
+ }
+ // 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.
+ public static class Header
+ {
+ // Text.
+ public static String TextLeft { get; set; } = "";
+ public static String TextRight { get; set; } = "";
+ // Toolbar.
+ public static DisplayPosition ToolbarDisplay { get; set; } = Sam.DisplayPosition.None;
+ public static bool Display_ToolbarItem_Back { get; set; } = false;
+ // 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.
+ public static class Caption
+ {
+ public static Color BackgroundColor { get; set; } = Color.FromArgb("#558395");
+ public static double PaddingHorizontal { get; set; } = 4.0;
+ public static double PaddingVertical { get; set; } = 8.0;
+ public static Color TextColor { get; set; } = Color.FromArgb("#f8f8f8");
+ public static double TextSizeRatio { get; set; } = 1.25;
+ }
+ // Navigation.
+ public static class Navigation
+ {
+ public static double BorderTopWidth { get; set; } = 0.75;
+ public static double PaddingVertical { get; set; } = 4.0;
+ public static Color BorderColor { get; set; } = Color.FromArgb("#808080");
+ public static Color BackgroundColor { get; set; } = Color.FromArgb("#e9f6fb");
+ }
+ // Body.
+ public static class Body
+ {
+ public static double PaddingVerticalMin { get; set; } = 4.0;
+ public static double PaddingHorizontalMin { get; set; } = 4.0;
+ }
+ // Footer.
+ public static class Footer
+ {
+ public static String TextLeft { get; set; } = "";
+ public static String TextMiddle { get; set; } = "";
+ public static String TextRight { get; set; } = "";
+ public static String DateTimeFormat { get; set; } = "ddd d-MMM-yyyy HH:mm:ss";
+ public static DisplayPosition DateTimeDisplay { get; set; } = DisplayPosition.None;
+ public static Color BackgroundColor { get; set; } = Colors.Honeydew;
+ public static Color BorderColor { get; set; } = Colors.LightSlateGray;
+ public static double BorderStrokeWidthTop { get; set; } = 1.0;
+ public static double BorderStrokeWidthBottom { get; set; } = 2.0;
+ public static Thickness Padding { get; set; } = new Thickness(8, 4);
+ }
+ }
+ // Fonts.
+ public static class Fonts
+ {
+ // 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";
+ return "10pt";
+ }
+ }
+ public static double FontSizeRatio
+ {
+ get
+ {
+ if (SamApplication.MinDeviceDimension > 2000) return 1.75;
+ if (SamApplication.MinDeviceDimension > 1600) return 1.50;
+ if (SamApplication.MinDeviceDimension > 1200) return 1.25;
+ return 1.0;
+ }
+ }
+ }
+ // Data display visuals.
+ public static class Data
+ {
+ // 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");
+ }
+ // Associated items panel.
+ public static class AssociatedItems
+ {
+ public static Color CaptionBackgroundColor { get; set; } = Color.FromArgb("#becee4");
+ public static Color CaptionTextColor { get; set; } = Color.FromArgb("#f8f8f8");
+ public static double CaptionTextSizeRatio { get; set; } = 1.2;
+ public static Color BackgroundColor { get; set; } = Color.FromArgb("#dbe4f0");
+ }
+ }
+ // 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.
+ public static class Label
+ {
+ public static Thickness Margin { get; set; } = new Thickness(8.0, 4.0, 8.0, 2.0);
+ }
+ // 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.
+ public static bool NativeNoBorder { get; set; } = false;
+ public static bool NativeNoUnderline { get; set; } = false;
+ public static bool NativeBackgroundClear { get; set; } = false;
+ }
+ // Tab controls.
+ public static class TabControl
+ {
+ public static Color HeaderBackgroundColor { get; set; } = Colors.Transparent;
+ public static Color CaptionBackgroundColor { get; set; } = Color.FromArgb("#E0E0E0");
+ public static Color BodyBackgroundColor { get; set; } = Color.FromArgb("#F8F8F8");
+ public static Thickness BodyPadding { get; set; } = new Thickness(4.0);
+ }
+ }
+ }
+
+ //-------------------------------------------------------------------
+ }
+ //=======================================================================
+}
diff --git a/SacsMobileMaui/Maui/App/SamApplication.cs b/SacsMobileMaui/Maui/App/SamApplication.cs
new file mode 100644
index 0000000..947bc94
--- /dev/null
+++ b/SacsMobileMaui/Maui/App/SamApplication.cs
@@ -0,0 +1,60 @@
+using Sarand.Net.Standard.Base.App;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Sarand.Net.Mobile.Maui.App
+{
+ //=======================================================================
+ public class SamApplication : Application
+ {
+ //-------------------------------------------------------------------
+ // Definitions.
+
+ //-------------------------------------------------------------------
+ // Data members.
+ private Sam.Platform platform;
+
+ //-------------------------------------------------------------------
+ protected SamApplication()
+ {
+ // Determine platform.
+#if ANDROID
+ platform = Sam.Platform.Android;
+#elif IOS
+ platform = Sam.Platform.IOS;
+#elif MACCATALYST
+ platform = Sam.Platform.MacCatalyst;
+#elif WINDOWS
+ platform = Sam.Platform.Windows;
+#endif
+
+ // Log environment.
+ 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("DeviceDensity: " + DeviceDisplay.MainDisplayInfo.Density);
+ }
+
+ //-------------------------------------------------------------------
+ // 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);
+
+ //-------------------------------------------------------------------
+ }
+ //=======================================================================
+}
diff --git a/SacsMobileMaui/Maui/Base/SamComponents.cs b/SacsMobileMaui/Maui/Base/SamComponents.cs
new file mode 100644
index 0000000..868951a
--- /dev/null
+++ b/SacsMobileMaui/Maui/Base/SamComponents.cs
@@ -0,0 +1,54 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Sarand.Net.Mobile.Maui.Base
+{
+ //=======================================================================
+ public class SamComponents
+ {
+ //-------------------------------------------------------------------
+ // Definitions.
+
+ //-------------------------------------------------------------------
+ // Data members.
+ private bool readOnly = false;
+
+ //-------------------------------------------------------------------
+ // Construction.
+ public SamComponents()
+ {
+ }
+
+ //-------------------------------------------------------------------
+ // Properties.
+ public bool ReadOnly
+ {
+ get { return readOnly; }
+ set { if (readOnly == value) return; readOnly = value; SetControlsReadOnly(); }
+ }
+ public bool Changed { get; protected set; } = false;
+
+ //-------------------------------------------------------------------
+ // C o n f i g u r e C o m p o n e n t s
+ //-------------------------------------------------------------------
+ protected virtual void ConfigureComponents()
+ {
+ }
+
+ //-------------------------------------------------------------------
+ // C o n t r o l A c t i v i t y
+ //-------------------------------------------------------------------
+ protected virtual void SetControlActivity()
+ {
+ }
+ protected virtual void SetControlsReadOnly()
+ {
+ }
+
+ //-------------------------------------------------------------------
+ }
+ //=======================================================================
+}
diff --git a/SacsMobileMaui/Maui/Consoles/SamWebServiceConsole.cs b/SacsMobileMaui/Maui/Consoles/SamWebServiceConsole.cs
new file mode 100644
index 0000000..eee48d3
--- /dev/null
+++ b/SacsMobileMaui/Maui/Consoles/SamWebServiceConsole.cs
@@ -0,0 +1,30 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Sarand.Net.Mobile.Maui.Consoles
+{
+ //======================================================================
+ public class SamWebServiceConsole
+ {
+ //------------------------------------------------------------------
+ // Definitions.
+
+ //------------------------------------------------------------------
+ // Data members.
+
+ //------------------------------------------------------------------
+ // Construction.
+ protected SamWebServiceConsole()
+ {
+ }
+
+ //------------------------------------------------------------------
+ // Properties.
+
+ //------------------------------------------------------------------
+ }
+ //======================================================================
+}
diff --git a/SacsMobileMaui/Maui/Controls/Panels/SamCheckBoxPanel.cs b/SacsMobileMaui/Maui/Controls/Panels/SamCheckBoxPanel.cs
new file mode 100644
index 0000000..48210cc
--- /dev/null
+++ b/SacsMobileMaui/Maui/Controls/Panels/SamCheckBoxPanel.cs
@@ -0,0 +1,147 @@
+using Sarand.Net.Mobile.Maui.Controls.Views;
+using Sarand.Net.Standard.Base.Utils;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.CompilerServices;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Sarand.Net.Mobile.Maui.Controls.Panels
+{
+ //=======================================================================
+ public class SamCheckBoxPanel : SamControlPanel
+ {
+ //-------------------------------------------------------------------
+ // Definitions.
+
+ //-------------------------------------------------------------------
+ // Data members.
+ private readonly SamCheckBox checkbox = new SamCheckBox() { };
+ private readonly TapGestureRecognizer gestureSuffixTap = new TapGestureRecognizer() { };
+
+ //-------------------------------------------------------------------
+ // Construction.
+ public SamCheckBoxPanel()
+ {
+ // View configuration.
+ HorizontalOptions = LayoutOptions.Start;
+
+ // Control configuration.
+ BorderThickness = 0;
+ BorderColor = Colors.Transparent;
+ BackgroundColor = Colors.Transparent;
+
+ // Checkbox events.
+ checkbox.Focused += Entry_Focused;
+ checkbox.Unfocused += Entry_Unfocused;
+ checkbox.CheckedChanged += Checkbox_CheckedChanged;
+ gestureSuffixTap.Tapped += GestureSuffixTap_Tapped;
+ LabelSuffix.GestureRecognizers.Add(gestureSuffixTap);
+
+ ConfigureComponents();
+ SetControlActivity();
+ }
+
+ //-------------------------------------------------------------------
+ // Properties.
+ protected override View ControlView { get { return checkbox; } }
+ public bool IsChecked
+ {
+ get { return checkbox.IsChecked; }
+ set { if (checkbox.IsChecked == value) return; checkbox.IsChecked = value; }
+ }
+ public new LayoutOptions HorizontalOptions
+ {
+ get { return checkbox.HorizontalOptions; }
+ 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();
+ }
+
+ //-------------------------------------------------------------------
+ // 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)
+ {
+ ReadOnlyText = checkbox.IsChecked ? "X" : "";
+ if (ReadOnly) return;
+ if (CheckedChanged != null) CheckedChanged.Invoke(sender, e);
+ }
+
+ private void GestureSuffixTap_Tapped(object? sender, TappedEventArgs e)
+ {
+ 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);
+ }
+
+ //-------------------------------------------------------------------
+ }
+ //=======================================================================
+}
diff --git a/SacsMobileMaui/Maui/Controls/Panels/SamControlPanel.cs b/SacsMobileMaui/Maui/Controls/Panels/SamControlPanel.cs
new file mode 100644
index 0000000..7d780a0
--- /dev/null
+++ b/SacsMobileMaui/Maui/Controls/Panels/SamControlPanel.cs
@@ -0,0 +1,162 @@
+using Sarand.Net.Mobile.Maui.App;
+using Sarand.Net.Mobile.Maui.Views;
+using Sarand.Net.Standard.Base.Utils;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.CompilerServices;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Sarand.Net.Mobile.Maui.Controls.Views
+{
+ //=======================================================================
+ public abstract class SamControlPanel : SamContentView
+ {
+ //-------------------------------------------------------------------
+ // Definitions.
+
+ //-------------------------------------------------------------------
+ // Data members.
+ // Components.
+ private HorizontalStackLayout controlLayout = new HorizontalStackLayout();
+ private SamContentView controlBorder = new SamContentView();
+ private SamContentView controlPanel = new SamContentView() { Padding = Sam.Visuals.Form.Controls.Padding };
+ // Styling.
+ private Thickness controlBorderThickness = new Thickness(Sam.Visuals.Form.Controls.BorderWidth);
+ private Color controlBorderColor = Sam.Visuals.Form.Controls.BorderColor;
+ private Color controlBackgroundColor = Sam.Visuals.Form.Controls.BackgroundColor;
+
+ //-------------------------------------------------------------------
+ // Construction.
+ protected SamControlPanel()
+ {
+ // Control layout: [Border[Panel]] [Suffix].
+ controlBorder.Content = controlPanel;
+ controlLayout.Add(controlBorder);
+ controlLayout.Add(LabelSuffix);
+
+ // Main layout.
+ Content = controlLayout;
+
+ SetControlActivity();
+ }
+
+ //-------------------------------------------------------------------
+ // Properties.
+
+ // Label.
+ public SamLabel Label { get; } = new SamLabel() { Margin = Sam.Visuals.Form.Label.Margin };
+ public String LabelText { get { return Label.Text; } set { if (Label.Text.Equals(value)) return; Label.Text = value; } }
+
+ // Control.
+ /// The control itself. NB May be composite
+ protected abstract View ControlView { get; }
+ /// Control used for read-only display.
+ protected SamLabel ReadOnlyLabel { get; } =
+ new SamLabel() { BackgroundColor = Colors.Transparent };
+
+ // Suffix.
+ protected SamLabel LabelSuffix { get; } = new SamLabel() { Margin = Sam.Visuals.Form.Label.Margin };
+ public String SuffixText { get { return LabelSuffix.Text; } set { if (LabelSuffix.Text.Equals(value)) return; LabelSuffix.Text = value; } }
+ public double SuffixPadding { get { return LabelSuffix.Padding.Left; } set { if (LabelSuffix.Padding.Left == value) return; LabelSuffix.Padding = new(value, 0, 0, 0); } }
+
+ // Styling.
+ public new Color BackgroundColor
+ {
+ get { return controlBackgroundColor; }
+ set { controlBackgroundColor = value; ConfigureComponents(); }
+ }
+ public Thickness BorderThickness
+ {
+ get { return controlBorderThickness; }
+ set { controlBorderThickness = value; ConfigureComponents(); }
+ }
+ public double BorderWidth
+ {
+ set { BorderThickness = new Thickness(value); }
+ }
+ public Color BorderColor
+ {
+ get { return controlBorderColor; }
+ set { controlBorderColor = value; }
+ }
+ public new LayoutOptions HorizontalOptions
+ {
+ get { return base.HorizontalOptions; }
+ set { base.HorizontalOptions = value; ControlView.HorizontalOptions = value; }
+ }
+ protected String ReadOnlyText
+ {
+ get { return ReadOnlyLabel.Text; }
+ 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
+ //-------------------------------------------------------------------
+ /// Control configuration that can be changed dynamically.
+ public override void ConfigureComponents()
+ {
+ // Outer view acts as border.
+ controlBorder.BackgroundColor = controlBorderColor;
+ controlBorder.Padding = controlBorderThickness;
+ controlPanel.BackgroundColor = controlBackgroundColor;
+ }
+
+ //-------------------------------------------------------------------
+ // C o n t r o l A c t i v i t y
+ //-------------------------------------------------------------------
+ public override void SetControlActivity()
+ {
+ base.SetControlActivity();
+ }
+ public override void SetControlsReadOnly()
+ {
+ base.SetControlsReadOnly();
+ if (ReadOnly) controlPanel.Content = ReadOnlyLabel;
+ else controlPanel.Content = ControlView;
+ }
+
+ //-------------------------------------------------------------------
+ // C o n t r o l E v e n t s
+ //-------------------------------------------------------------------
+ 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
+ //-------------------------------------------------------------------
+ protected override void OnPropertyChanged([CallerMemberName] string propertyName = "")
+ {
+ base.OnPropertyChanged(propertyName);
+ //Logger.Debug(GetType().Name + ".OnPropertyChanged Name=" + propertyName);
+
+ if (propertyName.EqualsCI(PaddingProperty.PropertyName))
+ {
+ if (!controlPanel.Padding.Equals(Padding))
+ {
+ // Apply the padding to the embedded view and clear this view's padding.
+ controlPanel.Padding = Padding;
+ Padding = new Thickness();
+ }
+ }
+ else if (propertyName.EqualsCI(BackgroundColorProperty.PropertyName))
+ {
+ controlPanel.BackgroundColor = BackgroundColor;
+ }
+ }
+
+ //-------------------------------------------------------------------
+ }
+ //=======================================================================
+}
diff --git a/SacsMobileMaui/Maui/Controls/Panels/SamEntryPanel.cs b/SacsMobileMaui/Maui/Controls/Panels/SamEntryPanel.cs
new file mode 100644
index 0000000..6a6ee41
--- /dev/null
+++ b/SacsMobileMaui/Maui/Controls/Panels/SamEntryPanel.cs
@@ -0,0 +1,179 @@
+using Sarand.Net.Standard.Base.Utils;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.CompilerServices;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Sarand.Net.Mobile.Maui.Controls.Views
+{
+ //=======================================================================
+ public class SamEntryPanel : SamControlPanel
+ {
+ //-------------------------------------------------------------------
+ // Definitions.
+
+ //-------------------------------------------------------------------
+ // Data members.
+ private readonly SamEntry entry = new SamEntry() { HorizontalOptions = LayoutOptions.Fill };
+
+ //-------------------------------------------------------------------
+ // Construction.
+ public SamEntryPanel()
+ {
+ // View configuration.
+ HorizontalOptions = LayoutOptions.Start;
+
+ // Control configuration.
+ entry.BackgroundColor = Colors.Transparent;
+
+ // Entry events.
+ entry.Focused += Entry_Focused;
+ entry.Unfocused += Entry_Unfocused;
+ entry.TextChanged += Entry_TextChanged;
+ entry.Completed += Entry_Completed;
+
+ SetControlActivity();
+ }
+
+ //-------------------------------------------------------------------
+ // Properties.
+ protected override View ControlView { get { return entry; } }
+ public int MaxLength
+ {
+ get { return entry.MaxLength; }
+ set { entry.MaxLength = value; }
+ }
+ public SamEntry.TextCaseType TextCase
+ {
+ get { return entry.TextCase; }
+ set { entry.TextCase = value; }
+ }
+ public bool NumericOnly
+ {
+ get { return entry.NumericOnly; }
+ set { entry.NumericOnly = value; ReadOnlyLabel.HorizontalTextAlignment = entry.HorizontalTextAlignment; }
+ }
+ public byte Decimals
+ {
+ get { return entry.Decimals; }
+ set { entry.Decimals = value; ReadOnlyLabel.HorizontalTextAlignment = entry.HorizontalTextAlignment; }
+ }
+ public Keyboard Keyboard
+ {
+ get { return entry.Keyboard; }
+ set { entry.Keyboard = value; }
+ }
+ public bool IsPassword
+ {
+ get { return entry.IsPassword; }
+ set { entry.IsPassword = value; }
+ }
+ public String Text
+ {
+ get { return entry.Text; }
+ set { value = StringUtil.EmptyOnNull(value); if (entry.Text != null && entry.Text.Equals(value)) return; entry.Text = value; }
+ }
+ public new double WidthRequest
+ {
+ get { return entry.WidthRequest; }
+ set { entry.WidthRequest = value; ReadOnlyLabel.WidthRequest = value; }
+ }
+ public TextAlignment HorizontalTextAlignment
+ {
+ get { return entry.HorizontalTextAlignment; }
+ 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();
+ }
+
+ //-------------------------------------------------------------------
+ // 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)
+ {
+ ReadOnlyText = entry.Text;
+ if (ReadOnly) return;
+ if (TextChanged != null) TextChanged.Invoke(sender, e);
+ }
+ //-------------------------------------------------------------------
+ // Completed.
+ public event EventHandler? Completed;
+ private void Entry_Completed(object? sender, EventArgs e)
+ {
+ if (ReadOnly) return;
+ 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);
+ }
+
+ //-------------------------------------------------------------------
+ }
+ //=======================================================================
+}
diff --git a/SacsMobileMaui/Maui/Controls/SamButton.cs b/SacsMobileMaui/Maui/Controls/SamButton.cs
new file mode 100644
index 0000000..a6ef2f1
--- /dev/null
+++ b/SacsMobileMaui/Maui/Controls/SamButton.cs
@@ -0,0 +1,36 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Sarand.Net.Mobile.Maui.Controls
+{
+ //=======================================================================
+ public class SamButton : Button
+ {
+ //-------------------------------------------------------------------
+ // Definitions.
+
+ //-------------------------------------------------------------------
+ // Data members.
+
+ //-------------------------------------------------------------------
+ // Construction.
+ public SamButton()
+ {
+ DefaultFontSize = FontSize;
+ }
+
+ //-------------------------------------------------------------------
+ // Properties.
+ public double DefaultFontSize { get; }
+
+ //-------------------------------------------------------------------
+ // C o n t r o l E v e n t s
+ //-------------------------------------------------------------------
+
+ //-------------------------------------------------------------------
+ }
+ //=======================================================================
+}
diff --git a/SacsMobileMaui/Maui/Controls/SamCheckBox.cs b/SacsMobileMaui/Maui/Controls/SamCheckBox.cs
new file mode 100644
index 0000000..04a4b93
--- /dev/null
+++ b/SacsMobileMaui/Maui/Controls/SamCheckBox.cs
@@ -0,0 +1,33 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Sarand.Net.Mobile.Maui.Controls
+{
+ //======================================================================
+ public class SamCheckBox : CheckBox
+ {
+ //-------------------------------------------------------------------
+ // Definitions.
+
+ //-------------------------------------------------------------------
+ // Data members.
+
+ //-------------------------------------------------------------------
+ // Construction.
+ public SamCheckBox()
+ {
+ }
+
+ //-------------------------------------------------------------------
+ // Properties.
+
+ //-------------------------------------------------------------------
+ // Control events.
+
+ //-------------------------------------------------------------------
+ }
+ //======================================================================
+}
diff --git a/SacsMobileMaui/Maui/Controls/SamDatePicker.cs b/SacsMobileMaui/Maui/Controls/SamDatePicker.cs
new file mode 100644
index 0000000..7d5b183
--- /dev/null
+++ b/SacsMobileMaui/Maui/Controls/SamDatePicker.cs
@@ -0,0 +1,221 @@
+using Sarand.Net.Mobile.Maui.Layouts;
+using Sarand.Net.Mobile.Maui.Views;
+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.Controls
+{
+ //=======================================================================
+ public class SamDatePicker : SamContentView
+ {
+ //-------------------------------------------------------------------
+ // Definitions.
+
+ //-------------------------------------------------------------------
+ // Data members.
+ private bool allowInvalidDate = false;
+ private bool invalidDate = true;
+ private DateTime previousDate = DateTime.MinValue;
+ 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.
+ public SamDatePicker()
+ {
+ // Initialise controls.
+ // Date picker. Hidden but used to display calendar.
+ DefaultFontSize = pickerDate.FontSize;
+ pickerDate.Format = "dd-MMM-yyyy";
+ pickerDate.DateSelected += PickerDate_DateSelected;
+ // Date display entry.
+ entryDate.FontSize = pickerDate.FontSize;
+ entryDate.FontFamily = pickerDate.FontFamily;
+ entryDate.FontAttributes = pickerDate.FontAttributes;
+ entryDate.TextColor = pickerDate.TextColor;
+ entryDate.Focused += EntryDate_Focused;
+ // 'Reset' date label - to invalidate the date value.
+ labelResetDate.FontSize = pickerDate.FontSize;
+ labelResetDate.FontFamily = pickerDate.FontFamily;
+
+ // 'Reset' label tap gesture.
+ var tapGestureRecognizer = new TapGestureRecognizer();
+ tapGestureRecognizer.Tapped += LabelValidDate_Tapped;
+ labelResetDate.GestureRecognizers.Add(tapGestureRecognizer);
+
+ // Initialise layout.
+ Content = controlLayout;
+ controlLayout.Children.Add(pickerDate);
+ controlLayout.Children.Add(entryDate);
+ controlLayout.Children.Add(labelResetDate);
+
+ RefreshDateDisplay();
+ }
+
+ //-------------------------------------------------------------------
+ // Properties.
+ // Configuration.
+ public Boolean AllowInvalid
+ {
+ get { return allowInvalidDate; }
+ set
+ {
+ if (allowInvalidDate = value) return;
+ allowInvalidDate = value;
+ if (!allowInvalidDate) invalidDate = false;
+ RefreshDateDisplay();
+ }
+ }
+ public string Format
+ {
+ get { return pickerDate.Format; }
+ set
+ {
+ if (pickerDate.Format.Equals(value)) return;
+ pickerDate.Format = value;
+ }
+ }
+ public DateTime MinimumDate
+ {
+ get { return pickerDate.MinimumDate; }
+ set { pickerDate.MinimumDate = value; }
+ }
+ public DateTime MaximumDate
+ {
+ get { return pickerDate.MaximumDate; }
+ set { pickerDate.MaximumDate = value; }
+ }
+ //-------------------------------------------------------------------
+ // Date value.
+ public DateTime Date
+ {
+ get
+ {
+ if (invalidDate) return DateTime.MinValue;
+ return pickerDate.Date;
+ }
+ set
+ {
+ if (Date.Equals(value)) return;
+ if (!DateUtil.IsValid(value))
+ {
+ invalidDate = true;
+ pickerDate.Date = DateTime.Today;
+ }
+ else
+ {
+ invalidDate = false;
+ pickerDate.Date = value;
+ }
+ previousDate = value;
+ }
+ }
+ public String FormattedDate { get { return !invalidDate ? DateUtil.Format(Date, pickerDate.Format) : ""; } }
+ //-------------------------------------------------------------------
+ // Font.
+ public double DefaultFontSize { get; }
+ public double FontSize
+ {
+ get { return pickerDate.FontSize; }
+ set
+ {
+ if (pickerDate.FontSize == value) return;
+ pickerDate.FontSize = value;
+ entryDate.FontSize = value;
+ }
+ }
+ public String FontFamily
+ {
+ get { return pickerDate.FontFamily; }
+ set
+ {
+ if (pickerDate.FontFamily == value) return;
+ pickerDate.FontFamily = value;
+ entryDate.FontFamily = value;
+ }
+ }
+ public FontAttributes FontAttributes
+ {
+ get { return pickerDate.FontAttributes; }
+ set
+ {
+ if (pickerDate.FontAttributes == value) return;
+ pickerDate.FontAttributes = value;
+ entryDate.FontAttributes = value;
+ }
+ }
+ public Color TextColor
+ {
+ get { return pickerDate.TextColor; }
+ set
+ {
+ if (pickerDate.TextColor == value) return;
+ pickerDate.TextColor = value;
+ entryDate.TextColor = value;
+ }
+ }
+
+ //-------------------------------------------------------------------
+ // Utilities.
+ private void RefreshDateDisplay()
+ {
+ if (allowInvalidDate)
+ {
+ pickerDate.IsVisible = false;
+ entryDate.IsVisible = true;
+ labelResetDate.IsVisible = true;
+ }
+ else
+ {
+ pickerDate.IsVisible = true;
+ entryDate.IsVisible = false;
+ labelResetDate.IsVisible = false;
+ }
+ String dateValue = "";
+ if (!invalidDate)
+ dateValue = DateUtil.Format(Date, pickerDate.Format);
+ entryDate.Text = dateValue;
+ }
+
+ //-------------------------------------------------------------------
+ // C o n t r o l E v e n t s
+ //-------------------------------------------------------------------
+ private void PickerDate_DateSelected(object? sender, DateChangedEventArgs e)
+ {
+ // Date has changed in control.
+ invalidDate = !DateUtil.IsValid(e.NewDate);
+ 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;
+ RefreshDateDisplay();
+ }
+ //-------------------------------------------------------------------
+ private void LabelValidDate_Tapped(object? sender, EventArgs e)
+ {
+ // 'Valid' label tapped.
+ if (invalidDate) return;
+ invalidDate = true;
+ DateTime oldDate = previousDate;
+ RefreshDateDisplay();
+ if (DateSelected != null) DateSelected.Invoke(sender, new DateChangedEventArgs(oldDate, Date));
+ previousDate = Date;
+ }
+
+ //-------------------------------------------------------------------
+ }
+ //=======================================================================
+}
diff --git a/SacsMobileMaui/Maui/Controls/SamDateTimePicker.cs b/SacsMobileMaui/Maui/Controls/SamDateTimePicker.cs
new file mode 100644
index 0000000..52e731c
--- /dev/null
+++ b/SacsMobileMaui/Maui/Controls/SamDateTimePicker.cs
@@ -0,0 +1,160 @@
+using Sarand.Net.Mobile.Maui.Layouts;
+using Sarand.Net.Mobile.Maui.Views;
+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.Controls
+{
+ //=======================================================================
+ public class SamDateTimePicker : SamContentView
+ {
+ //-------------------------------------------------------------------
+ // Definitions.
+ // 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.
+ public SamDateTimePicker()
+ {
+ DefaultFontSize = pickerDate.FontSize;
+
+ // Configure components.
+ Content = layoutControl;
+ layoutControl.Children.Add(pickerDate);
+ layoutControl.Children.Add(pickerTime);
+
+ // Control events.
+ pickerDate.DateSelected += PickerDate_DateSelected;
+ pickerTime.TimeChanged += PickerTime_TimeChanged;
+ }
+
+ //-------------------------------------------------------------------
+ // Properties.
+ public string DateFormat
+ {
+ get { return pickerDate.Format; }
+ set
+ {
+ if (pickerDate.Format.Equals(value)) return;
+ pickerDate.Format = value;
+ }
+ }
+ public DateTime MinimumDate
+ {
+ get { return pickerDate.MinimumDate; }
+ set { pickerDate.MinimumDate = value; }
+ }
+ public DateTime MaximumDate
+ {
+ get { return pickerDate.MaximumDate; }
+ set { pickerDate.MaximumDate = value; }
+ }
+ public string TimeFormat
+ {
+ get { return pickerTime.Format; }
+ set
+ {
+ if (pickerTime.Format.Equals(value)) return;
+ pickerTime.Format = value;
+ }
+ }
+ // Control value.
+ public DateTime DateTime
+ {
+ get { return pickerDate.Date.Date + pickerTime.Time; }
+ set
+ {
+ if (DateTime.CompareTo(value) == 0) return;
+ pickerDate.Date = value.Date;
+ pickerTime.Time = value - value.Date;
+ }
+ }
+ public String DateTimeDelim { get; set; } = " ";
+ public String FormattedDateTime { get { return pickerDate.FormattedDate + DateTimeDelim + pickerTime.FormattedTime; } }
+ // Styling.
+ public double DefaultFontSize { get; }
+ public double FontSize
+ {
+ get { return pickerDate.FontSize; }
+ set { if (pickerDate.FontSize == value) return; pickerDate.FontSize = value; pickerTime.FontSize = value; labelTimePrefix.FontSize = value; }
+ }
+ /// Amount of space between date and time pickers.
+ public double ControlSpacing
+ {
+ get { return layoutControl.Spacing; }
+ set { if (layoutControl.Spacing == value) return; layoutControl.Spacing = value; }
+ }
+ public String TimePickerPrefixText
+ {
+ get { return labelTimePrefix.Text; }
+ 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
+ //-------------------------------------------------------------------
+ public override void ConfigureComponents()
+ {
+ base.ConfigureComponents();
+
+ // Time Picker prefix label text.
+ if (!StringUtil.IsEmpty(TimePickerPrefixText))
+ {
+ // Label has text - ensure label is in the layout.
+ if (!layoutControl.Children.Contains(labelTimePrefix))
+ {
+ // Label will be inserted before the time picker.
+ int insertIdx = layoutControl.Children.IndexOf(pickerTime);
+ layoutControl.Children.Insert(insertIdx, labelTimePrefix);
+ }
+ }
+ else
+ {
+ // Blank label text - remove label from layout.
+ if (layoutControl.Children.Contains(labelTimePrefix))
+ layoutControl.Children.Remove(labelTimePrefix);
+ }
+ }
+
+ //-------------------------------------------------------------------
+ // C o n t r o l A c t i v i t y
+ //-------------------------------------------------------------------
+ public override void SetControlActivity()
+ {
+ base.SetControlActivity();
+ }
+ public override void SetControlsReadOnly()
+ {
+ base.SetControlsReadOnly();
+ }
+
+ //-------------------------------------------------------------------
+ // C o n t r o l E v e n t s
+ //-------------------------------------------------------------------
+ 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;
+
+ //-------------------------------------------------------------------
+ }
+ //=======================================================================
+}
diff --git a/SacsMobileMaui/Maui/Controls/SamEditor.cs b/SacsMobileMaui/Maui/Controls/SamEditor.cs
new file mode 100644
index 0000000..7a83494
--- /dev/null
+++ b/SacsMobileMaui/Maui/Controls/SamEditor.cs
@@ -0,0 +1,84 @@
+using Sarand.Net.Mobile.Maui.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.Controls
+{
+ //=======================================================================
+ public class SamEditor : Editor
+ {
+ //-------------------------------------------------------------------
+ // Definitions.
+
+ //-------------------------------------------------------------------
+ // Data members.
+ private bool trimTextOnCompletion = true;
+ private int NumberOfLinesPrevious = 0;
+
+ //-------------------------------------------------------------------
+ // Construction.
+ public SamEditor()
+ {
+ DefaultFontSize = FontSize;
+ Text = "";
+ TextChanged += SamEditor_TextChanged;
+ Completed += SamEditor_Completed;
+ }
+
+ //-------------------------------------------------------------------
+ // Properties.
+
+ public double DefaultFontSize { get; }
+
+ public bool TrimText
+ {
+ get { return trimTextOnCompletion; }
+ set
+ {
+ if (trimTextOnCompletion == value) return;
+ trimTextOnCompletion = value;
+ }
+ }
+
+ // Native behaviour modified by SamHandler.
+ 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
+ //-------------------------------------------------------------------
+ // Key Press event is not supported directly in Xamarin.
+ // Requires O/S specific implementation.
+ // See: Sarand.LogisticsMgr.Mobile.Droid.Forms.Renderers.SlmEditorRenderer
+ public void OnKeyPress_Enter()
+ {
+ // 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.
+ int NumberOfLines = StringUtil.EmptyOnNull(Text).Split('\n').Length;
+ if (NumberOfLines != NumberOfLinesPrevious)
+ // Resize control based on entered text.
+ InvalidateMeasure();
+ NumberOfLinesPrevious = NumberOfLines;
+ }
+ private void SamEditor_Completed(object? sender, EventArgs e)
+ {
+ if (trimTextOnCompletion)
+ Text = Text.Trim();
+ // Resize control based on entered text.
+ InvalidateMeasure();
+ }
+
+ //-------------------------------------------------------------------
+ }
+ //=======================================================================
+}
diff --git a/SacsMobileMaui/Maui/Controls/SamEntry.cs b/SacsMobileMaui/Maui/Controls/SamEntry.cs
new file mode 100644
index 0000000..2738565
--- /dev/null
+++ b/SacsMobileMaui/Maui/Controls/SamEntry.cs
@@ -0,0 +1,167 @@
+using Sarand.Net.Mobile.Maui.App;
+using Sarand.Net.Standard.Base.App;
+using Sarand.Net.Standard.Base.Utils;
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Sarand.Net.Mobile.Maui.Controls
+{
+ //=======================================================================
+ public class SamEntry : Entry
+ {
+ //-------------------------------------------------------------------
+ // Definitions.
+ public enum TextCaseType
+ {
+ Normal,
+ Upper,
+ Lower,
+ Title
+ }
+
+ //-------------------------------------------------------------------
+ // 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.
+ public SamEntry()
+ {
+ DefaultFontSize = FontSize;
+ TextChanged += SamEntry_TextChanged;
+ Completed += SamEntry_Completed;
+ }
+
+ //-------------------------------------------------------------------
+ // Properties.
+ public bool TrimText
+ {
+ get { return trimTextOnCompletion; }
+ set
+ {
+ if (trimTextOnCompletion == value) return;
+ trimTextOnCompletion = value;
+ }
+ }
+ ///
+ /// Whether to format text as it is entered.
+ /// NB This can lead to problems with the cursor position and typing doesn't give expected results.
+ /// Default: False.
+ ///
+ public bool FormatTextOnChange { get; set; } = false;
+ public double DefaultFontSize { get; }
+ public TextCaseType TextCase
+ {
+ get { return textCase; }
+ set
+ {
+ if (textCase == value) return;
+ textCase = value;
+ FormatText();
+ }
+ }
+ public bool NumericOnly
+ {
+ get { return numericOnly; }
+ set
+ {
+ if (numericOnly == value) return;
+ numericOnly = value;
+ if (numericOnly)
+ HorizontalTextAlignment = TextAlignment.End;
+ Keyboard = Keyboard.Numeric;
+ FormatText();
+ }
+ }
+ public byte Decimals
+ {
+ get { return decimals; }
+ set
+ {
+ if (numericOnly && decimals == value) return;
+ numericOnly = true;
+ HorizontalTextAlignment = TextAlignment.End;
+ decimals = value;
+ Keyboard = Keyboard.Numeric;
+ FormatText();
+ }
+ }
+
+ // Native behaviour modified by SamHandler.
+ 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
+ //-------------------------------------------------------------------
+ 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)
+ {
+ 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);
+ }
+
+ if (!originalText.Equals(formattedText))
+ {
+ SaLogger.Debug(fn + " Text: " + originalText + ". Formatted: " + formattedText);
+ Text = formattedText;
+ }
+ }
+
+ //-------------------------------------------------------------------
+ // C o n t r o l E v e n t s
+ //-------------------------------------------------------------------
+ private void SamEntry_TextChanged(object? sender, TextChangedEventArgs e)
+ {
+ if (FormatTextOnChange) FormatText();
+ }
+ private void SamEntry_Completed(object? sender, EventArgs e)
+ {
+ FormatText(trimTextOnCompletion);
+ }
+
+ //-------------------------------------------------------------------
+ }
+ //=======================================================================
+}
diff --git a/SacsMobileMaui/Maui/Controls/SamImageButton.cs b/SacsMobileMaui/Maui/Controls/SamImageButton.cs
new file mode 100644
index 0000000..2ee174f
--- /dev/null
+++ b/SacsMobileMaui/Maui/Controls/SamImageButton.cs
@@ -0,0 +1,40 @@
+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.Controls
+{
+ //=======================================================================
+ public class SamImageButton : ImageButton
+ {
+ //-------------------------------------------------------------------
+ // Definitions.
+
+ //-------------------------------------------------------------------
+ // Data members.
+
+ //-------------------------------------------------------------------
+ // Construction.
+ public SamImageButton()
+ {
+ //Clicked += Button_Clicked;
+ }
+
+ //-------------------------------------------------------------------
+ // Properties.
+
+ //-------------------------------------------------------------------
+ // Events.
+ protected virtual void Button_Clicked(object? sender, EventArgs e)
+ {
+ SaLogger.Info(SysUtil.Fn + " Button clicked.");
+ }
+
+ //-------------------------------------------------------------------
+ }
+ //=======================================================================
+}
diff --git a/SacsMobileMaui/Maui/Controls/SamLabel.cs b/SacsMobileMaui/Maui/Controls/SamLabel.cs
new file mode 100644
index 0000000..ec1eb24
--- /dev/null
+++ b/SacsMobileMaui/Maui/Controls/SamLabel.cs
@@ -0,0 +1,78 @@
+using Sarand.Net.Standard.Base.Utils;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.CompilerServices;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Sarand.Net.Mobile.Maui.Controls
+{
+ //=======================================================================
+ public class SamLabel : Label
+ {
+ //-------------------------------------------------------------------
+ // Definitions.
+
+ //-------------------------------------------------------------------
+ // Data members.
+ private double fontSize;
+ private double fontSizeProportion = 1.0;
+
+ //-------------------------------------------------------------------
+ // Construction.
+ public SamLabel()
+ {
+ fontSize = base.FontSize;
+ FontSizeDefault = fontSize;
+ Text = "";
+ }
+
+ //-------------------------------------------------------------------
+ // Properties.
+
+ /// Default (initial) font size.
+ public double FontSizeDefault { get; private set; }
+
+ public new double FontSize
+ {
+ get { return fontSize; }
+ set { fontSize = value; base.FontSize = fontSize * fontSizeProportion; }
+ }
+
+ /// Font size proportion.
+ public double FontSizeProportion
+ {
+ get { return fontSizeProportion; }
+ set { fontSizeProportion = value; base.FontSize = fontSize * fontSizeProportion; }
+ }
+
+ //-------------------------------------------------------------------
+ // Control events.
+
+ //-------------------------------------------------------------------
+ // View events.
+
+ protected override void OnPropertyChanging([CallerMemberName] string propertyName = "")
+ {
+ base.OnPropertyChanging(propertyName);
+ if (propertyName.EqualsCI("FontSize"))
+ {
+ }
+ }
+
+ protected override void OnPropertyChanged([CallerMemberName] string propertyName = "")
+ {
+ base.OnPropertyChanged(propertyName);
+ if (propertyName.EqualsCI("FontSize"))
+ {
+ if (FontSizeDefault <= 0)
+ FontSizeDefault = base.FontSize;
+ fontSize = base.FontSize;
+ }
+ }
+
+ //-------------------------------------------------------------------
+ }
+ //=======================================================================
+}
diff --git a/SacsMobileMaui/Maui/Controls/SamPicker.cs b/SacsMobileMaui/Maui/Controls/SamPicker.cs
new file mode 100644
index 0000000..2481afe
--- /dev/null
+++ b/SacsMobileMaui/Maui/Controls/SamPicker.cs
@@ -0,0 +1,178 @@
+using Sarand.Net.Mobile.Maui.App;
+using Sarand.Net.Standard.Base.Bus;
+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.Controls
+{
+ //=======================================================================
+ public class SamPicker : Picker
+ {
+ //-------------------------------------------------------------------
+ // Definitions.
+ public class SamPickerItem
+ {
+ public SysId Id { get; set; } = new SysId();
+ public String Code { get; set; } = "";
+ public String Caption { get; set; } = "";
+ public Object? Object { get; set; }
+ public override string ToString()
+ {
+ return Caption;
+ }
+ }
+
+ //-------------------------------------------------------------------
+ // Data members.
+ private readonly List pickerItems = new List();
+
+ //-------------------------------------------------------------------
+ // Construction.
+ public SamPicker()
+ {
+ DefaultFontSize = FontSize;
+ }
+
+ //-------------------------------------------------------------------
+ // Properties.
+
+ public double DefaultFontSize { get; }
+ public bool BeingPopulated { get; protected set; } = false;
+
+ // Native behaviour modified by SamHandler.
+ 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
+ //-------------------------------------------------------------------
+ ///
+ /// 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));
+ }
+ ///
+ /// Does the list contain an item with the same code.
+ /// NB Codes are case-insensitive.
+ ///
+ public bool ContainsItemCode(String code)
+ {
+ return pickerItems.Exists(item => item.Code.EqualsCI(code));
+ }
+ //-------------------------------------------------------------------
+ /// Remove all items from the picker.
+ public void ClearItems()
+ {
+ Items.Clear();
+ ItemsSource = null;
+ pickerItems.Clear();
+ }
+ /// Replace existing picker item list.
+ public void SetItems(List pickerItems)
+ {
+ ClearItems();
+ this.pickerItems.AddRange(pickerItems);
+ RefreshPickerItems();
+ }
+ //-------------------------------------------------------------------
+ ///
+ /// 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 });
+ RefreshPickerItems();
+ return ContainsItemId(id);
+ }
+ ///
+ /// Add an item identified by a code value.
+ /// NB Fails if an item already exists with the same case-insensitive code.
+ ///
+ public bool AddItem(String code, String caption)
+ {
+ if (ContainsItemCode(code)) return false;
+ pickerItems.Add(new SamPickerItem() { Code = code, Caption = caption });
+ 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.
+ ItemsSource = null;
+ ItemsSource = pickerItems;
+ }
+
+ //-------------------------------------------------------------------
+ // I t e m S e l e c t i o n
+ //-------------------------------------------------------------------
+ public SamPickerItem? SelectedPickerItem
+ {
+ get
+ {
+ object selectedItem = SelectedItem;
+ if (selectedItem == null || !(selectedItem is SamPickerItem)) return null;
+ return (selectedItem as SamPickerItem);
+ }
+ set
+ {
+ if (SelectedPickerItem == value) return;
+ if (ItemsSource == null || !ItemsSource.Contains(value)) return;
+ base.SelectedItem = value;
+ }
+ }
+ public SysId SelectedItemId
+ {
+ get
+ {
+ SamPickerItem? selectedPickerItem = SelectedPickerItem;
+ return selectedPickerItem?.Id ?? new SysId();
+ }
+ set
+ {
+ SelectedPickerItem = pickerItems.Find(item => item.Id.Equals(value));
+ }
+ }
+ public String SelectedItemCode
+ {
+ get
+ {
+ SamPickerItem? selectedPickerItem = SelectedPickerItem;
+ return selectedPickerItem?.Code ?? "";
+ }
+ set
+ {
+ SelectedPickerItem = pickerItems.Find(item => item.Code.EqualsCI(value));
+ }
+ }
+
+ //-------------------------------------------------------------------
+ // C o n t r o l E v e n t s
+ //-------------------------------------------------------------------
+ // Picker population completed.
+ public class PopulatedEventArgs : EventArgs { }
+ public delegate void PopulatedEventHandler(object? sender, PopulatedEventArgs e);
+ /// Picker population completed event handler.
+ public event PopulatedEventHandler? Populated;
+ protected void InvokePopulatedEvent()
+ {
+ if (Populated != null) Populated.Invoke(this, new PopulatedEventArgs() { });
+ }
+
+ //-------------------------------------------------------------------
+ }
+ //=======================================================================
+}
diff --git a/SacsMobileMaui/Maui/Controls/SamTabControl.cs b/SacsMobileMaui/Maui/Controls/SamTabControl.cs
new file mode 100644
index 0000000..29a2841
--- /dev/null
+++ b/SacsMobileMaui/Maui/Controls/SamTabControl.cs
@@ -0,0 +1,193 @@
+using Microsoft.Maui;
+using Microsoft.Maui.Layouts;
+using Sarand.Net.Mobile.Maui.Layouts;
+using Sarand.Net.Mobile.Maui.Panels;
+using Sarand.Net.Mobile.Maui.Views;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Sarand.Net.Mobile.Maui.Controls
+{
+ //=======================================================================
+ public class SamTabControl : ContentView
+ {
+ //-------------------------------------------------------------------
+ // Definitions.
+
+ //-------------------------------------------------------------------
+ // Data members.
+ private readonly List tabPanels = new List();
+ private short currentTabIdx = -1;
+ // Main layout to host header and body.
+ private SamStackLayout mainLayout = new SamStackLayout() { Orientation = StackOrientation.Vertical };
+ // Header layout - horizontal row.
+ private SamFlexLayout headerPanel = new SamFlexLayout() { Direction = FlexDirection.Row, AlignItems = FlexAlignItems.Center, JustifyContent = FlexJustify.Start, HorizontalOptions = LayoutOptions.Start };
+ // Body - Content view to host tab panel.
+ private SamContentView bodyPanel = new SamContentView();
+
+ //-------------------------------------------------------------------
+ // Construction.
+ public SamTabControl()
+ {
+ // Main layout - vertical stack with two rows.
+ // 1. Header - tab panel captions.
+ // 2. Body - current tab panel.
+ VerticalOptions = LayoutOptions.FillAndExpand;
+ //BackgroundColor = Color.LightSkyBlue;
+ mainLayout.VerticalOptions = LayoutOptions.FillAndExpand;
+ mainLayout.Spacing = 2.0;
+ Content = mainLayout;
+
+ // Header panel - tab captions.
+ //headerPanel.HorizontalOptions = LayoutOptions.Fill;
+ mainLayout.Children.Add(headerPanel);
+
+ // Default label font size.
+ DefaultHeaderFontSize = new Label().FontSize;
+ HeaderFontSize = DefaultHeaderFontSize;
+
+ // Body panel.
+ bodyPanel.HorizontalOptions = LayoutOptions.Fill;
+ bodyPanel.VerticalOptions = LayoutOptions.FillAndExpand;
+ bodyPanel.BackgroundColor = Colors.LightYellow;
+ mainLayout.Children.Add(bodyPanel);
+ }
+
+ //-------------------------------------------------------------------
+ // Properties.
+ // Header.
+ public Color HeaderBackgroundColor { get { return headerPanel.BackgroundColor; } set { headerPanel.BackgroundColor = value; } }
+ public Color CaptionBackgroundColor { get; set; } = Colors.Transparent;
+ public double DefaultHeaderFontSize { get; }
+ public double HeaderFontSize { get; set; }
+ public double HeaderMarginBottom
+ {
+ get { return mainLayout.Spacing; }
+ set { if (mainLayout.Spacing == value) return; mainLayout.Spacing = value; }
+ }
+ public Thickness HeaderPadding
+ {
+ get { return mainLayout.Padding; }
+ set { if (mainLayout.Padding == value) return; mainLayout.Padding = value; }
+ }
+ public Thickness HeaderCaptionPadding { get; set; } = new Thickness(12.0, 4.0);
+ public double HeaderCaptionSpacing { get; set; } = 4.0;
+ // Tab panels.
+ public Color BodyBackgroundColor { get { return bodyPanel.BackgroundColor; } set { bodyPanel.BackgroundColor = value; } }
+ public int PanelCount { get { return tabPanels.Count; } }
+ public short TabPanelIndex
+ {
+ get { return currentTabIdx; }
+ set { ChangeTabPanel(value); }
+ }
+
+ //-------------------------------------------------------------------
+ // T a b P a n e l s
+ //-------------------------------------------------------------------
+ public void ClearPanels()
+ {
+ tabPanels.Clear();
+ RefreshLayout();
+ }
+ public void AddPanel(String caption, SamContentView panel)
+ {
+ AddPanel(new SamTabPanel(caption, panel));
+ }
+ public void AddPanel(SamTabPanel tabPanel)
+ {
+ AddPanels(new List() { tabPanel });
+ }
+ public void AddPanels(IEnumerable tabPanels)
+ {
+ this.tabPanels.AddRange(tabPanels);
+ RefreshLayout();
+ }
+
+ //-------------------------------------------------------------------
+ // R e f r e s h L a y o u t
+ //-------------------------------------------------------------------
+ private void RefreshLayout()
+ {
+ // Clear existing components.
+ headerPanel.Children.Clear();
+ bodyPanel.Content = null;
+
+ // Add each Tab panel in turn.
+ for (short tabPanelIdx = 0; tabPanelIdx < tabPanels.Count; tabPanelIdx++)
+ {
+ short tabPanelIdx_Local = tabPanelIdx; // Loop closure.
+ SamTabPanel tabPanel = tabPanels[tabPanelIdx];
+
+ // Header caption.
+ SamContentView captionPanel = new SamContentView()
+ {
+ Padding = HeaderCaptionPadding,
+ BackgroundColor = CaptionBackgroundColor,
+ HorizontalOptions = LayoutOptions.FillAndExpand,
+ VerticalOptions = LayoutOptions.FillAndExpand
+ };
+ if (tabPanelIdx > 0)
+ captionPanel.Margin = new Thickness(HeaderCaptionSpacing, 0, 0, 0);
+ SamLabel captionLabel = new SamLabel()
+ {
+ Text = tabPanel.Caption,
+ FontSize = HeaderFontSize
+ };
+ captionPanel.Content = captionLabel;
+ headerPanel.Children.Add(captionPanel);
+
+ // Tap Gesture.
+ var tapGestureRecognizer = new TapGestureRecognizer();
+ tapGestureRecognizer.Tapped += (s, e) => { ChangeTabPanel(tabPanelIdx_Local); };
+ captionPanel.GestureRecognizers.Add(tapGestureRecognizer);
+ }
+ // Current tab retains previous value if still valid (in panel range),
+ // is restricted to the range 0 to number of panels - 1 if there are panels,
+ // or -1 if no panels.
+ currentTabIdx = (short)(tabPanels.Count > 0 ? Math.Max(0, Math.Min(tabPanels.Count - 1, currentTabIdx)) : -1);
+ if (tabPanels.Count > 0)
+ ChangeTabPanel(currentTabIdx);
+ }
+
+ //-------------------------------------------------------------------
+ // C h a n g e T a b P a n e l
+ //-------------------------------------------------------------------
+ private void ChangeTabPanel(short tabPanelIdx)
+ {
+ // Validate requested panel index.
+ if (tabPanelIdx < 0 || tabPanelIdx >= tabPanels.Count)
+ throw new IndexOutOfRangeException("Tab panel index must be between 0 & " + (tabPanels.Count - 1) + ". Supplied value: " + tabPanelIdx);
+
+ // Highlight selected tab panel header caption.
+ for (short headerPanelIdx = 0; headerPanelIdx < headerPanel.Children.Count; headerPanelIdx++)
+ {
+ // View.
+ View? headerPanelView = headerPanel.Children[headerPanelIdx] as View;
+ // Label.
+ Label? headerPanelLabel = (headerPanelView as Label);
+ if (headerPanelLabel == null && headerPanelView is ContentView)
+ headerPanelLabel = ((ContentView)headerPanelView).Content as Label;
+ if (headerPanelLabel != null)
+ {
+ if (headerPanelIdx == tabPanelIdx)
+ headerPanelLabel.FontAttributes = FontAttributes.Bold;
+ else headerPanelLabel.FontAttributes = FontAttributes.None;
+ }
+ }
+
+ // Display selected tab panel.
+ bodyPanel.Content = tabPanels[tabPanelIdx];
+ currentTabIdx = tabPanelIdx;
+ }
+
+ //-------------------------------------------------------------------
+ // C o n t r o l E v e n t s
+ //-------------------------------------------------------------------
+
+ //-------------------------------------------------------------------
+ }
+ //=======================================================================
+}
diff --git a/SacsMobileMaui/Maui/Controls/SamTimePicker.cs b/SacsMobileMaui/Maui/Controls/SamTimePicker.cs
new file mode 100644
index 0000000..6718681
--- /dev/null
+++ b/SacsMobileMaui/Maui/Controls/SamTimePicker.cs
@@ -0,0 +1,53 @@
+using Sarand.Net.Standard.Base.Utils;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.CompilerServices;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Sarand.Net.Mobile.Maui.Controls
+{
+ //=======================================================================
+ public class SamTimePicker : TimePicker
+ {
+ //-------------------------------------------------------------------
+ // Definitions.
+
+ //-------------------------------------------------------------------
+ // Data members.
+
+ //-------------------------------------------------------------------
+ // Construction.
+ public SamTimePicker()
+ {
+ DefaultFontSize = FontSize;
+ Format = "HH:mm";
+ }
+
+ //-------------------------------------------------------------------
+ // Properties.
+ public String FormattedTime { get { return DateUtil.Format(DateTime.Today + Time, Format); } }
+ // Styling.
+ public double DefaultFontSize { get; }
+
+ //-------------------------------------------------------------------
+ // C o n t r o l E v e n t s
+ //-------------------------------------------------------------------
+ public class TimeChangedEventArgs : EventArgs { };
+ public event EventHandler? TimeChanged;
+ protected override void OnPropertyChanged([CallerMemberName] string propertyName = "")
+ {
+ if (propertyName.EqualsCI(TimePicker.TimeProperty.PropertyName))
+ {
+ base.OnPropertyChanged(propertyName);
+ if (TimeChanged != null) TimeChanged.Invoke(this, new TimeChangedEventArgs());
+ return;
+ }
+ base.OnPropertyChanged(propertyName);
+ }
+
+ //-------------------------------------------------------------------
+ }
+ //=======================================================================
+}
diff --git a/SacsMobileMaui/Maui/Controls/SamWebView.cs b/SacsMobileMaui/Maui/Controls/SamWebView.cs
new file mode 100644
index 0000000..cedb4f1
--- /dev/null
+++ b/SacsMobileMaui/Maui/Controls/SamWebView.cs
@@ -0,0 +1,103 @@
+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.Controls
+{
+ //=======================================================================
+ public class SamWebView : WebView
+ {
+ //-------------------------------------------------------------------
+ // Definitions.
+
+ //-------------------------------------------------------------------
+ // Data members.
+ public String html = "";
+ private HtmlWebViewSource htmlSource;
+ private UrlWebViewSource aboutBlankSource = new UrlWebViewSource() { Url = "about:blank" };
+ // Managers.
+
+ //-------------------------------------------------------------------
+ // Construction.
+ public SamWebView()
+ {
+ // Events.
+ Navigating += WebView_Navigating;
+ Navigated += WebView_Navigated;
+
+ // HTML source.
+ htmlSource = new HtmlWebViewSource() { Html = html };
+ Source = aboutBlankSource;
+ }
+
+ //-------------------------------------------------------------------
+ // Properties.
+ // HTML Panel.
+ public static String AppActionPrefix => "App:";
+ public String Html
+ {
+ get { return html; }
+ set
+ {
+ Source = aboutBlankSource;
+ htmlSource.Html = null;
+ html = value;
+ htmlSource.Html = html;
+ Source = htmlSource;
+ }
+ }
+
+ //-------------------------------------------------------------------
+ // N a v i g a t i o n
+ //-------------------------------------------------------------------
+ public class AppNavigatingEventArgs : EventArgs
+ {
+ public AppNavigatingEventArgs(String appAction)
+ {
+ this.AppAction = appAction;
+ }
+ public String AppAction { get; }
+ }
+ public delegate void AppNavigatingEventHandler(object? sender, AppNavigatingEventArgs e);
+ public event AppNavigatingEventHandler? AppNavigating;
+ private void OnAppNavigating(String appAction)
+ {
+ if (AppNavigating != null) AppNavigating.Invoke(this, new AppNavigatingEventArgs(appAction));
+ }
+ private String GetAppAction(String location)
+ {
+ if (!location.StartsWithCI(AppActionPrefix)) return "";
+ return location.Right(location.Length - AppActionPrefix.Length);
+ }
+
+ //-------------------------------------------------------------------
+ // C o n t r o l E v e n t s
+ //-------------------------------------------------------------------
+ private void WebView_Navigating(object? sender, WebNavigatingEventArgs e)
+ {
+ String appAction = GetAppAction(e.Url);
+ SaLogger.Debug("SamWebView_Navigating URL: " + e.Url + ". App action: " + appAction);
+ if (!StringUtil.IsEmpty(appAction))
+ {
+ e.Cancel = true;
+ OnAppNavigating(appAction);
+ }
+ }
+ private void WebView_Navigated(object? sender, WebNavigatedEventArgs e)
+ {
+ SaLogger.Debug("SamWebView_Navigated URL: " + e.Url);
+ String appAction = GetAppAction(e.Url);
+ if (!StringUtil.IsEmpty(appAction))
+ {
+ Source = htmlSource;
+ }
+ }
+
+ //-------------------------------------------------------------------
+ }
+ //=======================================================================
+}
diff --git a/SacsMobileMaui/Maui/Handlers/SamFormHandler.cs b/SacsMobileMaui/Maui/Handlers/SamFormHandler.cs
new file mode 100644
index 0000000..433fdca
--- /dev/null
+++ b/SacsMobileMaui/Maui/Handlers/SamFormHandler.cs
@@ -0,0 +1,139 @@
+#if ANDROID
+using Microsoft.Maui.Controls.Compatibility.Platform.Android;
+#endif
+using Sarand.Net.Mobile.Maui.Controls;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+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.
+
+ //--------------------------------------------------------------------
+ ///
+ /// Remove borders around entry and picker controls.
+ /// Also make background transparent.
+ ///
+ public static void AssignControlHandlers()
+ {
+ //---------------------------------------------------------------
+ // Entry handler.
+ Microsoft.Maui.Handlers.EntryHandler.Mapper.AppendToMapping("SamEntryHandler", (handler, view) =>
+ {
+ if ((view as SamEntry)?.NativeNoBorder ?? false)
+ {
+#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.UITextBorderStyle.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 ANDROID
+ handler.PlatformView.BackgroundTintList = Android.Content.Res.ColorStateList.ValueOf(Colors.Transparent.ToAndroid());
+#elif IOS || MACCATALYST
+#elif WINDOWS
+#endif
+ }
+ if ((view as SamEntry)?.NativeBackgroundClear ?? false)
+ {
+#if ANDROID
+ handler.PlatformView.Background = null;
+ handler.PlatformView.SetBackgroundColor(Android.Graphics.Color.Transparent);
+#elif IOS || MACCATALYST
+ handler.PlatformView.BackgroundColor = UIKit.UIColor.Clear;
+#elif WINDOWS
+#endif
+ }
+ });
+
+ //---------------------------------------------------------------
+ // Editor handler.
+ Microsoft.Maui.Handlers.EditorHandler.Mapper.AppendToMapping("SamEditorHandler", (handler, view) =>
+ {
+ if ((view as SamEditor)?.NativeNoBorder ?? false)
+ {
+#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 ANDROID
+ handler.PlatformView.BackgroundTintList = Android.Content.Res.ColorStateList.ValueOf(Colors.Transparent.ToAndroid());
+#elif IOS || MACCATALYST
+#elif WINDOWS
+#endif
+ }
+ if ((view as SamEntry)?.NativeBackgroundClear ?? false)
+ {
+#if ANDROID
+ handler.PlatformView.Background = null;
+ handler.PlatformView.SetBackgroundColor(Android.Graphics.Color.Transparent);
+#elif IOS || MACCATALYST
+ handler.PlatformView.BackgroundColor = UIKit.UIColor.Clear;
+#elif WINDOWS
+#endif
+ }
+ });
+
+ //---------------------------------------------------------------
+ // Picker handler.
+ Microsoft.Maui.Handlers.PickerHandler.Mapper.AppendToMapping("Borderless", (handler, view) =>
+ {
+ if ((view as SamEditor)?.NativeNoBorder ?? false)
+ {
+#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.UITextBorderStyle.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 ANDROID
+ handler.PlatformView.BackgroundTintList = Android.Content.Res.ColorStateList.ValueOf(Colors.Transparent.ToAndroid());
+#elif IOS || MACCATALYST
+#elif WINDOWS
+#endif
+ }
+ if ((view as SamEntry)?.NativeBackgroundClear ?? false)
+ {
+#if ANDROID
+ handler.PlatformView.Background = null;
+ handler.PlatformView.SetBackgroundColor(Android.Graphics.Color.Transparent);
+#elif IOS || MACCATALYST
+ handler.PlatformView.BackgroundColor = UIKit.UIColor.Clear;
+#elif WINDOWS
+#endif
+ }
+ });
+
+ //---------------------------------------------------------------
+ }
+
+ //--------------------------------------------------------------------
+ }
+ //========================================================================
+}
diff --git a/SacsMobileMaui/Maui/Layouts/SamAbsoluteLayout.cs b/SacsMobileMaui/Maui/Layouts/SamAbsoluteLayout.cs
new file mode 100644
index 0000000..6b6b6b2
--- /dev/null
+++ b/SacsMobileMaui/Maui/Layouts/SamAbsoluteLayout.cs
@@ -0,0 +1,30 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Sarand.Net.Mobile.Maui.Layouts
+{
+ //=======================================================================
+ public class SamAbsoluteLayout : AbsoluteLayout
+ {
+ //-------------------------------------------------------------------
+ // Definitions.
+
+ //-------------------------------------------------------------------
+ // Data members.
+
+ //-------------------------------------------------------------------
+ // Construction.
+ public SamAbsoluteLayout()
+ {
+ }
+
+ //-------------------------------------------------------------------
+ // Properties.
+
+ //-------------------------------------------------------------------
+ }
+ //=======================================================================
+}
diff --git a/SacsMobileMaui/Maui/Layouts/SamFlexLayout.cs b/SacsMobileMaui/Maui/Layouts/SamFlexLayout.cs
new file mode 100644
index 0000000..8b8ba34
--- /dev/null
+++ b/SacsMobileMaui/Maui/Layouts/SamFlexLayout.cs
@@ -0,0 +1,30 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Sarand.Net.Mobile.Maui.Layouts
+{
+ //=======================================================================
+ public class SamFlexLayout : FlexLayout
+ {
+ //-------------------------------------------------------------------
+ // Definitions.
+
+ //-------------------------------------------------------------------
+ // Data members.
+
+ //-------------------------------------------------------------------
+ // Construction.
+ public SamFlexLayout()
+ {
+ }
+
+ //-------------------------------------------------------------------
+ // Properties.
+
+ //-------------------------------------------------------------------
+ }
+ //=======================================================================
+}
diff --git a/SacsStandardMob/Mobile/Forms/Layouts/SaxGrid.cs b/SacsMobileMaui/Maui/Layouts/SamGrid.cs
similarity index 63%
rename from SacsStandardMob/Mobile/Forms/Layouts/SaxGrid.cs
rename to SacsMobileMaui/Maui/Layouts/SamGrid.cs
index c87c97c..7935ffb 100644
--- a/SacsStandardMob/Mobile/Forms/Layouts/SaxGrid.cs
+++ b/SacsMobileMaui/Maui/Layouts/SamGrid.cs
@@ -1,13 +1,14 @@
using System;
using System.Collections.Generic;
+using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
-using Xamarin.Forms;
+using System.Threading.Tasks;
-namespace Sarand.Net.Standard.Mobile.Forms.Templates
+namespace Sarand.Net.Mobile.Maui.Layouts
{
//=======================================================================
- public class SaxGrid : Grid
+ public class SamGrid : Grid
{
//-------------------------------------------------------------------
// Definitions.
@@ -17,7 +18,7 @@ namespace Sarand.Net.Standard.Mobile.Forms.Templates
//-------------------------------------------------------------------
// Construction.
- public SaxGrid ()
+ public SamGrid()
{
}
@@ -27,37 +28,36 @@ namespace Sarand.Net.Standard.Mobile.Forms.Templates
//-------------------------------------------------------------------
// G r i d E v e n t s
//-------------------------------------------------------------------
- protected override void OnParentSet ()
+ protected override void OnParentSet()
{
base.OnParentSet();
}
//-------------------------------------------------------------------
- protected override void OnPropertyChanging ([CallerMemberName] string propertyName = null)
+ protected override void OnPropertyChanging([CallerMemberName] string propertyName = "")
{
base.OnPropertyChanging(propertyName);
}
- protected override void OnPropertyChanged ([CallerMemberName] string propertyName = null)
+ protected override void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
base.OnPropertyChanged(propertyName);
}
//-------------------------------------------------------------------
- protected override void OnAdded (View view)
+ protected override void OnAdd(int index, IView view)
{
- base.OnAdded(view);
+ base.OnAdd(index, view);
}
- protected override void OnChildAdded (Element child)
+ protected override void OnChildAdded(Element child)
{
base.OnChildAdded(child);
}
//-------------------------------------------------------------------
- protected override void OnRemoved (View view)
+ protected override void OnRemove(int index, IView view)
{
- base.OnRemoved(view);
+ base.OnRemove(index, view);
}
- [Obsolete("OnChildRemoved(Element) is obsolete as of version 4.8.0. Please use OnChildRemoved(Element, int) instead.")]
- protected override void OnChildRemoved (Element child)
+ protected override void OnChildRemoved(Element child, int oldLogicalIndex)
{
- base.OnChildRemoved(child);
+ base.OnChildRemoved(child, oldLogicalIndex);
}
//-------------------------------------------------------------------
diff --git a/SacsMobileMaui/Maui/Layouts/SamStackLayout.cs b/SacsMobileMaui/Maui/Layouts/SamStackLayout.cs
new file mode 100644
index 0000000..b1aea8b
--- /dev/null
+++ b/SacsMobileMaui/Maui/Layouts/SamStackLayout.cs
@@ -0,0 +1,30 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Sarand.Net.Mobile.Maui.Layouts
+{
+ //=======================================================================
+ public class SamStackLayout : StackLayout
+ {
+ //-------------------------------------------------------------------
+ // Definitions.
+
+ //-------------------------------------------------------------------
+ // Data members.
+
+ //-------------------------------------------------------------------
+ // Construction.
+ public SamStackLayout()
+ {
+ }
+
+ //-------------------------------------------------------------------
+ // Properties.
+
+ //-------------------------------------------------------------------
+ }
+ //=======================================================================
+}
diff --git a/SacsMobileMaui/Maui/Layouts/SamWrapLayout.cs b/SacsMobileMaui/Maui/Layouts/SamWrapLayout.cs
new file mode 100644
index 0000000..3b03a75
--- /dev/null
+++ b/SacsMobileMaui/Maui/Layouts/SamWrapLayout.cs
@@ -0,0 +1,249 @@
+using Microsoft.Maui.Controls.Compatibility;
+using System;
+using System.Collections.Generic;
+using System.Drawing;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Sarand.Net.Mobile.Maui.Layouts
+{
+ //=======================================================================
+ ///
+ /// Wrapping columnar layout - to achieve resposive behaviour.
+ /// Layout which performs wrapping on the boundaries.
+ /// Base Code -> https://forums.xamarin.com/discussion/comment/57486/#Comment_57486
+ /// http://bit.ly/xf-custompanel
+ ///
+ //=======================================================================
+ public class SamWrapLayout : Layout
+ {
+ //-------------------------------------------------------------------
+ /// Horizontal spacing between elements.
+ public double SpacingX
+ {
+ get { return (double)GetValue(SpacingXProperty); }
+ set { SetValue(SpacingXProperty, value); }
+ }
+ public static BindableProperty SpacingXProperty =
+ BindableProperty.Create(
+ nameof(SpacingX),
+ typeof(double),
+ typeof(SamWrapLayout),
+ default(double),
+ defaultBindingMode: BindingMode.OneWay,
+ propertyChanged: (bindable, oldvalue, newvalue) => ((SamWrapLayout)bindable).InvalidateMeasure()
+ );
+ /// Vertical spacing between elements.
+ public double SpacingY
+ {
+ get { return (double)GetValue(SpacingYProperty); }
+ set { SetValue(SpacingYProperty, value); }
+ }
+ public static BindableProperty SpacingYProperty =
+ BindableProperty.Create(
+ nameof(SpacingY),
+ typeof(double),
+ typeof(SamWrapLayout),
+ default(double),
+ defaultBindingMode: BindingMode.OneWay,
+ propertyChanged: (bindable, oldvalue, newvalue) => ((SamWrapLayout)bindable).InvalidateMeasure()
+ );
+
+ //-------------------------------------------------------------------
+ /// Number of uniform columns in each row.
+ public int UniformColumns
+ {
+ get { return (int)GetValue(UniformColumnsProperty); }
+ set { SetValue(UniformColumnsProperty, value); }
+ }
+ public static BindableProperty UniformColumnsProperty =
+ BindableProperty.Create(
+ nameof(UniformColumns),
+ typeof(int),
+ typeof(SamWrapLayout),
+ default(int),
+ defaultBindingMode: BindingMode.OneWay,
+ propertyChanged: (bindable, oldvalue, newvalue) => ((SamWrapLayout)bindable).InvalidateMeasure()
+ );
+
+ //-------------------------------------------------------------------
+ /// Item height equals item width when UniformColums > 0
+ public bool IsSquare
+ {
+ get { return (bool)GetValue(IsSquareProperty); }
+ set { SetValue(IsSquareProperty, value); }
+ }
+ public static BindableProperty IsSquareProperty =
+ BindableProperty.Create(
+ nameof(IsSquare),
+ typeof(bool),
+ typeof(SamWrapLayout),
+ false,
+ propertyChanged: (bindable, oldvalue, newvalue) => ((SamWrapLayout)bindable).InvalidateMeasure()
+ );
+
+ //-------------------------------------------------------------------
+ protected override SizeRequest OnMeasure(double widthConstraint, double heightConstraint)
+ {
+ if (WidthRequest > 0)
+ widthConstraint = Math.Min(widthConstraint, WidthRequest);
+ if (HeightRequest > 0)
+ heightConstraint = Math.Min(heightConstraint, HeightRequest);
+
+ double internalWidth = double.IsPositiveInfinity(widthConstraint) ? double.PositiveInfinity : Math.Max(0, widthConstraint);
+ double internalHeight = double.IsPositiveInfinity(heightConstraint) ? double.PositiveInfinity : Math.Max(0, heightConstraint);
+
+ if (double.IsPositiveInfinity(widthConstraint) && double.IsPositiveInfinity(heightConstraint))
+ {
+ return new SizeRequest(Microsoft.Maui.Graphics.Size.Zero, Microsoft.Maui.Graphics.Size.Zero);
+ }
+
+ return UniformColumns > 0 ? UniformMeasureAndLayout(internalWidth, internalHeight) :
+ VariableMeasureAndLayout(internalWidth, internalHeight);
+
+ }
+
+ //-------------------------------------------------------------------
+ protected override void LayoutChildren(double x, double y, double width, double height)
+ {
+ if (UniformColumns > 0)
+ UniformMeasureAndLayout(width, height, true, x, y);
+ else
+ VariableMeasureAndLayout(width, height, true, x, y);
+ }
+
+ //-------------------------------------------------------------------
+ private SizeRequest UniformMeasureAndLayout
+ (double widthConstraint, double heightConstraint, bool doLayout = false, double x = 0, double y = 0)
+ {
+ double totalWidth = 0;
+ double totalHeight = 0;
+ double rowHeight = 0;
+ double minWidth = 0;
+ double minHeight = 0;
+ double xPos = x;
+ double yPos = y;
+
+ totalWidth = widthConstraint;
+ var exceptedWidth = widthConstraint - (UniformColumns - 1) * SpacingX; //excepted spacing width
+
+ var columnsSize = exceptedWidth / UniformColumns;
+ if (columnsSize < 1)
+ {
+ columnsSize = 1;
+ }
+
+ foreach (var child in Children.Where(c => c.IsVisible))
+ {
+
+ var size = child.Measure(widthConstraint, heightConstraint);
+ var itemWidth = (double)columnsSize;
+ var itemHeight = size.Request.Height;
+
+ if (IsSquare)
+ {
+ itemHeight = columnsSize;
+ }
+
+ rowHeight = Math.Max(rowHeight, itemHeight + SpacingY);
+
+ minHeight = Math.Max(minHeight, itemHeight);
+ minWidth = Math.Max(minWidth, itemWidth);
+
+ if (doLayout)
+ {
+ var region = new Rect(xPos, yPos, itemWidth, itemHeight);
+ LayoutChildIntoBoundingRegion(child, region);
+ }
+
+ xPos += itemWidth + SpacingX;
+
+ if (xPos + columnsSize - x > widthConstraint)
+ {
+ xPos = x;
+ yPos += rowHeight;
+ totalHeight += rowHeight;
+ rowHeight = 0;
+ }
+ }
+
+ totalHeight = Math.Max(totalHeight + rowHeight - SpacingY, 0);
+
+ return new SizeRequest(new Microsoft.Maui.Graphics.Size(totalWidth, totalHeight), new Microsoft.Maui.Graphics.Size(minWidth, minHeight));
+ }
+
+ //-------------------------------------------------------------------
+ private SizeRequest VariableMeasureAndLayout
+ (double widthConstraint, double heightConstraint, bool doLayout = false, double x = 0, double y = 0)
+ {
+ double totalWidth = 0;
+ double totalHeight = 0;
+ double rowHeight = 0;
+ double rowWidth = 0;
+ double minWidth = 0;
+ double minHeight = 0;
+ double xPos = x;
+ double yPos = y;
+
+ var visibleChildren = Children.Where(c => c.IsVisible).Select(c => new
+ {
+ child = c,
+ size = c.Measure(widthConstraint, heightConstraint)
+ });
+
+ 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)
+ {
+ var child = childBlock.current.child;
+ var size = childBlock.current.size;
+ var itemWidth = size.Request.Width;
+ var itemHeight = size.Request.Height;
+
+ rowHeight = Math.Max(rowHeight, itemHeight + SpacingY);
+ rowWidth += itemWidth + SpacingX;
+
+ minHeight = Math.Max(minHeight, itemHeight);
+ minWidth = Math.Max(minWidth, itemWidth);
+
+ if (doLayout)
+ {
+ var region = new Rect(xPos, yPos, itemWidth, itemHeight);
+ LayoutChildIntoBoundingRegion(child, region);
+ }
+
+ if (childBlock.next == null)
+ {
+ totalHeight += rowHeight;
+ totalWidth = Math.Max(totalWidth, rowWidth);
+ break;
+ }
+
+ xPos += itemWidth + SpacingX;
+ var nextWitdh = childBlock.next.size.Request.Width;
+
+ if (xPos + nextWitdh - x > widthConstraint)
+ {
+ xPos = x;
+ yPos += rowHeight;
+ totalHeight += rowHeight;
+ totalWidth = Math.Max(totalWidth, rowWidth);
+ rowHeight = 0;
+ rowWidth = 0;
+ }
+ }
+
+ totalWidth = Math.Max(totalWidth - SpacingX, 0);
+ totalHeight = Math.Max(totalHeight - SpacingY, 0);
+
+ return new SizeRequest(new Microsoft.Maui.Graphics.Size(totalWidth, totalHeight), new Microsoft.Maui.Graphics.Size(minWidth, minHeight));
+ }
+
+ //-------------------------------------------------------------------
+ }
+ //=======================================================================
+}
diff --git a/SacsMobileMaui/Maui/Pages/SamContentPage.cs b/SacsMobileMaui/Maui/Pages/SamContentPage.cs
new file mode 100644
index 0000000..57a2f15
--- /dev/null
+++ b/SacsMobileMaui/Maui/Pages/SamContentPage.cs
@@ -0,0 +1,182 @@
+using Microsoft.Maui.Controls;
+using Sarand.Net.Mobile.Maui.Views;
+using Sarand.Net.Standard.Base.Utils;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.CompilerServices;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Sarand.Net.Mobile.Maui.Pages
+{
+ //=======================================================================
+ public abstract class SamContentPage : ContentPage
+ {
+ //-------------------------------------------------------------------
+ // Definitions.
+
+ //-------------------------------------------------------------------
+ // Data members.
+
+ //-------------------------------------------------------------------
+ // Construction.
+ public SamContentPage()
+ {
+ }
+
+ //-------------------------------------------------------------------
+ // Properties.
+ public abstract String MessageTitleDefault { get; }
+
+ //-------------------------------------------------------------------
+ // C o n f i g u r e C o m p o n e n t s
+ //-------------------------------------------------------------------
+ protected virtual void ConfigureComponents()
+ {
+ }
+
+ 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();
+ }
+ }
+
+ //-------------------------------------------------------------------
+ // C o n t r o l A c t i v i t y
+ //-------------------------------------------------------------------
+ protected virtual void SetControlActivity()
+ {
+ 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();
+ }
+ }
+ protected virtual void SetControlsReadOnly()
+ {
+ }
+
+ //-------------------------------------------------------------------
+ // P o p u p M e s s a g e s
+ //-------------------------------------------------------------------
+ public enum AlertMode
+ {
+ OK,
+ Close,
+ }
+ public Task DisplayAlert(String message, AlertMode alertMode = AlertMode.OK)
+ {
+ String title = StringUtil.OnEmpty(Title, MessageTitleDefault);
+ String cancel = "OK";
+ if (alertMode == AlertMode.Close)
+ cancel = "Close";
+ return DisplayAlert(title, message, cancel);
+ }
+ public enum ConfirmMode
+ {
+ YesNo,
+ OkCancel
+ }
+ public async Task DisplayConfirm(String message, ConfirmMode confirmMode = ConfirmMode.OkCancel)
+ {
+ String title = StringUtil.OnEmpty(Title, MessageTitleDefault);
+ String accept = "Yes";
+ String cancel = "No";
+ if (confirmMode == ConfirmMode.OkCancel)
+ {
+ accept = "OK";
+ cancel = "Cancel";
+ }
+ return await DisplayAlert(title, message, accept, cancel);
+ }
+
+ //-------------------------------------------------------------------
+ // P a g e E v e n t s
+ //-------------------------------------------------------------------
+ protected override void OnParentSet()
+ {
+ base.OnParentSet();
+ }
+
+ //-------------------------------------------------------------------
+ protected override void OnPropertyChanged([CallerMemberName] string propertyName = "")
+ {
+ base.OnPropertyChanged(propertyName);
+ }
+
+ //-------------------------------------------------------------------
+ protected override void OnAppearing()
+ {
+ base.OnAppearing();
+ ConfigureComponents();
+ ConfigureChildComponents();
+ }
+
+ //-------------------------------------------------------------------
+ // Layout changes.
+ private bool inOnSizeAllocated = false;
+ protected override void OnSizeAllocated(double width, double height)
+ {
+ bool alreadyInOnSizeAllocated = inOnSizeAllocated;
+ inOnSizeAllocated = true;
+ base.OnSizeAllocated(width, height);
+ // Only configure components if this is the first entry to this event
+ // // to avoid iterative layout changes caused by components being configured.
+ if (!alreadyInOnSizeAllocated)
+ {
+ if (Width > 0 && Height > 0)
+ {
+ ConfigureComponents();
+ ConfigureChildComponents();
+ }
+ inOnSizeAllocated = false;
+ }
+ }
+
+ //-------------------------------------------------------------------
+ protected override void OnDisappearing()
+ {
+ base.OnDisappearing();
+ }
+
+ //-------------------------------------------------------------------
+ }
+ //=======================================================================
+}
diff --git a/SacsMobileMaui/Maui/Panels/SamPanel.cs b/SacsMobileMaui/Maui/Panels/SamPanel.cs
new file mode 100644
index 0000000..7bda81f
--- /dev/null
+++ b/SacsMobileMaui/Maui/Panels/SamPanel.cs
@@ -0,0 +1,81 @@
+using Sarand.Net.Mobile.Maui.Views;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.CompilerServices;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Sarand.Net.Mobile.Maui.Panels
+{
+ //=======================================================================
+ public class SamPanel : SamContentView
+ {
+ //-------------------------------------------------------------------
+ // Definitions.
+
+ //-------------------------------------------------------------------
+ // Data members.
+
+ //-------------------------------------------------------------------
+ // Construction.
+ public SamPanel()
+ {
+ }
+
+ //-------------------------------------------------------------------
+ // Properties.
+
+ //-------------------------------------------------------------------
+ // C o n f i g u r e C o m p o n e n t s
+ //-------------------------------------------------------------------
+ public override void ConfigureComponents()
+ {
+ base.ConfigureComponents();
+ }
+
+ //-------------------------------------------------------------------
+ // C o n t r o l A c t i v i t y
+ //-------------------------------------------------------------------
+ public override void SetControlActivity()
+ {
+ }
+ public override void SetControlsReadOnly()
+ {
+ }
+
+ //-------------------------------------------------------------------
+ // P a n e l 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);
+ }
+ //-------------------------------------------------------------------
+ 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);
+ }
+
+ //-------------------------------------------------------------------
+ }
+ //=======================================================================
+}
diff --git a/SacsMobileMaui/Maui/Panels/SamPanel_ItemFilter.cs b/SacsMobileMaui/Maui/Panels/SamPanel_ItemFilter.cs
new file mode 100644
index 0000000..9f3904f
--- /dev/null
+++ b/SacsMobileMaui/Maui/Panels/SamPanel_ItemFilter.cs
@@ -0,0 +1,169 @@
+using Sarand.Net.Mobile.Maui.Layouts;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Sarand.Net.Mobile.Maui.Panels
+{
+ //=======================================================================
+ public class SacsFilterPropertyType
+ {
+ public enum FilterMode { Text, DropDownList }
+ public SacsFilterPropertyType(int propertyType, String caption)
+ {
+ PropertyType = propertyType;
+ Caption = caption;
+ }
+ public int PropertyType { get; }
+ public String Caption { get; }
+ public FilterMode Mode { get; set; } = FilterMode.Text;
+ }
+ public class SamPanel_ItemFilter : SamWrapLayout
+ {
+ //-------------------------------------------------------------------
+ // Definitions.
+ public class Config
+ {
+ public List FilterPropertyTypes { get; } = new List();
+ public ushort FiltersToDisplay { get; set; } = 2;
+ public int PropertyTypeWidth { get; set; } = 100;
+ public int PropertyValueWidth { get; set; } = 120;
+ }
+
+ //-------------------------------------------------------------------
+ // Data members.
+ private Config config;
+ private SamPanel_ItemFilterProperty[] itemFilterProperties = new SamPanel_ItemFilterProperty[0];
+
+ //-------------------------------------------------------------------
+ // Construction.
+ public SamPanel_ItemFilter(Config config)
+ {
+ this.config = config;
+ InitialiseComponents();
+ }
+
+ //-------------------------------------------------------------------
+ // Properties.
+ public int FilterCount { get { return Math.Min(config.FiltersToDisplay, config.FilterPropertyTypes.Count); } }
+
+ //-------------------------------------------------------------------
+ // Configure.
+ public void SetConfig(Config config)
+ {
+ this.config = config;
+ ConfigurePanel();
+ }
+
+ //-------------------------------------------------------------------
+ // P u b l i c I n t e r f a c e
+ //-------------------------------------------------------------------
+ public String GetFilterPropertyValue(int propertyType)
+ {
+ foreach (SamPanel_ItemFilterProperty propertyFilter in itemFilterProperties)
+ {
+ if (propertyFilter.SelectedPropertyType == propertyType)
+ return propertyFilter.PropertyValue;
+ }
+ return "";
+ }
+
+ //-------------------------------------------------------------------
+ // I n i t i a l i s e
+ //-------------------------------------------------------------------
+ private void InitialiseComponents()
+ {
+ // Styling.
+ SpacingX = 8.0;
+ SpacingY = 4.0;
+ BackgroundColor = Colors.Transparent;
+
+ ConfigurePanel();
+ }
+
+ //-------------------------------------------------------------------
+ // C o n f i g u r e P a n e l
+ //-------------------------------------------------------------------
+ private void ConfigurePanel()
+ {
+ // Clear filter panels.
+ Children.Clear();
+
+ // Reconfigure fiter properties.
+ if (itemFilterProperties.Length != FilterCount)
+ {
+ // Create new set of filters and copy previous filters across up to required number.
+ SamPanel_ItemFilterProperty[] itemFilterPropertiesNew = new SamPanel_ItemFilterProperty[FilterCount];
+ for (int filterIdx = 0; filterIdx < Math.Min(itemFilterProperties.Length, FilterCount); filterIdx++)
+ itemFilterPropertiesNew[filterIdx] = itemFilterProperties[filterIdx];
+ for (int filterIdx = itemFilterProperties.Length; filterIdx < FilterCount; filterIdx++)
+ {
+ itemFilterPropertiesNew[filterIdx] = new SamPanel_ItemFilterProperty(new SamPanel_ItemFilterProperty.Config());
+ itemFilterPropertiesNew[filterIdx].PropertyFilterChanged += SamPanel_ItemFilter_PropertyFilterChanged;
+ }
+ itemFilterProperties = itemFilterPropertiesNew;
+ }
+
+ // Retrieve currently selected property types.
+ List selectedPropertyTypes = new List();
+ for (int filterIdx = 0; filterIdx < FilterCount; filterIdx++)
+ {
+ int selectedPropertyType = itemFilterProperties[filterIdx].SelectedPropertyType;
+ if (selectedPropertyType >= 0 && !selectedPropertyTypes.Contains(selectedPropertyType))
+ selectedPropertyTypes.Add(selectedPropertyType);
+ }
+
+ // Configure filter panels.
+ for (int filterIdx = 0; filterIdx < FilterCount; filterIdx++)
+ {
+ SamPanel_ItemFilterProperty propertyPanel = itemFilterProperties[filterIdx];
+
+ // Determine the property types to display - remove property types selected in other panels.
+ List filterPropertyTypes = new List(config.FilterPropertyTypes);
+ filterPropertyTypes.RemoveAll(item => item.PropertyType != propertyPanel.SelectedPropertyType && selectedPropertyTypes.Contains(item.PropertyType));
+ if (filterPropertyTypes.Count == 0) continue;
+
+ SamPanel_ItemFilterProperty.Config propertyPanelConfig = new SamPanel_ItemFilterProperty.Config();
+ propertyPanelConfig.FilterPropertyTypes.AddRange(filterPropertyTypes);
+ propertyPanelConfig.PropertyTypeWidth = config.PropertyTypeWidth;
+ propertyPanelConfig.PropertyValueWidth = config.PropertyValueWidth;
+ propertyPanel.SetConfig(propertyPanelConfig);
+
+ // Include the selecetd type in the list of not already present.
+ int selectedPropertyType = propertyPanel.SelectedPropertyType;
+ if (selectedPropertyType >= 0 && !selectedPropertyTypes.Contains(selectedPropertyType))
+ selectedPropertyTypes.Add(selectedPropertyType);
+
+ // Add panel to the layout.
+ Children.Add(propertyPanel);
+ }
+ }
+
+ //-------------------------------------------------------------------
+ // F i l t e r E v e n t s
+ //-------------------------------------------------------------------
+ public class FilterChangedEventArgs : EventArgs
+ {
+ public int PropertyType { get; set; } = -1;
+ }
+ public event EventHandler? FilterChanged;
+ private void FireFilterChangedEvent(int propertyType)
+ {
+ FilterChangedEventArgs filterChangedEventArgs = new FilterChangedEventArgs()
+ {
+ PropertyType = propertyType,
+ };
+ if (FilterChanged != null) FilterChanged.Invoke(this, filterChangedEventArgs);
+ }
+ //-------------------------------------------------------------------
+ private void SamPanel_ItemFilter_PropertyFilterChanged(object? sender, SamPanel_ItemFilterProperty.PropertyFilterChangedEventArgs e)
+ {
+ FireFilterChangedEvent(e.PropertyType);
+ }
+
+ //-------------------------------------------------------------------
+ }
+ //=======================================================================
+}
diff --git a/SacsMobileMaui/Maui/Panels/SamPanel_ItemFilterProperty.cs b/SacsMobileMaui/Maui/Panels/SamPanel_ItemFilterProperty.cs
new file mode 100644
index 0000000..539e703
--- /dev/null
+++ b/SacsMobileMaui/Maui/Panels/SamPanel_ItemFilterProperty.cs
@@ -0,0 +1,174 @@
+using Sarand.Net.Mobile.Maui.Controls;
+using Sarand.Net.Mobile.Maui.Layouts;
+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.Panels
+{
+ //=======================================================================
+ public class SamPanel_ItemFilterProperty : SamPanel
+ {
+ //-------------------------------------------------------------------
+ // Definitions.
+ public class Config
+ {
+ public List FilterPropertyTypes { get; } = new List();
+ public GridLength Height { get; set; } = GridLength.Auto;
+ public int PropertyTypeWidth { get; set; } = 0;
+ public int PropertyValueWidth { get; set; } = 0;
+ }
+
+ //-------------------------------------------------------------------
+ // Data members.
+ private Config config;
+ private SamGrid? grid;
+ private SamPicker pickerPropertyType = new SamPicker();
+ private SamEntry entryPropertyValue = new SamEntry() { Text = "", HorizontalOptions = LayoutOptions.FillAndExpand };
+ private SamPicker pickerPropertyValue = new SamPicker() { HorizontalOptions = LayoutOptions.FillAndExpand, IsVisible = false };
+
+ //-------------------------------------------------------------------
+ // Construction.
+ public SamPanel_ItemFilterProperty(Config config)
+ {
+ this.config = config;
+ InitialiseComponents();
+ }
+
+ //-------------------------------------------------------------------
+ // Properties.
+ public bool Initialising { get; set; } = true;
+ private SacsFilterPropertyType? SelectedProperty { get { return config.FilterPropertyTypes.Find(item => item.PropertyType == StringUtil.ToInt(pickerPropertyType.SelectedItemCode)); } }
+ public int SelectedPropertyType { get { return SelectedProperty?.PropertyType ?? -1; } }
+ public String PropertyValue
+ {
+ get
+ {
+ if (SelectedProperty?.Mode == SacsFilterPropertyType.FilterMode.DropDownList)
+ return pickerPropertyValue.SelectedItemCode;
+ return entryPropertyValue.Text;
+ }
+ }
+
+ //-------------------------------------------------------------------
+ // Configure.
+ public void SetConfig(Config config)
+ {
+ this.config = config;
+ ConfigurePanel();
+ }
+
+ //-------------------------------------------------------------------
+ // I n i t i a l i s e
+ //-------------------------------------------------------------------
+ private void InitialiseComponents()
+ {
+ // Grid - property type picker followed by property value control.
+ grid = new SamGrid()
+ {
+ BackgroundColor = Colors.Transparent,
+ RowSpacing = 0,
+ ColumnSpacing = 4,
+ RowDefinitions = new RowDefinitionCollection()
+ {
+ // Single row.
+ new RowDefinition(){ Height = config.Height }
+ },
+ ColumnDefinitions = new ColumnDefinitionCollection()
+ {
+ // Two columns - property type & value.
+ new ColumnDefinition(){ Width = config.PropertyTypeWidth > 0 ? config.PropertyTypeWidth : GridLength.Auto },
+ new ColumnDefinition(){ Width = config.PropertyValueWidth > 0 ? config.PropertyValueWidth : GridLength.Auto }
+ }
+ };
+ Content = grid;
+
+ // Property type.
+ if (config.PropertyTypeWidth > 0)
+ pickerPropertyType.WidthRequest = config.PropertyTypeWidth;
+ grid.Add(pickerPropertyType, 0, 0);
+ pickerPropertyType.SelectedIndexChanged += PickerPropertyType_SelectedIndexChanged;
+
+ // Property value.
+ if (config.PropertyValueWidth > 0)
+ entryPropertyValue.WidthRequest = config.PropertyValueWidth;
+ entryPropertyValue.TextChanged += EntryPropertyValue_TextChanged;
+ grid.Add(entryPropertyValue, 1, 0);
+ if (config.PropertyValueWidth > 0)
+ pickerPropertyValue.WidthRequest = config.PropertyValueWidth;
+ pickerPropertyValue.SelectedIndexChanged += PickerPropertyValue_SelectedIndexChanged;
+ grid.Add(pickerPropertyValue, 1, 0);
+
+ ConfigurePanel();
+ }
+
+ //-------------------------------------------------------------------
+ // C o n f i g u r e P a n e l
+ //-------------------------------------------------------------------
+ private void ConfigurePanel()
+ {
+ Initialising = true;
+
+ // Configure layout.
+ if (grid != null)
+ {
+ grid.ColumnDefinitions.Clear();
+ grid.ColumnDefinitions.Add(new ColumnDefinition() { Width = config.PropertyTypeWidth });
+ grid.ColumnDefinitions.Add(new ColumnDefinition() { Width = config.PropertyValueWidth });
+ }
+
+ // Make a note of the currently selected property type.
+ int selectedPropertyType = SelectedPropertyType;
+ pickerPropertyType.ClearItems();
+
+ // Populate list of property types.
+ foreach (SacsFilterPropertyType propertyType in config.FilterPropertyTypes)
+ {
+ pickerPropertyType.AddItem(propertyType.PropertyType.ToString(), propertyType.Caption);
+ }
+ pickerPropertyType.SelectedItemCode = selectedPropertyType.ToString();
+ if (pickerPropertyType.SelectedIndex < 0 && pickerPropertyType.Items.Count > 0)
+ pickerPropertyType.SelectedIndex = 0;
+
+ Initialising = false;
+ }
+
+ //-------------------------------------------------------------------
+ // P r o p e r t y F i l t e r E v e n t s
+ //-------------------------------------------------------------------
+ public class PropertyFilterChangedEventArgs : EventArgs
+ {
+ public int PropertyType { get; set; } = -1;
+ }
+ public event EventHandler? PropertyFilterChanged;
+ private void FireFilterChangedEvent(int propertyType)
+ {
+ if (Initialising) return;
+ PropertyFilterChangedEventArgs filterChangedEventArgs = new PropertyFilterChangedEventArgs()
+ {
+ PropertyType = propertyType,
+ };
+ if (PropertyFilterChanged != null) PropertyFilterChanged.Invoke(this, filterChangedEventArgs);
+ }
+
+ //-------------------------------------------------------------------
+ private void PickerPropertyType_SelectedIndexChanged(object? sender, EventArgs e)
+ {
+ FireFilterChangedEvent(SelectedPropertyType);
+ }
+ private void EntryPropertyValue_TextChanged(object? sender, TextChangedEventArgs e)
+ {
+ FireFilterChangedEvent(SelectedPropertyType);
+ }
+ private void PickerPropertyValue_SelectedIndexChanged(object? sender, EventArgs e)
+ {
+ FireFilterChangedEvent(SelectedPropertyType);
+ }
+
+ //-------------------------------------------------------------------
+ }
+ //=======================================================================
+}
diff --git a/SacsMobileMaui/Maui/Panels/SamPanel_ItemListHeader.cs b/SacsMobileMaui/Maui/Panels/SamPanel_ItemListHeader.cs
new file mode 100644
index 0000000..a3083ae
--- /dev/null
+++ b/SacsMobileMaui/Maui/Panels/SamPanel_ItemListHeader.cs
@@ -0,0 +1,286 @@
+using Sarand.Net.Mobile.Maui.Controls;
+using Sarand.Net.Mobile.Maui.Layouts;
+using Sarand.Net.Mobile.Maui.Utils;
+using Sarand.Net.Mobile.Maui.Views;
+using Sarand.Net.Standard.Base.Dbas;
+using Sarand.Net.Standard.Base.Helpers;
+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.Panels
+{
+ //=======================================================================
+ public class SamPanel_ItemListHeader : SamPanel
+ {
+ //-------------------------------------------------------------------
+ // Definitions.
+ public class Config
+ {
+ public Thickness? Margin { get; set; } = new Thickness(0, 0, 0, 8);
+ public String Caption { get; set; } = "";
+ public double HeaderBorderWidth { get; set; } = 4.0;
+ public float HeaderBorderRadius { get; set; } = 8.0f;
+ public Color HeaderBorderColor { get; set; } = Color.FromArgb("#808080");
+ public Color HeaderBackgroundColor { get; set; } = Color.FromArgb("#F0F0F0");
+ // Labels.
+ // Styling.
+ public double CaptionLabelFontSizeRatio { get; set; } = 1.0;
+ public double PagingLabelFontSizeRatio { get; set; } = 1.0;
+ public Thickness? LabelMargin { get; set; } = null;
+ // Paging controls.
+ public static int PagingButtonWidthMin { get; } = 40;
+ public static int PagingButtonWidthMax { get; } = 64;
+ public static int PagingButtonWidthDefault { get; } = PagingButtonWidthMin;
+ public int PagingButtonWidth { get; set; } = PagingButtonWidthDefault;
+ public static int PagingButtonHeightMin { get; } = 40;
+ public static int PagingButtonHeightMax { get; } = 64;
+ public static int PagingButtonHeightDefault { get; } = PagingButtonHeightMin;
+ public int PagingButtonHeight { get; set; } = PagingButtonHeightDefault;
+ // Filters.
+ public List FilterPropertyTypes { get; } = new List();
+ public ushort FiltersToDisplay { get; set; } = 2;
+ }
+
+ //-------------------------------------------------------------------
+ // Data members.
+ private Config config;
+ // Layout.
+ private SamFrame frame = new SamFrame();
+ private SamStackLayout mainLayout = new SamStackLayout() { Orientation = StackOrientation.Vertical };
+ // Header.
+ private SamWrapLayout headerLayout = new SamWrapLayout() { SpacingX = 8.0 };
+ private SamContentView captionLabelView = new SamContentView();
+ private SamLabel labelCaption = new SamLabel() { Text = "" };
+ private double initialLabelFontSize = 0;
+ // Paging.
+ private class PagingData : DbQuery.Paging
+ {
+ public int TotalItems { get; set; }
+ public int TotalPages { get { return PageLength > 0 ? (int)Math.Ceiling((double)TotalItems / PageLength) : 0; } }
+ public override bool Paged { get { return base.Paged && TotalItems > 0; } }
+ }
+ private readonly PagingData paging = new PagingData();
+ private SamStackLayout pagingStack = new SamStackLayout() { Orientation = StackOrientation.Horizontal };
+ private SamLabel labelPaging = new SamLabel() { Text = "[Paging]" };
+ private SamImageButton buttonPagingFirst = new SamImageButton() { WidthRequest = 32, HeightRequest = 32, Aspect = Aspect.AspectFit, Padding = new Thickness(2, 4), BackgroundColor = Colors.Transparent };
+ private SamImageButton buttonPagingPrev = new SamImageButton() { WidthRequest = 32, HeightRequest = 32, Aspect = Aspect.AspectFit, Padding = new Thickness(2, 4), BackgroundColor = Colors.Transparent };
+ private SamImageButton buttonPagingNext = new SamImageButton() { WidthRequest = 32, HeightRequest = 32, Aspect = Aspect.AspectFit, Padding = new Thickness(2, 4), BackgroundColor = Colors.Transparent };
+ private SamImageButton buttonPagingLast = new SamImageButton() { WidthRequest = 32, HeightRequest = 32, Aspect = Aspect.AspectFit, Padding = new Thickness(2, 4), BackgroundColor = Colors.Transparent };
+
+ //-------------------------------------------------------------------
+ // Construction.
+ public SamPanel_ItemListHeader(Config config)
+ {
+ this.config = config;
+ initialLabelFontSize = labelCaption.FontSize;
+ InitialiseComponents();
+ }
+
+ //-------------------------------------------------------------------
+ // Properties.
+ // Filter.
+ public SamPanel_ItemFilter? PanelFilter { get; private set; }
+ // Paging.
+ public uint PageNo { get { return paging.PageNo; } }
+ public ushort PageLength { get { return paging.PageLength; } }
+ public int TotalItems { get { return paging.TotalItems; } }
+
+ //-------------------------------------------------------------------
+ // I n i t i a l i s e
+ //-------------------------------------------------------------------
+ private void InitialiseComponents()
+ {
+ // Frame - to allow rounded corners.
+ Content = frame;
+ frame.Content = mainLayout;
+
+ // Header.
+ mainLayout.Spacing = 0;
+ mainLayout.HorizontalOptions = LayoutOptions.StartAndExpand;
+ mainLayout.BackgroundColor = Colors.Transparent;
+ //headerLayout.BackgroundColor = Color.LightYellow;
+ mainLayout.Children.Add(headerLayout);
+
+ // Header - Caption.
+ captionLabelView.VerticalOptions = LayoutOptions.Center;
+ labelCaption.FontAttributes = FontAttributes.Bold;
+ labelCaption.HorizontalOptions = LayoutOptions.StartAndExpand;
+ labelCaption.VerticalOptions = LayoutOptions.CenterAndExpand;
+ labelCaption.LineBreakMode = LineBreakMode.NoWrap;
+ labelCaption.PropertyChanging += LabelCaption_PropertyChanging;
+ // Label is embedded in view to ensure correct sizing.
+ captionLabelView.Content = labelCaption;
+ headerLayout.Children.Add(captionLabelView);
+
+ // Header - Paging.
+ pagingStack.HorizontalOptions = LayoutOptions.EndAndExpand;
+ pagingStack.VerticalOptions = LayoutOptions.CenterAndExpand;
+ headerLayout.Children.Add(pagingStack);
+ labelPaging.VerticalOptions = LayoutOptions.CenterAndExpand;
+ pagingStack.Children.Add(labelPaging);
+ pagingStack.Children.Add(buttonPagingFirst);
+ pagingStack.Children.Add(buttonPagingPrev);
+ pagingStack.Children.Add(buttonPagingNext);
+ pagingStack.Children.Add(buttonPagingLast);
+
+ // Paging actions.
+ buttonPagingFirst.Clicked += ButtonPagingFirst_Clicked;
+ buttonPagingPrev.Clicked += ButtonPagingPrev_Clicked;
+ buttonPagingNext.Clicked += ButtonPagingNext_Clicked;
+ buttonPagingLast.Clicked += ButtonPagingLast_Clicked;
+
+ // Complete panel configuration.
+ ConfigurePanel();
+ }
+
+ private void LabelCaption_PropertyChanging(object? sender, PropertyChangingEventArgs e)
+ {
+ if (e.PropertyName.EqualsCI("Height"))
+ {
+ }
+ }
+
+ //-------------------------------------------------------------------
+ // C o n f i g u r e P a n e l
+ //-------------------------------------------------------------------
+ private void ConfigurePanel()
+ {
+ if (config.Margin != null)
+ frame.Margin = (Thickness)config.Margin;
+
+ // Header.
+ labelCaption.Text = StringUtil.EmptyOnNull(config.Caption);
+ frame.Padding = new Thickness(config.HeaderBorderWidth);
+ frame.CornerRadius = config.HeaderBorderRadius;
+ frame.BorderColor = config.HeaderBorderColor;
+ frame.BackgroundColor = config.HeaderBackgroundColor;
+
+ // Labels.
+ labelCaption.FontSize = initialLabelFontSize * config.CaptionLabelFontSizeRatio;
+ labelPaging.FontSize = initialLabelFontSize * config.PagingLabelFontSizeRatio;
+ if (config.LabelMargin != null)
+ {
+ labelCaption.Margin = (Thickness)config.LabelMargin;
+ labelPaging.Margin = (Thickness)config.LabelMargin;
+ }
+
+ // Paging controls.
+ buttonPagingFirst.Source = SamImageUtil.GetImageFromByteArray(SacsResourceHelper.GetResourceImage(SacsResourceHelper.ImageResource.Arrow_Left_Double_Black_24_png));
+ buttonPagingFirst.WidthRequest = Math.Max(Config.PagingButtonWidthMin, Math.Min(Config.PagingButtonWidthMax, config.PagingButtonWidth));
+ buttonPagingFirst.HeightRequest = Math.Max(Config.PagingButtonHeightMin, Math.Min(Config.PagingButtonHeightMax, config.PagingButtonHeight));
+ buttonPagingPrev.Source = SamImageUtil.GetImageFromByteArray(SacsResourceHelper.GetResourceImage(SacsResourceHelper.ImageResource.Arrow_Left_Black_24_png));
+ buttonPagingPrev.WidthRequest = Math.Max(Config.PagingButtonWidthMin, Math.Min(Config.PagingButtonWidthMax, config.PagingButtonWidth));
+ buttonPagingPrev.HeightRequest = Math.Max(Config.PagingButtonHeightMin, Math.Min(Config.PagingButtonHeightMax, config.PagingButtonHeight));
+ buttonPagingNext.Source = SamImageUtil.GetImageFromByteArray(SacsResourceHelper.GetResourceImage(SacsResourceHelper.ImageResource.Arrow_Right_Black_24_png));
+ buttonPagingNext.WidthRequest = Math.Max(Config.PagingButtonWidthMin, Math.Min(Config.PagingButtonWidthMax, config.PagingButtonWidth));
+ buttonPagingNext.HeightRequest = Math.Max(Config.PagingButtonHeightMin, Math.Min(Config.PagingButtonHeightMax, config.PagingButtonHeight));
+ buttonPagingLast.Source = SamImageUtil.GetImageFromByteArray(SacsResourceHelper.GetResourceImage(SacsResourceHelper.ImageResource.Arrow_Right_Double_Black_24_png));
+ buttonPagingLast.WidthRequest = Math.Max(Config.PagingButtonWidthMin, Math.Min(Config.PagingButtonWidthMax, config.PagingButtonWidth));
+ buttonPagingLast.HeightRequest = Math.Max(Config.PagingButtonHeightMin, Math.Min(Config.PagingButtonHeightMax, config.PagingButtonHeight));
+ RefreshPaging();
+
+ // Property type filters.
+ SamPanel_ItemFilter.Config propertyFilterConfig = new SamPanel_ItemFilter.Config();
+ propertyFilterConfig.FilterPropertyTypes.AddRange(config.FilterPropertyTypes);
+ propertyFilterConfig.FiltersToDisplay = config.FiltersToDisplay;
+ if (PanelFilter == null)
+ {
+ PanelFilter = new SamPanel_ItemFilter(propertyFilterConfig);
+ mainLayout.Children.Add(PanelFilter);
+ }
+ else
+ {
+ PanelFilter.SetConfig(propertyFilterConfig);
+ }
+ }
+
+ //-------------------------------------------------------------------
+ // P a g i n g
+ //-------------------------------------------------------------------
+ public void SetPaging(uint pageNo, ushort pageLength, int totalItems)
+ {
+ paging.PageNo = pageNo;
+ paging.PageLength = pageLength;
+ paging.TotalItems = totalItems;
+ RefreshPaging();
+ }
+ private void RefreshPaging()
+ {
+ String pagingText = "";
+ Boolean paged = (paging.Paged && paging.TotalPages > 1);
+ if (paging.TotalItems > 0)
+ {
+ if (paged)
+ pagingText = "Page " + paging.PageNo + " of " + paging.TotalPages;
+ else
+ pagingText = paging.TotalItems + " items";
+ }
+ labelPaging.Text = pagingText;
+ // Control visibility.
+ labelPaging.IsVisible = !StringUtil.IsEmpty(labelPaging.Text);
+ buttonPagingFirst.IsVisible = paged;
+ buttonPagingPrev.IsVisible = paged;
+ buttonPagingNext.IsVisible = paged;
+ buttonPagingLast.IsVisible = paged;
+ // Button activity.
+ buttonPagingFirst.IsEnabled = paging.PageNo > 1;
+ buttonPagingPrev.IsEnabled = paging.PageNo > 1;
+ buttonPagingNext.IsEnabled = paging.PageNo < paging.TotalPages;
+ buttonPagingLast.IsEnabled = paging.PageNo < paging.TotalPages;
+ }
+
+ //-------------------------------------------------------------------
+ // Paging events.
+ public enum PagingActionType
+ {
+ Unknown,
+ First,
+ Next,
+ Prev,
+ Last
+ }
+ public class PagingActionEventArgs : EventArgs
+ {
+ public PagingActionType PagingActionType { get; set; } = PagingActionType.Unknown;
+ }
+ public event EventHandler? PagingAction;
+ private void FirePagingActionEvent(PagingActionType pagingActionType)
+ {
+ PagingActionEventArgs filterChangedEventArgs = new PagingActionEventArgs()
+ {
+ PagingActionType = pagingActionType,
+ };
+ if (PagingAction != null) PagingAction.Invoke(this, filterChangedEventArgs);
+ }
+
+ //-------------------------------------------------------------------
+ // Paging actions.
+ private void ButtonPagingFirst_Clicked(object? sender, EventArgs e)
+ {
+ FirePagingActionEvent(PagingActionType.First);
+ }
+ private void ButtonPagingPrev_Clicked(object? sender, EventArgs e)
+ {
+ FirePagingActionEvent(PagingActionType.Prev);
+ }
+ private void ButtonPagingNext_Clicked(object? sender, EventArgs e)
+ {
+ FirePagingActionEvent(PagingActionType.Next);
+ }
+ private void ButtonPagingLast_Clicked(object? sender, EventArgs e)
+ {
+ FirePagingActionEvent(PagingActionType.Last);
+ }
+
+ //-------------------------------------------------------------------
+ // P a n e l E v e n t s
+ //-------------------------------------------------------------------
+
+ //-------------------------------------------------------------------
+ }
+ //=======================================================================
+}
diff --git a/SacsMobileMaui/Maui/Panels/SamPanel_ItemListSummary.cs b/SacsMobileMaui/Maui/Panels/SamPanel_ItemListSummary.cs
new file mode 100644
index 0000000..ffb4111
--- /dev/null
+++ b/SacsMobileMaui/Maui/Panels/SamPanel_ItemListSummary.cs
@@ -0,0 +1,128 @@
+using Sarand.Net.Mobile.Maui.Controls;
+using Sarand.Net.Mobile.Maui.Layouts;
+using Sarand.Net.Mobile.Maui.Utils;
+using Sarand.Net.Mobile.Maui.Views;
+using Sarand.Net.Standard.Base.Core;
+using Sarand.Net.Standard.Base.Helpers;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Sarand.Net.Mobile.Maui.Panels
+{
+ //=======================================================================
+ ///
+ /// Panel to display with item list where no filter required, eg. items associated with parent.
+ /// - Message shown to left
+ /// - Buttons shown to right
+ ///
+ public class SamPanel_ItemListSummary : SamContentView
+ {
+ //-------------------------------------------------------------------
+ // Definitions.
+
+ //-------------------------------------------------------------------
+ // Data members.
+ private SacsMessage? message;
+ private bool showAddButton = false;
+ private double actionButtonSpacing = 4.0;
+ // Panel layout.
+ private SamStackLayout panelLayout = new SamStackLayout() { Orientation = StackOrientation.Horizontal };
+ private SamContentView panelMessage = new SamContentView();
+ private SamLabel labelMessage = new SamLabel() { HorizontalOptions = LayoutOptions.Start };
+ // Action buttons.
+ private SamStackLayout panelActions = new SamStackLayout() { Orientation = StackOrientation.Horizontal };
+ private SamImageButton buttonAddItem = new SamImageButton() { IsVisible = false, HorizontalOptions = LayoutOptions.End, VerticalOptions = LayoutOptions.Start };
+
+ //-------------------------------------------------------------------
+ // Construction.
+ public SamPanel_ItemListSummary()
+ {
+ // Panel layout.
+ panelLayout.HorizontalOptions = LayoutOptions.Fill;
+ Content = panelLayout;
+
+ // Message.
+ panelMessage.HorizontalOptions = LayoutOptions.Fill;
+ panelMessage.VerticalOptions = LayoutOptions.Fill;
+ //panelMessage.BackgroundColor = Color.LightGoldenrodYellow;
+ panelLayout.Children.Add(panelMessage);
+ labelMessage.HorizontalOptions = LayoutOptions.Center;
+ labelMessage.VerticalOptions = LayoutOptions.Center;
+ panelMessage.Content = labelMessage;
+
+ // Action buttons.
+ panelActions.HorizontalOptions = LayoutOptions.EndAndExpand;
+ panelLayout.Children.Add(panelActions);
+
+ // 'Add' button.
+ buttonAddItem.IsVisible = showAddButton;
+ buttonAddItem.Padding = new Thickness(0, 0, actionButtonSpacing, 0);
+ buttonAddItem.BackgroundColor = Colors.Transparent;
+ buttonAddItem.Source = SamImageUtil.GetImageFromByteArray(SacsResourceHelper.GetResourceImage(SacsResourceHelper.ImageResource.IconShock_Add_Green_72_png));
+ buttonAddItem.Clicked += ButtonAddItem_Clicked;
+ panelActions.Children.Add(buttonAddItem);
+ }
+
+ //-------------------------------------------------------------------
+ // Properties.
+ // Message.
+ public double MessageFontSizeDefault { get { return labelMessage.FontSizeDefault; } }
+ public double MessageFontSize
+ {
+ get { return labelMessage.FontSize; }
+ set { labelMessage.FontSize = value; }
+ }
+ // Action buttons.
+ public double ActionButtonSpacing
+ {
+ get { return actionButtonSpacing; }
+ set
+ {
+ if (actionButtonSpacing == value) return;
+ buttonAddItem.Padding = new Thickness(0, 0, actionButtonSpacing, 0);
+ }
+ }
+ public bool ShowAddButton
+ {
+ get { return showAddButton; }
+ set { if (showAddButton == value) return; showAddButton = value; buttonAddItem.IsVisible = showAddButton; }
+ }
+
+ //-------------------------------------------------------------------
+ // M e s s a g e
+ //-------------------------------------------------------------------
+ public void ClearMessage()
+ {
+ message = null;
+ labelMessage.Text = "";
+ }
+ public void SetMessage(String message, SaMessageLevel messageLevel = SaMessageLevel.Info)
+ {
+ SetMessage(new SacsMessage(message, messageLevel));
+ }
+ public void SetMessage(SacsMessage message)
+ {
+ this.message = message;
+ labelMessage.Text = (message != null ? message.Message : "");
+ }
+
+ //-------------------------------------------------------------------
+ // A c t i o n B u t t o n s
+ //-------------------------------------------------------------------
+
+ //-------------------------------------------------------------------
+ // P a n e l E v e n t s
+ //-------------------------------------------------------------------
+ public event EventHandler? AddClicked;
+ protected virtual void ButtonAddItem_Clicked(object? sender, EventArgs e)
+ {
+ if (AddClicked != null) AddClicked.Invoke(sender, e);
+ }
+
+ //-------------------------------------------------------------------
+ }
+ //=======================================================================
+}
diff --git a/SacsMobileMaui/Maui/Panels/SamPanel_Messages.cs b/SacsMobileMaui/Maui/Panels/SamPanel_Messages.cs
new file mode 100644
index 0000000..99c1985
--- /dev/null
+++ b/SacsMobileMaui/Maui/Panels/SamPanel_Messages.cs
@@ -0,0 +1,77 @@
+using Sarand.Net.Mobile.Maui.Controls;
+using Sarand.Net.Mobile.Maui.Layouts;
+using Sarand.Net.Standard.Base.Core;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Sarand.Net.Mobile.Maui.Panels
+{
+ //=======================================================================
+ public class SamPanel_Messages : SamPanel
+ {
+ //-------------------------------------------------------------------
+ // Definitions.
+ public class Config
+ {
+ }
+
+ //-------------------------------------------------------------------
+ // Data members.
+ private Config config;
+ private readonly List messages = new List();
+ // Components.
+ private SamStackLayout mainLayout = new SamStackLayout() { Orientation = StackOrientation.Vertical };
+
+ //-------------------------------------------------------------------
+ // Construction.
+ public SamPanel_Messages(Config config)
+ {
+ this.config = config;
+ Content = mainLayout;
+ }
+
+ //-------------------------------------------------------------------
+ // Properties.
+
+ //-------------------------------------------------------------------
+ // M e s s a g e s
+ //-------------------------------------------------------------------
+ public void ClearMessages()
+ {
+ messages.Clear();
+ RefreshLayout();
+ }
+ public void AddMessage(SacsMessage message)
+ {
+ messages.Add(message);
+ RefreshLayout();
+ }
+
+ //-------------------------------------------------------------------
+ // R e f r e s h L a y o u t
+ //-------------------------------------------------------------------
+ private void RefreshLayout()
+ {
+ // Reset layout.
+ IsVisible = messages.Count > 0;
+ mainLayout.Children.Clear();
+
+ foreach (SacsMessage message in messages)
+ {
+ SamPanel messagePanel = new SamPanel();
+ messagePanel.Content = new SamLabel() { Text = message.Message };
+ mainLayout.Children.Add(messagePanel);
+ }
+ }
+
+ //-------------------------------------------------------------------
+ // P a n e l E v e n t s
+ //-------------------------------------------------------------------
+
+ //-------------------------------------------------------------------
+ }
+ //=======================================================================
+}
diff --git a/SacsMobileMaui/Maui/Panels/SamPanel_Page_Footer.cs b/SacsMobileMaui/Maui/Panels/SamPanel_Page_Footer.cs
new file mode 100644
index 0000000..fced510
--- /dev/null
+++ b/SacsMobileMaui/Maui/Panels/SamPanel_Page_Footer.cs
@@ -0,0 +1,135 @@
+using Sarand.Net.Mobile.Maui.App;
+using Sarand.Net.Mobile.Maui.Controls;
+using Sarand.Net.Mobile.Maui.Layouts;
+using Sarand.Net.Mobile.Maui.Utils;
+using Sarand.Net.Mobile.Maui.Views;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Sarand.Net.Mobile.Maui.Panels
+{
+ //=======================================================================
+ public class SamPanel_Page_Footer : SamPanel
+ {
+ //-------------------------------------------------------------------
+ // Definitions.
+
+ //-------------------------------------------------------------------
+ // Data members.
+ // Layout.
+ private VerticalStackLayout panelFooter = new VerticalStackLayout() { BackgroundColor = Sam.Visuals.Page.Footer.BackgroundColor };
+ private SamBoxView panelBorderTop = new SamBoxView() { BackgroundColor = Sam.Visuals.Page.Footer.BorderColor, HeightRequest = Sam.Visuals.Page.Footer.BorderStrokeWidthTop };
+ private SamBoxView panelBorderBottom = new SamBoxView() { BackgroundColor = Sam.Visuals.Page.Footer.BorderColor, HeightRequest = Sam.Visuals.Page.Footer.BorderStrokeWidthBottom };
+ // Grid.
+ private SamGrid gridFooter = new SamGrid() { Padding = Sam.Visuals.Page.Footer.Padding };
+ private ColumnDefinition colLeft = new ColumnDefinition(GridLength.Star);
+ private ColumnDefinition colMiddle = new ColumnDefinition(GridLength.Auto);
+ private ColumnDefinition colRight = new ColumnDefinition(GridLength.Star);
+ // Panels.
+ private VerticalStackLayout panelLeft = new VerticalStackLayout() { HorizontalOptions = LayoutOptions.Start };
+ private VerticalStackLayout panelMiddle = new VerticalStackLayout() { HorizontalOptions = LayoutOptions.Center };
+ private VerticalStackLayout panelRight = new VerticalStackLayout() { HorizontalOptions = LayoutOptions.End };
+ // Date-time display.
+ private String dateTimeFormat = "ddd d-MMM-yyyy HH:mm:ss";
+ private IDispatcherTimer? timerDateTime;
+ private Sam.DisplayPosition dateTimeDisplay = Sam.Visuals.Page.Footer.DateTimeDisplay;
+ private SamLabel labelDateTime = new SamLabel() { FontSize = 12.0, HorizontalTextAlignment = TextAlignment.Center };
+
+ //-------------------------------------------------------------------
+ // Construction.
+ public SamPanel_Page_Footer()
+ {
+ // Footer panel.
+ Content = panelFooter;
+
+ // Footer border top.
+ panelFooter.Children.Add(panelBorderTop);
+
+ // Footer grid.
+ gridFooter.ColumnDefinitions.Add(colLeft);
+ gridFooter.ColumnDefinitions.Add(colMiddle);
+ gridFooter.ColumnDefinitions.Add(colRight);
+ gridFooter.Add(panelLeft, 0, 0);
+ gridFooter.Add(panelMiddle, 1, 0);
+ gridFooter.Add(panelRight, 2, 0);
+ panelFooter.Children.Add(gridFooter);
+
+ // Footer border bottom.
+ panelFooter.Children.Add(panelBorderBottom);
+
+ RefreshComponents();
+ }
+
+ //-------------------------------------------------------------------
+ // Properties.
+ public Sam.DisplayPosition DateTimeDisplay
+ {
+ get { return dateTimeDisplay; }
+ set { dateTimeDisplay = value; RefreshComponents(); }
+ }
+ public String DateTimeFormat
+ {
+ get { return dateTimeFormat; }
+ set { dateTimeFormat = value; RefreshComponents(); }
+ }
+
+ //-------------------------------------------------------------------
+ // Events.
+ protected override void OnParentChanged()
+ {
+ base.OnParentChanged();
+ if (Parent == null)
+ ClearDateTimeTimer();
+ }
+
+ //-------------------------------------------------------------------
+ // DateTime display.
+ private void ClearDateTimeTimer()
+ {
+ if (timerDateTime != null && timerDateTime.IsRunning)
+ timerDateTime.Stop();
+ }
+
+ private void TimerDateTime_Tick(object? sender, EventArgs e)
+ {
+ String dateTimeValue = DateTime.Now.ToString(dateTimeFormat);
+ if (!labelDateTime.Text.Equals(dateTimeValue))
+ labelDateTime.Text = dateTimeValue;
+ }
+
+ //-------------------------------------------------------------------
+ private void RefreshComponents()
+ {
+ bool contentDisplayed = false;
+
+ // Date time.
+ ClearDateTimeTimer();
+ panelLeft.Children.Remove(labelDateTime);
+ panelMiddle.Children.Remove(labelDateTime);
+ panelRight.Children.Remove(labelDateTime);
+ if (dateTimeDisplay == Sam.DisplayPosition.Left)
+ panelLeft.Children.Add(labelDateTime);
+ else if (dateTimeDisplay == Sam.DisplayPosition.Middle)
+ panelMiddle.Children.Add(labelDateTime);
+ else if (dateTimeDisplay == Sam.DisplayPosition.Right)
+ panelRight.Children.Add(labelDateTime);
+ if (dateTimeDisplay != Sam.DisplayPosition.None)
+ {
+ contentDisplayed = true;
+ timerDateTime = SamTimerUtil.DoTimer(this, TimeSpan.FromMilliseconds(250), true, TimerDateTime_Tick);
+ timerDateTime.Start();
+ }
+
+ // Hide panel if nothing displayed.
+ if (contentDisplayed)
+ IsVisible = true;
+ else IsVisible = false;
+ }
+
+ //-------------------------------------------------------------------
+ }
+ //=======================================================================
+}
diff --git a/SacsMobileMaui/Maui/Panels/SamPanel_Page_Header.cs b/SacsMobileMaui/Maui/Panels/SamPanel_Page_Header.cs
new file mode 100644
index 0000000..9cc4c8b
--- /dev/null
+++ b/SacsMobileMaui/Maui/Panels/SamPanel_Page_Header.cs
@@ -0,0 +1,205 @@
+using Microsoft.Maui.Controls.Internals;
+using Sarand.Net.Mobile.Maui.App;
+using Sarand.Net.Mobile.Maui.Controls;
+using Sarand.Net.Mobile.Maui.Layouts;
+using Sarand.Net.Mobile.Maui.Utils;
+using Sarand.Net.Mobile.Maui.Views;
+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.Panels
+{
+ //=======================================================================
+ public class SamPanel_Page_Header : SamPanel
+ {
+ //-------------------------------------------------------------------
+ // Definitions.
+
+ //-------------------------------------------------------------------
+ // Data members.
+ // Layout.
+ private SamBoxView panelBorderTop = new SamBoxView() { BackgroundColor = Sam.Visuals.Page.Header.BorderColor, HeightRequest = Sam.Visuals.Page.Header.BorderStrokeWidthTop };
+ private VerticalStackLayout panelHeader = new VerticalStackLayout() { BackgroundColor = Sam.Visuals.Page.Header.BackgroundColor };
+ private SamBoxView panelBorderBottom = new SamBoxView() { BackgroundColor = Sam.Visuals.Page.Header.BorderColor, HeightRequest = Sam.Visuals.Page.Header.BorderStrokeWidthBottom };
+ // Grid.
+ private SamGrid gridHeader = new SamGrid() { Padding = Sam.Visuals.Page.Header.Padding };
+ private ColumnDefinition colLeft = new ColumnDefinition(GridLength.Star);
+ private ColumnDefinition colMiddle = new ColumnDefinition(GridLength.Auto);
+ private ColumnDefinition colRight = new ColumnDefinition(GridLength.Star);
+ // Panels.
+ private VerticalStackLayout panelLeft = new VerticalStackLayout() { HorizontalOptions = LayoutOptions.Start };
+ private VerticalStackLayout panelMiddle = new VerticalStackLayout() { HorizontalOptions = LayoutOptions.Center };
+ private VerticalStackLayout panelRight = new VerticalStackLayout() { HorizontalOptions = LayoutOptions.End };
+ // Caption.
+ private SamLabel labelCaption = new SamLabel() { HorizontalOptions = LayoutOptions.Center };
+ private Sam.DisplayPosition captionDisplay = Sam.DisplayPosition.Middle;
+ // Text labels.
+ private SamLabel labelLeft = new SamLabel() { HorizontalOptions = LayoutOptions.Start };
+ private SamLabel labelRight= new SamLabel() { HorizontalOptions = LayoutOptions.End };
+ // Toolbar.
+ private Sam.DisplayPosition toolbarDisplay = Sam.Visuals.Page.Header.ToolbarDisplay;
+ private HorizontalStackLayout panelToolbar = new HorizontalStackLayout() { };
+ private bool display_ToolbarItem_Back = Sam.Visuals.Page.Header.Display_ToolbarItem_Back;
+ private Button toolbarItem_Back = new Button() { WidthRequest = 16, HeightRequest = 16, Text="<", Padding=new Thickness(0), BorderWidth = 0.0, BorderColor = Colors.DarkOliveGreen };
+
+ //-------------------------------------------------------------------
+ // Construction.
+ public SamPanel_Page_Header()
+ {
+ InitialiseComponents();
+ }
+
+ //-------------------------------------------------------------------
+ // Properties.
+ public String Caption
+ {
+ get { return labelCaption.Text; }
+ set { labelCaption.Text = value; ConfigureComponents(); }
+ }
+ public Sam.DisplayPosition CaptionDisplay
+ {
+ get { return captionDisplay; }
+ set { captionDisplay = value; ConfigureComponents(); }
+ }
+
+ // Text labels.
+ public String TextLeft
+ {
+ get { return labelLeft.Text; }
+ set { labelLeft.Text = value; ConfigureComponents(); }
+ }
+ public String TextRight
+ {
+ get { return labelRight.Text; }
+ set { labelRight.Text = value; ConfigureComponents(); }
+ }
+
+ // Toolbar.
+ public Sam.DisplayPosition ToolbarDisplay
+ {
+ get { return toolbarDisplay; }
+ set { toolbarDisplay = value; ConfigureComponents(); }
+ }
+ public bool Display_ToolbarItem_Back
+ {
+ get { return display_ToolbarItem_Back; }
+ set { display_ToolbarItem_Back = value; ConfigureComponents(); }
+ }
+
+ //-------------------------------------------------------------------
+ // Components.
+
+ private void InitialiseComponents()
+ {
+ // Header panel.
+ Content = panelHeader;
+
+ // Top border.
+ panelBorderTop.Margin = new Thickness(0, 0, 0, Sam.Visuals.Page.Header.Padding.Top);
+ panelHeader.Children.Add(panelBorderTop);
+
+ // Header grid.
+ gridHeader.Margin = new Thickness(Sam.Visuals.Page.Header.Padding.Left, 0, Sam.Visuals.Page.Header.Padding.Right, 0);
+ gridHeader.ColumnDefinitions.Add(colLeft);
+ gridHeader.ColumnDefinitions.Add(colMiddle);
+ gridHeader.ColumnDefinitions.Add(colRight);
+ gridHeader.Add(panelLeft, 0, 0);
+ gridHeader.Add(panelMiddle, 1, 0);
+ gridHeader.Add(panelRight, 2, 0);
+ panelHeader.Children.Add(gridHeader);
+
+ // Bottom border.
+ panelBorderBottom.Margin = new Thickness(0, Sam.Visuals.Page.Header.Padding.Bottom, 0, 0);
+ panelHeader.Children.Add(panelBorderBottom);
+
+ // Configure standard toolbar icons.
+ toolbarItem_Back.Clicked += ToolbarItem_Back_Clicked;
+ }
+
+ public override void ConfigureComponents()
+ {
+ base.ConfigureComponents();
+ bool contentDisplayed = false;
+
+ // Caption.
+ panelLeft.Children.Remove(labelCaption);
+ panelMiddle.Children.Remove(labelCaption);
+ panelRight.Children.Remove(labelCaption);
+ if (captionDisplay != Sam.DisplayPosition.None && !StringUtil.IsBlank(labelCaption.Text))
+ {
+ contentDisplayed = true;
+ if (captionDisplay == Sam.DisplayPosition.Left)
+ panelLeft.Children.Add(labelCaption);
+ else if (captionDisplay == Sam.DisplayPosition.Middle)
+ panelMiddle.Children.Add(labelCaption);
+ else if (captionDisplay == Sam.DisplayPosition.Right)
+ panelRight.Children.Add(labelCaption);
+ }
+
+ // Text labels.
+ panelLeft.Children.Remove(labelLeft);
+ if (!StringUtil.IsBlank(labelLeft.Text))
+ {
+ contentDisplayed = true;
+ panelLeft.Children.Add(labelLeft);
+ }
+ panelRight.Children.Remove(labelRight);
+ if (!StringUtil.IsBlank(labelRight.Text))
+ {
+ contentDisplayed = true;
+ panelRight.Children.Add(labelRight);
+ }
+
+ // Toolbar.
+ panelLeft.Children.Remove(panelToolbar);
+ panelMiddle.Children.Remove(panelToolbar);
+ panelRight.Children.Remove(panelToolbar);
+ panelToolbar.Children.Clear();
+
+ // Toolbar icons.
+ if (display_ToolbarItem_Back)
+ panelToolbar.Children.Add(toolbarItem_Back);
+ if (panelToolbar.Children.Any() && ToolbarDisplay != Sam.DisplayPosition.None)
+ {
+ contentDisplayed = true;
+ if (ToolbarDisplay == Sam.DisplayPosition.Left)
+ panelLeft.Children.Add(panelToolbar);
+ else if (ToolbarDisplay == Sam.DisplayPosition.Middle)
+ panelMiddle.Children.Add(panelToolbar);
+ else if (ToolbarDisplay == Sam.DisplayPosition.Right)
+ panelRight.Children.Add(panelToolbar);
+ }
+
+ // Hide panel if nothing displayed.
+ if (contentDisplayed)
+ IsVisible = true;
+ else IsVisible = false;
+ }
+
+ //-------------------------------------------------------------------
+ // Toolbar actions.
+
+ private void ToolbarItem_Back_Clicked(object? sender, EventArgs e)
+ {
+ //if (Navigation.NavigationStack.Where(item => System.Object.ReferenceEquals(item, this)).Any())
+ Page? page = this.GetPage();
+ if (Navigation.NavigationStack.Any() && System.Object.ReferenceEquals(Navigation.NavigationStack.Last(), page))
+ {
+ Navigation.PopAsync();
+ return;
+ }
+ if (Navigation.ModalStack.Where(item => System.Object.ReferenceEquals(item, page)).Any())
+ {
+ Navigation.PopModalAsync();
+ return;
+ }
+ }
+
+ //-------------------------------------------------------------------
+ }
+ //=======================================================================
+}
diff --git a/SacsMobileMaui/Maui/Panels/SamPanel_Progress.cs b/SacsMobileMaui/Maui/Panels/SamPanel_Progress.cs
new file mode 100644
index 0000000..bf36767
--- /dev/null
+++ b/SacsMobileMaui/Maui/Panels/SamPanel_Progress.cs
@@ -0,0 +1,94 @@
+using Sarand.Net.Mobile.Maui.Controls;
+using Sarand.Net.Mobile.Maui.Layouts;
+using Sarand.Net.Mobile.Maui.Views;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Sarand.Net.Mobile.Maui.Panels
+{
+ //=======================================================================
+ public class SamPanel_Progress : SamContentView
+ {
+ //-------------------------------------------------------------------
+ // Definitions.
+ public class Config
+ {
+ }
+
+ //-------------------------------------------------------------------
+ // Data members.
+ private Config config;
+ // Panel layout.
+ private SamGrid gridPanel = new SamGrid();
+ private SamContentView viewPanel = new SamContentView();
+ private SamLabel labelProgressMessage = new SamLabel();
+
+ //-------------------------------------------------------------------
+ // Construction.
+ public SamPanel_Progress(Config config)
+ {
+ this.config = config;
+ InitialiseComponents();
+ }
+
+ //-------------------------------------------------------------------
+ // Properties.
+ public double ProgressMessageFontSizeDefault { get { return labelProgressMessage.FontSizeDefault; } }
+ public double ProgressMessageFontSize { get { return labelProgressMessage.FontSize; } set { labelProgressMessage.FontSize = value; } }
+ public FontAttributes ProgressMessageFontAttributes { get { return labelProgressMessage.FontAttributes; } set { labelProgressMessage.FontAttributes = value; } }
+ public Color ProgressMessageColor { get { return labelProgressMessage.TextColor; } set { labelProgressMessage.TextColor = value; } }
+ public String ProgressMessage { get { return labelProgressMessage.Text; } set { labelProgressMessage.Text = value; } }
+ public Thickness ProgressMessagePadding { get { return viewPanel.Padding; } set { viewPanel.Padding = value; } }
+ public Color ProgressMessageBackgroundColor { get { return viewPanel.BackgroundColor; } set { viewPanel.BackgroundColor = value; } }
+
+ //-------------------------------------------------------------------
+ // I n i t i a l i s e
+ //-------------------------------------------------------------------
+ private void InitialiseComponents()
+ {
+ // Layout is a 3x3 grid with only the central cell populated.
+ // All other cells are transparent/opaque.
+ gridPanel.BackgroundColor = new Color(0, 0, 0, 0.5f);
+ gridPanel.HorizontalOptions = LayoutOptions.FillAndExpand;
+ gridPanel.VerticalOptions = LayoutOptions.FillAndExpand;
+ gridPanel.ColumnDefinitions.Add(new ColumnDefinition() { Width = GridLength.Star });
+ gridPanel.ColumnDefinitions.Add(new ColumnDefinition() { Width = GridLength.Auto });
+ gridPanel.ColumnDefinitions.Add(new ColumnDefinition() { Width = GridLength.Star });
+ gridPanel.RowDefinitions.Add(new RowDefinition() { Height = GridLength.Star });
+ gridPanel.RowDefinitions.Add(new RowDefinition() { Height = GridLength.Auto });
+ gridPanel.RowDefinitions.Add(new RowDefinition() { Height = GridLength.Star });
+ Content = gridPanel;
+
+ // The panel's view.
+ viewPanel.Padding = new Thickness(12.0);
+ viewPanel.BackgroundColor = Colors.WhiteSmoke;
+ viewPanel.HorizontalOptions = LayoutOptions.Center;
+ viewPanel.VerticalOptions = LayoutOptions.Center;
+ gridPanel.Add(viewPanel, 1, 1);
+
+ // Progress message.
+ viewPanel.Content = labelProgressMessage;
+
+ // Complete panel configuration.
+ ConfigurePanel();
+ }
+
+ //-------------------------------------------------------------------
+ // C o n f i g u r e P a n e l
+ //-------------------------------------------------------------------
+ private void ConfigurePanel()
+ {
+ // Panel initialisation that depends on the configuration.
+ }
+
+ //-------------------------------------------------------------------
+ // P a n e l E v e n t s
+ //-------------------------------------------------------------------
+
+ //-------------------------------------------------------------------
+ }
+ //=======================================================================
+}
diff --git a/SacsMobileMaui/Maui/Panels/SamPanel_PropertySummary.cs b/SacsMobileMaui/Maui/Panels/SamPanel_PropertySummary.cs
new file mode 100644
index 0000000..631e691
--- /dev/null
+++ b/SacsMobileMaui/Maui/Panels/SamPanel_PropertySummary.cs
@@ -0,0 +1,141 @@
+using Sarand.Net.Mobile.Maui.Controls;
+using Sarand.Net.Mobile.Maui.Layouts;
+using Sarand.Net.Mobile.Maui.Views;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Sarand.Net.Mobile.Maui.Panels
+{
+ //=======================================================================
+ public class SamPanel_PropertySummary : SamContentView
+ {
+ //-------------------------------------------------------------------
+ // Definitions.
+ private class PropertyData
+ {
+ public String Caption { get; set; } = "";
+ public String Value { get; set; } = "";
+ }
+
+ //-------------------------------------------------------------------
+ // Data members.
+ private float internalFrameRadiusFactor = 0.8f;
+ private readonly List properties = new List();
+ // Panel components.
+ private SamFrame framePanel = new SamFrame();
+ private SamFrame frameInternal = new SamFrame();
+ private SamGrid gridProperties = new SamGrid();
+
+ //-------------------------------------------------------------------
+ // Construction.
+ public SamPanel_PropertySummary()
+ {
+ // View properties.
+ //BackgroundColor = Color.Transparent;
+ framePanel.CornerRadius = 8.0f;
+ framePanel.Padding = new Thickness(2.0);
+ framePanel.BackgroundColor = Colors.Transparent;
+ framePanel.BorderColor = framePanel.BorderColor;
+ Content = framePanel;
+
+ // Frame.
+ frameInternal.CornerRadius = framePanel.CornerRadius * internalFrameRadiusFactor;
+ frameInternal.Padding = new Thickness(0);
+ frameInternal.BorderColor = Colors.White;
+ frameInternal.BackgroundColor = Colors.White;
+ framePanel.Content = frameInternal;
+ frameInternal.Content = gridProperties;
+
+ // Properties grid.
+ gridProperties.Padding = new Thickness(4.0);
+ gridProperties.ColumnDefinitions.Add(new ColumnDefinition() { Width = GridLength.Auto });
+ gridProperties.ColumnDefinitions.Add(new ColumnDefinition() { Width = GridLength.Star });
+
+ // Property styling.
+ DefaultFontSize = new SamLabel().FontSize;
+ FontSize = DefaultFontSize;
+
+ ConfigureComponents();
+ }
+
+ //-------------------------------------------------------------------
+ // Properties.
+ public Color BorderColor
+ {
+ get { return framePanel.BorderColor; }
+ set
+ {
+ framePanel.BorderColor = value;
+ framePanel.BackgroundColor = value;
+ }
+ }
+ public Thickness BorderWidth { get { return framePanel.Padding; } set { framePanel.Padding = value; } }
+ public float CornerRadius
+ {
+ get { return framePanel.CornerRadius; }
+ set
+ {
+ framePanel.CornerRadius = value;
+ frameInternal.CornerRadius = framePanel.CornerRadius * internalFrameRadiusFactor;
+ }
+ }
+ public Color PanelBackgroundColor
+ {
+ get { return frameInternal.BackgroundColor; }
+ set
+ {
+ frameInternal.BorderColor = value;
+ frameInternal.BackgroundColor = value;
+ }
+ }
+ // Property label styling.
+ public double DefaultFontSize { get; }
+ public double FontSize { get; set; }
+
+ //-------------------------------------------------------------------
+ // S u m m a r y P r o p e r t i e s
+ //-------------------------------------------------------------------
+ public void AddProperty(String caption, String value)
+ {
+ properties.Add(new PropertyData() { Caption = caption, Value = value });
+ RefreshProperties();
+ }
+ public void ClearProperties()
+ {
+ properties.Clear();
+ RefreshProperties();
+ }
+ private void RefreshProperties()
+ {
+ // Clear existing properties.
+ gridProperties.Children.Clear();
+ gridProperties.RowDefinitions.Clear();
+
+ foreach (PropertyData property in properties)
+ {
+ int rowIdx = gridProperties.RowDefinitions.Count;
+ gridProperties.RowDefinitions.Add(new RowDefinition() { Height = GridLength.Auto });
+ gridProperties.Add(new SamLabel() { Text = property.Caption, FontSize = FontSize, HorizontalOptions = LayoutOptions.End }, 0, rowIdx);
+ gridProperties.Add(new SamLabel() { Text = property.Value, FontSize = FontSize, HorizontalOptions = LayoutOptions.StartAndExpand, VerticalOptions = LayoutOptions.StartAndExpand }, 1, rowIdx);
+ }
+ }
+
+ //-------------------------------------------------------------------
+ // C o n f i g u r e C o m p o n e n t s
+ //-------------------------------------------------------------------
+ public override void ConfigureComponents()
+ {
+ base.ConfigureComponents();
+ }
+
+ //-------------------------------------------------------------------
+ // P a n e l E v e n t s
+ //-------------------------------------------------------------------
+
+ //-------------------------------------------------------------------
+ }
+ //=======================================================================
+}
diff --git a/SacsMobileMaui/Maui/Panels/SamPanel_Tab.cs b/SacsMobileMaui/Maui/Panels/SamPanel_Tab.cs
new file mode 100644
index 0000000..f1a906d
--- /dev/null
+++ b/SacsMobileMaui/Maui/Panels/SamPanel_Tab.cs
@@ -0,0 +1,82 @@
+
+using Sarand.Net.Mobile.Maui.Views;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.CompilerServices;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Sarand.Net.Mobile.Maui.Panels
+{
+ //=======================================================================
+ public class SamPanel_Tab : SamPanel
+ {
+ //-------------------------------------------------------------------
+ // Definitions.
+
+ //-------------------------------------------------------------------
+ // Data members.
+
+ //-------------------------------------------------------------------
+ // Construction.
+ public SamPanel_Tab()
+ {
+ }
+
+ //-------------------------------------------------------------------
+ // Properties.
+
+ //-------------------------------------------------------------------
+ // C o n f i g u r e C o m p o n e n t s
+ //-------------------------------------------------------------------
+ public override void ConfigureComponents()
+ {
+ base.ConfigureComponents();
+ }
+
+ //-------------------------------------------------------------------
+ // C o n t r o l A c t i v i t y
+ //-------------------------------------------------------------------
+ public override void SetControlActivity()
+ {
+ }
+ public override void SetControlsReadOnly()
+ {
+ }
+
+ //-------------------------------------------------------------------
+ // P a n e l 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);
+ }
+ //-------------------------------------------------------------------
+ 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);
+ }
+
+ //-------------------------------------------------------------------
+ }
+ //=======================================================================
+}
diff --git a/SacsMobileMaui/Maui/Panels/SamPanel_Tabbed.cs b/SacsMobileMaui/Maui/Panels/SamPanel_Tabbed.cs
new file mode 100644
index 0000000..912f923
--- /dev/null
+++ b/SacsMobileMaui/Maui/Panels/SamPanel_Tabbed.cs
@@ -0,0 +1,212 @@
+using Sarand.Net.Mobile.Maui.Views;
+using Sarand.Net.Standard.Base.Utils;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.CompilerServices;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Sarand.Net.Mobile.Maui.Panels
+{
+ //=======================================================================
+ public class SamPanel_Tabbed : SamPanel
+ {
+ //-------------------------------------------------------------------
+ // Definitions.
+ private class TabPanelData
+ {
+ public TabPanelData(int tabPos, SamPanel_TabCaption tabCaptionPanel, SamPanel_Tab tabPanel)
+ {
+ TabPos = tabPos;
+ TabCaptionPanel = tabCaptionPanel;
+ TabPanel = tabPanel;
+ }
+ public int TabPos { get; }
+ public SamPanel_TabCaption TabCaptionPanel { get; }
+ public SamPanel_Tab TabPanel { get; }
+ }
+
+ ///
+ /// Tab Caption panel
+ ///
+ public class SamPanel_TabCaption : SamPanel
+ {
+ // Properties.
+ private SamPanel_Tabbed TabbedPanel { get; }
+ public int TabPos { get; }
+ public String Caption { get; }
+ // Data members.
+ private static readonly Color ActiveBackgroundColor = Colors.WhiteSmoke;
+ private static readonly Color ActiveBorderColor = Colors.DarkSlateBlue;
+ private static readonly Color InactiveBackgroundColor = Colors.LightGray;
+ private static readonly Color InactiveBorderColor = Colors.SlateBlue;
+ private Border border = new Border() { Stroke = InactiveBorderColor, StrokeThickness = 2.0 };
+ private TapGestureRecognizer tabCaptionTapGesture = new TapGestureRecognizer();
+ public SamPanel_TabCaption(SamPanel_Tabbed tabbedPanel, int tabPos, String caption)
+ {
+ TabbedPanel = tabbedPanel;
+ TabPos = tabPos;
+ Caption = caption;
+ Content = border;
+ tabCaptionTapGesture.Tapped += TabCaptionTapGesture_Tapped;
+ GestureRecognizers.Add(tabCaptionTapGesture);
+ Deactivate();
+ }
+ private void TabCaptionTapGesture_Tapped(object? sender, TappedEventArgs e)
+ {
+ TabbedPanel.TabCaptionTapped(sender, e, TabPos);
+ }
+ public void Activate()
+ {
+ BackgroundColor = ActiveBackgroundColor;
+ border.Stroke = ActiveBorderColor;
+ }
+ public void Deactivate()
+ {
+ BackgroundColor = InactiveBackgroundColor;
+ border.Stroke = InactiveBorderColor;
+ }
+ }
+
+ //-------------------------------------------------------------------
+ // Data members.
+
+ // Content.
+ private VerticalStackLayout contentStack = new VerticalStackLayout() { };
+ private HorizontalStackLayout panelTabCaptions = new HorizontalStackLayout() { Spacing = 8.0 };
+ private ScrollView scrollTabPanels = new ScrollView() { };
+ private VerticalStackLayout panelTabPanels = new VerticalStackLayout() { };
+
+ // Tab panels.
+ private int tabCount = 0;
+ private readonly List tabPanels = new List();
+
+ //-------------------------------------------------------------------
+ // Construction.
+ public SamPanel_Tabbed()
+ {
+ // Content.
+ Content = contentStack;
+
+ // Tab captions.
+ contentStack.Children.Add(panelTabCaptions);
+
+ // Tab panels.
+ contentStack.Children.Add(scrollTabPanels);
+ scrollTabPanels.Content = panelTabPanels;
+ }
+
+ //-------------------------------------------------------------------
+ // Properties.
+
+ //-------------------------------------------------------------------
+ // Manage tabs.
+
+ public void AddTabPanel(String caption, SamPanel_Tab tabPanel)
+ {
+ tabCount++;
+
+ // Tab caption.
+ if (StringUtil.IsBlank(caption))
+ caption = $"Tab #{tabCount}";
+ SamPanel_TabCaption panelTabCaption = new SamPanel_TabCaption(this, tabCount, caption);
+ panelTabCaptions.Children.Add(panelTabCaption);
+
+ // Tab panel.
+ panelTabPanels.Children.Add(tabPanel);
+
+ // Tab panel data.
+ TabPanelData tabPanelData = new TabPanelData(tabCount, panelTabCaption, tabPanel);
+ tabPanels.Add(tabPanelData);
+
+ if (tabCount == 1)
+ {
+ panelTabCaption.Activate();
+ }
+ }
+
+ public void ClearTabPanels()
+ {
+ tabCount = 0;
+ tabPanels.Clear();
+ panelTabCaptions.Children.Clear();
+ panelTabPanels.Children.Clear();
+ }
+
+ //-------------------------------------------------------------------
+ // Actions.
+
+ private void TabCaptionTapped(object? sender, TappedEventArgs e, int tabPos)
+ {
+ foreach (TabPanelData tabPanel in tabPanels)
+ {
+ if (tabPanel.TabPos == tabPos)
+ {
+ tabPanel.TabCaptionPanel.Activate();
+ tabPanel.TabPanel.IsVisible = true;
+ }
+ else
+ {
+ tabPanel.TabCaptionPanel.Deactivate();
+ tabPanel.TabPanel.IsVisible = false;
+ }
+ }
+ }
+
+ //-------------------------------------------------------------------
+ // C o n f i g u r e C o m p o n e n t s
+ //-------------------------------------------------------------------
+ public override void ConfigureComponents()
+ {
+ base.ConfigureComponents();
+ }
+
+ //-------------------------------------------------------------------
+ // C o n t r o l A c t i v i t y
+ //-------------------------------------------------------------------
+ public override void SetControlActivity()
+ {
+ }
+ public override void SetControlsReadOnly()
+ {
+ }
+
+ //-------------------------------------------------------------------
+ // P a n e l 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);
+ }
+
+ //-------------------------------------------------------------------
+ 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);
+ }
+
+ //-------------------------------------------------------------------
+ }
+ //=======================================================================
+}
diff --git a/SacsMobileMaui/Maui/Panels/SamTabPanel.cs b/SacsMobileMaui/Maui/Panels/SamTabPanel.cs
new file mode 100644
index 0000000..0235740
--- /dev/null
+++ b/SacsMobileMaui/Maui/Panels/SamTabPanel.cs
@@ -0,0 +1,52 @@
+using Sarand.Net.Mobile.Maui.Views;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Sarand.Net.Mobile.Maui.Panels
+{
+ //=======================================================================
+ public class SamTabPanel : SamPanel
+ {
+ //-------------------------------------------------------------------
+ // Definitions.
+
+ //-------------------------------------------------------------------
+ // Data members.
+
+ //-------------------------------------------------------------------
+ // Construction.
+ public SamTabPanel(String caption, SamContentView panel)
+ {
+ Caption = caption;
+
+ // This panel includes border layout around the actual tab panel content.
+ Padding = new Thickness(2.0);
+ BackgroundColor = Colors.LightSlateGray;
+ Content = panel;
+ }
+
+ //-------------------------------------------------------------------
+ // Properties.
+ public String Caption { get; }
+ public Thickness BorderWidth
+ {
+ get { return Padding; }
+ set { if (Padding == value) return; Padding = value; }
+ }
+ public Color BorderColor
+ {
+ get { return BackgroundColor; }
+ set { if (BackgroundColor == value) return; BackgroundColor = value; }
+ }
+
+ //-------------------------------------------------------------------
+ // T a b P a n e l s
+ //-------------------------------------------------------------------
+
+ //-------------------------------------------------------------------
+ }
+ //=======================================================================
+}
diff --git a/SacsMobileMaui/Maui/Utils/SamElementUtil.cs b/SacsMobileMaui/Maui/Utils/SamElementUtil.cs
new file mode 100644
index 0000000..c5b69aa
--- /dev/null
+++ b/SacsMobileMaui/Maui/Utils/SamElementUtil.cs
@@ -0,0 +1,29 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Sarand.Net.Mobile.Maui.Utils
+{
+ //=======================================================================
+ public static class SamElementUtil
+ {
+ //-------------------------------------------------------------------
+ ///
+ /// Identify the page fo rthis element.
+ ///
+ public static Page? GetPage(this Element element)
+ {
+ while (element != null)
+ {
+ if (element is Page) return element as Page;
+ element = element.Parent;
+ }
+ return null;
+ }
+
+ //-------------------------------------------------------------------
+ }
+ //=======================================================================
+}
diff --git a/SacsMobileMaui/Maui/Utils/SamFormUtil.cs b/SacsMobileMaui/Maui/Utils/SamFormUtil.cs
new file mode 100644
index 0000000..bc07216
--- /dev/null
+++ b/SacsMobileMaui/Maui/Utils/SamFormUtil.cs
@@ -0,0 +1,49 @@
+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
+{
+ //=======================================================================
+ 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")
+ {
+ if (Application.Current?.MainPage == null)
+ {
+ SaLogger.Warn(SysUtil.Fn + " Null main page. Message: " + message);
+ return;
+ }
+ SamThreadUtil.InvokeOnMainThreadSync(async () => await Application.Current.MainPage.DisplayAlert(title, message, cancel));
+ }
+ public static bool DisplayConfirmSync(String title, String message, String accept = "Yes", String cancel = "No")
+ {
+ if (Application.Current?.MainPage == null)
+ {
+ SaLogger.Warn(SysUtil.Fn + " Null main page. Message: " + message);
+ return 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;
+ }
+
+ //-------------------------------------------------------------------
+ }
+ //=======================================================================
+}
diff --git a/SacsMobileMaui/Maui/Utils/SamImageUtil.cs b/SacsMobileMaui/Maui/Utils/SamImageUtil.cs
new file mode 100644
index 0000000..cb78529
--- /dev/null
+++ b/SacsMobileMaui/Maui/Utils/SamImageUtil.cs
@@ -0,0 +1,25 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Sarand.Net.Mobile.Maui.Utils
+{
+ //=======================================================================
+ public static class SamImageUtil
+ {
+ //-------------------------------------------------------------------
+ ///
+ /// Method that uses the ImageConverter object in .Net Framework to convert a byte array,
+ /// presumably containing a JPEG or PNG file image, into an ImageSource.
+ ///
+ public static ImageSource GetImageFromByteArray(byte[] imageAsBytes)
+ {
+ return ImageSource.FromStream(() => new MemoryStream(imageAsBytes));
+ }
+
+ //-------------------------------------------------------------------
+ }
+ //=======================================================================
+}
diff --git a/SacsMobileMaui/Maui/Utils/SamPageUtil.cs b/SacsMobileMaui/Maui/Utils/SamPageUtil.cs
new file mode 100644
index 0000000..b2fbd2d
--- /dev/null
+++ b/SacsMobileMaui/Maui/Utils/SamPageUtil.cs
@@ -0,0 +1,18 @@
+using Microsoft.Maui.Graphics;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Sarand.Net.Mobile.Maui.Utils
+{
+ //=======================================================================
+ public static class SamPageUtil
+ {
+ //-------------------------------------------------------------------
+
+ //-------------------------------------------------------------------
+ }
+ //=======================================================================
+}
diff --git a/SacsMobileMaui/Maui/Utils/SamResourceUtil.cs b/SacsMobileMaui/Maui/Utils/SamResourceUtil.cs
new file mode 100644
index 0000000..3985cbe
--- /dev/null
+++ b/SacsMobileMaui/Maui/Utils/SamResourceUtil.cs
@@ -0,0 +1,61 @@
+using Sarand.Net.Standard.Base.App;
+using Sarand.Net.Standard.Base.Utils;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Sarand.Net.Mobile.Maui.Utils
+{
+ //=======================================================================
+ public class SamResourceUtil
+ {
+ //-------------------------------------------------------------------
+ // Resource as Stream.
+
+ public static Stream? GetResourceStream(String resourcePath)
+ {
+ return GetResourceStream(Assembly.GetExecutingAssembly(), resourcePath);
+ }
+
+ //-------------------------------------------------------------------
+ public static Stream? GetResourceStream(Assembly assembly, String resourcePath)
+ {
+ Stream? stream = assembly.GetManifestResourceStream(resourcePath);
+ if (stream != null) return stream;
+
+ foreach (var res 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);
+ }
+ return null;
+ }
+
+ //-------------------------------------------------------------------
+ // Log resources.
+ public static void LogResources(Assembly assembly)
+ {
+ foreach (var res in assembly.GetManifestResourceNames())
+ SaLogger.Info(SysUtil.Fn + $" Resource: {res.ToString()}");
+ }
+
+ //-------------------------------------------------------------------
+ }
+ //=======================================================================
+}
diff --git a/SacsMobileMaui/Maui/Utils/SamThreadUtil.cs b/SacsMobileMaui/Maui/Utils/SamThreadUtil.cs
new file mode 100644
index 0000000..2502347
--- /dev/null
+++ b/SacsMobileMaui/Maui/Utils/SamThreadUtil.cs
@@ -0,0 +1,69 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Sarand.Net.Mobile.Maui.Utils
+{
+ //=======================================================================
+ public static class SamThreadUtil
+ {
+ //-------------------------------------------------------------------
+ // Thread utilities.
+ //-------------------------------------------------------------------
+
+ /// Is the current thread the main thread.
+ public static bool IsMainThread()
+ {
+ 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
+ //-------------------------------------------------------------------
+
+ /// Invoke task on main thread.
+ public static void InvokeOnMainThread(Action taskFn)
+ {
+ MainThread.BeginInvokeOnMainThread(() =>
+ {
+ taskFn();
+ });
+ }
+
+ /// Invoke task on main thread and wait for completion.
+ public static void InvokeOnMainThreadSync(Action taskFn)
+ {
+ var tcs = new TaskCompletionSource