diff --git a/.openpublishing.publish.config.json b/.openpublishing.publish.config.json index b0ddbf4b4..e196e49f2 100644 --- a/.openpublishing.publish.config.json +++ b/.openpublishing.publish.config.json @@ -80,5 +80,8 @@ "target_framework": "net45", "version": "latest" } - ] + ], + "docs_build_engine": { + "name": "docfx_v3" + } } diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7260abb45..826476b3c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -87,7 +87,7 @@ Every pull request which affects public types or members should include correspo If you're looking for something to fix, please browse [open issues](https://github.com/xamarin/Essentials/issues). -Follow the style used by the [.NET Foundation](https://github.com/dotnet/corefx/blob/master/Documentation/coding-guidelines/coding-style.md), with two primary exceptions: +Follow the style used by the [.NET Foundation](https://github.com/dotnet/runtime/blob/master/docs/coding-guidelines/coding-style.md), with two primary exceptions: - We do not use the `private` keyword as it is the default accessibility level in C#. - We will **not** use `_` or `s_` as a prefix for internal or private field names diff --git a/DeviceTests/DeviceTests.Android/DeviceTests.Android.csproj b/DeviceTests/DeviceTests.Android/DeviceTests.Android.csproj index 3634e033b..fba0d928f 100644 --- a/DeviceTests/DeviceTests.Android/DeviceTests.Android.csproj +++ b/DeviceTests/DeviceTests.Android/DeviceTests.Android.csproj @@ -49,22 +49,12 @@ - - - - - - - - - - - - + + diff --git a/DeviceTests/DeviceTests.Android/MainActivity.cs b/DeviceTests/DeviceTests.Android/MainActivity.cs index 4a0908899..5e176fe81 100644 --- a/DeviceTests/DeviceTests.Android/MainActivity.cs +++ b/DeviceTests/DeviceTests.Android/MainActivity.cs @@ -18,7 +18,7 @@ protected override void OnCreate(Bundle bundle) Xamarin.Essentials.Platform.Init(this, bundle); var hostIp = Intent.Extras?.GetString("HOST_IP", null); - var hostPort = Intent.Extras?.GetInt("HOST_PORT", 10578) ?? 10578; + var hostPort = Intent.Extras?.GetInt("HOST_PORT", 63559) ?? 63559; if (!string.IsNullOrEmpty(hostIp)) { diff --git a/DeviceTests/DeviceTests.Android/Properties/AndroidManifest.xml b/DeviceTests/DeviceTests.Android/Properties/AndroidManifest.xml index ffaf3624a..491919aab 100644 --- a/DeviceTests/DeviceTests.Android/Properties/AndroidManifest.xml +++ b/DeviceTests/DeviceTests.Android/Properties/AndroidManifest.xml @@ -6,6 +6,7 @@ + diff --git a/DeviceTests/DeviceTests.Android/Tests/FileProvider_Tests.cs b/DeviceTests/DeviceTests.Android/Tests/FileProvider_Tests.cs index ca18121e3..0db2872a9 100644 --- a/DeviceTests/DeviceTests.Android/Tests/FileProvider_Tests.cs +++ b/DeviceTests/DeviceTests.Android/Tests/FileProvider_Tests.cs @@ -20,7 +20,7 @@ public void Share_Simple_Text_File_Test() Assert.False(FileProvider.IsFileInPublicLocation(file)); // Actually get a safe shareable file uri - var shareableUri = Platform.GetShareableFileUri(file); + var shareableUri = Platform.GetShareableFileUri(new ReadOnlyFile(file)); // Launch an intent to let tye user pick where to open this content var intent = new Android.Content.Intent(Android.Content.Intent.ActionSend); @@ -232,7 +232,7 @@ static Android.Net.Uri GetShareableUri(string file, FileProviderLocation locatio FileProvider.TemporaryLocation = location; // get the uri - return Platform.GetShareableFileUri(file); + return Platform.GetShareableFileUri(new ReadOnlyFile(file)); } finally { diff --git a/DeviceTests/DeviceTests.Shared/AppActions_Tests.cs b/DeviceTests/DeviceTests.Shared/AppActions_Tests.cs new file mode 100644 index 000000000..04189303e --- /dev/null +++ b/DeviceTests/DeviceTests.Shared/AppActions_Tests.cs @@ -0,0 +1,48 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using Xamarin.Essentials; +using Xamarin.Forms; +using Xunit; + +namespace DeviceTests +{ + public class AppActions_Tests + { + [Fact] + public void IsSupported() + { + var expectSupported = false; + +#if __ANDROID_25__ + expectSupported = true; +#endif + +#if __IOS__ + if (Platform.HasOSVersion(9, 0)) + expectSupported = true; +#endif + Assert.Equal(expectSupported, AppActions.IsSupported); + } + +#if __ANDROID_25__ || __IOS__ + [Fact] + public async Task GetSetItems() + { + if (!AppActions.IsSupported) + return; + + var actions = new List + { + new AppAction("TEST1", "Test 1", "This is a test", "myapp://test1"), + new AppAction("TEST2", "Test 2", "This is a test 2", "myapp://test2"), + }; + + await AppActions.SetAsync(actions); + + var get = await AppActions.GetAsync(); + + Assert.Contains(get, a => a.Id == "TEST1"); + } +#endif + } +} diff --git a/DeviceTests/DeviceTests.Shared/DeviceTests.Shared.csproj b/DeviceTests/DeviceTests.Shared/DeviceTests.Shared.csproj index fd15dda89..f84c87329 100644 --- a/DeviceTests/DeviceTests.Shared/DeviceTests.Shared.csproj +++ b/DeviceTests/DeviceTests.Shared/DeviceTests.Shared.csproj @@ -23,7 +23,7 @@ - + diff --git a/DeviceTests/DeviceTests.Shared/HapticFeedback_Tests.cs b/DeviceTests/DeviceTests.Shared/HapticFeedback_Tests.cs new file mode 100644 index 000000000..23a996c30 --- /dev/null +++ b/DeviceTests/DeviceTests.Shared/HapticFeedback_Tests.cs @@ -0,0 +1,15 @@ +using System; +using Xamarin.Essentials; +using Xunit; + +namespace DeviceTests +{ + public class HapticFeedback_Tests + { + [Fact] + public void Click() => HapticFeedback.Perform(HapticFeedbackType.Click); + + [Fact] + public void LongPress() => HapticFeedback.Perform(HapticFeedbackType.LongPress); + } +} diff --git a/DeviceTests/DeviceTests.UWP/DeviceTests.UWP.csproj b/DeviceTests/DeviceTests.UWP/DeviceTests.UWP.csproj index 5fd16a1d1..e69730a56 100644 --- a/DeviceTests/DeviceTests.UWP/DeviceTests.UWP.csproj +++ b/DeviceTests/DeviceTests.UWP/DeviceTests.UWP.csproj @@ -114,7 +114,7 @@ - + diff --git a/DeviceTests/DeviceTests.iOS/DeviceTests.iOS.csproj b/DeviceTests/DeviceTests.iOS/DeviceTests.iOS.csproj index 8a14bb7c3..d285e57f0 100644 --- a/DeviceTests/DeviceTests.iOS/DeviceTests.iOS.csproj +++ b/DeviceTests/DeviceTests.iOS/DeviceTests.iOS.csproj @@ -77,7 +77,7 @@ - + diff --git a/DeviceTests/build.cake b/DeviceTests/build.cake index f0d304342..40a4c0cc5 100644 --- a/DeviceTests/build.cake +++ b/DeviceTests/build.cake @@ -1,38 +1,55 @@ #addin nuget:?package=Cake.AppleSimulator&version=0.2.0 #addin nuget:?package=Cake.Android.Adb&version=3.2.0 #addin nuget:?package=Cake.Android.AvdManager&version=2.2.0 -#addin nuget:?package=Cake.FileHelpers +#addin nuget:?package=Cake.FileHelpers&version=3.3.0 var TARGET = Argument("target", "Default"); -var IOS_SIM_NAME = EnvironmentVariable("IOS_SIM_NAME") ?? "iPhone 11"; -var IOS_SIM_RUNTIME = EnvironmentVariable("IOS_SIM_RUNTIME") ?? "com.apple.CoreSimulator.SimRuntime.iOS-13-1"; +var IOS_SIM_NAME = Argument("ios-device", EnvironmentVariable("IOS_SIM_NAME") ?? "iPhone 11"); +var IOS_SIM_RUNTIME = Argument("ios-runtime", EnvironmentVariable("IOS_SIM_RUNTIME") ?? "com.apple.CoreSimulator.SimRuntime.iOS-13-7"); var IOS_PROJ = "./DeviceTests.iOS/DeviceTests.iOS.csproj"; var IOS_BUNDLE_ID = "com.xamarin.essentials.devicetests"; var IOS_IPA_PATH = "./DeviceTests.iOS/bin/iPhoneSimulator/Release/XamarinEssentialsDeviceTestsiOS.app"; -var IOS_TEST_RESULTS_PATH = "./xunit-ios.xml"; +var IOS_TEST_RESULTS_PATH = MakeAbsolute((FilePath)"../output/test-results/ios/TestResults.xml"); var ANDROID_PROJ = "./DeviceTests.Android/DeviceTests.Android.csproj"; var ANDROID_APK_PATH = "./DeviceTests.Android/bin/Release/com.xamarin.essentials.devicetests-Signed.apk"; -var ANDROID_TEST_RESULTS_PATH = "./xunit-android.xml"; +var ANDROID_TEST_RESULTS_PATH = MakeAbsolute((FilePath)"../output/test-results/android/TestResults.xml"); +var ANDROID_SCREENSHOT_PATH = MakeAbsolute((DirectoryPath)"../output/test-results/android"); var ANDROID_AVD = EnvironmentVariable("ANDROID_AVD") ?? "CABOODLE"; var ANDROID_PKG_NAME = "com.xamarin.essentials.devicetests"; -var ANDROID_EMU_TARGET = EnvironmentVariable("ANDROID_EMU_TARGET") ?? "system-images;android-29;google_apis_playstore;x86_64"; -var ANDROID_EMU_DEVICE = EnvironmentVariable("ANDROID_EMU_DEVICE") ?? "pixel"; +var ANDROID_EMU_TARGET = Argument("avd-target", EnvironmentVariable("ANDROID_EMU_TARGET") ?? "system-images;android-29;google_apis_playstore;x86"); +var ANDROID_EMU_DEVICE = Argument("avd-device", EnvironmentVariable("ANDROID_EMU_DEVICE") ?? "Nexus 5X"); var UWP_PROJ = "./DeviceTests.UWP/DeviceTests.UWP.csproj"; -var UWP_TEST_RESULTS_PATH = "./xunit-uwp.xml"; +var UWP_TEST_RESULTS_PATH = MakeAbsolute((FilePath)"../output/test-results/uwp/TestResults.xml"); var UWP_PACKAGE_ID = "ec0cc741-fd3e-485c-81be-68815c480690"; var TCP_LISTEN_TIMEOUT = 240; -var TCP_LISTEN_PORT = 10578; +var TCP_LISTEN_PORT = 63559; var TCP_LISTEN_HOST = System.Net.Dns.GetHostEntry(System.Net.Dns.GetHostName()) - .AddressList.First(f => f.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork).ToString(); + .AddressList.First(f => f.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork) + .ToString(); + +var OUTPUT_PATH = MakeAbsolute((DirectoryPath)"../output/"); var ANDROID_HOME = EnvironmentVariable("ANDROID_HOME"); -Func DownloadTcpTextAsync = (int port, FilePath filename) => - System.Threading.Tasks.Task.Run (() => { +System.Environment.SetEnvironmentVariable("PATH", + $"{ANDROID_HOME}/tools/bin" + System.IO.Path.PathSeparator + + $"{ANDROID_HOME}/platform-tools" + System.IO.Path.PathSeparator + + $"{ANDROID_HOME}/emulator" + System.IO.Path.PathSeparator + + EnvironmentVariable("PATH")); + + +// utils + +Task DownloadTcpTextAsync(int port, FilePath filename, Action waitAction = null) +{ + filename = MakeAbsolute(filename); + EnsureDirectoryExists(filename.GetDirectory()); + + return System.Threading.Tasks.Task.Run(() => { var tcpListener = new System.Net.Sockets.TcpListener(System.Net.IPAddress.Any, port); tcpListener.Start(); var listening = true; @@ -40,9 +57,11 @@ Func DownloadTcpTextAsync = (int port, FilePath filename) = System.Threading.Tasks.Task.Run(() => { // Sleep until timeout elapses or tcp listener stopped after a successful connection var elapsed = 0; - while (elapsed <= TCP_LISTEN_TIMEOUT && listening) { + while(elapsed <= TCP_LISTEN_TIMEOUT && listening) { System.Threading.Thread.Sleep(1000); elapsed++; + Information($"Still waiting for tests... {elapsed}/{TCP_LISTEN_TIMEOUT}"); + waitAction?.Invoke(); } // If still listening, timeout elapsed, stop the listener @@ -54,76 +73,76 @@ Func DownloadTcpTextAsync = (int port, FilePath filename) = try { var tcpClient = tcpListener.AcceptTcpClient(); - var fileName = MakeAbsolute (filename).FullPath; - using (var file = System.IO.File.Open(fileName, System.IO.FileMode.Create)) + using (var file = System.IO.File.Open(filename.FullPath, System.IO.FileMode.Create)) using (var stream = tcpClient.GetStream()) stream.CopyTo(file); tcpClient.Close(); tcpListener.Stop(); - listening = false; + listening = false; } catch { throw new Exception("Test results listener failed or timed out."); } }); +} -Action AddPlatformToTestResults = (FilePath testResultsFile, string platformName) => { +void AddPlatformToTestResults(FilePath testResultsFile, string platformName) +{ if (FileExists(testResultsFile)) { var txt = FileReadText(testResultsFile); txt = txt.Replace(" + +// iOS tasks + +Task("build-ios") + .Does(() => { // Setup the test listener config to be built into the app FileWriteText((new FilePath(IOS_PROJ)).GetDirectory().CombineWithFilePath("tests.cfg"), $"{TCP_LISTEN_HOST}:{TCP_LISTEN_PORT}"); - // Nuget restore - MSBuild (IOS_PROJ, c => { - c.Configuration = "Release"; - c.Targets.Clear(); - c.Targets.Add("Restore"); - }); - - // Build the project (with ipa) - MSBuild (IOS_PROJ, c => { + MSBuild(IOS_PROJ, c => { c.Configuration = "Release"; + c.Restore = true; c.Properties["Platform"] = new List { "iPhoneSimulator" }; c.Properties["BuildIpa"] = new List { "true" }; c.Properties["ContinuousIntegrationBuild"] = new List { "false" }; c.Targets.Clear(); c.Targets.Add("Rebuild"); + c.BinaryLogger = new MSBuildBinaryLogSettings { + Enabled = true, + FileName = OUTPUT_PATH.CombineWithFilePath("binlogs/device-tests-ios-build.binlog").FullPath, + }; }); }); -Task ("test-ios-emu") - .IsDependentOn ("build-ios") - .Does (() => +Task("test-ios-emu") + .IsDependentOn("build-ios") + .Does(() => { - var sims = ListAppleSimulators (); - foreach (var s in sims) - { - Information("Info: {0} ({1} - {2} - {3})", s.Name, s.Runtime, s.UDID, s.Availability); + var sims = ListAppleSimulators(); + foreach (var s in sims) { + Information("Info: {0}({1} - {2} - {3})", s.Name, s.Runtime, s.UDID, s.Availability); } // Look for a matching simulator on the system - var sim = sims.First (s => s.Name == IOS_SIM_NAME && s.Runtime == IOS_SIM_RUNTIME); + var sim = sims.First(s => s.Name == IOS_SIM_NAME && s.Runtime == IOS_SIM_RUNTIME); // Boot the simulator - Information("Booting: {0} ({1} - {2})", sim.Name, sim.Runtime, sim.UDID); - if (!sim.State.ToLower().Contains ("booted")) - BootAppleSimulator (sim.UDID); + Information("Booting: {0}({1} - {2})", sim.Name, sim.Runtime, sim.UDID); + if (!sim.State.ToLower().Contains("booted")) + BootAppleSimulator(sim.UDID); // Wait for it to be booted var booted = false; for (int i = 0; i < 100; i++) { - if (ListAppleSimulators().Any (s => s.UDID == sim.UDID && s.State.ToLower().Contains("booted"))) { + if (ListAppleSimulators().Any(s => s.UDID == sim.UDID && s.State.ToLower().Contains("booted"))) { booted = true; break; } @@ -132,12 +151,12 @@ Task ("test-ios-emu") // Install the IPA that was previously built var ipaPath = new FilePath(IOS_IPA_PATH); - Information ("Installing: {0}", ipaPath); + Information("Installing: {0}", ipaPath); InstalliOSApplication(sim.UDID, MakeAbsolute(ipaPath).FullPath); // Start our Test Results TCP listener Information("Started TCP Test Results Listener on port: {0}", TCP_LISTEN_PORT); - var tcpListenerTask = DownloadTcpTextAsync (TCP_LISTEN_PORT, IOS_TEST_RESULTS_PATH); + var tcpListenerTask = DownloadTcpTextAsync(TCP_LISTEN_PORT, IOS_TEST_RESULTS_PATH); // Launch the IPA Information("Launching: {0}", IOS_BUNDLE_ID); @@ -145,53 +164,53 @@ Task ("test-ios-emu") // Wait for the TCP listener to get results Information("Waiting for tests..."); - tcpListenerTask.Wait (); + tcpListenerTask.Wait(); AddPlatformToTestResults(IOS_TEST_RESULTS_PATH, "iOS"); // Close up simulators Information("Closing Simulator"); - ShutdownAllAppleSimulators (); + ShutdownAllAppleSimulators(); }); -Task ("build-android") - .Does (() => -{ - // Nuget restore - MSBuild (ANDROID_PROJ, c => { - c.Configuration = "Debug"; - c.Targets.Clear(); - c.Targets.Add("Restore"); - }); +// Android tasks +Task("build-android") + .Does(() => +{ // Build the app in debug mode // needs to be debug so unit tests get discovered - MSBuild (ANDROID_PROJ, c => { + MSBuild(ANDROID_PROJ, c => { c.Configuration = "Debug"; + c.Restore = true; c.Properties["ContinuousIntegrationBuild"] = new List { "false" }; c.Targets.Clear(); c.Targets.Add("Rebuild"); + c.BinaryLogger = new MSBuildBinaryLogSettings { + Enabled = true, + FileName = OUTPUT_PATH.CombineWithFilePath("binlogs/device-tests-android-build.binlog").FullPath, + }; }); }); -Task ("test-android-emu") - .IsDependentOn ("build-android") - .Does (() => +Task("test-android-emu") + .IsDependentOn("build-android") + .Does(() => { - var avdSettings = new AndroidAvdManagerToolSettings { SdkRoot = ANDROID_HOME }; - var emuSettings = new AndroidEmulatorToolSettings { SdkRoot = ANDROID_HOME }; + var avdSettings = new AndroidAvdManagerToolSettings { SdkRoot = ANDROID_HOME }; // Delete AVD first, if it exists - Information ("Deleting AVD if exists: {0}...", ANDROID_AVD); + Information("Deleting AVD if exists: {0}...", ANDROID_AVD); try { AndroidAvdDelete(ANDROID_AVD, avdSettings); } catch { } // Create the AVD - Information ("Creating AVD: {0}...", ANDROID_AVD); - AndroidAvdCreate (ANDROID_AVD, ANDROID_EMU_TARGET, ANDROID_EMU_DEVICE, force: true, settings: avdSettings); - - Information ("Starting Emulator: {0}...", ANDROID_AVD); + Information("Creating AVD: {0}...", ANDROID_AVD); + AndroidAvdCreate(ANDROID_AVD, ANDROID_EMU_TARGET, ANDROID_EMU_DEVICE, force: true, settings: avdSettings); + + Information("Starting Emulator: {0}...", ANDROID_AVD); + var emuSettings = new AndroidEmulatorToolSettings { SdkRoot = ANDROID_HOME, ArgumentCustomization = args => args.Append("-no-window") }; var emulatorProcess = AndroidEmulatorStart(ANDROID_AVD, emuSettings); var adbSettings = new AdbToolSettings { SdkRoot = ANDROID_HOME }; @@ -212,38 +231,54 @@ Task ("test-android-emu") System.Threading.Thread.Sleep(1000); } - Information ("Matched ADB Serial: {0}", emuSerial); + Information("Matched ADB Serial: {0}", emuSerial); adbSettings = new AdbToolSettings { SdkRoot = ANDROID_HOME, Serial = emuSerial }; // Wait for the emulator to enter a 'booted' state AdbWaitForEmulatorToBoot(TimeSpan.FromSeconds(100), adbSettings); - Information ("Emulator finished booting."); + Information("Emulator finished booting."); + + // Read the logcat + AdbLogcat(new AdbLogcatOptions { Clear = true }, settings: adbSettings); + AdbLogcat(settings: adbSettings); - // Try uninstalling the existing package (if installed) - try { - AdbUninstall (ANDROID_PKG_NAME, false, adbSettings); - Information ("Uninstalled old: {0}", ANDROID_PKG_NAME); + // Try uninstalling the existing package(if installed) + try { + AdbUninstall(ANDROID_PKG_NAME, false, adbSettings); + Information("Uninstalled old: {0}", ANDROID_PKG_NAME); } catch { } // Use the Install target to push the app onto emulator - MSBuild (ANDROID_PROJ, c => { + MSBuild(ANDROID_PROJ, c => { c.Configuration = "Debug"; c.Properties["ContinuousIntegrationBuild"] = new List { "false" }; c.Properties["AdbTarget"] = new List { "-s " + emuSerial }; c.Targets.Clear(); c.Targets.Add("Install"); + c.BinaryLogger = new MSBuildBinaryLogSettings { + Enabled = true, + FileName = OUTPUT_PATH.CombineWithFilePath("binlogs/device-tests-android-install.binlog").FullPath, + }; }); // Start the TCP Test results listener Information("Started TCP Test Results Listener on port: {0}:{1}", TCP_LISTEN_HOST, TCP_LISTEN_PORT); - var tcpListenerTask = DownloadTcpTextAsync (TCP_LISTEN_PORT, ANDROID_TEST_RESULTS_PATH); + // var printed = false; + var tcpListenerTask = DownloadTcpTextAsync(TCP_LISTEN_PORT, ANDROID_TEST_RESULTS_PATH, () => { + // if (!printed) { + // AdbScreenCapture(ANDROID_SCREENSHOT_PATH.CombineWithFilePath($"screenshot.png"), adbSettings); + // AdbLogcat(settings: adbSettings); + // printed = true; + // } + }); // Launch the app on the emulator - AdbShell ($"am start -n {ANDROID_PKG_NAME}/{ANDROID_PKG_NAME}.MainActivity --es HOST_IP {TCP_LISTEN_HOST} --ei HOST_PORT {TCP_LISTEN_PORT}", adbSettings); + AdbShell($"am start -n {ANDROID_PKG_NAME}/{ANDROID_PKG_NAME}.MainActivity --es HOST_IP {TCP_LISTEN_HOST} --ei HOST_PORT {TCP_LISTEN_PORT}", adbSettings); + AdbLogcat(settings: adbSettings); // Wait for the test results to come back Information("Waiting for tests..."); - tcpListenerTask.Wait (); + tcpListenerTask.Wait(); AddPlatformToTestResults(ANDROID_TEST_RESULTS_PATH, "Android"); @@ -260,55 +295,54 @@ Task ("test-android-emu") }); -Task ("build-uwp") - .Does (() => -{ - // Nuget restore - MSBuild (UWP_PROJ, c => { - c.Targets.Clear(); - c.Targets.Add("Restore"); - }); +// UWP tasks - // Build the project (with ipa) - MSBuild (UWP_PROJ, c => { +Task("build-uwp") + .Does(() => +{ + MSBuild(UWP_PROJ, c => { c.Configuration = "Debug"; + c.Restore = true; c.Properties["ContinuousIntegrationBuild"] = new List { "false" }; c.Properties["AppxBundlePlatforms"] = new List { "x86" }; c.Properties["AppxBundle"] = new List { "Always" }; c.Targets.Clear(); c.Targets.Add("Rebuild"); + c.BinaryLogger = new MSBuildBinaryLogSettings { + Enabled = true, + FileName = OUTPUT_PATH.CombineWithFilePath("binlogs/device-tests-uwp-build.binlog").FullPath, + }; }); }); - -Task ("test-uwp-emu") - .IsDependentOn ("build-uwp") +Task("test-uwp-emu") + .IsDependentOn("build-uwp") .WithCriteria(IsRunningOnWindows()) - .Does (() => + .Does(() => { - var uninstallPS = new Action (() => { + var uninstallPS = new Action(() => { try { - StartProcess ("powershell", + StartProcess("powershell", "$app = Get-AppxPackage -Name " + UWP_PACKAGE_ID + "; if ($app) { Remove-AppxPackage -Package $app.PackageFullName }"); } catch { } }); // Try to uninstall the app if it exists from before uninstallPS(); - + // Install the appx var dependencies = GetFiles("./**/AppPackages/**/Dependencies/x86/*.appx"); foreach (var dep in dependencies) { Information("Installing Dependency appx: {0}", dep); StartProcess("powershell", "Add-AppxPackage -Path \"" + MakeAbsolute(dep).FullPath + "\""); } - var appxBundlePath = GetFiles("./**/AppPackages/**/*.appxbundle").First (); + var appxBundlePath = GetFiles("./**/AppPackages/**/*.appxbundle").First(); Information("Installing appx: {0}", appxBundlePath); - StartProcess ("powershell", "Add-AppxPackage -Path \"" + MakeAbsolute(appxBundlePath).FullPath + "\""); + StartProcess("powershell", "Add-AppxPackage -Path \"" + MakeAbsolute(appxBundlePath).FullPath + "\""); // Start the TCP Test results listener Information("Started TCP Test Results Listener on port: {0}:{1}", TCP_LISTEN_HOST, TCP_LISTEN_PORT); - var tcpListenerTask = DownloadTcpTextAsync (TCP_LISTEN_PORT, UWP_TEST_RESULTS_PATH); + var tcpListenerTask = DownloadTcpTextAsync(TCP_LISTEN_PORT, UWP_TEST_RESULTS_PATH); // Launch the app Information("Running appx: {0}", appxBundlePath); @@ -317,12 +351,13 @@ Task ("test-uwp-emu") // Wait for the test results to come back Information("Waiting for tests..."); - tcpListenerTask.Wait (); + tcpListenerTask.Wait(); AddPlatformToTestResults(UWP_TEST_RESULTS_PATH, "UWP"); - // Uninstall the app (this will terminate it too) + // Uninstall the app(this will terminate it too) uninstallPS(); }); + RunTarget(TARGET); diff --git a/README.md b/README.md index 4c193add7..23cafdfd5 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Xamarin.Essentials gives developers essential cross-platform APIs for their mobi iOS, Android, and UWP offer unique operating system and platform APIs that developers have access to, all in C# leveraging Xamarin. It is great that developers have 100% API access in C# with Xamarin, but these APIs are different per platform. This means developers have to learn three different APIs to access platform-specific features. With Xamarin.Essentials, developers have a single cross-platform API that works with any iOS, Android, or UWP application that can be accessed from shared code no matter how the user interface is created. -[![Gitter chat](https://badges.gitter.im/gitterHQ/gitter.png)](https://gitter.im/xamarin/Essentials) +[![Discord](https://img.shields.io/discord/732297728826277939?label=discord&style=plastic)](https://discord.com/invite/Y8828kE) ## Build Status diff --git a/Samples/Sample.Server.WebAuthenticator/Controllers/MobileAuthController.cs b/Samples/Sample.Server.WebAuthenticator/Controllers/MobileAuthController.cs index 71cf15823..10c288c96 100644 --- a/Samples/Sample.Server.WebAuthenticator/Controllers/MobileAuthController.cs +++ b/Samples/Sample.Server.WebAuthenticator/Controllers/MobileAuthController.cs @@ -31,13 +31,18 @@ public async Task Get([FromRoute]string scheme) } else { + var claims = auth.Principal.Identities.FirstOrDefault()?.Claims; + var email = string.Empty; + email = claims?.FirstOrDefault(c => c.Type == System.Security.Claims.ClaimTypes.Email)?.Value; + // Get parameters to send back to the callback var qs = new Dictionary - { - { "access_token", auth.Properties.GetTokenValue("access_token") }, - { "refresh_token", auth.Properties.GetTokenValue("refresh_token") ?? string.Empty }, - { "expires", (auth.Properties.ExpiresUtc?.ToUnixTimeSeconds() ?? -1).ToString() } - }; + { + { "access_token", auth.Properties.GetTokenValue("access_token") }, + { "refresh_token", auth.Properties.GetTokenValue("refresh_token") ?? string.Empty }, + { "expires", (auth.Properties.ExpiresUtc?.ToUnixTimeSeconds() ?? -1).ToString() }, + { "email", email } + }; // Build the result url var url = callbackScheme + "://#" + string.Join( diff --git a/Samples/Sample.Server.WebAuthenticator/Startup.cs b/Samples/Sample.Server.WebAuthenticator/Startup.cs index cfd45d086..52a403314 100644 --- a/Samples/Sample.Server.WebAuthenticator/Startup.cs +++ b/Samples/Sample.Server.WebAuthenticator/Startup.cs @@ -62,6 +62,13 @@ public void ConfigureServices(IServiceCollection services) => WebHostEnvironment.ContentRootFileProvider.GetFileInfo($"AuthKey_{keyId}.p8")); a.SaveTokens = true; }); + + /* + * For Apple signin + * If you are running the app on Azure you must add the Configuration setting + * WEBSITE_LOAD_USER_PROFILE = 1 + * Without this setting you will get a File Not Found exception when AppleAuthenticationHandler tries to generate a certificate using your Auth_{keyId].P8 file. + */ } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. diff --git a/Samples/Samples.Android/MainActivity.cs b/Samples/Samples.Android/MainActivity.cs index 383c77fdb..16c627aef 100644 --- a/Samples/Samples.Android/MainActivity.cs +++ b/Samples/Samples.Android/MainActivity.cs @@ -4,12 +4,18 @@ using Android.OS; using Android.Runtime; using Android.Widget; +using Samples.View; namespace Samples.Droid { [Activity(Label = "@string/app_name", Theme = "@style/MainTheme", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation)] + [IntentFilter( + new[] { Xamarin.Essentials.Platform.Intent.ActionAppAction }, + Categories = new[] { Intent.CategoryDefault })] public class MainActivity : Xamarin.Forms.Platform.Android.FormsAppCompatActivity { + static App formsApp; + protected override void OnCreate(Bundle bundle) { TabLayoutResource = Resource.Layout.Tabbar; @@ -23,19 +29,27 @@ protected override void OnCreate(Bundle bundle) Xamarin.Essentials.Platform.ActivityStateChanged += Platform_ActivityStateChanged; - LoadApplication(new App()); + LoadApplication(formsApp ??= new App()); } protected override void OnResume() { base.OnResume(); - Xamarin.Essentials.Platform.OnResume(); + Xamarin.Essentials.Platform.OnResume(this); + } + + protected override void OnNewIntent(Intent intent) + { + base.OnNewIntent(intent); + + Xamarin.Essentials.Platform.OnNewIntent(intent); } protected override void OnDestroy() { base.OnDestroy(); + Xamarin.Essentials.Platform.ActivityStateChanged -= Platform_ActivityStateChanged; } diff --git a/Samples/Samples.Android/Properties/AndroidManifest.xml b/Samples/Samples.Android/Properties/AndroidManifest.xml index 95365fa47..22facf77a 100644 --- a/Samples/Samples.Android/Properties/AndroidManifest.xml +++ b/Samples/Samples.Android/Properties/AndroidManifest.xml @@ -8,7 +8,10 @@ + + + diff --git a/Samples/Samples.Android/Resources/drawable-hdpi/app_info_action_icon.png b/Samples/Samples.Android/Resources/drawable-hdpi/app_info_action_icon.png new file mode 100644 index 000000000..28bd78b2b Binary files /dev/null and b/Samples/Samples.Android/Resources/drawable-hdpi/app_info_action_icon.png differ diff --git a/Samples/Samples.Android/Resources/drawable-hdpi/battery_action_icon.png b/Samples/Samples.Android/Resources/drawable-hdpi/battery_action_icon.png new file mode 100644 index 000000000..2b9ecc489 Binary files /dev/null and b/Samples/Samples.Android/Resources/drawable-hdpi/battery_action_icon.png differ diff --git a/Samples/Samples.Android/Resources/drawable-xhdpi/app_info_action_icon.png b/Samples/Samples.Android/Resources/drawable-xhdpi/app_info_action_icon.png new file mode 100644 index 000000000..4b0bd34ea Binary files /dev/null and b/Samples/Samples.Android/Resources/drawable-xhdpi/app_info_action_icon.png differ diff --git a/Samples/Samples.Android/Resources/drawable-xhdpi/battery_action_icon.png b/Samples/Samples.Android/Resources/drawable-xhdpi/battery_action_icon.png new file mode 100644 index 000000000..0f0339dfa Binary files /dev/null and b/Samples/Samples.Android/Resources/drawable-xhdpi/battery_action_icon.png differ diff --git a/Samples/Samples.Android/Resources/drawable-xxhdpi/app_info_action_icon.png b/Samples/Samples.Android/Resources/drawable-xxhdpi/app_info_action_icon.png new file mode 100644 index 000000000..b3209b088 Binary files /dev/null and b/Samples/Samples.Android/Resources/drawable-xxhdpi/app_info_action_icon.png differ diff --git a/Samples/Samples.Android/Resources/drawable-xxhdpi/battery_action_icon.png b/Samples/Samples.Android/Resources/drawable-xxhdpi/battery_action_icon.png new file mode 100644 index 000000000..554b363a1 Binary files /dev/null and b/Samples/Samples.Android/Resources/drawable-xxhdpi/battery_action_icon.png differ diff --git a/Samples/Samples.Android/Resources/drawable/app_info_action_icon.png b/Samples/Samples.Android/Resources/drawable/app_info_action_icon.png new file mode 100644 index 000000000..28bd78b2b Binary files /dev/null and b/Samples/Samples.Android/Resources/drawable/app_info_action_icon.png differ diff --git a/Samples/Samples.Android/Resources/drawable/battery_action_icon.png b/Samples/Samples.Android/Resources/drawable/battery_action_icon.png new file mode 100644 index 000000000..2b9ecc489 Binary files /dev/null and b/Samples/Samples.Android/Resources/drawable/battery_action_icon.png differ diff --git a/Samples/Samples.Android/Samples.Android.csproj b/Samples/Samples.Android/Samples.Android.csproj index 17e189237..9ac88fafe 100644 --- a/Samples/Samples.Android/Samples.Android.csproj +++ b/Samples/Samples.Android/Samples.Android.csproj @@ -44,7 +44,7 @@ true false armeabi-v7a;x86 - true + r8 Full Xamarin.Forms.Platform.Android;Xamarin.Forms.Platform;Xamarin.Forms.Core;Xamarin.Forms.Xaml;Samples;FormsViewGroup; @@ -62,14 +62,11 @@ - - - - - - - + + + + @@ -114,6 +111,14 @@ + + + + + + + + diff --git a/Samples/Samples.Mac/AppDelegate.cs b/Samples/Samples.Mac/AppDelegate.cs new file mode 100644 index 000000000..1a006d273 --- /dev/null +++ b/Samples/Samples.Mac/AppDelegate.cs @@ -0,0 +1,44 @@ +using AppKit; +using CoreGraphics; +using Foundation; +using Xamarin.Forms; +using Xamarin.Forms.Platform.MacOS; + +namespace Samples.Mac +{ + [Register(nameof(AppDelegate))] + public class AppDelegate : FormsApplicationDelegate + { + static App formsApp; + + NSWindow window; + + public AppDelegate() + { + var style = NSWindowStyle.Closable | NSWindowStyle.Resizable | NSWindowStyle.Titled; + + var screenSize = NSScreen.MainScreen.Frame.Size; + var rect = new CGRect(0, 0, 1024, 768); + rect.Offset((screenSize.Width - rect.Width) / 2, (screenSize.Height - rect.Height) / 2); + + window = new NSWindow(rect, style, NSBackingStore.Buffered, false) + { + Title = "Xamarin.Essentials", + TitleVisibility = NSWindowTitleVisibility.Hidden, + }; + } + + public override NSWindow MainWindow => window; + + public override void DidFinishLaunching(NSNotification notification) + { + Forms.Init(); + + LoadApplication(formsApp ??= new App()); + + base.DidFinishLaunching(notification); + } + + public override bool ApplicationShouldTerminateAfterLastWindowClosed(NSApplication sender) => true; + } +} diff --git a/Samples/Samples.Mac/Assets.xcassets/AppIcon.appiconset/AppIcon-128.png b/Samples/Samples.Mac/Assets.xcassets/AppIcon.appiconset/AppIcon-128.png new file mode 100644 index 000000000..d0b5a8098 Binary files /dev/null and b/Samples/Samples.Mac/Assets.xcassets/AppIcon.appiconset/AppIcon-128.png differ diff --git a/Samples/Samples.Mac/Assets.xcassets/AppIcon.appiconset/AppIcon-128@2x.png b/Samples/Samples.Mac/Assets.xcassets/AppIcon.appiconset/AppIcon-128@2x.png new file mode 100644 index 000000000..f4c8d2904 Binary files /dev/null and b/Samples/Samples.Mac/Assets.xcassets/AppIcon.appiconset/AppIcon-128@2x.png differ diff --git a/Samples/Samples.Mac/Assets.xcassets/AppIcon.appiconset/AppIcon-16.png b/Samples/Samples.Mac/Assets.xcassets/AppIcon.appiconset/AppIcon-16.png new file mode 100644 index 000000000..ebb5a0fe4 Binary files /dev/null and b/Samples/Samples.Mac/Assets.xcassets/AppIcon.appiconset/AppIcon-16.png differ diff --git a/Samples/Samples.Mac/Assets.xcassets/AppIcon.appiconset/AppIcon-16@2x.png b/Samples/Samples.Mac/Assets.xcassets/AppIcon.appiconset/AppIcon-16@2x.png new file mode 100644 index 000000000..0986d31be Binary files /dev/null and b/Samples/Samples.Mac/Assets.xcassets/AppIcon.appiconset/AppIcon-16@2x.png differ diff --git a/Samples/Samples.Mac/Assets.xcassets/AppIcon.appiconset/AppIcon-256.png b/Samples/Samples.Mac/Assets.xcassets/AppIcon.appiconset/AppIcon-256.png new file mode 100644 index 000000000..f4c8d2904 Binary files /dev/null and b/Samples/Samples.Mac/Assets.xcassets/AppIcon.appiconset/AppIcon-256.png differ diff --git a/Samples/Samples.Mac/Assets.xcassets/AppIcon.appiconset/AppIcon-256@2x.png b/Samples/Samples.Mac/Assets.xcassets/AppIcon.appiconset/AppIcon-256@2x.png new file mode 100644 index 000000000..a142c83fb Binary files /dev/null and b/Samples/Samples.Mac/Assets.xcassets/AppIcon.appiconset/AppIcon-256@2x.png differ diff --git a/Samples/Samples.Mac/Assets.xcassets/AppIcon.appiconset/AppIcon-32.png b/Samples/Samples.Mac/Assets.xcassets/AppIcon.appiconset/AppIcon-32.png new file mode 100644 index 000000000..0986d31be Binary files /dev/null and b/Samples/Samples.Mac/Assets.xcassets/AppIcon.appiconset/AppIcon-32.png differ diff --git a/Samples/Samples.Mac/Assets.xcassets/AppIcon.appiconset/AppIcon-32@2x.png b/Samples/Samples.Mac/Assets.xcassets/AppIcon.appiconset/AppIcon-32@2x.png new file mode 100644 index 000000000..412d6ca9b Binary files /dev/null and b/Samples/Samples.Mac/Assets.xcassets/AppIcon.appiconset/AppIcon-32@2x.png differ diff --git a/Samples/Samples.Mac/Assets.xcassets/AppIcon.appiconset/AppIcon-512.png b/Samples/Samples.Mac/Assets.xcassets/AppIcon.appiconset/AppIcon-512.png new file mode 100644 index 000000000..a142c83fb Binary files /dev/null and b/Samples/Samples.Mac/Assets.xcassets/AppIcon.appiconset/AppIcon-512.png differ diff --git a/Samples/Samples.Mac/Assets.xcassets/AppIcon.appiconset/AppIcon-512@2x.png b/Samples/Samples.Mac/Assets.xcassets/AppIcon.appiconset/AppIcon-512@2x.png new file mode 100644 index 000000000..e99022ae8 Binary files /dev/null and b/Samples/Samples.Mac/Assets.xcassets/AppIcon.appiconset/AppIcon-512@2x.png differ diff --git a/Samples/Samples.Mac/Assets.xcassets/AppIcon.appiconset/Contents.json b/Samples/Samples.Mac/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 000000000..6b2854529 --- /dev/null +++ b/Samples/Samples.Mac/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,68 @@ +{ + "images": [ + { + "filename": "AppIcon-16.png", + "size": "16x16", + "scale": "1x", + "idiom": "mac" + }, + { + "filename": "AppIcon-16@2x.png", + "size": "16x16", + "scale": "2x", + "idiom": "mac" + }, + { + "filename": "AppIcon-32.png", + "size": "32x32", + "scale": "1x", + "idiom": "mac" + }, + { + "filename": "AppIcon-32@2x.png", + "size": "32x32", + "scale": "2x", + "idiom": "mac" + }, + { + "filename": "AppIcon-128.png", + "size": "128x128", + "scale": "1x", + "idiom": "mac" + }, + { + "filename": "AppIcon-128@2x.png", + "size": "128x128", + "scale": "2x", + "idiom": "mac" + }, + { + "filename": "AppIcon-256.png", + "size": "256x256", + "scale": "1x", + "idiom": "mac" + }, + { + "filename": "AppIcon-256@2x.png", + "size": "256x256", + "scale": "2x", + "idiom": "mac" + }, + { + "filename": "AppIcon-512.png", + "size": "512x512", + "scale": "1x", + "idiom": "mac" + }, + { + "filename": "AppIcon-512@2x.png", + "size": "512x512", + "scale": "2x", + "idiom": "mac" + } + ], + "info": { + "version": 1, + "author": "xcode" + } +} \ No newline at end of file diff --git a/Samples/Samples.Mac/Assets.xcassets/Contents.json b/Samples/Samples.Mac/Assets.xcassets/Contents.json new file mode 100644 index 000000000..4caf392f9 --- /dev/null +++ b/Samples/Samples.Mac/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Samples/Samples.Mac/Entitlements.plist b/Samples/Samples.Mac/Entitlements.plist new file mode 100644 index 000000000..9ae599370 --- /dev/null +++ b/Samples/Samples.Mac/Entitlements.plist @@ -0,0 +1,6 @@ + + + + + + diff --git a/Samples/Samples.Mac/Info.plist b/Samples/Samples.Mac/Info.plist new file mode 100644 index 000000000..dbd109b59 --- /dev/null +++ b/Samples/Samples.Mac/Info.plist @@ -0,0 +1,47 @@ + + + + + CFBundleName + Xamarin.Essentials + CFBundleIdentifier + com.xamarin.essentials + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + LSMinimumSystemVersion + 10.12 + CFBundleDevelopmentRegion + en + CFBundleInfoDictionaryVersion + 6.0 + CFBundlePackageType + APPL + CFBundleSignature + ???? + NSHumanReadableCopyright + © Microsoft Corporation. All rights reserved. + NSPrincipalClass + NSApplication + NSMainStoryboardFile + Main + XSAppIconAssets + Assets.xcassets/AppIcon.appiconset + NSLocationWhenInUseUsageDescription + Access to your location is required for cool things to happen! + CFBundleURLTypes + + + CFBundleURLName + xamarinessentials + CFBundleURLSchemes + + xamarinessentials + + CFBundleTypeRole + Editor + + + + diff --git a/Samples/Samples.Mac/Main.cs b/Samples/Samples.Mac/Main.cs new file mode 100644 index 000000000..2d1f2545c --- /dev/null +++ b/Samples/Samples.Mac/Main.cs @@ -0,0 +1,14 @@ +using AppKit; + +namespace Samples.Mac +{ + static class MainClass + { + static void Main(string[] args) + { + NSApplication.Init(); + NSApplication.SharedApplication.Delegate = new AppDelegate(); + NSApplication.Main(args); + } + } +} diff --git a/Samples/Samples.Mac/Main.storyboard b/Samples/Samples.Mac/Main.storyboard new file mode 100644 index 000000000..05fe4395c --- /dev/null +++ b/Samples/Samples.Mac/Main.storyboard @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Samples/Samples.Mac/Resources/FileSystemTemplate.txt b/Samples/Samples.Mac/Resources/FileSystemTemplate.txt new file mode 100644 index 000000000..184c6db9e --- /dev/null +++ b/Samples/Samples.Mac/Resources/FileSystemTemplate.txt @@ -0,0 +1,4 @@ +This file was loaded from the app package. + +You can use this as a starting point for your comments... + diff --git a/Samples/Samples.Mac/Samples.Mac.csproj b/Samples/Samples.Mac/Samples.Mac.csproj new file mode 100644 index 000000000..88774546a --- /dev/null +++ b/Samples/Samples.Mac/Samples.Mac.csproj @@ -0,0 +1,106 @@ + + + + Debug + AnyCPU + {89899D16-4BD1-49B1-9903-9F6BB26C5DC5} + {A3F8F2AB-B479-4A4A-A458-A89E7DC349F1};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + Exe + Samples.Mac + Samples.Mac + v2.0 + Xamarin.Mac + Resources + + + true + portable + false + bin\Debug + DEBUG; + prompt + 4 + false + Mac Developer + false + false + false + true + true + true + + + + + + true + portable + true + bin\Release + + prompt + 4 + false + true + false + true + true + true + SdkOnly + + + + + + + + + + + + + + + + + + + + {CD6D6AE6-83A1-41B1-BD7C-C555A77C288B} + Xamarin.Essentials + + + {B4227123-2EEB-494A-A221-C061B5659AED} + Samples + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Samples/Samples.Tizen/Program.cs b/Samples/Samples.Tizen/Program.cs index adf3e711e..bb570d642 100644 --- a/Samples/Samples.Tizen/Program.cs +++ b/Samples/Samples.Tizen/Program.cs @@ -5,11 +5,13 @@ namespace Samples.Tizen { class Program : FormsApplication { + static App formsApp; + protected override void OnCreate() { base.OnCreate(); - LoadApplication(new App()); + LoadApplication(formsApp ??= new App()); } static void Main(string[] args) diff --git a/Samples/Samples.Tizen/tizen-manifest.xml b/Samples/Samples.Tizen/tizen-manifest.xml index b8fb3c7f7..c67be599f 100755 --- a/Samples/Samples.Tizen/tizen-manifest.xml +++ b/Samples/Samples.Tizen/tizen-manifest.xml @@ -1,12 +1,15 @@  - + Samples.Tizen.png + + + http://tizen.org/privilege/appdir.shareddata http://tizen.org/privilege/appmanager.launch http://tizen.org/privilege/externalstorage http://tizen.org/privilege/haptic @@ -17,7 +20,10 @@ http://tizen.org/privilege/mediastorage http://tizen.org/privilege/message.read http://tizen.org/privilege/network.get + http://tizen.org/privilege/externalstorage.appdata + http://tizen.org/privilege/contact.read + true true diff --git a/Samples/Samples.UWP/App.xaml.cs b/Samples/Samples.UWP/App.xaml.cs index 883f4a18d..273b2cdac 100644 --- a/Samples/Samples.UWP/App.xaml.cs +++ b/Samples/Samples.UWP/App.xaml.cs @@ -1,4 +1,5 @@ using System; +using System.Text; using Windows.ApplicationModel; using Windows.ApplicationModel.Activation; using Windows.UI.Xaml; @@ -14,13 +15,6 @@ public App() { InitializeComponent(); Suspending += OnSuspending; - - MainThread.BeginInvokeOnMainThread(() => - { - Console.WriteLine("Success!"); - }); - - var test = DeviceInfo.DeviceType; } protected override void OnLaunched(LaunchActivatedEventArgs e) @@ -57,6 +51,8 @@ protected override void OnLaunched(LaunchActivatedEventArgs e) // Ensure the current window is active Window.Current.Activate(); + + Xamarin.Essentials.Platform.OnLaunched(e); } void OnNavigationFailed(object sender, NavigationFailedEventArgs e) diff --git a/Samples/Samples.UWP/Assets/app_info_action_icon.png b/Samples/Samples.UWP/Assets/app_info_action_icon.png new file mode 100644 index 000000000..b3209b088 Binary files /dev/null and b/Samples/Samples.UWP/Assets/app_info_action_icon.png differ diff --git a/Samples/Samples.UWP/Assets/battery_action_icon.png b/Samples/Samples.UWP/Assets/battery_action_icon.png new file mode 100644 index 000000000..554b363a1 Binary files /dev/null and b/Samples/Samples.UWP/Assets/battery_action_icon.png differ diff --git a/Samples/Samples.UWP/MainPage.xaml.cs b/Samples/Samples.UWP/MainPage.xaml.cs index cd0e977ae..690282df6 100644 --- a/Samples/Samples.UWP/MainPage.xaml.cs +++ b/Samples/Samples.UWP/MainPage.xaml.cs @@ -4,13 +4,15 @@ namespace Samples.UWP { public sealed partial class MainPage : Xamarin.Forms.Platform.UWP.WindowsPage { + static Samples.App formsApp; + public MainPage() { InitializeComponent(); Platform.MapServiceToken = "RJHqIE53Onrqons5CNOx~FrDr3XhjDTyEXEjng-CRoA~Aj69MhNManYUKxo6QcwZ0wmXBtyva0zwuHB04rFYAPf7qqGJ5cHb03RCDw1jIW8l"; - LoadApplication(new Samples.App()); + LoadApplication(formsApp ??= new Samples.App()); } } } diff --git a/Samples/Samples.UWP/Samples.UWP.csproj b/Samples/Samples.UWP/Samples.UWP.csproj index 9e849cb12..f706b8de1 100644 --- a/Samples/Samples.UWP/Samples.UWP.csproj +++ b/Samples/Samples.UWP/Samples.UWP.csproj @@ -119,8 +119,8 @@ - - + + @@ -159,6 +159,8 @@ + + diff --git a/Samples/Samples.iOS/AppDelegate.cs b/Samples/Samples.iOS/AppDelegate.cs index 3976b598c..b1f5114e3 100644 --- a/Samples/Samples.iOS/AppDelegate.cs +++ b/Samples/Samples.iOS/AppDelegate.cs @@ -1,5 +1,7 @@ -using Foundation; +using System; +using Foundation; using Microsoft.AppCenter.Distribute; +using Samples.View; using UIKit; namespace Samples.iOS @@ -7,12 +9,15 @@ namespace Samples.iOS [Register(nameof(AppDelegate))] public partial class AppDelegate : Xamarin.Forms.Platform.iOS.FormsApplicationDelegate { + static App formsApp; + public override bool FinishedLaunching(UIApplication app, NSDictionary options) { Xamarin.Forms.Forms.Init(); Xamarin.Forms.FormsMaterial.Init(); + Distribute.DontCheckForUpdatesInDebug(); - LoadApplication(new App()); + LoadApplication(formsApp ??= new App()); return base.FinishedLaunching(app, options); } @@ -24,5 +29,8 @@ public override bool OpenUrl(UIApplication app, NSUrl url, NSDictionary options) return base.OpenUrl(app, url, options); } + + public override void PerformActionForShortcutItem(UIApplication application, UIApplicationShortcutItem shortcutItem, UIOperationHandler completionHandler) + => Xamarin.Essentials.Platform.PerformActionForShortcutItem(application, shortcutItem, completionHandler); } } diff --git a/Samples/Samples.iOS/Assets.xcassets/app_info_action_icon.imageset/Contents.json b/Samples/Samples.iOS/Assets.xcassets/app_info_action_icon.imageset/Contents.json new file mode 100644 index 000000000..b4e8d34b5 --- /dev/null +++ b/Samples/Samples.iOS/Assets.xcassets/app_info_action_icon.imageset/Contents.json @@ -0,0 +1,533 @@ +{ + "images": [ + { + "idiom": "universal" + }, + { + "filename": "app_info_action_icon.png", + "scale": "1x", + "idiom": "universal" + }, + { + "filename": "app_info_action_icon@2x.png", + "scale": "2x", + "idiom": "universal" + }, + { + "filename": "app_info_action_icon@3x.png", + "scale": "3x", + "idiom": "universal" + }, + { + "idiom": "iphone" + }, + { + "scale": "1x", + "idiom": "iphone" + }, + { + "scale": "2x", + "idiom": "iphone" + }, + { + "subtype": "retina4", + "scale": "2x", + "idiom": "iphone" + }, + { + "scale": "3x", + "idiom": "iphone" + }, + { + "idiom": "ipad" + }, + { + "scale": "1x", + "idiom": "ipad" + }, + { + "scale": "2x", + "idiom": "ipad" + }, + { + "idiom": "watch" + }, + { + "scale": "2x", + "idiom": "watch" + }, + { + "screenWidth": "{130,145}", + "scale": "2x", + "idiom": "watch" + }, + { + "screenWidth": "{146,165}", + "scale": "2x", + "idiom": "watch" + }, + { + "idiom": "mac" + }, + { + "scale": "1x", + "idiom": "mac" + }, + { + "scale": "2x", + "idiom": "mac" + }, + { + "idiom": "car" + }, + { + "scale": "2x", + "idiom": "car" + }, + { + "scale": "3x", + "idiom": "car" + }, + { + "appearances": [ + { + "appearance": "luminosity", + "value": "dark" + } + ], + "idiom": "universal" + }, + { + "appearances": [ + { + "appearance": "luminosity", + "value": "dark" + } + ], + "scale": "1x", + "idiom": "universal" + }, + { + "appearances": [ + { + "appearance": "luminosity", + "value": "dark" + } + ], + "scale": "2x", + "idiom": "universal" + }, + { + "appearances": [ + { + "appearance": "luminosity", + "value": "dark" + } + ], + "scale": "3x", + "idiom": "universal" + }, + { + "appearances": [ + { + "appearance": "luminosity", + "value": "dark" + } + ], + "idiom": "iphone" + }, + { + "appearances": [ + { + "appearance": "luminosity", + "value": "dark" + } + ], + "scale": "1x", + "idiom": "iphone" + }, + { + "appearances": [ + { + "appearance": "luminosity", + "value": "dark" + } + ], + "scale": "2x", + "idiom": "iphone" + }, + { + "subtype": "retina4", + "appearances": [ + { + "appearance": "luminosity", + "value": "dark" + } + ], + "scale": "2x", + "idiom": "iphone" + }, + { + "appearances": [ + { + "appearance": "luminosity", + "value": "dark" + } + ], + "scale": "3x", + "idiom": "iphone" + }, + { + "appearances": [ + { + "appearance": "luminosity", + "value": "dark" + } + ], + "idiom": "ipad" + }, + { + "appearances": [ + { + "appearance": "luminosity", + "value": "dark" + } + ], + "scale": "1x", + "idiom": "ipad" + }, + { + "appearances": [ + { + "appearance": "luminosity", + "value": "dark" + } + ], + "scale": "2x", + "idiom": "ipad" + }, + { + "appearances": [ + { + "appearance": "luminosity", + "value": "dark" + } + ], + "idiom": "watch" + }, + { + "appearances": [ + { + "appearance": "luminosity", + "value": "dark" + } + ], + "scale": "2x", + "idiom": "watch" + }, + { + "screenWidth": "{130,145}", + "appearances": [ + { + "appearance": "luminosity", + "value": "dark" + } + ], + "scale": "2x", + "idiom": "watch" + }, + { + "screenWidth": "{146,165}", + "appearances": [ + { + "appearance": "luminosity", + "value": "dark" + } + ], + "scale": "2x", + "idiom": "watch" + }, + { + "appearances": [ + { + "appearance": "luminosity", + "value": "dark" + } + ], + "idiom": "mac" + }, + { + "appearances": [ + { + "appearance": "luminosity", + "value": "dark" + } + ], + "scale": "1x", + "idiom": "mac" + }, + { + "appearances": [ + { + "appearance": "luminosity", + "value": "dark" + } + ], + "scale": "2x", + "idiom": "mac" + }, + { + "appearances": [ + { + "appearance": "luminosity", + "value": "dark" + } + ], + "idiom": "car" + }, + { + "appearances": [ + { + "appearance": "luminosity", + "value": "dark" + } + ], + "scale": "2x", + "idiom": "car" + }, + { + "appearances": [ + { + "appearance": "luminosity", + "value": "dark" + } + ], + "scale": "3x", + "idiom": "car" + }, + { + "appearances": [ + { + "appearance": "luminosity", + "value": "light" + } + ], + "idiom": "universal" + }, + { + "appearances": [ + { + "appearance": "luminosity", + "value": "light" + } + ], + "scale": "1x", + "idiom": "universal" + }, + { + "appearances": [ + { + "appearance": "luminosity", + "value": "light" + } + ], + "scale": "2x", + "idiom": "universal" + }, + { + "appearances": [ + { + "appearance": "luminosity", + "value": "light" + } + ], + "scale": "3x", + "idiom": "universal" + }, + { + "appearances": [ + { + "appearance": "luminosity", + "value": "light" + } + ], + "idiom": "iphone" + }, + { + "appearances": [ + { + "appearance": "luminosity", + "value": "light" + } + ], + "scale": "1x", + "idiom": "iphone" + }, + { + "appearances": [ + { + "appearance": "luminosity", + "value": "light" + } + ], + "scale": "2x", + "idiom": "iphone" + }, + { + "subtype": "retina4", + "appearances": [ + { + "appearance": "luminosity", + "value": "light" + } + ], + "scale": "2x", + "idiom": "iphone" + }, + { + "appearances": [ + { + "appearance": "luminosity", + "value": "light" + } + ], + "scale": "3x", + "idiom": "iphone" + }, + { + "appearances": [ + { + "appearance": "luminosity", + "value": "light" + } + ], + "idiom": "ipad" + }, + { + "appearances": [ + { + "appearance": "luminosity", + "value": "light" + } + ], + "scale": "1x", + "idiom": "ipad" + }, + { + "appearances": [ + { + "appearance": "luminosity", + "value": "light" + } + ], + "scale": "2x", + "idiom": "ipad" + }, + { + "appearances": [ + { + "appearance": "luminosity", + "value": "light" + } + ], + "idiom": "watch" + }, + { + "appearances": [ + { + "appearance": "luminosity", + "value": "light" + } + ], + "scale": "2x", + "idiom": "watch" + }, + { + "screenWidth": "{130,145}", + "appearances": [ + { + "appearance": "luminosity", + "value": "light" + } + ], + "scale": "2x", + "idiom": "watch" + }, + { + "screenWidth": "{146,165}", + "appearances": [ + { + "appearance": "luminosity", + "value": "light" + } + ], + "scale": "2x", + "idiom": "watch" + }, + { + "appearances": [ + { + "appearance": "luminosity", + "value": "light" + } + ], + "idiom": "mac" + }, + { + "appearances": [ + { + "appearance": "luminosity", + "value": "light" + } + ], + "scale": "1x", + "idiom": "mac" + }, + { + "appearances": [ + { + "appearance": "luminosity", + "value": "light" + } + ], + "scale": "2x", + "idiom": "mac" + }, + { + "appearances": [ + { + "appearance": "luminosity", + "value": "light" + } + ], + "idiom": "car" + }, + { + "appearances": [ + { + "appearance": "luminosity", + "value": "light" + } + ], + "scale": "2x", + "idiom": "car" + }, + { + "appearances": [ + { + "appearance": "luminosity", + "value": "light" + } + ], + "scale": "3x", + "idiom": "car" + } + ], + "info": { + "version": 1, + "author": "xcode" + }, + "properties": { + "template-rendering-intent": "template" + } +} \ No newline at end of file diff --git a/Samples/Samples.iOS/Assets.xcassets/app_info_action_icon.imageset/app_info_action_icon.png b/Samples/Samples.iOS/Assets.xcassets/app_info_action_icon.imageset/app_info_action_icon.png new file mode 100644 index 000000000..8ad602bc9 Binary files /dev/null and b/Samples/Samples.iOS/Assets.xcassets/app_info_action_icon.imageset/app_info_action_icon.png differ diff --git a/Samples/Samples.iOS/Assets.xcassets/app_info_action_icon.imageset/app_info_action_icon@2x.png b/Samples/Samples.iOS/Assets.xcassets/app_info_action_icon.imageset/app_info_action_icon@2x.png new file mode 100644 index 000000000..644de0354 Binary files /dev/null and b/Samples/Samples.iOS/Assets.xcassets/app_info_action_icon.imageset/app_info_action_icon@2x.png differ diff --git a/Samples/Samples.iOS/Assets.xcassets/app_info_action_icon.imageset/app_info_action_icon@3x.png b/Samples/Samples.iOS/Assets.xcassets/app_info_action_icon.imageset/app_info_action_icon@3x.png new file mode 100644 index 000000000..4d55ee2cd Binary files /dev/null and b/Samples/Samples.iOS/Assets.xcassets/app_info_action_icon.imageset/app_info_action_icon@3x.png differ diff --git a/Samples/Samples.iOS/Assets.xcassets/battery_action_icon.imageset/Contents.json b/Samples/Samples.iOS/Assets.xcassets/battery_action_icon.imageset/Contents.json new file mode 100644 index 000000000..941417708 --- /dev/null +++ b/Samples/Samples.iOS/Assets.xcassets/battery_action_icon.imageset/Contents.json @@ -0,0 +1,533 @@ +{ + "images": [ + { + "idiom": "universal" + }, + { + "filename": "battery_action_icon.png", + "scale": "1x", + "idiom": "universal" + }, + { + "filename": "battery_action_icon@2x.png", + "scale": "2x", + "idiom": "universal" + }, + { + "filename": "battery_action_icon@3x.png", + "scale": "3x", + "idiom": "universal" + }, + { + "idiom": "iphone" + }, + { + "scale": "1x", + "idiom": "iphone" + }, + { + "scale": "2x", + "idiom": "iphone" + }, + { + "subtype": "retina4", + "scale": "2x", + "idiom": "iphone" + }, + { + "scale": "3x", + "idiom": "iphone" + }, + { + "idiom": "ipad" + }, + { + "scale": "1x", + "idiom": "ipad" + }, + { + "scale": "2x", + "idiom": "ipad" + }, + { + "idiom": "watch" + }, + { + "scale": "2x", + "idiom": "watch" + }, + { + "screenWidth": "{130,145}", + "scale": "2x", + "idiom": "watch" + }, + { + "screenWidth": "{146,165}", + "scale": "2x", + "idiom": "watch" + }, + { + "idiom": "mac" + }, + { + "scale": "1x", + "idiom": "mac" + }, + { + "scale": "2x", + "idiom": "mac" + }, + { + "idiom": "car" + }, + { + "scale": "2x", + "idiom": "car" + }, + { + "scale": "3x", + "idiom": "car" + }, + { + "appearances": [ + { + "appearance": "luminosity", + "value": "dark" + } + ], + "idiom": "universal" + }, + { + "appearances": [ + { + "appearance": "luminosity", + "value": "dark" + } + ], + "scale": "1x", + "idiom": "universal" + }, + { + "appearances": [ + { + "appearance": "luminosity", + "value": "dark" + } + ], + "scale": "2x", + "idiom": "universal" + }, + { + "appearances": [ + { + "appearance": "luminosity", + "value": "dark" + } + ], + "scale": "3x", + "idiom": "universal" + }, + { + "appearances": [ + { + "appearance": "luminosity", + "value": "dark" + } + ], + "idiom": "iphone" + }, + { + "appearances": [ + { + "appearance": "luminosity", + "value": "dark" + } + ], + "scale": "1x", + "idiom": "iphone" + }, + { + "appearances": [ + { + "appearance": "luminosity", + "value": "dark" + } + ], + "scale": "2x", + "idiom": "iphone" + }, + { + "subtype": "retina4", + "appearances": [ + { + "appearance": "luminosity", + "value": "dark" + } + ], + "scale": "2x", + "idiom": "iphone" + }, + { + "appearances": [ + { + "appearance": "luminosity", + "value": "dark" + } + ], + "scale": "3x", + "idiom": "iphone" + }, + { + "appearances": [ + { + "appearance": "luminosity", + "value": "dark" + } + ], + "idiom": "ipad" + }, + { + "appearances": [ + { + "appearance": "luminosity", + "value": "dark" + } + ], + "scale": "1x", + "idiom": "ipad" + }, + { + "appearances": [ + { + "appearance": "luminosity", + "value": "dark" + } + ], + "scale": "2x", + "idiom": "ipad" + }, + { + "appearances": [ + { + "appearance": "luminosity", + "value": "dark" + } + ], + "idiom": "watch" + }, + { + "appearances": [ + { + "appearance": "luminosity", + "value": "dark" + } + ], + "scale": "2x", + "idiom": "watch" + }, + { + "screenWidth": "{130,145}", + "appearances": [ + { + "appearance": "luminosity", + "value": "dark" + } + ], + "scale": "2x", + "idiom": "watch" + }, + { + "screenWidth": "{146,165}", + "appearances": [ + { + "appearance": "luminosity", + "value": "dark" + } + ], + "scale": "2x", + "idiom": "watch" + }, + { + "appearances": [ + { + "appearance": "luminosity", + "value": "dark" + } + ], + "idiom": "mac" + }, + { + "appearances": [ + { + "appearance": "luminosity", + "value": "dark" + } + ], + "scale": "1x", + "idiom": "mac" + }, + { + "appearances": [ + { + "appearance": "luminosity", + "value": "dark" + } + ], + "scale": "2x", + "idiom": "mac" + }, + { + "appearances": [ + { + "appearance": "luminosity", + "value": "dark" + } + ], + "idiom": "car" + }, + { + "appearances": [ + { + "appearance": "luminosity", + "value": "dark" + } + ], + "scale": "2x", + "idiom": "car" + }, + { + "appearances": [ + { + "appearance": "luminosity", + "value": "dark" + } + ], + "scale": "3x", + "idiom": "car" + }, + { + "appearances": [ + { + "appearance": "luminosity", + "value": "light" + } + ], + "idiom": "universal" + }, + { + "appearances": [ + { + "appearance": "luminosity", + "value": "light" + } + ], + "scale": "1x", + "idiom": "universal" + }, + { + "appearances": [ + { + "appearance": "luminosity", + "value": "light" + } + ], + "scale": "2x", + "idiom": "universal" + }, + { + "appearances": [ + { + "appearance": "luminosity", + "value": "light" + } + ], + "scale": "3x", + "idiom": "universal" + }, + { + "appearances": [ + { + "appearance": "luminosity", + "value": "light" + } + ], + "idiom": "iphone" + }, + { + "appearances": [ + { + "appearance": "luminosity", + "value": "light" + } + ], + "scale": "1x", + "idiom": "iphone" + }, + { + "appearances": [ + { + "appearance": "luminosity", + "value": "light" + } + ], + "scale": "2x", + "idiom": "iphone" + }, + { + "subtype": "retina4", + "appearances": [ + { + "appearance": "luminosity", + "value": "light" + } + ], + "scale": "2x", + "idiom": "iphone" + }, + { + "appearances": [ + { + "appearance": "luminosity", + "value": "light" + } + ], + "scale": "3x", + "idiom": "iphone" + }, + { + "appearances": [ + { + "appearance": "luminosity", + "value": "light" + } + ], + "idiom": "ipad" + }, + { + "appearances": [ + { + "appearance": "luminosity", + "value": "light" + } + ], + "scale": "1x", + "idiom": "ipad" + }, + { + "appearances": [ + { + "appearance": "luminosity", + "value": "light" + } + ], + "scale": "2x", + "idiom": "ipad" + }, + { + "appearances": [ + { + "appearance": "luminosity", + "value": "light" + } + ], + "idiom": "watch" + }, + { + "appearances": [ + { + "appearance": "luminosity", + "value": "light" + } + ], + "scale": "2x", + "idiom": "watch" + }, + { + "screenWidth": "{130,145}", + "appearances": [ + { + "appearance": "luminosity", + "value": "light" + } + ], + "scale": "2x", + "idiom": "watch" + }, + { + "screenWidth": "{146,165}", + "appearances": [ + { + "appearance": "luminosity", + "value": "light" + } + ], + "scale": "2x", + "idiom": "watch" + }, + { + "appearances": [ + { + "appearance": "luminosity", + "value": "light" + } + ], + "idiom": "mac" + }, + { + "appearances": [ + { + "appearance": "luminosity", + "value": "light" + } + ], + "scale": "1x", + "idiom": "mac" + }, + { + "appearances": [ + { + "appearance": "luminosity", + "value": "light" + } + ], + "scale": "2x", + "idiom": "mac" + }, + { + "appearances": [ + { + "appearance": "luminosity", + "value": "light" + } + ], + "idiom": "car" + }, + { + "appearances": [ + { + "appearance": "luminosity", + "value": "light" + } + ], + "scale": "2x", + "idiom": "car" + }, + { + "appearances": [ + { + "appearance": "luminosity", + "value": "light" + } + ], + "scale": "3x", + "idiom": "car" + } + ], + "info": { + "version": 1, + "author": "xcode" + }, + "properties": { + "template-rendering-intent": "template" + } +} \ No newline at end of file diff --git a/Samples/Samples.iOS/Assets.xcassets/battery_action_icon.imageset/battery_action_icon.png b/Samples/Samples.iOS/Assets.xcassets/battery_action_icon.imageset/battery_action_icon.png new file mode 100644 index 000000000..313d80fb2 Binary files /dev/null and b/Samples/Samples.iOS/Assets.xcassets/battery_action_icon.imageset/battery_action_icon.png differ diff --git a/Samples/Samples.iOS/Assets.xcassets/battery_action_icon.imageset/battery_action_icon@2x.png b/Samples/Samples.iOS/Assets.xcassets/battery_action_icon.imageset/battery_action_icon@2x.png new file mode 100644 index 000000000..60dd5f27f Binary files /dev/null and b/Samples/Samples.iOS/Assets.xcassets/battery_action_icon.imageset/battery_action_icon@2x.png differ diff --git a/Samples/Samples.iOS/Assets.xcassets/battery_action_icon.imageset/battery_action_icon@3x.png b/Samples/Samples.iOS/Assets.xcassets/battery_action_icon.imageset/battery_action_icon@3x.png new file mode 100644 index 000000000..8535fbdfc Binary files /dev/null and b/Samples/Samples.iOS/Assets.xcassets/battery_action_icon.imageset/battery_action_icon@3x.png differ diff --git a/Samples/Samples.iOS/Info.plist b/Samples/Samples.iOS/Info.plist index 94697620f..06926b44c 100644 --- a/Samples/Samples.iOS/Info.plist +++ b/Samples/Samples.iOS/Info.plist @@ -82,5 +82,13 @@ Get Location NSLocationAlwaysUsageDescription Get Location + NSPhotoLibraryAddUsageDescription + Pick Photos + NSPhotoLibraryUsageDescription + Pick Photos + NSMicrophoneUsageDescription + Catpure Video + NSContactsUsageDescription + Contacts diff --git a/Samples/Samples.iOS/Resources/app_info_action_icon@2x.png b/Samples/Samples.iOS/Resources/app_info_action_icon@2x.png new file mode 100644 index 000000000..b3209b088 Binary files /dev/null and b/Samples/Samples.iOS/Resources/app_info_action_icon@2x.png differ diff --git a/Samples/Samples.iOS/Resources/battery_action_icon@2x.png b/Samples/Samples.iOS/Resources/battery_action_icon@2x.png new file mode 100644 index 000000000..554b363a1 Binary files /dev/null and b/Samples/Samples.iOS/Resources/battery_action_icon@2x.png differ diff --git a/Samples/Samples.iOS/Samples.iOS.csproj b/Samples/Samples.iOS/Samples.iOS.csproj index edecd23ad..3952e28b9 100644 --- a/Samples/Samples.iOS/Samples.iOS.csproj +++ b/Samples/Samples.iOS/Samples.iOS.csproj @@ -77,8 +77,8 @@ - - + + @@ -117,5 +117,35 @@ + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + + + + \ No newline at end of file diff --git a/Samples/Samples/App.xaml.cs b/Samples/Samples/App.xaml.cs index 4949d5445..c152f8573 100644 --- a/Samples/Samples/App.xaml.cs +++ b/Samples/Samples/App.xaml.cs @@ -1,4 +1,8 @@ -using Microsoft.AppCenter; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AppCenter; using Microsoft.AppCenter.Analytics; using Microsoft.AppCenter.Crashes; using Microsoft.AppCenter.Distribute; @@ -20,13 +24,16 @@ public App() InitializeComponent(); // Enable currently experimental features + Device.SetFlags(new string[] { "MediaElement_Experimental" }); VersionTracking.Track(); MainPage = new NavigationPage(new HomePage()); + + AppActions.OnAppAction += AppActions_OnAppAction; } - protected override void OnStart() + protected override async void OnStart() { if ((Device.RuntimePlatform == Device.Android && CommonConstants.AppCenterAndroid != "AC_ANDROID") || (Device.RuntimePlatform == Device.iOS && CommonConstants.AppCenteriOS != "AC_IOS") || @@ -40,6 +47,37 @@ protected override void OnStart() typeof(Crashes), typeof(Distribute)); } + + await AppActions.SetAsync( + new AppAction("app_info", "App Info", icon: "app_info_action_icon"), + new AppAction("battery_info", "Battery Info")); + } + + void AppActions_OnAppAction(object sender, AppActionEventArgs e) + { + // Don't handle events fired for old application instances + // and cleanup the old instance's event handler + if (Application.Current != this && Application.Current is App app) + { + AppActions.OnAppAction -= app.AppActions_OnAppAction; + return; + } + + Device.BeginInvokeOnMainThread(async () => + { + var page = e.AppAction.Id switch + { + "battery_info" => new BatteryPage(), + "app_info" => new AppInfoPage(), + _ => default(Page) + }; + + if (page != null) + { + await Application.Current.MainPage.Navigation.PopToRootAsync(); + await Application.Current.MainPage.Navigation.PushAsync(page); + } + }); } protected override void OnSleep() diff --git a/Samples/Samples/Helpers/ViewHelpers.cs b/Samples/Samples/Helpers/ViewHelpers.cs new file mode 100644 index 000000000..73fd41b19 --- /dev/null +++ b/Samples/Samples/Helpers/ViewHelpers.cs @@ -0,0 +1,32 @@ +using Xamarin.Forms; + +namespace Samples.Helpers +{ + public static class ViewHelpers + { + public static Rectangle GetAbsoluteBounds(this Xamarin.Forms.View element) + { + Element looper = element; + + var absoluteX = element.X + element.Margin.Top; + var absoluteY = element.Y + element.Margin.Left; + + // TODO: add logic to handle titles, headers, or other non-view bars + + while (looper.Parent != null) + { + looper = looper.Parent; + if (looper is Xamarin.Forms.View v) + { + absoluteX += v.X + v.Margin.Top; + absoluteY += v.Y + v.Margin.Left; + } + } + + return new Rectangle(absoluteX, absoluteY, element.Width, element.Height); + } + + public static System.Drawing.Rectangle ToSystemRectangle(this Rectangle rect) => + new System.Drawing.Rectangle((int)rect.X, (int)rect.Y, (int)rect.Width, (int)rect.Height); + } +} diff --git a/Samples/Samples/Model/PermissionItem.cs b/Samples/Samples/Model/PermissionItem.cs index 34135dff7..94c592e35 100644 --- a/Samples/Samples/Model/PermissionItem.cs +++ b/Samples/Samples/Model/PermissionItem.cs @@ -3,12 +3,13 @@ using System.ComponentModel; using System.Text; using System.Windows.Input; +using Samples.ViewModel; using Xamarin.Essentials; using Xamarin.Forms; namespace Samples.Model { - public class PermissionItem : INotifyPropertyChanged + public class PermissionItem : ObservableObject { public PermissionItem(string title, Permissions.BasePermission permission) { @@ -19,6 +20,8 @@ public PermissionItem(string title, Permissions.BasePermission permission) public string Title { get; set; } + public string Rationale { get; set; } + public PermissionStatus Status { get; set; } public Permissions.BasePermission Permission { get; set; } @@ -29,7 +32,7 @@ public PermissionItem(string title, Permissions.BasePermission permission) try { Status = await Permission.CheckStatusAsync(); - NotifyPropertyChanged(nameof(Status)); + OnPropertyChanged(nameof(Status)); } catch (Exception ex) { @@ -43,7 +46,7 @@ public PermissionItem(string title, Permissions.BasePermission permission) try { Status = await Permission.RequestAsync(); - NotifyPropertyChanged(nameof(Status)); + OnPropertyChanged(nameof(Status)); } catch (Exception ex) { @@ -51,9 +54,18 @@ public PermissionItem(string title, Permissions.BasePermission permission) } }); - public event PropertyChangedEventHandler PropertyChanged; - - public void NotifyPropertyChanged(string name) - => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name)); + public ICommand ShouldShowRationaleCommand => + new Command(() => + { + try + { + Rationale = $"Should show rationale: {Permission.ShouldShowRationale()}"; + OnPropertyChanged(nameof(Rationale)); + } + catch (Exception ex) + { + MessagingCenter.Send(this, nameof(PermissionException), ex); + } + }); } } diff --git a/Samples/Samples/Samples.csproj b/Samples/Samples/Samples.csproj index fdf05730e..47a2eb636 100644 --- a/Samples/Samples/Samples.csproj +++ b/Samples/Samples/Samples.csproj @@ -15,8 +15,8 @@ - - + + diff --git a/Samples/Samples/View/ColorConvertersPage.xaml b/Samples/Samples/View/ColorConvertersPage.xaml index 0ba4a38c9..86f39f1de 100644 --- a/Samples/Samples/View/ColorConvertersPage.xaml +++ b/Samples/Samples/View/ColorConvertersPage.xaml @@ -13,10 +13,6 @@ - - - - @@ -28,6 +24,8 @@ + + diff --git a/Samples/Samples/View/ContactsPage.xaml b/Samples/Samples/View/ContactsPage.xaml new file mode 100644 index 000000000..c4e621529 --- /dev/null +++ b/Samples/Samples/View/ContactsPage.xaml @@ -0,0 +1,37 @@ + + + + + + + +