diff --git a/samples/ControlCatalog/Pages/PlatformInfoPage.xaml b/samples/ControlCatalog/Pages/PlatformInfoPage.xaml index f02741d2da..22c47f6bef 100644 --- a/samples/ControlCatalog/Pages/PlatformInfoPage.xaml +++ b/samples/ControlCatalog/Pages/PlatformInfoPage.xaml @@ -19,6 +19,9 @@ + + + diff --git a/src/Android/Avalonia.Android/AndroidPlatform.cs b/src/Android/Avalonia.Android/AndroidPlatform.cs index d8202064e7..b991d8067f 100644 --- a/src/Android/Avalonia.Android/AndroidPlatform.cs +++ b/src/Android/Avalonia.Android/AndroidPlatform.cs @@ -20,7 +20,7 @@ namespace Avalonia public static AppBuilder UseAndroid(this AppBuilder builder) { return builder - .UseStandardRuntimePlatformSubsystem() + .UseAndroidRuntimePlatformSubsystem() .UseWindowingSubsystem(() => AndroidPlatform.Initialize(), "Android") .UseSkia(); } diff --git a/src/Android/Avalonia.Android/AndroidRuntimePlatform.cs b/src/Android/Avalonia.Android/AndroidRuntimePlatform.cs new file mode 100644 index 0000000000..f38492005a --- /dev/null +++ b/src/Android/Avalonia.Android/AndroidRuntimePlatform.cs @@ -0,0 +1,52 @@ +using System; +using Android.Content.PM; +using Android.Content; +using Avalonia.Platform; +using App = Android.App.Application; +using System.Reflection; + +namespace Avalonia +{ + internal static class AndroidRuntimePlatformServices + { + public static AppBuilder UseAndroidRuntimePlatformSubsystem(this AppBuilder builder) + { + builder.UseRuntimePlatformSubsystem(() => Register(builder.ApplicationType?.Assembly), nameof(AndroidRuntimePlatform)); + return builder; + } + + public static void Register(Assembly? assembly = null) + { + AssetLoader.RegisterResUriParsers(); + AvaloniaLocator.CurrentMutable + .Bind().ToSingleton() + .Bind().ToConstant(new StandardAssetLoader(assembly)); + } + } + + + internal class AndroidRuntimePlatform : StandardRuntimePlatform + { + private static readonly Lazy Info = new(() => + { + var isDesktop = IsRunningOnDesktop(App.Context); + var isTv = IsRunningOnTv(App.Context); + + return new RuntimePlatformInfo + { + IsDesktop = isDesktop, + IsMobile = !isTv && !isDesktop, + IsTV = isTv + }; + }); + + private static bool IsRunningOnDesktop(Context context) => + context.PackageManager.HasSystemFeature("org.chromium.arc") || + context.PackageManager.HasSystemFeature("org.chromium.arc.device_management"); + + private static bool IsRunningOnTv(Context context) => + context.PackageManager.HasSystemFeature(PackageManager.FeatureLeanback); + + public override RuntimePlatformInfo GetRuntimeInfo() => Info.Value; + } +} diff --git a/src/Avalonia.Base/Platform/IRuntimePlatform.cs b/src/Avalonia.Base/Platform/IRuntimePlatform.cs index c6efadc99a..565f44ec40 100644 --- a/src/Avalonia.Base/Platform/IRuntimePlatform.cs +++ b/src/Avalonia.Base/Platform/IRuntimePlatform.cs @@ -13,15 +13,17 @@ namespace Avalonia.Platform public record struct RuntimePlatformInfo { public FormFactorType FormFactor => IsDesktop ? FormFactorType.Desktop : - IsMobile ? FormFactorType.Mobile : FormFactorType.Unknown; + IsMobile ? FormFactorType.Mobile : IsTV ? FormFactorType.TV : FormFactorType.Unknown; public bool IsDesktop { get; set; } public bool IsMobile { get; set; } + public bool IsTV { get; set; } } public enum FormFactorType { Unknown, Desktop, - Mobile + Mobile, + TV } } diff --git a/src/Browser/Avalonia.Browser/BrowserRuntimePlatform.cs b/src/Browser/Avalonia.Browser/BrowserRuntimePlatform.cs index eee2b70c9c..8709fd79fd 100644 --- a/src/Browser/Avalonia.Browser/BrowserRuntimePlatform.cs +++ b/src/Browser/Avalonia.Browser/BrowserRuntimePlatform.cs @@ -30,10 +30,12 @@ internal class BrowserRuntimePlatform : StandardRuntimePlatform private static readonly Lazy Info = new(() => { var isMobile = AvaloniaModule.IsMobile(); + var isTv = AvaloniaModule.IsTv(); var result = new RuntimePlatformInfo { - IsMobile = isMobile, - IsDesktop = !isMobile + IsMobile = isMobile && !isTv, + IsDesktop = !isMobile && !isTv, + IsTV = isTv }; return result; diff --git a/src/Browser/Avalonia.Browser/Interop/AvaloniaModule.cs b/src/Browser/Avalonia.Browser/Interop/AvaloniaModule.cs index cb7aabbc39..b48a00919c 100644 --- a/src/Browser/Avalonia.Browser/Interop/AvaloniaModule.cs +++ b/src/Browser/Avalonia.Browser/Interop/AvaloniaModule.cs @@ -33,7 +33,10 @@ internal static partial class AvaloniaModule [JSImport("Caniuse.isMobile", AvaloniaModule.MainModuleName)] public static partial bool IsMobile(); - + + [JSImport("Caniuse.isTv", AvaloniaModule.MainModuleName)] + public static partial bool IsTv(); + [JSImport("registerServiceWorker", AvaloniaModule.MainModuleName)] public static partial void RegisterServiceWorker(string path, string? scope); } diff --git a/src/Browser/Avalonia.Browser/webapp/modules/avalonia/caniuse.ts b/src/Browser/Avalonia.Browser/webapp/modules/avalonia/caniuse.ts index 8fdc3a5c01..54e6900abc 100644 --- a/src/Browser/Avalonia.Browser/webapp/modules/avalonia/caniuse.ts +++ b/src/Browser/Avalonia.Browser/webapp/modules/avalonia/caniuse.ts @@ -14,4 +14,8 @@ export class Caniuse { const regex2 = /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw(n|u)|c55\/|capi|ccwa|cdm|cell|chtm|cldc|cmd|co(mp|nd)|craw|da(it|ll|ng)|dbte|dcs|devi|dica|dmob|do(c|p)o|ds(12|d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(|_)|g1 u|g560|gene|gf5|gmo|go(\.w|od)|gr(ad|un)|haie|hcit|hd(m|p|t)|hei|hi(pt|ta)|hp( i|ip)|hsc|ht(c(| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i(20|go|ma)|i230|iac( ||\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|[a-w])|libw|lynx|m1w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|mcr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|([1-8]|c))|phil|pire|pl(ay|uc)|pn2|po(ck|rt|se)|prox|psio|ptg|qaa|qc(07|12|21|32|60|[2-7]|i)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h|oo|p)|sdk\/|se(c(|0|1)|47|mc|nd|ri)|sgh|shar|sie(|m)|sk0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h|v|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl|tdg|tel(i|m)|tim|tmo|to(pl|sh)|ts(70|m|m3|m5)|tx9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas|your|zeto|zte/i; return regex1.test(userAgent) || regex2.test(userAgent.substr(0, 4)); } + + public static isTv(): boolean { + return navigator.userAgent.includes("SmartTV"); + } } diff --git a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/OnFormFactorExtension.cs b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/OnFormFactorExtension.cs index a07595a35b..1ac733a345 100644 --- a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/OnFormFactorExtension.cs +++ b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/OnFormFactorExtension.cs @@ -4,6 +4,7 @@ using Avalonia.Platform; namespace Avalonia.Markup.Xaml.MarkupExtensions; +/// public sealed class OnFormFactorExtension : OnFormFactorExtensionBase { public OnFormFactorExtension() @@ -22,6 +23,7 @@ public sealed class OnFormFactorExtension : OnFormFactorExtensionBase public sealed class OnFormFactorExtension : OnFormFactorExtensionBase> { public OnFormFactorExtension() @@ -40,18 +42,38 @@ public sealed class OnFormFactorExtension : OnFormFactorExtensionBase +/// Provides form factor-specific value for T for the current target device. +/// This extension defines "form-factor" as a "device type" rather than "screen type". +/// public abstract class OnFormFactorExtensionBase : IAddChild where TOn : On { + /// + /// Gets or sets the value applied by default. + /// If not set, default(TReturn) is assigned to the value. + /// [MarkupExtensionDefaultOption] public TReturn? Default { get; set; } + /// + /// Gets or sets the value applied on desktop systems. + /// [MarkupExtensionOption(FormFactorType.Desktop)] public TReturn? Desktop { get; set; } + /// + /// Gets or sets the value applied on mobile systems. + /// [MarkupExtensionOption(FormFactorType.Mobile)] public TReturn? Mobile { get; set; } + /// + /// Gets or sets the value applied on TV systems. + /// + [MarkupExtensionOption(FormFactorType.TV)] + public TReturn? TV { get; set; } + // Required for the compiler, will be replaced with actual method compile time. public object ProvideValue() { return this; } void IAddChild.AddChild(TOn child) {} diff --git a/src/Tizen/Avalonia.Tizen/TizenApplicationExtensions.cs b/src/Tizen/Avalonia.Tizen/TizenApplicationExtensions.cs index 72297a4fc5..f1ee66a494 100644 --- a/src/Tizen/Avalonia.Tizen/TizenApplicationExtensions.cs +++ b/src/Tizen/Avalonia.Tizen/TizenApplicationExtensions.cs @@ -1,4 +1,7 @@ -namespace Avalonia.Tizen; +using System.Reflection; +using Avalonia.Platform; + +namespace Avalonia.Tizen; /// /// Extension to setup app builder with tizen backend @@ -13,8 +16,8 @@ public static class TizenApplicationExtensions public static AppBuilder UseTizen(this AppBuilder builder) { return builder + .UseTizenRuntimePlatformSubsystem() .UseWindowingSubsystem(TizenPlatform.Initialize, "Tizen") - .UseStandardRuntimePlatformSubsystem() .UseSkia(); } } diff --git a/src/Tizen/Avalonia.Tizen/TizenRuntimePlatform.cs b/src/Tizen/Avalonia.Tizen/TizenRuntimePlatform.cs new file mode 100644 index 0000000000..b28db8dff8 --- /dev/null +++ b/src/Tizen/Avalonia.Tizen/TizenRuntimePlatform.cs @@ -0,0 +1,38 @@ +using System.Reflection; +using Avalonia.Platform; + +namespace Avalonia.Tizen; + +internal static class TizenRuntimePlatformServices +{ + public static AppBuilder UseTizenRuntimePlatformSubsystem(this AppBuilder builder) + { + builder.UseRuntimePlatformSubsystem(() => Register(builder.ApplicationType?.Assembly), nameof(TizenRuntimePlatform)); + return builder; + } + + public static void Register(Assembly? assembly = null) + { + AssetLoader.RegisterResUriParsers(); + AvaloniaLocator.CurrentMutable + .Bind().ToSingleton() + .Bind().ToConstant(new StandardAssetLoader(assembly)); + } +} + +internal class TizenRuntimePlatform : StandardRuntimePlatform +{ + private static readonly Lazy Info = new(() => + { + global::Tizen.System.Information.TryGetValue("http://tizen.org/feature/profile", out string profile); + + return new RuntimePlatformInfo + { + IsMobile = profile.Equals("mobile", StringComparison.OrdinalIgnoreCase), + IsTV = profile.Equals("tv", StringComparison.OrdinalIgnoreCase), + IsDesktop = false + }; + }); + + public override RuntimePlatformInfo GetRuntimeInfo() => Info.Value; +}