diff --git a/.gitignore b/.gitignore
index 2359c60d8..1fc804458 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,9 +1,11 @@
log/
+bin/
obj/
_site/
.optemp/
_themes*/
_repo.*/
+.vs/
.openpublishing.buildcore.ps1
.DS_Store
diff --git a/docs/TOC.yml b/docs/TOC.yml
index c9f17993b..271692cea 100644
--- a/docs/TOC.yml
+++ b/docs/TOC.yml
@@ -413,6 +413,84 @@
href: user-interface/visual-states.md
- name: Platform integration
items:
+ - name: Application model
+ items:
+ - name: App actions
+ href: platform-integration/appmodel/app-actions.md
+ - name: App information
+ href: platform-integration/appmodel/app-information.md
+ - name: Browser
+ href: platform-integration/appmodel/open-browser.md
+ - name: Launcher
+ href: platform-integration/appmodel/launcher.md
+ - name: Main thread
+ href: platform-integration/appmodel/main-thread.md
+ - name: Maps
+ href: platform-integration/appmodel/maps.md
+ - name: Permissions
+ href: platform-integration/appmodel/permissions.md
+ - name: Version tracking
+ href: platform-integration/appmodel/version-tracking.md
+ - name: Communication
+ items:
+ - name: Contacts
+ href: platform-integration/communication/contacts.md
+ - name: Email
+ href: platform-integration/communication/email.md
+ - name: Networking
+ href: platform-integration/communication/networking.md
+ - name: Phone dialer
+ href: platform-integration/communication/phone-dialer.md
+ - name: SMS (messaging)
+ href: platform-integration/communication/sms.md
+ - name: Web authentication
+ href: platform-integration/communication/authentication.md
+ - name: Device features
+ items:
+ - name: Battery
+ href: platform-integration/device/battery.md
+ - name: Device display
+ href: platform-integration/device/display.md
+ - name: Device information
+ href: platform-integration/device/information.md
+ - name: Device sensors
+ href: platform-integration/device/sensors.md
+ - name: Flashlight
+ href: platform-integration/device/flashlight.md
+ - name: Geocoding
+ href: platform-integration/device/geocoding.md
+ - name: Geolocation
+ href: platform-integration/device/geolocation.md
+ - name: Haptic feedback
+ href: platform-integration/device/haptic-feedback.md
+ - name: Vibration
+ href: platform-integration/device/vibrate.md
+ - name: Media
+ items:
+ - name: Photo picker
+ href: platform-integration/device-media/picker.md
+ - name: Screenshot
+ href: platform-integration/device-media/screenshot.md
+ - name: Text-to-speech
+ href: platform-integration/device-media/text-to-speech.md
+ - name: Unit converters
+ href: platform-integration/device-media/unit-converters.md
+ - name: Sharing
+ items:
+ - name: Clipboard
+ href: platform-integration/data/clipboard.md
+ - name: Share files and text
+ href: platform-integration/data/share.md
+ - name: Storage
+ items:
+ - name: File picker
+ href: platform-integration/storage/file-picker.md
+ - name: File system helpers
+ href: platform-integration/storage/file-system-helpers.md
+ - name: Preferences
+ href: platform-integration/storage/preferences.md
+ - name: Secure storage
+ href: platform-integration/storage/secure-storage.md
- name: Configure multi-targeting
href: platform-integration/configure-multi-targeting.md
- name: Invoke platform code
diff --git a/docs/fundamentals/accessibility.md b/docs/fundamentals/accessibility.md
index 21d1829e4..43f439b36 100644
--- a/docs/fundamentals/accessibility.md
+++ b/docs/fundamentals/accessibility.md
@@ -176,13 +176,17 @@ label.SetSemanticFocus();
## Semantic screen reader
-.NET MAUI Essentials includes a `SemanticScreenReader` class that enables you to instruct a screen reader to announce specified text. This can be achieved by calling the `SemanticScreenReader.Announce` method, passing a `string` argument that represents the text:
+.NET Maui provides the `ISemanticScreenReader` interface, with which you can instruct a screen reader to announce text to the user. The interface is exposed through the `SemanticScreenReader.Default` property, and is available in the `Microsoft.Maui.Accessability` namespace.
+
+To instruct a screen reader to announce text, use the `Announce` method, passing a `string` argument that represents the text. The following example demonstrates using this method:
```csharp
-SemanticScreenReader.Announce("This is the announcement text.");
+SemanticScreenReader.Default.Announce("This is the announcement text.");
```
-
+### Limitations
+
+The default platform screen reader must be enabled for text to be read aloud.
+
+# [Android](#tab/android)
+
+In the _Platforms/Android/MainActivity.cs_ file, add the following `IntentFilter` attribute to the `MainActivity` class:
+
+:::code language="csharp" source="../snippets/shared_2/Platforms/Android/MainActivity.cs" id="intent_filter_1":::
+
+# [iOS](#tab/ios)
+
+No setup is required.
+
+# [Windows](#tab/windows)
+
+No setup is required.
+
+-----
+
+
+
+## Create actions
+
+App actions can be created at any time, but are often created when an app starts. To configure app actions, add the `ConfigureEssentials` step to the `CreateMauiApp` bootstrap code. The app startup code is configured in the _MauiProgram.cs_ file. There are two methods you must call to enable an app action:
+
+01. `AddAppAction`
+
+ This method creates an action. It takes an `id` string to uniquely identify the action, and a `title` string that's displayed to the user. You can optionally provide a subtitle and an icon.
+
+01. `OnAppAction`
+
+ The delegate passed to this method is called when the user invokes an app action, provided the app action instance. Check the `Id` property of the action to determine which app action was started by the user.
+
+The following code demonstrates how to configure the app actions at app startup:
+
+:::code language="csharp" source="../snippets/shared_1/MauiProgram.cs" id="bootstrap_appaction" highlight="12-18":::
+
+## Responding to actions
+
+After app actions [have been configured](#create-actions), the `OnAppAction` method is called for all app actions invoked by the user. Use the `Id` property to differentiate them. The following code demonstrates handling an app action:
+
+:::code language="csharp" source="../snippets/shared_1/App.xaml.cs" id="appaction_handle":::
+
+### Check if app actions are supported
+
+When you create an app action, either at app startup or while the app is being used, check to see if app actions are supported by reading the `AppActions.Current.IsSupported` property.
+
+### Create an app action outside of the startup bootstrap
+
+To create app actions, call the `SetAsync` method:
+
+:::code language="csharp" source="../snippets/shared_2/MainPage.xaml.cs" id="app_actions":::
+
+### More information about app actions
+
+If app actions aren't supported on the specific version of the operating system, a `FeatureNotSupportedException` will be thrown.
+
+The following properties can be set on an `AppAction`:
+
+- **Id**: A unique identifier used to respond to the action tap.
+- **Title**: the visible title to display.
+- **Subtitle**: If supported a subtitle to display under the title.
+- **Icon**: Must match icons in the corresponding resources directory on each platform.
+
+
+:::image type="content" source="media/app-actions/appactions.png" alt-text="App actions on home screen.":::
+
+## Get actions
+
+You can get the current list of app actions by calling `AppActions.Current.GetAsync()`.
diff --git a/docs/platform-integration/appmodel/app-information.md b/docs/platform-integration/appmodel/app-information.md
new file mode 100644
index 000000000..ae49a630e
--- /dev/null
+++ b/docs/platform-integration/appmodel/app-information.md
@@ -0,0 +1,104 @@
+---
+title: "App Information"
+description: "Describes the IAppInfo interface in the Microsoft.Maui.ApplicationModel namespace, which provides information about your application. For example, it exposes the app name and version."
+ms.date: 05/23/2022
+no-loc: ["Microsoft.Maui", "Microsoft.Maui.ApplicationModel"]
+---
+
+# App information
+
+This article describes how you can use the .NET Multi-platform App UI (.NET MAUI) `IAppInfo` interface, which provides information about your application. The `IAppInfo` interface is exposed through the `AppInfo.Current` property.
+
+[!INCLUDE [docs under construction](~/includes/preview-note.md)]
+
+The `AppInfo` and `IAppInfo` types are available in the `Microsoft.Maui.ApplicationModel` namespace.
+
+## Read the app information
+
+There are four properties exposed by the `IAppInfo` interface:
+
+- `IAppInfo.Name` — The application name
+- `IAppInfo.PackageName` — The package name or application identifier, such as `com.microsoft.myapp`.
+- `IAppInfo.VersionString` — The application version, such as `1.0.0`.
+- `IAppInfo.BuildString` — The build number of the version, such as `1000`.
+
+The following code example demonstrates accessing these properties:
+
+:::code language="csharp" source="../snippets/shared_1/AppModelPage.xaml.cs" id="read_info":::
+
+## Read the current theme
+
+The `RequestedTheme` property provides the current requested theme by the system for your application. One of the following values is returned:
+
+- `Unspecified`
+- `Light`
+- `Dark`
+
+`Unspecified` is returned when the operating system doesn't have a specific user interface style. An example of this is on devices running versions of iOS older than 13.0.
+
+The following code example demonstrates reading the theme:
+
+:::code language="csharp" source="../snippets/shared_1/AppModelPage.xaml.cs" id="read_theme":::
+
+## Display app settings
+
+The `IAppInfo` class can also display a page of settings maintained by the operating system for the application:
+
+:::code language="csharp" source="../snippets/shared_1/AppModelPage.xaml.cs" id="show_settings":::
+
+This settings page allows the user to change application permissions and perform other platform-specific tasks.
+
+## Platform implementation specifics
+
+This section describes platform-specific implementation details related to the `IAppInfo` interface.
+
+
+
+# [Android](#tab/android)
+
+App information is taken from the _AndroidManifest.xml_ for the following fields:
+
+- **Build** — `android:versionCode` in `manifest` node
+- **Name** — `android:label` in the `application` node
+- **PackageName** — `package` in the `manifest` node
+- **VersionString** — `android:versionName` in the `application` node
+
+### Requested theme
+
+Android uses configuration modes to specify the type of theme to request from the user. Based on the version of Android, it can be changed by the user or may be changed when battery saver mode is enabled.
+
+You can read more on the official [Android documentation for Dark Theme](https://developer.android.com/guide/topics/ui/look-and-feel/darktheme).
+
+# [iOS](#tab/ios)
+
+App information is taken from the _Info.plist_ for the following fields:
+
+- **Build** — `CFBundleVersion`
+- **Name** — `CFBundleDisplayName` if set, else `CFBundleName`
+- **PackageName** — `CFBundleIdentifier`
+- **VersionString** — `CFBundleShortVersionString`
+
+### Requested theme
+
+_Unspecified_ is always returned on versions of iOS older than 13.0
+
+# [Windows](#tab/windows)
+
+App information is taken from the _Package.appxmanifest_ for the following fields:
+
+- **Build** — Uses the `Build` from the `Version` on the `Identity` node
+- **Name** — `DisplayName` on the `Properties` node
+- **PackageName** — `Name` on the `Identity` node
+- **VersionString** — `Version` on the `Identity` node
+
+### Requested theme
+
+Code that accesses the `IAppInfo.RequestedTheme` property must be called on the UI thread or an exception will be thrown.
+
+Windows applications respect the `RequestedTheme` property setting in the Windows _App.xaml_. If it's set to a specific theme, this API always returns this setting. To use the dynamic theme of the OS, remove this property from your application. When your app is run, it returns the theme set by the user in Windows settings: **Settings** > **Personalization** > **Colors** > **Choose your default app mode**.
+
+
+
+--------------
+
+
diff --git a/docs/platform-integration/appmodel/launcher.md b/docs/platform-integration/appmodel/launcher.md
new file mode 100644
index 000000000..434f67a97
--- /dev/null
+++ b/docs/platform-integration/appmodel/launcher.md
@@ -0,0 +1,95 @@
+---
+title: "Launcher"
+description: "Learn how to use the .NET MAUI ILauncher interface in the Microsoft.Maui.ApplicationModel namespace, which can open another application by URI."
+ms.date: 05/23/2022
+no-loc: ["Microsoft.Maui", "Microsoft.Maui.ApplicationModel"]
+---
+
+# Launcher
+
+This article describes how you can use the .NET Multi-platform App UI (.NET MAUI) `ILauncher` interface. This interface enables an application to open a URI by the system. This is often used when deep linking into another application's custom URI schemes. The `ILauncher` interface is exposed through the `Launcher.Default` property.
+
+[!INCLUDE [docs under construction](~/includes/preview-note.md)]
+
+> [!IMPORTANT]
+> To open the browser to a website, use the [Browser](open-browser.md) API instead.
+
+## Get started
+
+To access the launcher functionality, the following platform-specific setup is required.
+
+
+# [Android](#tab/android)
+
+No setup is required.
+
+# [iOS](#tab/ios)
+
+Apple requires that you define the schemes you want to use. Add the `LSApplicationQueriesSchemes` key and schemes to the _Platforms/iOS/Info.plist_ file:
+
+```xml
+LSApplicationQueriesSchemes
+
+ lyft
+ fb
+
+```
+
+The `` elements are the URI schemes preregistered with your app. You can't use schemes outside of this list.
+
+# [Windows](#tab/windows)
+
+No setup is required.
+
+-----
+
+
+## Open another app
+
+To use the Launcher functionality, call the `Launcher.OpenAsync` method and pass in a `string` or `Uri` representing the app to open. Optionally, the `Launcher.CanOpenAsync` method can be used to check if the URI scheme can be handled by an app on the device. The following code demonstrates how to check if a URI scheme is supported or not, and then opens the URI:
+
+:::code language="csharp" source="../snippets/shared_1/AppModelPage.xaml.cs" id="launcher_open":::
+
+The previous code example can be simplified by using the `TryOpenAsync`, which checks if the URI scheme can be opened, before opening it:
+
+:::code language="csharp" source="../snippets/shared_1/AppModelPage.xaml.cs" id="launcher_open_try":::
+
+## Open another app via a file
+
+The launcher can also be used to open an app with a selected file. .NET MAUI automatically detects the file type (MIME), and opens the default app for that file type. If more than one app is registered with the file type, an app selection popover is shown to the user.
+
+The following code example writes text to a file, and opens the text file with the launcher:
+
+:::code language="csharp" source="../snippets/shared_1/AppModelPage.xaml.cs" id="launcher_open_file":::
+
+## Set the launcher location
+
+[!INCLUDE [ios-PresentationSourceBounds](../includes/ios-PresentationSourceBounds.md)]
+
+## Platform differences
+
+This section describes the platform-specific differences with the launcher API.
+
+
+
+# [Android](#tab/android)
+
+The `Task` returned from `CanOpenAsync` completes immediately.
+
+# [iOS](#tab/ios)
+
+The `Task` returned from `CanOpenAsync` completes immediately.
+
+If the target app on the device has never been opened by your application with `OpenAsync`, iOS displays a popover to the user, requesting permission to allow this action.
+
+
+
+# [Windows](#tab/windows)
+
+No platform differences.
+
+-----
+
+
diff --git a/docs/platform-integration/appmodel/main-thread.md b/docs/platform-integration/appmodel/main-thread.md
new file mode 100644
index 000000000..c1b8fc768
--- /dev/null
+++ b/docs/platform-integration/appmodel/main-thread.md
@@ -0,0 +1,50 @@
+---
+title: Run code on the main UI thread
+description: "In .NET MAUI, event handlers may be called on a secondary thread. The MainThread class allows an application to run code on the main UI thread. This article describes how to use the MainThread class."
+ms.date: 05/23/2022
+no-loc: ["Microsoft.Maui", "Microsoft.Maui.ApplicationModel"]
+---
+
+# Create a thread on the .NET MAUI UI thread
+
+This article describes how you can use the .NET Multi-platform App UI (.NET MAUI) `MainThread` class to run code on the main UI thread. Most operating systems use a single-threading model for code involving the user interface. This model is necessary to properly serialize user-interface events, including keystrokes and touch input. This thread is often called the _main thread_, the _user-interface thread_, or the _UI thread_. The disadvantage of this model is that all code that accesses user interface elements must run on the application's main thread.
+
+[!INCLUDE [docs under construction](~/includes/preview-note.md)]
+
+The `MainThread` class is available in the `Microsoft.Maui.ApplicationModel` namespace.
+
+## When is it required
+
+Applications sometimes need to use events that call the event handler on a secondary thread, such as the [`Accelerometer`](../device/sensors.md#accelerometer) or [`Compass`](../device/sensors.md#compass) sensors. All sensors might return information on a secondary thread when used with faster sensing speeds. If the event handler needs to access user-interface elements, it must invoke code on the main thread.
+
+## Run code on the UI thread
+
+To run code on the main thread, call the static `MainThread.BeginInvokeOnMainThread` method. The argument is an [`Action`](xref:System.Action) object, which is simply a method with no arguments and no return value:
+
+:::code language="csharp" source="../snippets/shared_1/AppModelPage.xaml.cs" id="runcode_lambda":::
+
+It is also possible to define a separate method for the code, and then call that code with the `BeginInvokeOnMainThread` method:
+
+:::code language="csharp" source="../snippets/shared_1/AppModelPage.xaml.cs" id="runcode_func_pointer":::
+
+## Determine if invocation is required
+
+With the `MainThread` class, you can determine if the current code is running on the main thread. The `MainThread.IsMainThread` property returns `true` if the code calling the property is running on the main thread, and `false` if it isn't. It's logical to assume that you need to determine if the code is running on the main thread before calling `BeginInvokeOnMainThread`. For example, the following code uses the `MainThread.IsMainThread` to detect if the `MyMainThreadCode` method should be called directly if the code is running on the main thread. If it isn't running on the main thread, the method is passed to `MainThread.BeginInvokeOnMainThread`:
+
+:::code language="csharp" source="../snippets/shared_1/AppModelPage.xaml.cs" id="runcode_test_thread":::
+
+This check isn't necessary. `BeginInvokeOnMainThread` itself tests if the current code is running on the main thread or not. If the code is running on the main thread, `BeginInvokeOnMainThread` just calls the provided method directly. If the code is running on a secondary thread, `BeginInvokeOnMainThread` invokes the provided method on the main thread. Therefore, if the code you run is the same, regardless of the main or secondary thread, simply call `BeginInvokeOnMainThread` without checking if it's required. There is negligible overhead in doing so.
+
+The only reason you would need to check the `MainThread.IsMainThread` property is if you have branching logic that does something different based on the thread.
+
+## Additional methods
+
+The `MainThread` class includes the following additional `static` methods that can be used to interact with user interface elements from backgrounds threads:
+
+| Method | Arguments | Returns | Purpose |
+|------------------------------|-----------------|-----------|-----------------------------------------------------------------------------|
+| `InvokeOnMainThreadAsync` | `Func` | `Task` | Invokes a `Func` on the main thread, and waits for it to complete. |
+| `InvokeOnMainThreadAsync` | `Action` | `Task` | Invokes an `Action` on the main thread, and waits for it to complete. |
+| `InvokeOnMainThreadAsync` | `Func>` | `Task` | Invokes a `Func>` on the main thread, and waits for it to complete. |
+| `InvokeOnMainThreadAsync` | `Func` | `Task` | Invokes a `Func` on the main thread, and waits for it to complete. |
+| `GetMainThreadSynchronizationContextAsync` | | `Task` | Returns the `SynchronizationContext` for the main thread. |
diff --git a/docs/platform-integration/appmodel/maps.md b/docs/platform-integration/appmodel/maps.md
new file mode 100644
index 000000000..8fe561b43
--- /dev/null
+++ b/docs/platform-integration/appmodel/maps.md
@@ -0,0 +1,66 @@
+---
+title: "Map"
+description: "Learn how to use the .NET MAUI Map class in the Microsoft.Maui.ApplicationModel namespace. This class enables an application to open the installed map application to a specific location or placemark."
+ms.date: 05/23/2022
+no-loc: ["Microsoft.Maui", "Microsoft.Maui.ApplicationModel"]
+---
+
+# Map
+
+This article describes how you can use the .NET Multi-platform App UI (.NET MAUI) `IMap` interface. This interface enables an application to open the installed map application to a specific location or placemark. A default implementation of the `IMap` interface is exposed through the `Map.Default` property.
+
+[!INCLUDE [docs under construction](~/includes/preview-note.md)]
+
+The `IMap` and `Map` types are available in the `Microsoft.Maui.ApplicationModel` namespace.
+
+## Using the map
+
+The map functionality works by calling the `Map.OpenAsync` method, and passing either an instance of the `Location` or `Placemark` type. The following example opens the installed map app at a specific GPS location:
+
+:::code language="csharp" source="../snippets/shared_1/AppModelPage.xaml.cs" id="navigate_building":::
+
+> [!TIP]
+> The `Location` and `Placemark` types are in the `Microsoft.Maui.Devices.Sensors` namespace.
+
+When you use a `Placemark` to open the map, more information is required. The information helps the map app search for the place you're looking for. The following information is required:
+
+- `CountryName`
+- `AdminArea`
+- `Thoroughfare`
+- `Locality`
+
+:::code language="csharp" source="../snippets/shared_1/AppModelPage.xaml.cs" id="navigate_building_placemark":::
+
+## Extension methods
+
+As long as the `Microsoft.Maui.Devices.Sensors` namespace is imported, which a new .NET MAUI project automatically does, you can use the built-in extension method `OpenMapAsync` to open the map:
+
+:::code language="csharp" source="../snippets/shared_1/AppModelPage.xaml.cs" id="navigate_building_placemark_extension":::
+
+## Add navigation
+
+When you open the map, you can calculate a route from the device's current location to the specified location. Pass the `MapLaunchOptions` type to the `Map.OpenAsync` method, specifying the navigation mode. The following example opens the map app and specifies a driving navigation mode:
+
+:::code language="csharp" source="../snippets/shared_1/AppModelPage.xaml.cs" id="navigate_building_driving":::
+
+## Platform differences
+
+This section describes the platform-specific differences with the maps API.
+
+
+# [Android](#tab/android)
+
+`NavigationMode` supports Bicycling, Driving, and Walking.
+
+Android uses the `geo:` Uri scheme to launch the maps application on the device. This may prompt the user to select from an existing app that supports this Uri scheme. Google Maps supports this scheme.
+
+# [iOS](#tab/ios)
+
+`NavigationMode` supports Driving, Transit, and Walking.
+
+# [Windows](#tab/windows)
+
+`NavigationMode` supports Driving, Transit, and Walking.
+
+-----
+
diff --git a/docs/platform-integration/appmodel/media/app-actions/appactions.png b/docs/platform-integration/appmodel/media/app-actions/appactions.png
new file mode 100644
index 000000000..3dabca581
Binary files /dev/null and b/docs/platform-integration/appmodel/media/app-actions/appactions.png differ
diff --git a/docs/platform-integration/appmodel/open-browser.md b/docs/platform-integration/appmodel/open-browser.md
new file mode 100644
index 000000000..9a898dde0
--- /dev/null
+++ b/docs/platform-integration/appmodel/open-browser.md
@@ -0,0 +1,101 @@
+---
+title: "Open the browser"
+description: "The Browser class in the Microsoft.Maui.ApplicationModel namespace enables an application to open a web link in the optimized system preferred browser or the external browser."
+ms.date: 05/23/2022
+no-loc: ["Microsoft.Maui", "Microsoft.Maui.ApplicationModel"]
+---
+
+# Browser
+
+This article describes how you can use the .NET Multi-platform App UI (.NET MAUI) `IBrowser` interface. This interface enables an application to open a web link in the system-preferred browser or the external browser. A default implementation of the `IBrowser` interface is exposed through the `Browser.Default` property.
+
+[!INCLUDE [docs under construction](~/includes/preview-note.md)]
+
+## Get started
+
+To access the browser functionality, the following platform-specific setup is required.
+
+
+# [Android](#tab/android)
+
+If your project's Target Android version is set to **Android 11 (R API 30)** or higher, you must update your _Android Manifest_ with queries that use Android's [package visibility requirements](https://developer.android.com/preview/privacy/package-visibility).
+
+In the _Platforms/Android/AndroidManifest.xml_ file, add the following `queries/intent` nodes the `manifest` node:
+
+```xml
+
+
+
+
+
+
+
+
+
+
+```
+
+# [iOS](#tab/ios)
+
+No setup is required.
+
+# [Windows](#tab/windows)
+
+No setup is required.
+
+-----
+
+
+## Open the browser
+
+The browser is opened by calling the `Browser.OpenAsync` method with the `Uri` and the type of `BrowserLaunchMode`. The following code example demonstrates opening the browser:
+
+:::code language="csharp" source="../snippets/shared_1/AppModelPage.xaml.cs" id="browser_open":::
+
+This method returns after the browser is launched, not after it was closed by the user. `Browser.OpenAsync` returns a `bool` value to indicate if the browser was successfully launched.
+
+## Customization
+
+When using the system preferred browser, there are several customization options available for iOS and Android. This includes a `TitleMode` (Android only), and preferred color options for the `Toolbar` (iOS and Android) and `Controls` (iOS only) that appear.
+
+These options are specified using `BrowserLaunchOptions` when calling `OpenAsync`.
+
+:::code language="csharp" source="../snippets/shared_1/AppModelPage.xaml.cs" id="browser_open_custom":::
+
+## Platform differences
+
+This section describes the platform-specific differences with the browser API.
+
+
+
+### [Android](#tab/android)
+
+The `BrowserLaunchOptions.LaunchMode` determines how the browser is launched:
+
+- `SystemPreferred`
+
+ [Custom Tabs](https://developer.chrome.com/multidevice/android/customtabs) will try to be used to load the Uri and keep navigation awareness.
+
+- `External`
+
+ An `Intent` is used to request the Uri be opened through the system's normal browser.
+
+# [iOS](#tab/ios)
+
+The `BrowserLaunchOptions.LaunchMode` determines how the browser is launched:
+
+- `SystemPreferred`
+
+ [SFSafariViewController](xref:SafariServices.SFSafariViewController) is used to load the Uri and keep navigation awareness.
+
+- `External`
+
+ The standard `OpenUrl` on the main application is used to launch the default browser outside of the application.
+
+# [Windows](#tab/windows)
+
+The user's default browser will always be launched regardless of the `BrowserLaunchMode`.
+
+-----
+
+
diff --git a/docs/platform-integration/appmodel/permissions.md b/docs/platform-integration/appmodel/permissions.md
new file mode 100644
index 000000000..b82247687
--- /dev/null
+++ b/docs/platform-integration/appmodel/permissions.md
@@ -0,0 +1,181 @@
+---
+title: "Permissions"
+description: "Learn how to use the .NET MAUI Permissions class, to check and request permissions. This class is in the Microsoft.Maui.ApplicationModel namespace."
+ms.date: 05/23/2022
+no-loc: ["Microsoft.Maui", "Microsoft.Maui.ApplicationModel"]
+---
+
+# Permissions
+
+This article describes how you can use the .NET Multi-platform App UI (.NET MAUI) `Permissions` class. This class allows you to check and request permissions at run-time. The `Permissions` type is available in the `Microsoft.Maui.ApplicationModel` namespace.
+
+[!INCLUDE [docs under construction](~/includes/preview-note.md)]
+
+## Available permissions
+
+.NET MAUI attempts to abstract as many permissions as possible. However, each operating system has a different set of permissions. Even though the API allows access to a common permission, there may be differences between operating systems related to that permission. The following table describes the available permissions:
+
+The following table uses ✔️ to indicate that the permission is supported and ❌ to indicate the permission isn't supported or isn't required:
+
+| Permission | Android | iOS | Windows | tvOS |
+|-------------------|:-------:|:---:|:-------:|:----:|
+| CalendarRead | ✔️ | ✔️ | ❌ | ❌ |
+| CalendarWrite | ✔️ | ✔️ | ❌ | ❌ |
+| Camera | ✔️ | ✔️ | ❌ | ❌ |
+| ContactsRead | ✔️ | ✔️ | ✔️ | ❌ |
+| ContactsWrite | ✔️ | ✔️ | ✔️ | ❌ |
+| Flashlight | ✔️ | ❌ | ❌ | ❌ |
+| LocationWhenInUse | ✔️ | ✔️ | ✔️ | ✔️ |
+| LocationAlways | ✔️ | ✔️ | ✔️ | ❌ |
+| Media | ❌ | ✔️ | ❌ | ❌ |
+| Microphone | ✔️ | ✔️ | ✔️ | ❌ |
+| Phone | ✔️ | ✔️ | ❌ | ❌ |
+| Photos | ❌ | ✔️ | ❌ | ✔️ |
+| Reminders | ❌ | ✔️ | ❌ | ❌ |
+| Sensors | ✔️ | ✔️ | ✔️ | ❌ |
+| Sms | ✔️ | ✔️ | ❌ | ❌ |
+| Speech | ✔️ | ✔️ | ❌ | ❌ |
+| StorageRead | ✔️ | ❌ | ❌ | ❌ |
+| StorageWrite | ✔️ | ❌ | ❌ | ❌ |
+
+If a permission is marked as ❌, it will always return `Granted` when checked or requested.
+
+## Checking permissions
+
+To check the current status of a permission, use the `Permissions.CheckStatusAsync` method along with the specific permission to get the status for. The following example checks the status of the `LocationWhenInUse` permission:
+
+:::code language="csharp" source="../snippets/shared_1/AppModelPage.xaml.cs" id="permission_check":::
+
+A `PermissionException` is thrown if the required permission isn't declared.
+
+It's best to check the status of the permission before requesting it. Each operating system returns a different default state, if the user has never been prompted. iOS returns `Unknown`, while others return `Denied`. If the status is `Granted` then there's no need to make other calls. On iOS if the status is `Denied` you should prompt the user to change the permission in the settings. On Android, you can call `ShouldShowRationale` to detect if the user has already denied the permission in the past.
+
+### Permission status
+
+When using `CheckStatusAsync` or `RequestAsync`, a `PermissionStatus` is returned that can be used to determine the next steps:
+
+- `Unknown`\
+The permission is in an unknown state, or on iOS, the user has never been prompted.
+
+- `Denied`\
+The user denied the permission request.
+
+- `Disabled`\
+The feature is disabled on the device.
+
+- `Granted`\
+The user granted permission or is automatically granted.
+
+- `Restricted`\
+In a restricted state.
+
+## Requesting permissions
+
+To request a permission from the users, use the `Permissions.RequestAsync` method along with the specific permission to request. If the user previously granted permission, and hasn't revoked it, then this method will return `Granted` without showing a dialog to the user. The following example requests the `LocationWhenInUse` permission:
+
+:::code language="csharp" source="../snippets/shared_1/AppModelPage.xaml.cs" id="permission_request":::
+
+A `PermissionException` is thrown if the required permission isn't declared.
+
+> [!IMPORTANT]
+> On some platforms, a permission request can only be activated a single time. Further prompts must be handled by the developer to check if a permission is in the `Denied` state, and then ask the user to manually turn it on.
+
+## Explain why permission is needed
+
+It's best practice to explain to your user why your application needs a specific permission. On iOS, you must specify a string that is displayed to the user. Android doesn't have this ability, and also defaults permission status to `Disabled`. This limits the ability to know if the user denied the permission or if it's the first time the permission is being requested. The `ShouldShowRationale` method can be used to determine if an informative UI should be displayed. If the method returns `true`, this is because the user has denied or disabled the permission in the past. Other platforms always return `false` when calling this method.
+
+## Example
+
+The following code presents the general usage pattern for determining whether a permission has been granted, and then requesting it if it hasn't.
+
+:::code language="csharp" source="../snippets/shared_1/AppModelPage.xaml.cs" id="permission_check_and_request":::
+
+## Extending permissions
+
+The Permissions API was created to be flexible and extensible for applications that require more validation or permissions that aren't included in .NET MAUI. Create a class that inherits from `Permissions.BasePermission`, and implement the required abstract methods. The following example code demonstrates the basic abstract members, but without implementation:
+
+:::code language="csharp" source="../snippets/shared_1/AppModelPage.xaml.cs" id="permission_class":::
+
+When implementing a permission in a specific platform, the `BasePlatformPermission` class can be inherited from. This class provides extra platform helper methods to automatically check the permission declarations. This helps when creating custom permissions that do groupings, for example requesting both **Read** and **Write** access to storage on Android. The following code example demonstrates requesting **Read** and **Write** storage access:
+
+:::code language="csharp" source="../snippets/shared_1/AppModelPage.xaml.cs" id="permission_readwrite":::
+
+You then check the permission in the same way as any other permission type provided by .NET MAUI:
+
+:::code language="csharp" source="../snippets/shared_1/AppModelPage.xaml.cs" id="permission_readwrite_request":::
+
+
+
+## Platform differences
+
+This section describes the platform-specific differences with the permissions API.
+
+
+# [Android](#tab/android)
+
+Permissions must have the matching attributes set in the Android Manifest file. Permission status defaults to `Denied`.
+
+
+
+# [iOS](#tab/ios)
+
+Permissions must have a matching string in the _Info.plist_ file. Once a permission is requested and denied, a pop-up will no longer appear if you request the permission a second time. You must prompt your user to manually adjust the setting in the applications settings screen in iOS. Permission status defaults to `Unknown`.
+
+
+
+# [Windows](#tab/windows)
+
+Permissions must have matching capabilities declared in the package manifest. Permission status defaults to `Unknown` in most instances.
+
+
+
+-----
+
diff --git a/docs/platform-integration/appmodel/version-tracking.md b/docs/platform-integration/appmodel/version-tracking.md
new file mode 100644
index 000000000..4e23a3553
--- /dev/null
+++ b/docs/platform-integration/appmodel/version-tracking.md
@@ -0,0 +1,32 @@
+---
+title: "Version tracking"
+description: "Learn how to use the .NET MAUI VersionTracking class, which lets you check the applications version and build numbers along with seeing additional information."
+ms.date: 05/23/2022
+no-loc: ["Microsoft.Maui", "Microsoft.Maui.ApplicationModel"]
+---
+
+# Version tracking
+
+This article describes how you can use the .NET Multi-platform App UI (.NET MAUI) `IVersionTracking` interface. This class lets you check the applications version and build numbers along with seeing additional information such as if it's the first time the application launched. The `IVersionTracking` interface is exposed through the `VersionTracking.Default` property.
+
+[!INCLUDE [docs under construction](~/includes/preview-note.md)]
+
+The `VersionTracking` and `IVersionTracking` types are available in the `Microsoft.Maui.ApplicationModel` namespace.
+
+## Get started
+
+To enable version tracking in your app, add the `ConfigureEssentials` step to the `CreateMauiApp` bootstrap code. The app startup code is configured in the _MauiProgram.cs_ file. Call the `UseVersionTracking` method to enable version tracking.
+
+:::code language="csharp" source="../snippets/shared_1/MauiProgram.cs" id="bootstrap_versiontracking" highlight="12-15":::
+
+## Check the version
+
+The `IVersionTracking` interface provides many properties that describe the current version of the app and how it relates to the previous version. The following example writes the tracking information to labels on the page:
+
+:::code language="csharp" source="../snippets/shared_1/AppModelPage.xaml.cs" id="version_read":::
+
+The first time the app is run after version tracking is enabled, the `IsFirstLaunchEver` property will return `true`. If you add version tracking in a newer version of an already released app, `IsFirstLaunchEver` may incorrectly report `true`. This property always returns `true` the first time version tracking is enabled and the user runs the app. You can't fully rely on this property if users have upgraded from older versions that weren't tracking the version.
+
+## Platform differences
+
+All version information is stored using the [Preferences](../storage/preferences.md) API, and is stored with a filename of _[YOUR-APP-PACKAGE-ID].microsoft.maui.essentials.versiontracking_ and follows the same data persistence outlined in the [Preferences](../storage/preferences.md#persistence) documentation.
diff --git a/docs/platform-integration/communication/authentication.md b/docs/platform-integration/communication/authentication.md
new file mode 100644
index 000000000..4aee7ea18
--- /dev/null
+++ b/docs/platform-integration/communication/authentication.md
@@ -0,0 +1,308 @@
+---
+title: "Web Authenticator"
+description: "Learn how to use the .NET MAUI WebAuthenticator class, which lets you start browser-based authentication flows, which listen for a callback to the app."
+ms.date: 05/23/2022
+no-loc: ["Microsoft.Maui", "Microsoft.Maui.Authentication"]
+---
+
+# Web authenticator
+
+This article describes how you can use the .NET Multi-platform App UI (.NET MAUI) the `IWebAuthenticator` interface. This interface lets you start browser-based authentication flows, which listen for a callback to a specific URL registered to the app. The `IWebAuthenticator` interface is exposed through the `WebAuthenticator.Default` property.
+
+[!INCLUDE [docs under construction](~/includes/preview-note.md)]
+
+The `WebAuthenticator` and `IWebAuthenticator` types are available in the `Microsoft.Maui.Authentication` namespace.
+
+## Overview
+
+Many apps require adding user authentication, and this often means enabling your users to sign in to their existing Microsoft, Facebook, Google, or Apple Sign In account.
+
+> [!TIP]
+> [Microsoft Authentication Library (MSAL)](/azure/active-directory/develop/msal-overview) provides an excellent turn-key solution to adding authentication to your app.
+
+If you're interested in using your own web service for authentication, it's possible to use `WebAuthenticator` to implement the client-side functionality.
+
+## Why use a server back end
+
+Many authentication providers have moved to only offering explicit or two-legged authentication flows to ensure better security. This means you'll need a **client secret** from the provider to complete the authentication flow. Unfortunately, mobile apps aren't a great place to store secrets and anything stored in a mobile app's code, binaries, or otherwise, is considered to be insecure.
+
+The best practice here's to use a web backend as a middle layer between your mobile app and the authentication provider.
+
+> [!IMPORTANT]
+> We strongly recommend against using older mobile-only authentication libraries and patterns which do not leverage a web backend in the authentication flow, due to their inherent lack of security for storing client secrets.
+
+## Get started
+
+To access the `WebAuthenticator` functionality the following platform-specific setup is required.
+
+
+# [Android](#tab/android)
+
+Android requires an **Intent Filter** setup to handle your callback URI. This is accomplished by inheriting from the `WebAuthenticatorCallbackActivity` class:
+
+```csharp
+using Android.App;
+using Android.Content.PM;
+
+namespace YourRootNamespace
+{
+ [Activity(NoHistory = true, LaunchMode = LaunchMode.SingleTop, Exported = true)]
+ [IntentFilter(new[] { Android.Content.Intent.ActionView },
+ Categories = new[] { Android.Content.Intent.CategoryDefault, Android.Content.Intent.CategoryBrowsable },
+ DataScheme = CALLBACK_SCHEME)]
+ public class WebAuthenticationCallbackActivity : Microsoft.Maui.WebAuthenticatorCallbackActivity
+ {
+ const string CALLBACK_SCHEME = "myapp";
+
+ }
+}
+```
+
+If your project's Target Android version is set to **Android 11 (R API 30)** or higher, you must update your _Android Manifest_ with queries that use Android's [package visibility requirements](https://developer.android.com/preview/privacy/package-visibility).
+
+In the _Platforms/Android/AndroidManifest.xml_ file, add the following `queries/intent` nodes the `manifest` node:
+
+```xml
+
+
+
+
+
+```
+
+# [iOS](#tab/ios)
+
+Add your app's callback URI pattern to the _Platforms/iOS/Info.plist_ file:
+
+```xml
+CFBundleURLTypes
+
+
+ CFBundleURLName
+ My App
+ CFBundleURLSchemes
+
+ myapp
+
+ CFBundleTypeRole
+ Editor
+
+
+```
+
+# [Windows](#tab/windows)
+
+> [!CAUTION]
+> At the moment, `WebAuthenticator` isn't working on Windows. For more information, see [GitHub issue #2702](https://github.com/dotnet/maui/issues/2702).
+
+For WinUI 3, you'll need to declare your callback URI protocol in your _Package.appxmanifest_ file:
+
+```xml
+
+
+
+
+
+ My App
+
+
+
+
+
+```
+
+-----
+
+## Using WebAuthenticator
+
+The API consists mainly of a single method, `AuthenticateAsync`, which takes two parameters:
+
+01. The URL used to start the web browser flow.
+01. The URI the flow is expected to ultimately call back to, that is registered to your app.
+
+The result is a `WebAuthenticatorResult`, which includes any query parameters parsed from the callback URI:
+
+```csharp
+try
+{
+ WebAuthenticatorResult authResult = await WebAuthenticator.Default.AuthenticateAsync(
+ new Uri("https://mysite.com/mobileauth/Microsoft"),
+ new Uri("myapp://"));
+
+ string accessToken = authResult?.AccessToken;
+
+ // Do something with the token
+}
+catch (TaskCanceledException e)
+{
+ // Use stopped auth
+}
+```
+
+The `WebAuthenticator` API takes care of launching the url in the browser and waiting until the callback is received:
+
+:::image type="content" source="media/authentication/web-authenticator.png" alt-text="Typical web authentication flow.":::
+
+If the user cancels the flow at any point, a `TaskCanceledException` is thrown.
+
+### Private authentication session
+
+iOS 13 introduced an ephemeral web browser API for developers to launch the authentication session as private. This enables developers to request that no shared cookies or browsing data is available between authentication sessions and will be a fresh login session each time. This is available through the `WebAuthenticatorOptions` parameter passed to the `AuthenticateAsync` method:
+
+```csharp
+try
+{
+ WebAuthenticatorResult authResult = await WebAuthenticator.Default.AuthenticateAsync(
+ new WebAuthenticatorOptions()
+ {
+ Url = new Uri("https://mysite.com/mobileauth/Microsoft"),
+ CallbackUrl = new Uri("myapp://"),
+ PrefersEphemeralWebBrowserSession = true
+ });
+
+ string accessToken = authResult?.AccessToken;
+
+ // Do something with the token
+}
+catch (TaskCanceledException e)
+{
+ // Use stopped auth
+}
+```
+
+## Platform differences
+
+This section describes the platform-specific differences with the web authentication API.
+
+
+
+### [Android](#tab/android)
+
+**Custom Tabs** are used whenever available, otherwise an **Intent** is started for the URL.
+
+# [iOS](#tab/ios)
+
+Depending on the iOS version, the behavior is slightly different:
+
+- iOS 12 or higher\
+ `ASWebAuthenticationSession` is used.
+
+- iOS 11\
+ `SFAuthenticationSession` is used.
+
+- Older iOS versions\
+ `SFSafariViewController` is used if available, otherwise **Safari** is used.
+
+# [Windows](#tab/windows)
+
+> [!CAUTION]
+> At the moment, `WebAuthenticator` isn't working on Windows. For more information, see [GitHub issue #2702](https://github.com/dotnet/maui/issues/2702).
+
+On WinUI 3, the `WebAuthenticationBroker` is used, if supported, otherwise the system browser is used.
+
+-----
+
+
+
+## Apple Sign In
+
+According to [Apple's review guidelines](https://developer.apple.com/app-store/review/guidelines/#sign-in-with-apple), if your app uses any social login service to authenticate, it must also offer Apple Sign In as an option. To add Apple Sign In to your apps, first you'll need to configure your app to use Apple Sign In.
+
+For iOS 13 and higher, call the `AppleSignInAuthenticator.AuthenticateAsync()` method. This will use automatically the native Apple Sign in APIs so your users get the best experience possible on these devices. For example, you can write your shared code to use the correct API at runtime:
+
+```csharp
+var scheme = "..."; // Apple, Microsoft, Google, Facebook, etc.
+var authUrlRoot = "https://mysite.com/mobileauth/";
+WebAuthenticatorResult result = null;
+
+if (scheme.Equals("Apple")
+ && DeviceInfo.Platform == DevicePlatform.iOS
+ && DeviceInfo.Version.Major >= 13)
+{
+ // Use Native Apple Sign In API's
+ result = await AppleSignInAuthenticator.AuthenticateAsync();
+}
+else
+{
+ // Web Authentication flow
+ var authUrl = new Uri($"{authUrlRoot}{scheme}");
+ var callbackUrl = new Uri("myapp://");
+
+ result = await WebAuthenticator.Default.AuthenticateAsync(authUrl, callbackUrl);
+}
+
+var authToken = string.Empty;
+
+if (result.Properties.TryGetValue("name", out string name) && !string.IsNullOrEmpty(name))
+ authToken += $"Name: {name}{Environment.NewLine}";
+
+if (result.Properties.TryGetValue("email", out string email) && !string.IsNullOrEmpty(email))
+ authToken += $"Email: {email}{Environment.NewLine}";
+
+// Note that Apple Sign In has an IdToken and not an AccessToken
+authToken += result?.AccessToken ?? result?.IdToken;
+```
+
+> [!TIP]
+> For non-iOS 13 devices, this will start the web authentication flow, which can also be used to enable Apple Sign In on your Android and Windows devices.
+> You can sign into your iCloud account on your iOS simulator to test Apple Sign In.
+
+## ASP.NET core server back end
+
+It's possible to use the `WebAuthenticator` API with any web back-end service. To use it with an ASP.NET core app, configure the web app with the following steps:
+
+01. Set up your [external social authentication providers](/aspnet/core/security/authentication/social/?tabs=visual-studio) in an ASP.NET Core web app.
+01. Set the **Default Authentication Scheme** to `CookieAuthenticationDefaults.AuthenticationScheme` in your `.AddAuthentication()` call.
+01. Use `.AddCookie()` in your _Startup.cs_ `.AddAuthentication()` call.
+01. All providers must be configured with `.SaveTokens = true;`.
+
+```csharp
+services.AddAuthentication(o =>
+ {
+ o.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
+ })
+ .AddCookie()
+ .AddFacebook(fb =>
+ {
+ fb.AppId = Configuration["FacebookAppId"];
+ fb.AppSecret = Configuration["FacebookAppSecret"];
+ fb.SaveTokens = true;
+ });
+```
+
+> [!TIP]
+> If you'd like to include Apple Sign In, you can use the `AspNet.Security.OAuth.Apple` NuGet package. You can view the full [Startup.cs sample](https://github.com/dotnet/maui/tree/main/src/Essentials/samples/Sample.Server.WebAuthenticator/Startup.cs#L33-L64).
+
+### Add a custom mobile auth controller
+
+With a mobile authentication flow, you usually start the flow directly to a provider the user has chosen. For example, clicking a "Microsoft" button on the sign-in screen of the app. It's also important to return relevant information to your app at a specific callback URI to end the authentication flow.
+
+To achieve this, use a custom API Controller:
+
+```csharp
+[Route("mobileauth")]
+[ApiController]
+public class AuthController : ControllerBase
+{
+ const string callbackScheme = "myapp";
+
+ [HttpGet("{scheme}")] // eg: Microsoft, Facebook, Apple, etc
+ public async Task Get([FromRoute]string scheme)
+ {
+ // 1. Initiate authentication flow with the scheme (provider)
+ // 2. When the provider calls back to this URL
+ // a. Parse out the result
+ // b. Build the app callback URL
+ // c. Redirect back to the app
+ }
+}
+```
+
+The purpose of this controller is to infer the scheme (provider) the app is requesting, and start the authentication flow with the social provider. When the provider calls back to the web backend, the controller parses out the result and redirects to the app's callback URI with parameters.
+
+Sometimes you may want to return data such as the provider's `access_token` back to the app, which you can do via the callback URI's query parameters. Or, you may want to instead create your own identity on your server and pass back your own token to the app. What and how you do this part is up to you!
+
+Check out the [full controller sample](https://github.com/dotnet/maui/tree/main/src/Essentials/samples/Sample.Server.WebAuthenticator/Controllers/MobileAuthController.cs).
+
+> [!NOTE]
+> The above sample demonstrates how to return the access token from the 3rd party authentication (ie: OAuth) provider. To obtain a token you can use to authorize web requests to the web backend itself, you should create your own token in your web app, and return that instead. The [Overview of ASP.NET Core authentication](/aspnet/core/security/authentication) has more information about advanced authentication scenarios in ASP.NET Core.
diff --git a/docs/platform-integration/communication/contacts.md b/docs/platform-integration/communication/contacts.md
new file mode 100644
index 000000000..ce81930f0
--- /dev/null
+++ b/docs/platform-integration/communication/contacts.md
@@ -0,0 +1,105 @@
+---
+title: "Contacts"
+description: "Learn how to use the .NET MAUI Contacts class in the Microsoft.Maui.ApplicationModel.Communication namespace, which lets a pick a contact and retrieve information about it."
+ms.date: 05/23/2022
+no-loc: ["Microsoft.Maui", "Microsoft.Maui.ApplicationModel.Communication"]
+---
+
+# Contacts
+
+This article describes how you can use the .NET Multi-platform App UI (.NET MAUI) `IContacts` interface to select a contact and read information about it.
+The `IContacts` interface is exposed through the `Contacts.Default` property.
+
+[!INCLUDE [docs under construction](~/includes/preview-note.md)]
+
+The `Contacts` and `IContacts` types are available in the `Microsoft.Maui.ApplicationModel.Communication` namespace.
+
+## Get started
+
+To access the **Contacts** functionality the following platform-specific setup is required.
+
+
+# [Android](#tab/android)
+
+The `ReadContacts` permission is required and must be configured in the Android project. This can be added in the following ways:
+
+- Add the assembly-based permission:
+
+ Open the _AssemblyInfo.cs_ file under the **Properties** folder and add:
+
+ ```csharp
+ [assembly: UsesPermission(Android.Manifest.Permission.ReadContacts)]
+ ```
+
+ \- or -
+
+- Update the Android Manifest:
+
+ Open the _AndroidManifest.xml_ file under the **Properties** folder and add the following in the `manifest` node:
+
+ ```xml
+
+ ```
+
+
+
+# [iOS](#tab/ios)
+
+In the **Solution Explorer** pane, right-click on the _Platforms/iOS/Info.plist_ file. Select **Open With** and then select the **XML (Text) Editor** item. Press the **OK** button. In the file, add the following key and value:
+
+```xml
+NSContactsUsageDescription
+This app needs access to contacts to pick a contact and get info.
+```
+
+The `` element is the description specific to your app and is shown to the user.
+
+# [Windows](#tab/windows)
+
+In the **Solution Explorer** pane, right-click on the _Platforms/Windows/Package.appxmanifest_ file, and select **View Code**. Under the `` node, add ``.
+
+-----
+
+
+## Pick a contact
+
+You can request the user to pick a contact by calling the `PickContactAsync` method. A contact dialog will appear on the device allowing the user to select a contact. If the user doesn't select a contact, `null` is returned.
+
+:::code language="csharp" source="../snippets/shared_1/CommsPage.xaml.cs" id="contact_select":::
+
+## Get all contacts
+
+The `GetAllAsync` method returns a collection of contacts.
+
+:::code language="csharp" source="../snippets/shared_1/CommsPage.xaml.cs" id="contact_all":::
+
+## Platform differences
+
+This section describes the platform-specific differences with the contacts API.
+
+
+
+# [Android](#tab/android)
+
+- The `cancellationToken` parameter in the `GetAllAsync` method isn't supported.
+
+# [iOS](#tab/ios)
+
+- The `cancellationToken` parameter in the `GetAllAsync` method isn't supported.
+- The iOS platform doesn't support the `DisplayName` property natively, thus, the `DisplayName` value is constructed as "GivenName FamilyName".
+
+# [Windows](#tab/windows)
+
+No platform differences.
+
+-----
+
+
diff --git a/docs/platform-integration/communication/email.md b/docs/platform-integration/communication/email.md
new file mode 100644
index 000000000..4dda1ca8a
--- /dev/null
+++ b/docs/platform-integration/communication/email.md
@@ -0,0 +1,91 @@
+---
+title: "Email"
+description: "Learn how to use the .NET MAUI Email class in the Microsoft.Maui.ApplicationModel.Communication namespace to open the default email application. The subject, body, and recipients of an email can be set."
+ms.date: 05/23/2022
+no-loc: ["Microsoft.Maui", "Microsoft.Maui.ApplicationModel.Communication"]
+---
+
+# Email
+
+This article describes how you can use the .NET Multi-platform App UI (.NET MAUI) `IEmail` interface to open the default email app. When the email app is loaded, it can be set to create a new email with the specified recipients, subject, and body. The `IEmail` interface is exposed through the `Email.Default` property.
+
+[!INCLUDE [docs under construction](~/includes/preview-note.md)]
+
+The `Email` and `IEmail` types are available in the `Microsoft.Maui.ApplicationModel.Communication` namespace.
+
+## Get started
+
+To access the email functionality, the following platform specific setup is required.
+
+
+# [Android](#tab/android)
+
+If your project's Target Android version is set to **Android 11 (R API 30)** or higher, you must update your _Android Manifest_ with queries that use Android's [package visibility requirements](https://developer.android.com/preview/privacy/package-visibility).
+
+In the _Platforms/Android/AndroidManifest.xml_ file, add the following `queries/intent` nodes the `manifest` node:
+
+```xml
+
+
+
+
+
+
+```
+
+# [iOS](#tab/ios)
+
+Apple requires that you define the schemes you want to use. Add the `LSApplicationQueriesSchemes` key and schemes to the _Platforms/iOS/Info.plist_ file:
+
+```xml
+LSApplicationQueriesSchemes
+
+ mailto
+
+```
+
+# [Windows](#tab/windows)
+
+No additional setup required.
+
+-----
+
+
+## Using Email
+
+The Email functionality works by providing the email information as an argument to the `ComposeAsync` method. In this example, the `EmailMessage` type is used to represent the email information:
+
+:::code language="csharp" source="../snippets/shared_1/CommsPage.xaml.cs" id="email_compose":::
+
+## File attachments
+
+When creating the email provided to the email client, you can add file attachments. The file type (MIME) is automatically detected, so you don't need to specify it. Some mail clients may restrict the types of files you send, or possibly prevent attachments altogether.
+
+Use the `EmailMessage.Attachments` collection to manage the files attached to an email.
+
+The following example demonstrates adding arbitrary text to a file, and then adding it to the email.
+
+:::code language="csharp" source="../snippets/shared_1/CommsPage.xaml.cs" id="email_picture" highlight="18":::
+
+
+
+# [Android](#tab/android)
+
+Not all email clients for Android support `EmailBodyFormat.Html`, since there is no way to detect this, we recommend using `EmailBodyFormat.PlainText` when sending emails.
+
+# [iOS](#tab/ios)
+
+Both `EmailBodyFormat.Html` and `EmailBodyFormat.PlainText` are supported.
+
+> [!WARNING]
+> To use the email API on iOS, you must run it on a physical device. Otherwise, an exception is thrown.
+
+# [Windows](#tab/windows)
+
+Only supports `EmailBodyFormat.PlainText` as the EmailBodyFormat.`BodyFormat`. Attempting to send an `Html` email throws the exception: `FeatureNotSupportedException`.
+
+Not all email clients support sending attachments.
+
+-----
+
+
diff --git a/docs/platform-integration/communication/media/authentication/web-authenticator.png b/docs/platform-integration/communication/media/authentication/web-authenticator.png
new file mode 100644
index 000000000..13875d1e8
Binary files /dev/null and b/docs/platform-integration/communication/media/authentication/web-authenticator.png differ
diff --git a/docs/platform-integration/communication/networking.md b/docs/platform-integration/communication/networking.md
new file mode 100644
index 000000000..feea9d23c
--- /dev/null
+++ b/docs/platform-integration/communication/networking.md
@@ -0,0 +1,84 @@
+---
+title: "Connectivity"
+description: "Learn how to use the .NET MAUI Connectivity interface in the Microsoft.Maui.Networking namespace. With this interface, you can determine if you can communicate with the internet and which network devices are connected"
+ms.date: 05/23/2022
+no-loc: ["Microsoft.Maui", "Microsoft.Maui.Networking", "Connectivity"]
+---
+
+# Connectivity
+
+This article describes how you can use the .NET Multi-platform App UI (.NET MAUI) `IConnectivity` interface to inspect the network accessibility of the device. The network connection may have access to the internet. Devices also contain different kinds of network connections, such as Bluetooth, cellular, or WiFi. The `IConnectivity` interface has an event to monitor changes in the devices connection state. The `IConnectivity` interface is exposed through the `Connectivity.Current` property.
+
+[!INCLUDE [docs under construction](~/includes/preview-note.md)]
+
+The `Connectivity` and `IConnectivity` types are available in the `Microsoft.Maui.Networking` namespace.
+
+## Get started
+
+To access the **Connectivity** functionality, the following platform-specific setup is required.
+
+
+# [Android](#tab/android)
+
+The `AccessNetworkState` permission is required and must be configured in the Android project. This can be added in the following ways:
+
+- Add the assembly-based permission:
+
+ Open the _Platforms/Android/MainApplication.cs_ file and add the following assembly attributes after `using` directives:
+
+ ```csharp
+ [assembly: UsesPermission(Android.Manifest.Permission.AccessNetworkState)]
+ ```
+
+ \- or -
+
+- Update the Android Manifest:
+
+ Open the _Platforms/Android/AndroidManifest.xml_ file and add the following in the `manifest` node:
+
+ ```xml
+
+ ```
+
+
+
+# [iOS](#tab/ios)
+
+No setup is required.
+
+# [Windows](#tab/windows)
+
+No setup is required.
+
+-----
+
+
+## Using Connectivity
+
+You can determine the scope of the current network by checking the `NetworkAccess` property.
+
+:::code language="csharp" source="../snippets/shared_1/NetworkingPage.cs" id="network_test":::
+
+Network access falls into the following categories:
+
+- `Internet` — Local and internet access.
+- `ConstrainedInternet` — Limited internet access. This value means that there's a captive portal, where local access to a web portal is provided. Once the portal is used to provide authentication credentials, internet access is granted.
+- `Local` — Local network access only.
+- `None` — No connectivity is available.
+- `Unknown` — Unable to determine internet connectivity.
+
+You can check what type of connection profile the device is actively using:
+
+:::code language="csharp" source="../snippets/shared_1/NetworkingPage.cs" id="network_profiles":::
+
+Whenever the connection profile or network access changes, the `ConnectivityChanged` event is raised:
+
+:::code language="csharp" source="../snippets/shared_1/NetworkingPage.cs" id="network_implementation":::
+
+## Limitations
+
+It's important to know that it's possible that `Internet` is reported by `NetworkAccess` but full access to the web isn't available. Because of how connectivity works on each platform, it can only guarantee that a connection is available. For instance, the device may be connected to a Wi-Fi network, but the router is disconnected from the internet. In this instance `Internet` may be reported, but an active connection isn't available.
diff --git a/docs/platform-integration/communication/phone-dialer.md b/docs/platform-integration/communication/phone-dialer.md
new file mode 100644
index 000000000..bc5250837
--- /dev/null
+++ b/docs/platform-integration/communication/phone-dialer.md
@@ -0,0 +1,51 @@
+---
+title: "Phone dialer"
+description: "Learn how to open the phone dialer to a specific number, in .NET MAUI. The PhoneDialer class in the Microsoft.Maui.ApplicationModel.Communication namespace is used to open the phone dialer."
+ms.date: 05/23/2022
+no-loc: ["Microsoft.Maui", "Microsoft.Maui.ApplicationModel.Communication"]
+---
+
+# Phone dialer
+
+This article describes how you can use the .NET Multi-platform App UI (.NET MAUI) `IPhoneDialer` interface. This interface enables an application to open a phone number in the dialer. The `IPhoneDialer` interface is exposed through the `PhoneDialer.Default` property.
+
+[!INCLUDE [docs under construction](~/includes/preview-note.md)]
+
+The `PhoneDialer` and `IPhoneDialer` types are available in the `Microsoft.Maui.ApplicationModel.Communication` namespace.
+
+## Get started
+
+To access the phone dialer functionality, the following platform-specific setup is required.
+
+
+# [Android](#tab/android)
+
+If your project's Target Android version is set to **Android 11 (R API 30)** or higher, you must update your _Android Manifest_ with queries that use Android's [package visibility requirements](https://developer.android.com/preview/privacy/package-visibility).
+
+In the _Platforms/Android/AndroidManifest.xml_ file, add the following `queries/intent` nodes the `manifest` node:
+
+```xml
+
+
+
+
+
+
+```
+
+# [iOS](#tab/ios)
+
+No setup is required.
+
+# [Windows](#tab/windows)
+
+No setup is required.
+
+-----
+
+
+## Open the phone dialer
+
+The phone dialer functionality works by calling the `Open` method with a phone number. When the phone dialer is opened, .NET MAUI will automatically attempt to format the number based on the country code, if specified.
+
+:::code language="csharp" source="../snippets/shared_1/CommsPage.xaml.cs" id="phone_dial":::
diff --git a/docs/platform-integration/communication/sms.md b/docs/platform-integration/communication/sms.md
new file mode 100644
index 000000000..bc4aaf196
--- /dev/null
+++ b/docs/platform-integration/communication/sms.md
@@ -0,0 +1,51 @@
+---
+title: "SMS"
+description: "Learn how to use the .NET MAUI Sms class in Microsoft.Maui.ApplicationModel.Communication to open the default SMS application. The text message can be preloaded with a message and recipient."
+ms.date: 05/23/2022
+no-loc: ["Microsoft.Maui", "Microsoft.Maui.ApplicationModel.Communication"]
+---
+
+# SMS
+
+This article describes how you can use the .NET Multi-platform App UI (.NET MAUI) `ISms` interface to open the default SMS app and preload it with a message and recipient. The `ISms` interface is exposed through the `Sms.Default` property.
+
+[!INCLUDE [docs under construction](~/includes/preview-note.md)]
+
+The `Sms` and `ISms` types are available in the `Microsoft.Maui.ApplicationModel.Communication` namespace.
+
+## Get started
+
+To access the SMS functionality, the following platform specific setup is required.
+
+
+# [Android](#tab/android)
+
+If your project's Target Android version is set to **Android 11 (R API 30)** or higher, you must update your _Android Manifest_ with queries that use Android's [package visibility requirements](https://developer.android.com/preview/privacy/package-visibility).
+
+In the _Platforms/Android/AndroidManifest.xml_ file, add the following `queries/intent` nodes the `manifest` node:
+
+```xml
+
+
+
+
+
+
+```
+
+# [iOS](#tab/ios)
+
+No additional setup required.
+
+# [Windows](#tab/windows)
+
+No additional setup required.
+
+-----
+
+
+## Create a message
+
+The SMS functionality works by creating a new `SmsMessage` object, and calling the `ComposeAsync` method. You can optionally include a message and zero or more recipients.
+
+:::code language="csharp" source="../snippets/shared_1/CommsPage.xaml.cs" id="sms_send":::
diff --git a/docs/platform-integration/data/clipboard.md b/docs/platform-integration/data/clipboard.md
new file mode 100644
index 000000000..77aa72046
--- /dev/null
+++ b/docs/platform-integration/data/clipboard.md
@@ -0,0 +1,41 @@
+---
+title: "Clipboard"
+description: "Learn how to use the .NET MAUI Clipboard class in the Microsoft.Maui.ApplicationModel.DataTransfer namespace, which lets you copy and paste text to the system clipboard."
+ms.date: 05/23/2022
+no-loc: ["Microsoft.Maui", "Microsoft.Maui.ApplicationModel.DataTransfer"]
+---
+
+# Clipboard
+
+This article describes how you can use the .NET Multi-platform App UI (.NET MAUI) `Clipboard` class. With this class, you can copy and paste text to and from the system clipboard.
+
+[!INCLUDE [docs under construction](~/includes/preview-note.md)]
+
+The `Clipboard` class is available in the `Microsoft.Maui.ApplicationModel.DataTransfer` namespace.
+
+> [!TIP]
+> Access to the clipboard must be done on the main user interface thread. For more information on how to invoke methods on the main user interface thread, see [MainThread](../appmodel/main-thread.md).
+
+## Using Clipboard
+
+The clipboard is accessed through default implementation of the `IClipboard` interface, available from the `Microsoft.Maui.ApplicationModel.DataTransfer.Clipboard.Default` property. Access to the clipboard is limited to string data. You can check if the clipboard contains data, set or clear the data, and read the data. The `ClipboardContentChanged` event is raised whenever the clipboard data changes.
+
+The following code example demonstrates using a button to set the clipboard data:
+
+:::code language="csharp" source="../snippets/shared_1/DataPage.xaml.cs" id="clipboard_set":::
+
+The following code example demonstrates using a button to read the clipboard data. The code first checks if the clipboard has data, read that data, and then uses a `null` value with `SetTextAsync` to clear the clipboard:
+
+:::code language="csharp" source="../snippets/shared_1/DataPage.xaml.cs" id="clipboard_read":::
+
+## Clear the clipboard
+
+You can clear the clipboard by passing `null` to the `SetTextAsync` method, as the following code example demonstrates:
+
+:::code language="csharp" source="../snippets/shared_1/DataPage.xaml.cs" id="clipboard_clear":::
+
+## Detecting clipboard changes
+
+The `IClipboard` interface provides the `ClipboardContentChanged` event. When this event is raised, the clipboard content has changed. The following code example adds a handler to the event when the content page is loaded:
+
+:::code language="csharp" source="../snippets/shared_1/DataPage.xaml.cs" id="clipboard_event":::
diff --git a/docs/platform-integration/data/media/share/share.png b/docs/platform-integration/data/media/share/share.png
new file mode 100644
index 000000000..ee320592a
Binary files /dev/null and b/docs/platform-integration/data/media/share/share.png differ
diff --git a/docs/platform-integration/data/share.md b/docs/platform-integration/data/share.md
new file mode 100644
index 000000000..3d380b8f5
--- /dev/null
+++ b/docs/platform-integration/data/share.md
@@ -0,0 +1,93 @@
+---
+title: "Share"
+description: "Learn how to use the .NET MAUI Share class, which can share data, such as web links, to other applications on the device."
+ms.date: 05/23/2022
+no-loc: ["Microsoft.Maui", "Microsoft.Maui.ApplicationModel.DataTransfer"]
+---
+
+# Share
+
+This article describes how you can use the .NET Multi-platform App UI (.NET MAUI) `Share` class. This class provides an API to send date, such as text or web links, to the devices share function.
+
+[!INCLUDE [docs under construction](~/includes/preview-note.md)]
+
+The `Share` class is available in the `Microsoft.Maui.ApplicationModel.DataTransfer` namespace.
+
+When a share request is made, the device displays a share window, prompting the user to choose an app to share with:
+
+:::image type="content" source="media/share/share.png" alt-text="Share from your app to a different app":::
+
+## Get started
+
+To access the **Share** functionality, the following platform-specific setup is required:
+
+
+# [Android](#tab/android)
+
+No setup is required.
+
+# [iOS](#tab/ios)
+
+If your application is going to share media files, such as photos and videos, you must add the following keys to your _Info.plist_ file:
+
+```xml
+NSPhotoLibraryAddUsageDescription
+This app needs access to the photo gallery to save photos and videos.
+NSPhotoLibraryUsageDescription
+This app needs access to the photo gallery to save photos and videos.
+```
+
+The `` elements represent the text shown to your users when permission is requested. Make sure that you change the text to something specific to your application.
+
+# [Windows](#tab/windows)
+
+No setup is required.
+
+-----
+
+## Share text and links
+
+The share functionality works by calling the `RequestAsync` method with a data payload that includes information to share to other applications. `Text` and `Uri` can be mixed and each platform will handle filtering based on content.
+
+:::code language="csharp" source="../snippets/shared_1/DataPage.xaml.cs" id="share_text_uri":::
+
+## Share a file
+
+You can also share files to other applications on the device. .NET MAUI automatically detects the file type (MIME) and requests a share. However, operating systems may restrict which types of files can be shared.
+
+The following code example writes a text file to the device, and then requests a share:
+
+:::code language="csharp" source="../snippets/shared_1/DataPage.xaml.cs" id="share_file":::
+
+## Share multiple files
+
+Sharing multiple files is slightly different from sharing a single file. Instead of using the `File` property of the share request, use the `Files` property:
+
+:::code language="csharp" source="../snippets/shared_1/DataPage.xaml.cs" id="share_file_multiple":::
+
+## Presentation location
+
+[!INCLUDE [ios-PresentationSourceBounds](../includes/ios-PresentationSourceBounds.md)]
+
+## Platform differences
+
+This section describes the platform-specific differences with the share API.
+
+
+
+# [Android](#tab/android)
+
+- The `Subject` property is used for the desired subject of a message.
+
+# [iOS](#tab/ios)
+
+- The `Subject` property isn't used.
+
+# [Windows](#tab/windows)
+
+- The `Title` property will default to the application name if not set.
+- The `Subject` property isn't used.
+
+-----
+
+
diff --git a/docs/platform-integration/device-media/picker.md b/docs/platform-integration/device-media/picker.md
new file mode 100644
index 000000000..5661b9fce
--- /dev/null
+++ b/docs/platform-integration/device-media/picker.md
@@ -0,0 +1,117 @@
+---
+title: "Media picker"
+description: "Learn how to use the MediaPicker class in the Microsoft.Maui.Media namespace, to prompt the user to select or take a photo or video."
+ms.date: 05/23/2022
+no-loc: ["Microsoft.Maui", "Microsoft.Maui.Media", "MediaPicker"]
+---
+
+# Media picker
+
+This article describes how you can use the .NET Multi-platform App UI (.NET MAUI) `IMediaPicker` interface. This interfaces lets a user pick or take a photo or video on the device. The `IMediaPicker` interface is exposed through the `MediaPicker.Default` property.
+
+[!INCLUDE [docs under construction](~/includes/preview-note.md)]
+
+The `MediaPicker` and `IMediaPicker` types are available in the `Microsoft.Maui.Media` namespace.
+
+## Get started
+
+To access the media picker functionality, the following platform-specific setup is required.
+
+
+# [Android](#tab/android)
+
+The `CAMERA`, `WRITE_EXTERNAL_STORAGE`, `READ_EXTERNAL_STORAGE` permissions are required, and must be configured in the Android project. These can be added in the following ways:
+
+- Add the assembly-based permission:
+
+ Open the _Platforms/Android/MainApplication.cs_ file and add the following assembly attributes after `using` directives:
+
+ :::code language="csharp" source="../snippets/shared_1/Platforms/Android/MainApplication.cs" id="media_picker":::
+
+ \- or -
+
+- Update the Android Manifest:
+
+ Open the _Platforms/Android/AndroidManifest.xml_ file and add the following in the `manifest` node:
+
+ ```xml
+
+
+
+ ```
+
+
+If your project's Target Android version is set to **Android 11 (R API 30)** or higher, you must update your _Android Manifest_ with queries that use Android's [package visibility requirements](https://developer.android.com/preview/privacy/package-visibility).
+
+In the _Platforms/Android/AndroidManifest.xml_ file, add the following `queries/intent` nodes the `manifest` node:
+
+```xml
+
+
+
+
+
+```
+
+# [iOS](#tab/ios)
+
+In the _Platforms/iOS/Info.plist_ file, add the following keys and values:
+
+```xml
+NSCameraUsageDescription
+This app needs access to the camera to take photos.
+NSMicrophoneUsageDescription
+This app needs access to microphone for taking videos.
+NSPhotoLibraryAddUsageDescription
+This app needs access to the photo gallery for picking photos and videos.
+NSPhotoLibraryUsageDescription
+This app needs access to photos gallery for picking photos and videos.
+```
+
+Each `` element represents the reason the app is requesting access to that specific permission. This text is shown to the user.
+
+# [Windows](#tab/windows)
+
+
+
+In the **Solution Explorer** pane, right-click on the _Platforms/Windows/Package.appxmanifest_ file, and select **View Code**. Under the `` node, add `` and `` elements.
+
+-----
+
+
+## Using media picker
+
+The `IMediaPicker` interface has the following methods that all return a `FileResult`, which can be used to get the file's location or read it.
+
+- `PickPhotoAsync`\
+Opens the media browser to select a photo.
+
+- `CapturePhotoAsync`\
+Opens the camera to take a photo.
+
+- `PickVideoAsync`\
+Opens the media browser to select a video.
+
+- `CaptureVideoAsync`\
+Opens the camera to take a video.
+
+Each method optionally takes in a `MediaPickerOptions` parameter type that allows the `Title` to be set on some operating systems, which is displayed to the user.
+
+> [!IMPORTANT]
+> All methods must be called on the UI thread because permission checks and requests are automatically handled by .NET MAUI.
+
+## Take a photo
+
+Call the `CapturePhotoAsync` method to open the camera and let the user take a photo. If the user takes a photo, the return value of the method will be a non-null value. The following code sample uses the media picker to take a photo and save it to the cache directory:
+
+:::code language="csharp" source="../snippets/shared_1/MediaPage.cs" id="photo_take_and_save":::
+
+[!INCLUDE [tip-file-result](../includes/tip-file-result.md)]
diff --git a/docs/platform-integration/device-media/screenshot.md b/docs/platform-integration/device-media/screenshot.md
new file mode 100644
index 000000000..bcbce7ef5
--- /dev/null
+++ b/docs/platform-integration/device-media/screenshot.md
@@ -0,0 +1,24 @@
+---
+title: "Screenshot"
+description: "Learn how to use the Screenshot class in the Microsoft.Maui.Media namespace, to capture of the current displayed screen of the app."
+ms.date: 05/23/2022
+no-loc: ["Microsoft.Maui", "Microsoft.Maui.Media", "ScreenShot"]
+---
+
+# Screenshot
+
+This article describes how you can use the .NET Multi-platform App UI (.NET MAUI) `IScreenshot` interface. This interfaces lets you take a capture of the current displayed screen of the app. The `IScreenshot` interface is exposed through the `Screenshot.Default` property.
+
+[!INCLUDE [docs under construction](~/includes/preview-note.md)]
+
+The `Screenshot` and `IScreenshot` types are available in the `Microsoft.Maui.Media` namespace.
+
+## Capture a screenshot
+
+To capture a screenshot of the current app, use the `CaptureAsync` method. This method returns a `IScreenshotResult`, which contains information about the capture, such as the width and height of the screenshot. `IScreenshotResult` also includes a `Stream` property that is used to convert the screenshot into an image object for use by your app. The following example demonstrates a method that captures a screenshot and returns it as an `ImageSource`.
+
+:::code language="csharp" source="../snippets/shared_1/MediaPage.cs" id="screenshot":::
+
+## Limitations
+
+Not all views support being captured at a screen level, such as an OpenGL view.
diff --git a/docs/platform-integration/device-media/text-to-speech.md b/docs/platform-integration/device-media/text-to-speech.md
new file mode 100644
index 000000000..61bbaa715
--- /dev/null
+++ b/docs/platform-integration/device-media/text-to-speech.md
@@ -0,0 +1,46 @@
+---
+title: "Text-to-Speech"
+description: "Learn how to use the .NET MAUI TextToSpeech class, which enables an application utilize the built in text-to-speech engines to speak back text from the device."
+ms.date: 05/23/2022
+no-loc: ["Microsoft.Maui", "Microsoft.Maui.Media", "TextToSpeech"]
+---
+
+# Text-to-Speech
+
+This article describes how you can use the .NET Multi-platform App UI (.NET MAUI) `ITextToSpeech` interface. This interface enables an application to utilize the built-in text-to-speech engines to speak back text from the device. You can also use it to query for available languages. The `ITextToSpeech` interface is exposed through the `TextToSpeech.Default` property.
+
+[!INCLUDE [docs under construction](~/includes/preview-note.md)]
+
+The `TextToSpeech` and `ITextToSpeech` types are available in the `Microsoft.Maui.Media` namespace.
+
+## Using Text-to-Speech
+
+Text-to-speech works by calling the `SpeakAsync` method with the text to speak, as the following code example demonstrates:
+
+:::code language="csharp" source="../snippets/shared_1/MediaPage.cs" id="speak":::
+
+This method takes in an optional `CancellationToken` to stop the utterance once it starts.
+
+:::code language="csharp" source="../snippets/shared_1/MediaPage.cs" id="speak_cancel":::
+
+Text-to-Speech will automatically queue speech requests from the same thread.
+
+:::code language="csharp" source="../snippets/shared_1/MediaPage.cs" id="speak_queue":::
+
+## Settings
+
+To control the volume, pitch, and locale of the voice, use the `SpeechOptions` class. Pass an instance of that class to the `SpeakAsync` method. the `GetLocalesAsync` method retrieves a collection of the locales provided by the operating system.
+
+:::code language="csharp" source="../snippets/shared_1/MediaPage.cs" id="speak_options":::
+
+The following are supported values for these parameters:
+
+| Parameter | Minimum | Maximum |
+|-----------|:-------:|:-------:|
+| `Pitch` | 0 | 2.0 |
+| `Volume` | 0 | 1.0 |
+
+## Limitations
+
+- Utterance queueing is not guaranteed if called across multiple threads.
+- Background audio playback is not officially supported.
diff --git a/docs/platform-integration/device-media/unit-converters.md b/docs/platform-integration/device-media/unit-converters.md
new file mode 100644
index 000000000..3c8c0e9e9
--- /dev/null
+++ b/docs/platform-integration/device-media/unit-converters.md
@@ -0,0 +1,50 @@
+---
+title: "Unit converters"
+description: "Learn how to use the .NET MAUI UnitConverters class, which provides several unit converters to help developers."
+ms.date: 05/23/2022
+no-loc: ["Microsoft.Maui", "Microsoft.Maui.Media", "UnitConverters"]
+---
+
+# Unit converters
+
+This article describes how you can use the .NET Multi-platform App UI (.NET MAUI) `UnitConverters` class. This class provides several unit converters to help developers convert from one unit of measurement to another.
+
+## Using unit converters
+
+All unit converters are available by using the static `Microsoft.Maui.Media.UnitConverters` class. For example, you can convert Fahrenheit to Celsius with the `FahrenheitToCelsius` method:
+
+```csharp
+var celsius = UnitConverters.FahrenheitToCelsius(32.0);
+```
+
+Here is a list of available conversions:
+
+- `FahrenheitToCelsius`
+- `CelsiusToFahrenheit`
+- `CelsiusToKelvin`
+- `KelvinToCelsius`
+- `MilesToMeters`
+- `MilesToKilometers`
+- `KilometersToMiles`
+- `MetersToInternationalFeet`
+- `InternationalFeetToMeters`
+- `DegreesToRadians`
+- `RadiansToDegrees`
+- `DegreesPerSecondToRadiansPerSecond`
+- `RadiansPerSecondToDegreesPerSecond`
+- `DegreesPerSecondToHertz`
+- `RadiansPerSecondToHertz`
+- `HertzToDegreesPerSecond`
+- `HertzToRadiansPerSecond`
+- `KilopascalsToHectopascals`
+- `HectopascalsToKilopascals`
+- `KilopascalsToPascals`
+- `HectopascalsToPascals`
+- `AtmospheresToPascals`
+- `PascalsToAtmospheres`
+- `CoordinatesToMiles`
+- `CoordinatesToKilometers`
+- `KilogramsToPounds`
+- `PoundsToKilograms`
+- `StonesToPounds`
+- `PoundsToStones`
diff --git a/docs/platform-integration/device/battery.md b/docs/platform-integration/device/battery.md
new file mode 100644
index 000000000..9292d252d
--- /dev/null
+++ b/docs/platform-integration/device/battery.md
@@ -0,0 +1,119 @@
+---
+title: "Battery"
+description: "Learn how to use the .NET MAUI Battery class in the Microsoft.Maui.Devices namespace. You can check the device's battery information and monitor for changes."
+ms.date: 05/23/2022
+no-loc: ["Microsoft.Maui", "Microsoft.Maui.Devices"]
+---
+
+# Battery
+
+This article describes how you can use the .NET Multi-platform App UI (.NET MAUI) `Battery` class to check the device's battery information and monitor for changes. This class also provides information about the device's energy-saver status, which indicates if the device is running in a low-power mode.
+
+[!INCLUDE [docs under construction](~/includes/preview-note.md)]
+
+The `Battery` class is available in the `Microsoft.Maui.Devices` namespace.
+
+## Get started
+
+To access the **Battery** functionality the following platform-specific setup is required.
+
+
+# [Android](#tab/android)
+
+The `Battery` permission is required and must be configured in the Android project. This can be added in the following ways:
+
+- Add the assembly-based permission:
+
+ Open the _AssemblyInfo.cs_ file under the **Properties** folder and add:
+
+ ```csharp
+ [assembly: UsesPermission(Android.Manifest.Permission.BatteryStats)]
+ ```
+
+ \- or -
+
+- Update the Android Manifest:
+
+ In the **Solution Explorer**, open the _AndroidManifest.xml_ file. This is typically located in the **Your-project** > **Platforms** > **Android** folder. Add the following node as a child to the `` node:
+
+ ```xml
+
+ ```
+
+
+
+# [iOS](#tab/ios)
+
+No setup is required.
+
+# [Windows](#tab/windows)
+
+No setup is required.
+
+-----
+
+
+## Check the battery status
+
+The battery status can be checked by accessing the default implementation of the `IBattery` interface. This implementation is available from the `Microsoft.Maui.Devices.Battery.Default` property. The interface defines the `BatteryInfoChanged` event which is raised when the state of the battery changed. Outside of this event, you can still check the state of the battery at any time by using the various properties defined by the interface.
+
+The following example demonstrates how to use the monitor the `BatteryInfoChanged` event and report the battery status two `Label` controls:
+
+:::code language="csharp" source="../snippets/shared_1/BatteryTestPage.xaml.cs" id="watch_battery":::
+
+The `ChargeLevel` property returns a value between **0.0** and **1.0**, indicating the battery's charge level from empty to full, respectively.
+
+## Low-power energy-saver mode
+
+Devices that run on batteries can be put into a low-power energy-saver mode. Sometimes devices are switched into this mode automatically, like when the battery drops below 20% capacity. The operating system responds to energy-saver mode by reducing activities that tend to deplete the battery. Applications can help by avoiding background processing or other high-power activities when energy-saver mode is on.
+
+> [!IMPORTANT]
+> Applications should avoid background processing if the device's energy-saver status is on.
+
+The energy-saver status of the device can be read by accessing the `EnergySaverStatus` property, which is either `On`, `Off`, or `Unknown`. If the status is `On`, the application should avoid background processing or other activities that may consume a lot of power.
+
+The battery will raise the `EnergySaverStatusChanged` event when the battery enters or leaves energy-saver mode.
+You can also obtain the current energy-saver status of the device using the static `Battery.EnergySaverStatus` property:
+
+The following code example monitors the energy-saver status and sets a property accordingly.
+
+:::code language="csharp" source="../snippets/shared_1/BatteryTestPage.xaml.cs" id="energy_saver":::
+
+## Power source
+
+The `PowerSource` property returns a `BatteryPowerSource` enumeration that indicates how the device is being charged, if at all. If it's not being charged, the status will be `Battery`. The `AC`, `Usb`, and `Wireless` values indicate that the battery is being charged.
+
+The following code example sets the text of a `Label` control based on power source.
+
+:::code language="csharp" source="../snippets/shared_1/BatteryTestPage.xaml.cs" id="charge_mode":::
+
+## Platform differences
+
+This section describes the platform-specific differences with the battery.
+
+
+
+
+# [Android](#tab/android)
+
+No platform differences.
+
+# [iOS](#tab/ios)
+
+- APIs won't work in a simulator and you must use a real device.
+- Only returns `AC` or `Battery` for `PowerSource`.
+
+# [Windows](#tab/windows)
+
+- Only returns `AC` or `Battery` for `PowerSource`.
+
+-----
+
+
+
diff --git a/docs/platform-integration/device/display.md b/docs/platform-integration/device/display.md
new file mode 100644
index 000000000..889b50dee
--- /dev/null
+++ b/docs/platform-integration/device/display.md
@@ -0,0 +1,50 @@
+---
+title: "Device Display Information"
+description: "Learn how to use the .NET MAUI DeviceDisplay class in the Microsoft.Maui.Devices namespace, which provides screen metrics for the device on which the app is running."
+ms.date: 05/23/2022
+no-loc: ["Microsoft.Maui", "Microsoft.Maui.Devices"]
+---
+
+# Device display information
+
+This article describes how you can use the .NET Multi-platform App UI (.NET MAUI) `DeviceDisplay` class to read information about the device's screen metrics. This class can be used to request the screen stays awake while the app is running.
+
+[!INCLUDE [docs under construction](~/includes/preview-note.md)]
+
+The `DeviceDisplay` class is available in the `Microsoft.Maui.Devices` namespace.
+
+## Main display info
+
+Information about your device's screen is accessed by the default implementation of the `IDeviceDisplay` interface, which is available by accessing the `Microsoft.Maui.Devices.DeviceDisplay.Current` property.
+
+The `IDeviceDisplay.MainDisplayInfo` property returns information about the screen and orientation. The following code example uses the `Loaded` event of a page to read information about the current screen:
+
+:::code language="csharp" source="../snippets/shared_1/DeviceDetailsPage.xaml.cs" id="main_display":::
+
+The `IDeviceDisplay` interface also provides the `MainDisplayInfoChanged` event that is raised when any screen metric changes, such as when the device orientation changes from `DisplayOrientation.Landscape` to `DisplayOrientation.Portrait`.
+
+## Keep the screen on
+
+You can also prevent the device from locking or the screen turning off by setting the `IDeviceDisplay.KeepScreenOn` property to `true`. The following code example toggles the screen lock whenever the switch control is pressed:
+
+:::code language="csharp" source="../snippets/shared_1/DeviceDetailsPage.xaml.cs" id="always_on":::
+
+## Platform differences
+
+This section describes the platform-specific differences with the device display.
+
+
+# [Android](#tab/android)
+
+No platform differences.
+
+# [iOS](#tab/ios)
+
+- Accessing `DeviceDisplay` must be done on the UI thread or else an exception will be thrown. You can use the [`MainThread.BeginInvokeOnMainThread`](../appmodel/main-thread.md) method to run that code on the UI thread.
+
+# [Windows](#tab/windows)
+
+No platform differences.
+
+-----
+
diff --git a/docs/platform-integration/device/flashlight.md b/docs/platform-integration/device/flashlight.md
new file mode 100644
index 000000000..6d0cb8e82
--- /dev/null
+++ b/docs/platform-integration/device/flashlight.md
@@ -0,0 +1,107 @@
+---
+title: "Flashlight"
+description: "Learn how to use the .NET MAUI Flashlight class in the Microsoft.Maui.Devices namespace. This class provides the ability to turn on or off the device's camera flash, to emulate a flashlight."
+ms.date: 05/23/2022
+no-loc: ["Microsoft.Maui", "Microsoft.Maui.Devices"]
+---
+
+# Flashlight
+
+This article describes how you can use the .NET Multi-platform App UI (.NET MAUI) `Flashlight` class. With this class, you can toggle the device's camera flash on and off, to emulate a flashlight.
+
+[!INCLUDE [docs under construction](~/includes/preview-note.md)]
+
+The `Flashlight` class is available in the `Microsoft.Maui.Devices` namespace.
+
+## Get started
+
+To access the flashlight functionality the following platform-specific setup is required.
+
+
+# [Android](#tab/android)
+
+There are two permissions to configure in your project: `Flashlight` and `Camera`. These permissions can be set in the following ways:
+
+- Add the assembly-based permission:
+
+ Open the _AssemblyInfo.cs_ file under the **Properties** folder and add:
+
+ ```csharp
+ [assembly: UsesPermission(Android.Manifest.Permission.Flashlight)]
+ [assembly: UsesPermission(Android.Manifest.Permission.Camera)]
+ ```
+
+ \- or -
+
+- Update the Android Manifest:
+
+ Open the _AndroidManifest.xml_ file under the **Properties** folder and add the following in the `manifest` node:
+
+ ```xml
+
+
+ ```
+
+
+
+By adding these permissions, [Google Play will automatically filter out devices](https://developer.android.com/guide/topics/manifest/uses-feature-element.html#permissions-features) without specific hardware. You can get around this by adding the following to your _AssemblyInfo.cs_ file in your Android project:
+
+```csharp
+[assembly: UsesFeature("android.hardware.camera", Required = false)]
+[assembly: UsesFeature("android.hardware.camera.autofocus", Required = false)]
+```
+
+# [iOS](#tab/ios)
+
+No setup is required.
+
+# [Windows](#tab/windows)
+
+No setup is required.
+
+-----
+
+
+## Use Flashlight
+
+The flashlight is accessed through default implementation of the `IFlashlight` interface, available from the `Microsoft.Maui.Devices.Flashlight.Default` property. The flashlight can be turned on and off through the `TurnOnAsync` and `TurnOffAsync` methods. The following code example ties the flashlight's on or off state to a `Switch` control:
+
+:::code language="csharp" source="../snippets/shared_1/DeviceDetailsPage.xaml.cs" id="flashlight":::
+
+## Platform differences
+
+This section describes the platform-specific differences with the flashlight.
+
+
+
+### [Android](#tab/android)
+
+The `Flashlight` class has been optimized based on the device's operating system.
+
+#### API level 23 and higher
+
+On newer API levels, [Torch Mode](https://developer.android.com/reference/android/hardware/camera2/CameraManager.html#setTorchMode) will be used to turn on or off the flash unit of the device.
+
+#### API level 22 and lower
+
+A camera surface texture is created to turn on or off the `FlashMode` of the camera unit.
+
+### [iOS](#tab/ios)
+
+The `AVCaptureDevice` API is used to turn on and off the Torch and Flash mode of the device.
+
+### [Windows](#tab/windows)
+
+The API is used to turn on or off the first detected lamp on the back of the device.
+
+-----
+
+
diff --git a/docs/platform-integration/device/geocoding.md b/docs/platform-integration/device/geocoding.md
new file mode 100644
index 000000000..cb412691e
--- /dev/null
+++ b/docs/platform-integration/device/geocoding.md
@@ -0,0 +1,56 @@
+---
+title: "Geocoding"
+description: "Learn how to use the .NET MAUI Geocoding class in the Microsoft.Maui.Devices.Sensors namespace. This class provides APIs to both geocode a placemark to a positional coordinate, and reverse geocode coordinates to a placemark."
+ms.date: 05/23/2022
+no-loc: ["Microsoft.Maui", "Microsoft.Maui.Devices", "Microsoft.Maui.Devices.Sensors"]
+---
+
+# Geocoding
+
+This article describes how you can use the .NET Multi-platform App UI (.NET MAUI) `IGeocoding` interface. This interfaces provides APIs to geocode a placemark to a positional coordinates and reverse geocode coordinates to a placemark. The `IGeocoding` interface is exposed through the `Geocoding.Default` property.
+
+[!INCLUDE [docs under construction](~/includes/preview-note.md)]
+
+The `Geocoding` and `IGeocoding` types are available in the `Microsoft.Maui.Devices.Sensors` namespace.
+
+## Get started
+
+To access the **Geocoding** functionality the following platform-specific setup is required.
+
+
+# [Android](#tab/android)
+
+No setup is required.
+
+# [iOS](#tab/ios)
+
+No setup is required.
+
+# [Windows](#tab/windows)
+
+A Bing Maps API key is required to use geocoding functionality. Sign up for a free [Bing Maps](https://www.bingmapsportal.com/) account. Under **My account** > **My keys**, create a new key and fill out information based on your application type, which should be **Windows Application**.
+
+To enable geocoding functionality in your app, add the `ConfigureEssentials` step to the `CreateMauiApp` bootstrap code. The app startup code is configured in the _MauiProgram.cs_ file. Call the `UseMapServiceToken` method to enable geocoding functionality:
+
+:::code language="csharp" source="../snippets/shared_1/MauiProgram.cs" id="bootstrap_maptoken" highlight="12-15":::
+
+-----
+
+
+## Use geocoding
+
+The following example demonstrates how to get the location coordinates for an address:
+
+:::code language="csharp" source="../snippets/shared_1/SensorsPage.xaml.cs" id="geocoding_location":::
+
+The altitude isn't always available. If it isn't available, the `Altitude` property might be `null`, or the value might be `0`. If the altitude is available, the value is in meters above sea level.
+
+## Reverse geocoding
+
+Reverse geocoding is the process of getting placemarks for an existing set of coordinates. The following example demonstrates getting placemarks:
+
+:::code language="csharp" source="../snippets/shared_1/SensorsPage.xaml.cs" id="geocoding_reverse":::
+
+## Get the distance between two locations
+
+The `Location` and `LocationExtensions` classes define methods to calculate the distance between two locations. For an example of getting the distance between two locations, see [Distance between two locations](geolocation.md#distance-between-two-locations).
diff --git a/docs/platform-integration/device/geolocation.md b/docs/platform-integration/device/geolocation.md
new file mode 100644
index 000000000..2090fc2c5
--- /dev/null
+++ b/docs/platform-integration/device/geolocation.md
@@ -0,0 +1,208 @@
+---
+title: "Geolocation"
+description: "Learn how to use the .NET MAUI Geolocation class in the Microsoft.Maui.Devices.Sensors namespace. This class provides API to retrieve the device's current geolocation coordinates."
+ms.date: 05/23/2022
+no-loc: ["Microsoft.Maui", "Microsoft.Maui.Devices", "Microsoft.Maui.Devices.Sensors"]
+---
+
+# Geolocation
+
+This article describes how you can use the .NET Multi-platform App UI (.NET MAUI) `IGeolocation` class. This class provides APIs to retrieve the device's current geolocation coordinates. The `IGeolocation` interface is exposed through the `Geolocation.Default` property.
+
+[!INCLUDE [docs under construction](~/includes/preview-note.md)]
+
+The `Geolocation` and `IGeolocation` types are available in the `Microsoft.Maui.Devices.Sensors` namespace.
+
+## Get started
+
+To access the **Geolocation** functionality, the following platform-specific setup is required:
+
+
+# [Android](#tab/android)
+
+**Coarse and Fine Location** permissions are required and must be configured in the Android project. Additionally, if your app targets Android 5.0 (API level 21) or higher, you must declare that your app uses the hardware features in the manifest file. This can be added in the following ways:
+
+- Add the assembly-based permission:
+
+ Open the _Platforms/Android/MainApplication.cs_ file and add the following assembly attributes after `using` directives:
+
+ ```csharp
+ [assembly: UsesPermission(Android.Manifest.Permission.AccessCoarseLocation)]
+ [assembly: UsesPermission(Android.Manifest.Permission.AccessFineLocation)]
+ [assembly: UsesFeature("android.hardware.location", Required = false)]
+ [assembly: UsesFeature("android.hardware.location.gps", Required = false)]
+ [assembly: UsesFeature("android.hardware.location.network", Required = false)]
+ ```
+
+ If your application is targeting Android 10 - Q (API Level 29 or higher) and is requesting `LocationAlways`, you must also add this permission request:
+
+ ```csharp
+ [assembly: UsesPermission(Manifest.Permission.AccessBackgroundLocation)]
+ ```
+
+ \- or -
+
+- Update the Android Manifest:
+
+ Open the _Platforms/Android/AndroidManifest.xml_ file and add the following in the `manifest` node:
+
+ ```xml
+
+
+
+
+
+ ```
+
+ If your application is targeting Android 10 - Q (API Level 29 or higher) and is requesting `LocationAlways`, you must also add this permission request:
+
+ ```xml
+
+ ```
+
+
+
+> [!TIP]
+> Be sure to read the [Android documentation on background location updates](https://developer.android.com/training/location/permissions), as there are many restrictions that need to be considered.
+
+# [iOS](#tab/ios)
+
+In the _Platforms/iOS/Info.plist_ file, add the following keys and values:
+
+```xml
+NSLocationWhenInUseUsageDescription
+Fill in a reason why your app needs access to location.
+```
+
+The `` element is the reason the app is requesting access to location information. This text is shown to the user.
+
+
+
+# [Windows](#tab/windows)
+
+
+
+In the **Solution Explorer** pane, right-click on the _Platforms/Windows/Package.appxmanifest_ file, and select **View Code**. Under the `` node, add the `` element.
+
+-----
+
+
+## Get the last known location
+
+The device may have cached the most recent location of the device. Use the `GetLastKnownLocationAsync` method to access the cached location, if available. This is often faster then doing a full location query, but can be less accurate. If no cached location exists, this method returns `null`.
+
+> [!NOTE]
+> When necessary, the Geolocation API prompts the user for permissions.
+
+The following code example demonstrates checking for a cached location:
+
+:::code language="csharp" source="../snippets/shared_1/SensorsPage.xaml.cs" id="geolocation_cached":::
+
+Depending on the device, not all location values may be available. For example, the `Altitude` property might be `null`, have a value of 0, or have a positive value indicating the meters above sea level. Other values that may not be present include the `Speed` and `Course` properties.
+
+## Get the current location
+
+While checking for the [last known location](#get-the-last-known-location) of the device may be quicker, it can be inaccurate. Use the `GetLocationAsync` method to query the device for the current location. You can configure the accuracy and timeout of the query. It's best to the method overload that uses the `GeolocationRequest` and `CancellationToken` parameters, since it may take some time to get the device's location.
+
+> [!NOTE]
+> When necessary, the Geolocation API prompt's the user for permissions.
+
+The following code example demonstrates how to request the device's location, while supporting cancellation:
+
+:::code language="csharp" source="../snippets/shared_1/SensorsPage.xaml.cs" id="geolocation_get":::
+
+Not all location values may be available, depending on the device. For example, the `Altitude` property might be `null`, have a value of 0, or have a positive value indicating the meters above sea level. Other values that may not be present include `Speed` and `Course`.
+
+## Accuracy
+
+The following sections outline the location accuracy distance, per platform:
+
+### Lowest
+
+| Platform | Distance (in meters) |
+|----------|----------------------|
+| Android | 500 |
+| iOS | 3000 |
+| Windows | 1000 - 5000 |
+
+### Low
+
+| Platform | Distance (in meters) |
+|----------|----------------------|
+| Android | 500 |
+| iOS | 1000 |
+| Windows | 300 - 3000 |
+
+### Medium (Default)
+
+| Platform | Distance (in meters) |
+|----------|----------------------|
+| Android | 100 - 500 |
+| iOS | 100 |
+| Windows | 30-500 |
+
+### High
+
+| Platform | Distance (in meters) |
+|----------|----------------------|
+| Android | 0 - 100 |
+| iOS | 10 |
+| Windows | <= 10 |
+
+### Best
+
+| Platform | Distance (in meters) |
+|----------|----------------------|
+| Android | 0 - 100 |
+| iOS | ~0 |
+| Windows | <= 10 |
+
+## Detecting mock locations
+
+Some devices may return a mock location from the provider or by an application that provides mock locations. You can detect this by using the `IsFromMockProvider` on any `Location`:
+
+:::code language="csharp" source="../snippets/shared_1/SensorsPage.xaml.cs" id="geolocation_ismock":::
+
+## Distance between two locations
+
+The `Location.CalculateDistance` method calculates the distance between two geographic locations. This calculated distance doesn't take roads or other pathways into account, and is merely the shortest distance between the two points along the surface of the Earth. This calculation is known as the _great-circle distance_ calculation.
+
+The following code calculates the distance between the United States of America cities of Boston and San Francisco:
+
+:::code language="csharp" source="../snippets/shared_1/SensorsPage.xaml.cs" id="geolocation_distance":::
+
+The `Location` constructor accepts the latitude and longitude arguments, respectively. Positive latitude values are north of the equator, and positive longitude values are east of the Prime Meridian. Use the final argument to `CalculateDistance` to specify miles or kilometers. The `UnitConverters` class also defines `KilometersToMiles` and `MilesToKilometers` methods for converting between the two units.
+
+## Platform differences
+
+This section describes the platform-specific differences with the geolocation API.
+
+Altitude is calculated differently on each platform.
+
+
+
+# [Android](#tab/android)
+
+On Android, [altitude](https://developer.android.com/reference/android/location/Location#getAltitude()), if available, is returned in meters above the WGS 84 reference ellipsoid. If this location doesn't have an altitude, `0.0` is returned.
+
+# [iOS](#tab/ios)
+
+On iOS, [altitude](https://developer.apple.com/documentation/corelocation/cllocation/1423820-altitude) is measured in meters. Positive values indicate altitudes above sea level, while negative values indicate altitudes below sea level.
+
+# [Windows](#tab/windows)
+
+On Windows, altitude is returned in meters.
+
+-----
+
+
diff --git a/docs/platform-integration/device/haptic-feedback.md b/docs/platform-integration/device/haptic-feedback.md
new file mode 100644
index 000000000..ee6e9d2f1
--- /dev/null
+++ b/docs/platform-integration/device/haptic-feedback.md
@@ -0,0 +1,68 @@
+---
+title: "Haptic Feedback"
+description: "Learn how to use the .NET MAUI HapticFeedback class in the Microsoft.Maui.Devices namespace. This class lets you control haptic feedback on a device."
+ms.date: 05/23/2022
+no-loc: ["Microsoft.Maui", "Microsoft.Maui.Devices"]
+---
+
+# Haptic feedback
+
+This article describes how you can use the .NET Multi-platform App UI (.NET MAUI) `HapticFeedback` class to control haptic feedback on a device. Haptic feedback is generally manifested by a gentle vibration sensation provided by the device to give a response to the user. Some examples of haptic feedback are when a user types on a virtual keyboard or when they play a game where the player's character has an encounter with an enemy character.
+
+[!INCLUDE [docs under construction](~/includes/preview-note.md)]
+
+The `HapticFeedback` class is available in the `Microsoft.Maui.Devices` namespace.
+
+## Get started
+
+To access the haptic feedback functionality, the following platform-specific setup is required.
+
+
+# [Android](#tab/android)
+
+The `Vibrate` permission is required and must be configured in the Android project. This can be added in the following ways:
+
+- Add the assembly-based permission:
+
+ Open the _AssemblyInfo.cs_ file under the **Properties** folder and add:
+
+ ```csharp
+ [assembly: UsesPermission(Android.Manifest.Permission.Vibrate)]
+ ```
+
+ \- or -
+
+- Update the Android Manifest:
+
+ Open the _AndroidManifest.xml_ file under the **Properties** folder and add the following in the `manifest` node:
+
+ ```xml
+
+ ```
+
+
+
+# [iOS](#tab/ios)
+
+No setup is required.
+
+# [Windows](#tab/windows)
+
+No setup is required.
+
+-----
+
+
+## Use haptic feedback
+
+Haptic feedback is accessed through default implementation of the `IHapticFeedback` interface, available from the `Microsoft.Maui.Devices.HapticFeedback.Current` property. The haptic feedback functionality is performed in two modes: a short `Click` or a `LongPress`. The following code example initiates a `Click` or `LongPress` haptic feedback response to the user based on which `Button` they click:
+
+:::code language="csharp" source="../snippets/shared_1/DeviceDetailsPage.xaml.cs" id="hapticfeedback":::
diff --git a/docs/platform-integration/device/information.md b/docs/platform-integration/device/information.md
new file mode 100644
index 000000000..d41951eee
--- /dev/null
+++ b/docs/platform-integration/device/information.md
@@ -0,0 +1,79 @@
+---
+title: "Device information"
+description: "Learn how to use the .NET MAUI DeviceInfo class in the Microsoft.Maui.Devices namespace, which provides information about the device the app is running on."
+ms.date: 05/23/2022
+no-loc: ["Microsoft.Maui", "Microsoft.Maui.Devices"]
+---
+
+# Device information
+
+This article describes how you can use the .NET Multi-platform App UI (.NET MAUI) `DeviceInfo` class to read information about the device the app is running on.
+
+[!INCLUDE [docs under construction](~/includes/preview-note.md)]
+
+The `DeviceInfo` class is available in the `Microsoft.Maui.Devices` namespace.
+
+## Read device info
+
+Information about your device's is accessed by the default implementation of the `IDeviceInfo` interface, which is available by accessing the `Microsoft.Maui.Devices.DeviceInfo.Current` property. The `IDeviceInfo` interface provides many properties that describe the device, such as the manufacturer and idiom. The following example demonstrates reading the device info properties:
+
+:::code language="csharp" source="../snippets/shared_1/DeviceDetailsPage.xaml.cs" id="read_info":::
+
+## Get the device platform
+
+The `IDeviceInfo.Platform` property represents the operating system the app is running on. The `DevicePlatform` type provides a property for each operating system:
+
+- `DevicePlatform.Android`
+- `DevicePlatform.iOS`
+- `DevicePlatform.macOS`
+- `DevicePlatform.MacCatalyst`
+- `DevicePlatform.tvOS`
+- `DevicePlatform.Tizen`
+- `DevicePlatform.WinUI`
+- `DevicePlatform.watchOS`
+- `DevicePlatform.Unknown`
+
+The following example demonstrates checking if the `DeviceInfo.Platform` property matches the `Android` operating system:
+
+:::code language="csharp" source="../snippets/shared_1/DeviceDetailsPage.xaml.cs" id="check_for_android":::
+
+## Get the device type
+
+The `DeviceInfo.Idiom` property represents the type of device the app is running on, such as a desktop computer or a tablet. The `DeviceIdiom` type provides a property for each type of device:
+
+- `DeviceIdiom.Phone`
+- `DeviceIdiom.Tablet`
+- `DeviceIdiom.Desktop`
+- `DeviceIdiom.TV`
+- `DeviceIdiom.Watch`
+- `DeviceIdiom.Unknown`
+
+The following example demonstrates comparing the `DeviceInfo.Idiom` value to a `DeviceIdiom` property:
+
+:::code language="csharp" source="../snippets/shared_1/DeviceDetailsPage.xaml.cs" id="idiom":::
+
+## Device type
+
+`IDeviceInfo.DeviceType` property an enumeration to determine if the application is running on a physical or virtual device. A virtual device is a simulator or emulator.
+
+:::code language="csharp" source="../snippets/shared_1/DeviceDetailsPage.xaml.cs" id="device_type":::
+
+## Platform differences
+
+This section describes the platform-specific differences with the device information.
+
+
+# [Android](#tab/android)
+
+No platform differences.
+
+# [iOS](#tab/ios)
+
+iOS doesn't expose an API for developers to get the model of the specific iOS device. Instead, a hardware identifier is returned, like _iPhone10,6_, which refers to the iPhone X. A mapping of these identifiers isn't provided by Apple, but can be found on the internet such as at [The iPhone Wiki](https://www.theiphonewiki.com/wiki/Models) and [Get iOS Model](https://github.com/dannycabrera/Get-iOS-Model) websites.
+
+# [Windows](#tab/windows)
+
+No platform differences.
+
+-----
+
diff --git a/docs/platform-integration/device/sensors.md b/docs/platform-integration/device/sensors.md
new file mode 100644
index 000000000..160d72926
--- /dev/null
+++ b/docs/platform-integration/device/sensors.md
@@ -0,0 +1,245 @@
+---
+title: Accessing device sensors overview
+description: "Learn how to use and monitor sensors provided by your device, with .NET MAUI. You can monitor the following sensors: accelerometer, barometer, compass, shake, gyroscope, magnetometer, orientation."
+ms.topic: overview
+ms.date: 05/23/2022
+ms.custom: template-overview
+show_latex: true
+no-loc: ["Microsoft.Maui", "Microsoft.Maui.Devices", "Microsoft.Maui.Devices.Sensors"]
+---
+
+# Accessing device sensors
+
+Devices have all sorts of sensors that are available to you. Some sensors can detect movement, others changes in the environment, such as light. Monitoring and reacting to these sensors makes your app dynamic in adapting to how the device is being used. You can also respond to changes in the sensors and alert the user. This article gives you a brief overview of the common sensors supported by .NET Multi-User Application (.NET MAUI).
+
+[!INCLUDE [docs under construction](~/includes/preview-note.md)]
+
+Device sensor-related types are available in the `Microsoft.Maui.Devices.Sensors` namespace.
+
+## Sensor speed
+
+Sensor speed sets the speed in which a sensor will return data to your app. When you start a sensor, you provide the desired sensor speed with the `SensorSpeed` enumeration.
+
+- `Fastest`\
+Get the sensor data as fast as possible (not guaranteed to return on UI thread).
+
+- `Game`\
+Rate suitable for games (not guaranteed to return on UI thread).
+
+- `Default`\
+Default rate suitable for screen orientation changes.
+
+- `UI`\
+Rate suitable for general user interface.
+
+> [!WARNING]
+> Monitoring too many sensors at once may affect the rate sensor data is returned to your app.
+
+### Sensor event handlers
+
+Event handlers added to sensors with either the `Game` or `Fastest` speeds **aren't** guaranteed to run on the UI thread. If the event handler needs to access user-interface elements, use the [`MainThread.BeginInvokeOnMainThread`](../appmodel/main-thread.md) method to run that code on the UI thread.
+
+## Accelerometer
+
+The accelerometer sensor measures the acceleration of the device along its three axes. The data reported by the sensor represents how the user is moving the device.
+
+To start monitoring the accelerometer sensor, call the `IAccelerometer.Start` method. .NET MAUI sends accelerometer data changes to your app by raising the `IAccelerometer.ReadingChanged` event. Use the `IAccelerometer.Stop` method to stop monitoring the sensor. You can detect the monitoring state of the accelerometer with the `IAccelerometer.IsMonitoring` property, which will be `true` if the accelerometer was started and is currently being monitored.
+
+The following code example demonstrates monitoring the accelerometer for changes. The `Accelerometer.Default` instance would be passed to the `ToggleAccelerometer` method:
+
+:::code language="csharp" source="../snippets/shared_1/SensorsPage.xaml.cs" id="toggle_accelerometer":::
+
+Accelerometer readings are reported back in **G**. A **G** is a unit of gravitation force equal to the gravity exerted by the earth's gravitational field $(9.81 m/s^2)$.
+
+The coordinate-system is defined relative to the screen of the device in its default orientation. The axes aren't swapped when the device's screen orientation changes.
+
+The **X** axis is horizontal and points to the right, the **Y** axis is vertical and points up and the **Z** axis points towards the outside of the front face of the screen. In this system, coordinates behind the screen have negative **Z** values.
+
+Examples:
+
+- When the device lies flat on a table and is pushed on its left side toward the right, the **X** acceleration value is positive.
+
+- When the device lies flat on a table, the acceleration value is +1.00 G or $(+9.81 m/s^2)$, which correspond to the acceleration of the device $(0 m/s^2)$ minus the force of gravity $(-9.81 m/s^2)$ and normalized as in G.
+
+
+- When the device lies flat on a table and is pushed toward the sky with an acceleration of **A** $m/s^2$, the acceleration value is equal to $A+9.81$ which corresponds to the acceleration of the device $(+A m/s^2)$ minus the force of gravity $(-9.81 m/s^2)$ and normalized in **G**.
+
+### Platform-specific information (Accelerometer)
+
+There is no platform-specific information related to the accelerometer sensor.
+
+## Barometer
+
+The barometer sensor measures the ambient air pressure. The data reported by the sensor represents the current air pressure. This data is reported the first time you start monitoring the sensor and then each time the pressure changes.
+
+To start monitoring the barometer sensor, call the `IBarometer.Start` method. .NET MAUI sends air pressure readings to your app by raising the `IBarometer.ReadingChanged` event. Use the `IBarometer.Stop` method to stop monitoring the sensor. You can detect the monitoring state of the barometer with the `IBarometer.IsMonitoring` property, which will be `true` if the barometer is currently being monitored.
+
+The pressure reading is represented in hectopascals.
+
+The following code example demonstrates monitoring the barometer for changes. The `Barometer.Default` instance would be passed to the `ToggleBarometer` method:
+
+:::code language="csharp" source="../snippets/shared_1/SensorsPage.xaml.cs" id="toggle_barometer":::
+
+### Platform-specific information (Barometer)
+
+This section describes platform-specific implementation details related to the barometer sensor.
+
+
+# [Android](#tab/android)
+
+No platform-specific implementation details.
+
+# [iOS](#tab/ios)
+
+This API uses [CMAltimeter](https://developer.apple.com/documentation/coremotion/cmaltimeter#//apple_ref/occ/cl/CMAltimeter) to monitor pressure changes, which is a hardware feature that was added to iPhone 6 and newer devices. A `FeatureNotSupportedException` will be thrown on devices that don't support the altimeter, the sensor used to report air pressure.
+
+`SensorSpeed` isn't used by this API, as it isn't supported on iOS.
+
+# [Windows](#tab/windows)
+
+No platform-specific implementation details.
+
+-----
+
+
+## Compass
+
+The compass sensor monitors the device's magnetic north heading.
+
+To start monitoring the compass sensor, call the `Compass.Start` method. .NET MAUI raises the `Compass.ReadingChanged` event when the compass heading changes. Use the `Compass.Stop` method to stop monitoring the sensor. You can detect the monitoring state of the compass with the `Compass.IsMonitoring` property, which will be `true` if the compass is currently being monitored.
+
+The following code example demonstrates monitoring the compass for changes. The `Compass.Default` instance would be passed to the `ToggleCompass` method:
+
+:::code language="csharp" source="../snippets/shared_1/SensorsPage.xaml.cs" id="toggle_compass":::
+
+### Platform-specific information (Compass)
+
+This section describes platform-specific implementation details related to the compass feature.
+
+
+# [Android](#tab/android)
+
+Android doesn't provide an API for retrieving the compass heading. .NET MAUI uses the accelerometer and magnetometer sensors to calculate the magnetic north heading, which is recommended by Google.
+
+In rare instances, you maybe see inconsistent results because the sensors need to be calibrated. Recalibrating the compass on Android varies by phone model and Android version. You'll need to search the internet on how to recalibrate the compass. Here are two links that may help in recalibrating the compass:
+
+- [Google Help Center: Find and improve your location’s accuracy](https://support.google.com/maps/answer/2839911)
+- [Stack Exchange Android Enthusiasts: How can I calibrate the compass on my phone?](https://android.stackexchange.com/questions/10145/how-can-i-calibrate-the-compass-on-my-phone)
+
+Running multiple sensors from your app at the same time may impair the sensor speed.
+
+### Lowpass filter
+
+Because of how the Android compass values are updated and calculated, there may be a need to smooth out the values. A _Lowpass filter_ can be applied that averages the sine and cosine values of the angles and can be turned on by using the `Start` method overload, which accepts the `bool applyLowPassFilter` parameter:
+
+```csharp
+Compass.Default.Start(SensorSpeed.UI, applyLowPassFilter: true);
+```
+
+This is only applied on the Android platform, and the parameter is ignored on iOS and Windows. For more information, see [this GitHub issue comment](https://github.com/xamarin/Essentials/pull/354#issuecomment-405316860).
+
+# [iOS](#tab/ios)
+
+No platform-specific implementation details.
+
+# [Windows](#tab/windows)
+
+No platform-specific implementation details.
+
+-----
+
+
+## Shake
+
+Even though this article is listing **shake** as a sensor, it isn't. The [accelerometer](#accelerometer) is used to detect when the device is shaken.
+
+The detect shake API uses raw readings from the accelerometer to calculate acceleration. It uses a simple queue mechanism to detect if 3/4ths of the recent accelerometer events occurred in the last half second. Acceleration is calculated by adding the square of the X, Y, and Z ($x^2+y^2+z^2$) readings from the accelerometer and comparing it to a specific threshold.
+
+To start monitoring the accelerometer sensor, call the `IAccelerometer.Start` method. When a shake is detected, the `IAccelerometer.ShakeDetected` event is raised. Use the `IAccelerometer.Stop` method to stop monitoring the sensor. You can detect the monitoring state of the accelerometer with the `IAccelerometer.IsMonitoring` property, which will be `true` if the accelerometer was started and is currently being monitored.
+
+It's recommended to use `Game` or faster for the `SensorSpeed`.
+
+The following code example demonstrates monitoring the accelerometer for the `ShakeDetected` event. The `Accelerometer.Default` instance would be passed to the `ToggleShake` method:
+
+:::code language="csharp" source="../snippets/shared_1/SensorsPage.xaml.cs" id="toggle_shake":::
+
+### Platform-specific information (Shake)
+
+There is no platform-specific information related to the accelerometer sensor.
+
+## Gyroscope
+
+The gyroscope sensor measures the angular rotation speed around the device's three primary axes.
+
+To start monitoring the gyroscope sensor, call the `IGyroscope.Start` method. .NET MAUI sends gyroscope data changes to your app by raising the `IGyroscope.ReadingChanged` event. The data provided by this event is measured in rad/s (radian per second). Use the `IGyroscope.Stop` method to stop monitoring the sensor. You can detect the monitoring state of the gyroscope with the `IGyroscope.IsMonitoring` property, which will be `true` if the gyroscope was started and is currently being monitored.
+
+The following code example demonstrates monitoring the gyroscope. The `Gyroscope.Default` instance would be passed to the `ToggleGyroscope` method:
+
+:::code language="csharp" source="../snippets/shared_1/SensorsPage.xaml.cs" id="toggle_gyroscope":::
+
+### Platform-specific information (Gyroscope)
+
+There is no platform-specific information related to the gyroscope sensor.
+
+## Magnetometer
+
+The magnetometer sensor indicates the device's orientation relative to Earth's magnetic field.
+
+To start monitoring the magnetometer sensor, call the `IMagnetometer.Start` method. .NET MAUI sends magnetometer data changes to your app by raising the `IMagnetometer.ReadingChanged` event. The data provided by this event is measured in $µT$ (microteslas). Use the `IMagnetometer.Stop` method to stop monitoring the sensor. You can detect the monitoring state of the magnetometer with the `IMagnetometer.IsMonitoring` property, which will be `true` if the magnetometer was started and is currently being monitored.
+
+The following code example demonstrates monitoring the magnetometer. The `Magnetometer.Default` instance would be passed to the `ToggleMagnetometer` method:
+
+:::code language="csharp" source="../snippets/shared_1/SensorsPage.xaml.cs" id="toggle_magnetometer":::
+
+### Platform-specific information (Magnetometer)
+
+There is no platform-specific information related to the magnetometer sensor.
+
+## Orientation
+
+The orientation sensor monitors the orientation of a device in 3D space.
+
+> [!NOTE]
+> This sensor isn't used for determining if the device's video display is in portrait or landscape mode. Use the `Orientation` property of the `ScreenMetrics` object available from the [`DeviceDisplay`](../device/display.md) class.
+
+To start monitoring the orientation sensor, call the `IOrientationSensor.Start` method. .NET MAUI sends orientation data changes to your app by raising the `IOrientationSensor.ReadingChanged` event. Use the `IOrientationSensor.Stop` method to stop monitoring the sensor. You can detect the monitoring state of the orientation with the `IOrientationSensor.IsMonitoring` property, which will be `true` if the orientation was started and is currently being monitored.
+
+The following code example demonstrates monitoring the orientation sensor. The `OrientationSensor.Default` instance would be passed to the `ToggleOrientation` method:
+
+:::code language="csharp" source="../snippets/shared_1/SensorsPage.xaml.cs" id="toggle_orientation":::
+
+`IOrientationSensor` readings are reported back in the form of a [`Quaternion`](xref:System.Numerics.Quaternion) that describes the orientation of the device based on two 3D coordinate systems:
+
+The device (generally a phone or tablet) has a 3D coordinate system with the following axes:
+
+- The positive X-axis points to the right of the display in portrait mode.
+- The positive Y-axis points to the top of the device in portrait mode.
+- The positive Z-axis points out of the screen.
+
+The 3D coordinate system of the Earth has the following axes:
+
+- The positive X-axis is tangent to the surface of the Earth and points east.
+- The positive Y-axis is also tangent to the surface of the Earth and points north.
+- The positive Z-axis is perpendicular to the surface of the Earth and points up.
+
+The `Quaternion` describes the rotation of the device's coordinate system relative to the Earth's coordinate system.
+
+A `Quaternion` value is closely related to rotation around an axis. If an axis of rotation is the normalized vector ($a_x, a_y, a_z$), and the rotation angle is $\theta$, then the (X, Y, Z, W) components of the quaternion are:
+
+$(a_x \times \sin(\theta/2), a_y \times \sin(\theta/2), a_z \times \sin(\theta/2), \cos(\theta/2)$
+
+These are right-hand coordinate systems, so with the thumb of the right hand pointed in the positive direction of the rotation axis, the curve of the fingers indicate the direction of rotation for positive angles.
+
+Examples:
+
+- When the device lies flat on a table with its screen facing up, with the top of the device (in portrait mode) pointing north, the two coordinate systems are aligned. The `Quaternion` value represents the identity quaternion (0, 0, 0, 1). All rotations can be analyzed relative to this position.
+
+- When the device lies flat on a table with its screen facing up, and the top of the device (in portrait mode) pointing west, the `Quaternion` value is (0, 0, 0.707, 0.707). The device has been rotated 90 degrees around the Z axis of the Earth.
+
+- When the device is held upright so that the top (in portrait mode) points towards the sky, and the back of the device faces north, the device has been rotated 90 degrees around the X axis. The `Quaternion` value is (0.707, 0, 0, 0.707).
+
+- If the device is positioned so its left edge is on a table, and the top points north, the device has been rotated -90 degrees around the Y axis (or 90 degrees around the negative Y axis). The `Quaternion` value is (0, -0.707, 0, 0.707).
+
+### Platform-specific information (Orientation)
+
+There is no platform-specific information related to the orientation sensor.
diff --git a/docs/platform-integration/device/vibrate.md b/docs/platform-integration/device/vibrate.md
new file mode 100644
index 000000000..9d2ada4ff
--- /dev/null
+++ b/docs/platform-integration/device/vibrate.md
@@ -0,0 +1,89 @@
+---
+title: "Vibration"
+description: "Learn how to use the .NET MAUI Vibration class, which lets you start and stop the vibrate functionality for a desired amount of time."
+ms.date: 05/23/2022
+no-loc: ["Microsoft.Maui", "Microsoft.Maui.Devices"]
+---
+
+# Vibration
+
+This article describes how you can use the .NET Multi-platform App UI (.NET MAUI) `Vibration` class. This class lets you start and stop the vibrate functionality for a desired amount of time.
+
+[!INCLUDE [docs under construction](~/includes/preview-note.md)]
+
+The `HapticFeedback` class is available in the `Microsoft.Maui.Devices` namespace.
+
+## Get started
+
+To access the Vibration functionality, the following platform specific setup is required.
+
+
+# [Android](#tab/android)
+
+The `VIBRATE` permission is required, and must be configured in the Android project. This can be added in the following ways:
+
+- Add the assembly-based permission:
+
+ Open the _AssemblyInfo.cs_ file under the **Properties** folder and add:
+
+ ```csharp
+ [assembly: UsesPermission(Android.Manifest.Permission.Vibrate)]
+ ```
+
+ \- or -
+
+- Update the Android Manifest:
+
+ Open the _AndroidManifest.xml_ file under the **Properties** folder and add the following in the `manifest` node:
+
+ ```xml
+
+ ```
+
+
+
+# [iOS](#tab/ios)
+
+No additional setup required.
+
+# [Windows](#tab/windows)
+
+No additional setup required.
+
+-----
+
+## Vibrate the device
+
+Haptic feedback is accessed through default implementation of the `IVibration` interface, available from the `Microsoft.Maui.Devices.Vibration.Default` property. The vibration functionality can be requested for a set amount of time or the default of 500 milliseconds. The following code example randomly vibrates the device between one and seven seconds:
+
+:::code language="csharp" source="../snippets/shared_1/DeviceDetailsPage.xaml.cs" id="vibrate":::
+
+## Platform differences
+
+This section describes the platform-specific differences with the vibration API.
+
+
+
+### [Android](#tab/android)
+
+No platform differences.
+
+# [iOS](#tab/ios)
+
+- Only vibrates when device is set to "Vibrate on ring".
+- Always vibrates for 500 milliseconds.
+- Not possible to cancel vibration.
+
+# [Windows](#tab/windows)
+
+No platform differences.
+
+-----
+
+
diff --git a/docs/platform-integration/includes/ios-PresentationSourceBounds.md b/docs/platform-integration/includes/ios-PresentationSourceBounds.md
new file mode 100644
index 000000000..a1c4a9009
--- /dev/null
+++ b/docs/platform-integration/includes/ios-PresentationSourceBounds.md
@@ -0,0 +1,93 @@
+---
+ms.topic: include
+ms.date: 05/23/2022
+---
+
+When requesting a share or opening launcher on iPadOS, you can present it in a popover. This specifies where the popover will appear and point an arrow directly to. This location is often the control that launched the action. You can specify the location using the `PresentationSourceBounds` property:
+
+```csharp
+await Share.RequestAsync(new ShareFileRequest
+ {
+ Title = Title,
+ File = new ShareFile(file),
+ PresentationSourceBounds = DeviceInfo.Platform == DevicePlatform.iOS && DeviceInfo.Idiom == DeviceIdiom.Tablet
+ ? new System.Drawing.Rectangle(0, 20, 0, 0)
+ : System.Drawing.Rectangle.Empty
+ });
+```
+
+```csharp
+await Launcher.OpenAsync(new OpenFileRequest
+ {
+ File = new ReadOnlyFile(file),
+ PresentationSourceBounds = DeviceInfo.Platform == DevicePlatform.iOS && DeviceInfo.Idiom == DeviceIdiom.Tablet
+ ? new System.Drawing.Rectangle(0, 20, 0, 0)
+ : System.Drawing.Rectangle.Empty
+ });
+```
+
+
+
+Everything described here works equally for `Share` and `Launcher`.
+
+Here are some extension methods that help calculate the bounds of a view:
+
+```csharp
+public static class ViewHelpers
+{
+ public static Rectangle GetAbsoluteBounds(this Microsoft.Maui.Controls.View element)
+ {
+ Element looper = element;
+
+ var absoluteX = element.X + element.Margin.Top;
+ var absoluteY = element.Y + element.Margin.Left;
+
+ // Add logic to handle titles, headers, or other non-view bars
+
+ while (looper.Parent != null)
+ {
+ looper = looper.Parent;
+ if (looper is Microsoft.Maui.Controls.View v)
+ {
+ absoluteX += v.X + v.Margin.Top;
+ absoluteY += v.Y + v.Margin.Left;
+ }
+ }
+
+ return new Rectangle(absoluteX, absoluteY, element.Width, element.Height);
+ }
+}
+```
+
+This can then be used when calling `RequestAsync`:
+
+```csharp
+public Command ShareCommand { get; } = new Command(Share);
+
+async void Share(Microsoft.Maui.Controls.View element)
+{
+ try
+ {
+ await Share.Default.RequestAsync(new ShareTextRequest
+ {
+ PresentationSourceBounds = element.GetAbsoluteBounds(),
+ Title = "Title",
+ Text = "Text"
+ });
+ }
+ catch (Exception)
+ {
+ // Handle exception that share failed
+ }
+}
+```
+
+You can pass in the calling element when the `Command` is triggered:
+
+```xml
+
+```
+
+For an example of the `ViewHelpers` class, see the [.NET MAUI Sample hosted on GitHub](https://github.com/dotnet/maui/blob/main/src/Essentials/samples/Samples/Helpers/ViewHelpers.cs).
diff --git a/docs/platform-integration/includes/tip-file-result.md b/docs/platform-integration/includes/tip-file-result.md
new file mode 100644
index 000000000..c5861b005
--- /dev/null
+++ b/docs/platform-integration/includes/tip-file-result.md
@@ -0,0 +1,7 @@
+---
+ms.topic: include
+ms.date: 05/23/2022
+---
+
+> [!TIP]
+> The `FullPath` property doesn't always return the physical path to the file. To get the file, use the `OpenReadAsync` method.
diff --git a/docs/platform-integration/snippets/shared_1/App.xaml b/docs/platform-integration/snippets/shared_1/App.xaml
new file mode 100644
index 000000000..88422b344
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_1/App.xaml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
diff --git a/docs/platform-integration/snippets/shared_1/App.xaml.cs b/docs/platform-integration/snippets/shared_1/App.xaml.cs
new file mode 100644
index 000000000..33ae02130
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_1/App.xaml.cs
@@ -0,0 +1,32 @@
+namespace PlatformIntegration;
+
+public partial class App : Application
+{
+ public App()
+ {
+ InitializeComponent();
+
+ MainPage = new AppShell();
+ }
+
+ //
+ public static void HandleAppActions(AppAction appAction)
+ {
+ App.Current.Dispatcher.Dispatch(async () =>
+ {
+ var page = appAction.Id switch
+ {
+ "battery_info" => new SensorsPage(),
+ "app_info" => new AppModelPage(),
+ _ => default(Page)
+ };
+
+ if (page != null)
+ {
+ await Application.Current.MainPage.Navigation.PopToRootAsync();
+ await Application.Current.MainPage.Navigation.PushAsync(page);
+ }
+ });
+ }
+ //
+}
diff --git a/docs/platform-integration/snippets/shared_1/AppModelPage.xaml b/docs/platform-integration/snippets/shared_1/AppModelPage.xaml
new file mode 100644
index 000000000..3519988b8
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_1/AppModelPage.xaml
@@ -0,0 +1,49 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/platform-integration/snippets/shared_1/AppModelPage.xaml.cs b/docs/platform-integration/snippets/shared_1/AppModelPage.xaml.cs
new file mode 100644
index 000000000..3f7bce821
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_1/AppModelPage.xaml.cs
@@ -0,0 +1,457 @@
+namespace PlatformIntegration;
+
+public partial class AppModelPage : ContentPage
+{
+ public AppModelPage()
+ {
+ InitializeComponent();
+ }
+
+ private void ReadAppInfoButton_Clicked(object sender, EventArgs e)
+ {
+ //
+ string name = AppInfo.Current.Name;
+ string package = AppInfo.Current.PackageName;
+ string version = AppInfo.Current.VersionString;
+ string build = AppInfo.Current.BuildString;
+ //
+
+ //
+ AppInfo.Current.ShowSettingsUI();
+ //
+ }
+
+ private void ReadThemeButton_Clicked(object sender, EventArgs e)
+ {
+ //
+ ThemeInfoLabel.Text = AppInfo.Current.RequestedTheme switch
+ {
+ AppTheme.Dark => "Dark theme",
+ AppTheme.Light => "Light theme",
+ _ => "Unknown"
+ };
+ //
+ }
+
+ private async void OpenRideShareButton_Clicked(object sender, EventArgs e)
+ {
+ //
+ bool supportsUri = await Launcher.Default.CanOpenAsync("lyft://");
+
+ if (supportsUri)
+ await Launcher.Default.OpenAsync("lyft://ridetype?id=lyft_line");
+ //
+ }
+
+ private async void OpenRideShare2Button_Clicked(object sender, EventArgs e)
+ {
+ //
+ bool launcherOpened = await Launcher.Default.TryOpenAsync("lyft://ridetype?id=lyft_line");
+
+ if (launcherOpened)
+ {
+ // Do something fun
+ }
+ //
+ }
+
+ private async void OpenFileButton_Clicked(object sender, EventArgs e)
+ {
+ //
+ string popoverTitle = "Read text file";
+ string name = "File.txt";
+ string file = System.IO.Path.Combine(FileSystem.CacheDirectory, name);
+
+ System.IO.File.WriteAllText(file, "Hello World");
+
+ await Launcher.Default.OpenAsync(new OpenFileRequest(popoverTitle, new ReadOnlyFile(file)));
+ //
+ }
+
+ public void RunCode1()
+ {
+ //
+ MainThread.BeginInvokeOnMainThread(() =>
+ {
+ // Code to run on the main thread
+ });
+ //
+ }
+
+ public void RunCode2()
+ {
+ //
+ void MyMainThreadCode()
+ {
+ // Code to run on the main thread
+ }
+
+ MainThread.BeginInvokeOnMainThread(MyMainThreadCode);
+ //
+ }
+
+ public void RunCode3()
+ {
+ void MyMainThreadCode()
+ {
+ // Code to run on the main thread
+ }
+
+ //
+ if (MainThread.IsMainThread)
+ MyMainThreadCode();
+
+ else
+ MainThread.BeginInvokeOnMainThread(MyMainThreadCode);
+ //
+ }
+
+ private void Navigate1_Clicked(object sender, EventArgs e)
+ {
+ NavigateToBuilding25().Wait();
+ }
+
+ //
+ public async Task NavigateToBuilding25()
+ {
+ var location = new Location(47.645160, -122.1306032);
+ var options = new MapLaunchOptions { Name = "Microsoft Building 25" };
+
+ try
+ {
+ await Map.Default.OpenAsync(location, options);
+ }
+ catch (Exception ex)
+ {
+ // No map application available to open
+ }
+ }
+ //
+
+ //
+ public async Task NavigateToBuilding()
+ {
+ var placemark = new Placemark
+ {
+ CountryName = "United States",
+ AdminArea = "WA",
+ Thoroughfare = "Microsoft Building 25",
+ Locality = "Redmond"
+ };
+ var options = new MapLaunchOptions { Name = "Microsoft Building 25" };
+
+ try
+ {
+ await Map.Default.OpenAsync(placemark, options);
+ }
+ catch (Exception ex)
+ {
+ // No map application available to open or placemark can not be located
+ }
+ }
+ //
+
+ //
+ public async Task NavigateToBuildingByPlacemark()
+ {
+ var placemark = new Placemark
+ {
+ CountryName = "United States",
+ AdminArea = "WA",
+ Thoroughfare = "Microsoft Building 25",
+ Locality = "Redmond"
+ };
+
+ var options = new MapLaunchOptions { Name = "Microsoft Building 25" };
+
+ try
+ {
+ await placemark.OpenMapsAsync(options);
+ }
+ catch (Exception ex)
+ {
+ // No map application available to open or placemark can not be located
+ }
+ }
+ //
+
+ //
+ public async Task DriveToBuilding25()
+ {
+ var location = new Location(47.645160, -122.1306032);
+ var options = new MapLaunchOptions { Name = "Microsoft Building 25",
+ NavigationMode = NavigationMode.Driving };
+
+ try
+ {
+ await Map.Default.OpenAsync(location, options);
+ }
+ catch (Exception ex)
+ {
+ // No map application available to open
+ }
+ }
+ //
+
+ //
+ private async void BrowserOpen_Clicked(object sender, EventArgs e)
+ {
+ try
+ {
+ Uri uri = new Uri("https://www.microsoft.com");
+ await Browser.Default.OpenAsync(uri, BrowserLaunchMode.SystemPreferred);
+ }
+ catch (Exception ex)
+ {
+ // An unexpected error occured. No browser may be installed on the device.
+ }
+ }
+ //
+
+ //
+ private async void BrowserCustomOpen_Clicked(object sender, EventArgs e)
+ {
+ try
+ {
+ Uri uri = new Uri("https://www.microsoft.com");
+ BrowserLaunchOptions options = new BrowserLaunchOptions()
+ {
+ LaunchMode = BrowserLaunchMode.SystemPreferred,
+ TitleMode = BrowserTitleMode.Show,
+ PreferredToolbarColor = Colors.Violet,
+ PreferredControlColor = Colors.SandyBrown
+ };
+
+ await Browser.Default.OpenAsync(uri, options);
+ }
+ catch (Exception ex)
+ {
+ // An unexpected error occured. No browser may be installed on the device.
+ }
+ }
+ //
+
+ private async void CheckLocationPermission()
+ {
+ try
+ {
+ //
+ PermissionStatus status = await Permissions.CheckStatusAsync();
+ //
+ switch (status)
+ {
+ case PermissionStatus.Unknown:
+ break;
+ case PermissionStatus.Denied:
+ break;
+ case PermissionStatus.Disabled:
+ break;
+ case PermissionStatus.Granted:
+ break;
+ case PermissionStatus.Restricted:
+ break;
+ case PermissionStatus.Limited:
+ break;
+ default:
+ break;
+ }
+ }
+ catch (PermissionException ex)
+ {
+
+ // Permission not declared
+ }
+ }
+
+ private async void RequestLocationPermission()
+ {
+ try
+ {
+ //
+ PermissionStatus status = await Permissions.RequestAsync();
+ //
+ switch (status)
+ {
+ case PermissionStatus.Unknown:
+ break;
+ case PermissionStatus.Denied:
+ break;
+ case PermissionStatus.Disabled:
+ break;
+ case PermissionStatus.Granted:
+ break;
+ case PermissionStatus.Restricted:
+ break;
+ case PermissionStatus.Limited:
+ break;
+ default:
+ break;
+ }
+ }
+ catch (PermissionException ex)
+ {
+
+ // Permission not declared
+ }
+ }
+
+ //
+ public async Task CheckAndRequestLocationPermission()
+ {
+ PermissionStatus status = await Permissions.CheckStatusAsync();
+
+ if (status == PermissionStatus.Granted)
+ return status;
+
+ if (status == PermissionStatus.Denied && DeviceInfo.Platform == DevicePlatform.iOS)
+ {
+ // Prompt the user to turn on in settings
+ // On iOS once a permission has been denied it may not be requested again from the application
+ return status;
+ }
+
+ if (Permissions.ShouldShowRationale())
+ {
+ // Prompt the user with additional information as to why the permission is needed
+ }
+
+ status = await Permissions.RequestAsync();
+
+ return status;
+ }
+ //
+
+ // The following code was removed from the article. I felt like it confused the previous code example without
+ // adding anything much to the concept.
+ //
+ public async Task GetLocationAsync()
+ {
+ PermissionStatus status = await CheckAndRequestPermissionAsync(new Permissions.LocationWhenInUse());
+ if (status != PermissionStatus.Granted)
+ {
+ // Notify user permission was denied
+ return null;
+ }
+
+ return await Geolocation.GetLocationAsync();
+ }
+
+ public async Task CheckAndRequestPermissionAsync(T permission)
+ where T : Permissions.BasePermission
+ {
+ PermissionStatus status = await permission.CheckStatusAsync();
+
+ if (status != PermissionStatus.Granted)
+ status = await permission.RequestAsync();
+
+ return status;
+ }
+ //
+
+ //
+ public class MyPermission : Permissions.BasePermission
+ {
+ // This method checks if current status of the permission.
+ public override Task CheckStatusAsync()
+ {
+ throw new System.NotImplementedException();
+ }
+
+ // This method is optional and a PermissionException is often thrown if a permission is not declared.
+ public override void EnsureDeclared()
+ {
+ throw new System.NotImplementedException();
+ }
+
+ // Requests the user to accept or deny a permission.
+ public override Task RequestAsync()
+ {
+ throw new System.NotImplementedException();
+ }
+
+ // Indicates that the requestor should prompt the user as to why the app requires the permission, because the
+ // user has previously denied this permission.
+ public override bool ShouldShowRationale()
+ {
+ throw new NotImplementedException();
+ }
+ }
+ //
+
+#if ANDROID
+ //
+ public class ReadWriteStoragePerms : Permissions.BasePlatformPermission
+ {
+ public override (string androidPermission, bool isRuntime)[] RequiredPermissions =>
+ new List<(string androidPermission, bool isRuntime)>
+ {
+ (global::Android.Manifest.Permission.ReadExternalStorage, true),
+ (global::Android.Manifest.Permission.WriteExternalStorage, true)
+ }.ToArray();
+ }
+ //
+#elif WINDOWS
+ public class ReadWriteStoragePerms : Permissions.BasePlatformPermission
+ {
+ public override (string androidPermission, bool isRuntime)[] RequiredPermissions =>
+ new List<(string androidPermission, bool isRuntime)>
+ {
+ //(global::Android.Manifest.Permission.ReadExternalStorage, true),
+ //(global::Android.Manifest.Permission.WriteExternalStorage, true)
+ }.ToArray();
+ }
+#endif
+
+ private async void RequestCustomPermission()
+ {
+ try
+ {
+ //
+ PermissionStatus status = await Permissions.RequestAsync();
+ //
+ switch (status)
+ {
+ case PermissionStatus.Unknown:
+ break;
+ case PermissionStatus.Denied:
+ break;
+ case PermissionStatus.Disabled:
+ break;
+ case PermissionStatus.Granted:
+ break;
+ case PermissionStatus.Restricted:
+ break;
+ case PermissionStatus.Limited:
+ break;
+ default:
+ break;
+ }
+ }
+ catch (PermissionException ex)
+ {
+
+ // Permission not declared
+ }
+ }
+
+
+ //
+ private void ReadVersion_Clicked(object sender, EventArgs e)
+ {
+ labelIsFirst.Text = VersionTracking.IsFirstLaunchEver.ToString();
+ labelCurrentVersionIsFirst.Text = VersionTracking.IsFirstLaunchForCurrentVersion.ToString();
+ labelCurrentBuildIsFirst.Text = VersionTracking.IsFirstLaunchForCurrentBuild.ToString();
+ labelCurrentVersion.Text = VersionTracking.CurrentVersion.ToString();
+ labelCurrentBuild.Text = VersionTracking.CurrentBuild.ToString();
+ labelFirstInstalledVer.Text = VersionTracking.FirstInstalledVersion.ToString();
+ labelFirstInstalledBuild.Text = VersionTracking.FirstInstalledBuild.ToString();
+ labelVersionHistory.Text = String.Join(',', VersionTracking.VersionHistory);
+ labelBuildHistory.Text = String.Join(',', VersionTracking.BuildHistory);
+
+ // These two properties may be null if this is the first version
+ labelPreviousVersion.Text = VersionTracking.PreviousVersion?.ToString() ?? "none";
+ labelPreviousBuild.Text = VersionTracking.PreviousBuild?.ToString() ?? "none";
+ }
+ //
+
+}
\ No newline at end of file
diff --git a/docs/platform-integration/snippets/shared_1/AppShell.xaml b/docs/platform-integration/snippets/shared_1/AppShell.xaml
new file mode 100644
index 000000000..6067d8c7f
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_1/AppShell.xaml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
diff --git a/docs/platform-integration/snippets/shared_1/AppShell.xaml.cs b/docs/platform-integration/snippets/shared_1/AppShell.xaml.cs
new file mode 100644
index 000000000..820bb28cd
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_1/AppShell.xaml.cs
@@ -0,0 +1,9 @@
+namespace PlatformIntegration;
+
+public partial class AppShell : Shell
+{
+ public AppShell()
+ {
+ InitializeComponent();
+ }
+}
diff --git a/docs/platform-integration/snippets/shared_1/BatteryTestPage.xaml b/docs/platform-integration/snippets/shared_1/BatteryTestPage.xaml
new file mode 100644
index 000000000..5f60907c1
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_1/BatteryTestPage.xaml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/platform-integration/snippets/shared_1/BatteryTestPage.xaml.cs b/docs/platform-integration/snippets/shared_1/BatteryTestPage.xaml.cs
new file mode 100644
index 000000000..1f7e5ab81
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_1/BatteryTestPage.xaml.cs
@@ -0,0 +1,89 @@
+namespace PlatformIntegration;
+
+public partial class BatteryTestPage : ContentPage
+{
+ public BatteryTestPage()
+ {
+ InitializeComponent();
+ }
+
+ //
+ private void BatterySwitch_Toggled(object sender, ToggledEventArgs e) =>
+ WatchBattery(Battery.Default);
+
+ private bool _isBatteryWatched;
+
+ private void WatchBattery(IBattery battery)
+ {
+
+ if (!_isBatteryWatched)
+ {
+ battery.BatteryInfoChanged += Battery_BatteryInfoChanged;
+ }
+ else
+ {
+ battery.BatteryInfoChanged -= Battery_BatteryInfoChanged;
+ }
+
+ _isBatteryWatched = !_isBatteryWatched;
+ }
+
+ private void Battery_BatteryInfoChanged(object sender, BatteryInfoChangedEventArgs e)
+ {
+ BatteryStateLabel.Text = e.State switch
+ {
+ BatteryState.Charging => "Battery is currently charging",
+ BatteryState.Discharging => "Charger is not connected and the battery is discharging",
+ BatteryState.Full => "Battery is full",
+ BatteryState.NotCharging => "The battery isn't charging.",
+ BatteryState.NotPresent => "Battery is not available.",
+ BatteryState.Unknown => "Battery is unknown",
+ _ => "Battery is unknown"
+ };
+
+ BatteryLevelLabel.Text = $"Battery is {e.ChargeLevel * 100}% charged.";
+ }
+
+ //
+
+ //
+ private bool _isBatteryLow = false;
+
+ private void BatterySaverSwitch_Toggled(object sender, ToggledEventArgs e)
+ {
+ // Capture the initial state of the battery
+ _isBatteryLow = Battery.Default.EnergySaverStatus == EnergySaverStatus.On;
+ BatterySaverLabel.Text = _isBatteryLow.ToString();
+
+ // Watch for any changes to the battery saver mode
+ Battery.Default.EnergySaverStatusChanged += Battery_EnergySaverStatusChanged;
+ }
+
+ private void Battery_EnergySaverStatusChanged(object sender, EnergySaverStatusChangedEventArgs e)
+ {
+ // Update the variable based on the state
+ _isBatteryLow = Battery.Default.EnergySaverStatus == EnergySaverStatus.On;
+ BatterySaverLabel.Text = _isBatteryLow.ToString();
+ }
+ //
+
+
+ private void BatSourceButton_Clicked(object sender, EventArgs e)
+ {
+ SetChargeModeLabel();
+ }
+
+ //
+ private void SetChargeModeLabel()
+ {
+ BatteryPowerSourceLabel.Text = Battery.Default.PowerSource switch
+ {
+ BatteryPowerSource.Wireless => "Wireless charging",
+ BatteryPowerSource.Usb => "USB cable charging",
+ BatteryPowerSource.AC => "Device is plugged in to a power source",
+ BatteryPowerSource.Battery => "Device isn't charging",
+ _ => "Unknown"
+ };
+ }
+ //
+}
\ No newline at end of file
diff --git a/docs/platform-integration/snippets/shared_1/CommsPage.xaml b/docs/platform-integration/snippets/shared_1/CommsPage.xaml
new file mode 100644
index 000000000..d226a84c8
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_1/CommsPage.xaml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/platform-integration/snippets/shared_1/CommsPage.xaml.cs b/docs/platform-integration/snippets/shared_1/CommsPage.xaml.cs
new file mode 100644
index 000000000..656270602
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_1/CommsPage.xaml.cs
@@ -0,0 +1,123 @@
+namespace PlatformIntegration;
+
+public partial class CommsPage : ContentPage
+{
+ public CommsPage()
+ {
+ InitializeComponent();
+ }
+
+ //
+ private async void SelectContactButton_Clicked(object sender, EventArgs e)
+ {
+ try
+ {
+ var contact = await Contacts.Default.PickContactAsync();
+
+ if (contact == null)
+ return;
+
+ string id = contact.Id;
+ string namePrefix = contact.NamePrefix;
+ string givenName = contact.GivenName;
+ string middleName = contact.MiddleName;
+ string familyName = contact.FamilyName;
+ string nameSuffix = contact.NameSuffix;
+ string displayName = contact.DisplayName;
+ List phones = contact.Phones; // List of phone numbers
+ List emails = contact.Emails; // List of email addresses
+ }
+ catch (Exception ex)
+ {
+ // Most likely permission denied
+ }
+ }
+ //
+
+ //
+ public async IAsyncEnumerable GetContactNames()
+ {
+ var contacts = await Contacts.GetAllAsync();
+
+ // No contacts
+ if (contacts == null)
+ yield break;
+
+ foreach (var contact in contacts)
+ yield return contact.DisplayName;
+ }
+ //
+
+ private async void SendEmailButton_Clicked(object sender, EventArgs e)
+ {
+ //
+ if (Email.Default.IsComposeSupported)
+ {
+
+ string subject = "Hello friends!";
+ string body = "It was great to see you last weekend.";
+ string[] recipients = new[] { "john@contoso.com", "jane@contoso.com" };
+
+ var message = new EmailMessage
+ {
+ Subject = subject,
+ Body = body,
+ BodyFormat = EmailBodyFormat.PlainText,
+ To = new List(recipients)
+ };
+
+ await Email.Default.ComposeAsync(message);
+ }
+ //
+ }
+
+ private async void SendEmailPictureButton_Clicked(object sender, EventArgs e)
+ {
+ //
+ if (Email.Default.IsComposeSupported)
+ {
+
+ string subject = "Hello friends!";
+ string body = "It was great to see you last weekend. I've attached a photo of our adventures together.";
+ string[] recipients = new[] { "john@contoso.com", "jane@contoso.com" };
+
+ var message = new EmailMessage
+ {
+ Subject = subject,
+ Body = body,
+ BodyFormat = EmailBodyFormat.PlainText,
+ To = new List(recipients)
+ };
+
+ string picturePath = Path.Combine(FileSystem.CacheDirectory, "memories.jpg");
+
+ message.Attachments.Add(new EmailAttachment(picturePath));
+
+ await Email.Default.ComposeAsync(message);
+ }
+ //
+ }
+
+ private async void SendSmsButton_Clicked(object sender, EventArgs e)
+ {
+ //
+ if (Sms.Default.IsComposeSupported)
+ {
+ string[] recipients = new[] { "000-000-0000" };
+ string text = "Hello, I'm interested in buying your vase.";
+
+ var message = new SmsMessage(text, recipients);
+
+ await Sms.Default.ComposeAsync(message);
+ }
+ //
+ }
+
+ private void DialPhoneButton_Clicked(object sender, EventArgs e)
+ {
+ //
+ if (PhoneDialer.Default.IsSupported)
+ PhoneDialer.Default.Open("000-000-0000");
+ //
+ }
+}
\ No newline at end of file
diff --git a/docs/platform-integration/snippets/shared_1/DataPage.xaml b/docs/platform-integration/snippets/shared_1/DataPage.xaml
new file mode 100644
index 000000000..ff5d095c1
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_1/DataPage.xaml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/platform-integration/snippets/shared_1/DataPage.xaml.cs b/docs/platform-integration/snippets/shared_1/DataPage.xaml.cs
new file mode 100644
index 000000000..939bc497d
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_1/DataPage.xaml.cs
@@ -0,0 +1,104 @@
+namespace PlatformIntegration;
+
+public partial class DataPage : ContentPage
+{
+ public DataPage()
+ {
+ InitializeComponent();
+ }
+
+ //
+ private async void SetClipboardButton_Clicked(object sender, EventArgs e) =>
+ await Clipboard.Default.SetTextAsync("This text was highlighted in the UI.");
+ //
+
+ //
+ private async void ReadClipboardButton_Clicked(object sender, EventArgs e)
+ {
+ if (Clipboard.Default.HasText)
+ {
+ ClipboardOutputLabel.Text = await Clipboard.Default.GetTextAsync();
+ await ClearClipboard();
+ }
+ else
+ ClipboardOutputLabel.Text = "Clipboard is empty";
+ }
+
+ //
+ private async Task ClearClipboard() =>
+ await Clipboard.Default.SetTextAsync(null);
+ //
+ //
+
+ //
+ private void ContentPage_Loaded(object sender, EventArgs e)
+ {
+ Clipboard.Default.ClipboardContentChanged += Clipboard_ClipboardContentChanged;
+ }
+
+ private async void Clipboard_ClipboardContentChanged(object sender, EventArgs e)
+ {
+ ClipboardOutputLabel.Text = await Clipboard.Default.GetTextAsync();
+ }
+ //
+
+ private async void ShareButton_Clicked(object sender, EventArgs e)
+ {
+ await ShareText("Hello, welcome to the show.", Share.Default);
+ await ShareUri("http://www.microsoft.com", Share.Default);
+ await ShareFile(Share.Default);
+ await ShareMultipleFiles(Share.Default);
+ }
+ //
+ public async Task ShareText(string text, IShare share)
+ {
+ await share.RequestAsync(new ShareTextRequest
+ {
+ Text = text,
+ Title = "Share Text"
+ });
+ }
+
+ public async Task ShareUri(string uri, IShare share)
+ {
+ await share.RequestAsync(new ShareTextRequest
+ {
+ Uri = uri,
+ Title = "Share Web Link"
+ });
+ }
+ //
+
+ //
+ public async Task ShareFile(IShare share)
+ {
+ string fn = "Attachment.txt";
+ string file = Path.Combine(FileSystem.CacheDirectory, fn);
+
+ File.WriteAllText(file, "Hello World");
+
+ await share.RequestAsync(new ShareFileRequest
+ {
+ Title = "Share text file",
+ File = new ShareFile(file)
+ });
+ }
+ //
+
+ //
+ public async Task ShareMultipleFiles(IShare share)
+ {
+ string file1 = Path.Combine(FileSystem.CacheDirectory, "Attachment1.txt");
+ string file2 = Path.Combine(FileSystem.CacheDirectory, "Attachment2.txt");
+
+ File.WriteAllText(file1, "Content 1");
+ File.WriteAllText(file2, "Content 2");
+
+ await share.RequestAsync(new ShareMultipleFilesRequest
+ {
+ Title = "Share multiple files",
+ Files = new List { new ShareFile(file1), new ShareFile(file2) }
+ });
+ }
+ //
+}
\ No newline at end of file
diff --git a/docs/platform-integration/snippets/shared_1/DeviceDetailsPage.xaml b/docs/platform-integration/snippets/shared_1/DeviceDetailsPage.xaml
new file mode 100644
index 000000000..62a1eb49d
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_1/DeviceDetailsPage.xaml
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/platform-integration/snippets/shared_1/DeviceDetailsPage.xaml.cs b/docs/platform-integration/snippets/shared_1/DeviceDetailsPage.xaml.cs
new file mode 100644
index 000000000..31024dea8
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_1/DeviceDetailsPage.xaml.cs
@@ -0,0 +1,128 @@
+namespace PlatformIntegration;
+
+public partial class DeviceDetailsPage : ContentPage
+{
+ public DeviceDetailsPage()
+ {
+ InitializeComponent();
+ }
+
+ private void ContentPage_Loaded(object sender, EventArgs e)
+ {
+ ReadDeviceDisplay();
+ ReadDeviceInfo();
+ }
+
+ //
+ private void ReadDeviceDisplay()
+ {
+ System.Text.StringBuilder sb = new System.Text.StringBuilder();
+
+ sb.AppendLine($"Pixel width: {DeviceDisplay.Current.MainDisplayInfo.Width} / Pixel Height: {DeviceDisplay.Current.MainDisplayInfo.Height}");
+ sb.AppendLine($"Density: {DeviceDisplay.Current.MainDisplayInfo.Density}");
+ sb.AppendLine($"Orientation: {DeviceDisplay.Current.MainDisplayInfo.Orientation}");
+ sb.AppendLine($"Rotation: {DeviceDisplay.Current.MainDisplayInfo.Rotation}");
+ sb.AppendLine($"Refresh Rate: {DeviceDisplay.Current.MainDisplayInfo.RefreshRate}");
+
+ DisplayDetailsLabel.Text = sb.ToString();
+ }
+ //
+
+ //
+ private void ReadDeviceInfo()
+ {
+ System.Text.StringBuilder sb = new System.Text.StringBuilder();
+
+ sb.AppendLine($"Model: {DeviceInfo.Current.Model}");
+ sb.AppendLine($"Manufacturer: {DeviceInfo.Current.Manufacturer}");
+ sb.AppendLine($"Name: {DeviceInfo.Name}");
+ sb.AppendLine($"OS Version: {DeviceInfo.VersionString}");
+ sb.AppendLine($"Refresh Rate: {DeviceInfo.Current}");
+ sb.AppendLine($"Idiom: {DeviceInfo.Current.Idiom}");
+ sb.AppendLine($"Platform: {DeviceInfo.Current.Platform}");
+
+ //
+ bool isVirtual = DeviceInfo.Current.DeviceType switch
+ {
+ DeviceType.Physical => false,
+ DeviceType.Virtual => true,
+ _ => false
+ };
+ //
+
+ sb.AppendLine($"Virtual device? {isVirtual}");
+
+ DisplayDeviceLabel.Text = sb.ToString();
+ }
+ //
+
+ //
+ private bool IsAndroid() =>
+ DeviceInfo.Current.Platform == DevicePlatform.Android;
+ //
+
+
+ //
+ private void AlwaysOnSwitch_Toggled(object sender, ToggledEventArgs e) =>
+ DeviceDisplay.Current.KeepScreenOn = AlwaysOnSwitch.IsToggled;
+ //
+
+ //
+ private void PrintIdiom()
+ {
+ if (DeviceInfo.Current.Idiom == DeviceIdiom.Desktop)
+ Console.WriteLine("The current device is a desktop");
+ else if (DeviceInfo.Current.Idiom == DeviceIdiom.Phone)
+ Console.WriteLine("The current device is a phone");
+ else if (DeviceInfo.Current.Idiom == DeviceIdiom.Tablet)
+ Console.WriteLine("The current device is a Tablet");
+ }
+ //
+
+ //
+ private async void FlashlightSwitch_Toggled(object sender, ToggledEventArgs e)
+ {
+ try
+ {
+ if (FlashlightSwitch.IsToggled)
+ await Flashlight.Default.TurnOnAsync();
+ else
+ await Flashlight.Default.TurnOffAsync();
+ }
+ catch (FeatureNotSupportedException ex)
+ {
+ // Handle not supported on device exception
+ }
+ catch (PermissionException ex)
+ {
+ // Handle permission exception
+ }
+ catch (Exception ex)
+ {
+ // Unable to turn on/off flashlight
+ }
+ }
+ //
+
+ //
+ private void HapticShortButton_Clicked(object sender, EventArgs e) =>
+ HapticFeedback.Current.Perform(HapticFeedbackType.Click);
+
+ private void HapticLongButton_Clicked(object sender, EventArgs e) =>
+ HapticFeedback.Current.Perform(HapticFeedbackType.LongPress);
+ //
+
+
+ //
+ private void VibrateStartButton_Clicked(object sender, EventArgs e)
+ {
+ int secondsToVibrate = Random.Shared.Next(1, 7);
+ TimeSpan vibrationLength = TimeSpan.FromSeconds(secondsToVibrate);
+
+ Vibration.Default.Vibrate(vibrationLength);
+ }
+
+ private void VibrateStopButton_Clicked(object sender, EventArgs e) =>
+ Vibration.Default.Cancel();
+ //
+}
\ No newline at end of file
diff --git a/docs/platform-integration/snippets/shared_1/Features/BrowserTest.cs b/docs/platform-integration/snippets/shared_1/Features/BrowserTest.cs
new file mode 100644
index 000000000..e72e17a51
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_1/Features/BrowserTest.cs
@@ -0,0 +1,24 @@
+using Microsoft.Maui.Controls.PlatformConfiguration;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace PlatformIntegration.Features
+{
+ public class BrowserTest
+ {
+ public async Task OpenBrowser(Uri uri)
+ {
+ try
+ {
+ await Browser.OpenAsync(uri, BrowserLaunchMode.SystemPreferred);
+ }
+ catch (Exception ex)
+ {
+ // An unexpected error occured. No browser may be installed on the device.
+ }
+ }
+ }
+}
diff --git a/docs/platform-integration/snippets/shared_1/Features/ClipboardTest.cs b/docs/platform-integration/snippets/shared_1/Features/ClipboardTest.cs
new file mode 100644
index 000000000..4555a8874
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_1/Features/ClipboardTest.cs
@@ -0,0 +1,34 @@
+using Microsoft.Maui.Controls.PlatformConfiguration;
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace PlatformIntegration.Features
+{
+ public class ClipboardTest
+ {
+ public ClipboardTest()
+ {
+ // Register for clipboard changes, be sure to unsubscribe when needed
+ Clipboard.ClipboardContentChanged += OnClipboardContentChanged;
+ }
+
+ void OnClipboardContentChanged(object sender, EventArgs e)
+ {
+ Console.WriteLine($"Last clipboard change at {DateTime.UtcNow:T}");
+ }
+
+ public async void SetText()
+ {
+ await Clipboard.SetTextAsync("Hello World");
+ }
+
+ public async void GetText()
+ {
+ string text = await Clipboard.GetTextAsync();
+ }
+ }
+}
diff --git a/docs/platform-integration/snippets/shared_1/Features/ConnectivityTest.cs b/docs/platform-integration/snippets/shared_1/Features/ConnectivityTest.cs
new file mode 100644
index 000000000..f5b18940c
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_1/Features/ConnectivityTest.cs
@@ -0,0 +1,54 @@
+using Microsoft.Maui.Controls.PlatformConfiguration;
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace PlatformIntegration.Features
+{
+ public class ConnectivityTest
+ {
+ public ConnectivityTest()
+ {
+ // Register for connectivity changes, be sure to unsubscribe when finished
+ Connectivity.ConnectivityChanged += Connectivity_ConnectivityChanged;
+ }
+
+ void Connectivity_ConnectivityChanged(object sender, ConnectivityChangedEventArgs e)
+ {
+ if (e.NetworkAccess == NetworkAccess.ConstrainedInternet)
+ Console.WriteLine("Internet access is available but is limited.");
+
+ else if (e.NetworkAccess != NetworkAccess.Internet)
+ Console.WriteLine("Internet access has been lost.");
+
+ // Log each active connection
+ Console.Write("Connections active: ");
+
+ foreach (var item in e.ConnectionProfiles)
+ {
+ switch (item)
+ {
+ case ConnectionProfile.Bluetooth:
+ Console.Write("Bluetooth");
+ break;
+ case ConnectionProfile.Cellular:
+ Console.Write("Cell");
+ break;
+ case ConnectionProfile.Ethernet:
+ Console.Write("Ethernet");
+ break;
+ case ConnectionProfile.WiFi:
+ Console.Write("WiFi");
+ break;
+ default:
+ break;
+ }
+ }
+
+ Console.WriteLine();
+ }
+ }
+}
diff --git a/docs/platform-integration/snippets/shared_1/Features/ContactsTest.cs b/docs/platform-integration/snippets/shared_1/Features/ContactsTest.cs
new file mode 100644
index 000000000..3883f488e
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_1/Features/ContactsTest.cs
@@ -0,0 +1,68 @@
+using Microsoft.Maui.Controls.PlatformConfiguration;
+
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Linq;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace PlatformIntegration.Features
+{
+ public class ContactsTest
+ {
+ public ContactsTest()
+ {
+ }
+
+ public async void PickContact()
+ {
+ try
+ {
+ // TODO: Wont compile
+ //var contact = await Contacts.PickContactAsync();
+
+ //if (contact == null)
+ // return;
+
+ //var id = contact.Id;
+ //var namePrefix = contact.NamePrefix;
+ //var givenName = contact.GivenName;
+ //var middleName = contact.MiddleName;
+ //var familyName = contact.FamilyName;
+ //var nameSuffix = contact.NameSuffix;
+ //var displayName = contact.DisplayName;
+ //var phones = contact.Phones; // List of phone numbers
+ //var emails = contact.Emails; // List of email addresses
+ }
+ catch (Exception ex)
+ {
+ // Handle exception here.
+ }
+ }
+
+ public async void GetAllContacts()
+ {
+ ObservableCollection contactsCollect = new ObservableCollection();
+
+ try
+ {
+ // TODO: Wont compile
+ //// cancellationToken parameter is optional
+ //var cancellationToken = default(CancellationToken);
+ //var contacts = await Contacts.GetAllAsync(cancellationToken);
+
+ //if (contacts == null)
+ // return;
+
+ //foreach (var contact in contacts)
+ // contactsCollect.Add(contact);
+ }
+ catch (Exception ex)
+ {
+ // Handle exception here.
+ }
+ }
+ }
+}
diff --git a/docs/platform-integration/snippets/shared_1/Features/DeviceInfoTest.cs b/docs/platform-integration/snippets/shared_1/Features/DeviceInfoTest.cs
new file mode 100644
index 000000000..106a21434
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_1/Features/DeviceInfoTest.cs
@@ -0,0 +1,58 @@
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace PlatformIntegration.Features
+{
+ public class DeviceInfoTest
+ {
+ public void ReadInfo()
+ {
+ // Device Model (SMG-950U, iPhone10,6)
+ string device = DeviceInfo.Model;
+
+ // Manufacturer (Samsung)
+ string manufacturer = DeviceInfo.Manufacturer;
+
+ // Device Name (Motz's iPhone)
+ string deviceName = DeviceInfo.Name;
+
+ // Operating System Version Number (7.0)
+ string version = DeviceInfo.VersionString;
+
+ // Platform (Android)
+ DevicePlatform platform = DeviceInfo.Platform;
+
+ // Idiom (Phone)
+ DeviceIdiom idiom = DeviceInfo.Idiom;
+
+ // Device Type (Physical)
+ DeviceType deviceType = DeviceInfo.DeviceType;
+ }
+
+ public void WritePlatform()
+ {
+ if (DeviceInfo.Platform == DevicePlatform.Android)
+ Console.WriteLine("The current OS is Android");
+ else if (DeviceInfo.Platform == DevicePlatform.iOS)
+ Console.WriteLine("The current OS is iOS");
+ else if (DeviceInfo.Platform == DevicePlatform.WinUI)
+ Console.WriteLine("The current OS is Windows");
+ else if (DeviceInfo.Platform == DevicePlatform.Tizen)
+ Console.WriteLine("The current OS is Tizen");
+ }
+
+ public void WriteIdiom()
+ {
+ if (DeviceInfo.Idiom == DeviceIdiom.Desktop)
+ Console.WriteLine("The current device is a desktop");
+ else if (DeviceInfo.Idiom == DeviceIdiom.Phone)
+ Console.WriteLine("The current device is a phone");
+ else if (DeviceInfo.Idiom == DeviceIdiom.Tablet)
+ Console.WriteLine("The current device is a Tablet");
+ }
+ }
+}
diff --git a/docs/platform-integration/snippets/shared_1/Features/DisplayInfoTest.cs b/docs/platform-integration/snippets/shared_1/Features/DisplayInfoTest.cs
new file mode 100644
index 000000000..e4c52d7d2
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_1/Features/DisplayInfoTest.cs
@@ -0,0 +1,53 @@
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace PlatformIntegration.Features
+{
+ public partial class DisplayInfoTest
+ {
+ public DisplayInfoTest()
+ {
+ // Subscribe to changes of screen metrics
+ DeviceDisplay.MainDisplayInfoChanged += OnMainDisplayInfoChanged;
+ }
+
+ void OnMainDisplayInfoChanged(object sender, DisplayInfoChangedEventArgs e)
+ {
+ // Process changes
+ DisplayInfo displayInfo = e.DisplayInfo;
+ }
+ }
+
+ public partial class DisplayInfoTest
+ {
+ public void ReadInfo()
+ {
+ // Get Metrics
+ DisplayInfo mainDisplayInfo = DeviceDisplay.MainDisplayInfo;
+
+ // Orientation (Landscape, Portrait, Square, Unknown)
+ DisplayOrientation orientation = mainDisplayInfo.Orientation;
+
+ // Rotation (0, 90, 180, 270)
+ DisplayRotation rotation = mainDisplayInfo.Rotation;
+
+ // Width (in pixels)
+ double width = mainDisplayInfo.Width;
+
+ // Height (in pixels)
+ double height = mainDisplayInfo.Height;
+
+ // Screen density
+ double density = mainDisplayInfo.Density;
+ }
+
+ public void ToggleScreenLock()
+ {
+ DeviceDisplay.KeepScreenOn = !DeviceDisplay.KeepScreenOn;
+ }
+ }
+}
diff --git a/docs/platform-integration/snippets/shared_1/Features/FIlePickerTest.cs b/docs/platform-integration/snippets/shared_1/Features/FIlePickerTest.cs
new file mode 100644
index 000000000..9fcf330d1
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_1/Features/FIlePickerTest.cs
@@ -0,0 +1,57 @@
+using Microsoft.Maui.Controls;
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using static System.Net.Mime.MediaTypeNames;
+
+namespace PlatformIntegration.Features
+{
+ class FilePickerTest
+ {
+ async Task PickAndShow(PickOptions options)
+ {
+ try
+ {
+ var result = await FilePicker.PickAsync(options);
+ if (result != null)
+ {
+ if (result.FileName.EndsWith("jpg", StringComparison.OrdinalIgnoreCase) ||
+ result.FileName.EndsWith("png", StringComparison.OrdinalIgnoreCase))
+ {
+ using var stream = await result.OpenReadAsync();
+ var image = ImageSource.FromStream(() => stream);
+ }
+ }
+
+ return result;
+ }
+ catch (Exception ex)
+ {
+ // The user canceled or something went wrong
+ }
+
+ return null;
+ }
+
+ public void DoStuff()
+ {
+ var customFileType = new FilePickerFileType(new Dictionary>
+ {
+ { DevicePlatform.iOS, new[] { "public.my.comic.extension" } }, // or general UTType values
+ { DevicePlatform.Android, new[] { "application/comics" } },
+ { DevicePlatform.WinUI, new[] { ".cbr", ".cbz" } },
+ { DevicePlatform.Tizen, new[] { "*/*" } },
+ { DevicePlatform.macOS, new[] { "cbr", "cbz" } }, // or general UTType values
+ });
+
+ PickOptions options = new()
+ {
+ PickerTitle = "Please select a comic file",
+ FileTypes = customFileType,
+ };
+ }
+ }
+}
diff --git a/docs/platform-integration/snippets/shared_1/Features/FileHelpersTest.cs b/docs/platform-integration/snippets/shared_1/Features/FileHelpersTest.cs
new file mode 100644
index 000000000..ab4ee5c6b
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_1/Features/FileHelpersTest.cs
@@ -0,0 +1,23 @@
+using System.IO;
+using System.Threading.Tasks;
+
+
+namespace PlatformIntegration.Features
+{
+ class FileHelpersTest
+ {
+ public async Task ReadTextFile(string filePath)
+ {
+ using Stream fileStream = await FileSystem.OpenAppPackageFileAsync(filePath);
+ using StreamReader reader = new StreamReader(fileStream);
+
+ return await reader.ReadToEndAsync();
+ }
+
+ public void GetFiles()
+ {
+ string cacheDir = FileSystem.CacheDirectory;
+ string mainDir = FileSystem.AppDataDirectory;
+ }
+ }
+}
diff --git a/docs/platform-integration/snippets/shared_1/Features/FlashlightTest.cs b/docs/platform-integration/snippets/shared_1/Features/FlashlightTest.cs
new file mode 100644
index 000000000..d2acfcfc6
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_1/Features/FlashlightTest.cs
@@ -0,0 +1,37 @@
+using System;
+using System.IO;
+using System.Threading.Tasks;
+
+
+namespace PlatformIntegration.Features
+{
+ class FlashlightTest
+ {
+ public async Task RunFlashlight()
+ {
+ try
+ {
+ // Turn On
+ await Flashlight.TurnOnAsync();
+
+ // Pause for 3 seconds
+ await Task.Delay(TimeSpan.FromSeconds(3));
+
+ // Turn Off
+ await Flashlight.TurnOffAsync();
+ }
+ catch (FeatureNotSupportedException fnsEx)
+ {
+ // Handle not supported on device exception
+ }
+ catch (PermissionException pEx)
+ {
+ // Handle permission exception
+ }
+ catch (Exception ex)
+ {
+ // Unable to turn on/off flashlight
+ }
+ }
+ }
+}
diff --git a/docs/platform-integration/snippets/shared_1/Features/GeocodingTest.cs b/docs/platform-integration/snippets/shared_1/Features/GeocodingTest.cs
new file mode 100644
index 000000000..8d24b46e3
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_1/Features/GeocodingTest.cs
@@ -0,0 +1,71 @@
+using System;
+using System.IO;
+using System.Linq;
+using System.Threading.Tasks;
+
+
+namespace PlatformIntegration.Features
+{
+ class GeocodingTest
+ {
+ public async Task TranslateLocation()
+ {
+ try
+ {
+ var address = "Microsoft Building 25 Redmond WA USA";
+ var locations = await Geocoding.GetLocationsAsync(address);
+
+ var location = locations?.FirstOrDefault();
+
+ if (location != null)
+ Console.WriteLine($"Latitude: {location.Latitude}, Longitude: {location.Longitude}, Altitude: {location.Altitude}");
+ }
+ catch (FeatureNotSupportedException fnsEx)
+ {
+ // Feature not supported on device
+ }
+ catch (Exception ex)
+ {
+ // Handle exception that may have occurred in geocoding
+ }
+ }
+
+ public async Task TranslateCoordinates()
+ {
+ try
+ {
+ var lat = 47.673988;
+ var lon = -122.121513;
+
+ var placemarks = await Geocoding.GetPlacemarksAsync(lat, lon);
+
+ var placemark = placemarks?.FirstOrDefault();
+
+ if (placemark != null)
+ {
+ var geocodeAddress =
+ $"AdminArea: {placemark.AdminArea}\n" +
+ $"CountryCode: {placemark.CountryCode}\n" +
+ $"CountryName: {placemark.CountryName}\n" +
+ $"FeatureName: {placemark.FeatureName}\n" +
+ $"Locality: {placemark.Locality}\n" +
+ $"PostalCode: {placemark.PostalCode}\n" +
+ $"SubAdminArea: {placemark.SubAdminArea}\n" +
+ $"SubLocality: {placemark.SubLocality}\n" +
+ $"SubThoroughfare: {placemark.SubThoroughfare}\n" +
+ $"Thoroughfare: {placemark.Thoroughfare}\n";
+
+ Console.WriteLine(geocodeAddress);
+ }
+ }
+ catch (FeatureNotSupportedException fnsEx)
+ {
+ // Feature not supported on device
+ }
+ catch (Exception ex)
+ {
+ // Handle exception that may have occurred in geocoding
+ }
+ }
+ }
+}
diff --git a/docs/platform-integration/snippets/shared_1/Features/GeolocationTest.cs b/docs/platform-integration/snippets/shared_1/Features/GeolocationTest.cs
new file mode 100644
index 000000000..75034a8f7
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_1/Features/GeolocationTest.cs
@@ -0,0 +1,103 @@
+using System;
+using System.IO;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+
+
+namespace PlatformIntegration.Features
+{
+ class GeolocationTest
+ {
+ public async Task GetCachedLocation()
+ {
+ try
+ {
+ Location location = await Geolocation.GetLastKnownLocationAsync();
+
+ if (location != null)
+ Console.WriteLine($"Latitude: {location.Latitude}, Longitude: {location.Longitude}, Altitude: {location.Altitude}");
+ }
+ catch (FeatureNotSupportedException fnsEx)
+ {
+ // Handle not supported on device exception
+ }
+ catch (FeatureNotEnabledException fneEx)
+ {
+ // Handle not enabled on device exception
+ }
+ catch (PermissionException pEx)
+ {
+ // Handle permission exception
+ }
+ catch (Exception ex)
+ {
+ // Unable to get location
+ }
+ }
+
+ private CancellationTokenSource _cancelTokenSource;
+ private bool _isCheckingLocation;
+
+ public async Task GetCurrentLocation()
+ {
+ try
+ {
+ _isCheckingLocation = true;
+
+ var request = new GeolocationRequest(GeolocationAccuracy.Medium, TimeSpan.FromSeconds(10));
+
+ _cancelTokenSource = new CancellationTokenSource();
+
+ Location location = await Geolocation.GetLocationAsync(request, _cancelTokenSource.Token);
+
+ if (location != null)
+ Console.WriteLine($"Latitude: {location.Latitude}, Longitude: {location.Longitude}, Altitude: {location.Altitude}");
+ }
+ catch (FeatureNotSupportedException fnsEx)
+ {
+ // Handle not supported on device exception
+ }
+ catch (FeatureNotEnabledException fneEx)
+ {
+ // Handle not enabled on device exception
+ }
+ catch (PermissionException pEx)
+ {
+ // Handle permission exception
+ }
+ catch (Exception ex)
+ {
+ // Unable to get location
+ }
+ finally
+ {
+ _isCheckingLocation = false;
+ }
+ }
+
+ public void CancelRequest()
+ {
+ if (_cancelTokenSource != null && _cancelTokenSource.IsCancellationRequested == false)
+ _cancelTokenSource.Cancel();
+ }
+
+ public async Task CheckMock()
+ {
+ var request = new GeolocationRequest(GeolocationAccuracy.Medium);
+ Location location = await Geolocation.GetLocationAsync(request);
+
+ if (location != null && location.IsFromMockProvider)
+ {
+ // location is from a mock provider
+ }
+ }
+
+ public void Distance()
+ {
+ Location boston = new Location(42.358056, -71.063611);
+ Location sanFrancisco = new Location(37.783333, -122.416667);
+ double miles = Location.CalculateDistance(boston, sanFrancisco, DistanceUnits.Miles);
+ }
+ }
+}
diff --git a/docs/platform-integration/snippets/shared_1/Features/HapticTest.cs b/docs/platform-integration/snippets/shared_1/Features/HapticTest.cs
new file mode 100644
index 000000000..3fb1a7e9f
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_1/Features/HapticTest.cs
@@ -0,0 +1,32 @@
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace PlatformIntegration.Features
+{
+ public class HapticTest
+ {
+ public void TriggerHapticFeedback()
+ {
+ try
+ {
+ // Perform click feedback
+ HapticFeedback.Perform(HapticFeedbackType.Click);
+
+ // Or use long press
+ HapticFeedback.Perform(HapticFeedbackType.LongPress);
+ }
+ catch (FeatureNotSupportedException ex)
+ {
+ // Feature not supported on device
+ }
+ catch (Exception ex)
+ {
+ // Other error has occurred.
+ }
+ }
+ }
+}
diff --git a/docs/platform-integration/snippets/shared_1/Features/LauncherTest.cs b/docs/platform-integration/snippets/shared_1/Features/LauncherTest.cs
new file mode 100644
index 000000000..1f131b223
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_1/Features/LauncherTest.cs
@@ -0,0 +1,34 @@
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace PlatformIntegration.Features
+{
+ public class LauncherTest
+ {
+ public async Task OpenRideShareAsync()
+ {
+ var supportsUri = await Launcher.CanOpenAsync("lyft://");
+
+ if (supportsUri)
+ await Launcher.OpenAsync("lyft://ridetype?id=lyft_line");
+ }
+
+ public async Task TryOpenRideShareAsync() =>
+ await Launcher.TryOpenAsync("lyft://ridetype?id=lyft_line");
+
+ public async Task OpenTextFile()
+ {
+ string popoverTitle = "Read text file";
+ string name = "File.txt";
+ string file = System.IO.Path.Combine(FileSystem.CacheDirectory, name);
+
+ System.IO.File.WriteAllText(file, "Hello World");
+
+ await Launcher.OpenAsync(new OpenFileRequest(popoverTitle, new ReadOnlyFile(file)));
+ }
+ }
+}
diff --git a/docs/platform-integration/snippets/shared_1/Features/MapTest.cs b/docs/platform-integration/snippets/shared_1/Features/MapTest.cs
new file mode 100644
index 000000000..7402fd64f
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_1/Features/MapTest.cs
@@ -0,0 +1,62 @@
+using Microsoft.Maui.Controls.PlatformConfiguration;
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace PlatformIntegration.Features
+{
+ public class MapTest
+ {
+ public async Task NavigateToBuilding25()
+ {
+ var location = new Location(47.645160, -122.1306032);
+ var options = new MapLaunchOptions { Name = "Microsoft Building 25" };
+
+ try
+ {
+ await Map.OpenAsync(location, options);
+ }
+ catch (Exception ex)
+ {
+ // No map application available to open
+ }
+ }
+ }
+ public class MapTest2
+ {
+ public async Task NavigateToBuilding25()
+ {
+ var placemark = new Placemark
+ {
+ CountryName = "United States",
+ AdminArea = "WA",
+ Thoroughfare = "Microsoft Building 25",
+ Locality = "Redmond"
+ };
+ var options = new MapLaunchOptions { Name = "Microsoft Building 25" };
+
+ try
+ {
+ await Map.OpenAsync(placemark, options);
+ }
+ catch (Exception ex)
+ {
+ // No map application available to open or placemark can not be located
+ }
+ }
+ }
+
+ public class MapTest3
+ {
+ public async Task NavigateToBuilding25()
+ {
+ var location = new Location(47.645160, -122.1306032);
+ var options = new MapLaunchOptions { NavigationMode = NavigationMode.Driving };
+
+ await Map.OpenAsync(location, options);
+ }
+ }
+}
diff --git a/docs/platform-integration/snippets/shared_1/Features/MediaPickerTest.cs b/docs/platform-integration/snippets/shared_1/Features/MediaPickerTest.cs
new file mode 100644
index 000000000..495a9f9c0
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_1/Features/MediaPickerTest.cs
@@ -0,0 +1,53 @@
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace PlatformIntegration.Features
+{
+ class MediaPickerTest
+ {
+ async Task TakePhotoAsync()
+ {
+ try
+ {
+ FileResult photo = await MediaPicker.CapturePhotoAsync();
+
+ //Well, not looking good so far for truck rental. Should know for sure today. Called a dozen places, no one will rent a truck for towing, round trip, let you go to montana and back. Should know today if enterprise truck will have one or not. Otherwise I'll be calling safeco asking if I can just use the rental prices for a class b/c and see if I can find one.
+ string filePath = await LoadPhotoAsync(photo);
+ Console.WriteLine($"CapturePhotoAsync COMPLETED: { filePath }");
+ }
+ catch (FeatureNotSupportedException fnsEx)
+ {
+ // Feature is not supported on the device
+ }
+ catch (PermissionException pEx)
+ {
+ // Permissions not granted
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"CapturePhotoAsync THREW: {ex.Message}");
+ }
+ }
+
+ async Task LoadPhotoAsync(FileResult photo)
+ {
+ // canceled
+ if (photo == null)
+ return string.Empty;
+
+ // save the file into local storage
+ string localFilePath = Path.Combine(FileSystem.CacheDirectory, photo.FileName);
+
+ using Stream sourceStream = await photo.OpenReadAsync();
+ using FileStream localFileStream = File.OpenWrite(localFilePath);
+
+ await sourceStream.CopyToAsync(localFileStream);
+
+ return localFilePath;
+ }
+ }
+}
diff --git a/docs/platform-integration/snippets/shared_1/Features/PermissionsTest.cs b/docs/platform-integration/snippets/shared_1/Features/PermissionsTest.cs
new file mode 100644
index 000000000..f961a661b
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_1/Features/PermissionsTest.cs
@@ -0,0 +1,119 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Microsoft.Maui.Controls;
+using Microsoft.Maui.ApplicationModel;
+
+
+namespace PlatformIntegration.Features
+{
+ class PermissionsTest
+ {
+ public async Task SmallCheckStatus()
+ {
+ //
+ PermissionStatus status = await Permissions.CheckStatusAsync();
+ //
+ }
+
+ public async Task SmallRequestStatus()
+ {
+ //
+ PermissionStatus status = await Permissions.RequestAsync();
+ //
+ }
+
+ public async Task SmallRequestStatusCustom()
+ {
+#if ANDROID
+ //
+ PermissionStatus status = await Permissions.RequestAsync();
+ //
+#endif
+ }
+
+ //
+ public async Task CheckAndRequestLocationPermission()
+ {
+ PermissionStatus status = await Permissions.CheckStatusAsync();
+
+ if (status == PermissionStatus.Granted)
+ return status;
+
+ if (status == PermissionStatus.Denied && DeviceInfo.Platform == DevicePlatform.iOS)
+ {
+ // Prompt the user to turn on in settings
+ // On iOS once a permission has been denied it may not be requested again from the application
+ return status;
+ }
+
+ if (Permissions.ShouldShowRationale())
+ {
+ // Prompt the user with additional information as to why the permission is needed
+ }
+
+ status = await Permissions.RequestAsync();
+
+ return status;
+ }
+ //
+
+ //
+#nullable enable
+ public async Task GetLocationAsync()
+ {
+ PermissionStatus status = await CheckAndRequestPermissionAsync(new Permissions.LocationWhenInUse());
+ if (status != PermissionStatus.Granted)
+ {
+ // Notify user permission was denied
+ return null;
+ }
+
+ return await Geolocation.GetLocationAsync();
+ }
+
+ public async Task CheckAndRequestPermissionAsync(T permission)
+ where T : Permissions.BasePermission
+ {
+ PermissionStatus status = await permission.CheckStatusAsync();
+
+ if (status != PermissionStatus.Granted)
+ status = await permission.RequestAsync();
+
+ return status;
+ }
+#nullable restore
+ //
+
+ public class MyPermission : Permissions.BasePermission
+ {
+ // This method checks if current status of the permission.
+ public override Task CheckStatusAsync()
+ {
+ throw new System.NotImplementedException();
+ }
+
+ // This method is optional and a PermissionException is often thrown if a permission is not declared.
+ public override void EnsureDeclared()
+ {
+ throw new System.NotImplementedException();
+ }
+
+ // Requests the user to accept or deny a permission.
+ public override Task RequestAsync()
+ {
+ throw new System.NotImplementedException();
+ }
+
+ // Indicates that the requestor should prompt the user as to why the app requires the permission, because the
+ // user has previously denied this permission.
+ public override bool ShouldShowRationale()
+ {
+ throw new NotImplementedException();
+ }
+ }
+
+ }
+}
diff --git a/docs/platform-integration/snippets/shared_1/Features/PreferencesTest.cs b/docs/platform-integration/snippets/shared_1/Features/PreferencesTest.cs
new file mode 100644
index 000000000..b98f7e1de
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_1/Features/PreferencesTest.cs
@@ -0,0 +1,48 @@
+using Microsoft.Maui.Controls.PlatformConfiguration;
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace PlatformIntegration.Features
+{
+ public class PreferencesTest
+ {
+ public void SetExamples()
+ {
+ // Set a string value:
+ Preferences.Set("first_name", "John");
+
+ // Set an numerical value:
+ Preferences.Set("age", 28);
+
+ // Set a boolean value:
+ Preferences.Set("has_pets", true);
+ }
+
+ public void GetExamples()
+ {
+ string firstName = Preferences.Get("first_name", "Unknown");
+ int age = Preferences.Get("age", -1);
+ bool hasPets = Preferences.Get("has_pets", false);
+ }
+
+ public void ContainsExamples()
+ {
+ bool hasFirstName = Preferences.ContainsKey("first_name");
+ }
+
+ public void RemoveKey()
+ {
+ Preferences.Remove("first_name");
+ }
+
+ public void Clear()
+ {
+ Preferences.Clear();
+ Preferences.Clear("shared_first_name");
+ }
+ }
+}
diff --git a/docs/platform-integration/snippets/shared_1/Features/ScreenshotTest.cs b/docs/platform-integration/snippets/shared_1/Features/ScreenshotTest.cs
new file mode 100644
index 000000000..40a1b2be2
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_1/Features/ScreenshotTest.cs
@@ -0,0 +1,21 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Microsoft.Maui.Controls;
+
+
+namespace PlatformIntegration.Features
+{
+ class ScreenshotTest
+ {
+ async Task CaptureScreenshot()
+ {
+ IScreenshotResult screenshot = await Screenshot.CaptureAsync();
+ Stream stream = await screenshot.OpenReadAsync();
+
+ return ImageSource.FromStream(() => stream);
+ }
+ }
+}
diff --git a/docs/platform-integration/snippets/shared_1/Features/SecureStorage.cs b/docs/platform-integration/snippets/shared_1/Features/SecureStorage.cs
new file mode 100644
index 000000000..970a12c9e
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_1/Features/SecureStorage.cs
@@ -0,0 +1,51 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+
+namespace PlatformIntegration.Features
+{
+ public class SecureStorageTest
+ {
+ public static async Task SetValue()
+ {
+ try
+ {
+ await SecureStorage.SetAsync("oauth_token", "secret-oauth-token-value");
+ }
+ catch (Exception ex)
+ {
+ // Possible that device doesn't support secure storage on device.
+ }
+ }
+
+ public static async Task GetValue()
+ {
+ try
+ {
+ string oauthToken = await SecureStorage.GetAsync("oauth_token");
+
+ if (oauthToken == null)
+ {
+ // No value is associated with the key "oauth_token"
+ }
+ }
+ catch (Exception ex)
+ {
+ // Possible that device doesn't support secure storage on device.
+ }
+ }
+
+ public static void RemoveValue()
+ {
+ SecureStorage.Remove("oauth_token");
+ }
+
+ public static void RemoveAllValues()
+ {
+ SecureStorage.RemoveAll();
+ }
+ }
+}
diff --git a/docs/platform-integration/snippets/shared_1/Features/Share.cs b/docs/platform-integration/snippets/shared_1/Features/Share.cs
new file mode 100644
index 000000000..d1050af3f
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_1/Features/Share.cs
@@ -0,0 +1,60 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+
+namespace PlatformIntegration.Features
+{
+ public class ShareTest
+ {
+ public async Task ShareText(string text)
+ {
+ await Share.RequestAsync(new ShareTextRequest
+ {
+ Text = text,
+ Title = "Share Text"
+ });
+ }
+
+ public async Task ShareUri(string uri)
+ {
+ await Share.RequestAsync(new ShareTextRequest
+ {
+ Uri = uri,
+ Title = "Share Web Link"
+ });
+ }
+
+ public async Task ShareFile()
+ {
+ string fn = "Attachment.txt";
+ string file = Path.Combine(FileSystem.CacheDirectory, fn);
+
+ File.WriteAllText(file, "Hello World");
+
+ await Share.RequestAsync(new ShareFileRequest
+ {
+ Title = "Share text file",
+ File = new ShareFile(file)
+ });
+ }
+
+ public async Task ShareMultipleFiles()
+ {
+ string file1 = Path.Combine(FileSystem.CacheDirectory, "Attachment1.txt");
+ string file2 = Path.Combine(FileSystem.CacheDirectory, "Attachment2.txt");
+
+ File.WriteAllText(file1, "Content 1");
+ File.WriteAllText(file2, "Content 2");
+
+ await Share.RequestAsync(new ShareMultipleFilesRequest
+ {
+ Title = "Share multiple files",
+ Files = new List { new ShareFile(file1), new ShareFile(file2) }
+ });
+ }
+ }
+}
diff --git a/docs/platform-integration/snippets/shared_1/Features/SmsTest.cs b/docs/platform-integration/snippets/shared_1/Features/SmsTest.cs
new file mode 100644
index 000000000..9c315a7ef
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_1/Features/SmsTest.cs
@@ -0,0 +1,49 @@
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace PlatformIntegration.Features
+{
+ public partial class SmsTest
+ {
+ public async Task SendSms(string messageText, string recipient)
+ {
+ try
+ {
+ var message = new SmsMessage(messageText, new[] { recipient });
+ await Sms.ComposeAsync(message);
+ }
+ catch (FeatureNotSupportedException ex)
+ {
+ // Sms is not supported on this device.
+ }
+ catch (Exception ex)
+ {
+ // Other error has occurred.
+ }
+ }
+ }
+
+ public partial class SmsTest
+ {
+ public async Task SendSms(string messageText, string[] recipients)
+ {
+ try
+ {
+ var message = new SmsMessage(messageText, recipients);
+ await Sms.ComposeAsync(message);
+ }
+ catch (FeatureNotSupportedException ex)
+ {
+ // Sms is not supported on this device.
+ }
+ catch (Exception ex)
+ {
+ // Other error has occurred.
+ }
+ }
+ }
+}
diff --git a/docs/platform-integration/snippets/shared_1/Features/WebAuthTest.cs b/docs/platform-integration/snippets/shared_1/Features/WebAuthTest.cs
new file mode 100644
index 000000000..5ef7d73c3
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_1/Features/WebAuthTest.cs
@@ -0,0 +1,86 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+
+namespace PlatformIntegration.Features
+{
+ internal class WebAuthTest
+ {
+ public async Task AuthIt()
+ {
+ try
+ {
+ WebAuthenticatorResult authResult = await WebAuthenticator.AuthenticateAsync(
+ new Uri("https://mysite.com/mobileauth/Microsoft"),
+ new Uri("myapp://"));
+
+ string accessToken = authResult?.AccessToken;
+
+ // Do something with the token
+ }
+ catch (TaskCanceledException e)
+ {
+ // Use stopped auth
+ }
+ }
+
+ public async Task AuthItSecure()
+ {
+ try
+ {
+ WebAuthenticatorResult authResult = await WebAuthenticator.AuthenticateAsync(
+ new WebAuthenticatorOptions()
+ {
+ Url = new Uri("https://mysite.com/mobileauth/Microsoft"),
+ CallbackUrl = new Uri("myapp://"),
+ PrefersEphemeralWebBrowserSession = true
+ });
+
+ string accessToken = authResult?.AccessToken;
+
+ // Do something with the token
+ }
+ catch (TaskCanceledException e)
+ {
+ // Use stopped auth
+ }
+ }
+
+ public async Task AuthItApple()
+ {
+ var scheme = "..."; // Apple, Microsoft, Google, Facebook, etc.
+ var authUrlRoot = "https://mysite.com/mobileauth/";
+ WebAuthenticatorResult result = null;
+
+ if (scheme.Equals("Apple")
+ && DeviceInfo.Platform == DevicePlatform.iOS
+ && DeviceInfo.Version.Major >= 13)
+ {
+ // Use Native Apple Sign In API's
+ result = await AppleSignInAuthenticator.AuthenticateAsync();
+ }
+ else
+ {
+ // Web Authentication flow
+ var authUrl = new Uri($"{authUrlRoot}{scheme}");
+ var callbackUrl = new Uri("myapp://");
+
+ result = await WebAuthenticator.AuthenticateAsync(authUrl, callbackUrl);
+ }
+
+ var authToken = string.Empty;
+
+ if (result.Properties.TryGetValue("name", out string name) && !string.IsNullOrEmpty(name))
+ authToken += $"Name: {name}{Environment.NewLine}";
+
+ if (result.Properties.TryGetValue("email", out string email) && !string.IsNullOrEmpty(email))
+ authToken += $"Email: {email}{Environment.NewLine}";
+
+ // Note that Apple Sign In has an IdToken and not an AccessToken
+ authToken += result?.AccessToken ?? result?.IdToken;
+ }
+ }
+}
diff --git a/docs/platform-integration/snippets/shared_1/MainPage.xaml b/docs/platform-integration/snippets/shared_1/MainPage.xaml
new file mode 100644
index 000000000..e00183598
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_1/MainPage.xaml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/platform-integration/snippets/shared_1/MainPage.xaml.cs b/docs/platform-integration/snippets/shared_1/MainPage.xaml.cs
new file mode 100644
index 000000000..ed910ea96
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_1/MainPage.xaml.cs
@@ -0,0 +1,57 @@
+namespace PlatformIntegration;
+
+public partial class MainPage : ContentPage
+{
+ int count = 0;
+
+ public MainPage()
+ {
+ InitializeComponent();
+ }
+
+ private void OnCounterClicked(object sender, EventArgs e)
+ {
+ }
+
+ private void Battery_Clicked(object sender, EventArgs e)
+ {
+ Navigation.PushAsync(new BatteryTestPage());
+ }
+
+ private void Sensors_Clicked(object sender, EventArgs e)
+ {
+ Navigation.PushAsync(new SensorsPage());
+
+ }
+
+ private void Details1_Clicked(object sender, EventArgs e)
+ {
+ Navigation.PushAsync(new DeviceDetailsPage());
+ }
+
+ private void Data_Clicked(object sender, EventArgs e)
+ {
+ Navigation.PushAsync(new DataPage());
+ }
+
+ private void AppModel_Clicked(object sender, EventArgs e)
+ {
+ Navigation.PushAsync(new AppModelPage());
+ }
+
+ private void Comms_Clicked(object sender, EventArgs e)
+ {
+ Navigation.PushAsync(new CommsPage());
+ }
+
+ private void Reader_Clicked(object sender, EventArgs e)
+ {
+ Navigation.PushAsync(new ScreenReaderPage());
+ }
+
+ private void Media_Clicked(object sender, EventArgs e)
+ {
+ Navigation.PushAsync(new MediaPage());
+ }
+}
+
diff --git a/docs/platform-integration/snippets/shared_1/MauiProgram.cs b/docs/platform-integration/snippets/shared_1/MauiProgram.cs
new file mode 100644
index 000000000..f8dd5e81f
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_1/MauiProgram.cs
@@ -0,0 +1,107 @@
+namespace PlatformIntegration;
+
+public static class MauiProgram
+{
+ public static MauiApp CreateMauiApp()
+ {
+ var builder = MauiApp.CreateBuilder();
+
+ builder
+ .UseMauiApp()
+ .ConfigureFonts(fonts =>
+ {
+ fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
+ fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
+ })
+ .ConfigureEssentials(essentials =>
+ {
+ essentials
+ .UseVersionTracking()
+#if WINDOWS
+ .UseMapServiceToken("YOUR-KEY-HERE")
+#endif
+ .AddAppAction("app_info", "App Info", icon: "app_info_action_icon")
+ .AddAppAction("battery_info", "Battery Info")
+ .OnAppAction(App.HandleAppActions);
+ })
+ ;
+
+ return builder.Build();
+ }
+
+ private static class BootstrapVersionTracking
+ {
+ //
+ public static MauiApp CreateMauiApp()
+ {
+ var builder = MauiApp.CreateBuilder();
+
+ builder
+ .UseMauiApp()
+ .ConfigureFonts(fonts =>
+ {
+ fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
+ fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
+ })
+ .ConfigureEssentials(essentials =>
+ {
+ essentials.UseVersionTracking();
+ });
+
+ return builder.Build();
+ }
+ //
+ }
+
+ private static class BootstrapAppAction
+ {
+ //
+ public static MauiApp CreateMauiApp()
+ {
+ var builder = MauiApp.CreateBuilder();
+
+ builder
+ .UseMauiApp()
+ .ConfigureFonts(fonts =>
+ {
+ fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
+ fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
+ })
+ .ConfigureEssentials(essentials =>
+ {
+ essentials
+ .AddAppAction("app_info", "App Info", icon: "app_info_action_icon")
+ .AddAppAction("battery_info", "Battery Info")
+ .OnAppAction(App.HandleAppActions);
+ });
+
+ return builder.Build();
+ }
+ //
+ }
+
+ private static class BootstrapMapToken
+ {
+ //
+ public static MauiApp CreateMauiApp()
+ {
+ var builder = MauiApp.CreateBuilder();
+
+ builder
+ .UseMauiApp()
+ .ConfigureFonts(fonts =>
+ {
+ fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
+ fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
+ })
+ .ConfigureEssentials(essentials =>
+ {
+ essentials.UseMapServiceToken("YOUR-API-TOKEN");
+ });
+
+ return builder.Build();
+ }
+ //
+ }
+}
+}
diff --git a/docs/platform-integration/snippets/shared_1/MediaPage.cs b/docs/platform-integration/snippets/shared_1/MediaPage.cs
new file mode 100644
index 000000000..c3f2957c7
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_1/MediaPage.cs
@@ -0,0 +1,128 @@
+namespace PlatformIntegration;
+
+public class MediaPage : ContentPage
+{
+ public ImageSource ImageItem
+ {
+ get { return (ImageSource)GetValue(ImageItemProperty); }
+ set { SetValue(ImageItemProperty, value); }
+ }
+
+ public static readonly BindableProperty ImageItemProperty =
+ BindableProperty.Create("ImageItem", typeof(ImageSource), typeof(MediaPage), null);
+
+
+ public MediaPage()
+ {
+ this.BindingContext = this;
+
+ var imageControl = new Image();
+ imageControl.SetBinding(Image.SourceProperty, new Binding("ImageItem"));
+
+ Content = new VerticalStackLayout
+ {
+ imageControl,
+ new Button { Text = "Take photo",
+ Command = new Command(TakePhoto) },
+ new Button { Text = "Take screenshot",
+ Command = new Command(() => { ImageItem = TakeScreenshotAsync().Result; }) },
+ new Button { Text = "Text to speech",
+ Command = new Command(Speak) },
+ };
+ }
+
+ //
+ public async void TakePhoto()
+ {
+ if (MediaPicker.Default.IsCaptureSupported)
+ {
+ FileResult photo = await MediaPicker.Default.CapturePhotoAsync();
+
+ if (photo != null)
+ {
+ // save the file into local storage
+ string localFilePath = Path.Combine(FileSystem.CacheDirectory, photo.FileName);
+
+ using Stream sourceStream = await photo.OpenReadAsync();
+ using FileStream localFileStream = File.OpenWrite(localFilePath);
+
+ await sourceStream.CopyToAsync(localFileStream);
+ }
+ }
+ }
+ //
+
+ //
+ public async Task TakeScreenshotAsync()
+ {
+ if (Screenshot.Default.IsCaptureSupported)
+ {
+ IScreenshotResult screen = await Screenshot.Default.CaptureAsync();
+
+ Stream stream = await screen.OpenReadAsync();
+
+ return ImageSource.FromStream(() => stream);
+ }
+
+ return null;
+ }
+ //
+
+ //
+ public async void Speak() =>
+ await TextToSpeech.Default.SpeakAsync("Hello World");
+ //
+
+ //
+ public async void SpeakSettings()
+ {
+ IEnumerable locales = await TextToSpeech.Default.GetLocalesAsync();
+
+ SpeechOptions options = new SpeechOptions()
+ {
+ Pitch = 1.5f, // 0.0 - 2.0
+ Volume = 0.75f, // 0.0 - 1.0
+ Locale = locales.FirstOrDefault()
+ };
+
+ await TextToSpeech.Default.SpeakAsync("How nice to meet you!", options);
+ }
+ //
+
+ //
+ CancellationTokenSource cts;
+
+ public async Task SpeakNowDefaultSettingsAsync()
+ {
+ cts = new CancellationTokenSource();
+ await TextToSpeech.Default.SpeakAsync("Hello World", cancelToken: cts.Token);
+
+ // This method will block until utterance finishes.
+ }
+
+ // Cancel speech if a cancellation token exists & hasn't been already requested.
+ public void CancelSpeech()
+ {
+ if (cts?.IsCancellationRequested ?? true)
+ return;
+
+ cts.Cancel();
+ }
+ //
+
+
+ //
+ bool isBusy = false;
+
+ public void SpeakMultiple()
+ {
+ isBusy = true;
+
+ Task.WhenAll(
+ TextToSpeech.Default.SpeakAsync("Hello World 1"),
+ TextToSpeech.Default.SpeakAsync("Hello World 2"),
+ TextToSpeech.Default.SpeakAsync("Hello World 3"))
+ .ContinueWith((t) => { isBusy = false; }, TaskScheduler.FromCurrentSynchronizationContext());
+ }
+ //
+}
\ No newline at end of file
diff --git a/docs/platform-integration/snippets/shared_1/NetworkingPage.cs b/docs/platform-integration/snippets/shared_1/NetworkingPage.cs
new file mode 100644
index 000000000..1f7f71d43
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_1/NetworkingPage.cs
@@ -0,0 +1,81 @@
+namespace PlatformIntegration;
+
+public class NetworkingPage : ContentPage
+{
+ public NetworkingPage()
+ {
+ this.BindingContext = this;
+
+ Content = new VerticalStackLayout
+ {
+ };
+ }
+
+
+ public void Test()
+ {
+ //
+ NetworkAccess accessType = Connectivity.Current.NetworkAccess;
+
+ if (accessType == NetworkAccess.Internet)
+ {
+ // Connection to internet is available
+ }
+ //
+
+ //
+ IEnumerable profiles = Connectivity.Current.ConnectionProfiles;
+
+ if (profiles.Contains(ConnectionProfile.WiFi))
+ {
+ // Active Wi-Fi connection.
+ }
+ //
+ }
+
+ //
+ public class ConnectivityTest
+ {
+ public ConnectivityTest() =>
+ Connectivity.ConnectivityChanged += Connectivity_ConnectivityChanged;
+
+ ~ConnectivityTest() =>
+ Connectivity.ConnectivityChanged -= Connectivity_ConnectivityChanged;
+
+ void Connectivity_ConnectivityChanged(object sender, ConnectivityChangedEventArgs e)
+ {
+ if (e.NetworkAccess == NetworkAccess.ConstrainedInternet)
+ Console.WriteLine("Internet access is available but is limited.");
+
+ else if (e.NetworkAccess != NetworkAccess.Internet)
+ Console.WriteLine("Internet access has been lost.");
+
+ // Log each active connection
+ Console.Write("Connections active: ");
+
+ foreach (var item in e.ConnectionProfiles)
+ {
+ switch (item)
+ {
+ case ConnectionProfile.Bluetooth:
+ Console.Write("Bluetooth");
+ break;
+ case ConnectionProfile.Cellular:
+ Console.Write("Cell");
+ break;
+ case ConnectionProfile.Ethernet:
+ Console.Write("Ethernet");
+ break;
+ case ConnectionProfile.WiFi:
+ Console.Write("WiFi");
+ break;
+ default:
+ break;
+ }
+ }
+
+ Console.WriteLine();
+ }
+ }
+ //
+}
\ No newline at end of file
diff --git a/docs/platform-integration/snippets/shared_1/PermissionsPerPlatform.cs b/docs/platform-integration/snippets/shared_1/PermissionsPerPlatform.cs
new file mode 100644
index 000000000..f8b762211
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_1/PermissionsPerPlatform.cs
@@ -0,0 +1,38 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace PlatformIntegration
+{
+ internal class PermissionsPerPlatform
+ {
+ public interface IReadWritePermission
+ {
+ Task CheckStatusAsync();
+ Task RequestAsync();
+ }
+
+#if ANDROID
+ public class ReadWriteStoragePerms : Permissions.BasePlatformPermission
+ {
+ public override (string androidPermission, bool isRuntime)[] RequiredPermissions =>
+ new List<(string androidPermission, bool isRuntime)>
+ {
+ (global::Android.Manifest.Permission.ReadExternalStorage, true),
+ (global::Android.Manifest.Permission.WriteExternalStorage, true)
+ }
+ .ToArray();
+ }
+#endif
+#if WINDOWS
+ public class ReadWriteStoragePerms : Permissions.BasePlatformPermission
+ {
+
+ }
+#endif
+#if IOS
+#endif
+ }
+}
diff --git a/docs/platform-integration/snippets/shared_1/PlatExtensions.cs b/docs/platform-integration/snippets/shared_1/PlatExtensions.cs
new file mode 100644
index 000000000..78c65426e
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_1/PlatExtensions.cs
@@ -0,0 +1,17 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Microsoft.Maui.Platform;
+
+namespace PlatformIntegration
+{
+ internal class PlatExtensions
+ {
+ public void test1()
+ {
+
+ }
+ }
+}
diff --git a/docs/platform-integration/snippets/shared_1/PlatformIntegration.csproj b/docs/platform-integration/snippets/shared_1/PlatformIntegration.csproj
new file mode 100644
index 000000000..985323788
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_1/PlatformIntegration.csproj
@@ -0,0 +1,78 @@
+
+
+
+ net6.0-android;net6.0-ios;net6.0-maccatalyst
+ $(TargetFrameworks);net6.0-windows10.0.19041.0
+
+
+ Exe
+ PlatformIntegration
+ true
+ true
+ enable
+
+
+ PlatformIntegrationa
+
+
+ com.companyname.platformintegrationa
+ B668375E-5F70-4844-BB98-583AB74C06E1
+
+
+ 3.0
+ 3
+
+ 14.2
+ 14.0
+ 21.0
+ 10.0.17763.0
+ 10.0.17763.0
+ 6.5
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ SensorsPage.xaml
+
+
+
+
+
+ MSBuild:Compile
+
+
+ MSBuild:Compile
+
+
+ MSBuild:Compile
+
+
+ MSBuild:Compile
+
+
+ MSBuild:Compile
+
+
+ MSBuild:Compile
+
+
+
+
diff --git a/docs/platform-integration/snippets/shared_1/PlatformIntegration.csproj.user b/docs/platform-integration/snippets/shared_1/PlatformIntegration.csproj.user
new file mode 100644
index 000000000..666b6d135
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_1/PlatformIntegration.csproj.user
@@ -0,0 +1,39 @@
+
+
+
+ net6.0-android
+ False
+ Pixel 3a - API 30 (Android 11.0 - API 30)
+ pixel_3a_-_api_30
+ Emulator
+ pixel_3a_-_api_30
+
+
+ ProjectDebugger
+
+
+
+ Designer
+
+
+ Designer
+
+
+ Designer
+
+
+ Designer
+
+
+ Designer
+
+
+ Designer
+
+
+
+
+ Designer
+
+
+
\ No newline at end of file
diff --git a/docs/platform-integration/snippets/shared_1/PlatformIntegration.sln b/docs/platform-integration/snippets/shared_1/PlatformIntegration.sln
new file mode 100644
index 000000000..fb16615bf
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_1/PlatformIntegration.sln
@@ -0,0 +1,27 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.0.31611.283
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PlatformIntegration", "PlatformIntegration.csproj", "{C719BBF0-D151-46D4-9270-962DCE07C3BA}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {C719BBF0-D151-46D4-9270-962DCE07C3BA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {C719BBF0-D151-46D4-9270-962DCE07C3BA}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {C719BBF0-D151-46D4-9270-962DCE07C3BA}.Debug|Any CPU.Deploy.0 = Debug|Any CPU
+ {C719BBF0-D151-46D4-9270-962DCE07C3BA}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {C719BBF0-D151-46D4-9270-962DCE07C3BA}.Release|Any CPU.Build.0 = Release|Any CPU
+ {C719BBF0-D151-46D4-9270-962DCE07C3BA}.Release|Any CPU.Deploy.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {61F7FB11-1E47-470C-91E2-47F8143E1572}
+ EndGlobalSection
+EndGlobal
diff --git a/docs/platform-integration/snippets/shared_1/Platforms/Android/AndroidManifest.xml b/docs/platform-integration/snippets/shared_1/Platforms/Android/AndroidManifest.xml
new file mode 100644
index 000000000..4a32fa88d
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_1/Platforms/Android/AndroidManifest.xml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/docs/platform-integration/snippets/shared_1/Platforms/Android/MainActivity.cs b/docs/platform-integration/snippets/shared_1/Platforms/Android/MainActivity.cs
new file mode 100644
index 000000000..0317c6b3d
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_1/Platforms/Android/MainActivity.cs
@@ -0,0 +1,25 @@
+using Android.App;
+using Android.Content.PM;
+using Android.OS;
+
+namespace PlatformIntegration;
+
+[Activity(Theme = "@style/Maui.SplashTheme", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation | ConfigChanges.UiMode | ConfigChanges.ScreenLayout | ConfigChanges.SmallestScreenSize | ConfigChanges.Density)]
+public class MainActivity : MauiAppCompatActivity
+{
+ //
+ protected override void OnCreate(Bundle savedInstanceState)
+ {
+ base.OnCreate(savedInstanceState);
+ Platform.Init(this, savedInstanceState);
+ }
+ //
+
+ //
+ public override void OnRequestPermissionsResult(int requestCode, string[] permissions, Permission[] grantResults)
+ {
+ Platform.OnRequestPermissionsResult(requestCode, permissions, grantResults);
+ base.OnRequestPermissionsResult(requestCode, permissions, grantResults);
+ }
+ //
+}
diff --git a/docs/platform-integration/snippets/shared_1/Platforms/Android/MainApplication.cs b/docs/platform-integration/snippets/shared_1/Platforms/Android/MainApplication.cs
new file mode 100644
index 000000000..7bdfb3025
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_1/Platforms/Android/MainApplication.cs
@@ -0,0 +1,32 @@
+using Android.App;
+using Android.Runtime;
+
+//
+[assembly: UsesPermission(Android.Manifest.Permission.ReadContacts)]
+//
+
+//
+// Needed for Picking photo/video
+[assembly: UsesPermission(Android.Manifest.Permission.ReadExternalStorage)]
+
+// Needed for Taking photo/video
+[assembly: UsesPermission(Android.Manifest.Permission.WriteExternalStorage)]
+[assembly: UsesPermission(Android.Manifest.Permission.Camera)]
+
+// Add these properties if you would like to filter out devices that do not have cameras, or set to false to make them optional
+[assembly: UsesFeature("android.hardware.camera", Required = true)]
+[assembly: UsesFeature("android.hardware.camera.autofocus", Required = true)]
+//
+
+namespace PlatformIntegration;
+
+[Application]
+public class MainApplication : MauiApplication
+{
+ public MainApplication(IntPtr handle, JniHandleOwnership ownership)
+ : base(handle, ownership)
+ {
+ }
+
+ protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
+}
diff --git a/docs/platform-integration/snippets/shared_1/Platforms/Android/ReadWriteStoragePerms.cs b/docs/platform-integration/snippets/shared_1/Platforms/Android/ReadWriteStoragePerms.cs
new file mode 100644
index 000000000..b85c50ec5
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_1/Platforms/Android/ReadWriteStoragePerms.cs
@@ -0,0 +1,20 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace PlatformIntegration.Android.Permissions
+{
+#if ANDROID
+ public class ReadWriteStoragePerms: Microsoft.Maui.ApplicationModel.Permissions.BasePlatformPermission
+ {
+ public override (string androidPermission, bool isRuntime)[] RequiredPermissions =>
+ new List<(string androidPermission, bool isRuntime)>
+ {
+ (global::Android.Manifest.Permission.ReadExternalStorage, true),
+ (global::Android.Manifest.Permission.WriteExternalStorage, true)
+ }.ToArray();
+ }
+#endif
+}
diff --git a/docs/platform-integration/snippets/shared_1/Platforms/Android/Resources/values/colors.xml b/docs/platform-integration/snippets/shared_1/Platforms/Android/Resources/values/colors.xml
new file mode 100644
index 000000000..c04d7492a
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_1/Platforms/Android/Resources/values/colors.xml
@@ -0,0 +1,6 @@
+
+
+ #512BD4
+ #2B0B98
+ #2B0B98
+
\ No newline at end of file
diff --git a/docs/platform-integration/snippets/shared_1/Platforms/MacCatalyst/AppDelegate.cs b/docs/platform-integration/snippets/shared_1/Platforms/MacCatalyst/AppDelegate.cs
new file mode 100644
index 000000000..18c18bf52
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_1/Platforms/MacCatalyst/AppDelegate.cs
@@ -0,0 +1,9 @@
+using Foundation;
+
+namespace PlatformIntegration;
+
+[Register("AppDelegate")]
+public class AppDelegate : MauiUIApplicationDelegate
+{
+ protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
+}
diff --git a/docs/platform-integration/snippets/shared_1/Platforms/MacCatalyst/Info.plist b/docs/platform-integration/snippets/shared_1/Platforms/MacCatalyst/Info.plist
new file mode 100644
index 000000000..c96dd0a22
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_1/Platforms/MacCatalyst/Info.plist
@@ -0,0 +1,30 @@
+
+
+
+
+ UIDeviceFamily
+
+ 1
+ 2
+
+ UIRequiredDeviceCapabilities
+
+ arm64
+
+ UISupportedInterfaceOrientations
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+ UISupportedInterfaceOrientations~ipad
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationPortraitUpsideDown
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+ XSAppIconAssets
+ Assets.xcassets/appicon.appiconset
+
+
diff --git a/docs/platform-integration/snippets/shared_1/Platforms/MacCatalyst/Program.cs b/docs/platform-integration/snippets/shared_1/Platforms/MacCatalyst/Program.cs
new file mode 100644
index 000000000..2557d3130
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_1/Platforms/MacCatalyst/Program.cs
@@ -0,0 +1,15 @@
+using ObjCRuntime;
+using UIKit;
+
+namespace PlatformIntegration;
+
+public class Program
+{
+ // This is the main entry point of the application.
+ static void Main(string[] args)
+ {
+ // if you want to use a different Application Delegate class from "AppDelegate"
+ // you can specify it here.
+ UIApplication.Main(args, null, typeof(AppDelegate));
+ }
+}
diff --git a/docs/platform-integration/snippets/shared_1/Platforms/Tizen/Main.cs b/docs/platform-integration/snippets/shared_1/Platforms/Tizen/Main.cs
new file mode 100644
index 000000000..1a51d5bf7
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_1/Platforms/Tizen/Main.cs
@@ -0,0 +1,16 @@
+using System;
+using Microsoft.Maui;
+using Microsoft.Maui.Hosting;
+
+namespace PlatformIntegration;
+
+class Program : MauiApplication
+{
+ protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
+
+ static void Main(string[] args)
+ {
+ var app = new Program();
+ app.Run(args);
+ }
+}
diff --git a/docs/platform-integration/snippets/shared_1/Platforms/Tizen/tizen-manifest.xml b/docs/platform-integration/snippets/shared_1/Platforms/Tizen/tizen-manifest.xml
new file mode 100644
index 000000000..73d30ca93
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_1/Platforms/Tizen/tizen-manifest.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+ appicon.xhigh.png
+
+
+
+
+ http://tizen.org/privilege/internet
+
+
+
+
\ No newline at end of file
diff --git a/docs/platform-integration/snippets/shared_1/Platforms/Windows/App.xaml b/docs/platform-integration/snippets/shared_1/Platforms/Windows/App.xaml
new file mode 100644
index 000000000..38b8d0687
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_1/Platforms/Windows/App.xaml
@@ -0,0 +1,8 @@
+
+
+
diff --git a/docs/platform-integration/snippets/shared_1/Platforms/Windows/App.xaml.cs b/docs/platform-integration/snippets/shared_1/Platforms/Windows/App.xaml.cs
new file mode 100644
index 000000000..3401d8eac
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_1/Platforms/Windows/App.xaml.cs
@@ -0,0 +1,24 @@
+using Microsoft.UI.Xaml;
+
+// To learn more about WinUI, the WinUI project structure,
+// and more about our project templates, see: http://aka.ms/winui-project-info.
+
+namespace PlatformIntegration.WinUI;
+
+///
+/// Provides application-specific behavior to supplement the default Application class.
+///
+public partial class App : MauiWinUIApplication
+{
+ ///
+ /// Initializes the singleton application object. This is the first line of authored code
+ /// executed, and as such is the logical equivalent of main() or WinMain().
+ ///
+ public App()
+ {
+ this.InitializeComponent();
+ }
+
+ protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
+}
+
diff --git a/docs/platform-integration/snippets/shared_1/Platforms/Windows/Package.appxmanifest b/docs/platform-integration/snippets/shared_1/Platforms/Windows/Package.appxmanifest
new file mode 100644
index 000000000..3036ee95a
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_1/Platforms/Windows/Package.appxmanifest
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+ User Name
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/platform-integration/snippets/shared_1/Platforms/Windows/app.manifest b/docs/platform-integration/snippets/shared_1/Platforms/Windows/app.manifest
new file mode 100644
index 000000000..e064d0160
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_1/Platforms/Windows/app.manifest
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+ true/PM
+ PerMonitorV2, PerMonitor
+
+
+
diff --git a/docs/platform-integration/snippets/shared_1/Platforms/iOS/AppDelegate.cs b/docs/platform-integration/snippets/shared_1/Platforms/iOS/AppDelegate.cs
new file mode 100644
index 000000000..18c18bf52
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_1/Platforms/iOS/AppDelegate.cs
@@ -0,0 +1,9 @@
+using Foundation;
+
+namespace PlatformIntegration;
+
+[Register("AppDelegate")]
+public class AppDelegate : MauiUIApplicationDelegate
+{
+ protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
+}
diff --git a/docs/platform-integration/snippets/shared_1/Platforms/iOS/Info.plist b/docs/platform-integration/snippets/shared_1/Platforms/iOS/Info.plist
new file mode 100644
index 000000000..d67495ffa
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_1/Platforms/iOS/Info.plist
@@ -0,0 +1,37 @@
+
+
+
+
+ LSRequiresIPhoneOS
+
+ UIDeviceFamily
+
+ 1
+ 2
+
+ UIRequiredDeviceCapabilities
+
+ arm64
+
+ UISupportedInterfaceOrientations
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+ UISupportedInterfaceOrientations~ipad
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationPortraitUpsideDown
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+ XSAppIconAssets
+ Assets.xcassets/appicon.appiconset
+ LSApplicationQueriesSchemes
+
+ lyft
+ fb
+
+
+
diff --git a/docs/platform-integration/snippets/shared_1/Platforms/iOS/Program.cs b/docs/platform-integration/snippets/shared_1/Platforms/iOS/Program.cs
new file mode 100644
index 000000000..2557d3130
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_1/Platforms/iOS/Program.cs
@@ -0,0 +1,15 @@
+using ObjCRuntime;
+using UIKit;
+
+namespace PlatformIntegration;
+
+public class Program
+{
+ // This is the main entry point of the application.
+ static void Main(string[] args)
+ {
+ // if you want to use a different Application Delegate class from "AppDelegate"
+ // you can specify it here.
+ UIApplication.Main(args, null, typeof(AppDelegate));
+ }
+}
diff --git a/docs/platform-integration/snippets/shared_1/Properties/launchSettings.json b/docs/platform-integration/snippets/shared_1/Properties/launchSettings.json
new file mode 100644
index 000000000..edf8aadcc
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_1/Properties/launchSettings.json
@@ -0,0 +1,8 @@
+{
+ "profiles": {
+ "Windows Machine": {
+ "commandName": "MsixPackage",
+ "nativeDebugging": false
+ }
+ }
+}
\ No newline at end of file
diff --git a/docs/platform-integration/snippets/shared_1/Resources/Fonts/OpenSans-Regular.ttf b/docs/platform-integration/snippets/shared_1/Resources/Fonts/OpenSans-Regular.ttf
new file mode 100644
index 000000000..c9237de1c
Binary files /dev/null and b/docs/platform-integration/snippets/shared_1/Resources/Fonts/OpenSans-Regular.ttf differ
diff --git a/docs/platform-integration/snippets/shared_1/Resources/Fonts/OpenSans-Semibold.ttf b/docs/platform-integration/snippets/shared_1/Resources/Fonts/OpenSans-Semibold.ttf
new file mode 100644
index 000000000..0da9f9986
Binary files /dev/null and b/docs/platform-integration/snippets/shared_1/Resources/Fonts/OpenSans-Semibold.ttf differ
diff --git a/docs/platform-integration/snippets/shared_1/Resources/Images/dotnet_bot.svg b/docs/platform-integration/snippets/shared_1/Resources/Images/dotnet_bot.svg
new file mode 100644
index 000000000..abfaff26a
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_1/Resources/Images/dotnet_bot.svg
@@ -0,0 +1,93 @@
+
diff --git a/docs/platform-integration/snippets/shared_1/Resources/Raw/AboutAssets.txt b/docs/platform-integration/snippets/shared_1/Resources/Raw/AboutAssets.txt
new file mode 100644
index 000000000..3f7a940be
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_1/Resources/Raw/AboutAssets.txt
@@ -0,0 +1,14 @@
+Any raw assets you want to be deployed with your application can be placed in
+this directory (and child directories) and given a Build Action of "MauiAsset":
+
+
+
+These files will be deployed with you package and will be accessible using Essentials:
+
+ async Task LoadMauiAsset()
+ {
+ using var stream = await FileSystem.OpenAppPackageFileAsync("AboutAssets.txt");
+ using var reader = new StreamReader(stream);
+
+ var contents = reader.ReadToEnd();
+ }
diff --git a/docs/platform-integration/snippets/shared_1/Resources/Styles.xaml b/docs/platform-integration/snippets/shared_1/Resources/Styles.xaml
new file mode 100644
index 000000000..0eb436944
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_1/Resources/Styles.xaml
@@ -0,0 +1,456 @@
+
+
+
+
+ #512BD4
+ #DFD8F7
+ #2B0B98
+ White
+ Black
+ #E5E5E1
+ #969696
+ #505050
+
+
+
+
+
+
+
+
+
+ #F7B548
+ #FFD590
+ #FFE5B9
+ #28C2D1
+ #7BDDEF
+ #C3F2F4
+ #3E8EED
+ #72ACF1
+ #A7CBF6
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/docs/platform-integration/snippets/shared_1/Resources/appicon.svg b/docs/platform-integration/snippets/shared_1/Resources/appicon.svg
new file mode 100644
index 000000000..9d63b6513
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_1/Resources/appicon.svg
@@ -0,0 +1,4 @@
+
+
\ No newline at end of file
diff --git a/docs/platform-integration/snippets/shared_1/Resources/appiconfg.svg b/docs/platform-integration/snippets/shared_1/Resources/appiconfg.svg
new file mode 100644
index 000000000..21dfb25f1
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_1/Resources/appiconfg.svg
@@ -0,0 +1,8 @@
+
+
+
\ No newline at end of file
diff --git a/docs/platform-integration/snippets/shared_1/ScreenReaderPage.cs b/docs/platform-integration/snippets/shared_1/ScreenReaderPage.cs
new file mode 100644
index 000000000..86a8d41b0
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_1/ScreenReaderPage.cs
@@ -0,0 +1,20 @@
+namespace PlatformIntegration;
+
+public class ScreenReaderPage : ContentPage
+{
+ public ScreenReaderPage()
+ {
+ Content = new VerticalStackLayout
+ {
+ Children = {
+ new Button { Text = "Read",
+ Command = new Command(Announce) }
+ }
+ };
+ }
+
+ public void Announce() =>
+ //
+ SemanticScreenReader.Default.Announce("This is the announcement text.");
+ //
+}
\ No newline at end of file
diff --git a/docs/platform-integration/snippets/shared_1/Sensors/Accelerometer.cs b/docs/platform-integration/snippets/shared_1/Sensors/Accelerometer.cs
new file mode 100644
index 000000000..75b16e746
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_1/Sensors/Accelerometer.cs
@@ -0,0 +1,43 @@
+
+using System;
+
+namespace PlatformIntegration.Sensors
+{
+ public class AccelerometerTest
+ {
+ public void ToggleAccelerometer()
+ {
+ const SensorSpeed speed = SensorSpeed.UI;
+
+ try
+ {
+ if (Accelerometer.IsMonitoring)
+ {
+ Accelerometer.Stop();
+ Accelerometer.ReadingChanged -= Accelerometer_ReadingChanged;
+ }
+ else
+ {
+ Accelerometer.ReadingChanged += Accelerometer_ReadingChanged;
+ Accelerometer.Start(speed);
+ }
+ }
+ catch (FeatureNotSupportedException fnsEx)
+ {
+ // Feature not supported on device
+ }
+ catch (Exception ex)
+ {
+ // Other error has occurred.
+ }
+ }
+
+ void Accelerometer_ReadingChanged(object sender, AccelerometerChangedEventArgs e)
+ {
+ AccelerometerData data = e.Reading;
+
+ // Process Acceleration X, Y, Z
+ Console.WriteLine($"Reading: X: {data.Acceleration.X}, Y: {data.Acceleration.Y}, Z: {data.Acceleration.Z}");
+ }
+ }
+}
diff --git a/docs/platform-integration/snippets/shared_1/Sensors/Barometer.cs b/docs/platform-integration/snippets/shared_1/Sensors/Barometer.cs
new file mode 100644
index 000000000..d6bff299d
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_1/Sensors/Barometer.cs
@@ -0,0 +1,43 @@
+
+using System;
+
+namespace PlatformIntegration.Sensors
+{
+ public class BarometerTest
+ {
+ public void ToggleBarometer()
+ {
+ const SensorSpeed speed = SensorSpeed.UI;
+
+ try
+ {
+ if (Barometer.IsMonitoring)
+ {
+ Barometer.Stop();
+ Barometer.ReadingChanged -= Barometer_ReadingChanged;
+ }
+ else
+ {
+ Barometer.ReadingChanged += Barometer_ReadingChanged;
+ Barometer.Start(speed);
+ }
+ }
+ catch (FeatureNotSupportedException fnsEx)
+ {
+ // Feature not supported on device
+ }
+ catch (Exception ex)
+ {
+ // Other error has occurred.
+ }
+ }
+
+ void Barometer_ReadingChanged(object sender, BarometerChangedEventArgs e)
+ {
+ var data = e.Reading;
+
+ // Process Pressure
+ Console.WriteLine($"Reading: Pressure: {data.PressureInHectopascals} hectopascals");
+ }
+ }
+}
diff --git a/docs/platform-integration/snippets/shared_1/Sensors/Compass.cs b/docs/platform-integration/snippets/shared_1/Sensors/Compass.cs
new file mode 100644
index 000000000..519c0a4bb
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_1/Sensors/Compass.cs
@@ -0,0 +1,43 @@
+
+using System;
+
+namespace PlatformIntegration.Sensors
+{
+ public class CompassTest
+ {
+ public void ToggleCompass(ICompass compassInstance)
+ {
+ const SensorSpeed speed = SensorSpeed.UI;
+
+ try
+ {
+ if (compassInstance.IsMonitoring)
+ {
+ compassInstance.Stop();
+ compassInstance.ReadingChanged -= Compass_ReadingChanged;
+ }
+ else
+ {
+ compassInstance.ReadingChanged += Compass_ReadingChanged;
+ compassInstance.Start(speed);
+ }
+ }
+ catch (FeatureNotSupportedException fnsEx)
+ {
+ // Feature not supported on device
+ }
+ catch (Exception ex)
+ {
+ // Some other exception has occurred
+ }
+ }
+
+ void Compass_ReadingChanged(object sender, CompassChangedEventArgs e)
+ {
+ var data = e.Reading;
+
+ // Process Heading Magnetic North
+ Console.WriteLine($"Reading: {data.HeadingMagneticNorth} degrees");
+ }
+ }
+}
diff --git a/docs/platform-integration/snippets/shared_1/Sensors/Gyroscope.cs b/docs/platform-integration/snippets/shared_1/Sensors/Gyroscope.cs
new file mode 100644
index 000000000..5ec3380dc
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_1/Sensors/Gyroscope.cs
@@ -0,0 +1,43 @@
+
+using System;
+
+namespace PlatformIntegration.Sensors
+{
+ public class GyroscopeTest
+ {
+ public void ToggleGyroscope()
+ {
+ const SensorSpeed speed = SensorSpeed.UI;
+
+ try
+ {
+ if (Gyroscope.IsMonitoring)
+ {
+ Gyroscope.Stop();
+ Gyroscope.ReadingChanged -= Gyroscope_ReadingChanged;
+ }
+ else
+ {
+ Gyroscope.ReadingChanged += Gyroscope_ReadingChanged;
+ Gyroscope.Start(speed);
+ }
+ }
+ catch (FeatureNotSupportedException fnsEx)
+ {
+ // Feature not supported on device
+ }
+ catch (Exception ex)
+ {
+ // Other error has occurred.
+ }
+ }
+
+ void Gyroscope_ReadingChanged(object sender, GyroscopeChangedEventArgs e)
+ {
+ GyroscopeData data = e.Reading;
+
+ // Process Angular Velocity X, Y, and Z reported in rad/s
+ Console.WriteLine($"Reading: X: {data.AngularVelocity.X}, Y: {data.AngularVelocity.Y}, Z: {data.AngularVelocity.Z}");
+ }
+ }
+}
diff --git a/docs/platform-integration/snippets/shared_1/Sensors/Magnetometer.cs b/docs/platform-integration/snippets/shared_1/Sensors/Magnetometer.cs
new file mode 100644
index 000000000..110f375b0
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_1/Sensors/Magnetometer.cs
@@ -0,0 +1,43 @@
+
+using System;
+
+namespace PlatformIntegration.Sensors
+{
+ public class MagnetometerTest
+ {
+ public void ToggleMagnetometer()
+ {
+ const SensorSpeed speed = SensorSpeed.UI;
+
+ try
+ {
+ if (Magnetometer.IsMonitoring)
+ {
+ Magnetometer.Stop();
+ Magnetometer.ReadingChanged -= Magnetometer_ReadingChanged;
+ }
+ else
+ {
+ Magnetometer.ReadingChanged += Magnetometer_ReadingChanged;
+ Magnetometer.Start(speed);
+ }
+ }
+ catch (FeatureNotSupportedException fnsEx)
+ {
+ // Feature not supported on device
+ }
+ catch (Exception ex)
+ {
+ // Other error has occurred.
+ }
+ }
+
+ void Magnetometer_ReadingChanged(object sender, MagnetometerChangedEventArgs e)
+ {
+ MagnetometerData data = e.Reading;
+
+ // Process MagneticField X, Y, and Z
+ Console.WriteLine($"Reading: X: {data.MagneticField.X}, Y: {data.MagneticField.Y}, Z: {data.MagneticField.Z}");
+ }
+ }
+}
diff --git a/docs/platform-integration/snippets/shared_1/Sensors/Orientation.cs b/docs/platform-integration/snippets/shared_1/Sensors/Orientation.cs
new file mode 100644
index 000000000..73850b63e
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_1/Sensors/Orientation.cs
@@ -0,0 +1,43 @@
+
+using System;
+
+namespace PlatformIntegration.Sensors
+{
+ public class OrientationTest
+ {
+ public void ToggleOrientation()
+ {
+ const SensorSpeed speed = SensorSpeed.UI;
+
+ try
+ {
+ if (OrientationSensor.IsMonitoring)
+ {
+ OrientationSensor.Stop();
+ OrientationSensor.ReadingChanged -= OrientationSensor_ReadingChanged;
+ }
+ else
+ {
+ OrientationSensor.ReadingChanged += OrientationSensor_ReadingChanged;
+ OrientationSensor.Start(speed);
+ }
+ }
+ catch (FeatureNotSupportedException fnsEx)
+ {
+ // Feature not supported on device
+ }
+ catch (Exception ex)
+ {
+ // Other error has occurred.
+ }
+ }
+
+ void OrientationSensor_ReadingChanged(object sender, OrientationSensorChangedEventArgs e)
+ {
+ OrientationSensorData data = e.Reading;
+
+ // Process Orientation quaternion (X, Y, Z, and W)
+ Console.WriteLine($"Reading: X: {data.Orientation.X}, Y: {data.Orientation.Y}, Z: {data.Orientation.Z}");
+ }
+ }
+}
diff --git a/docs/platform-integration/snippets/shared_1/Sensors/Shake.cs b/docs/platform-integration/snippets/shared_1/Sensors/Shake.cs
new file mode 100644
index 000000000..c35fda5cb
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_1/Sensors/Shake.cs
@@ -0,0 +1,41 @@
+
+using System;
+
+namespace PlatformIntegration.Sensors
+{
+ public class ShakeTest
+ {
+ public void ToggleAccelerometer()
+ {
+ const SensorSpeed speed = SensorSpeed.Game;
+
+ try
+ {
+ if (Accelerometer.IsMonitoring)
+ {
+ Accelerometer.Stop();
+ Accelerometer.ShakeDetected -= Accelerometer_ShakeDetected;
+ }
+ else
+ {
+ Accelerometer.ShakeDetected += Accelerometer_ShakeDetected;
+ Accelerometer.Start(speed);
+ }
+ }
+ catch (FeatureNotSupportedException fnsEx)
+ {
+ // Feature not supported on device
+ }
+ catch (Exception ex)
+ {
+ // Other error has occurred.
+ }
+ }
+
+ void Accelerometer_ShakeDetected(object sender, EventArgs e)
+ {
+ // Process shake event
+ Console.WriteLine("Device shake detected!");
+ }
+ }
+}
diff --git a/docs/platform-integration/snippets/shared_1/SensorsPage.xaml b/docs/platform-integration/snippets/shared_1/SensorsPage.xaml
new file mode 100644
index 000000000..5140f63d6
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_1/SensorsPage.xaml
@@ -0,0 +1,50 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/platform-integration/snippets/shared_1/SensorsPage.xaml.cs b/docs/platform-integration/snippets/shared_1/SensorsPage.xaml.cs
new file mode 100644
index 000000000..9d7e9d29d
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_1/SensorsPage.xaml.cs
@@ -0,0 +1,403 @@
+namespace PlatformIntegration;
+
+public partial class SensorsPage : ContentPage
+{
+ public SensorsPage()
+ {
+ InitializeComponent();
+ }
+
+ private void ContentPage_Loaded(object sender, EventArgs e)
+ {
+
+ }
+
+ #region Accelerometer
+ private void AccelSwitch_Toggled(object sender, ToggledEventArgs e)
+ {
+ ToggleAccelerometer(Accelerometer.Default);
+ }
+
+ //
+ public void ToggleAccelerometer(IAccelerometer accelerometer)
+ {
+ if (accelerometer.IsSupported)
+ {
+ if (!accelerometer.IsMonitoring)
+ {
+ // Turn on accelerometer
+ accelerometer.ReadingChanged += Accelerometer_ReadingChanged;
+ accelerometer.Start(SensorSpeed.UI);
+ }
+ else
+ {
+ // Turn off accelerometer
+ accelerometer.Stop();
+ accelerometer.ReadingChanged -= Accelerometer_ReadingChanged;
+ }
+ }
+ }
+
+ private void Accelerometer_ReadingChanged(object sender, AccelerometerChangedEventArgs e)
+ {
+ // Update UI Label with accelerometer state
+ AccelLabel.TextColor = Colors.Green;
+ AccelLabel.Text = $"Accel: {e.Reading}";
+ }
+ //
+ #endregion
+
+ #region Barometer
+ private void BarometerSwitch_Toggled(object sender, ToggledEventArgs e)
+ {
+ ToggleBarometer(Barometer.Default);
+ }
+
+ //
+ public void ToggleBarometer(IBarometer barometer)
+ {
+ if (barometer.IsSupported)
+ {
+ if (!barometer.IsMonitoring)
+ {
+ // Turn on accelerometer
+ barometer.ReadingChanged += Barometer_ReadingChanged;
+ barometer.Start(SensorSpeed.UI);
+ }
+ else
+ {
+ // Turn off accelerometer
+ barometer.Stop();
+ barometer.ReadingChanged -= Barometer_ReadingChanged;
+ }
+ }
+ }
+
+ private void Barometer_ReadingChanged(object sender, BarometerChangedEventArgs e)
+ {
+ // Update UI Label with barometer state
+ BarometerLabel.TextColor = Colors.Green;
+ BarometerLabel.Text = $"Barometer: {e.Reading}";
+ }
+ //
+ #endregion
+
+ #region Compass
+ private void CompassSwitch_Toggled(object sender, ToggledEventArgs e)
+ {
+ ToggleCompass(Compass.Default);
+ }
+
+ //
+ private void ToggleCompass(ICompass compass)
+ {
+ if (compass.IsSupported)
+ {
+ if (!compass.IsMonitoring)
+ {
+ // Turn on compass
+ compass.ReadingChanged += Compass_ReadingChanged;
+ compass.Start(SensorSpeed.UI);
+ }
+ else
+ {
+ // Turn off compass
+ compass.Stop();
+ compass.ReadingChanged -= Compass_ReadingChanged;
+ }
+ }
+ }
+
+ private void Compass_ReadingChanged(object sender, CompassChangedEventArgs e)
+ {
+ // Update UI Label with compass state
+ CompassLabel.TextColor = Colors.Green;
+ CompassLabel.Text = $"Compass: {e.Reading}";
+ }
+ //
+ #endregion
+
+ #region Gyroscope
+ private void GyroscopeSwitch_Toggled(object sender, ToggledEventArgs e)
+ {
+ ToggleGyroscope(Gyroscope.Default);
+ }
+
+ //
+ private void ToggleGyroscope(IGyroscope gyroscope)
+ {
+ if (gyroscope.IsSupported)
+ {
+ if (!gyroscope.IsMonitoring)
+ {
+ // Turn on compass
+ gyroscope.ReadingChanged += Gyroscope_ReadingChanged;
+ gyroscope.Start(SensorSpeed.UI);
+ }
+ else
+ {
+ // Turn off compass
+ gyroscope.Stop();
+ gyroscope.ReadingChanged -= Gyroscope_ReadingChanged;
+ }
+ }
+ }
+
+ private void Gyroscope_ReadingChanged(object sender, GyroscopeChangedEventArgs e)
+ {
+ // Update UI Label with gyroscope state
+ GyroscopeLabel.TextColor = Colors.Green;
+ GyroscopeLabel.Text = $"Gyroscope: {e.Reading}";
+ }
+ //
+ #endregion
+
+ #region Magnetometer
+ private void MagnetometerSwitch_Toggled(object sender, ToggledEventArgs e)
+ {
+ ToggleMagnetometer(Magnetometer.Default);
+ }
+
+ //
+ private void ToggleMagnetometer(IMagnetometer magnetometer)
+ {
+ if (magnetometer.IsSupported)
+ {
+ if (!magnetometer.IsMonitoring)
+ {
+ // Turn on compass
+ magnetometer.ReadingChanged += Magnetometer_ReadingChanged;
+ magnetometer.Start(SensorSpeed.UI);
+ }
+ else
+ {
+ // Turn off compass
+ magnetometer.Stop();
+ magnetometer.ReadingChanged -= Magnetometer_ReadingChanged;
+ }
+ }
+ }
+
+ private void Magnetometer_ReadingChanged(object sender, MagnetometerChangedEventArgs e)
+ {
+ // Update UI Label with magnetometer state
+ MagnetometerLabel.TextColor = Colors.Green;
+ MagnetometerLabel.Text = $"Magnetometer: {e.Reading}";
+ }
+ //
+ #endregion
+
+ #region Orientation
+ private void OrientationSwitch_Toggled(object sender, ToggledEventArgs e)
+ {
+ ToggleOrientation(OrientationSensor.Default);
+ }
+
+ //
+ private void ToggleOrientation(IOrientationSensor orientation)
+ {
+ if (orientation.IsSupported)
+ {
+ if (!orientation.IsMonitoring)
+ {
+ // Turn on compass
+ orientation.ReadingChanged += Orientation_ReadingChanged;
+ orientation.Start(SensorSpeed.UI);
+ }
+ else
+ {
+ // Turn off compass
+ orientation.Stop();
+ orientation.ReadingChanged -= Orientation_ReadingChanged;
+ }
+ }
+ }
+
+ private void Orientation_ReadingChanged(object sender, OrientationSensorChangedEventArgs e)
+ {
+ // Update UI Label with orientation state
+ OrientationLabel.TextColor = Colors.Green;
+ OrientationLabel.Text = $"Orientation: {e.Reading}";
+ }
+ //
+ #endregion
+
+ #region Shake
+ private void ShakeSwitch_Toggled(object sender, ToggledEventArgs e)
+ {
+ ToggleShake(Accelerometer.Default);
+ }
+
+ //
+ private void ToggleShake(IAccelerometer accelerometer)
+ {
+ if (accelerometer.IsSupported)
+ {
+ if (!accelerometer.IsMonitoring)
+ {
+ // Turn on compass
+ accelerometer.ShakeDetected += Accelerometer_ShakeDetected;
+ accelerometer.Start(SensorSpeed.Game);
+ }
+ else
+ {
+ // Turn off compass
+ accelerometer.Stop();
+ accelerometer.ShakeDetected -= Accelerometer_ShakeDetected;
+ }
+ }
+ }
+
+ private void Accelerometer_ShakeDetected(object sender, EventArgs e)
+ {
+ // Update UI Label with a "shaked detected" notice, in a randomized color
+ ShakeLabel.TextColor = new Color(Random.Shared.Next(256), Random.Shared.Next(256), Random.Shared.Next(256));
+ ShakeLabel.Text = $"Shake detected";
+ }
+ //
+ #endregion
+
+ #region Geocoding
+ private async void Geocoding_Clicked(object sender, EventArgs e)
+ {
+ //
+ string address = "Microsoft Building 25 Redmond WA USA";
+ IEnumerable locations = await Geocoding.Default.GetLocationsAsync(address);
+
+ Location location = locations?.FirstOrDefault();
+
+ if (location != null)
+ Console.WriteLine($"Latitude: {location.Latitude}, Longitude: {location.Longitude}, Altitude: {location.Altitude}");
+ //
+ }
+
+ //
+ private async Task GetGeocodeReverseData(double latitude = 47.673988, double longitude = -122.121513)
+ {
+ IEnumerable placemarks = await Geocoding.Default.GetPlacemarksAsync(latitude, longitude);
+
+ Placemark placemark = placemarks?.FirstOrDefault();
+
+ if (placemark != null)
+ {
+ return
+ $"AdminArea: {placemark.AdminArea}\n" +
+ $"CountryCode: {placemark.CountryCode}\n" +
+ $"CountryName: {placemark.CountryName}\n" +
+ $"FeatureName: {placemark.FeatureName}\n" +
+ $"Locality: {placemark.Locality}\n" +
+ $"PostalCode: {placemark.PostalCode}\n" +
+ $"SubAdminArea: {placemark.SubAdminArea}\n" +
+ $"SubLocality: {placemark.SubLocality}\n" +
+ $"SubThoroughfare: {placemark.SubThoroughfare}\n" +
+ $"Thoroughfare: {placemark.Thoroughfare}\n";
+
+ }
+
+ return "";
+ }
+ //
+ #endregion
+
+ #region Geolocation
+
+ private async void Geolocation1_Clicked(object sender, EventArgs e)
+ {
+ LastLocationLabel.Text = await GetCachedLocation();
+ }
+
+ //
+ public async Task GetCachedLocation()
+ {
+ try
+ {
+ Location location = await Geolocation.Default.GetLastKnownLocationAsync();
+
+ if (location != null)
+ return $"Latitude: {location.Latitude}, Longitude: {location.Longitude}, Altitude: {location.Altitude}";
+ }
+ catch (FeatureNotSupportedException fnsEx)
+ {
+ // Handle not supported on device exception
+ }
+ catch (FeatureNotEnabledException fneEx)
+ {
+ // Handle not enabled on device exception
+ }
+ catch (PermissionException pEx)
+ {
+ // Handle permission exception
+ }
+ catch (Exception ex)
+ {
+ // Unable to get location
+ }
+
+ return "None";
+ }
+ //
+
+ //
+ private CancellationTokenSource _cancelTokenSource;
+ private bool _isCheckingLocation;
+
+ public async Task GetCurrentLocation()
+ {
+ try
+ {
+ _isCheckingLocation = true;
+
+ GeolocationRequest request = new GeolocationRequest(GeolocationAccuracy.Medium, TimeSpan.FromSeconds(10));
+
+ _cancelTokenSource = new CancellationTokenSource();
+
+ Location location = await Geolocation.Default.GetLocationAsync(request, _cancelTokenSource.Token);
+
+ if (location != null)
+ Console.WriteLine($"Latitude: {location.Latitude}, Longitude: {location.Longitude}, Altitude: {location.Altitude}");
+ }
+ // Catch one of the following exceptions:
+ // FeatureNotSupportedException
+ // FeatureNotEnabledException
+ // PermissionException
+ catch (Exception ex)
+ {
+ // Unable to get location
+ }
+ finally
+ {
+ _isCheckingLocation = false;
+ }
+ }
+
+ public void CancelRequest()
+ {
+ if (_isCheckingLocation && _cancelTokenSource != null && _cancelTokenSource.IsCancellationRequested == false)
+ _cancelTokenSource.Cancel();
+ }
+ //
+
+ //
+ public async Task CheckMock()
+ {
+ GeolocationRequest request = new GeolocationRequest(GeolocationAccuracy.Medium);
+ Location location = await Geolocation.Default.GetLocationAsync(request);
+
+ if (location != null && location.IsFromMockProvider)
+ {
+ // location is from a mock provider
+ }
+ }
+ //
+
+ public void CheckDistance()
+ {
+ //
+ Location boston = new Location(42.358056, -71.063611);
+ Location sanFrancisco = new Location(37.783333, -122.416667);
+
+ double miles = Location.CalculateDistance(boston, sanFrancisco, DistanceUnits.Miles);
+ //
+ }
+ #endregion
+}
\ No newline at end of file
diff --git a/docs/platform-integration/snippets/shared_1/Storage.cs b/docs/platform-integration/snippets/shared_1/Storage.cs
new file mode 100644
index 000000000..4b28177cb
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_1/Storage.cs
@@ -0,0 +1,146 @@
+namespace PlatformIntegration;
+
+public class StoragePage : ContentPage
+{
+ public ImageSource ImageItem
+ {
+ get { return (ImageSource)GetValue(ImageItemProperty); }
+ set { SetValue(ImageItemProperty, value); }
+ }
+
+ public static readonly BindableProperty ImageItemProperty =
+ BindableProperty.Create("ImageItem", typeof(ImageSource), typeof(MediaPage), null);
+
+ public StoragePage()
+ {
+ this.BindingContext = this;
+
+ Content = new VerticalStackLayout
+ {
+ };
+ }
+
+ //
+ public async Task PickAndShow(PickOptions options)
+ {
+ try
+ {
+ var result = await FilePicker.Default.PickAsync(options);
+ if (result != null)
+ {
+ if (result.FileName.EndsWith("jpg", StringComparison.OrdinalIgnoreCase) ||
+ result.FileName.EndsWith("png", StringComparison.OrdinalIgnoreCase))
+ {
+ using var stream = await result.OpenReadAsync();
+ var image = ImageSource.FromStream(() => stream);
+ }
+ }
+
+ return result;
+ }
+ catch (Exception ex)
+ {
+ // The user canceled or something went wrong
+ }
+
+ return null;
+ }
+ //
+
+ public void Nothing()
+ {
+ //
+ var customFileType = new FilePickerFileType(
+ new Dictionary>
+ {
+ { DevicePlatform.iOS, new[] { "public.my.comic.extension" } }, // or general UTType values
+ { DevicePlatform.Android, new[] { "application/comics" } },
+ { DevicePlatform.WinUI, new[] { ".cbr", ".cbz" } },
+ { DevicePlatform.Tizen, new[] { "*/*" } },
+ { DevicePlatform.macOS, new[] { "cbr", "cbz" } }, // or general UTType values
+ });
+
+ PickOptions options = new()
+ {
+ PickerTitle = "Please select a comic file",
+ FileTypes = customFileType,
+ };
+ //
+ }
+
+ public void FileSystem1()
+ {
+ //
+ string cacheDir = FileSystem.Current.CacheDirectory;
+ //
+ //
+ string mainDir = FileSystem.Current.AppDataDirectory;
+ //
+ }
+
+ //
+ public async Task ReadTextFile(string filePath)
+ {
+ using Stream fileStream = await FileSystem.Current.OpenAppPackageFileAsync(filePath);
+ using StreamReader reader = new StreamReader(fileStream);
+
+ return await reader.ReadToEndAsync();
+ }
+ //
+
+ public void PreferencesSet()
+ {
+ //
+ // Set a string value:
+ Preferences.Default.Set("first_name", "John");
+
+ // Set an numerical value:
+ Preferences.Default.Set("age", 28);
+
+ // Set a boolean value:
+ Preferences.Default.Set("has_pets", true);
+ //
+
+ //
+ string firstName = Preferences.Get("first_name", "Unknown");
+ int age = Preferences.Get("age", -1);
+ bool hasPets = Preferences.Get("has_pets", false);
+ //
+
+ //
+ bool hasKey = Preferences.ContainsKey("my_key");
+ //
+
+ //
+ Preferences.Remove("first_name");
+ //
+
+ //
+ Preferences.Clear();
+ //
+ }
+
+ public async Task Storage()
+ {
+ //
+ await SecureStorage.Default.SetAsync("oauth_token", "secret-oauth-token-value");
+ //
+
+ //
+ string oauthToken = await SecureStorage.Default.GetAsync("oauth_token");
+
+ if (oauthToken == null)
+ {
+ // No value is associated with the key "oauth_token"
+ }
+ //
+
+ //
+ bool success = SecureStorage.Default.Remove("oauth_token");
+ //
+
+ //
+ SecureStorage.Default.RemoveAll();
+ //
+ }
+}
\ No newline at end of file
diff --git a/docs/platform-integration/snippets/shared_1/WebAuthPage.cs b/docs/platform-integration/snippets/shared_1/WebAuthPage.cs
new file mode 100644
index 000000000..898abd7e3
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_1/WebAuthPage.cs
@@ -0,0 +1,128 @@
+namespace PlatformIntegration;
+
+public class WebAuthPage : ContentPage
+{
+ public ImageSource ImageItem
+ {
+ get { return (ImageSource)GetValue(ImageItemProperty); }
+ set { SetValue(ImageItemProperty, value); }
+ }
+
+ public static readonly BindableProperty ImageItemProperty =
+ BindableProperty.Create("ImageItem", typeof(ImageSource), typeof(MediaPage), null);
+
+
+ public WebAuthPage()
+ {
+ this.BindingContext = this;
+
+ var imageControl = new Image();
+ imageControl.SetBinding(Image.SourceProperty, new Binding("ImageItem"));
+
+ Content = new VerticalStackLayout
+ {
+ imageControl,
+ new Button { Text = "Take photo",
+ Command = new Command(TakePhoto) },
+ new Button { Text = "Take screenshot",
+ Command = new Command(() => { ImageItem = TakeScreenshot().Result; }) },
+ new Button { Text = "Text to speech",
+ Command = new Command(Speak) },
+ };
+ }
+
+ //
+ public async void TakePhoto()
+ {
+ if (MediaPicker.Default.IsCaptureSupported)
+ {
+ FileResult photo = await MediaPicker.Default.CapturePhotoAsync();
+
+ if (photo != null)
+ {
+ // save the file into local storage
+ string localFilePath = Path.Combine(FileSystem.CacheDirectory, photo.FileName);
+
+ using Stream sourceStream = await photo.OpenReadAsync();
+ using FileStream localFileStream = File.OpenWrite(localFilePath);
+
+ await sourceStream.CopyToAsync(localFileStream);
+ }
+ }
+ }
+ //
+
+ //
+ public async Task TakeScreenshot()
+ {
+ if (Screenshot.Default.IsCaptureSupported)
+ {
+ IScreenshotResult screen = await Screenshot.Default.CaptureAsync();
+
+ Stream stream = await screen.OpenReadAsync();
+
+ return ImageSource.FromStream(() => stream);
+ }
+
+ return null;
+ }
+ //
+
+ //
+ public async void Speak() =>
+ await TextToSpeech.Default.SpeakAsync("Hello World");
+ //
+
+ //
+ public async void SpeakSettings()
+ {
+ IEnumerable locales = await TextToSpeech.Default.GetLocalesAsync();
+
+ SpeechOptions options = new SpeechOptions()
+ {
+ Pitch = 1.5f, // 0.0 - 2.0
+ Volume = 0.75f, // 0.0 - 1.0
+ Locale = locales.FirstOrDefault()
+ };
+
+ await TextToSpeech.Default.SpeakAsync("How nice to meet you!", options);
+ }
+ //
+
+ //
+ CancellationTokenSource cts;
+
+ public async Task SpeakNowDefaultSettings()
+ {
+ cts = new CancellationTokenSource();
+ await TextToSpeech.Default.SpeakAsync("Hello World", cancelToken: cts.Token);
+
+ // This method will block until utterance finishes.
+ }
+
+ // Cancel speech if a cancellation token exists & hasn't been already requested.
+ public void CancelSpeech()
+ {
+ if (cts?.IsCancellationRequested ?? true)
+ return;
+
+ cts.Cancel();
+ }
+ //
+
+
+ //
+ bool isBusy = false;
+
+ public void SpeakMultiple()
+ {
+ isBusy = true;
+
+ Task.WhenAll(
+ TextToSpeech.Default.SpeakAsync("Hello World 1"),
+ TextToSpeech.Default.SpeakAsync("Hello World 2"),
+ TextToSpeech.Default.SpeakAsync("Hello World 3"))
+ .ContinueWith((t) => { isBusy = false; }, TaskScheduler.FromCurrentSynchronizationContext());
+ }
+ //
+}
\ No newline at end of file
diff --git a/docs/platform-integration/snippets/shared_2/App.xaml b/docs/platform-integration/snippets/shared_2/App.xaml
new file mode 100644
index 000000000..88422b344
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_2/App.xaml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
diff --git a/docs/platform-integration/snippets/shared_2/App.xaml.cs b/docs/platform-integration/snippets/shared_2/App.xaml.cs
new file mode 100644
index 000000000..febd1f137
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_2/App.xaml.cs
@@ -0,0 +1,29 @@
+namespace PlatformIntegration;
+
+public partial class App : Application
+{
+ //
+ public App()
+ {
+ InitializeComponent();
+
+ MainPage = new AppShell();
+
+ AppActions.Current.AppActionActivated += App_AppActionActivated;
+ }
+
+ private void App_AppActionActivated(object sender, AppActionEventArgs e)
+ {
+ // If the app instance this code is running in is not the current app instance,
+ // remove the handler and return.
+ if (Application.Current != this && Application.Current is App app)
+ AppActions.Current.AppActionActivated -= app.App_AppActionActivated;
+ {
+ MainThread.BeginInvokeOnMainThread(async () =>
+ {
+ await Shell.Current.GoToAsync($"//{e.AppAction.Id}");
+ });
+ }
+ }
+ //
+}
diff --git a/docs/platform-integration/snippets/shared_2/AppActionPlatformIntegration.csproj b/docs/platform-integration/snippets/shared_2/AppActionPlatformIntegration.csproj
new file mode 100644
index 000000000..ac8687357
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_2/AppActionPlatformIntegration.csproj
@@ -0,0 +1,66 @@
+
+
+
+ net6.0-android;net6.0-ios;net6.0-maccatalyst
+ $(TargetFrameworks);net6.0-windows10.0.19041.0
+
+
+ Exe
+ PlatformIntegration
+ true
+ true
+ enable
+
+
+ PlatformIntegration
+
+
+ com.companyname.platformintegration
+ B668375E-5F70-4844-BB98-583AB74C06E2
+
+
+ 1.0
+ 1
+
+ 14.2
+ 14.0
+ 21.0
+ 10.0.17763.0
+ 10.0.17763.0
+ 6.5
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ SensorsPage.xaml
+
+
+
+
+
+ MSBuild:Compile
+
+
+ MSBuild:Compile
+
+
+
+
diff --git a/docs/platform-integration/snippets/shared_2/AppActionPlatformIntegration.csproj.user b/docs/platform-integration/snippets/shared_2/AppActionPlatformIntegration.csproj.user
new file mode 100644
index 000000000..683b5b9e6
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_2/AppActionPlatformIntegration.csproj.user
@@ -0,0 +1,19 @@
+
+
+
+ net6.0-android
+ False
+ Pixel 3a - API 30 (Android 11.0 - API 30)
+ pixel_3a_-_api_30
+ Emulator
+ pixel_3a_-_api_30
+
+
+ ProjectDebugger
+
+
+
+ Designer
+
+
+
\ No newline at end of file
diff --git a/docs/platform-integration/snippets/shared_2/AppActionPlatformIntegration.sln b/docs/platform-integration/snippets/shared_2/AppActionPlatformIntegration.sln
new file mode 100644
index 000000000..5d6d2152f
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_2/AppActionPlatformIntegration.sln
@@ -0,0 +1,27 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.0.31611.283
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AppActionPlatformIntegration", "AppActionPlatformIntegration.csproj", "{C719BBF0-D151-46D4-9270-962DCE07C3BA}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {C719BBF0-D151-46D4-9270-962DCE07C3BA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {C719BBF0-D151-46D4-9270-962DCE07C3BA}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {C719BBF0-D151-46D4-9270-962DCE07C3BA}.Debug|Any CPU.Deploy.0 = Debug|Any CPU
+ {C719BBF0-D151-46D4-9270-962DCE07C3BA}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {C719BBF0-D151-46D4-9270-962DCE07C3BA}.Release|Any CPU.Build.0 = Release|Any CPU
+ {C719BBF0-D151-46D4-9270-962DCE07C3BA}.Release|Any CPU.Deploy.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {61F7FB11-1E47-470C-91E2-47F8143E1572}
+ EndGlobalSection
+EndGlobal
diff --git a/docs/platform-integration/snippets/shared_2/AppShell.xaml b/docs/platform-integration/snippets/shared_2/AppShell.xaml
new file mode 100644
index 000000000..6067d8c7f
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_2/AppShell.xaml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
diff --git a/docs/platform-integration/snippets/shared_2/AppShell.xaml.cs b/docs/platform-integration/snippets/shared_2/AppShell.xaml.cs
new file mode 100644
index 000000000..820bb28cd
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_2/AppShell.xaml.cs
@@ -0,0 +1,9 @@
+namespace PlatformIntegration;
+
+public partial class AppShell : Shell
+{
+ public AppShell()
+ {
+ InitializeComponent();
+ }
+}
diff --git a/docs/platform-integration/snippets/shared_2/Features/BrowserTest.cs b/docs/platform-integration/snippets/shared_2/Features/BrowserTest.cs
new file mode 100644
index 000000000..e72e17a51
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_2/Features/BrowserTest.cs
@@ -0,0 +1,24 @@
+using Microsoft.Maui.Controls.PlatformConfiguration;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace PlatformIntegration.Features
+{
+ public class BrowserTest
+ {
+ public async Task OpenBrowser(Uri uri)
+ {
+ try
+ {
+ await Browser.OpenAsync(uri, BrowserLaunchMode.SystemPreferred);
+ }
+ catch (Exception ex)
+ {
+ // An unexpected error occured. No browser may be installed on the device.
+ }
+ }
+ }
+}
diff --git a/docs/platform-integration/snippets/shared_2/Features/ClipboardTest.cs b/docs/platform-integration/snippets/shared_2/Features/ClipboardTest.cs
new file mode 100644
index 000000000..4555a8874
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_2/Features/ClipboardTest.cs
@@ -0,0 +1,34 @@
+using Microsoft.Maui.Controls.PlatformConfiguration;
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace PlatformIntegration.Features
+{
+ public class ClipboardTest
+ {
+ public ClipboardTest()
+ {
+ // Register for clipboard changes, be sure to unsubscribe when needed
+ Clipboard.ClipboardContentChanged += OnClipboardContentChanged;
+ }
+
+ void OnClipboardContentChanged(object sender, EventArgs e)
+ {
+ Console.WriteLine($"Last clipboard change at {DateTime.UtcNow:T}");
+ }
+
+ public async void SetText()
+ {
+ await Clipboard.SetTextAsync("Hello World");
+ }
+
+ public async void GetText()
+ {
+ string text = await Clipboard.GetTextAsync();
+ }
+ }
+}
diff --git a/docs/platform-integration/snippets/shared_2/Features/ConnectivityTest.cs b/docs/platform-integration/snippets/shared_2/Features/ConnectivityTest.cs
new file mode 100644
index 000000000..f5b18940c
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_2/Features/ConnectivityTest.cs
@@ -0,0 +1,54 @@
+using Microsoft.Maui.Controls.PlatformConfiguration;
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace PlatformIntegration.Features
+{
+ public class ConnectivityTest
+ {
+ public ConnectivityTest()
+ {
+ // Register for connectivity changes, be sure to unsubscribe when finished
+ Connectivity.ConnectivityChanged += Connectivity_ConnectivityChanged;
+ }
+
+ void Connectivity_ConnectivityChanged(object sender, ConnectivityChangedEventArgs e)
+ {
+ if (e.NetworkAccess == NetworkAccess.ConstrainedInternet)
+ Console.WriteLine("Internet access is available but is limited.");
+
+ else if (e.NetworkAccess != NetworkAccess.Internet)
+ Console.WriteLine("Internet access has been lost.");
+
+ // Log each active connection
+ Console.Write("Connections active: ");
+
+ foreach (var item in e.ConnectionProfiles)
+ {
+ switch (item)
+ {
+ case ConnectionProfile.Bluetooth:
+ Console.Write("Bluetooth");
+ break;
+ case ConnectionProfile.Cellular:
+ Console.Write("Cell");
+ break;
+ case ConnectionProfile.Ethernet:
+ Console.Write("Ethernet");
+ break;
+ case ConnectionProfile.WiFi:
+ Console.Write("WiFi");
+ break;
+ default:
+ break;
+ }
+ }
+
+ Console.WriteLine();
+ }
+ }
+}
diff --git a/docs/platform-integration/snippets/shared_2/Features/ContactsTest.cs b/docs/platform-integration/snippets/shared_2/Features/ContactsTest.cs
new file mode 100644
index 000000000..3883f488e
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_2/Features/ContactsTest.cs
@@ -0,0 +1,68 @@
+using Microsoft.Maui.Controls.PlatformConfiguration;
+
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Linq;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace PlatformIntegration.Features
+{
+ public class ContactsTest
+ {
+ public ContactsTest()
+ {
+ }
+
+ public async void PickContact()
+ {
+ try
+ {
+ // TODO: Wont compile
+ //var contact = await Contacts.PickContactAsync();
+
+ //if (contact == null)
+ // return;
+
+ //var id = contact.Id;
+ //var namePrefix = contact.NamePrefix;
+ //var givenName = contact.GivenName;
+ //var middleName = contact.MiddleName;
+ //var familyName = contact.FamilyName;
+ //var nameSuffix = contact.NameSuffix;
+ //var displayName = contact.DisplayName;
+ //var phones = contact.Phones; // List of phone numbers
+ //var emails = contact.Emails; // List of email addresses
+ }
+ catch (Exception ex)
+ {
+ // Handle exception here.
+ }
+ }
+
+ public async void GetAllContacts()
+ {
+ ObservableCollection contactsCollect = new ObservableCollection();
+
+ try
+ {
+ // TODO: Wont compile
+ //// cancellationToken parameter is optional
+ //var cancellationToken = default(CancellationToken);
+ //var contacts = await Contacts.GetAllAsync(cancellationToken);
+
+ //if (contacts == null)
+ // return;
+
+ //foreach (var contact in contacts)
+ // contactsCollect.Add(contact);
+ }
+ catch (Exception ex)
+ {
+ // Handle exception here.
+ }
+ }
+ }
+}
diff --git a/docs/platform-integration/snippets/shared_2/Features/DeviceInfoTest.cs b/docs/platform-integration/snippets/shared_2/Features/DeviceInfoTest.cs
new file mode 100644
index 000000000..106a21434
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_2/Features/DeviceInfoTest.cs
@@ -0,0 +1,58 @@
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace PlatformIntegration.Features
+{
+ public class DeviceInfoTest
+ {
+ public void ReadInfo()
+ {
+ // Device Model (SMG-950U, iPhone10,6)
+ string device = DeviceInfo.Model;
+
+ // Manufacturer (Samsung)
+ string manufacturer = DeviceInfo.Manufacturer;
+
+ // Device Name (Motz's iPhone)
+ string deviceName = DeviceInfo.Name;
+
+ // Operating System Version Number (7.0)
+ string version = DeviceInfo.VersionString;
+
+ // Platform (Android)
+ DevicePlatform platform = DeviceInfo.Platform;
+
+ // Idiom (Phone)
+ DeviceIdiom idiom = DeviceInfo.Idiom;
+
+ // Device Type (Physical)
+ DeviceType deviceType = DeviceInfo.DeviceType;
+ }
+
+ public void WritePlatform()
+ {
+ if (DeviceInfo.Platform == DevicePlatform.Android)
+ Console.WriteLine("The current OS is Android");
+ else if (DeviceInfo.Platform == DevicePlatform.iOS)
+ Console.WriteLine("The current OS is iOS");
+ else if (DeviceInfo.Platform == DevicePlatform.WinUI)
+ Console.WriteLine("The current OS is Windows");
+ else if (DeviceInfo.Platform == DevicePlatform.Tizen)
+ Console.WriteLine("The current OS is Tizen");
+ }
+
+ public void WriteIdiom()
+ {
+ if (DeviceInfo.Idiom == DeviceIdiom.Desktop)
+ Console.WriteLine("The current device is a desktop");
+ else if (DeviceInfo.Idiom == DeviceIdiom.Phone)
+ Console.WriteLine("The current device is a phone");
+ else if (DeviceInfo.Idiom == DeviceIdiom.Tablet)
+ Console.WriteLine("The current device is a Tablet");
+ }
+ }
+}
diff --git a/docs/platform-integration/snippets/shared_2/Features/DisplayInfoTest.cs b/docs/platform-integration/snippets/shared_2/Features/DisplayInfoTest.cs
new file mode 100644
index 000000000..e4c52d7d2
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_2/Features/DisplayInfoTest.cs
@@ -0,0 +1,53 @@
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace PlatformIntegration.Features
+{
+ public partial class DisplayInfoTest
+ {
+ public DisplayInfoTest()
+ {
+ // Subscribe to changes of screen metrics
+ DeviceDisplay.MainDisplayInfoChanged += OnMainDisplayInfoChanged;
+ }
+
+ void OnMainDisplayInfoChanged(object sender, DisplayInfoChangedEventArgs e)
+ {
+ // Process changes
+ DisplayInfo displayInfo = e.DisplayInfo;
+ }
+ }
+
+ public partial class DisplayInfoTest
+ {
+ public void ReadInfo()
+ {
+ // Get Metrics
+ DisplayInfo mainDisplayInfo = DeviceDisplay.MainDisplayInfo;
+
+ // Orientation (Landscape, Portrait, Square, Unknown)
+ DisplayOrientation orientation = mainDisplayInfo.Orientation;
+
+ // Rotation (0, 90, 180, 270)
+ DisplayRotation rotation = mainDisplayInfo.Rotation;
+
+ // Width (in pixels)
+ double width = mainDisplayInfo.Width;
+
+ // Height (in pixels)
+ double height = mainDisplayInfo.Height;
+
+ // Screen density
+ double density = mainDisplayInfo.Density;
+ }
+
+ public void ToggleScreenLock()
+ {
+ DeviceDisplay.KeepScreenOn = !DeviceDisplay.KeepScreenOn;
+ }
+ }
+}
diff --git a/docs/platform-integration/snippets/shared_2/Features/FIlePickerTest.cs b/docs/platform-integration/snippets/shared_2/Features/FIlePickerTest.cs
new file mode 100644
index 000000000..9fcf330d1
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_2/Features/FIlePickerTest.cs
@@ -0,0 +1,57 @@
+using Microsoft.Maui.Controls;
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using static System.Net.Mime.MediaTypeNames;
+
+namespace PlatformIntegration.Features
+{
+ class FilePickerTest
+ {
+ async Task PickAndShow(PickOptions options)
+ {
+ try
+ {
+ var result = await FilePicker.PickAsync(options);
+ if (result != null)
+ {
+ if (result.FileName.EndsWith("jpg", StringComparison.OrdinalIgnoreCase) ||
+ result.FileName.EndsWith("png", StringComparison.OrdinalIgnoreCase))
+ {
+ using var stream = await result.OpenReadAsync();
+ var image = ImageSource.FromStream(() => stream);
+ }
+ }
+
+ return result;
+ }
+ catch (Exception ex)
+ {
+ // The user canceled or something went wrong
+ }
+
+ return null;
+ }
+
+ public void DoStuff()
+ {
+ var customFileType = new FilePickerFileType(new Dictionary>
+ {
+ { DevicePlatform.iOS, new[] { "public.my.comic.extension" } }, // or general UTType values
+ { DevicePlatform.Android, new[] { "application/comics" } },
+ { DevicePlatform.WinUI, new[] { ".cbr", ".cbz" } },
+ { DevicePlatform.Tizen, new[] { "*/*" } },
+ { DevicePlatform.macOS, new[] { "cbr", "cbz" } }, // or general UTType values
+ });
+
+ PickOptions options = new()
+ {
+ PickerTitle = "Please select a comic file",
+ FileTypes = customFileType,
+ };
+ }
+ }
+}
diff --git a/docs/platform-integration/snippets/shared_2/Features/FileHelpersTest.cs b/docs/platform-integration/snippets/shared_2/Features/FileHelpersTest.cs
new file mode 100644
index 000000000..ab4ee5c6b
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_2/Features/FileHelpersTest.cs
@@ -0,0 +1,23 @@
+using System.IO;
+using System.Threading.Tasks;
+
+
+namespace PlatformIntegration.Features
+{
+ class FileHelpersTest
+ {
+ public async Task ReadTextFile(string filePath)
+ {
+ using Stream fileStream = await FileSystem.OpenAppPackageFileAsync(filePath);
+ using StreamReader reader = new StreamReader(fileStream);
+
+ return await reader.ReadToEndAsync();
+ }
+
+ public void GetFiles()
+ {
+ string cacheDir = FileSystem.CacheDirectory;
+ string mainDir = FileSystem.AppDataDirectory;
+ }
+ }
+}
diff --git a/docs/platform-integration/snippets/shared_2/Features/FlashlightTest.cs b/docs/platform-integration/snippets/shared_2/Features/FlashlightTest.cs
new file mode 100644
index 000000000..d2acfcfc6
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_2/Features/FlashlightTest.cs
@@ -0,0 +1,37 @@
+using System;
+using System.IO;
+using System.Threading.Tasks;
+
+
+namespace PlatformIntegration.Features
+{
+ class FlashlightTest
+ {
+ public async Task RunFlashlight()
+ {
+ try
+ {
+ // Turn On
+ await Flashlight.TurnOnAsync();
+
+ // Pause for 3 seconds
+ await Task.Delay(TimeSpan.FromSeconds(3));
+
+ // Turn Off
+ await Flashlight.TurnOffAsync();
+ }
+ catch (FeatureNotSupportedException fnsEx)
+ {
+ // Handle not supported on device exception
+ }
+ catch (PermissionException pEx)
+ {
+ // Handle permission exception
+ }
+ catch (Exception ex)
+ {
+ // Unable to turn on/off flashlight
+ }
+ }
+ }
+}
diff --git a/docs/platform-integration/snippets/shared_2/Features/GeocodingTest.cs b/docs/platform-integration/snippets/shared_2/Features/GeocodingTest.cs
new file mode 100644
index 000000000..8d24b46e3
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_2/Features/GeocodingTest.cs
@@ -0,0 +1,71 @@
+using System;
+using System.IO;
+using System.Linq;
+using System.Threading.Tasks;
+
+
+namespace PlatformIntegration.Features
+{
+ class GeocodingTest
+ {
+ public async Task TranslateLocation()
+ {
+ try
+ {
+ var address = "Microsoft Building 25 Redmond WA USA";
+ var locations = await Geocoding.GetLocationsAsync(address);
+
+ var location = locations?.FirstOrDefault();
+
+ if (location != null)
+ Console.WriteLine($"Latitude: {location.Latitude}, Longitude: {location.Longitude}, Altitude: {location.Altitude}");
+ }
+ catch (FeatureNotSupportedException fnsEx)
+ {
+ // Feature not supported on device
+ }
+ catch (Exception ex)
+ {
+ // Handle exception that may have occurred in geocoding
+ }
+ }
+
+ public async Task TranslateCoordinates()
+ {
+ try
+ {
+ var lat = 47.673988;
+ var lon = -122.121513;
+
+ var placemarks = await Geocoding.GetPlacemarksAsync(lat, lon);
+
+ var placemark = placemarks?.FirstOrDefault();
+
+ if (placemark != null)
+ {
+ var geocodeAddress =
+ $"AdminArea: {placemark.AdminArea}\n" +
+ $"CountryCode: {placemark.CountryCode}\n" +
+ $"CountryName: {placemark.CountryName}\n" +
+ $"FeatureName: {placemark.FeatureName}\n" +
+ $"Locality: {placemark.Locality}\n" +
+ $"PostalCode: {placemark.PostalCode}\n" +
+ $"SubAdminArea: {placemark.SubAdminArea}\n" +
+ $"SubLocality: {placemark.SubLocality}\n" +
+ $"SubThoroughfare: {placemark.SubThoroughfare}\n" +
+ $"Thoroughfare: {placemark.Thoroughfare}\n";
+
+ Console.WriteLine(geocodeAddress);
+ }
+ }
+ catch (FeatureNotSupportedException fnsEx)
+ {
+ // Feature not supported on device
+ }
+ catch (Exception ex)
+ {
+ // Handle exception that may have occurred in geocoding
+ }
+ }
+ }
+}
diff --git a/docs/platform-integration/snippets/shared_2/Features/GeolocationTest.cs b/docs/platform-integration/snippets/shared_2/Features/GeolocationTest.cs
new file mode 100644
index 000000000..75034a8f7
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_2/Features/GeolocationTest.cs
@@ -0,0 +1,103 @@
+using System;
+using System.IO;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+
+
+namespace PlatformIntegration.Features
+{
+ class GeolocationTest
+ {
+ public async Task GetCachedLocation()
+ {
+ try
+ {
+ Location location = await Geolocation.GetLastKnownLocationAsync();
+
+ if (location != null)
+ Console.WriteLine($"Latitude: {location.Latitude}, Longitude: {location.Longitude}, Altitude: {location.Altitude}");
+ }
+ catch (FeatureNotSupportedException fnsEx)
+ {
+ // Handle not supported on device exception
+ }
+ catch (FeatureNotEnabledException fneEx)
+ {
+ // Handle not enabled on device exception
+ }
+ catch (PermissionException pEx)
+ {
+ // Handle permission exception
+ }
+ catch (Exception ex)
+ {
+ // Unable to get location
+ }
+ }
+
+ private CancellationTokenSource _cancelTokenSource;
+ private bool _isCheckingLocation;
+
+ public async Task GetCurrentLocation()
+ {
+ try
+ {
+ _isCheckingLocation = true;
+
+ var request = new GeolocationRequest(GeolocationAccuracy.Medium, TimeSpan.FromSeconds(10));
+
+ _cancelTokenSource = new CancellationTokenSource();
+
+ Location location = await Geolocation.GetLocationAsync(request, _cancelTokenSource.Token);
+
+ if (location != null)
+ Console.WriteLine($"Latitude: {location.Latitude}, Longitude: {location.Longitude}, Altitude: {location.Altitude}");
+ }
+ catch (FeatureNotSupportedException fnsEx)
+ {
+ // Handle not supported on device exception
+ }
+ catch (FeatureNotEnabledException fneEx)
+ {
+ // Handle not enabled on device exception
+ }
+ catch (PermissionException pEx)
+ {
+ // Handle permission exception
+ }
+ catch (Exception ex)
+ {
+ // Unable to get location
+ }
+ finally
+ {
+ _isCheckingLocation = false;
+ }
+ }
+
+ public void CancelRequest()
+ {
+ if (_cancelTokenSource != null && _cancelTokenSource.IsCancellationRequested == false)
+ _cancelTokenSource.Cancel();
+ }
+
+ public async Task CheckMock()
+ {
+ var request = new GeolocationRequest(GeolocationAccuracy.Medium);
+ Location location = await Geolocation.GetLocationAsync(request);
+
+ if (location != null && location.IsFromMockProvider)
+ {
+ // location is from a mock provider
+ }
+ }
+
+ public void Distance()
+ {
+ Location boston = new Location(42.358056, -71.063611);
+ Location sanFrancisco = new Location(37.783333, -122.416667);
+ double miles = Location.CalculateDistance(boston, sanFrancisco, DistanceUnits.Miles);
+ }
+ }
+}
diff --git a/docs/platform-integration/snippets/shared_2/Features/HapticTest.cs b/docs/platform-integration/snippets/shared_2/Features/HapticTest.cs
new file mode 100644
index 000000000..3fb1a7e9f
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_2/Features/HapticTest.cs
@@ -0,0 +1,32 @@
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace PlatformIntegration.Features
+{
+ public class HapticTest
+ {
+ public void TriggerHapticFeedback()
+ {
+ try
+ {
+ // Perform click feedback
+ HapticFeedback.Perform(HapticFeedbackType.Click);
+
+ // Or use long press
+ HapticFeedback.Perform(HapticFeedbackType.LongPress);
+ }
+ catch (FeatureNotSupportedException ex)
+ {
+ // Feature not supported on device
+ }
+ catch (Exception ex)
+ {
+ // Other error has occurred.
+ }
+ }
+ }
+}
diff --git a/docs/platform-integration/snippets/shared_2/Features/LauncherTest.cs b/docs/platform-integration/snippets/shared_2/Features/LauncherTest.cs
new file mode 100644
index 000000000..1f131b223
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_2/Features/LauncherTest.cs
@@ -0,0 +1,34 @@
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace PlatformIntegration.Features
+{
+ public class LauncherTest
+ {
+ public async Task OpenRideShareAsync()
+ {
+ var supportsUri = await Launcher.CanOpenAsync("lyft://");
+
+ if (supportsUri)
+ await Launcher.OpenAsync("lyft://ridetype?id=lyft_line");
+ }
+
+ public async Task TryOpenRideShareAsync() =>
+ await Launcher.TryOpenAsync("lyft://ridetype?id=lyft_line");
+
+ public async Task OpenTextFile()
+ {
+ string popoverTitle = "Read text file";
+ string name = "File.txt";
+ string file = System.IO.Path.Combine(FileSystem.CacheDirectory, name);
+
+ System.IO.File.WriteAllText(file, "Hello World");
+
+ await Launcher.OpenAsync(new OpenFileRequest(popoverTitle, new ReadOnlyFile(file)));
+ }
+ }
+}
diff --git a/docs/platform-integration/snippets/shared_2/Features/MapTest.cs b/docs/platform-integration/snippets/shared_2/Features/MapTest.cs
new file mode 100644
index 000000000..7402fd64f
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_2/Features/MapTest.cs
@@ -0,0 +1,62 @@
+using Microsoft.Maui.Controls.PlatformConfiguration;
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace PlatformIntegration.Features
+{
+ public class MapTest
+ {
+ public async Task NavigateToBuilding25()
+ {
+ var location = new Location(47.645160, -122.1306032);
+ var options = new MapLaunchOptions { Name = "Microsoft Building 25" };
+
+ try
+ {
+ await Map.OpenAsync(location, options);
+ }
+ catch (Exception ex)
+ {
+ // No map application available to open
+ }
+ }
+ }
+ public class MapTest2
+ {
+ public async Task NavigateToBuilding25()
+ {
+ var placemark = new Placemark
+ {
+ CountryName = "United States",
+ AdminArea = "WA",
+ Thoroughfare = "Microsoft Building 25",
+ Locality = "Redmond"
+ };
+ var options = new MapLaunchOptions { Name = "Microsoft Building 25" };
+
+ try
+ {
+ await Map.OpenAsync(placemark, options);
+ }
+ catch (Exception ex)
+ {
+ // No map application available to open or placemark can not be located
+ }
+ }
+ }
+
+ public class MapTest3
+ {
+ public async Task NavigateToBuilding25()
+ {
+ var location = new Location(47.645160, -122.1306032);
+ var options = new MapLaunchOptions { NavigationMode = NavigationMode.Driving };
+
+ await Map.OpenAsync(location, options);
+ }
+ }
+}
diff --git a/docs/platform-integration/snippets/shared_2/Features/MediaPickerTest.cs b/docs/platform-integration/snippets/shared_2/Features/MediaPickerTest.cs
new file mode 100644
index 000000000..495a9f9c0
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_2/Features/MediaPickerTest.cs
@@ -0,0 +1,53 @@
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace PlatformIntegration.Features
+{
+ class MediaPickerTest
+ {
+ async Task TakePhotoAsync()
+ {
+ try
+ {
+ FileResult photo = await MediaPicker.CapturePhotoAsync();
+
+ //Well, not looking good so far for truck rental. Should know for sure today. Called a dozen places, no one will rent a truck for towing, round trip, let you go to montana and back. Should know today if enterprise truck will have one or not. Otherwise I'll be calling safeco asking if I can just use the rental prices for a class b/c and see if I can find one.
+ string filePath = await LoadPhotoAsync(photo);
+ Console.WriteLine($"CapturePhotoAsync COMPLETED: { filePath }");
+ }
+ catch (FeatureNotSupportedException fnsEx)
+ {
+ // Feature is not supported on the device
+ }
+ catch (PermissionException pEx)
+ {
+ // Permissions not granted
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"CapturePhotoAsync THREW: {ex.Message}");
+ }
+ }
+
+ async Task LoadPhotoAsync(FileResult photo)
+ {
+ // canceled
+ if (photo == null)
+ return string.Empty;
+
+ // save the file into local storage
+ string localFilePath = Path.Combine(FileSystem.CacheDirectory, photo.FileName);
+
+ using Stream sourceStream = await photo.OpenReadAsync();
+ using FileStream localFileStream = File.OpenWrite(localFilePath);
+
+ await sourceStream.CopyToAsync(localFileStream);
+
+ return localFilePath;
+ }
+ }
+}
diff --git a/docs/platform-integration/snippets/shared_2/Features/PermissionsTest.cs b/docs/platform-integration/snippets/shared_2/Features/PermissionsTest.cs
new file mode 100644
index 000000000..f961a661b
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_2/Features/PermissionsTest.cs
@@ -0,0 +1,119 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Microsoft.Maui.Controls;
+using Microsoft.Maui.ApplicationModel;
+
+
+namespace PlatformIntegration.Features
+{
+ class PermissionsTest
+ {
+ public async Task SmallCheckStatus()
+ {
+ //
+ PermissionStatus status = await Permissions.CheckStatusAsync();
+ //
+ }
+
+ public async Task SmallRequestStatus()
+ {
+ //
+ PermissionStatus status = await Permissions.RequestAsync();
+ //
+ }
+
+ public async Task SmallRequestStatusCustom()
+ {
+#if ANDROID
+ //
+ PermissionStatus status = await Permissions.RequestAsync();
+ //
+#endif
+ }
+
+ //
+ public async Task CheckAndRequestLocationPermission()
+ {
+ PermissionStatus status = await Permissions.CheckStatusAsync();
+
+ if (status == PermissionStatus.Granted)
+ return status;
+
+ if (status == PermissionStatus.Denied && DeviceInfo.Platform == DevicePlatform.iOS)
+ {
+ // Prompt the user to turn on in settings
+ // On iOS once a permission has been denied it may not be requested again from the application
+ return status;
+ }
+
+ if (Permissions.ShouldShowRationale())
+ {
+ // Prompt the user with additional information as to why the permission is needed
+ }
+
+ status = await Permissions.RequestAsync();
+
+ return status;
+ }
+ //
+
+ //
+#nullable enable
+ public async Task GetLocationAsync()
+ {
+ PermissionStatus status = await CheckAndRequestPermissionAsync(new Permissions.LocationWhenInUse());
+ if (status != PermissionStatus.Granted)
+ {
+ // Notify user permission was denied
+ return null;
+ }
+
+ return await Geolocation.GetLocationAsync();
+ }
+
+ public async Task CheckAndRequestPermissionAsync(T permission)
+ where T : Permissions.BasePermission
+ {
+ PermissionStatus status = await permission.CheckStatusAsync();
+
+ if (status != PermissionStatus.Granted)
+ status = await permission.RequestAsync();
+
+ return status;
+ }
+#nullable restore
+ //
+
+ public class MyPermission : Permissions.BasePermission
+ {
+ // This method checks if current status of the permission.
+ public override Task CheckStatusAsync()
+ {
+ throw new System.NotImplementedException();
+ }
+
+ // This method is optional and a PermissionException is often thrown if a permission is not declared.
+ public override void EnsureDeclared()
+ {
+ throw new System.NotImplementedException();
+ }
+
+ // Requests the user to accept or deny a permission.
+ public override Task RequestAsync()
+ {
+ throw new System.NotImplementedException();
+ }
+
+ // Indicates that the requestor should prompt the user as to why the app requires the permission, because the
+ // user has previously denied this permission.
+ public override bool ShouldShowRationale()
+ {
+ throw new NotImplementedException();
+ }
+ }
+
+ }
+}
diff --git a/docs/platform-integration/snippets/shared_2/Features/PreferencesTest.cs b/docs/platform-integration/snippets/shared_2/Features/PreferencesTest.cs
new file mode 100644
index 000000000..b98f7e1de
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_2/Features/PreferencesTest.cs
@@ -0,0 +1,48 @@
+using Microsoft.Maui.Controls.PlatformConfiguration;
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace PlatformIntegration.Features
+{
+ public class PreferencesTest
+ {
+ public void SetExamples()
+ {
+ // Set a string value:
+ Preferences.Set("first_name", "John");
+
+ // Set an numerical value:
+ Preferences.Set("age", 28);
+
+ // Set a boolean value:
+ Preferences.Set("has_pets", true);
+ }
+
+ public void GetExamples()
+ {
+ string firstName = Preferences.Get("first_name", "Unknown");
+ int age = Preferences.Get("age", -1);
+ bool hasPets = Preferences.Get("has_pets", false);
+ }
+
+ public void ContainsExamples()
+ {
+ bool hasFirstName = Preferences.ContainsKey("first_name");
+ }
+
+ public void RemoveKey()
+ {
+ Preferences.Remove("first_name");
+ }
+
+ public void Clear()
+ {
+ Preferences.Clear();
+ Preferences.Clear("shared_first_name");
+ }
+ }
+}
diff --git a/docs/platform-integration/snippets/shared_2/Features/ScreenshotTest.cs b/docs/platform-integration/snippets/shared_2/Features/ScreenshotTest.cs
new file mode 100644
index 000000000..40a1b2be2
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_2/Features/ScreenshotTest.cs
@@ -0,0 +1,21 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Microsoft.Maui.Controls;
+
+
+namespace PlatformIntegration.Features
+{
+ class ScreenshotTest
+ {
+ async Task CaptureScreenshot()
+ {
+ IScreenshotResult screenshot = await Screenshot.CaptureAsync();
+ Stream stream = await screenshot.OpenReadAsync();
+
+ return ImageSource.FromStream(() => stream);
+ }
+ }
+}
diff --git a/docs/platform-integration/snippets/shared_2/Features/SecureStorage.cs b/docs/platform-integration/snippets/shared_2/Features/SecureStorage.cs
new file mode 100644
index 000000000..970a12c9e
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_2/Features/SecureStorage.cs
@@ -0,0 +1,51 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+
+namespace PlatformIntegration.Features
+{
+ public class SecureStorageTest
+ {
+ public static async Task SetValue()
+ {
+ try
+ {
+ await SecureStorage.SetAsync("oauth_token", "secret-oauth-token-value");
+ }
+ catch (Exception ex)
+ {
+ // Possible that device doesn't support secure storage on device.
+ }
+ }
+
+ public static async Task GetValue()
+ {
+ try
+ {
+ string oauthToken = await SecureStorage.GetAsync("oauth_token");
+
+ if (oauthToken == null)
+ {
+ // No value is associated with the key "oauth_token"
+ }
+ }
+ catch (Exception ex)
+ {
+ // Possible that device doesn't support secure storage on device.
+ }
+ }
+
+ public static void RemoveValue()
+ {
+ SecureStorage.Remove("oauth_token");
+ }
+
+ public static void RemoveAllValues()
+ {
+ SecureStorage.RemoveAll();
+ }
+ }
+}
diff --git a/docs/platform-integration/snippets/shared_2/Features/Share.cs b/docs/platform-integration/snippets/shared_2/Features/Share.cs
new file mode 100644
index 000000000..d1050af3f
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_2/Features/Share.cs
@@ -0,0 +1,60 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+
+namespace PlatformIntegration.Features
+{
+ public class ShareTest
+ {
+ public async Task ShareText(string text)
+ {
+ await Share.RequestAsync(new ShareTextRequest
+ {
+ Text = text,
+ Title = "Share Text"
+ });
+ }
+
+ public async Task ShareUri(string uri)
+ {
+ await Share.RequestAsync(new ShareTextRequest
+ {
+ Uri = uri,
+ Title = "Share Web Link"
+ });
+ }
+
+ public async Task ShareFile()
+ {
+ string fn = "Attachment.txt";
+ string file = Path.Combine(FileSystem.CacheDirectory, fn);
+
+ File.WriteAllText(file, "Hello World");
+
+ await Share.RequestAsync(new ShareFileRequest
+ {
+ Title = "Share text file",
+ File = new ShareFile(file)
+ });
+ }
+
+ public async Task ShareMultipleFiles()
+ {
+ string file1 = Path.Combine(FileSystem.CacheDirectory, "Attachment1.txt");
+ string file2 = Path.Combine(FileSystem.CacheDirectory, "Attachment2.txt");
+
+ File.WriteAllText(file1, "Content 1");
+ File.WriteAllText(file2, "Content 2");
+
+ await Share.RequestAsync(new ShareMultipleFilesRequest
+ {
+ Title = "Share multiple files",
+ Files = new List { new ShareFile(file1), new ShareFile(file2) }
+ });
+ }
+ }
+}
diff --git a/docs/platform-integration/snippets/shared_2/Features/SmsTest.cs b/docs/platform-integration/snippets/shared_2/Features/SmsTest.cs
new file mode 100644
index 000000000..9c315a7ef
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_2/Features/SmsTest.cs
@@ -0,0 +1,49 @@
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace PlatformIntegration.Features
+{
+ public partial class SmsTest
+ {
+ public async Task SendSms(string messageText, string recipient)
+ {
+ try
+ {
+ var message = new SmsMessage(messageText, new[] { recipient });
+ await Sms.ComposeAsync(message);
+ }
+ catch (FeatureNotSupportedException ex)
+ {
+ // Sms is not supported on this device.
+ }
+ catch (Exception ex)
+ {
+ // Other error has occurred.
+ }
+ }
+ }
+
+ public partial class SmsTest
+ {
+ public async Task SendSms(string messageText, string[] recipients)
+ {
+ try
+ {
+ var message = new SmsMessage(messageText, recipients);
+ await Sms.ComposeAsync(message);
+ }
+ catch (FeatureNotSupportedException ex)
+ {
+ // Sms is not supported on this device.
+ }
+ catch (Exception ex)
+ {
+ // Other error has occurred.
+ }
+ }
+ }
+}
diff --git a/docs/platform-integration/snippets/shared_2/Features/WebAuthTest.cs b/docs/platform-integration/snippets/shared_2/Features/WebAuthTest.cs
new file mode 100644
index 000000000..5ef7d73c3
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_2/Features/WebAuthTest.cs
@@ -0,0 +1,86 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+
+namespace PlatformIntegration.Features
+{
+ internal class WebAuthTest
+ {
+ public async Task AuthIt()
+ {
+ try
+ {
+ WebAuthenticatorResult authResult = await WebAuthenticator.AuthenticateAsync(
+ new Uri("https://mysite.com/mobileauth/Microsoft"),
+ new Uri("myapp://"));
+
+ string accessToken = authResult?.AccessToken;
+
+ // Do something with the token
+ }
+ catch (TaskCanceledException e)
+ {
+ // Use stopped auth
+ }
+ }
+
+ public async Task AuthItSecure()
+ {
+ try
+ {
+ WebAuthenticatorResult authResult = await WebAuthenticator.AuthenticateAsync(
+ new WebAuthenticatorOptions()
+ {
+ Url = new Uri("https://mysite.com/mobileauth/Microsoft"),
+ CallbackUrl = new Uri("myapp://"),
+ PrefersEphemeralWebBrowserSession = true
+ });
+
+ string accessToken = authResult?.AccessToken;
+
+ // Do something with the token
+ }
+ catch (TaskCanceledException e)
+ {
+ // Use stopped auth
+ }
+ }
+
+ public async Task AuthItApple()
+ {
+ var scheme = "..."; // Apple, Microsoft, Google, Facebook, etc.
+ var authUrlRoot = "https://mysite.com/mobileauth/";
+ WebAuthenticatorResult result = null;
+
+ if (scheme.Equals("Apple")
+ && DeviceInfo.Platform == DevicePlatform.iOS
+ && DeviceInfo.Version.Major >= 13)
+ {
+ // Use Native Apple Sign In API's
+ result = await AppleSignInAuthenticator.AuthenticateAsync();
+ }
+ else
+ {
+ // Web Authentication flow
+ var authUrl = new Uri($"{authUrlRoot}{scheme}");
+ var callbackUrl = new Uri("myapp://");
+
+ result = await WebAuthenticator.AuthenticateAsync(authUrl, callbackUrl);
+ }
+
+ var authToken = string.Empty;
+
+ if (result.Properties.TryGetValue("name", out string name) && !string.IsNullOrEmpty(name))
+ authToken += $"Name: {name}{Environment.NewLine}";
+
+ if (result.Properties.TryGetValue("email", out string email) && !string.IsNullOrEmpty(email))
+ authToken += $"Email: {email}{Environment.NewLine}";
+
+ // Note that Apple Sign In has an IdToken and not an AccessToken
+ authToken += result?.AccessToken ?? result?.IdToken;
+ }
+ }
+}
diff --git a/docs/platform-integration/snippets/shared_2/MainPage.xaml b/docs/platform-integration/snippets/shared_2/MainPage.xaml
new file mode 100644
index 000000000..9a8ef4dea
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_2/MainPage.xaml
@@ -0,0 +1,47 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/platform-integration/snippets/shared_2/MainPage.xaml.cs b/docs/platform-integration/snippets/shared_2/MainPage.xaml.cs
new file mode 100644
index 000000000..4bfaf719c
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_2/MainPage.xaml.cs
@@ -0,0 +1,31 @@
+namespace PlatformIntegration;
+
+public partial class MainPage : ContentPage
+{
+ int count = 0;
+
+ public MainPage()
+ {
+ InitializeComponent();
+ }
+
+ private void OnCounterClicked(object sender, EventArgs e)
+ {
+ count++;
+ CounterLabel.Text = $"Current count: {count}";
+
+ SemanticScreenReader.Announce(CounterLabel.Text);
+ }
+
+ private async void ActionButton_Clicked(object sender, EventArgs e)
+ {
+ //
+ if (AppActions.Current.IsSupported)
+ {
+ await AppActions.Current.SetAsync(new[] { new AppAction("app_info", "App Info", icon: "app_info_action_icon"),
+ new AppAction("battery_info", "Battery Info") });
+ }
+ //
+ }
+}
+
diff --git a/docs/platform-integration/snippets/shared_2/MauiProgram.cs b/docs/platform-integration/snippets/shared_2/MauiProgram.cs
new file mode 100644
index 000000000..72c269b09
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_2/MauiProgram.cs
@@ -0,0 +1,18 @@
+namespace PlatformIntegration;
+
+public static class MauiProgram
+{
+ public static MauiApp CreateMauiApp()
+ {
+ var builder = MauiApp.CreateBuilder();
+ builder
+ .UseMauiApp()
+ .ConfigureFonts(fonts =>
+ {
+ fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
+ fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
+ });
+
+ return builder.Build();
+ }
+}
diff --git a/docs/platform-integration/snippets/shared_2/Platforms/Android/AndroidManifest.xml b/docs/platform-integration/snippets/shared_2/Platforms/Android/AndroidManifest.xml
new file mode 100644
index 000000000..75edbf2a7
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_2/Platforms/Android/AndroidManifest.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/docs/platform-integration/snippets/shared_2/Platforms/Android/MainActivity.cs b/docs/platform-integration/snippets/shared_2/Platforms/Android/MainActivity.cs
new file mode 100644
index 000000000..63de50285
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_2/Platforms/Android/MainActivity.cs
@@ -0,0 +1,15 @@
+using Android.App;
+using Android.Content;
+using Android.Content.PM;
+using Android.OS;
+
+namespace PlatformIntegration;
+
+//
+[Activity(Theme = "@style/Maui.SplashTheme", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation | ConfigChanges.UiMode | ConfigChanges.ScreenLayout | ConfigChanges.SmallestScreenSize | ConfigChanges.Density)]
+[IntentFilter(Categories = new[] { global::Android.Content.Intent.CategoryDefault })]
+public class MainActivity : MauiAppCompatActivity
+//
+{
+
+}
diff --git a/docs/platform-integration/snippets/shared_2/Platforms/Android/MainApplication.cs b/docs/platform-integration/snippets/shared_2/Platforms/Android/MainApplication.cs
new file mode 100644
index 000000000..74838c039
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_2/Platforms/Android/MainApplication.cs
@@ -0,0 +1,15 @@
+using Android.App;
+using Android.Runtime;
+
+namespace PlatformIntegration;
+
+[Application]
+public class MainApplication : MauiApplication
+{
+ public MainApplication(IntPtr handle, JniHandleOwnership ownership)
+ : base(handle, ownership)
+ {
+ }
+
+ protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
+}
diff --git a/docs/platform-integration/snippets/shared_2/Platforms/Android/ReadWriteStoragePerms.cs b/docs/platform-integration/snippets/shared_2/Platforms/Android/ReadWriteStoragePerms.cs
new file mode 100644
index 000000000..b85c50ec5
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_2/Platforms/Android/ReadWriteStoragePerms.cs
@@ -0,0 +1,20 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace PlatformIntegration.Android.Permissions
+{
+#if ANDROID
+ public class ReadWriteStoragePerms: Microsoft.Maui.ApplicationModel.Permissions.BasePlatformPermission
+ {
+ public override (string androidPermission, bool isRuntime)[] RequiredPermissions =>
+ new List<(string androidPermission, bool isRuntime)>
+ {
+ (global::Android.Manifest.Permission.ReadExternalStorage, true),
+ (global::Android.Manifest.Permission.WriteExternalStorage, true)
+ }.ToArray();
+ }
+#endif
+}
diff --git a/docs/platform-integration/snippets/shared_2/Platforms/Android/Resources/values/colors.xml b/docs/platform-integration/snippets/shared_2/Platforms/Android/Resources/values/colors.xml
new file mode 100644
index 000000000..c04d7492a
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_2/Platforms/Android/Resources/values/colors.xml
@@ -0,0 +1,6 @@
+
+
+ #512BD4
+ #2B0B98
+ #2B0B98
+
\ No newline at end of file
diff --git a/docs/platform-integration/snippets/shared_2/Platforms/MacCatalyst/AppDelegate.cs b/docs/platform-integration/snippets/shared_2/Platforms/MacCatalyst/AppDelegate.cs
new file mode 100644
index 000000000..3758e077e
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_2/Platforms/MacCatalyst/AppDelegate.cs
@@ -0,0 +1,9 @@
+using Foundation;
+
+namespace PlatformIntegration;
+
+[Register("AppDelegate")]
+public class AppDelegate : MauiUIApplicationDelegate
+{
+ protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
+}
diff --git a/docs/platform-integration/snippets/shared_2/Platforms/MacCatalyst/Info.plist b/docs/platform-integration/snippets/shared_2/Platforms/MacCatalyst/Info.plist
new file mode 100644
index 000000000..403ce9c66
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_2/Platforms/MacCatalyst/Info.plist
@@ -0,0 +1,30 @@
+
+
+
+
+ UIDeviceFamily
+
+ 1
+ 2
+
+ UIRequiredDeviceCapabilities
+
+ arm64
+
+ UISupportedInterfaceOrientations
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+ UISupportedInterfaceOrientations~ipad
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationPortraitUpsideDown
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+ XSAppIconAssets
+ Assets.xcassets/appicon.appiconset
+
+
diff --git a/docs/platform-integration/snippets/shared_2/Platforms/MacCatalyst/Program.cs b/docs/platform-integration/snippets/shared_2/Platforms/MacCatalyst/Program.cs
new file mode 100644
index 000000000..67073f69f
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_2/Platforms/MacCatalyst/Program.cs
@@ -0,0 +1,15 @@
+using ObjCRuntime;
+using UIKit;
+
+namespace PlatformIntegration;
+
+public class Program
+{
+ // This is the main entry point of the application.
+ static void Main(string[] args)
+ {
+ // if you want to use a different Application Delegate class from "AppDelegate"
+ // you can specify it here.
+ UIApplication.Main(args, null, typeof(AppDelegate));
+ }
+}
diff --git a/docs/platform-integration/snippets/shared_2/Platforms/Tizen/Main.cs b/docs/platform-integration/snippets/shared_2/Platforms/Tizen/Main.cs
new file mode 100644
index 000000000..c6e53a216
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_2/Platforms/Tizen/Main.cs
@@ -0,0 +1,16 @@
+using System;
+using Microsoft.Maui;
+using Microsoft.Maui.Hosting;
+
+namespace PlatformIntegration;
+
+class Program : MauiApplication
+{
+ protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
+
+ static void Main(string[] args)
+ {
+ var app = new Program();
+ app.Run(args);
+ }
+}
diff --git a/docs/platform-integration/snippets/shared_2/Platforms/Tizen/tizen-manifest.xml b/docs/platform-integration/snippets/shared_2/Platforms/Tizen/tizen-manifest.xml
new file mode 100644
index 000000000..73d30ca93
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_2/Platforms/Tizen/tizen-manifest.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+ appicon.xhigh.png
+
+
+
+
+ http://tizen.org/privilege/internet
+
+
+
+
\ No newline at end of file
diff --git a/docs/platform-integration/snippets/shared_2/Platforms/Windows/App.xaml b/docs/platform-integration/snippets/shared_2/Platforms/Windows/App.xaml
new file mode 100644
index 000000000..38b8d0687
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_2/Platforms/Windows/App.xaml
@@ -0,0 +1,8 @@
+
+
+
diff --git a/docs/platform-integration/snippets/shared_2/Platforms/Windows/App.xaml.cs b/docs/platform-integration/snippets/shared_2/Platforms/Windows/App.xaml.cs
new file mode 100644
index 000000000..3401d8eac
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_2/Platforms/Windows/App.xaml.cs
@@ -0,0 +1,24 @@
+using Microsoft.UI.Xaml;
+
+// To learn more about WinUI, the WinUI project structure,
+// and more about our project templates, see: http://aka.ms/winui-project-info.
+
+namespace PlatformIntegration.WinUI;
+
+///
+/// Provides application-specific behavior to supplement the default Application class.
+///
+public partial class App : MauiWinUIApplication
+{
+ ///
+ /// Initializes the singleton application object. This is the first line of authored code
+ /// executed, and as such is the logical equivalent of main() or WinMain().
+ ///
+ public App()
+ {
+ this.InitializeComponent();
+ }
+
+ protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
+}
+
diff --git a/docs/platform-integration/snippets/shared_2/Platforms/Windows/Package.appxmanifest b/docs/platform-integration/snippets/shared_2/Platforms/Windows/Package.appxmanifest
new file mode 100644
index 000000000..0cb469c4d
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_2/Platforms/Windows/Package.appxmanifest
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+ User Name
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/platform-integration/snippets/shared_2/Platforms/Windows/app.manifest b/docs/platform-integration/snippets/shared_2/Platforms/Windows/app.manifest
new file mode 100644
index 000000000..e064d0160
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_2/Platforms/Windows/app.manifest
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+ true/PM
+ PerMonitorV2, PerMonitor
+
+
+
diff --git a/docs/platform-integration/snippets/shared_2/Platforms/iOS/AppDelegate.cs b/docs/platform-integration/snippets/shared_2/Platforms/iOS/AppDelegate.cs
new file mode 100644
index 000000000..3758e077e
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_2/Platforms/iOS/AppDelegate.cs
@@ -0,0 +1,9 @@
+using Foundation;
+
+namespace PlatformIntegration;
+
+[Register("AppDelegate")]
+public class AppDelegate : MauiUIApplicationDelegate
+{
+ protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
+}
diff --git a/docs/platform-integration/snippets/shared_2/Platforms/iOS/Info.plist b/docs/platform-integration/snippets/shared_2/Platforms/iOS/Info.plist
new file mode 100644
index 000000000..ecb7f719b
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_2/Platforms/iOS/Info.plist
@@ -0,0 +1,32 @@
+
+
+
+
+ LSRequiresIPhoneOS
+
+ UIDeviceFamily
+
+ 1
+ 2
+
+ UIRequiredDeviceCapabilities
+
+ arm64
+
+ UISupportedInterfaceOrientations
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+ UISupportedInterfaceOrientations~ipad
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationPortraitUpsideDown
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+ XSAppIconAssets
+ Assets.xcassets/appicon.appiconset
+
+
diff --git a/docs/platform-integration/snippets/shared_2/Platforms/iOS/Program.cs b/docs/platform-integration/snippets/shared_2/Platforms/iOS/Program.cs
new file mode 100644
index 000000000..67073f69f
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_2/Platforms/iOS/Program.cs
@@ -0,0 +1,15 @@
+using ObjCRuntime;
+using UIKit;
+
+namespace PlatformIntegration;
+
+public class Program
+{
+ // This is the main entry point of the application.
+ static void Main(string[] args)
+ {
+ // if you want to use a different Application Delegate class from "AppDelegate"
+ // you can specify it here.
+ UIApplication.Main(args, null, typeof(AppDelegate));
+ }
+}
diff --git a/docs/platform-integration/snippets/shared_2/Properties/launchSettings.json b/docs/platform-integration/snippets/shared_2/Properties/launchSettings.json
new file mode 100644
index 000000000..edf8aadcc
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_2/Properties/launchSettings.json
@@ -0,0 +1,8 @@
+{
+ "profiles": {
+ "Windows Machine": {
+ "commandName": "MsixPackage",
+ "nativeDebugging": false
+ }
+ }
+}
\ No newline at end of file
diff --git a/docs/platform-integration/snippets/shared_2/Resources/Fonts/OpenSans-Regular.ttf b/docs/platform-integration/snippets/shared_2/Resources/Fonts/OpenSans-Regular.ttf
new file mode 100644
index 000000000..c9237de1c
Binary files /dev/null and b/docs/platform-integration/snippets/shared_2/Resources/Fonts/OpenSans-Regular.ttf differ
diff --git a/docs/platform-integration/snippets/shared_2/Resources/Fonts/OpenSans-Semibold.ttf b/docs/platform-integration/snippets/shared_2/Resources/Fonts/OpenSans-Semibold.ttf
new file mode 100644
index 000000000..0da9f9986
Binary files /dev/null and b/docs/platform-integration/snippets/shared_2/Resources/Fonts/OpenSans-Semibold.ttf differ
diff --git a/docs/platform-integration/snippets/shared_2/Resources/Images/dotnet_bot.svg b/docs/platform-integration/snippets/shared_2/Resources/Images/dotnet_bot.svg
new file mode 100644
index 000000000..abfaff26a
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_2/Resources/Images/dotnet_bot.svg
@@ -0,0 +1,93 @@
+
diff --git a/docs/platform-integration/snippets/shared_2/Resources/Raw/AboutAssets.txt b/docs/platform-integration/snippets/shared_2/Resources/Raw/AboutAssets.txt
new file mode 100644
index 000000000..5055d0bab
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_2/Resources/Raw/AboutAssets.txt
@@ -0,0 +1,14 @@
+Any raw assets you want to be deployed with your application can be placed in
+this directory (and child directories) and given a Build Action of "MauiAsset":
+
+
+
+These files will be deployed with you package and will be accessible using Essentials:
+
+ async Task LoadMauiAsset()
+ {
+ using var stream = await FileSystem.OpenAppPackageFileAsync("AboutAssets.txt");
+ using var reader = new StreamReader(stream);
+
+ var contents = reader.ReadToEnd();
+ }
diff --git a/docs/platform-integration/snippets/shared_2/Resources/Styles.xaml b/docs/platform-integration/snippets/shared_2/Resources/Styles.xaml
new file mode 100644
index 000000000..0eb436944
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_2/Resources/Styles.xaml
@@ -0,0 +1,456 @@
+
+
+
+
+ #512BD4
+ #DFD8F7
+ #2B0B98
+ White
+ Black
+ #E5E5E1
+ #969696
+ #505050
+
+
+
+
+
+
+
+
+
+ #F7B548
+ #FFD590
+ #FFE5B9
+ #28C2D1
+ #7BDDEF
+ #C3F2F4
+ #3E8EED
+ #72ACF1
+ #A7CBF6
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/docs/platform-integration/snippets/shared_2/Resources/appicon.svg b/docs/platform-integration/snippets/shared_2/Resources/appicon.svg
new file mode 100644
index 000000000..9d63b6513
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_2/Resources/appicon.svg
@@ -0,0 +1,4 @@
+
+
\ No newline at end of file
diff --git a/docs/platform-integration/snippets/shared_2/Resources/appiconfg.svg b/docs/platform-integration/snippets/shared_2/Resources/appiconfg.svg
new file mode 100644
index 000000000..21dfb25f1
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_2/Resources/appiconfg.svg
@@ -0,0 +1,8 @@
+
+
+
\ No newline at end of file
diff --git a/docs/platform-integration/snippets/shared_2/Sensors/Accelerometer.cs b/docs/platform-integration/snippets/shared_2/Sensors/Accelerometer.cs
new file mode 100644
index 000000000..75b16e746
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_2/Sensors/Accelerometer.cs
@@ -0,0 +1,43 @@
+
+using System;
+
+namespace PlatformIntegration.Sensors
+{
+ public class AccelerometerTest
+ {
+ public void ToggleAccelerometer()
+ {
+ const SensorSpeed speed = SensorSpeed.UI;
+
+ try
+ {
+ if (Accelerometer.IsMonitoring)
+ {
+ Accelerometer.Stop();
+ Accelerometer.ReadingChanged -= Accelerometer_ReadingChanged;
+ }
+ else
+ {
+ Accelerometer.ReadingChanged += Accelerometer_ReadingChanged;
+ Accelerometer.Start(speed);
+ }
+ }
+ catch (FeatureNotSupportedException fnsEx)
+ {
+ // Feature not supported on device
+ }
+ catch (Exception ex)
+ {
+ // Other error has occurred.
+ }
+ }
+
+ void Accelerometer_ReadingChanged(object sender, AccelerometerChangedEventArgs e)
+ {
+ AccelerometerData data = e.Reading;
+
+ // Process Acceleration X, Y, Z
+ Console.WriteLine($"Reading: X: {data.Acceleration.X}, Y: {data.Acceleration.Y}, Z: {data.Acceleration.Z}");
+ }
+ }
+}
diff --git a/docs/platform-integration/snippets/shared_2/Sensors/Barometer.cs b/docs/platform-integration/snippets/shared_2/Sensors/Barometer.cs
new file mode 100644
index 000000000..d6bff299d
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_2/Sensors/Barometer.cs
@@ -0,0 +1,43 @@
+
+using System;
+
+namespace PlatformIntegration.Sensors
+{
+ public class BarometerTest
+ {
+ public void ToggleBarometer()
+ {
+ const SensorSpeed speed = SensorSpeed.UI;
+
+ try
+ {
+ if (Barometer.IsMonitoring)
+ {
+ Barometer.Stop();
+ Barometer.ReadingChanged -= Barometer_ReadingChanged;
+ }
+ else
+ {
+ Barometer.ReadingChanged += Barometer_ReadingChanged;
+ Barometer.Start(speed);
+ }
+ }
+ catch (FeatureNotSupportedException fnsEx)
+ {
+ // Feature not supported on device
+ }
+ catch (Exception ex)
+ {
+ // Other error has occurred.
+ }
+ }
+
+ void Barometer_ReadingChanged(object sender, BarometerChangedEventArgs e)
+ {
+ var data = e.Reading;
+
+ // Process Pressure
+ Console.WriteLine($"Reading: Pressure: {data.PressureInHectopascals} hectopascals");
+ }
+ }
+}
diff --git a/docs/platform-integration/snippets/shared_2/Sensors/Compass.cs b/docs/platform-integration/snippets/shared_2/Sensors/Compass.cs
new file mode 100644
index 000000000..519c0a4bb
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_2/Sensors/Compass.cs
@@ -0,0 +1,43 @@
+
+using System;
+
+namespace PlatformIntegration.Sensors
+{
+ public class CompassTest
+ {
+ public void ToggleCompass(ICompass compassInstance)
+ {
+ const SensorSpeed speed = SensorSpeed.UI;
+
+ try
+ {
+ if (compassInstance.IsMonitoring)
+ {
+ compassInstance.Stop();
+ compassInstance.ReadingChanged -= Compass_ReadingChanged;
+ }
+ else
+ {
+ compassInstance.ReadingChanged += Compass_ReadingChanged;
+ compassInstance.Start(speed);
+ }
+ }
+ catch (FeatureNotSupportedException fnsEx)
+ {
+ // Feature not supported on device
+ }
+ catch (Exception ex)
+ {
+ // Some other exception has occurred
+ }
+ }
+
+ void Compass_ReadingChanged(object sender, CompassChangedEventArgs e)
+ {
+ var data = e.Reading;
+
+ // Process Heading Magnetic North
+ Console.WriteLine($"Reading: {data.HeadingMagneticNorth} degrees");
+ }
+ }
+}
diff --git a/docs/platform-integration/snippets/shared_2/Sensors/Gyroscope.cs b/docs/platform-integration/snippets/shared_2/Sensors/Gyroscope.cs
new file mode 100644
index 000000000..5ec3380dc
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_2/Sensors/Gyroscope.cs
@@ -0,0 +1,43 @@
+
+using System;
+
+namespace PlatformIntegration.Sensors
+{
+ public class GyroscopeTest
+ {
+ public void ToggleGyroscope()
+ {
+ const SensorSpeed speed = SensorSpeed.UI;
+
+ try
+ {
+ if (Gyroscope.IsMonitoring)
+ {
+ Gyroscope.Stop();
+ Gyroscope.ReadingChanged -= Gyroscope_ReadingChanged;
+ }
+ else
+ {
+ Gyroscope.ReadingChanged += Gyroscope_ReadingChanged;
+ Gyroscope.Start(speed);
+ }
+ }
+ catch (FeatureNotSupportedException fnsEx)
+ {
+ // Feature not supported on device
+ }
+ catch (Exception ex)
+ {
+ // Other error has occurred.
+ }
+ }
+
+ void Gyroscope_ReadingChanged(object sender, GyroscopeChangedEventArgs e)
+ {
+ GyroscopeData data = e.Reading;
+
+ // Process Angular Velocity X, Y, and Z reported in rad/s
+ Console.WriteLine($"Reading: X: {data.AngularVelocity.X}, Y: {data.AngularVelocity.Y}, Z: {data.AngularVelocity.Z}");
+ }
+ }
+}
diff --git a/docs/platform-integration/snippets/shared_2/Sensors/Magnetometer.cs b/docs/platform-integration/snippets/shared_2/Sensors/Magnetometer.cs
new file mode 100644
index 000000000..110f375b0
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_2/Sensors/Magnetometer.cs
@@ -0,0 +1,43 @@
+
+using System;
+
+namespace PlatformIntegration.Sensors
+{
+ public class MagnetometerTest
+ {
+ public void ToggleMagnetometer()
+ {
+ const SensorSpeed speed = SensorSpeed.UI;
+
+ try
+ {
+ if (Magnetometer.IsMonitoring)
+ {
+ Magnetometer.Stop();
+ Magnetometer.ReadingChanged -= Magnetometer_ReadingChanged;
+ }
+ else
+ {
+ Magnetometer.ReadingChanged += Magnetometer_ReadingChanged;
+ Magnetometer.Start(speed);
+ }
+ }
+ catch (FeatureNotSupportedException fnsEx)
+ {
+ // Feature not supported on device
+ }
+ catch (Exception ex)
+ {
+ // Other error has occurred.
+ }
+ }
+
+ void Magnetometer_ReadingChanged(object sender, MagnetometerChangedEventArgs e)
+ {
+ MagnetometerData data = e.Reading;
+
+ // Process MagneticField X, Y, and Z
+ Console.WriteLine($"Reading: X: {data.MagneticField.X}, Y: {data.MagneticField.Y}, Z: {data.MagneticField.Z}");
+ }
+ }
+}
diff --git a/docs/platform-integration/snippets/shared_2/Sensors/Orientation.cs b/docs/platform-integration/snippets/shared_2/Sensors/Orientation.cs
new file mode 100644
index 000000000..73850b63e
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_2/Sensors/Orientation.cs
@@ -0,0 +1,43 @@
+
+using System;
+
+namespace PlatformIntegration.Sensors
+{
+ public class OrientationTest
+ {
+ public void ToggleOrientation()
+ {
+ const SensorSpeed speed = SensorSpeed.UI;
+
+ try
+ {
+ if (OrientationSensor.IsMonitoring)
+ {
+ OrientationSensor.Stop();
+ OrientationSensor.ReadingChanged -= OrientationSensor_ReadingChanged;
+ }
+ else
+ {
+ OrientationSensor.ReadingChanged += OrientationSensor_ReadingChanged;
+ OrientationSensor.Start(speed);
+ }
+ }
+ catch (FeatureNotSupportedException fnsEx)
+ {
+ // Feature not supported on device
+ }
+ catch (Exception ex)
+ {
+ // Other error has occurred.
+ }
+ }
+
+ void OrientationSensor_ReadingChanged(object sender, OrientationSensorChangedEventArgs e)
+ {
+ OrientationSensorData data = e.Reading;
+
+ // Process Orientation quaternion (X, Y, Z, and W)
+ Console.WriteLine($"Reading: X: {data.Orientation.X}, Y: {data.Orientation.Y}, Z: {data.Orientation.Z}");
+ }
+ }
+}
diff --git a/docs/platform-integration/snippets/shared_2/Sensors/Shake.cs b/docs/platform-integration/snippets/shared_2/Sensors/Shake.cs
new file mode 100644
index 000000000..c35fda5cb
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_2/Sensors/Shake.cs
@@ -0,0 +1,41 @@
+
+using System;
+
+namespace PlatformIntegration.Sensors
+{
+ public class ShakeTest
+ {
+ public void ToggleAccelerometer()
+ {
+ const SensorSpeed speed = SensorSpeed.Game;
+
+ try
+ {
+ if (Accelerometer.IsMonitoring)
+ {
+ Accelerometer.Stop();
+ Accelerometer.ShakeDetected -= Accelerometer_ShakeDetected;
+ }
+ else
+ {
+ Accelerometer.ShakeDetected += Accelerometer_ShakeDetected;
+ Accelerometer.Start(speed);
+ }
+ }
+ catch (FeatureNotSupportedException fnsEx)
+ {
+ // Feature not supported on device
+ }
+ catch (Exception ex)
+ {
+ // Other error has occurred.
+ }
+ }
+
+ void Accelerometer_ShakeDetected(object sender, EventArgs e)
+ {
+ // Process shake event
+ Console.WriteLine("Device shake detected!");
+ }
+ }
+}
diff --git a/docs/platform-integration/snippets/shared_2/SensorsPage.xaml b/docs/platform-integration/snippets/shared_2/SensorsPage.xaml
new file mode 100644
index 000000000..a0d3965b8
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_2/SensorsPage.xaml
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/platform-integration/snippets/shared_2/SensorsPage.xaml.cs b/docs/platform-integration/snippets/shared_2/SensorsPage.xaml.cs
new file mode 100644
index 000000000..366b4240a
--- /dev/null
+++ b/docs/platform-integration/snippets/shared_2/SensorsPage.xaml.cs
@@ -0,0 +1,259 @@
+namespace PlatformIntegration;
+
+public partial class SensorsPage : ContentPage
+{
+ public SensorsPage()
+ {
+ InitializeComponent();
+ }
+
+ private void ContentPage_Loaded(object sender, EventArgs e)
+ {
+
+ }
+
+ #region Accelerometer
+ private void AccelSwitch_Toggled(object sender, ToggledEventArgs e)
+ {
+ ToggleAccelerometer(Accelerometer.Default);
+ }
+
+ //
+ public void ToggleAccelerometer(IAccelerometer accelerometer)
+ {
+ if (accelerometer.IsSupported)
+ {
+ if (!accelerometer.IsMonitoring)
+ {
+ // Turn on accelerometer
+ accelerometer.ReadingChanged += Accelerometer_ReadingChanged;
+ accelerometer.Start(SensorSpeed.UI);
+ }
+ else
+ {
+ // Turn off accelerometer
+ accelerometer.Stop();
+ accelerometer.ReadingChanged -= Accelerometer_ReadingChanged;
+ }
+ }
+ }
+
+ private void Accelerometer_ReadingChanged(object sender, AccelerometerChangedEventArgs e)
+ {
+ // Update UI Label with accelerometer state
+ AccelLabel.TextColor = Colors.Green;
+ AccelLabel.Text = $"Accel: {e.Reading}";
+ }
+ //
+ #endregion
+
+ #region Barometer
+ private void BarometerSwitch_Toggled(object sender, ToggledEventArgs e)
+ {
+ ToggleBarometer(Barometer.Default);
+ }
+
+ //
+ public void ToggleBarometer(IBarometer barometer)
+ {
+ if (barometer.IsSupported)
+ {
+ if (!barometer.IsMonitoring)
+ {
+ // Turn on accelerometer
+ barometer.ReadingChanged += Barometer_ReadingChanged;
+ barometer.Start(SensorSpeed.UI);
+ }
+ else
+ {
+ // Turn off accelerometer
+ barometer.Stop();
+ barometer.ReadingChanged -= Barometer_ReadingChanged;
+ }
+ }
+ }
+
+ private void Barometer_ReadingChanged(object sender, BarometerChangedEventArgs e)
+ {
+ // Update UI Label with barometer state
+ BarometerLabel.TextColor = Colors.Green;
+ BarometerLabel.Text = $"Barometer: {e.Reading}";
+ }
+ //
+ #endregion
+
+ #region Compass
+ private void CompassSwitch_Toggled(object sender, ToggledEventArgs e)
+ {
+ ToggleCompass(Compass.Default);
+ }
+
+ //
+ private void ToggleCompass(ICompass compass)
+ {
+ if (compass.IsSupported)
+ {
+ if (!compass.IsMonitoring)
+ {
+ // Turn on compass
+ compass.ReadingChanged += Compass_ReadingChanged;
+ compass.Start(SensorSpeed.UI);
+ }
+ else
+ {
+ // Turn off compass
+ compass.Stop();
+ compass.ReadingChanged -= Compass_ReadingChanged;
+ }
+ }
+ }
+
+ private void Compass_ReadingChanged(object sender, CompassChangedEventArgs e)
+ {
+ // Update UI Label with compass state
+ CompassLabel.TextColor = Colors.Green;
+ CompassLabel.Text = $"Compass: {e.Reading}";
+ }
+ //
+ #endregion
+
+ #region Gyroscope
+ private void GyroscopeSwitch_Toggled(object sender, ToggledEventArgs e)
+ {
+ ToggleGyroscope(Gyroscope.Default);
+ }
+
+ //
+ private void ToggleGyroscope(IGyroscope gyroscope)
+ {
+ if (gyroscope.IsSupported)
+ {
+ if (!gyroscope.IsMonitoring)
+ {
+ // Turn on compass
+ gyroscope.ReadingChanged += Gyroscope_ReadingChanged;
+ gyroscope.Start(SensorSpeed.UI);
+ }
+ else
+ {
+ // Turn off compass
+ gyroscope.Stop();
+ gyroscope.ReadingChanged -= Gyroscope_ReadingChanged;
+ }
+ }
+ }
+
+ private void Gyroscope_ReadingChanged(object sender, GyroscopeChangedEventArgs e)
+ {
+ // Update UI Label with gyroscope state
+ GyroscopeLabel.TextColor = Colors.Green;
+ GyroscopeLabel.Text = $"Gyroscope: {e.Reading}";
+ }
+ //
+ #endregion
+
+ #region Magnetometer
+ private void MagnetometerSwitch_Toggled(object sender, ToggledEventArgs e)
+ {
+ ToggleMagnetometer(Magnetometer.Default);
+ }
+
+ //
+ private void ToggleMagnetometer(IMagnetometer magnetometer)
+ {
+ if (magnetometer.IsSupported)
+ {
+ if (!magnetometer.IsMonitoring)
+ {
+ // Turn on compass
+ magnetometer.ReadingChanged += Magnetometer_ReadingChanged;
+ magnetometer.Start(SensorSpeed.UI);
+ }
+ else
+ {
+ // Turn off compass
+ magnetometer.Stop();
+ magnetometer.ReadingChanged -= Magnetometer_ReadingChanged;
+ }
+ }
+ }
+
+ private void Magnetometer_ReadingChanged(object sender, MagnetometerChangedEventArgs e)
+ {
+ // Update UI Label with magnetometer state
+ MagnetometerLabel.TextColor = Colors.Green;
+ MagnetometerLabel.Text = $"Magnetometer: {e.Reading}";
+ }
+ //
+ #endregion
+
+ #region Orientation
+ private void OrientationSwitch_Toggled(object sender, ToggledEventArgs e)
+ {
+ ToggleOrientation(OrientationSensor.Default);
+ }
+
+ //
+ private void ToggleOrientation(IOrientationSensor orientation)
+ {
+ if (orientation.IsSupported)
+ {
+ if (!orientation.IsMonitoring)
+ {
+ // Turn on compass
+ orientation.ReadingChanged += Orientation_ReadingChanged;
+ orientation.Start(SensorSpeed.UI);
+ }
+ else
+ {
+ // Turn off compass
+ orientation.Stop();
+ orientation.ReadingChanged -= Orientation_ReadingChanged;
+ }
+ }
+ }
+
+ private void Orientation_ReadingChanged(object sender, OrientationSensorChangedEventArgs e)
+ {
+ // Update UI Label with orientation state
+ OrientationLabel.TextColor = Colors.Green;
+ OrientationLabel.Text = $"Orientation: {e.Reading}";
+ }
+ //
+ #endregion
+
+ #region Shake
+ private void ShakeSwitch_Toggled(object sender, ToggledEventArgs e)
+ {
+ ToggleShake(Accelerometer.Default);
+ }
+
+ //
+ private void ToggleShake(IAccelerometer accelerometer)
+ {
+ if (accelerometer.IsSupported)
+ {
+ if (!accelerometer.IsMonitoring)
+ {
+ // Turn on compass
+ accelerometer.ShakeDetected += Accelerometer_ShakeDetected;
+ accelerometer.Start(SensorSpeed.Game);
+ }
+ else
+ {
+ // Turn off compass
+ accelerometer.Stop();
+ accelerometer.ShakeDetected -= Accelerometer_ShakeDetected;
+ }
+ }
+ }
+
+ private void Accelerometer_ShakeDetected(object sender, EventArgs e)
+ {
+ // Update UI Label with a "shaked detected" notice, in a randomized color
+ ShakeLabel.TextColor = new Color(Random.Shared.Next(256), Random.Shared.Next(256), Random.Shared.Next(256));
+ ShakeLabel.Text = $"Shake detected";
+ }
+ //
+ #endregion
+}
\ No newline at end of file
diff --git a/docs/platform-integration/storage/file-picker.md b/docs/platform-integration/storage/file-picker.md
new file mode 100644
index 000000000..d39b14052
--- /dev/null
+++ b/docs/platform-integration/storage/file-picker.md
@@ -0,0 +1,82 @@
+---
+title: "File picker"
+description: "Learn how to use the .NET MAUI FilePicker class in the Microsoft.Maui.Storage namespace, which lets a user choose one or more files from the device."
+ms.date: 05/23/2022
+no-loc: ["Microsoft.Maui", "Microsoft.Maui.Storage", "FilePicker"]
+---
+
+# File picker
+
+This article describes how you can use the .NET Multi-platform App UI (.NET MAUI) `IFilePicker` interface. With the `IFilePicker` interface, you can prompt the user to pick one or more files from the device. The `IFilePicker` interface is exposed through the `FilePicker.Default` property.
+
+[!INCLUDE [docs under construction](~/includes/preview-note.md)]
+
+The `FilePicker` and `IFilePicker` types are available in the `Microsoft.Maui.Storage` namespace.
+
+## Get started
+
+To access the **FilePicker** functionality, the following platform specific setup is required.
+
+
+# [Android](#tab/android)
+
+The `ReadExternalStorage` permission is required and must be configured in the Android project. This can be added in the following ways:
+
+- Add the assembly-based permission:
+
+ Open the _Platforms/Android/MainApplication.cs_ file and add the following assembly attributes after `using` directives:
+
+ ```csharp
+ [assembly: UsesPermission(Android.Manifest.Permission.ReadExternalStorage)]
+ ```
+
+ \- or -
+
+- Update the Android Manifest:
+
+ Open the _Platforms/Android/AndroidManifest.xml_ file and add the following in the `manifest` node:
+
+ ```xml
+
+ ```
+
+
+# [iOS](#tab/ios)
+
+Enable iCloud capabilities.
+
+
+
+# [Windows](#tab/windows)
+
+No setup is required.
+
+-----
+
+
+> [!IMPORTANT]
+> All methods must be called on the UI thread because permission checks and requests are automatically handled by .NET MAUI.
+
+## Pick a file
+
+The `PickAsync` method prompts the user to pick a file from the device. Use the `PickOptions` type to specify the title and file types allowed with the picker. The following example demonstrates opening the picker and processing the selected image:
+
+:::code language="csharp" source="../snippets/shared_1/Storage.cs" id="file_pick":::
+
+Default file types are provided with `FilePickerFileType.Images`, `FilePickerFileType.Png`, and `FilePickerFilerType.Videos`. You can specify custom file types per platform, by creating an instance of the `FilePickerFileType` class. The constructor of this class takes a dictionary that is keyed by the `DevicePlatform` type to identify the platform. The value of the dictionary key is a collection of strings representing the file types. For example here's how you would specify specific comic file types:
+
+:::code language="csharp" source="../snippets/shared_1/Storage.cs" id="file_types":::
+
+## Pick multiple files
+
+If you want the user to pick multiple files, call the `FilePicker.PickMultipleAsync` method. This method also takes a `PickOptions` parameter to specify additional information. The results are the same as `PickAsync`, but instead of the `FileResult` type returned, an `IEnumerable` type is returned with all of the selected files.
+
+[!INCLUDE [tip-file-result](../includes/tip-file-result.md)]
diff --git a/docs/platform-integration/storage/file-system-helpers.md b/docs/platform-integration/storage/file-system-helpers.md
new file mode 100644
index 000000000..b42e25b33
--- /dev/null
+++ b/docs/platform-integration/storage/file-system-helpers.md
@@ -0,0 +1,80 @@
+---
+title: "File system helpers"
+description: "Learn how to use the .NET MAUI FileSystem class in the Microsoft.Maui.Storage namespace. This class contains helper methods that access the application's cache and data directories, and helps open files in the app package."
+ms.date: 05/23/2022
+no-loc: ["Microsoft.Maui", "Microsoft.Maui.Storage", "FileSystem"]
+---
+
+# File system helpers
+
+This article describes how you can use the .NET Multi-platform App UI (.NET MAUI) `IFileSystem` interface. This interface provides helper methods that access the app's cache and data directories, and helps access files in the app package. The `IFileSystem` interface is exposed through the `FileSystem.Current` property.
+
+[!INCLUDE [docs under construction](~/includes/preview-note.md)]
+
+The `FileSystem` and `IFileSystem` types are available in the `Microsoft.Maui.Storage` namespace.
+
+## Using file system helpers
+
+Each operating system will have unique paths to the app cache and app data directories. The `IFileSystem` interface provides a cross-platform API for accessing these directory paths.
+
+### Cache directory
+
+To get the application's directory to store **cache data**. Cache data can be used for any data that needs to persist longer than temporary data, but shouldn't be data that is required to operate the app, as the operating system may clear this storage.
+
+:::code language="csharp" source="../snippets/shared_1/Storage.cs" id="filesys_cache":::
+
+### App data directory
+
+To get the app's top-level directory for any files that aren't user data files. These files are backed up with the operating system syncing framework.
+
+:::code language="csharp" source="../snippets/shared_1/Storage.cs" id="filesys_appdata":::
+
+## Bundled files
+
+To open a file that is bundled into the app package, use the `OpenAppPackageFileAsync` method and pass the file name. This method returns an representing the file contents. The following example demonstrates using a method to read the text contents of a file:
+
+:::code language="csharp" source="../snippets/shared_1/Storage.cs" id="filesys_readtxtfile":::
+
+## Platform differences
+
+This section describes the platform-specific differences with the file system helpers.
+
+
+# [Android](#tab/android)
+
+- `FileSystem.CacheDirectory`\
+Returns the [CacheDir](https://developer.android.com/reference/android/content/Context.html#getCacheDir) of the current context.
+
+- `FileSystem.AppDataDirectory`\
+Returns the [FilesDir](https://developer.android.com/reference/android/content/Context.html#getFilesDir) of the current context, which are backed up using [Auto Backup](https://developer.android.com/guide/topics/data/autobackup.html) starting on API 23 and above.
+
+- `FileSystem.OpenAppPackageFileAsync`\
+Files that were added to the project with the **Build Action** of **MauiAsset** can be opened with this method. .NET MAUI projects will process any file in the _Platform\Resources\Raw_ folder as a **MauiAsset**.
+
+# [iOS](#tab/ios)
+
+- `FileSystem.CacheDirectory`\
+Returns the [Library/Caches](https://developer.apple.com/library/content/documentation/FileManagement/Conceptual/FileSystemProgrammingGuide/FileSystemOverview/FileSystemOverview.html) directory.
+
+- `FileSystem.AppDataDirectory`\
+Returns the [Library](https://developer.apple.com/library/content/documentation/FileManagement/Conceptual/FileSystemProgrammingGuide/FileSystemOverview/FileSystemOverview.html) directory that is backed up by iTunes and iCloud.
+
+ > [!IMPORTANT]
+ > In the iOS Simulator, the Application ID (which is part of the directory name) changes on every build so you have to retrieve the correct ID each time you build your application for the Simulator.
+
+- `FileSystem.OpenAppPackageFileAsync`\
+Files that were added to the project with the **Build Action** of **MauiAsset** can be opened with this method. .NET MAUI projects will process any file in the _Platform\Resources\Raw_ folder as a **MauiAsset**.
+
+# [Windows](#tab/windows)
+
+- `FileSystem.CacheDirectory`\
+Returns the `LocalCacheFolder` directory.
+
+- `FileSystem.AppDataDirectory`\
+Returns the `LocalFolder` directory that is backed up to the cloud.
+
+- `FileSystem.OpenAppPackageFileAsync`\
+Files that were added to the project with the **Build Action** of **MauiAsset** can be opened with this method. .NET MAUI projects will process any file in the _Platform\Resources\Raw_ folder as a **MauiAsset**.
+
+-----
+
diff --git a/docs/platform-integration/storage/preferences.md b/docs/platform-integration/storage/preferences.md
new file mode 100644
index 000000000..e0fc504c2
--- /dev/null
+++ b/docs/platform-integration/storage/preferences.md
@@ -0,0 +1,115 @@
+---
+title: "Preferences"
+description: "Learn how to read and write application preferences, in .NET MAUI. The Preferences class can save and load application preferences in a key/value store."
+ms.date: 05/23/2022
+no-loc: ["Microsoft.Maui", "Microsoft.Maui.Storage", "Preferences"]
+---
+
+# Preferences
+
+This article describes how you can use the .NET Multi-platform App UI (.NET MAUI) `IPreferences` interface. This interface helps store app preferences in a key/value store. The `IPreferences` interface is exposed through the `Preferences.Default` property.
+
+[!INCLUDE [docs under construction](~/includes/preview-note.md)]
+
+The `Preferences` and `IPreferences` types are available in the `Microsoft.Maui.Storage` namespace.
+
+## Storage types
+
+Preferences are stored with a key. The value of a preference must be one of the following data types:
+
+-
+-
+-
+-
+-
+-
+-
+
+Values of `DateTime` are stored in a 64-bit binary (long integer) format using two methods defined by the `DateTime` class:
+
+- The [`ToBinary`](xref:System.DateTime.ToBinary) method is used to encode the `DateTime` value.
+- The [`FromBinary`](xref:System.DateTime.FromBinary(System.Int64)) method decodes the value.
+
+See the documentation of these methods for adjustments that might be made to decoded values when a `DateTime` is stored that isn't a Coordinated Universal Time (UTC) value.
+
+## Set preferences
+
+Preferences are set by calling the `Preferences.Set` method, providing the key and value:
+
+:::code language="csharp" source="../snippets/shared_1/Storage.cs" id="prefs_set":::
+
+## Get preferences
+
+To retrieve a value from preferences, you pass the key of the preference, followed by the default value when the key doesn't exist:
+
+:::code language="csharp" source="../snippets/shared_1/Storage.cs" id="prefs_defaults":::
+
+## Check for a key
+
+It may be useful to check if a key exists in the preferences or not. Even though `Get` has you set a default value when the key doesn't exist, there may be cases where the key existed, but the value of the key matched the default value. So you can't rely on the default value as an indicator that the key doesn't exist. Use the `ContainsKey` method to determine if a key exists:
+
+:::code language="csharp" source="../snippets/shared_1/Storage.cs" id="prefs_containskey":::
+
+## Remove one or all keys
+
+Use the `Remove` method to remove a specific key from preferences:
+
+:::code language="csharp" source="../snippets/shared_1/Storage.cs" id="prefs_remove":::
+
+To remove all keys, use the `Clear` method:
+
+:::code language="csharp" source="../snippets/shared_1/Storage.cs" id="prefs_clear":::
+
+## Shared keys
+
+
+The preferences stored by your app are only visible to your app. However, you can also create a **shared** preference that can be used by other extensions or a watch app. When you set, remove, or retrieve a preference, an optional string parameter can be supplied to specify the name of the container the preference is stored in.
+
+The following methods take a string parameter named `sharedName`:
+
+- `Preferences.Set`
+- `Preferences.Get`
+- `Preferences.Remove`
+- `Preferences.Clear`
+
+> [!IMPORTANT]
+> Please read the platform implementation specifics, as shared preferences have behavior-specific implementations
+
+## Integrate with system settings
+
+Preferences are stored natively, which allows you to integrate your settings into the native system settings. Follow the platform documentation and samples to integrate with the platform:
+
+- Apple: [Implementing an iOS Settings Bundle](https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/UserDefaults/Preferences/Preferences.html)
+- [iOS Application Preferences Sample](/samples/xamarin/ios-samples/appprefs/)
+- Android: [Getting Started with Settings Screens](https://developer.android.com/guide/topics/ui/settings.html)
+
+## Platform differences
+
+This section describes the platform-specific differences with the preferences API.
+
+
+# [Android](#tab/android)
+
+All data is stored into [**Shared Preferences**](https://developer.android.com/training/data-storage/shared-preferences.html). If no `sharedName` is specified, the default **Shared Preferences** are used. Otherwise, the name is used to get a **private Shared Preferences** with the specified name.
+
+# [iOS](#tab/ios)
+
+**NSUserDefaults** is used to store values on iOS devices. If no `sharedName` is specified, the `StandardUserDefaults` are used. Otherwise, the name is used to create a new `NSUserDefaults` with the specified name used for the `NSUserDefaultsType.SuiteName`.
+
+# [Windows](#tab/windows)
+
+`ApplicationDataContainer` is used to store the values on the device. If no `sharedName` is specified, the `LocalSettings` are used. Otherwise the name is used to create a new container inside of `LocalSettings`.
+
+
+`LocalSettings` restricts the preference key names to 255 characters or less. Each preference value can be up to 8K bytes in size, and each composite setting can be up to 64 K bytes in size.
+
+-----
+
+
+## Persistence
+
+Uninstalling the application causes all _preferences_ to be removed, except when the app runs on Android 6.0 (API level 23) or later, while using the [**Auto Backup**`](https://developer.android.com/guide/topics/data/autobackup) feature. This feature is on by default and preserves app data, including **Shared Preferences**, which is what the **Preferences** API uses. You can disable this by following Google's [Auto Backup documentation](https://developer.android.com/guide/topics/data/autobackup).
+
+## Limitations
+
+Performance may be impacted if you store large amounts of text, as the API was designed to store small amounts of text.
diff --git a/docs/platform-integration/storage/secure-storage.md b/docs/platform-integration/storage/secure-storage.md
new file mode 100644
index 000000000..75d3395ad
--- /dev/null
+++ b/docs/platform-integration/storage/secure-storage.md
@@ -0,0 +1,149 @@
+---
+title: "Secure storage"
+description: "Learn how to use the .NET MAUI SecureStorage class, which helps securely store simple key/value pairs. This article discusses how to use the class, platform implementation specifics, and its limitations."
+ms.date: 05/23/2022
+no-loc: ["Microsoft.Maui", "Microsoft.Maui.Storage", "SecureStorage"]
+#acrolinx score 95
+---
+
+# Secure storage
+
+This article describes how you can use the .NET Multi-platform App UI (.NET MAUI) `ISecureStorage` interface. This interface helps securely store simple key/value pairs. The `ISecureStorage` interface is exposed through the `SecureStorage.Default` property.
+
+[!INCLUDE [docs under construction](~/includes/preview-note.md)]
+
+The `SecureStorage` and `ISecureStorage` types are available in the `Microsoft.Maui.Storage` namespace.
+
+## Get started
+
+To access the **SecureStorage** functionality, the following platform-specific setup is required:
+
+
+# [Android](#tab/android)
+
+
+
+
+[Auto Backup for Apps](https://developer.android.com/guide/topics/data/autobackup) is a feature of Android 6.0 (API level 23) and later that backs up user's app data (shared preferences, files in the app's internal storage, and other specific files). Data is restored when an app is reinstalled or installed on a new device. This can affect `SecureStorage`, which utilizes share preferences that are backed up and can't be decrypted when the restore occurs. .NET MAUI automatically handles this case by removing the key so it can be reset. Alternatively, you can disable Auto Backup.
+
+### Enable or disable backup
+
+You can choose to disable Auto Backup for your entire application by setting `android:allowBackup` to false in the _AndroidManifest.xml_ file. This approach is only recommended if you plan on restoring data in another way.
+
+```xml
+
+ ...
+
+ ...
+
+
+```
+
+### Selective backup
+
+Auto Backup can be configured to disable specific content from backing up. You can create a custom rule set to exclude `SecureStore` items from being backed up.
+
+01. Set the `android:fullBackupContent` attribute in your _AndroidManifest.xml_:
+
+ ```xml
+
+
+ ```
+
+01. Create a new XML file named _auto_backup_rules.xml_ in the _Resources/xml_ directory with the build action of **AndroidResource**. Set the following content that includes all shared preferences except for `SecureStorage`:
+
+ ```xml
+
+
+
+
+
+ ```
+
+# [iOS](#tab/ios)
+
+When developing on the **iOS simulator**, enable the **Keychain** entitlement and add a keychain access group for the application's bundle identifier.
+
+Create or open the _Entitlements.plist_ in the project and find the **Keychain** entitlement and enable it. This will automatically add the application's identifier as a group. For more information about editing the _Entitlements.plist_ file, see [Entitlements and capabilities](../../ios/deployment/entitlements.md).
+
+In the project properties, under **iOS Bundle Signing** set the **Custom Entitlements** to **Entitlements.plist**.
+
+> [!TIP]
+> When deploying to an iOS device, this entitlement isn't required and should be removed.
+
+# [Windows](#tab/windows)
+
+No setup is required.
+
+-----
+
+
+## Use secure storage
+
+The following code examples demonstrate how to use secure storage.
+
+> [!TIP]
+> It's possible that an exception is thrown when calling `GetAsync` or `SetAsync`. This can be caused by a device not supporting secure storage, encryption keys changing, or corruption of data. it's best to handle this by removing and adding the setting back if possible.
+
+### Write a value
+
+To save a value for a given _key_ in secure storage:
+
+:::code language="csharp" source="../snippets/shared_1/Storage.cs" id="secstorage_set":::
+
+### Read a value
+
+To retrieve a value from secure storage:
+
+:::code language="csharp" source="../snippets/shared_1/Storage.cs" id="secstorage_get":::
+
+> [!TIP]
+> If there isn't a value associated with the key, `GetAsync` returns `null`.
+
+### Remove a value
+
+To remove a specific value, remove the key:
+
+:::code language="csharp" source="../snippets/shared_1/Storage.cs" id="secstorage_remove":::
+
+To remove all values, use the `RemoveAll` method:
+
+:::code language="csharp" source="../snippets/shared_1/Storage.cs" id="secstorage_remove_all":::
+
+## Platform differences
+
+This section describes the platform-specific differences with the secure storage API.
+
+
+
+# [Android](#tab/android)
+
+`SecureStorage` uses the [Preferences](preferences.md) API and follows the same data persistence outlined in the [Preferences](preferences.md#persistence) documentation, with a filename of _[YOUR-APP-PACKAGE-ID].microsoft.maui.essentials.preferences_. However, data is encrypted with the Android [`EncryptedSharedPreferences`](https://developer.android.com/reference/androidx/security/crypto/EncryptedSharedPreferences) class, from the Android Security library, which wraps the `SharedPreferences` class and automatically encrypts keys and values using a two-scheme approach:
+
+- Keys are deterministically encrypted, so that the key can be encrypted and properly looked up.
+- Values are non-deterministically encrypted using AES-256 GCM.
+
+For more information about the Android Security library, see [Work with data more securely](https://developer.android.com/topic/security/data) on developer.android.com.
+
+# [iOS](#tab/ios)
+
+[KeyChain](xref:Security.SecKeyChain) is used to store values securely on iOS devices. The `SecRecord` used to store the value has a `Service` value set to _[YOUR-APP-BUNDLE-ID].microsoft.maui.essentials.preferences_.
+
+In some cases, KeyChain data is synchronized with iCloud, and uninstalling the application may not remove the secure values from user devices.
+
+# [Windows](#tab/windows)
+
+`DataProtectionProvider` is used to encrypt values securely on Windows devices.
+
+Encrypted values are stored in `ApplicationData.Current.LocalSettings`, inside a container with a name of _[YOUR-APP-ID].microsoft.maui.essentials.preferences_.
+
+`SecureStorage` uses the [Preferences](preferences.md) API and follows the same data persistence outlined in the [Preferences](preferences.md#persistence) documentation. It also uses `LocalSettings`, which has a restriction that a setting name length may be 255 characters at the most. Each setting can be up to 8K bytes in size, and each composite setting can be up to 64 K bytes in size.
+
+-----
+
+
+
+## Limitations
+
+Performance may be impacted if you store large amounts of text, as the API was designed to store small amounts of text.