From b8bf9f549bea27ec8af49807a9778b03b80a6621 Mon Sep 17 00:00:00 2001 From: Matthew Leibowitz Date: Wed, 16 Feb 2022 06:52:36 +0000 Subject: [PATCH] Initial work to get unpackaged WinUI apps (#4460) --- Directory.Build.props | 3 +- .../Maui.Controls.Sample.SingleProject.csproj | 1 + .../Properties/launchSettings.json | 5 ++- .../samples/Controls.Sample/Startup.cs | 5 ++- src/Core/src/Fonts/FontRegistrar.Windows.cs | 18 ++++----- src/Essentials/src/AppInfo/AppInfo.android.cs | 2 + .../AppInfo/AppInfo.ios.tvos.watchos.macos.cs | 2 + .../src/AppInfo/AppInfo.netstandard.cs | 2 + src/Essentials/src/AppInfo/AppInfo.shared.cs | 10 +++++ src/Essentials/src/AppInfo/AppInfo.tizen.cs | 2 + src/Essentials/src/AppInfo/AppInfo.uwp.cs | 20 ++++++++++ .../src/FileSystem/FileSystem.android.cs | 26 ++++++++++-- .../FileSystem.ios.tvos.watchos.macos.cs | 23 +++++++++-- .../src/FileSystem/FileSystem.netstandard.cs | 3 ++ .../src/FileSystem/FileSystem.shared.cs | 3 ++ .../src/FileSystem/FileSystem.tizen.cs | 21 ++++++++-- .../src/FileSystem/FileSystem.uwp.cs | 40 ++++++++++++++++++- 17 files changed, 162 insertions(+), 24 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index 8f36dbed1c5e..e56ab6847e96 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -35,12 +35,13 @@ 10.14 30.0 10.0.19041 + 10.0.20348 net$(_MauiDotNetVersion)-ios;net$(_MauiDotNetVersion)-maccatalyst;net$(_MauiDotNetVersion)-android - net$(_MauiDotNetVersion)-windows$(WindowsTargetFrameworkVersion) + net$(_MauiDotNetVersion)-windows$(WindowsTargetFrameworkVersion);net$(_MauiDotNetVersion)-windows$(Windows2TargetFrameworkVersion) $(MauiPlatforms);$(WindowsMauiPlatforms) diff --git a/src/Controls/samples/Controls.Sample.SingleProject/Maui.Controls.Sample.SingleProject.csproj b/src/Controls/samples/Controls.Sample.SingleProject/Maui.Controls.Sample.SingleProject.csproj index 805f1e9b1f15..891177798410 100644 --- a/src/Controls/samples/Controls.Sample.SingleProject/Maui.Controls.Sample.SingleProject.csproj +++ b/src/Controls/samples/Controls.Sample.SingleProject/Maui.Controls.Sample.SingleProject.csproj @@ -10,6 +10,7 @@ true MSIX + None .NET MAUI Controls com.microsoft.maui.sample 1 diff --git a/src/Controls/samples/Controls.Sample.SingleProject/Properties/launchSettings.json b/src/Controls/samples/Controls.Sample.SingleProject/Properties/launchSettings.json index af97d49d1598..c4107de6a176 100644 --- a/src/Controls/samples/Controls.Sample.SingleProject/Properties/launchSettings.json +++ b/src/Controls/samples/Controls.Sample.SingleProject/Properties/launchSettings.json @@ -1,8 +1,11 @@ { "profiles": { - "Windows Machine": { + "Windows Machine (on 10.0.19041)": { "commandName": "MsixPackage", "nativeDebugging": true + }, + "Unpackaged Windows Machine (on 10.0.20348)": { + "commandName": "Project" } } } \ No newline at end of file diff --git a/src/Controls/samples/Controls.Sample/Startup.cs b/src/Controls/samples/Controls.Sample/Startup.cs index 3f685d279479..53735cca67b5 100644 --- a/src/Controls/samples/Controls.Sample/Startup.cs +++ b/src/Controls/samples/Controls.Sample/Startup.cs @@ -144,7 +144,6 @@ public static MauiApp CreateMauiApp() .ConfigureEssentials(essentials => { essentials - .UseVersionTracking() .UseMapServiceToken("YOUR-KEY-HERE") .AddAppAction("test_action", "Test App Action") .AddAppAction("second_action", "Second App Action") @@ -152,6 +151,10 @@ public static MauiApp CreateMauiApp() { Debug.WriteLine($"You seem to have arrived from a special place: {appAction.Title} ({appAction.Id})"); }); + + // TODO: Unpackaged apps need to know the package ID and local data locations + if (AppInfo.PackagingModel == AppPackagingModel.Packaged) + essentials.UseVersionTracking(); }) .ConfigureLifecycleEvents(events => { diff --git a/src/Core/src/Fonts/FontRegistrar.Windows.cs b/src/Core/src/Fonts/FontRegistrar.Windows.cs index 4374cc904aca..f5052024953d 100644 --- a/src/Core/src/Fonts/FontRegistrar.Windows.cs +++ b/src/Core/src/Fonts/FontRegistrar.Windows.cs @@ -1,5 +1,6 @@ #nullable enable using System.IO; +using Microsoft.Maui.Essentials; namespace Microsoft.Maui { @@ -7,22 +8,19 @@ public partial class FontRegistrar : IFontRegistrar { string? LoadNativeAppFont(string font, string filename, string? alias) { - var root = global::Windows.ApplicationModel.Package.Current.InstalledLocation.Path; - - var packagePath = Path.Combine(root, filename); - if (File.Exists(packagePath)) + if (FileSystem.AppPackageFileExists(filename)) return $"ms-appx:///{filename}"; - packagePath = Path.Combine(root, "Assets", filename); - if (File.Exists(packagePath)) + var packagePath = Path.Combine("Assets", filename); + if (FileSystem.AppPackageFileExists(packagePath)) return $"ms-appx:///Assets/{filename}"; - packagePath = Path.Combine(root, "Fonts", filename); - if (File.Exists(packagePath)) + packagePath = Path.Combine("Fonts", filename); + if (FileSystem.AppPackageFileExists(packagePath)) return $"ms-appx:///Fonts/{filename}"; - packagePath = Path.Combine(root, "Assets", "Fonts", filename); - if (File.Exists(packagePath)) + packagePath = Path.Combine("Assets", "Fonts", filename); + if (FileSystem.AppPackageFileExists(packagePath)) return $"ms-appx:///Assets/Fonts/{filename}"; // TODO: check other folders as well diff --git a/src/Essentials/src/AppInfo/AppInfo.android.cs b/src/Essentials/src/AppInfo/AppInfo.android.cs index 30763ffd5986..a601c99362f1 100644 --- a/src/Essentials/src/AppInfo/AppInfo.android.cs +++ b/src/Essentials/src/AppInfo/AppInfo.android.cs @@ -82,5 +82,7 @@ public AppTheme RequestedTheme UiMode.NightNo => AppTheme.Light, _ => AppTheme.Unspecified }; + + public AppPackagingModel PackagingModel => AppPackagingModel.Packaged; } } diff --git a/src/Essentials/src/AppInfo/AppInfo.ios.tvos.watchos.macos.cs b/src/Essentials/src/AppInfo/AppInfo.ios.tvos.watchos.macos.cs index 445b7208e19e..26a343e72b31 100644 --- a/src/Essentials/src/AppInfo/AppInfo.ios.tvos.watchos.macos.cs +++ b/src/Essentials/src/AppInfo/AppInfo.ios.tvos.watchos.macos.cs @@ -13,6 +13,8 @@ namespace Microsoft.Maui.Essentials.Implementations { public class AppInfoImplementation : IAppInfo { + public AppPackagingModel PackagingModel => AppPackagingModel.Packaged; + public string PackageName => GetBundleValue("CFBundleIdentifier"); public string Name => GetBundleValue("CFBundleDisplayName") ?? GetBundleValue("CFBundleName"); diff --git a/src/Essentials/src/AppInfo/AppInfo.netstandard.cs b/src/Essentials/src/AppInfo/AppInfo.netstandard.cs index d02b7f4465c5..489fad55606e 100644 --- a/src/Essentials/src/AppInfo/AppInfo.netstandard.cs +++ b/src/Essentials/src/AppInfo/AppInfo.netstandard.cs @@ -16,5 +16,7 @@ public class AppInfoImplementation : IAppInfo public void ShowSettingsUI() => throw ExceptionUtils.NotSupportedOrImplementedException; public AppTheme RequestedTheme => throw ExceptionUtils.NotSupportedOrImplementedException; + + public AppPackagingModel PackagingModel => throw ExceptionUtils.NotSupportedOrImplementedException; } } diff --git a/src/Essentials/src/AppInfo/AppInfo.shared.cs b/src/Essentials/src/AppInfo/AppInfo.shared.cs index 679f9de718f1..61b85ad86c76 100644 --- a/src/Essentials/src/AppInfo/AppInfo.shared.cs +++ b/src/Essentials/src/AppInfo/AppInfo.shared.cs @@ -20,6 +20,8 @@ public interface IAppInfo void ShowSettingsUI(); AppTheme RequestedTheme { get; } + + AppPackagingModel PackagingModel { get; } } /// @@ -46,6 +48,8 @@ public static class AppInfo /// public static AppTheme RequestedTheme => Current.RequestedTheme; + public static AppPackagingModel PackagingModel => Current.PackagingModel; + static IAppInfo? currentImplementation; @@ -57,4 +61,10 @@ public static class AppInfo public static void SetCurrent(IAppInfo? implementation) => currentImplementation = implementation; } + + public enum AppPackagingModel + { + Packaged, + Unpackaged, + } } diff --git a/src/Essentials/src/AppInfo/AppInfo.tizen.cs b/src/Essentials/src/AppInfo/AppInfo.tizen.cs index 89c3bba30621..a398afe5ddac 100644 --- a/src/Essentials/src/AppInfo/AppInfo.tizen.cs +++ b/src/Essentials/src/AppInfo/AppInfo.tizen.cs @@ -27,5 +27,7 @@ public void PlatformShowSettingsUI() public AppTheme RequestedTheme => AppTheme.Unspecified; + + public AppPackagingModel PackagingModel => AppPackagingModel.Packaged; } } diff --git a/src/Essentials/src/AppInfo/AppInfo.uwp.cs b/src/Essentials/src/AppInfo/AppInfo.uwp.cs index 1bc526f0af14..8efcf1ce01a6 100644 --- a/src/Essentials/src/AppInfo/AppInfo.uwp.cs +++ b/src/Essentials/src/AppInfo/AppInfo.uwp.cs @@ -1,3 +1,4 @@ +using System; using System.Globalization; using Windows.ApplicationModel; #if WINDOWS @@ -10,6 +11,21 @@ namespace Microsoft.Maui.Essentials.Implementations { public class AppInfoImplementation : IAppInfo { + static Lazy _isPackagedAppLazy = new Lazy(() => + { + try + { + if (Package.Current != null) + return true; + } + catch + { + // no-op + } + + return false; + }); + public string PackageName => Package.Current.Id.Name; public string Name => Package.Current.DisplayName; @@ -33,5 +49,9 @@ public void ShowSettingsUI() => public AppTheme RequestedTheme => Application.Current.RequestedTheme == ApplicationTheme.Dark ? AppTheme.Dark : AppTheme.Light; + + public AppPackagingModel PackagingModel => _isPackagedAppLazy.Value + ? AppPackagingModel.Packaged + : AppPackagingModel.Unpackaged; } } \ No newline at end of file diff --git a/src/Essentials/src/FileSystem/FileSystem.android.cs b/src/Essentials/src/FileSystem/FileSystem.android.cs index 7bd8b333bd44..0b13dd30619a 100644 --- a/src/Essentials/src/FileSystem/FileSystem.android.cs +++ b/src/Essentials/src/FileSystem/FileSystem.android.cs @@ -37,15 +37,32 @@ static string PlatformCacheDirectory static string PlatformAppDataDirectory => Platform.AppContext.FilesDir.AbsolutePath; - static Task PlatformOpenAppPackageFileAsync(string filename) + static Task PlatformOpenAppPackageFileAsync(string filename) => + Task.FromResult(PlatformOpenAppPackageFile(filename)); + + static Task PlatformAppPackageFileExistsAsync(string filename) + { + try + { + using var stream = PlatformOpenAppPackageFile(filename); + return Task.FromResult(true); + } + catch (FileNotFoundException) + { + return Task.FromResult(false); + } + } + + static Stream PlatformOpenAppPackageFile(string filename) { if (filename == null) throw new ArgumentNullException(nameof(filename)); - filename = filename.Replace('\\', Path.DirectorySeparatorChar); + filename = NormalizePath(filename); + try { - return Task.FromResult(Platform.AppContext.Assets.Open(filename)); + return Platform.AppContext.Assets.Open(filename); } catch (Java.IO.FileNotFoundException ex) { @@ -53,6 +70,9 @@ static Task PlatformOpenAppPackageFileAsync(string filename) } } + static string NormalizePath(string filename) => + filename.Replace('\\', Path.DirectorySeparatorChar); + internal static Java.IO.File GetEssentialsTemporaryFile(Java.IO.File root, string fileName) { // create the directory for all Essentials files diff --git a/src/Essentials/src/FileSystem/FileSystem.ios.tvos.watchos.macos.cs b/src/Essentials/src/FileSystem/FileSystem.ios.tvos.watchos.macos.cs index 5f782d268cc4..5197d22d8286 100644 --- a/src/Essentials/src/FileSystem/FileSystem.ios.tvos.watchos.macos.cs +++ b/src/Essentials/src/FileSystem/FileSystem.ios.tvos.watchos.macos.cs @@ -15,19 +15,34 @@ static string PlatformAppDataDirectory => GetDirectory(NSSearchPathDirectory.LibraryDirectory); static Task PlatformOpenAppPackageFileAsync(string filename) + { + var file = PlatformGetFullAppPackageFilePath(filename); + return Task.FromResult((Stream)File.OpenRead(file)); + } + + static Task PlatformAppPackageFileExistsAsync(string filename) + { + var file = PlatformGetFullAppPackageFilePath(filename); + return Task.FromResult(File.Exists(file)); + } + + static string PlatformGetFullAppPackageFilePath(string filename) { if (filename == null) throw new ArgumentNullException(nameof(filename)); - filename = filename.Replace('\\', Path.DirectorySeparatorChar); + filename = NormalizePath(filename); + var root = NSBundle.MainBundle.BundlePath; #if MACCATALYST || MACOS - root = Path.Combine(root, "Contents", "Resources"); + root = Path.Combine(root, "Contents", "Resources"); #endif - var file = Path.Combine(root, filename); - return Task.FromResult((Stream)File.OpenRead(file)); + return Path.Combine(root, filename); } + static string NormalizePath(string filename) => + filename.Replace('\\', Path.DirectorySeparatorChar); + static string GetDirectory(NSSearchPathDirectory directory) { var dirs = NSSearchPath.GetDirectories(directory, NSSearchPathDomain.User); diff --git a/src/Essentials/src/FileSystem/FileSystem.netstandard.cs b/src/Essentials/src/FileSystem/FileSystem.netstandard.cs index 8b18d6bdce2b..48576d7bcc73 100644 --- a/src/Essentials/src/FileSystem/FileSystem.netstandard.cs +++ b/src/Essentials/src/FileSystem/FileSystem.netstandard.cs @@ -14,6 +14,9 @@ static string PlatformAppDataDirectory static Task PlatformOpenAppPackageFileAsync(string filename) => throw ExceptionUtils.NotSupportedOrImplementedException; + + static Task PlatformAppPackageFileExistsAsync(string filename) + => throw ExceptionUtils.NotSupportedOrImplementedException; } /// diff --git a/src/Essentials/src/FileSystem/FileSystem.shared.cs b/src/Essentials/src/FileSystem/FileSystem.shared.cs index fba2b4c0140f..bec56e8c24f3 100644 --- a/src/Essentials/src/FileSystem/FileSystem.shared.cs +++ b/src/Essentials/src/FileSystem/FileSystem.shared.cs @@ -19,6 +19,9 @@ public static string AppDataDirectory public static Task OpenAppPackageFileAsync(string filename) => PlatformOpenAppPackageFileAsync(filename); + public static Task AppPackageFileExistsAsync(string filename) + => PlatformAppPackageFileExistsAsync(filename); + internal static class MimeTypes { internal const string All = "*/*"; diff --git a/src/Essentials/src/FileSystem/FileSystem.tizen.cs b/src/Essentials/src/FileSystem/FileSystem.tizen.cs index 193757eee549..6ca4c2cb5108 100644 --- a/src/Essentials/src/FileSystem/FileSystem.tizen.cs +++ b/src/Essentials/src/FileSystem/FileSystem.tizen.cs @@ -14,14 +14,29 @@ static string PlatformAppDataDirectory => Application.Current.DirectoryInfo.Data; static Task PlatformOpenAppPackageFileAsync(string filename) + { + var file = PlatformGetFullAppPackageFilePath(filename); + return Task.FromResult((Stream)File.OpenRead(file)); + } + + static Task PlatformAppPackageFileExistsAsync(string filename) + { + var file = PlatformGetFullAppPackageFilePath(filename); + return Task.FromResult(File.Exists(file)); + } + + static string PlatformGetFullAppPackageFilePath(string filename) { if (string.IsNullOrWhiteSpace(filename)) throw new ArgumentNullException(nameof(filename)); - filename = filename.Replace('\\', Path.DirectorySeparatorChar); - Stream fs = File.OpenRead(Path.Combine(Application.Current.DirectoryInfo.Resource, filename)); - return Task.FromResult(fs); + filename = NormalizePath(filename); + + return Path.Combine(Application.Current.DirectoryInfo.Resource, filename); } + + static string NormalizePath(string filename) => + filename.Replace('\\', Path.DirectorySeparatorChar); } public partial class FileBase diff --git a/src/Essentials/src/FileSystem/FileSystem.uwp.cs b/src/Essentials/src/FileSystem/FileSystem.uwp.cs index 3d934fa3f909..25e3326b9bd4 100644 --- a/src/Essentials/src/FileSystem/FileSystem.uwp.cs +++ b/src/Essentials/src/FileSystem/FileSystem.uwp.cs @@ -19,7 +19,45 @@ static Task PlatformOpenAppPackageFileAsync(string filename) if (filename == null) throw new ArgumentNullException(nameof(filename)); - return Package.Current.InstalledLocation.OpenStreamForReadAsync(NormalizePath(filename)); + if (AppInfo.PackagingModel == AppPackagingModel.Packaged) + { + filename = NormalizePath(filename); + + return Package.Current.InstalledLocation.OpenStreamForReadAsync(filename); + } + else + { + var file = PlatformGetFullAppPackageFilePath(filename); + return Task.FromResult((Stream)File.OpenRead(file)); + } + } + + static Task PlatformAppPackageFileExistsAsync(string filename) + { + var file = PlatformGetFullAppPackageFilePath(filename); + return Task.FromResult(File.Exists(file)); + } + + internal static bool AppPackageFileExists(string filename) + { + var file = PlatformGetFullAppPackageFilePath(filename); + return File.Exists(file); + } + + static string PlatformGetFullAppPackageFilePath(string filename) + { + if (filename == null) + throw new ArgumentNullException(nameof(filename)); + + filename = NormalizePath(filename); + + string root; + if (AppInfo.PackagingModel == AppPackagingModel.Packaged) + root = Package.Current.InstalledLocation.Path; + else + root = AppContext.BaseDirectory; + + return Path.Combine(root, filename); } internal static string NormalizePath(string path)