Improve shared MAUI controls and tests

This commit is contained in:
Andy Howard
2026-10-06 14:13:28 +01:00
parent 0406adfc99
commit 5daf7ce97f
106 changed files with 2299 additions and 805 deletions
+12
View File
@@ -22,6 +22,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SacsStandardProc", "SacsSta
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SacsMobileMaui", "SacsMobileMaui\SacsMobileMaui.csproj", "{F167BCF8-8B99-4103-A710-D4B8FD5518A7}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SacsMobileMauiTests", "SacsMobileMauiTests\SacsMobileMauiTests.csproj", "{D7B6206D-6D7B-4A67-9F32-B09FCD6E8EAA}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SacsMobileMauiTestHost", "SacsMobileMauiTestHost\SacsMobileMauiTestHost.csproj", "{52B726C9-13B2-49DF-9352-90563709259B}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -56,6 +60,14 @@ Global
{F167BCF8-8B99-4103-A710-D4B8FD5518A7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F167BCF8-8B99-4103-A710-D4B8FD5518A7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F167BCF8-8B99-4103-A710-D4B8FD5518A7}.Release|Any CPU.Build.0 = Release|Any CPU
{D7B6206D-6D7B-4A67-9F32-B09FCD6E8EAA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D7B6206D-6D7B-4A67-9F32-B09FCD6E8EAA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D7B6206D-6D7B-4A67-9F32-B09FCD6E8EAA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D7B6206D-6D7B-4A67-9F32-B09FCD6E8EAA}.Release|Any CPU.Build.0 = Release|Any CPU
{52B726C9-13B2-49DF-9352-90563709259B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{52B726C9-13B2-49DF-9352-90563709259B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{52B726C9-13B2-49DF-9352-90563709259B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{52B726C9-13B2-49DF-9352-90563709259B}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
+52 -44
View File
@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
@@ -8,15 +8,18 @@ using System.Threading.Tasks;
namespace Sarand.Net.Mobile.Maui.App
{
//=======================================================================
// Colour references:
// http://www.html-color-names.com/skyblue.php
// http://www.html-color-names.com/lightsteelblue.php
//=======================================================================
public static class Sam
{
//-------------------------------------------------------------------
// Definitions.
// --- Configuration ---
// Values in this class are intended to be configured during application
// startup. Controls normally copy visual defaults when they are created;
// changing a value later does not automatically update existing controls.
// --- Definitions ---
public enum Platform
{
Unknown = 0,
@@ -25,26 +28,30 @@ namespace Sarand.Net.Mobile.Maui.App
MacCatalyst,
Windows
}
// Utilities.
/// <summary>Shared random-number generator for common MAUI code.</summary>
public static Random Random { get; set; } = new Random();
//-------------------------------------------------------------------
// Localisation.
// --- Localisation ---
/// <summary>Application-wide culture settings used by consumers that opt in.</summary>
public static class Locale
{
/// <summary>The culture used for application-level formatting.</summary>
public static CultureInfo CultureInfo { get; set; } = CultureInfo.CurrentCulture;
/// <summary>Text rules associated with <see cref="CultureInfo"/>.</summary>
public static TextInfo TextInfo { get; set; } = CultureInfo.TextInfo;
}
//-------------------------------------------------------------------
// Visuals.
// --- Visuals ---
/// <summary>Supported positions for optional page content.</summary>
public enum DisplayPosition { None, Left, Middle, Right }
/// <summary>Shared visual defaults for common MAUI components.</summary>
public static class Visuals
{
// Application styling.
// --- Application styling ---
public static class Application
{
// Application bar.
// --- Application bar ---
public static class Bar
{
public static double Height { get; set; } = 28;
@@ -54,7 +61,7 @@ namespace Sarand.Net.Mobile.Maui.App
public static Color BorderColor { get; set; } = Colors.DarkSlateBlue;
public static double BorderBottom { get; set; } = 1.0;
}
// Menu.
// --- Menu ---
public static class Menu
{
public static Color BorderColor { get; set; } = Colors.DarkSlateBlue;
@@ -63,28 +70,28 @@ namespace Sarand.Net.Mobile.Maui.App
public static Color HeaderBackgroundColor { get; set; } = Color.FromArgb("#E0E0E0");
}
}
// Page layout.
// --- Page layout ---
public static class Page
{
public static Color BaseColor { get; set; } = Color.FromArgb("#c8e8f5");
public static Color BackgroundColor { get; set; } = Color.FromArgb("#e9f6fb");
// Header.
// --- Header ---
public static class Header
{
// Text.
// --- Text ---
public static String TextLeft { get; set; } = "";
public static String TextRight { get; set; } = "";
// Toolbar.
// --- Toolbar ---
public static DisplayPosition ToolbarDisplay { get; set; } = Sam.DisplayPosition.None;
public static bool Display_ToolbarItem_Back { get; set; } = false;
// Styling.
// --- Styling ---
public static Color BackgroundColor { get; set; } = Colors.Honeydew;
public static Color BorderColor { get; set; } = Colors.LightSlateGray;
public static double BorderStrokeWidthTop { get; set; } = 2.0;
public static double BorderStrokeWidthBottom { get; set; } = 1.0;
public static Thickness Padding { get; set; } = new Thickness(8, 4);
}
// Caption.
// --- Caption ---
public static class Caption
{
public static Color BackgroundColor { get; set; } = Color.FromArgb("#558395");
@@ -93,7 +100,7 @@ namespace Sarand.Net.Mobile.Maui.App
public static Color TextColor { get; set; } = Color.FromArgb("#f8f8f8");
public static double TextSizeRatio { get; set; } = 1.25;
}
// Navigation.
// --- Navigation ---
public static class Navigation
{
public static double BorderTopWidth { get; set; } = 0.75;
@@ -101,13 +108,13 @@ namespace Sarand.Net.Mobile.Maui.App
public static Color BorderColor { get; set; } = Color.FromArgb("#808080");
public static Color BackgroundColor { get; set; } = Color.FromArgb("#e9f6fb");
}
// Body.
// --- Body ---
public static class Body
{
public static double PaddingVerticalMin { get; set; } = 4.0;
public static double PaddingHorizontalMin { get; set; } = 4.0;
}
// Footer.
// --- Footer ---
public static class Footer
{
public static String TextLeft { get; set; } = "";
@@ -122,18 +129,20 @@ namespace Sarand.Net.Mobile.Maui.App
public static Thickness Padding { get; set; } = new Thickness(8, 4);
}
}
// Fonts.
// --- Fonts ---
public static class Fonts
{
// Dynamic font sizing.
// Values are calculated from the current device dimensions and are not
// copied from a mutable setting.
// --- Dynamic font sizing ---
public static string HtmlFontSize
{
get
{
if (SamApplication.MinDeviceDimension > 2000) return "14pt";
if (SamApplication.MinDeviceDimension > 1600) return "13pt";
if (SamApplication.MinDeviceDimension > 1200) return "12pt";
if (SamApplication.MinDeviceDimension > 1000) return "11pt";
if (SamApplication.MinDeviceDimensionPx > 2000) return "14pt";
if (SamApplication.MinDeviceDimensionPx > 1600) return "13pt";
if (SamApplication.MinDeviceDimensionPx > 1200) return "12pt";
if (SamApplication.MinDeviceDimensionPx > 1000) return "11pt";
return "10pt";
}
}
@@ -141,25 +150,25 @@ namespace Sarand.Net.Mobile.Maui.App
{
get
{
if (SamApplication.MinDeviceDimension > 2000) return 1.75;
if (SamApplication.MinDeviceDimension > 1600) return 1.50;
if (SamApplication.MinDeviceDimension > 1200) return 1.25;
if (SamApplication.MinDeviceDimensionPx > 2000) return 1.75;
if (SamApplication.MinDeviceDimensionPx > 1600) return 1.50;
if (SamApplication.MinDeviceDimensionPx > 1200) return 1.25;
return 1.0;
}
}
}
// Data display visuals.
// --- Data display visuals ---
public static class Data
{
// Item list.
// --- Item list ---
public static class ItemList
{
public static double HeaderBorderWidth { get; set; } = 2.0;
public static float HeaderBorderRadius = 6.0f;
public static Color HeaderBorderColor = Sam.Visuals.Application.Bar.BackgroundColor;
public static Color HeaderBackgroundColor = Color.FromArgb("#F0F0F0");
public static float HeaderBorderRadius { get; set; } = 6.0f;
public static Color HeaderBorderColor { get; set; } = Sam.Visuals.Application.Bar.BackgroundColor;
public static Color HeaderBackgroundColor { get; set; } = Color.FromArgb("#F0F0F0");
}
// Associated items panel.
// --- Associated items panel ---
public static class AssociatedItems
{
public static Color CaptionBackgroundColor { get; set; } = Color.FromArgb("#becee4");
@@ -168,30 +177,30 @@ namespace Sarand.Net.Mobile.Maui.App
public static Color BackgroundColor { get; set; } = Color.FromArgb("#dbe4f0");
}
}
// Form.
// --- Form ---
public static class Form
{
public static Color BackgroundColor { get; set; } = Color.FromArgb("#e9f6fb");
public static double GridRowSpacing { get; set; } = 4.0;
public static double GridColumnSpacing { get; set; } = 4.0;
// Label.
// --- Label ---
public static class Label
{
public static Thickness Margin { get; set; } = new Thickness(8.0, 4.0, 8.0, 2.0);
}
// Controls.
// --- Controls ---
public static class Controls
{
public static Thickness Padding { get; set; } = new Thickness(4.0, 2.0, 4.0, 1.0);
public static Color BackgroundColor { get; set; } = Color.FromArgb("#F8F8F8");
public static Color BorderColor { get; set; } = Color.FromArgb("#E0E0E0");
public static double BorderWidth { get; set; } = 0.5;
// Native behaviour modified by SamHandler.
// --- Native behaviour modified by SamHandler ---
public static bool NativeNoBorder { get; set; } = false;
public static bool NativeNoUnderline { get; set; } = false;
public static bool NativeBackgroundClear { get; set; } = false;
}
// Tab controls.
// --- Tab controls ---
public static class TabControl
{
public static Color HeaderBackgroundColor { get; set; } = Colors.Transparent;
@@ -202,7 +211,6 @@ namespace Sarand.Net.Mobile.Maui.App
}
}
//-------------------------------------------------------------------
// --- The End ---
}
//=======================================================================
}
+21 -22
View File
@@ -1,4 +1,4 @@
using Sarand.Net.Standard.Base.App;
using Sarand.Net.Standard.Base.App;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -7,20 +7,19 @@ using System.Threading.Tasks;
namespace Sarand.Net.Mobile.Maui.App
{
//=======================================================================
// --- Shared MAUI application base ---
public class SamApplication : Application
{
//-------------------------------------------------------------------
// Definitions.
// --- Data Members ---
//-------------------------------------------------------------------
// Data members.
private Sam.Platform platform;
private readonly Sam.Platform platform;
// --- Construction ---
//-------------------------------------------------------------------
protected SamApplication()
{
// Determine platform.
// --- Platform ---
#if ANDROID
platform = Sam.Platform.Android;
#elif IOS
@@ -31,30 +30,30 @@ namespace Sarand.Net.Mobile.Maui.App
platform = Sam.Platform.Windows;
#endif
// Log environment.
// --- Startup diagnostics ---
SaLogger.Debug("Platform: " + platform.ToString());
SaLogger.Debug("AppInfo.AppName: " + AppInfo.Name);
SaLogger.Debug("AppInfo.PackageName: " + AppInfo.PackageName);
SaLogger.Debug("AppInfo.Version: " + AppInfo.VersionString);
SaLogger.Debug("AppInfo.Build: " + AppInfo.BuildString);
SaLogger.Debug("DeviceWidth: " + DeviceWidth);
SaLogger.Debug("DeviceHeight: " + DeviceHeight);
SaLogger.Debug("DeviceWidth: " + DeviceWidthPx);
SaLogger.Debug("DeviceHeight: " + DeviceHeightPx);
SaLogger.Debug("DeviceDensity: " + DeviceDisplay.MainDisplayInfo.Density);
}
//-------------------------------------------------------------------
// Properties.
// --- Properties ---
public Sam.Platform Platform { get { return platform; } }
// Device screen size and orientation.
public static bool Landscape => DeviceWidth > DeviceHeight;
public static double DeviceWidth => DeviceDisplay.MainDisplayInfo.Width;
public static double DeviceHeight => DeviceDisplay.MainDisplayInfo.Height;
public static double MinDeviceDimension => Math.Min(DeviceWidth, DeviceHeight);
public static double MaxDeviceDimension => Math.Max(DeviceWidth, DeviceHeight);
// --- Display ---
//-------------------------------------------------------------------
public static bool Landscape => DeviceWidthPx > DeviceHeightPx;
public static double DeviceWidthPx => DeviceDisplay.MainDisplayInfo.Width;
public static double DeviceHeightPx => DeviceDisplay.MainDisplayInfo.Height;
public static double MinDeviceDimensionPx => Math.Min(DeviceWidthPx, DeviceHeightPx);
public static double MaxDeviceDimensionPx => Math.Max(DeviceWidthPx, DeviceHeightPx);
// --- The End ---
}
//=======================================================================
}
+2 -2
View File
@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
@@ -41,7 +41,7 @@ namespace Sarand.Net.Mobile.Maui.Base
//-------------------------------------------------------------------
// C o n t r o l A c t i v i t y
//-------------------------------------------------------------------
protected virtual void SetControlActivity()
protected virtual void SetControlActivity(bool enabled)
{
}
protected virtual void SetControlsReadOnly()
@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
@@ -0,0 +1,10 @@
namespace Sarand.Net.Mobile.Maui.Controls
{
// --- Definitions ---
public interface ISamControl
{
void SetControlActivity(bool enabled);
// --- The End ---
}
}
@@ -0,0 +1,19 @@
namespace Sarand.Net.Mobile.Maui.Controls
{
// --- Native control styling ---
/// <summary>
/// Exposes native styles applied when a MAUI handler is mapped. These plain properties
/// are read during handler mapping and changes made afterward do not automatically reapply them.
/// </summary>
public interface ISamNativeControlStyle
{
/// <summary>Removes the native control border where supported.</summary>
bool NativeNoBorder { get; set; }
/// <summary>Removes an Android-style underline where supported; other platforms may have no equivalent.</summary>
bool NativeNoUnderline { get; set; }
/// <summary>Makes the native control background transparent where supported.</summary>
bool NativeBackgroundClear { get; set; }
// --- The End ---
}
}
@@ -1,4 +1,4 @@
using Sarand.Net.Mobile.Maui.Controls.Views;
using Sarand.Net.Mobile.Maui.Controls.Views;
using Sarand.Net.Standard.Base.Utils;
using System;
using System.Collections.Generic;
@@ -9,19 +9,15 @@ using System.Threading.Tasks;
namespace Sarand.Net.Mobile.Maui.Controls.Panels
{
//=======================================================================
public class SamCheckBoxPanel : SamControlPanel
{
//-------------------------------------------------------------------
// Definitions.
// --- Data Members ---
//-------------------------------------------------------------------
// Data members.
private readonly SamCheckBox checkbox = new SamCheckBox() { };
private readonly TapGestureRecognizer gestureSuffixTap = new TapGestureRecognizer() { };
//-------------------------------------------------------------------
// Construction.
// --- Construction ---
public SamCheckBoxPanel()
{
// View configuration.
@@ -40,11 +36,11 @@ namespace Sarand.Net.Mobile.Maui.Controls.Panels
LabelSuffix.GestureRecognizers.Add(gestureSuffixTap);
ConfigureComponents();
SetControlActivity();
SetControlActivity(IsEnabled);
}
//-------------------------------------------------------------------
// Properties.
// --- Properties ---
protected override View ControlView { get { return checkbox; } }
public bool IsChecked
{
@@ -57,41 +53,16 @@ namespace Sarand.Net.Mobile.Maui.Controls.Panels
set { checkbox.HorizontalOptions = value; ReadOnlyLabel.HorizontalOptions = checkbox.HorizontalOptions; }
}
//-------------------------------------------------------------------
// C o n f i g u r e C o m p o n e n t s
//-------------------------------------------------------------------
/// <summary>Control configuration that can be changed dynamically.</summary>
public override void ConfigureComponents()
{
base.ConfigureComponents();
}
// --- Control events ---
//-------------------------------------------------------------------
// C o n t r o l A c t i v i t y
//-------------------------------------------------------------------
public override void SetControlActivity()
{
base.SetControlActivity();
SetControlsReadOnly();
}
public override void SetControlsReadOnly()
{
base.SetControlsReadOnly();
}
//-------------------------------------------------------------------
// C o n t r o l E v e n t s
//-------------------------------------------------------------------
private void Entry_Focused(object? sender, FocusEventArgs e)
{
Control_Focused(sender, e);
}
//-------------------------------------------------------------------
private void Entry_Unfocused(object? sender, FocusEventArgs e)
{
Control_Unfocused(sender, e);
}
//-------------------------------------------------------------------
// Checked changed.
public event EventHandler<CheckedChangedEventArgs>? CheckedChanged;
private void Checkbox_CheckedChanged(object? sender, CheckedChangedEventArgs e)
@@ -106,42 +77,6 @@ namespace Sarand.Net.Mobile.Maui.Controls.Panels
checkbox.IsChecked = !checkbox.IsChecked;
}
//-------------------------------------------------------------------
// V i e w E v e n t s
//-------------------------------------------------------------------
protected override void OnParentSet()
{
base.OnParentSet();
}
//-------------------------------------------------------------------
protected override void OnPropertyChanging([CallerMemberName] string propertyName = "")
{
base.OnPropertyChanging(propertyName);
}
protected override void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
base.OnPropertyChanged(propertyName);
//Logger.Debug(GetType().Name + ".OnPropertyChanged Name=" + propertyName);
}
//-------------------------------------------------------------------
protected override void OnChildAdded(Element child)
{
base.OnChildAdded(child);
}
protected override void OnChildRemoved(Element child, int oldLogicalIndex)
{
base.OnChildRemoved(child, oldLogicalIndex);
}
//-------------------------------------------------------------------
protected override void OnSizeAllocated(double width, double height)
{
base.OnSizeAllocated(width, height);
}
//-------------------------------------------------------------------
// --- The End ---
}
//=======================================================================
}
@@ -1,4 +1,4 @@
using Sarand.Net.Mobile.Maui.App;
using Sarand.Net.Mobile.Maui.App;
using Sarand.Net.Mobile.Maui.Views;
using Sarand.Net.Standard.Base.Utils;
using System;
@@ -10,14 +10,10 @@ using System.Threading.Tasks;
namespace Sarand.Net.Mobile.Maui.Controls.Views
{
//=======================================================================
public abstract class SamControlPanel : SamContentView
{
//-------------------------------------------------------------------
// Definitions.
// --- Data Members ---
//-------------------------------------------------------------------
// Data members.
// Components.
private HorizontalStackLayout controlLayout = new HorizontalStackLayout();
private SamContentView controlBorder = new SamContentView();
@@ -27,8 +23,8 @@ namespace Sarand.Net.Mobile.Maui.Controls.Views
private Color controlBorderColor = Sam.Visuals.Form.Controls.BorderColor;
private Color controlBackgroundColor = Sam.Visuals.Form.Controls.BackgroundColor;
//-------------------------------------------------------------------
// Construction.
// --- Construction ---
protected SamControlPanel()
{
// Control layout: [Border[Panel]] [Suffix].
@@ -36,14 +32,12 @@ namespace Sarand.Net.Mobile.Maui.Controls.Views
controlLayout.Add(controlBorder);
controlLayout.Add(LabelSuffix);
// Main layout.
// --- Main layout ---
Content = controlLayout;
SetControlActivity();
}
//-------------------------------------------------------------------
// Properties.
// --- Properties ---
// Label.
public SamLabel Label { get; } = new SamLabel() { Margin = Sam.Visuals.Form.Label.Margin };
@@ -92,9 +86,8 @@ namespace Sarand.Net.Mobile.Maui.Controls.Views
set { if (ReadOnlyLabel.Text.Equals(value)) return; ReadOnlyLabel.Text = value; }
}
//-------------------------------------------------------------------
// C o n f i g u r e C o m p o n e n t s
//-------------------------------------------------------------------
// --- Component configuration ---
/// <summary>Control configuration that can be changed dynamically.</summary>
public override void ConfigureComponents()
{
@@ -104,13 +97,8 @@ namespace Sarand.Net.Mobile.Maui.Controls.Views
controlPanel.BackgroundColor = controlBackgroundColor;
}
//-------------------------------------------------------------------
// C o n t r o l A c t i v i t y
//-------------------------------------------------------------------
public override void SetControlActivity()
{
base.SetControlActivity();
}
// --- Control activity ---
public override void SetControlsReadOnly()
{
base.SetControlsReadOnly();
@@ -118,24 +106,21 @@ namespace Sarand.Net.Mobile.Maui.Controls.Views
else controlPanel.Content = ControlView;
}
//-------------------------------------------------------------------
// C o n t r o l E v e n t s
//-------------------------------------------------------------------
// --- Control events ---
public new event EventHandler<FocusEventArgs>? Focused;
protected void Control_Focused(object? sender, FocusEventArgs e)
{
if (Focused != null) Focused.Invoke(sender, e);
}
//-------------------------------------------------------------------
public new event EventHandler<FocusEventArgs>? Unfocused;
protected void Control_Unfocused(object? sender, FocusEventArgs e)
{
if (Unfocused != null) Unfocused.Invoke(sender, e);
}
//-------------------------------------------------------------------
// V i e w E v e n t s
//-------------------------------------------------------------------
// --- View events ---
protected override void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
base.OnPropertyChanged(propertyName);
@@ -156,7 +141,6 @@ namespace Sarand.Net.Mobile.Maui.Controls.Views
}
}
//-------------------------------------------------------------------
// --- The End ---
}
//=======================================================================
}
@@ -1,4 +1,4 @@
using Sarand.Net.Standard.Base.Utils;
using Sarand.Net.Standard.Base.Utils;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -8,18 +8,14 @@ using System.Threading.Tasks;
namespace Sarand.Net.Mobile.Maui.Controls.Views
{
//=======================================================================
public class SamEntryPanel : SamControlPanel
{
//-------------------------------------------------------------------
// Definitions.
// --- Data Members ---
//-------------------------------------------------------------------
// Data members.
private readonly SamEntry entry = new SamEntry() { HorizontalOptions = LayoutOptions.Fill };
//-------------------------------------------------------------------
// Construction.
// --- Construction ---
public SamEntryPanel()
{
// View configuration.
@@ -34,11 +30,12 @@ namespace Sarand.Net.Mobile.Maui.Controls.Views
entry.TextChanged += Entry_TextChanged;
entry.Completed += Entry_Completed;
SetControlActivity();
ConfigureComponents();
SetControlActivity(IsEnabled);
}
//-------------------------------------------------------------------
// Properties.
// --- Properties ---
protected override View ControlView { get { return entry; } }
public int MaxLength
{
@@ -86,41 +83,16 @@ namespace Sarand.Net.Mobile.Maui.Controls.Views
set { entry.HorizontalTextAlignment = value; ReadOnlyLabel.HorizontalTextAlignment = entry.HorizontalTextAlignment; }
}
//-------------------------------------------------------------------
// C o n f i g u r e C o m p o n e n t s
//-------------------------------------------------------------------
/// <summary>Control configuration that can be changed dynamically.</summary>
public override void ConfigureComponents()
{
base.ConfigureComponents();
}
// --- Control events ---
//-------------------------------------------------------------------
// C o n t r o l A c t i v i t y
//-------------------------------------------------------------------
public override void SetControlActivity()
{
base.SetControlActivity();
SetControlsReadOnly();
}
public override void SetControlsReadOnly()
{
base.SetControlsReadOnly();
}
//-------------------------------------------------------------------
// C o n t r o l E v e n t s
//-------------------------------------------------------------------
private void Entry_Focused(object? sender, FocusEventArgs e)
{
Control_Focused(sender, e);
}
//-------------------------------------------------------------------
private void Entry_Unfocused(object? sender, FocusEventArgs e)
{
Control_Unfocused(sender, e);
}
//-------------------------------------------------------------------
// Text changed.
public event EventHandler<TextChangedEventArgs>? TextChanged;
private void Entry_TextChanged(object? sender, TextChangedEventArgs e)
@@ -129,7 +101,6 @@ namespace Sarand.Net.Mobile.Maui.Controls.Views
if (ReadOnly) return;
if (TextChanged != null) TextChanged.Invoke(sender, e);
}
//-------------------------------------------------------------------
// Completed.
public event EventHandler? Completed;
private void Entry_Completed(object? sender, EventArgs e)
@@ -138,42 +109,6 @@ namespace Sarand.Net.Mobile.Maui.Controls.Views
if (Completed != null) Completed.Invoke(sender, e);
}
//-------------------------------------------------------------------
// V i e w E v e n t s
//-------------------------------------------------------------------
protected override void OnParentSet()
{
base.OnParentSet();
}
//-------------------------------------------------------------------
protected override void OnPropertyChanging([CallerMemberName] string propertyName = "")
{
base.OnPropertyChanging(propertyName);
}
protected override void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
base.OnPropertyChanged(propertyName);
//Logger.Debug(GetType().Name + ".OnPropertyChanged Name=" + propertyName);
}
//-------------------------------------------------------------------
protected override void OnChildAdded(Element child)
{
base.OnChildAdded(child);
}
protected override void OnChildRemoved(Element child, int oldLogicalIndex)
{
base.OnChildRemoved(child, oldLogicalIndex);
}
//-------------------------------------------------------------------
protected override void OnSizeAllocated(double width, double height)
{
base.OnSizeAllocated(width, height);
}
//-------------------------------------------------------------------
// --- The End ---
}
//=======================================================================
}
+17 -18
View File
@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
@@ -6,31 +6,30 @@ using System.Threading.Tasks;
namespace Sarand.Net.Mobile.Maui.Controls
{
//=======================================================================
public class SamButton : Button
/// <summary>
/// Application button extension point. It currently adds no behavior beyond
/// the MAUI <see cref="Button"/> contract.
/// </summary>
public class SamButton : Button, ISamControl
{
//-------------------------------------------------------------------
// Definitions.
// --- Construction ---
//-------------------------------------------------------------------
// Data members.
//-------------------------------------------------------------------
// Construction.
public SamButton()
{
DefaultFontSize = FontSize;
}
//-------------------------------------------------------------------
// Properties.
// --- Component state ---
public void SetControlActivity(bool enabled)
{
if (IsEnabled != enabled) IsEnabled = enabled;
}
// --- Properties ---
public double DefaultFontSize { get; }
//-------------------------------------------------------------------
// C o n t r o l E v e n t s
//-------------------------------------------------------------------
//-------------------------------------------------------------------
// --- The End ---
}
//=======================================================================
}
+13 -16
View File
@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
@@ -6,28 +6,25 @@ using System.Threading.Tasks;
namespace Sarand.Net.Mobile.Maui.Controls
{
//======================================================================
public class SamCheckBox : CheckBox
/// <summary>
/// Application check-box extension point. It currently adds no behavior
/// beyond the MAUI <see cref="CheckBox"/> contract.
/// </summary>
public class SamCheckBox : CheckBox, ISamControl
{
//-------------------------------------------------------------------
// Definitions.
// --- Construction ---
//-------------------------------------------------------------------
// Data members.
//-------------------------------------------------------------------
// Construction.
public SamCheckBox()
{
}
//-------------------------------------------------------------------
// Properties.
// --- Component state ---
//-------------------------------------------------------------------
// Control events.
public void SetControlActivity(bool enabled)
{
if (IsEnabled != enabled) IsEnabled = enabled;
}
//-------------------------------------------------------------------
// --- The End ---
}
//======================================================================
}
+22 -43
View File
@@ -1,4 +1,5 @@
using Sarand.Net.Mobile.Maui.Layouts;
using Sarand.Net.Mobile.Maui.Logic;
using Sarand.Net.Mobile.Maui.Views;
using Sarand.Net.Standard.Base.Utils;
using System;
@@ -9,24 +10,17 @@ using System.Threading.Tasks;
namespace Sarand.Net.Mobile.Maui.Controls
{
//=======================================================================
public class SamDatePicker : SamContentView
{
//-------------------------------------------------------------------
// Definitions.
//-------------------------------------------------------------------
// Data members.
// --- Data Members ---
private bool allowInvalidDate = false;
private bool invalidDate = true;
private DateTime previousDate = DateTime.MinValue;
private SamDateValueState dateState = new SamDateValueState();
private SamStackLayout controlLayout = new SamStackLayout() { Orientation = StackOrientation.Horizontal, HorizontalOptions = LayoutOptions.Fill };
private DatePicker pickerDate = new DatePicker() { IsVisible = false };
private SamEntry entryDate = new SamEntry() { HorizontalOptions = LayoutOptions.FillAndExpand, VerticalOptions = LayoutOptions.Fill };
private SamLabel labelResetDate = new SamLabel() { Text = "X", FontAttributes = FontAttributes.Bold, TextColor = Colors.Red, HorizontalOptions = LayoutOptions.Center, VerticalTextAlignment = TextAlignment.Center };
//-------------------------------------------------------------------
// Construction.
// --- Construction ---
public SamDatePicker()
{
// Initialise controls.
@@ -58,17 +52,16 @@ namespace Sarand.Net.Mobile.Maui.Controls
RefreshDateDisplay();
}
//-------------------------------------------------------------------
// Properties.
// Configuration.
// --- Properties ---
public Boolean AllowInvalid
{
get { return allowInvalidDate; }
set
{
if (allowInvalidDate = value) return;
if (allowInvalidDate == value) return;
allowInvalidDate = value;
if (!allowInvalidDate) invalidDate = false;
if (!allowInvalidDate && dateState.IsInvalid)
dateState.SetDate(pickerDate.Date ?? DateTime.Today);
RefreshDateDisplay();
}
}
@@ -91,34 +84,29 @@ namespace Sarand.Net.Mobile.Maui.Controls
get { return pickerDate.MaximumDate ?? DateTime.MaxValue; }
set { pickerDate.MaximumDate = value; }
}
//-------------------------------------------------------------------
// Date value.
// --- Date value ---
public DateTime Date
{
get
{
if (invalidDate) return DateTime.MinValue;
return pickerDate.Date ?? DateTime.MinValue;
return dateState.Date;
}
set
{
if (Date.Equals(value)) return;
if (!DateUtil.IsValid(value))
if (!dateState.SetDate(value)) return;
if (dateState.IsInvalid)
{
invalidDate = true;
pickerDate.Date = DateTime.Today;
}
else
{
invalidDate = false;
pickerDate.Date = value;
}
previousDate = value;
RefreshDateDisplay();
}
}
public String FormattedDate { get { return !invalidDate ? DateUtil.Format(Date, pickerDate.Format) : ""; } }
//-------------------------------------------------------------------
// Font.
public String FormattedDate { get { return !dateState.IsInvalid ? DateUtil.Format(Date, pickerDate.Format) : ""; } }
// --- Font ---
public double DefaultFontSize { get; }
public double FontSize
{
@@ -161,8 +149,7 @@ namespace Sarand.Net.Mobile.Maui.Controls
}
}
//-------------------------------------------------------------------
// Utilities.
// --- Utilities ---
private void RefreshDateDisplay()
{
if (allowInvalidDate)
@@ -178,44 +165,36 @@ namespace Sarand.Net.Mobile.Maui.Controls
labelResetDate.IsVisible = false;
}
String dateValue = "";
if (!invalidDate)
if (!dateState.IsInvalid)
dateValue = DateUtil.Format(Date, pickerDate.Format);
entryDate.Text = dateValue;
}
//-------------------------------------------------------------------
// C o n t r o l E v e n t s
//-------------------------------------------------------------------
// --- Control events ---
private void PickerDate_DateSelected(object? sender, DateChangedEventArgs e)
{
// Date has changed in control.
invalidDate = !DateUtil.IsValid(e.NewDate);
dateState.SetDate(e.NewDate ?? DateTime.MinValue);
RefreshDateDisplay();
if (DateSelected != null) DateSelected.Invoke(sender, e);
}
public event EventHandler<DateChangedEventArgs>? DateSelected;
//-------------------------------------------------------------------
private void EntryDate_Focused(object? sender, FocusEventArgs e)
{
// Control has gained focus.
entryDate.Unfocus();
pickerDate.Focus();
invalidDate = false;
dateState.SetDate(pickerDate.Date ?? DateTime.Today);
RefreshDateDisplay();
}
//-------------------------------------------------------------------
private void LabelValidDate_Tapped(object? sender, EventArgs e)
{
// 'Valid' label tapped.
if (invalidDate) return;
invalidDate = true;
DateTime oldDate = previousDate;
if (!dateState.TryInvalidate(out DateTime oldDate)) return;
RefreshDateDisplay();
if (DateSelected != null) DateSelected.Invoke(sender, new DateChangedEventArgs(oldDate, Date));
previousDate = Date;
}
//-------------------------------------------------------------------
// --- The End ---
}
//=======================================================================
}
@@ -9,22 +9,18 @@ using System.Threading.Tasks;
namespace Sarand.Net.Mobile.Maui.Controls
{
//=======================================================================
public class SamDateTimePicker : SamContentView
{
//-------------------------------------------------------------------
// Definitions.
// --- Data Members ---
// Control layout.
private SamStackLayout layoutControl = new SamStackLayout() { Orientation = StackOrientation.Horizontal };
private SamDatePicker pickerDate = new SamDatePicker();
private SamLabel labelTimePrefix = new SamLabel();
private SamTimePicker pickerTime = new SamTimePicker();
//-------------------------------------------------------------------
// Data members.
// --- Construction ---
//-------------------------------------------------------------------
// Construction.
public SamDateTimePicker()
{
DefaultFontSize = pickerDate.FontSize;
@@ -39,8 +35,8 @@ namespace Sarand.Net.Mobile.Maui.Controls
pickerTime.TimeChanged += PickerTime_TimeChanged;
}
//-------------------------------------------------------------------
// Properties.
// --- Properties ---
public string DateFormat
{
get { return pickerDate.Format; }
@@ -50,6 +46,11 @@ namespace Sarand.Net.Mobile.Maui.Controls
pickerDate.Format = value;
}
}
public bool AllowInvalid
{
get { return pickerDate.AllowInvalid; }
set { pickerDate.AllowInvalid = value; }
}
public DateTime MinimumDate
{
get { return pickerDate.MinimumDate; }
@@ -69,7 +70,7 @@ namespace Sarand.Net.Mobile.Maui.Controls
pickerTime.Format = value;
}
}
// Control value.
// --- Control value ---
public DateTime DateTime
{
get { return pickerDate.Date.Date + (pickerTime.Time ?? TimeSpan.Zero); }
@@ -81,8 +82,15 @@ namespace Sarand.Net.Mobile.Maui.Controls
}
}
public String DateTimeDelim { get; set; } = " ";
public String FormattedDateTime { get { return pickerDate.FormattedDate + DateTimeDelim + pickerTime.FormattedTime; } }
// Styling.
public String FormattedDateTime
{
get
{
if (StringUtil.IsEmpty(pickerDate.FormattedDate)) return "";
return pickerDate.FormattedDate + DateTimeDelim + pickerTime.FormattedTime;
}
}
// --- Styling ---
public double DefaultFontSize { get; }
public double FontSize
{
@@ -101,9 +109,8 @@ namespace Sarand.Net.Mobile.Maui.Controls
set { value = StringUtil.TrimAll(value); if (labelTimePrefix.Text.Equals(value)) return; labelTimePrefix.Text = value; ConfigureComponents(); }
}
//-------------------------------------------------------------------
// C o n f i g u r e C o m p o n e n t s
//-------------------------------------------------------------------
// --- Component configuration ---
public override void ConfigureComponents()
{
base.ConfigureComponents();
@@ -127,34 +134,30 @@ namespace Sarand.Net.Mobile.Maui.Controls
}
}
//-------------------------------------------------------------------
// C o n t r o l A c t i v i t y
//-------------------------------------------------------------------
public override void SetControlActivity()
// --- Control activity ---
public override void SetControlActivity(bool enabled)
{
base.SetControlActivity();
base.SetControlActivity(enabled);
}
public override void SetControlsReadOnly()
{
base.SetControlsReadOnly();
}
//-------------------------------------------------------------------
// C o n t r o l E v e n t s
//-------------------------------------------------------------------
// --- Control events ---
private void PickerDate_DateSelected(object? sender, DateChangedEventArgs e)
{
if (DateSelected != null) DateSelected.Invoke(sender, e);
}
public event EventHandler<DateChangedEventArgs>? DateSelected;
//-------------------------------------------------------------------
private void PickerTime_TimeChanged(object? sender, SamTimePicker.TimeChangedEventArgs e)
{
if (TimeChanged != null) TimeChanged.Invoke(sender, e);
}
public event EventHandler<SamTimePicker.TimeChangedEventArgs>? TimeChanged;
//-------------------------------------------------------------------
// --- The End ---
}
//=======================================================================
}
+18 -18
View File
@@ -1,4 +1,4 @@
using Sarand.Net.Mobile.Maui.App;
using Sarand.Net.Mobile.Maui.App;
using Sarand.Net.Standard.Base.Utils;
using System;
using System.Collections.Generic;
@@ -8,19 +8,15 @@ using System.Threading.Tasks;
namespace Sarand.Net.Mobile.Maui.Controls
{
//=======================================================================
public class SamEditor : Editor
public class SamEditor : Editor, ISamNativeControlStyle, ISamControl
{
//-------------------------------------------------------------------
// Definitions.
// --- Data Members ---
//-------------------------------------------------------------------
// Data members.
private bool trimTextOnCompletion = true;
private int NumberOfLinesPrevious = 0;
//-------------------------------------------------------------------
// Construction.
// --- Construction ---
public SamEditor()
{
DefaultFontSize = FontSize;
@@ -29,8 +25,15 @@ namespace Sarand.Net.Mobile.Maui.Controls
Completed += SamEditor_Completed;
}
//-------------------------------------------------------------------
// Properties.
// --- Component state ---
public void SetControlActivity(bool enabled)
{
if (IsEnabled != enabled) IsEnabled = enabled;
}
// --- Properties ---
public double DefaultFontSize { get; }
@@ -44,14 +47,13 @@ namespace Sarand.Net.Mobile.Maui.Controls
}
}
// Native behaviour modified by SamHandler.
// --- Native styling ---
public bool NativeNoBorder { get; set; } = Sam.Visuals.Form.Controls.NativeNoBorder;
public bool NativeNoUnderline { get; set; } = Sam.Visuals.Form.Controls.NativeNoUnderline;
public bool NativeBackgroundClear { get; set; } = Sam.Visuals.Form.Controls.NativeBackgroundClear;
//-------------------------------------------------------------------
// C o n t r o l E v e n t s
//-------------------------------------------------------------------
// --- Control events ---
// Key Press event is not supported directly in Xamarin.
// Requires O/S specific implementation.
// See: Sarand.LogisticsMgr.Mobile.Droid.Forms.Renderers.SlmEditorRenderer
@@ -60,7 +62,6 @@ namespace Sarand.Net.Mobile.Maui.Controls
// Resize control based on entered text.
InvalidateMeasure();
}
//-------------------------------------------------------------------
private void SamEditor_TextChanged(object? sender, TextChangedEventArgs e)
{
// If the number of lines of text has changed, resize the control.
@@ -78,7 +79,6 @@ namespace Sarand.Net.Mobile.Maui.Controls
InvalidateMeasure();
}
//-------------------------------------------------------------------
// --- The End ---
}
//=======================================================================
}
+32 -53
View File
@@ -1,4 +1,5 @@
using Sarand.Net.Mobile.Maui.App;
using Sarand.Net.Mobile.Maui.App;
using Sarand.Net.Mobile.Maui.Logic;
using Sarand.Net.Standard.Base.App;
using Sarand.Net.Standard.Base.Utils;
using System;
@@ -10,11 +11,10 @@ using System.Threading.Tasks;
namespace Sarand.Net.Mobile.Maui.Controls
{
//=======================================================================
public class SamEntry : Entry
public class SamEntry : Entry, ISamNativeControlStyle, ISamControl
{
//-------------------------------------------------------------------
// Definitions.
// --- Definitions ---
public enum TextCaseType
{
Normal,
@@ -23,16 +23,16 @@ namespace Sarand.Net.Mobile.Maui.Controls
Title
}
//-------------------------------------------------------------------
// Data members.
// --- Data Members ---
private bool trimTextOnCompletion = true;
private TextInfo textInfo = CultureInfo.CurrentCulture.TextInfo;
private TextCaseType textCase = TextCaseType.Normal;
private bool numericOnly = false;
private byte decimals = 0;
//-------------------------------------------------------------------
// Construction.
// --- Construction ---
public SamEntry()
{
DefaultFontSize = FontSize;
@@ -40,8 +40,15 @@ namespace Sarand.Net.Mobile.Maui.Controls
Completed += SamEntry_Completed;
}
//-------------------------------------------------------------------
// Properties.
// --- Component state ---
public void SetControlActivity(bool enabled)
{
if (IsEnabled != enabled) IsEnabled = enabled;
}
// --- Properties ---
public bool TrimText
{
get { return trimTextOnCompletion; }
@@ -95,52 +102,26 @@ namespace Sarand.Net.Mobile.Maui.Controls
}
}
// Native behaviour modified by SamHandler.
// --- Native styling ---
public bool NativeNoBorder { get; set; } = Sam.Visuals.Form.Controls.NativeNoBorder;
public bool NativeNoUnderline { get; set; } = Sam.Visuals.Form.Controls.NativeNoUnderline;
public bool NativeBackgroundClear { get; set; } = Sam.Visuals.Form.Controls.NativeBackgroundClear;
//-------------------------------------------------------------------
// F o r m a t T e x t
//-------------------------------------------------------------------
// --- Format text ---
private void FormatText(bool trimText = false)
{
String fn = GetType().Name + ".FormatText";
String originalText = Text;
String formattedText = originalText;
if (StringUtil.IsEmpty(formattedText)) return;
if (trimText)
formattedText = StringUtil.TrimAll(formattedText);
if (numericOnly)
SamEntryTextFormatOptions options = new SamEntryTextFormatOptions()
{
if (decimals > 0)
{
decimal value = StringUtil.ToDecimal(formattedText);
if (value != 0.0m)
formattedText = value.ToString("N" + decimals);
else if (!formattedText.Equals("0"))
formattedText = "";
}
else
{
long value = StringUtil.ToLong(formattedText);
if (value != 0)
formattedText = value.ToString();
else if (!formattedText.Equals("0"))
formattedText = "";
}
}
else
{
if (textCase == TextCaseType.Upper)
formattedText = textInfo.ToUpper(formattedText);
else if (textCase == TextCaseType.Lower)
formattedText = textInfo.ToLower(formattedText);
else if (textCase == TextCaseType.Title)
formattedText =
StringUtil.ToTitleCase(formattedText, textInfo);
}
TextCase = textCase,
NumericOnly = numericOnly,
Decimals = decimals,
TrimText = trimText,
TextInfo = textInfo
};
String formattedText = SamEntryTextFormatter.Format(originalText, options);
if (!originalText.Equals(formattedText))
{
@@ -149,9 +130,8 @@ namespace Sarand.Net.Mobile.Maui.Controls
}
}
//-------------------------------------------------------------------
// C o n t r o l E v e n t s
//-------------------------------------------------------------------
// --- Control events ---
private void SamEntry_TextChanged(object? sender, TextChangedEventArgs e)
{
if (FormatTextOnChange) FormatText();
@@ -161,7 +141,6 @@ namespace Sarand.Net.Mobile.Maui.Controls
FormatText(trimTextOnCompletion);
}
//-------------------------------------------------------------------
// --- The End ---
}
//=======================================================================
}
+16 -17
View File
@@ -1,4 +1,4 @@
using Sarand.Net.Standard.Base.App;
using Sarand.Net.Standard.Base.App;
using Sarand.Net.Standard.Base.Utils;
using System;
using System.Collections.Generic;
@@ -8,33 +8,32 @@ using System.Threading.Tasks;
namespace Sarand.Net.Mobile.Maui.Controls
{
//=======================================================================
public class SamImageButton : ImageButton
/// <summary>
/// Application image-button extension point. It currently adds no active
/// behavior beyond the MAUI <see cref="ImageButton"/> contract.
/// </summary>
public class SamImageButton : ImageButton, ISamControl
{
//-------------------------------------------------------------------
// Definitions.
// --- Construction ---
//-------------------------------------------------------------------
// Data members.
//-------------------------------------------------------------------
// Construction.
public SamImageButton()
{
//Clicked += Button_Clicked;
}
//-------------------------------------------------------------------
// Properties.
// --- Component state ---
public void SetControlActivity(bool enabled)
{
if (IsEnabled != enabled) IsEnabled = enabled;
}
// --- Events ---
//-------------------------------------------------------------------
// Events.
protected virtual void Button_Clicked(object? sender, EventArgs e)
{
SaLogger.Info(SysUtil.Fn + " Button clicked.");
}
//-------------------------------------------------------------------
// --- The End ---
}
//=======================================================================
}
+36 -27
View File
@@ -1,4 +1,4 @@
using Sarand.Net.Standard.Base.Utils;
using Sarand.Net.Standard.Base.Utils;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -8,19 +8,16 @@ using System.Threading.Tasks;
namespace Sarand.Net.Mobile.Maui.Controls
{
//=======================================================================
public class SamLabel : Label
public class SamLabel : Label, ISamControl
{
//-------------------------------------------------------------------
// Definitions.
// --- Data Members ---
//-------------------------------------------------------------------
// Data members.
private double fontSize;
private double fontSizeProportion = 1.0;
private bool applyingFontSize;
// --- Construction ---
//-------------------------------------------------------------------
// Construction.
public SamLabel()
{
fontSize = base.FontSize;
@@ -28,8 +25,8 @@ namespace Sarand.Net.Mobile.Maui.Controls
Text = "";
}
//-------------------------------------------------------------------
// Properties.
// --- Properties ---
/// <summary>Default (initial) font size.</summary>
public double FontSizeDefault { get; private set; }
@@ -37,42 +34,54 @@ namespace Sarand.Net.Mobile.Maui.Controls
public new double FontSize
{
get { return fontSize; }
set { fontSize = value; base.FontSize = fontSize * fontSizeProportion; }
set
{
if (fontSize == value) return;
fontSize = value;
ApplyFontSize();
}
}
/// <summary>Font size proportion.</summary>
public double FontSizeProportion
{
get { return fontSizeProportion; }
set { fontSizeProportion = value; base.FontSize = fontSize * fontSizeProportion; }
set
{
if (fontSizeProportion == value) return;
fontSizeProportion = value;
ApplyFontSize();
}
}
//-------------------------------------------------------------------
// Control events.
// --- Font sizing ---
//-------------------------------------------------------------------
// View events.
protected override void OnPropertyChanging([CallerMemberName] string propertyName = "")
private void ApplyFontSize()
{
base.OnPropertyChanging(propertyName);
if (propertyName.EqualsCI("FontSize"))
{
}
applyingFontSize = true;
base.FontSize = fontSize * fontSizeProportion;
applyingFontSize = false;
}
protected override void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
base.OnPropertyChanged(propertyName);
if (propertyName.EqualsCI("FontSize"))
if (propertyName.EqualsCI("FontSize") && !applyingFontSize)
{
if (FontSizeDefault <= 0)
FontSizeDefault = base.FontSize;
fontSize = base.FontSize;
if (fontSizeProportion != 0)
fontSize = base.FontSize / fontSizeProportion;
}
}
//-------------------------------------------------------------------
// --- Component state ---
public void SetControlActivity(bool enabled)
{
if (IsEnabled != enabled) IsEnabled = enabled;
}
// --- The End ---
}
//=======================================================================
}
+51 -44
View File
@@ -1,4 +1,5 @@
using Sarand.Net.Mobile.Maui.App;
using Sarand.Net.Mobile.Maui.App;
using Sarand.Net.Mobile.Maui.Logic;
using Sarand.Net.Standard.Base.Bus;
using Sarand.Net.Standard.Base.Utils;
using System;
@@ -9,11 +10,10 @@ using System.Threading.Tasks;
namespace Sarand.Net.Mobile.Maui.Controls
{
//=======================================================================
public class SamPicker : Picker
public class SamPicker : Picker, ISamNativeControlStyle, ISamControl
{
//-------------------------------------------------------------------
// Definitions.
// --- Definitions ---
public class SamPickerItem
{
public SysId Id { get; set; } = new SysId();
@@ -26,38 +26,37 @@ namespace Sarand.Net.Mobile.Maui.Controls
}
}
//-------------------------------------------------------------------
// Data members.
private readonly List<SamPickerItem> pickerItems = new List<SamPickerItem>();
// --- Data Members ---
private readonly SamPickerItemCollection pickerItems = new SamPickerItemCollection();
// --- Construction ---
//-------------------------------------------------------------------
// Construction.
public SamPicker()
{
DefaultFontSize = FontSize;
}
//-------------------------------------------------------------------
// Properties.
// --- Properties ---
public double DefaultFontSize { get; }
/// <summary>Whether the picker is currently replacing its item list.</summary>
public bool BeingPopulated { get; protected set; } = false;
// Native behaviour modified by SamHandler.
// --- Native styling ---
public bool NativeNoBorder { get; set; } = Sam.Visuals.Form.Controls.NativeNoBorder;
public bool NativeNoUnderline { get; set; } = Sam.Visuals.Form.Controls.NativeNoUnderline;
public bool NativeBackgroundClear { get; set; } = Sam.Visuals.Form.Controls.NativeBackgroundClear;
//-------------------------------------------------------------------
// P o p u l a t e I t e m s
//-------------------------------------------------------------------
// --- Item population ---
/// <summary>
/// Does the list contain an item with the same Id.
/// <para>NB Invalid Id values are matched.</para>
/// </summary>
public bool ContainsItemId(SysId id)
{
return pickerItems.Exists(item => SysId.ToInt(item.Id) == SysId.ToInt(id));
return pickerItems.ContainsId(id);
}
/// <summary>
/// Does the list contain an item with the same code.
@@ -65,9 +64,8 @@ namespace Sarand.Net.Mobile.Maui.Controls
/// </summary>
public bool ContainsItemCode(String code)
{
return pickerItems.Exists(item => item.Code.EqualsCI(code));
return pickerItems.ContainsCode(code);
}
//-------------------------------------------------------------------
/// <summary>Remove all items from the picker.</summary>
public void ClearItems()
{
@@ -75,22 +73,31 @@ namespace Sarand.Net.Mobile.Maui.Controls
ItemsSource = null;
pickerItems.Clear();
}
/// <summary>Replace existing picker item list.</summary>
/// <summary>Replace existing picker item list and raise <see cref="Populated"/>.</summary>
public void SetItems(List<SamPickerItem> pickerItems)
{
ClearItems();
this.pickerItems.AddRange(pickerItems);
RefreshPickerItems();
BeingPopulated = true;
bool populated = false;
try
{
ClearItems();
this.pickerItems.Replace(pickerItems);
RefreshPickerItems();
populated = true;
}
finally
{
BeingPopulated = false;
if (populated) InvokePopulatedEvent();
}
}
//-------------------------------------------------------------------
/// <summary>
/// Add an item identified by a SysId value.
/// <para>NB Fails if an item already exists with the same id.</para>
/// </summary>
public bool AddItem(SysId id, String caption)
{
if (ContainsItemId(id)) return false;
pickerItems.Add(new SamPickerItem() { Id = id, Caption = caption });
if (!pickerItems.AddById(new SamPickerItem() { Id = id, Caption = caption })) return false;
RefreshPickerItems();
return ContainsItemId(id);
}
@@ -100,25 +107,20 @@ namespace Sarand.Net.Mobile.Maui.Controls
/// </summary>
public bool AddItem(String code, String caption)
{
if (ContainsItemCode(code)) return false;
pickerItems.Add(new SamPickerItem() { Code = code, Caption = caption });
if (!pickerItems.AddByCode(new SamPickerItem() { Code = code, Caption = caption })) return false;
RefreshPickerItems();
return ContainsItemCode(code);
}
//-------------------------------------------------------------------
private void RefreshPickerItems()
{
//SetBinding(Picker.ItemsSourceProperty, BindableProperty.Create());
//ItemDisplayBinding = new Binding("Caption");
// Picker items doesn't automatically keep up to date with teh ItemsSource list.
// So clear it and reset to force the refresh.
// Picker items do not automatically keep the ItemsSource display in sync.
// Clear and reset the source to force a refresh.
ItemsSource = null;
ItemsSource = pickerItems;
ItemsSource = pickerItems.Items;
}
//-------------------------------------------------------------------
// I t e m S e l e c t i o n
//-------------------------------------------------------------------
// --- Item selection ---
public SamPickerItem? SelectedPickerItem
{
get
@@ -143,7 +145,7 @@ namespace Sarand.Net.Mobile.Maui.Controls
}
set
{
SelectedPickerItem = pickerItems.Find(item => item.Id.Equals(value));
SelectedPickerItem = pickerItems.FindById(value);
}
}
public String SelectedItemCode
@@ -155,13 +157,12 @@ namespace Sarand.Net.Mobile.Maui.Controls
}
set
{
SelectedPickerItem = pickerItems.Find(item => item.Code.EqualsCI(value));
SelectedPickerItem = pickerItems.FindByCode(value);
}
}
//-------------------------------------------------------------------
// C o n t r o l E v e n t s
//-------------------------------------------------------------------
// --- Control events ---
// Picker population completed.
public class PopulatedEventArgs : EventArgs { }
public delegate void PopulatedEventHandler(object? sender, PopulatedEventArgs e);
@@ -172,7 +173,13 @@ namespace Sarand.Net.Mobile.Maui.Controls
if (Populated != null) Populated.Invoke(this, new PopulatedEventArgs() { });
}
//-------------------------------------------------------------------
// --- Component state ---
public void SetControlActivity(bool enabled)
{
if (IsEnabled != enabled) IsEnabled = enabled;
}
// --- The End ---
}
//=======================================================================
}
+35 -35
View File
@@ -1,5 +1,6 @@
using Microsoft.Maui;
using Microsoft.Maui;
using Microsoft.Maui.Layouts;
using Sarand.Net.Mobile.Maui.App;
using Sarand.Net.Mobile.Maui.Layouts;
using Sarand.Net.Mobile.Maui.Panels;
using Sarand.Net.Mobile.Maui.Views;
@@ -11,14 +12,10 @@ using System.Threading.Tasks;
namespace Sarand.Net.Mobile.Maui.Controls
{
//=======================================================================
public class SamTabControl : ContentView
public class SamTabControl : ContentView, ISamView
{
//-------------------------------------------------------------------
// Definitions.
// --- Data Members ---
//-------------------------------------------------------------------
// Data members.
private readonly List<SamTabPanel> tabPanels = new List<SamTabPanel>();
private short currentTabIdx = -1;
// Main layout to host header and body.
@@ -28,39 +25,40 @@ namespace Sarand.Net.Mobile.Maui.Controls
// Body - Content view to host tab panel.
private SamContentView bodyPanel = new SamContentView();
//-------------------------------------------------------------------
// Construction.
// --- Construction ---
public SamTabControl()
{
// Main layout - vertical stack with two rows.
// 1. Header - tab panel captions.
// 2. Body - current tab panel.
// --- Main layout ---
VerticalOptions = LayoutOptions.FillAndExpand;
//BackgroundColor = Color.LightSkyBlue;
mainLayout.VerticalOptions = LayoutOptions.FillAndExpand;
mainLayout.Spacing = 2.0;
Content = mainLayout;
// Header panel - tab captions.
//headerPanel.HorizontalOptions = LayoutOptions.Fill;
// --- Header panel ---
headerPanel.BackgroundColor = Sam.Visuals.Form.TabControl.HeaderBackgroundColor;
mainLayout.Children.Add(headerPanel);
// Default label font size.
// --- Default font size ---
DefaultHeaderFontSize = new Label().FontSize;
HeaderFontSize = DefaultHeaderFontSize;
// Body panel.
// --- Body panel ---
bodyPanel.HorizontalOptions = LayoutOptions.Fill;
bodyPanel.VerticalOptions = LayoutOptions.FillAndExpand;
bodyPanel.BackgroundColor = Colors.LightYellow;
bodyPanel.BackgroundColor = Sam.Visuals.Form.TabControl.BodyBackgroundColor;
mainLayout.Children.Add(bodyPanel);
}
//-------------------------------------------------------------------
// Properties.
// --- Properties ---
// Header.
public Color HeaderBackgroundColor { get { return headerPanel.BackgroundColor; } set { headerPanel.BackgroundColor = value; } }
public Color CaptionBackgroundColor { get; set; } = Colors.Transparent;
public Color CaptionBackgroundColor { get; set; } = Sam.Visuals.Form.TabControl.CaptionBackgroundColor;
public double DefaultHeaderFontSize { get; }
public double HeaderFontSize { get; set; }
public double HeaderMarginBottom
@@ -84,9 +82,8 @@ namespace Sarand.Net.Mobile.Maui.Controls
set { ChangeTabPanel(value); }
}
//-------------------------------------------------------------------
// T a b P a n e l s
//-------------------------------------------------------------------
// --- Tab panels ---
public void ClearPanels()
{
tabPanels.Clear();
@@ -106,9 +103,8 @@ namespace Sarand.Net.Mobile.Maui.Controls
RefreshLayout();
}
//-------------------------------------------------------------------
// R e f r e s h L a y o u t
//-------------------------------------------------------------------
// --- Refresh layout ---
private void RefreshLayout()
{
// Clear existing components.
@@ -152,9 +148,8 @@ namespace Sarand.Net.Mobile.Maui.Controls
ChangeTabPanel(currentTabIdx);
}
//-------------------------------------------------------------------
// C h a n g e T a b P a n e l
//-------------------------------------------------------------------
// --- Change tab panel ---
private void ChangeTabPanel(short tabPanelIdx)
{
// Validate requested panel index.
@@ -183,11 +178,16 @@ namespace Sarand.Net.Mobile.Maui.Controls
currentTabIdx = tabPanelIdx;
}
//-------------------------------------------------------------------
// C o n t r o l E v e n t s
//-------------------------------------------------------------------
// --- Component state ---
//-------------------------------------------------------------------
public void ConfigureComponents() { }
public void SetControlActivity(bool enabled)
{
if (IsEnabled != enabled) IsEnabled = enabled;
SetControlsReadOnly();
}
public void SetControlsReadOnly() { }
// --- The End ---
}
//=======================================================================
}
@@ -9,7 +9,7 @@ using System.Threading.Tasks;
namespace Sarand.Net.Mobile.Maui.Controls
{
//=======================================================================
public class SamTimePicker : TimePicker
public class SamTimePicker : TimePicker, ISamControl
{
//-------------------------------------------------------------------
// Definitions.
@@ -47,6 +47,13 @@ namespace Sarand.Net.Mobile.Maui.Controls
base.OnPropertyChanged(propertyName);
}
// --- Component state ---
public void SetControlActivity(bool enabled)
{
if (IsEnabled != enabled) IsEnabled = enabled;
}
//-------------------------------------------------------------------
}
//=======================================================================
+27 -26
View File
@@ -1,4 +1,4 @@
using Sarand.Net.Standard.Base.App;
using Sarand.Net.Standard.Base.App;
using Sarand.Net.Standard.Base.Utils;
using System;
using System.Collections.Generic;
@@ -8,36 +8,33 @@ using System.Threading.Tasks;
namespace Sarand.Net.Mobile.Maui.Controls
{
//=======================================================================
public class SamWebView : WebView
public class SamWebView : WebView, ISamControl
{
//-------------------------------------------------------------------
// Definitions.
// --- Data Members ---
//-------------------------------------------------------------------
// Data members.
public String html = "";
private HtmlWebViewSource htmlSource;
private UrlWebViewSource aboutBlankSource = new UrlWebViewSource() { Url = "about:blank" };
// Managers.
private readonly HtmlWebViewSource htmlSource;
private readonly UrlWebViewSource aboutBlankSource = new UrlWebViewSource() { Url = "about:blank" };
// --- Construction ---
//-------------------------------------------------------------------
// Construction.
public SamWebView()
{
// Events.
// --- Events ---
Navigating += WebView_Navigating;
Navigated += WebView_Navigated;
// HTML source.
// --- HTML source ---
htmlSource = new HtmlWebViewSource() { Html = html };
Source = aboutBlankSource;
}
//-------------------------------------------------------------------
// Properties.
// HTML Panel.
// --- Properties ---
public static String AppActionPrefix => "App:";
/// <summary>Gets or sets the HTML displayed by the web view.</summary>
public String Html
{
get { return html; }
@@ -51,9 +48,8 @@ namespace Sarand.Net.Mobile.Maui.Controls
}
}
//-------------------------------------------------------------------
// N a v i g a t i o n
//-------------------------------------------------------------------
// --- Navigation ---
public class AppNavigatingEventArgs : EventArgs
{
public AppNavigatingEventArgs(String appAction)
@@ -70,13 +66,12 @@ namespace Sarand.Net.Mobile.Maui.Controls
}
private String GetAppAction(String location)
{
if (!location.StartsWithCI(AppActionPrefix)) return "";
if (String.IsNullOrEmpty(location) || !location.StartsWithCI(AppActionPrefix)) return "";
return location.Right(location.Length - AppActionPrefix.Length);
}
//-------------------------------------------------------------------
// C o n t r o l E v e n t s
//-------------------------------------------------------------------
// --- Control events ---
private void WebView_Navigating(object? sender, WebNavigatingEventArgs e)
{
String appAction = GetAppAction(e.Url);
@@ -97,7 +92,13 @@ namespace Sarand.Net.Mobile.Maui.Controls
}
}
//-------------------------------------------------------------------
// --- Component state ---
public void SetControlActivity(bool enabled)
{
if (IsEnabled != enabled) IsEnabled = enabled;
}
// --- The End ---
}
//=======================================================================
}
+33 -26
View File
@@ -1,4 +1,4 @@
#if ANDROID
#if ANDROID
using Microsoft.Maui.Controls.Compatibility.Platform.Android;
#endif
using Sarand.Net.Mobile.Maui.Controls;
@@ -10,26 +10,25 @@ using System.Threading.Tasks;
namespace Sarand.Net.Mobile.Maui.Handlers
{
//========================================================================
// Ref. https://dev.to/vhugogarcia/remove-entry-and-picker-borders-in-net-maui-2pk2
// Ref. https://github.com/dotnet/maui/issues/7906
public static class SamFormHandler
{
//--------------------------------------------------------------------
// Definitions.
// --- Control handler registration ---
//--------------------------------------------------------------------
/// <summary>
/// Remove borders around entry and picker controls.
/// Apply configured native styling to Sam entry, editor, and picker controls.
/// <para>Also make background transparent.</para>
/// </summary>
public static void AssignControlHandlers()
{
//---------------------------------------------------------------
// Entry handler.
// --- Entry handler ---
Microsoft.Maui.Handlers.EntryHandler.Mapper.AppendToMapping("SamEntryHandler", (handler, view) =>
{
if ((view as SamEntry)?.NativeNoBorder ?? false)
if (view is not ISamNativeControlStyle style) return;
if (style.NativeNoBorder)
{
#if ANDROID
handler.PlatformView.BackgroundTintList = Android.Content.Res.ColorStateList.ValueOf(Colors.Transparent.ToAndroid());
@@ -40,15 +39,17 @@ namespace Sarand.Net.Mobile.Maui.Handlers
handler.PlatformView.BorderThickness = new Microsoft.UI.Xaml.Thickness() { Bottom = 0, Top = 0, Left = 0, Right = 0, };
#endif
}
if ((view as SamEntry)?.NativeNoUnderline ?? false)
if (style.NativeNoUnderline)
{
#if ANDROID
handler.PlatformView.BackgroundTintList = Android.Content.Res.ColorStateList.ValueOf(Colors.Transparent.ToAndroid());
#elif IOS || MACCATALYST
// UITextField/UITextView do not expose a separate Android-style underline.
#elif WINDOWS
// WinUI text controls use a border, not a separate underline; use NativeNoBorder to remove it.
#endif
}
if ((view as SamEntry)?.NativeBackgroundClear ?? false)
if (style.NativeBackgroundClear)
{
#if ANDROID
handler.PlatformView.Background = null;
@@ -56,34 +57,37 @@ namespace Sarand.Net.Mobile.Maui.Handlers
#elif IOS || MACCATALYST
handler.PlatformView.BackgroundColor = UIKit.UIColor.Clear;
#elif WINDOWS
handler.PlatformView.Background = new Microsoft.UI.Xaml.Media.SolidColorBrush(Microsoft.UI.Colors.Transparent);
#endif
}
});
//---------------------------------------------------------------
// Editor handler.
// --- Editor handler ---
Microsoft.Maui.Handlers.EditorHandler.Mapper.AppendToMapping("SamEditorHandler", (handler, view) =>
{
if ((view as SamEditor)?.NativeNoBorder ?? false)
if (view is not ISamNativeControlStyle style) return;
if (style.NativeNoBorder)
{
#if ANDROID
handler.PlatformView.BackgroundTintList = Android.Content.Res.ColorStateList.ValueOf(Colors.Transparent.ToAndroid());
#elif IOS || MACCATALYST
handler.PlatformView.Layer.BorderWidth = 0;
//handler.PlatformView.BorderStyle = UIKit.UITextViewBorderStyle.None;
#elif WINDOWS
handler.PlatformView.BorderThickness = new Microsoft.UI.Xaml.Thickness() { Bottom = 0, Top = 0, Left = 0, Right = 0, };
#endif
}
if ((view as SamEntry)?.NativeNoUnderline ?? false)
if (style.NativeNoUnderline)
{
#if ANDROID
handler.PlatformView.BackgroundTintList = Android.Content.Res.ColorStateList.ValueOf(Colors.Transparent.ToAndroid());
#elif IOS || MACCATALYST
// UITextView has no separate underline to remove.
#elif WINDOWS
// WinUI text controls use a border, not a separate underline; use NativeNoBorder to remove it.
#endif
}
if ((view as SamEntry)?.NativeBackgroundClear ?? false)
if (style.NativeBackgroundClear)
{
#if ANDROID
handler.PlatformView.Background = null;
@@ -91,15 +95,17 @@ namespace Sarand.Net.Mobile.Maui.Handlers
#elif IOS || MACCATALYST
handler.PlatformView.BackgroundColor = UIKit.UIColor.Clear;
#elif WINDOWS
handler.PlatformView.Background = new Microsoft.UI.Xaml.Media.SolidColorBrush(Microsoft.UI.Colors.Transparent);
#endif
}
});
//---------------------------------------------------------------
// Picker handler.
Microsoft.Maui.Handlers.PickerHandler.Mapper.AppendToMapping("Borderless", (handler, view) =>
// --- Picker handler ---
Microsoft.Maui.Handlers.PickerHandler.Mapper.AppendToMapping("SamPickerHandler", (handler, view) =>
{
if ((view as SamEditor)?.NativeNoBorder ?? false)
if (view is not ISamNativeControlStyle style) return;
if (style.NativeNoBorder)
{
#if ANDROID
handler.PlatformView.BackgroundTintList = Android.Content.Res.ColorStateList.ValueOf(Colors.Transparent.ToAndroid());
@@ -110,15 +116,17 @@ namespace Sarand.Net.Mobile.Maui.Handlers
handler.PlatformView.BorderThickness = new Microsoft.UI.Xaml.Thickness() { Bottom = 0, Top = 0, Left = 0, Right = 0, };
#endif
}
if ((view as SamEntry)?.NativeNoUnderline ?? false)
if (style.NativeNoUnderline)
{
#if ANDROID
handler.PlatformView.BackgroundTintList = Android.Content.Res.ColorStateList.ValueOf(Colors.Transparent.ToAndroid());
#elif IOS || MACCATALYST
// The native picker has a full border, not a separate underline; use NativeNoBorder to remove it.
#elif WINDOWS
// The WinUI picker has a full border, not a separate underline; use NativeNoBorder to remove it.
#endif
}
if ((view as SamEntry)?.NativeBackgroundClear ?? false)
if (style.NativeBackgroundClear)
{
#if ANDROID
handler.PlatformView.Background = null;
@@ -126,14 +134,13 @@ namespace Sarand.Net.Mobile.Maui.Handlers
#elif IOS || MACCATALYST
handler.PlatformView.BackgroundColor = UIKit.UIColor.Clear;
#elif WINDOWS
handler.PlatformView.Background = new Microsoft.UI.Xaml.Media.SolidColorBrush(Microsoft.UI.Colors.Transparent);
#endif
}
});
//---------------------------------------------------------------
}
//--------------------------------------------------------------------
// --- The End ---
}
//========================================================================
}
@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
+1 -1
View File
@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
+1 -1
View File
@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
+8 -12
View File
@@ -1,4 +1,4 @@
using Microsoft.Maui.Controls.Compatibility;
using Microsoft.Maui.Controls.Compatibility;
namespace Sarand.Net.Mobile.Maui.Layouts
{
@@ -184,17 +184,13 @@ namespace Sarand.Net.Mobile.Maui.Layouts
{
child = c,
size = c.Measure(widthConstraint, heightConstraint)
});
}).ToList();
var nextChildren = visibleChildren.Skip(1).ToList();
nextChildren.Add(null); //make element count same
var zipChildren = visibleChildren.Zip(nextChildren, (c, n) => new { current = c, next = n });
foreach (var childBlock in zipChildren)
for (var childIndex = 0; childIndex < visibleChildren.Count; childIndex++)
{
var child = childBlock.current.child;
var size = childBlock.current.size;
var childBlock = visibleChildren[childIndex];
var child = childBlock.child;
var size = childBlock.size;
var itemWidth = size.Width;
var itemHeight = size.Height;
@@ -210,7 +206,7 @@ namespace Sarand.Net.Mobile.Maui.Layouts
LayoutChildIntoBoundingRegion(child, region);
}
if (childBlock.next == null)
if (childIndex == visibleChildren.Count - 1)
{
totalHeight += rowHeight;
totalWidth = Math.Max(totalWidth, rowWidth);
@@ -218,7 +214,7 @@ namespace Sarand.Net.Mobile.Maui.Layouts
}
xPos += itemWidth + SpacingX;
var nextWitdh = childBlock.next.size.Width;
var nextWitdh = visibleChildren[childIndex + 1].size.Width;
if (xPos + nextWitdh - x > widthConstraint)
{
@@ -0,0 +1,44 @@
using Sarand.Net.Standard.Base.Utils;
using System;
namespace Sarand.Net.Mobile.Maui.Logic
{
// --- Date value state ---
public sealed class SamDateValueState
{
private DateTime date = DateTime.MinValue;
private bool invalid = true;
public DateTime Date { get { return invalid ? DateTime.MinValue : date; } }
public bool IsInvalid { get { return invalid; } }
public bool SetDate(DateTime value)
{
if (Date.Equals(value)) return false;
if (DateUtil.IsValid(value))
{
date = value;
invalid = false;
}
else
{
date = DateTime.MinValue;
invalid = true;
}
return true;
}
public bool TryInvalidate(out DateTime previousDate)
{
if (invalid)
{
previousDate = DateTime.MinValue;
return false;
}
previousDate = date;
date = DateTime.MinValue;
invalid = true;
return true;
}
}
}
@@ -0,0 +1,18 @@
using Sarand.Net.Mobile.Maui.Controls;
using System;
using System.Globalization;
namespace Sarand.Net.Mobile.Maui.Logic
{
// --- Entry text format options ---
public sealed class SamEntryTextFormatOptions
{
public SamEntry.TextCaseType TextCase { get; init; } = SamEntry.TextCaseType.Normal;
public bool NumericOnly { get; init; }
public byte Decimals { get; init; }
public bool TrimText { get; init; }
public TextInfo TextInfo { get; init; } = CultureInfo.CurrentCulture.TextInfo;
// --- The End ---
}
}
@@ -0,0 +1,48 @@
using Sarand.Net.Standard.Base.Utils;
using System;
namespace Sarand.Net.Mobile.Maui.Logic
{
// --- Entry text formatting ---
public static class SamEntryTextFormatter
{
public static String Format(String? text, SamEntryTextFormatOptions options)
{
String originalText = text ?? "";
String formattedText = originalText;
if (StringUtil.IsEmpty(formattedText)) return formattedText;
if (options.TrimText)
formattedText = StringUtil.TrimAll(formattedText);
if (options.NumericOnly)
{
if (options.Decimals > 0)
{
decimal value = StringUtil.ToDecimal(formattedText);
if (value != 0.0m)
formattedText = value.ToString("N" + options.Decimals);
else if (!formattedText.Equals("0"))
formattedText = "";
}
else
{
long value = StringUtil.ToLong(formattedText);
if (value != 0)
formattedText = value.ToString();
else if (!formattedText.Equals("0"))
formattedText = "";
}
}
else if (options.TextCase == Controls.SamEntry.TextCaseType.Upper)
formattedText = options.TextInfo.ToUpper(formattedText);
else if (options.TextCase == Controls.SamEntry.TextCaseType.Lower)
formattedText = options.TextInfo.ToLower(formattedText);
else if (options.TextCase == Controls.SamEntry.TextCaseType.Title)
formattedText = StringUtil.ToTitleCase(formattedText, options.TextInfo);
return formattedText;
}
// --- The End ---
}
}
@@ -0,0 +1,61 @@
using Sarand.Net.Mobile.Maui.Controls;
using Sarand.Net.Standard.Base.Bus;
using Sarand.Net.Standard.Base.Utils;
using System;
using System.Collections.Generic;
namespace Sarand.Net.Mobile.Maui.Logic
{
// --- Picker item collection ---
public sealed class SamPickerItemCollection
{
private readonly List<SamPicker.SamPickerItem> items = new List<SamPicker.SamPickerItem>();
public List<SamPicker.SamPickerItem> Items { get { return items; } }
public void Clear()
{
items.Clear();
}
public void Replace(IEnumerable<SamPicker.SamPickerItem> replacementItems)
{
items.Clear();
items.AddRange(replacementItems);
}
public bool ContainsId(SysId id)
{
return items.Exists(item => SysId.ToInt(item.Id) == SysId.ToInt(id));
}
public bool ContainsCode(String code)
{
return items.Exists(item => item.Code.EqualsCI(code));
}
public bool AddById(SamPicker.SamPickerItem item)
{
if (ContainsId(item.Id)) return false;
items.Add(item);
return true;
}
public bool AddByCode(SamPicker.SamPickerItem item)
{
if (ContainsCode(item.Code)) return false;
items.Add(item);
return true;
}
public SamPicker.SamPickerItem? FindById(SysId id)
{
return items.Find(item => item.Id.Equals(id));
}
public SamPicker.SamPickerItem? FindByCode(String code)
{
return items.Find(item => item.Code.EqualsCI(code));
}
}
}
@@ -0,0 +1,36 @@
using Sarand.Net.Mobile.Maui.Views;
using Sarand.Net.Mobile.Maui.Controls;
using System.Collections.Generic;
namespace Sarand.Net.Mobile.Maui.Logic
{
// --- Sam view tree operations ---
public static class SamViewTreeUtil
{
public static void ConfigureChildren(Element? root)
{
foreach (Element element in GetElements(root))
if (element is ISamView samView)
samView.ConfigureComponents();
}
public static void SetChildControlActivity(Element? root, bool enabled)
{
foreach (Element element in GetElements(root))
{
if (element is ISamView samView)
samView.SetControlActivity(enabled);
if (element is ISamControl samControl)
samControl.SetControlActivity(enabled);
}
}
private static IEnumerable<Element> GetElements(Element? root)
{
if (root == null) yield break;
yield return root;
foreach (Element descendant in root.GetVisualTreeDescendants())
yield return descendant;
}
}
}
+22 -47
View File
@@ -1,4 +1,5 @@
using Microsoft.Maui.Controls;
using Microsoft.Maui.Controls;
using Sarand.Net.Mobile.Maui.Logic;
using Sarand.Net.Mobile.Maui.Views;
using Sarand.Net.Standard.Base.Utils;
using System;
@@ -11,7 +12,7 @@ using System.Threading.Tasks;
namespace Sarand.Net.Mobile.Maui.Pages
{
//=======================================================================
public abstract class SamContentPage : ContentPage
public abstract class SamContentPage : ContentPage, ISamView
{
//-------------------------------------------------------------------
// Definitions.
@@ -38,61 +39,35 @@ namespace Sarand.Net.Mobile.Maui.Pages
protected virtual void ConfigureChildComponents()
{
// Configure child components.
if (Content != null)
{
ConfigureChildComponent(Content);
foreach (var descendent in Content.GetVisualTreeDescendants())
{
View? childView = descendent as View;
if (childView != null)
ConfigureChildComponent(childView);
}
}
}
private void ConfigureChildComponent(View view)
{
if (view is ISamView)
{
ISamView? samView = view as ISamView;
if (samView != null)
samView.ConfigureComponents();
}
SamViewTreeUtil.ConfigureChildren(Content);
}
//-------------------------------------------------------------------
// C o n t r o l A c t i v i t y
//-------------------------------------------------------------------
protected virtual void SetControlActivity()
protected virtual void SetControlActivity(bool enabled)
{
if (IsEnabled != enabled) IsEnabled = enabled;
SetControlsReadOnly();
// Child component control activity.
if (Content != null)
{
SetChildControlActivity(Content);
foreach (var descendent in Content.GetVisualTreeDescendants())
{
View? childView = descendent as View;
if (childView != null)
SetChildControlActivity(childView);
}
}
}
private void SetChildControlActivity(View view)
{
if (view is ISamView)
{
ISamView? samView = view as ISamView;
if (samView != null)
samView.SetControlActivity();
}
SamViewTreeUtil.SetChildControlActivity(Content, enabled);
}
protected virtual void SetControlsReadOnly()
{
}
void ISamView.ConfigureComponents()
{
ConfigureComponents();
}
void ISamView.SetControlActivity(bool enabled)
{
SetControlActivity(enabled);
}
void ISamView.SetControlsReadOnly()
{
SetControlsReadOnly();
}
//-------------------------------------------------------------------
// P o p u p M e s s a g e s
//-------------------------------------------------------------------
@@ -107,7 +82,7 @@ namespace Sarand.Net.Mobile.Maui.Pages
String cancel = "OK";
if (alertMode == AlertMode.Close)
cancel = "Close";
return DisplayAlert(title, message, cancel);
return DisplayAlertAsync(title, message, cancel);
}
public enum ConfirmMode
{
@@ -124,7 +99,7 @@ namespace Sarand.Net.Mobile.Maui.Pages
accept = "OK";
cancel = "Cancel";
}
return await DisplayAlert(title, message, accept, cancel);
return await DisplayAlertAsync(title, message, accept, cancel);
}
//-------------------------------------------------------------------
+4 -2
View File
@@ -1,4 +1,4 @@
using Sarand.Net.Mobile.Maui.Views;
using Sarand.Net.Mobile.Maui.Views;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -37,11 +37,13 @@ namespace Sarand.Net.Mobile.Maui.Panels
//-------------------------------------------------------------------
// C o n t r o l A c t i v i t y
//-------------------------------------------------------------------
public override void SetControlActivity()
public override void SetControlActivity(bool enabled)
{
base.SetControlActivity(enabled);
}
public override void SetControlsReadOnly()
{
base.SetControlsReadOnly();
}
//-------------------------------------------------------------------
@@ -1,4 +1,4 @@
using Sarand.Net.Mobile.Maui.Layouts;
using Sarand.Net.Mobile.Maui.Layouts;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -1,4 +1,4 @@
using Sarand.Net.Mobile.Maui.Controls;
using Sarand.Net.Mobile.Maui.Controls;
using Sarand.Net.Mobile.Maui.Layouts;
using Sarand.Net.Standard.Base.Utils;
using System;
@@ -1,4 +1,4 @@
using Sarand.Net.Mobile.Maui.Controls;
using Sarand.Net.Mobile.Maui.Controls;
using Sarand.Net.Mobile.Maui.Layouts;
using Sarand.Net.Mobile.Maui.Utils;
using Sarand.Net.Mobile.Maui.Views;
@@ -1,4 +1,4 @@
using Sarand.Net.Mobile.Maui.Controls;
using Sarand.Net.Mobile.Maui.Controls;
using Sarand.Net.Mobile.Maui.Layouts;
using Sarand.Net.Mobile.Maui.Utils;
using Sarand.Net.Mobile.Maui.Views;
@@ -1,4 +1,4 @@
using Sarand.Net.Mobile.Maui.Controls;
using Sarand.Net.Mobile.Maui.Controls;
using Sarand.Net.Mobile.Maui.Layouts;
using Sarand.Net.Standard.Base.Core;
using System;
@@ -1,4 +1,4 @@
using Sarand.Net.Mobile.Maui.App;
using Sarand.Net.Mobile.Maui.App;
using Sarand.Net.Mobile.Maui.Controls;
using Sarand.Net.Mobile.Maui.Layouts;
using Sarand.Net.Mobile.Maui.Utils;
@@ -1,4 +1,4 @@
using Microsoft.Maui.Controls.Internals;
using Microsoft.Maui.Controls.Internals;
using Sarand.Net.Mobile.Maui.App;
using Sarand.Net.Mobile.Maui.Controls;
using Sarand.Net.Mobile.Maui.Layouts;
@@ -1,4 +1,4 @@
using Sarand.Net.Mobile.Maui.Controls;
using Sarand.Net.Mobile.Maui.Controls;
using Sarand.Net.Mobile.Maui.Layouts;
using Sarand.Net.Mobile.Maui.Views;
using System;
@@ -1,4 +1,4 @@
using Sarand.Net.Mobile.Maui.Controls;
using Sarand.Net.Mobile.Maui.Controls;
using Sarand.Net.Mobile.Maui.Layouts;
using Sarand.Net.Mobile.Maui.Views;
using System;
+4 -2
View File
@@ -1,4 +1,4 @@
using Sarand.Net.Mobile.Maui.Views;
using System;
using System.Collections.Generic;
@@ -38,11 +38,13 @@ namespace Sarand.Net.Mobile.Maui.Panels
//-------------------------------------------------------------------
// C o n t r o l A c t i v i t y
//-------------------------------------------------------------------
public override void SetControlActivity()
public override void SetControlActivity(bool enabled)
{
base.SetControlActivity(enabled);
}
public override void SetControlsReadOnly()
{
base.SetControlsReadOnly();
}
//-------------------------------------------------------------------
@@ -1,4 +1,4 @@
using Sarand.Net.Mobile.Maui.Views;
using Sarand.Net.Mobile.Maui.Views;
using Sarand.Net.Standard.Base.Utils;
using System;
using System.Collections.Generic;
@@ -165,11 +165,13 @@ namespace Sarand.Net.Mobile.Maui.Panels
//-------------------------------------------------------------------
// C o n t r o l A c t i v i t y
//-------------------------------------------------------------------
public override void SetControlActivity()
public override void SetControlActivity(bool enabled)
{
base.SetControlActivity(enabled);
}
public override void SetControlsReadOnly()
{
base.SetControlsReadOnly();
}
//-------------------------------------------------------------------
+1 -1
View File
@@ -1,4 +1,4 @@
using Sarand.Net.Mobile.Maui.Views;
using Sarand.Net.Mobile.Maui.Views;
using System;
using System.Collections.Generic;
using System.Linq;
+1 -1
View File
@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
+13 -27
View File
@@ -1,9 +1,6 @@
using Sarand.Net.Standard.Base.App;
using Sarand.Net.Standard.Base.App;
using Sarand.Net.Standard.Base.Utils;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Sarand.Net.Mobile.Maui.Utils
@@ -11,39 +8,28 @@ namespace Sarand.Net.Mobile.Maui.Utils
//=======================================================================
public static class SamFormsUtil
{
//-------------------------------------------------------------------
// D i s p l a y A l e r t a n d W a i t
//-------------------------------------------------------------------
public static void DisplayAlertSync(String title, String message, String cancel = "OK")
// --- Display dialogs ---
public static Task DisplayAlertAsync(String title, String message, String cancel = "OK")
{
if (Application.Current?.MainPage == null)
Page? mainPage = Application.Current?.Windows.FirstOrDefault()?.Page;
if (mainPage == null)
{
SaLogger.Warn(SysUtil.Fn + " Null main page. Message: " + message);
return;
return Task.CompletedTask;
}
SamThreadUtil.InvokeOnMainThreadSync(async () => await Application.Current.MainPage.DisplayAlert(title, message, cancel));
return SamThreadUtil.InvokeOnMainThreadAsync(() => mainPage.DisplayAlertAsync(title, message, cancel));
}
public static bool DisplayConfirmSync(String title, String message, String accept = "Yes", String cancel = "No")
public static Task<bool> DisplayConfirmAsync(String title, String message, String accept = "Yes", String cancel = "No")
{
if (Application.Current?.MainPage == null)
Page? mainPage = Application.Current?.Windows.FirstOrDefault()?.Page;
if (mainPage == null)
{
SaLogger.Warn(SysUtil.Fn + " Null main page. Message: " + message);
return false;
return Task.FromResult(false);
}
var tcs = new TaskCompletionSource<bool>();
MainThread.BeginInvokeOnMainThread(async () =>
{
try
{
bool result = await Application.Current.MainPage.DisplayAlert(title, message, accept, cancel);
tcs.SetResult(result);
}
catch (Exception ex) { tcs.SetException(ex); }
});
return tcs.Task.Result;
return SamThreadUtil.InvokeOnMainThreadAsync(() => mainPage.DisplayAlertAsync(title, message, accept, cancel));
}
//-------------------------------------------------------------------
// --- The End ---
}
//=======================================================================
}
+1 -1
View File
@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
+1 -1
View File
@@ -1,4 +1,4 @@
using Microsoft.Maui.Graphics;
using Microsoft.Maui.Graphics;
using System;
using System.Collections.Generic;
using System.Linq;
+13 -32
View File
@@ -1,61 +1,42 @@
using Sarand.Net.Standard.Base.App;
using Sarand.Net.Standard.Base.App;
using Sarand.Net.Standard.Base.Utils;
using System;
using System.Collections.Generic;
using System.Linq;
using System.IO;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
namespace Sarand.Net.Mobile.Maui.Utils
{
//=======================================================================
public class SamResourceUtil
{
//-------------------------------------------------------------------
// Resource as Stream.
// --- Resource stream ---
public static Stream? GetResourceStream(String resourcePath)
{
return GetResourceStream(Assembly.GetExecutingAssembly(), resourcePath);
}
//-------------------------------------------------------------------
public static Stream? GetResourceStream(Assembly assembly, String resourcePath)
{
if (String.IsNullOrWhiteSpace(resourcePath)) return null;
Stream? stream = assembly.GetManifestResourceStream(resourcePath);
if (stream != null) return stream;
foreach (var res in assembly.GetManifestResourceNames())
foreach (String resourceName in assembly.GetManifestResourceNames())
{
//SaLogger.Info(SysUtil.Fn + $" Resource: {res.ToString()}");
// Read source file
stream = assembly.GetManifestResourceStream(res);
//using var reader = new StreamReader(stream);
//var content = reader.ReadToEnd();
// generate target path inside data folder
//string targetFile = System.IO.Path.Combine(FileSystem.Current.AppDataDirectory, res);
// write file into data folder
//using FileStream outputStream = System.IO.File.OpenWrite(targetFile);
//using StreamWriter streamWriter = new StreamWriter(outputStream);
//streamWriter.Write(content);
if (!resourceName.EndsWith(resourcePath, StringComparison.OrdinalIgnoreCase)) continue;
return assembly.GetManifestResourceStream(resourceName);
}
return null;
}
//-------------------------------------------------------------------
// Log resources.
// --- Resource diagnostics ---
public static void LogResources(Assembly assembly)
{
foreach (var res in assembly.GetManifestResourceNames())
SaLogger.Info(SysUtil.Fn + $" Resource: {res.ToString()}");
foreach (String resourceName in assembly.GetManifestResourceNames())
SaLogger.Info(SysUtil.Fn + $" Resource: {resourceName}");
}
//-------------------------------------------------------------------
// --- The End ---
}
//=======================================================================
}
+25 -36
View File
@@ -1,7 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System;
using System.Threading.Tasks;
namespace Sarand.Net.Mobile.Maui.Utils
@@ -19,12 +16,10 @@ namespace Sarand.Net.Mobile.Maui.Utils
return MainThread.IsMainThread;
}
//-------------------------------------------------------------------
// I n v o k e o n M a i n T h r e a d a n d W a i t
//-------------------------------------------------------------------
// --- Fire-and-forget dispatch ---
/// <summary>Invoke task on main thread.</summary>
public static void InvokeOnMainThread(Action taskFn)
/// <summary>Invoke an action on the main thread without waiting for completion.</summary>
public static void PostToMainThread(Action taskFn)
{
MainThread.BeginInvokeOnMainThread(() =>
{
@@ -32,38 +27,32 @@ namespace Sarand.Net.Mobile.Maui.Utils
});
}
/// <summary>Invoke task on main thread and wait for completion.</summary>
public static void InvokeOnMainThreadSync(Action taskFn)
// --- Awaitable dispatch ---
/// <summary>Invoke an action on the main thread and await its completion.</summary>
public static Task InvokeOnMainThreadAsync(Action taskFn)
{
var tcs = new TaskCompletionSource<Object>();
MainThread.BeginInvokeOnMainThread(() =>
{
try
{
taskFn();
tcs.SetResult(true);
}
catch (Exception ex) { tcs.SetException(ex); }
});
return MainThread.InvokeOnMainThreadAsync(taskFn);
}
/// <summary>Invoke task on main thread and wait for completion.</summary>
public static T InvokeOnMainThreadSync<T>(Func<T> taskFn)
/// <summary>Invoke an asynchronous function on the main thread and await its completion.</summary>
public static Task InvokeOnMainThreadAsync(Func<Task> taskFn)
{
var tcs = new TaskCompletionSource<T>();
MainThread.BeginInvokeOnMainThread(() =>
{
try
{
var result = taskFn();
tcs.SetResult(result);
}
catch (Exception ex) { tcs.SetException(ex); }
});
return tcs.Task.Result;
return MainThread.InvokeOnMainThreadAsync(taskFn);
}
//-------------------------------------------------------------------
/// <summary>Invoke a function on the main thread and await its result.</summary>
public static Task<T> InvokeOnMainThreadAsync<T>(Func<T> taskFn)
{
return MainThread.InvokeOnMainThreadAsync(taskFn);
}
/// <summary>Invoke an asynchronous function on the main thread and await its result.</summary>
public static Task<T> InvokeOnMainThreadAsync<T>(Func<Task<T>> taskFn)
{
return MainThread.InvokeOnMainThreadAsync(taskFn);
}
// --- The End ---
}
//=======================================================================
}
+1 -1
View File
@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
+2 -2
View File
@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
@@ -18,7 +18,7 @@ namespace Sarand.Net.Mobile.Maui.Views
/// </summary>
void ConfigureComponents();
void SetControlActivity();
void SetControlActivity(bool enabled);
void SetControlsReadOnly();
+4 -3
View File
@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
@@ -7,7 +7,7 @@ using System.Threading.Tasks;
namespace Sarand.Net.Mobile.Maui.Views
{
//=======================================================================
public class SamBorder : Border
public class SamBorder : Border, ISamView
{
//-------------------------------------------------------------------
// Definitions.
@@ -32,8 +32,9 @@ namespace Sarand.Net.Mobile.Maui.Views
//-------------------------------------------------------------------
// Control activity.
public virtual void SetControlActivity()
public virtual void SetControlActivity(bool enabled)
{
if (IsEnabled != enabled) IsEnabled = enabled;
SetControlsReadOnly();
}
public virtual void SetControlsReadOnly()
+3 -2
View File
@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
@@ -32,8 +32,9 @@ namespace Sarand.Net.Mobile.Maui.Views
//-------------------------------------------------------------------
// Control activity.
public virtual void SetControlActivity()
public virtual void SetControlActivity(bool enabled)
{
if (IsEnabled != enabled) IsEnabled = enabled;
SetControlsReadOnly();
}
public virtual void SetControlsReadOnly()
+5 -4
View File
@@ -1,4 +1,4 @@
using Sarand.Net.Mobile.Maui.Utils;
using Sarand.Net.Mobile.Maui.Utils;
using Sarand.Net.Standard.Base.Utils;
using System;
using System.Collections.Generic;
@@ -33,7 +33,7 @@ namespace Sarand.Net.Mobile.Maui.Views
public bool ReadOnly
{
get { return readOnly; }
set { if (readOnly == value) return; readOnly = value; SetControlActivity(); }
set { if (readOnly == value) return; readOnly = value; SetControlActivity(IsEnabled); }
}
//-------------------------------------------------------------------
@@ -44,8 +44,9 @@ namespace Sarand.Net.Mobile.Maui.Views
//-------------------------------------------------------------------
// Control activity.
public virtual void SetControlActivity()
public virtual void SetControlActivity(bool enabled)
{
if (IsEnabled != enabled) IsEnabled = enabled;
SetControlsReadOnly();
}
public virtual void SetControlsReadOnly()
@@ -60,7 +61,7 @@ namespace Sarand.Net.Mobile.Maui.Views
base.OnPropertyChanged(propertyName);
if (propertyName.EqualsCI(IsEnabledProperty.PropertyName))
{
SetControlActivity();
SetControlActivity(IsEnabled);
}
}
+3 -2
View File
@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
@@ -32,8 +32,9 @@ namespace Sarand.Net.Mobile.Maui.Views
//-------------------------------------------------------------------
// Control activity.
public virtual void SetControlActivity()
public virtual void SetControlActivity(bool enabled)
{
if (IsEnabled != enabled) IsEnabled = enabled;
SetControlsReadOnly();
}
public virtual void SetControlsReadOnly()
+3 -2
View File
@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
@@ -32,8 +32,9 @@ namespace Sarand.Net.Mobile.Maui.Views
//-------------------------------------------------------------------
// Control activity.
public virtual void SetControlActivity()
public virtual void SetControlActivity(bool enabled)
{
if (IsEnabled != enabled) IsEnabled = enabled;
SetControlsReadOnly();
}
public virtual void SetControlsReadOnly()
@@ -1,4 +1,4 @@
namespace SacsMobileMaui
namespace SacsMobileMaui
{
// All the code in this file is only included on Android.
public class PlatformClass1
@@ -1,4 +1,4 @@
namespace SacsMobileMaui
namespace SacsMobileMaui
{
// All the code in this file is only included on Mac Catalyst.
public class PlatformClass1
@@ -1,4 +1,4 @@
using System;
using System;
namespace SacsMobileMaui
{
@@ -1,4 +1,4 @@
namespace SacsMobileMaui
namespace SacsMobileMaui
{
// All the code in this file is only included on Windows.
public class PlatformClass1
@@ -1,4 +1,4 @@
namespace SacsMobileMaui
namespace SacsMobileMaui
{
// All the code in this file is only included on iOS.
public class PlatformClass1
+8 -2
View File
@@ -7,8 +7,14 @@ table td {
}
</style>
Last updated: 2026-09-11
Last updated: 2026-10-06
| Component | Scope | Status | Date reviewed | Next steps | Notes |
|---|---|---|---|---|---|
| `SacsMobileMaui` | Project | .NET 10 migration complete; follow-up required | 2026-09-11 | Validate Apple targets on a Mac, then address the compatibility APIs and focused defects recorded in `topics.md`. | The project and both active consumers target .NET 10 with MAUI 10.0.20. Windows runtime smoke tests pass, and Windows and Android Debug builds complete with zero errors against Common’s final .NET 10-only libraries. Compatibility API, UI-thread synchronization, handler-cast, `AllowInvalid`, and resource-stream work remains tracked. |
| `SacsMobileMaui` | Project and project documentation review | .NET 10 migration complete; follow-up required | 2026-09-17 | Validate Apple targets on a Mac, then address the compatibility APIs, handler registration, and focused tests recorded in `topics.md`. | Reviewed the MAUI project README, topic tracker, component register, and shared Common trackers. The project and both active consumers target .NET 10 with MAUI 10.0.20. Windows runtime smoke tests pass, and Windows and Android Debug builds complete with zero errors against Common’s final .NET 10-only libraries. Async/dialog implementation and date-picker behavior are implemented; compatibility APIs, handler registration, and resource-stream tests remain. |
| `Maui/Utils/SamThreadUtil.cs`, `Maui/Utils/SamFormUtil.cs`, `Maui/Pages/SamContentPage.cs`, `SacsMobileMauiTests`, `SacsMobileMauiTestHost` | Async UI-thread dispatch and dialogs | Common behavior implemented and host-tested | 2026-10-06 | Resolve the separately scoped Sandtrap API call-site and Apple runtime-pack build failures before claiming consumer compatibility. | All six dispatcher tests and all three interactive dialog tests pass in the Windows MAUI host; all four non-interactive NUnit tests pass. The host builds with zero warnings/errors. Sandtrap's build check exposed removed-API call sites and Apple-target runtime-pack errors; no consumer files were changed. |
| `Maui/Controls/SamDatePicker.cs`, `SamDatePicker_Tester` | `AllowInvalid` transitions, date display, and reset behavior | Implemented and host-verified | 2026-10-06 | Retain the host test for future date-picker changes. | The `Date` setter now refreshes the formatted entry/reset display after changing state. The explicit host test verifies both mode transitions, valid-date display, invalid-date clearing, reset visibility, and restoration of the native picker date when invalid values are disabled; the user confirmed it passes. |
| `Maui/App/Sam.cs` | Shared MAUI configuration and visual defaults | Retained and clarified | 2026-09-29 | Classify remaining unused settings and decide whether localisation and font defaults should be wired into consumers. | Preserved all public settings, documented the startup/default configuration semantics, and made the remaining item-list visual fields consistent properties. |
| `Maui/Logic/SamEntryTextFormatter.cs`, `SamEntryTextFormatOptions.cs`, `SamPickerItemCollection.cs`, `SamDateValueState.cs`, `SamViewTreeUtil.cs` | Reusable control logic extraction | Implemented and covered where platform-independent | 2026-09-29 | Use these seams when adding further `Sam*` controls; add MAUI-host tests for visual-tree behavior. | `SamEntry`, `SamPicker`, `SamDatePicker`, and `SamContentPage` now delegate reusable formatting, item, date-state, and child-view traversal behavior. `SamEntryTextFormatter` receives an immutable formatting-options snapshot rather than positional settings. Four ordinary NUnit tests pass without a MAUI host. |
| `Maui/Controls/*` and `Maui/Controls/Panels/*` | MAUI control coverage and consistency review | Activity contracts separated for controls and composite views | 2026-10-06 | Add MAUI-host tests, assess `BindableProperty` support, and review the remaining platform-specific handler branches and no-op overrides. | Added the single-method `ISamControl` contract for leaf controls. `SamViewTreeUtil` now applies activity to child `ISamControl` and `ISamView` elements; `ISamView` remains the contract for composite views and layout configuration. Earlier work removed the `SamControlPanel` constructor’s virtual call, simplified redundant panel lifecycle overrides, corrected `SamLabel` proportional font handling, added `ISamNativeControlStyle`, and improved `SamWebView`, `SamTabControl`, `SamDateTimePicker`, and `SamPicker`. |
| `Maui/Handlers/SamFormHandler.cs`, `Maui/Controls/ISamNativeControlStyle.cs` | Native form-control handler mappings | Platform behavior clarified; registration still unverified | 2026-10-06 | Verify or add the startup call to `SamFormHandler.AssignControlHandlers()`; then host-test mapping behavior and visually check transparent backgrounds and borders. Consider bindable properties only if styling must change after handler creation. | All mappings guard with `ISamNativeControlStyle`. Windows background clearing now uses a transparent brush. `NativeNoUnderline` intentionally has no iOS/macOS or Windows implementation because those controls expose a full border rather than the Android-style underline; `NativeNoBorder` removes that border. A Common-project search found the registration method definition but no call site, so mapping activation is not yet established. |
+7 -5
View File
@@ -7,17 +7,15 @@ table td {
}
</style>
Last updated: 2026-09-11
Last updated: 2026-10-06
## Prioritised Topics to Be Actioned
| Priority | Topic | Date noted | Next step | Notes |
|---|---|---|---|---|
| P2 | Replace obsolete MAUI compatibility APIs | 2026-09-10 | Replace compatibility layouts, `Frame`, `MainPage`, synchronous popup calls, and deprecated expansion options; then remove `Microsoft.Maui.Controls.Compatibility`. | The .NET 10 Windows and Android builds succeed, but retain compatibility references and report the existing API-obsolescence warnings. |
| P1 | Make `SamThreadUtil` and form dialogs genuinely asynchronous and deadlock-safe | 2026-09-10 | Replace synchronous UI-thread bridges with `Task`-returning APIs, update the Sandtrap caller, and add focused tests. | The void `InvokeOnMainThreadSync` overload never waits; the generic overload and `DisplayConfirmSync` block on `Task.Result` and can deadlock on the UI thread. `DisplayAlertSync` also passes an async lambda to `Action`, producing `async void` behavior. |
| P2 | Correct MAUI native handler type checks | 2026-09-10 | Use `SamEditor` in editor mappings and `SamPicker` in picker mappings; validate each platform-specific option. | Several editor and every picker option currently cast to the wrong custom control type, so configured native styling is silently skipped. |
| P2 | Fix `SamDatePicker.AllowInvalid` state transition | 2026-09-10 | Change the assignment in the equality guard, verify both transitions, and add tests for display and reset behavior. | Setting `AllowInvalid` to `true` returns before refreshing the control; the guard uses `allowInvalidDate = value`. |
| P3 | Remove unused resource-stream enumeration | 2026-09-10 | Return `null` immediately after a failed named lookup or implement an intentional lookup strategy that disposes rejected streams. | `GetResourceStream` opens every manifest resource, discards each stream, and returns `null`. |
| P2 | Verify native form-handler registration | 2026-10-06 | Find or add the startup call to `SamFormHandler.AssignControlHandlers()`, then cover registration and configured-control guards in host tests. | A search of `SacsMobileMaui` currently finds the method definition but no call site. Handler mappings will have no effect unless this registration method runs during app startup. |
| P3 | Replace unused resource-stream enumeration | 2026-09-10 | Add focused tests for exact, case-insensitive suffix, and missing-resource lookups. | `SamResourceUtil.GetResourceStream` now performs an exact lookup followed by a case-insensitive manifest-name suffix lookup, opening a stream only for the selected resource. |
## .NET 10 Dependencies and Sequence
@@ -39,7 +37,11 @@ Last updated: 2026-09-11
| Topic | Date noted | Date started | Date completed | Outcome |
|---|---|---|---|---|
| Fix `SamDatePicker.AllowInvalid` state transition | 2026-09-10 | 2026-10-06 | 2026-10-06 | The equality guard compares rather than assigns; changing `Date` refreshes the formatted entry/reset display. The explicit MAUI-host test covers both `AllowInvalid` transitions, valid-date display, invalid-date clearing, reset visibility, and fallback to the native picker date. The host build succeeds and the user confirmed the test passes. |
| Correct MAUI native handler type checks and complete supported styling branches | 2026-09-10 | 2026-10-06 | 2026-10-06 | Each mapping checks `ISamNativeControlStyle`. Windows background clearing now assigns a transparent WinUI brush. `NativeNoUnderline` is intentionally a no-op on iOS/macOS and Windows because those controls have a full border rather than a separate Android-style underline; comments direct callers to `NativeNoBorder`. `SamEditor` and `SamPicker` derive from different MAUI controls, so no common concrete parent is appropriate. |
| Make `SamThreadUtil` and form dialogs genuinely asynchronous and deadlock-safe | 2026-09-10 | 2026-09-17 | 2026-10-06 | Added a Windows MAUI host for the explicit NUnit integration tests. All six dispatcher tests and all three interactive dialog tests pass; the four non-interactive NUnit tests also pass. The Sandtrap build check was performed but remains red because the consumer still calls the removed `InvokeOnMainThread` API and its iOS/Mac Catalyst targets lack ASP.NET Core runtime packs. Per scope, no Sandtrap files were changed; resolve that consumer fallout separately. |
| Establish authoritative source control for Sandtrap and Sandalwood | 2026-09-10 | 2026-09-10 | 2026-09-10 | Separate repositories were initialized, given baseline guidance and tracking files, committed, and pushed to their remotes. |
| Repair the MAUI workload and validate local targets | 2026-09-10 | 2026-09-11 | 2026-09-11 | SDK 10.0.401 reports coherent .NET 10 Android, iOS, Mac Catalyst, and MAUI Windows workloads. The upgraded shared project restores and builds successfully for Windows and Android; Apple builds still require compatible Mac/Xcode validation. |
| Upgrade `SacsMobileMaui` to .NET MAUI 10 | 2026-09-10 | 2026-09-11 | 2026-09-11 | Retargeted Android, iOS, Mac Catalyst, and Windows to .NET 10; aligned MAUI packages to 10.0.20; and adapted date/time controls to MAUI 10 nullable picker values. Windows and Android Debug builds complete with zero errors. |
| Upgrade Sandtrap and Sandalwood to .NET MAUI 10 | 2026-09-10 | 2026-09-11 | 2026-09-11 | Both applications now target .NET 10 with MAUI 10.0.20. Windows runtime smoke tests pass, and Windows and Android Debug builds complete with zero errors against the final Common .NET 10-only libraries. Apple validation remains separately tracked in each application repository. |
| Extract reusable logic from core MAUI controls | 2026-09-29 | 2026-09-29 | 2026-09-29 | Continue adding `Sam*` components using the extracted logic seams, keeping platform-specific behavior in the controls. | Added `SamEntryTextFormatter`, `SamEntryTextFormatOptions`, `SamPickerItemCollection`, `SamDateValueState`, and `SamViewTreeUtil`. `SamEntry` now passes an immutable formatting-options snapshot to its formatter. The Common MAUI project builds successfully; four pure-logic tests pass in `SacsMobileMauiTests`. The Windows MAUI host now runs the six dispatcher and three dialog integration tests; control-specific host tests are added as needed. |
+14
View File
@@ -0,0 +1,14 @@
<?xml version = "1.0" encoding = "UTF-8" ?>
<Application xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:SacsMobileMauiTestHost"
x:Class="SacsMobileMauiTestHost.App">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Resources/Styles/Colors.xaml" />
<ResourceDictionary Source="Resources/Styles/Styles.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</Application>
+16
View File
@@ -0,0 +1,16 @@
using Microsoft.Extensions.DependencyInjection;
namespace SacsMobileMauiTestHost;
public partial class App : Application
{
public App()
{
InitializeComponent();
}
protected override Window CreateWindow(IActivationState? activationState)
{
return new Window(new AppShell());
}
}
+14
View File
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8" ?>
<Shell
x:Class="SacsMobileMauiTestHost.AppShell"
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:SacsMobileMauiTestHost"
Title="SacsMobileMauiTestHost">
<ShellContent
Title="Home"
ContentTemplate="{DataTemplate local:MainPage}"
Route="MainPage" />
</Shell>
+9
View File
@@ -0,0 +1,9 @@
namespace SacsMobileMauiTestHost;
public partial class AppShell : Shell
{
public AppShell()
{
InitializeComponent();
}
}
+31
View File
@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="SacsMobileMauiTestHost.MainPage">
<Grid Padding="24" RowDefinitions="Auto,Auto,*" RowSpacing="16">
<Label Text="SacsMobileMaui dispatcher tests"
FontSize="24"
FontAttributes="Bold" />
<VerticalStackLayout Grid.Row="1" Spacing="8">
<Label Text="Runs the explicit NUnit dispatcher tests on a worker while this MAUI UI loop remains active." />
<Button x:Name="RunTestsButton"
Text="Run dispatcher tests"
Clicked="OnRunTestsClicked"
HorizontalOptions="Start" />
<Button x:Name="RunDialogTestsButton"
Text="Run interactive dialog tests"
Clicked="OnRunDialogTestsClicked"
HorizontalOptions="Start" />
<Button x:Name="RunDatePickerTestsButton"
Text="Run date picker tests"
Clicked="OnRunDatePickerTestsClicked"
HorizontalOptions="Start" />
<Label x:Name="StatusLabel" Text="Ready" />
</VerticalStackLayout>
<ScrollView Grid.Row="2">
<Label x:Name="ResultsLabel" FontFamily="Consolas" />
</ScrollView>
</Grid>
</ContentPage>
+144
View File
@@ -0,0 +1,144 @@
namespace SacsMobileMauiTestHost;
public partial class MainPage : ContentPage
{
private static readonly TimeSpan TestTimeout = TimeSpan.FromSeconds(15);
public MainPage()
{
InitializeComponent();
}
// --- Test Runner ---
private async void OnRunTestsClicked(object? sender, EventArgs e)
{
await RunTestsAsync(RunDispatcherTestsAsync);
}
private async void OnRunDialogTestsClicked(object? sender, EventArgs e)
{
await RunTestsAsync(RunDialogTestsAsync);
}
private async void OnRunDatePickerTestsClicked(object? sender, EventArgs e)
{
await RunTestsAsync(RunDatePickerTestsAsync);
}
private async Task RunTestsAsync(Func<Task<(List<String> results, int passed, int failed)>> runTests)
{
RunTestsButton.IsEnabled = false;
RunDialogTestsButton.IsEnabled = false;
RunDatePickerTestsButton.IsEnabled = false;
StatusLabel.Text = "Running...";
ResultsLabel.Text = String.Empty;
try
{
(List<String> results, int passed, int failed) = await Task.Run(runTests);
ResultsLabel.Text = String.Join(Environment.NewLine, results);
StatusLabel.Text = failed == 0 ? $"Passed: {passed}" : $"Passed: {passed}; failed: {failed}";
}
catch (Exception exception)
{
StatusLabel.Text = "Test runner failed.";
ResultsLabel.Text = exception.ToString();
}
finally
{
RunTestsButton.IsEnabled = true;
RunDialogTestsButton.IsEnabled = true;
RunDatePickerTestsButton.IsEnabled = true;
}
}
private static async Task<(List<String> results, int passed, int failed)> RunDispatcherTestsAsync()
{
Sarand.UnitTests.Mobile.Maui.Utils.SamThreadUtil_Tester tests = new();
List<String> results = new();
int passed = 0;
int failed = 0;
await RunTestAsync(nameof(tests.PostToMainThread_ExecutesAction), () => tests.PostToMainThread_ExecutesAction());
await RunTestAsync(nameof(tests.InvokeOnMainThreadAsync_Action_CompletesOnMainThread), () => tests.InvokeOnMainThreadAsync_Action_CompletesOnMainThread());
await RunTestAsync(nameof(tests.InvokeOnMainThreadAsync_AsyncAction_CompletesOnMainThread), () => tests.InvokeOnMainThreadAsync_AsyncAction_CompletesOnMainThread());
await RunTestAsync(nameof(tests.InvokeOnMainThreadAsync_ReturnsSynchronousResult), () => tests.InvokeOnMainThreadAsync_ReturnsSynchronousResult());
await RunTestAsync(nameof(tests.InvokeOnMainThreadAsync_ReturnsAsynchronousResult), () => tests.InvokeOnMainThreadAsync_ReturnsAsynchronousResult());
await RunTestAsync(nameof(tests.InvokeOnMainThreadAsync_PropagatesException), () =>
{
tests.InvokeOnMainThreadAsync_PropagatesException();
return Task.CompletedTask;
});
return (results, passed, failed);
async Task RunTestAsync(String name, Func<Task> test)
{
try
{
await test().WaitAsync(TestTimeout);
results.Add($"PASS {name}");
passed++;
}
catch (Exception exception)
{
results.Add($"FAIL {name}: {exception.GetBaseException().Message}");
failed++;
}
}
}
private static async Task<(List<String> results, int passed, int failed)> RunDialogTestsAsync()
{
Sarand.UnitTests.Mobile.Maui.Utils.SamThreadUtil_Tester tests = new();
List<String> results = new();
int passed = 0;
int failed = 0;
await RunTestAsync(nameof(tests.DisplayAlertAsync_CompletesAfterUserDismissesAlert), () => tests.DisplayAlertAsync_CompletesAfterUserDismissesAlert());
await RunTestAsync(nameof(tests.DisplayConfirmAsync_ReturnsTrueWhenAcceptIsSelected), () => tests.DisplayConfirmAsync_ReturnsTrueWhenAcceptIsSelected());
await RunTestAsync(nameof(tests.DisplayConfirmAsync_ReturnsFalseWhenCancelIsSelected), () => tests.DisplayConfirmAsync_ReturnsFalseWhenCancelIsSelected());
return (results, passed, failed);
async Task RunTestAsync(String name, Func<Task> test)
{
try
{
await test().WaitAsync(TimeSpan.FromMinutes(2));
results.Add($"PASS {name}");
passed++;
}
catch (Exception exception)
{
results.Add($"FAIL {name}: {exception.GetBaseException().Message}");
failed++;
}
}
}
private static async Task<(List<String> results, int passed, int failed)> RunDatePickerTestsAsync()
{
Sarand.UnitTests.Mobile.Maui.Controls.SamDatePicker_Tester tests = new();
List<String> results = new();
int passed = 0;
int failed = 0;
try
{
await tests.AllowInvalid_TransitionsRefreshDisplayAndDateReset().WaitAsync(TestTimeout);
results.Add("PASS AllowInvalid_TransitionsRefreshDisplayAndDateReset");
passed++;
}
catch (Exception exception)
{
results.Add($"FAIL AllowInvalid_TransitionsRefreshDisplayAndDateReset: {exception.GetBaseException().Message}");
failed++;
}
return (results, passed, failed);
}
// --- The End ---
}
+24
View File
@@ -0,0 +1,24 @@
using Microsoft.Extensions.Logging;
namespace SacsMobileMauiTestHost;
public static class MauiProgram
{
public static MauiApp CreateMauiApp()
{
var builder = MauiApp.CreateBuilder();
builder
.UseMauiApp<App>()
.ConfigureFonts(fonts =>
{
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
});
#if DEBUG
builder.Logging.AddDebug();
#endif
return builder.Build();
}
}
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application android:allowBackup="true" android:icon="@mipmap/appicon" android:roundIcon="@mipmap/appicon_round" android:supportsRtl="true"></application>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
</manifest>
@@ -0,0 +1,10 @@
using Android.App;
using Android.Content.PM;
using Android.OS;
namespace SacsMobileMauiTestHost;
[Activity(Theme = "@style/Maui.SplashTheme", MainLauncher = true, LaunchMode = LaunchMode.SingleTop, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation | ConfigChanges.UiMode | ConfigChanges.ScreenLayout | ConfigChanges.SmallestScreenSize | ConfigChanges.Density)]
public class MainActivity : MauiAppCompatActivity
{
}
@@ -0,0 +1,15 @@
using Android.App;
using Android.Runtime;
namespace SacsMobileMauiTestHost;
[Application]
public class MainApplication : MauiApplication
{
public MainApplication(IntPtr handle, JniHandleOwnership ownership)
: base(handle, ownership)
{
}
protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
}
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorPrimary">#512BD4</color>
<color name="colorPrimaryDark">#2B0B98</color>
<color name="colorAccent">#2B0B98</color>
</resources>
@@ -0,0 +1,9 @@
using Foundation;
namespace SacsMobileMauiTestHost;
[Register("AppDelegate")]
public class AppDelegate : MauiUIApplicationDelegate
{
protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
}
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<!-- See https://aka.ms/maui-publish-app-store#add-entitlements for more information about adding entitlements.-->
<dict>
<!-- App Sandbox must be enabled to distribute a MacCatalyst app through the Mac App Store. -->
<key>com.apple.security.app-sandbox</key>
<true/>
<!-- When App Sandbox is enabled, this value is required to open outgoing network connections. -->
<key>com.apple.security.network.client</key>
<true/>
</dict>
</plist>
@@ -0,0 +1,40 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<!-- The Mac App Store requires you specify if the app uses encryption. -->
<!-- Please consult https://developer.apple.com/documentation/bundleresources/information_property_list/itsappusesnonexemptencryption -->
<!-- <key>ITSAppUsesNonExemptEncryption</key> -->
<!-- Please indicate <true/> or <false/> here. -->
<!-- Specify the category for your app here. -->
<!-- Please consult https://developer.apple.com/documentation/bundleresources/information_property_list/lsapplicationcategorytype -->
<!-- <key>LSApplicationCategoryType</key> -->
<!-- <string>public.app-category.YOUR-CATEGORY-HERE</string> -->
<key>UIDeviceFamily</key>
<array>
<integer>2</integer>
</array>
<key>LSApplicationCategoryType</key>
<string>public.app-category.lifestyle</string>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>arm64</string>
</array>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>XSAppIconAssets</key>
<string>Assets.xcassets/appicon.appiconset</string>
</dict>
</plist>
@@ -0,0 +1,15 @@
using ObjCRuntime;
using UIKit;
namespace SacsMobileMauiTestHost;
public class Program
{
// This is the main entry point of the application.
static void Main(string[] args)
{
// if you want to use a different Application Delegate class from "AppDelegate"
// you can specify it here.
UIApplication.Main(args, null, typeof(AppDelegate));
}
}
@@ -0,0 +1,8 @@
<maui:MauiWinUIApplication
x:Class="SacsMobileMauiTestHost.WinUI.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:maui="using:Microsoft.Maui"
xmlns:local="using:SacsMobileMauiTestHost.WinUI">
</maui:MauiWinUIApplication>
@@ -0,0 +1,23 @@
using Microsoft.UI.Xaml;
// To learn more about WinUI, the WinUI project structure,
// and more about our project templates, see: http://aka.ms/winui-project-info.
namespace SacsMobileMauiTestHost.WinUI;
/// <summary>
/// Provides application-specific behavior to supplement the default Application class.
/// </summary>
public partial class App : MauiWinUIApplication
{
/// <summary>
/// Initializes the singleton application object. This is the first line of authored code
/// executed, and as such is the logical equivalent of main() or WinMain().
/// </summary>
public App()
{
this.InitializeComponent();
}
protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
}
@@ -0,0 +1,46 @@
<?xml version="1.0" encoding="utf-8"?>
<Package
xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
xmlns:mp="http://schemas.microsoft.com/appx/2014/phone/manifest"
xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
IgnorableNamespaces="uap rescap">
<Identity Name="maui-package-name-placeholder" Publisher="CN=User Name" Version="0.0.0.0" />
<mp:PhoneIdentity PhoneProductId="DDC3D0CC-2A08-44C5-AF13-6398AF6A786B" PhonePublisherId="00000000-0000-0000-0000-000000000000"/>
<Properties>
<DisplayName>$placeholder$</DisplayName>
<PublisherDisplayName>User Name</PublisherDisplayName>
<Logo>$placeholder$.png</Logo>
</Properties>
<Dependencies>
<TargetDeviceFamily Name="Windows.Universal" MinVersion="10.0.17763.0" MaxVersionTested="10.0.19041.0" />
<TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.17763.0" MaxVersionTested="10.0.19041.0" />
</Dependencies>
<Resources>
<Resource Language="x-generate" />
</Resources>
<Applications>
<Application Id="App" Executable="$targetnametoken$.exe" EntryPoint="$targetentrypoint$">
<uap:VisualElements
DisplayName="$placeholder$"
Description="$placeholder$"
Square150x150Logo="$placeholder$.png"
Square44x44Logo="$placeholder$.png"
BackgroundColor="transparent">
<uap:DefaultTile Square71x71Logo="$placeholder$.png" Wide310x150Logo="$placeholder$.png" Square310x310Logo="$placeholder$.png" />
<uap:SplashScreen Image="$placeholder$.png" />
</uap:VisualElements>
</Application>
</Applications>
<Capabilities>
<rescap:Capability Name="runFullTrust" />
</Capabilities>
</Package>
@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
<assemblyIdentity version="1.0.0.0" name="SacsMobileMauiTestHost.WinUI.app"/>
<application xmlns="urn:schemas-microsoft-com:asm.v3">
<windowsSettings>
<!-- The combination of below two tags have the following effect:
1) Per-Monitor for >= Windows 10 Anniversary Update
2) System < Windows 10 Anniversary Update
-->
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/PM</dpiAware>
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2, PerMonitor</dpiAwareness>
<longPathAware xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">true</longPathAware>
</windowsSettings>
</application>
</assembly>
@@ -0,0 +1,9 @@
using Foundation;
namespace SacsMobileMauiTestHost;
[Register("AppDelegate")]
public class AppDelegate : MauiUIApplicationDelegate
{
protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
}
@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UIDeviceFamily</key>
<array>
<integer>1</integer>
<integer>2</integer>
</array>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>arm64</string>
</array>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>XSAppIconAssets</key>
<string>Assets.xcassets/appicon.appiconset</string>
</dict>
</plist>
@@ -0,0 +1,15 @@
using ObjCRuntime;
using UIKit;
namespace SacsMobileMauiTestHost;
public class Program
{
// This is the main entry point of the application.
static void Main(string[] args)
{
// if you want to use a different Application Delegate class from "AppDelegate"
// you can specify it here.
UIApplication.Main(args, null, typeof(AppDelegate));
}
}
@@ -0,0 +1,51 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
This is the minimum required version of the Apple Privacy Manifest for .NET MAUI apps.
The contents below are needed because of APIs that are used in the .NET framework and .NET MAUI SDK.
You are responsible for adding extra entries as needed for your application.
More information: https://aka.ms/maui-privacy-manifest
-->
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSPrivacyAccessedAPITypes</key>
<array>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryFileTimestamp</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>C617.1</string>
</array>
</dict>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategorySystemBootTime</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>35F9.1</string>
</array>
</dict>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryDiskSpace</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>E174.1</string>
</array>
</dict>
<!--
The entry below is only needed when you're using the Preferences API in your app.
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryUserDefaults</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>CA92.1</string>
</array>
</dict> -->
</array>
</dict>
</plist>
@@ -0,0 +1,8 @@
{
"profiles": {
"Windows Machine": {
"commandName": "Project",
"nativeDebugging": false
}
}
}
+13
View File
@@ -0,0 +1,13 @@
# SacsMobileMaui Windows Test Host
This Windows-only MAUI app provides a live dispatcher for the explicit tests in `SacsMobileMauiTests`.
Run it from the Common workspace on Windows:
```powershell
dotnet run --project SacsMobileMauiTestHost/SacsMobileMauiTestHost.csproj --framework net10.0-windows10.0.19041.0
```
When the app opens, select **Run dispatcher tests** to run the six dispatcher checks, **Run interactive dialog tests** to run the dialog checks, or **Run date picker tests** to verify `AllowInvalid` state and display transitions. The dispatcher and control tests run on a worker thread while the MAUI UI loop remains active. For each dialog case, follow the prompt: dismiss the alert, select **Yes** for the accept case, and **No** for the cancel case. Results appear after the selected suite completes; failures and timeouts appear in the results area. The ordinary `dotnet test` run remains available for non-dispatcher tests.
The interactive dialog cases require a person to click the native prompt buttons; the runner cannot dismiss them automatically.
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="456" height="456" viewBox="0 0 456 456" version="1.1" xmlns="http://www.w3.org/2000/svg">
<rect x="0" y="0" width="456" height="456" fill="#512BD4" />
</svg>

After

Width:  |  Height:  |  Size: 228 B

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="456" height="456" viewBox="0 0 456 456" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<path d="m 105.50037,281.60863 c -2.70293,0 -5.00091,-0.90042 -6.893127,-2.70209 -1.892214,-1.84778 -2.837901,-4.04181 -2.837901,-6.58209 0,-2.58722 0.945687,-4.80389 2.837901,-6.65167 1.892217,-1.84778 4.190197,-2.77167 6.893127,-2.77167 2.74819,0 5.06798,0.92389 6.96019,2.77167 1.93749,1.84778 2.90581,4.06445 2.90581,6.65167 0,2.54028 -0.96832,4.73431 -2.90581,6.58209 -1.89221,1.80167 -4.212,2.70209 -6.96019,2.70209 z" style="fill:#ffffff;fill-rule:nonzero;stroke-width:0.838376" />
<path d="M 213.56111,280.08446 H 195.99044 L 149.69953,207.0544 c -1.17121,-1.84778 -2.14037,-3.76515 -2.90581,-5.75126 h -0.40578 c 0.36051,2.12528 0.54076,6.67515 0.54076,13.6496 v 65.13172 h -15.54349 v -99.36009 h 18.71925 l 44.7374,71.29798 c 1.89222,2.95695 3.1087,4.98917 3.64945,6.09751 h 0.26996 c -0.45021,-2.6325 -0.67573,-7.09015 -0.67573,-13.37293 v -64.02256 h 15.47557 z" style="fill:#ffffff;fill-rule:nonzero;stroke-width:0.838376" />
<path d="m 289.25134,280.08446 h -54.40052 v -99.36009 h 52.23835 v 13.99669 h -36.15411 v 28.13085 h 33.31621 v 13.9271 h -33.31621 v 29.37835 h 38.31628 z" style="fill:#ffffff;fill-rule:nonzero;stroke-width:0.838376" />
<path d="M 366.56466,194.72106 H 338.7222 v 85.3634 h -16.08423 v -85.3634 h -27.77455 v -13.99669 h 71.70124 z" style="fill:#ffffff;fill-rule:nonzero;stroke-width:0.838376" />
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

@@ -0,0 +1,15 @@
Any raw assets you want to be deployed with your application can be placed in
this directory (and child directories). Deployment of the asset to your application
is automatically handled by the following `MauiAsset` Build Action within your `.csproj`.
<MauiAsset Include="Resources\Raw\**" LogicalName="%(RecursiveDir)%(Filename)%(Extension)" />
These files will be deployed with your package and will be accessible using Essentials:
async Task LoadMauiAsset()
{
using var stream = await FileSystem.OpenAppPackageFileAsync("AboutAssets.txt");
using var reader = new StreamReader(stream);
var contents = reader.ReadToEnd();
}
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="456" height="456" viewBox="0 0 456 456" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<path d="m 105.50037,281.60863 c -2.70293,0 -5.00091,-0.90042 -6.893127,-2.70209 -1.892214,-1.84778 -2.837901,-4.04181 -2.837901,-6.58209 0,-2.58722 0.945687,-4.80389 2.837901,-6.65167 1.892217,-1.84778 4.190197,-2.77167 6.893127,-2.77167 2.74819,0 5.06798,0.92389 6.96019,2.77167 1.93749,1.84778 2.90581,4.06445 2.90581,6.65167 0,2.54028 -0.96832,4.73431 -2.90581,6.58209 -1.89221,1.80167 -4.212,2.70209 -6.96019,2.70209 z" style="fill:#ffffff;fill-rule:nonzero;stroke-width:0.838376" />
<path d="M 213.56111,280.08446 H 195.99044 L 149.69953,207.0544 c -1.17121,-1.84778 -2.14037,-3.76515 -2.90581,-5.75126 h -0.40578 c 0.36051,2.12528 0.54076,6.67515 0.54076,13.6496 v 65.13172 h -15.54349 v -99.36009 h 18.71925 l 44.7374,71.29798 c 1.89222,2.95695 3.1087,4.98917 3.64945,6.09751 h 0.26996 c -0.45021,-2.6325 -0.67573,-7.09015 -0.67573,-13.37293 v -64.02256 h 15.47557 z" style="fill:#ffffff;fill-rule:nonzero;stroke-width:0.838376" />
<path d="m 289.25134,280.08446 h -54.40052 v -99.36009 h 52.23835 v 13.99669 h -36.15411 v 28.13085 h 33.31621 v 13.9271 h -33.31621 v 29.37835 h 38.31628 z" style="fill:#ffffff;fill-rule:nonzero;stroke-width:0.838376" />
<path d="M 366.56466,194.72106 H 338.7222 v 85.3634 h -16.08423 v -85.3634 h -27.77455 v -13.99669 h 71.70124 z" style="fill:#ffffff;fill-rule:nonzero;stroke-width:0.838376" />
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

@@ -0,0 +1,44 @@
<?xml version="1.0" encoding="UTF-8" ?>
<ResourceDictionary
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml">
<!-- Note: For Android please see also Platforms\Android\Resources\values\colors.xml -->
<Color x:Key="Primary">#512BD4</Color>
<Color x:Key="PrimaryDark">#ac99ea</Color>
<Color x:Key="PrimaryDarkText">#242424</Color>
<Color x:Key="Secondary">#DFD8F7</Color>
<Color x:Key="SecondaryDarkText">#9880e5</Color>
<Color x:Key="Tertiary">#2B0B98</Color>
<Color x:Key="White">White</Color>
<Color x:Key="Black">Black</Color>
<Color x:Key="Magenta">#D600AA</Color>
<Color x:Key="MidnightBlue">#190649</Color>
<Color x:Key="OffBlack">#1f1f1f</Color>
<Color x:Key="Gray100">#E1E1E1</Color>
<Color x:Key="Gray200">#C8C8C8</Color>
<Color x:Key="Gray300">#ACACAC</Color>
<Color x:Key="Gray400">#919191</Color>
<Color x:Key="Gray500">#6E6E6E</Color>
<Color x:Key="Gray600">#404040</Color>
<Color x:Key="Gray900">#212121</Color>
<Color x:Key="Gray950">#141414</Color>
<SolidColorBrush x:Key="PrimaryBrush" Color="{StaticResource Primary}"/>
<SolidColorBrush x:Key="SecondaryBrush" Color="{StaticResource Secondary}"/>
<SolidColorBrush x:Key="TertiaryBrush" Color="{StaticResource Tertiary}"/>
<SolidColorBrush x:Key="WhiteBrush" Color="{StaticResource White}"/>
<SolidColorBrush x:Key="BlackBrush" Color="{StaticResource Black}"/>
<SolidColorBrush x:Key="Gray100Brush" Color="{StaticResource Gray100}"/>
<SolidColorBrush x:Key="Gray200Brush" Color="{StaticResource Gray200}"/>
<SolidColorBrush x:Key="Gray300Brush" Color="{StaticResource Gray300}"/>
<SolidColorBrush x:Key="Gray400Brush" Color="{StaticResource Gray400}"/>
<SolidColorBrush x:Key="Gray500Brush" Color="{StaticResource Gray500}"/>
<SolidColorBrush x:Key="Gray600Brush" Color="{StaticResource Gray600}"/>
<SolidColorBrush x:Key="Gray900Brush" Color="{StaticResource Gray900}"/>
<SolidColorBrush x:Key="Gray950Brush" Color="{StaticResource Gray950}"/>
</ResourceDictionary>

Some files were not shown because too many files have changed in this diff Show More