From 2e8966eef651fea110321b5f5d1fb4cf7c0e5eef Mon Sep 17 00:00:00 2001 From: Radek Doulik Date: Tue, 5 Sep 2017 13:39:44 +0200 Subject: [PATCH] [tests] added forms test - to measure UI app startup times - added RunUITests task (similar to unInstrumentationTests) to start app/activity - renamed .*UnitTestApk.* to .*TestApk.* as it handles also the new UI test - updated ProcessLogcatTiming to be able to process logcat output of started Android activity process --- Documentation/DevelopmentTips.md | 20 +-- Xamarin.Android-Tests.sln | 8 ++ ...{UnitTestApks.targets => TestApks.targets} | 45 ++++--- .../ProcessLogcatTiming.cs | 5 +- .../Test/Mono.Android-Tests.projitems | 6 +- .../Test/Mono.Android-Tests.targets | 2 +- ...amarin.Android.Tools.BootstrapTasks.csproj | 3 +- .../CreateAndroidEmulator.cs | 2 +- .../RunUITests.cs | 37 ++++++ .../StartAndroidEmulator.cs | 2 +- .../Xamarin.Android.JcwGen-Tests.projitems | 4 +- .../Xamarin.Android.JcwGen-Tests.targets | 2 +- tests/RunApkTests.targets | 11 +- .../App.xaml | 18 +++ .../App.xaml.cs | 27 ++++ .../Droid/Assets/AboutAssets.txt | 19 +++ .../Droid/MainActivity.cs | 71 ++++++++++ .../Droid/Properties/AndroidManifest.xml | 5 + .../Droid/Properties/AssemblyInfo.cs | 27 ++++ .../Droid/Resources/AboutResources.txt | 44 +++++++ .../Droid/Resources/drawable-hdpi/icon.png | Bin 0 -> 1431 bytes .../Droid/Resources/drawable-xhdpi/icon.png | Bin 0 -> 1789 bytes .../Droid/Resources/drawable-xxhdpi/icon.png | Bin 0 -> 2353 bytes .../Droid/Resources/drawable/icon.png | Bin 0 -> 1431 bytes .../Droid/Resources/drawable/xamarin_logo.png | Bin 0 -> 21481 bytes .../Droid/Resources/layout/Tabbar.axml | 2 + .../Droid/Resources/layout/Toolbar.axml | 2 + .../Droid/Resources/values/styles.xml | 27 ++++ ...Forms.Performance.Integration.Droid.csproj | 121 ++++++++++++++++++ ...ms.Performance.Integration.Droid.projitems | 11 ++ .../Droid/packages.config | 14 ++ .../Droid/timing-definitions.txt | 14 ++ .../Models/Item.cs | 11 ++ .../Services/CloudDataStore.cs | 81 ++++++++++++ .../Services/IDataStore.cs | 14 ++ .../Services/MockDataStore.cs | 64 +++++++++ .../ViewModels/AboutViewModel.cs | 19 +++ .../ViewModels/BaseViewModel.cs | 51 ++++++++ .../ViewModels/ItemDetailViewModel.cs | 14 ++ .../ViewModels/ItemsViewModel.cs | 48 +++++++ .../Views/AboutPage.xaml | 60 +++++++++ .../Views/AboutPage.xaml.cs | 14 ++ .../Views/ItemDetailPage.xaml | 7 + .../Views/ItemDetailPage.xaml.cs | 32 +++++ .../Views/ItemsPage.xaml | 22 ++++ .../Views/ItemsPage.xaml.cs | 45 +++++++ .../Views/MainPage.cs | 48 +++++++ .../Views/NewItemPage.xaml | 14 ++ .../Views/NewItemPage.xaml.cs | 29 +++++ ...in.Forms.Performance.Integration.projitems | 44 +++++++ ...marin.Forms.Performance.Integration.shproj | 11 ++ .../Xamarin.Android.Locale-Tests.csproj | 2 +- .../Xamarin.Android.Locale-Tests.projitems | 4 +- 53 files changed, 1136 insertions(+), 47 deletions(-) rename build-tools/scripts/{UnitTestApks.targets => TestApks.targets} (83%) create mode 100644 src/Xamarin.Android.Tools.BootstrapTasks/Xamarin.Android.Tools.BootstrapTasks/RunUITests.cs create mode 100644 tests/Xamarin.Forms-Performance-Integration/App.xaml create mode 100644 tests/Xamarin.Forms-Performance-Integration/App.xaml.cs create mode 100644 tests/Xamarin.Forms-Performance-Integration/Droid/Assets/AboutAssets.txt create mode 100644 tests/Xamarin.Forms-Performance-Integration/Droid/MainActivity.cs create mode 100644 tests/Xamarin.Forms-Performance-Integration/Droid/Properties/AndroidManifest.xml create mode 100644 tests/Xamarin.Forms-Performance-Integration/Droid/Properties/AssemblyInfo.cs create mode 100644 tests/Xamarin.Forms-Performance-Integration/Droid/Resources/AboutResources.txt create mode 100644 tests/Xamarin.Forms-Performance-Integration/Droid/Resources/drawable-hdpi/icon.png create mode 100644 tests/Xamarin.Forms-Performance-Integration/Droid/Resources/drawable-xhdpi/icon.png create mode 100644 tests/Xamarin.Forms-Performance-Integration/Droid/Resources/drawable-xxhdpi/icon.png create mode 100644 tests/Xamarin.Forms-Performance-Integration/Droid/Resources/drawable/icon.png create mode 100644 tests/Xamarin.Forms-Performance-Integration/Droid/Resources/drawable/xamarin_logo.png create mode 100644 tests/Xamarin.Forms-Performance-Integration/Droid/Resources/layout/Tabbar.axml create mode 100644 tests/Xamarin.Forms-Performance-Integration/Droid/Resources/layout/Toolbar.axml create mode 100644 tests/Xamarin.Forms-Performance-Integration/Droid/Resources/values/styles.xml create mode 100644 tests/Xamarin.Forms-Performance-Integration/Droid/Xamarin.Forms.Performance.Integration.Droid.csproj create mode 100644 tests/Xamarin.Forms-Performance-Integration/Droid/Xamarin.Forms.Performance.Integration.Droid.projitems create mode 100644 tests/Xamarin.Forms-Performance-Integration/Droid/packages.config create mode 100644 tests/Xamarin.Forms-Performance-Integration/Droid/timing-definitions.txt create mode 100644 tests/Xamarin.Forms-Performance-Integration/Models/Item.cs create mode 100644 tests/Xamarin.Forms-Performance-Integration/Services/CloudDataStore.cs create mode 100644 tests/Xamarin.Forms-Performance-Integration/Services/IDataStore.cs create mode 100644 tests/Xamarin.Forms-Performance-Integration/Services/MockDataStore.cs create mode 100644 tests/Xamarin.Forms-Performance-Integration/ViewModels/AboutViewModel.cs create mode 100644 tests/Xamarin.Forms-Performance-Integration/ViewModels/BaseViewModel.cs create mode 100644 tests/Xamarin.Forms-Performance-Integration/ViewModels/ItemDetailViewModel.cs create mode 100644 tests/Xamarin.Forms-Performance-Integration/ViewModels/ItemsViewModel.cs create mode 100644 tests/Xamarin.Forms-Performance-Integration/Views/AboutPage.xaml create mode 100644 tests/Xamarin.Forms-Performance-Integration/Views/AboutPage.xaml.cs create mode 100644 tests/Xamarin.Forms-Performance-Integration/Views/ItemDetailPage.xaml create mode 100644 tests/Xamarin.Forms-Performance-Integration/Views/ItemDetailPage.xaml.cs create mode 100644 tests/Xamarin.Forms-Performance-Integration/Views/ItemsPage.xaml create mode 100644 tests/Xamarin.Forms-Performance-Integration/Views/ItemsPage.xaml.cs create mode 100644 tests/Xamarin.Forms-Performance-Integration/Views/MainPage.cs create mode 100644 tests/Xamarin.Forms-Performance-Integration/Views/NewItemPage.xaml create mode 100644 tests/Xamarin.Forms-Performance-Integration/Views/NewItemPage.xaml.cs create mode 100644 tests/Xamarin.Forms-Performance-Integration/Xamarin.Forms.Performance.Integration.projitems create mode 100644 tests/Xamarin.Forms-Performance-Integration/Xamarin.Forms.Performance.Integration.shproj diff --git a/Documentation/DevelopmentTips.md b/Documentation/DevelopmentTips.md index 4c20dafff0a..05557888972 100644 --- a/Documentation/DevelopmentTips.md +++ b/Documentation/DevelopmentTips.md @@ -148,15 +148,15 @@ make variable: ## Running Individual `.apk` Projects See also the [`tests/RunApkTests.targets`](../tests/RunApkTests.targets) and -[`build-tools/scripts/UnitTestApks.targets`](../build-tools/scripts/UnitTestApks.targets) +[`build-tools/scripts/TestApks.targets`](../build-tools/scripts/TestApks.targets) files. All `.apk`-based unit test projects provide the following targets: -* `DeployUnitTestApks`: Installs the associated `.apk` to an Android device. -* `UndeployUnitTestApks`: Uninstalls the associated `.apk` from an Android device. -* `RunUnitTestApks`: Executes the unit tests contained within a `.apk`. - Must be executed *after* the `DeployUnitTestApks` target. +* `DeployTestApks`: Installs the associated `.apk` to an Android device. +* `UndeployTestApks`: Uninstalls the associated `.apk` from an Android device. +* `RunTestApks`: Executes the unit tests contained within a `.apk`. + Must be executed *after* the `DeployTestApks` target. To run an individual `.apk`-based test project, a package must be built, using the `SignAndroidPackage` target, installed, and executed. @@ -164,23 +164,23 @@ To run an individual `.apk`-based test project, a package must be built, using t For example: $ tools/scripts/xabuild /t:SignAndroidPackage tests/locales/Xamarin.Android.Locale-Tests/Xamarin.Android.Locale-Tests.csproj - $ tools/scripts/xabuild /t:DeployUnitTestApks tests/locales/Xamarin.Android.Locale-Tests/Xamarin.Android.Locale-Tests.csproj - $ tools/scripts/xabuild /t:RunUnitTestApks tests/locales/Xamarin.Android.Locale-Tests/Xamarin.Android.Locale-Tests.csproj + $ tools/scripts/xabuild /t:DeployTestApks tests/locales/Xamarin.Android.Locale-Tests/Xamarin.Android.Locale-Tests.csproj + $ tools/scripts/xabuild /t:RunTestApks tests/locales/Xamarin.Android.Locale-Tests/Xamarin.Android.Locale-Tests.csproj ### Running A Single Test Fixture A single NUnit *Test Fixture* -- a class with the `[TestFixture]` custom attribute -- may be executed instead of executing *all* test fixtures. -The `RunUnitTestApks` target accepts a `TestFixture` MSBuild property +The `RunTestApks` target accepts a `TestFixture` MSBuild property to specify the test fixture class to execute: - $ tools/scripts/xabuild /t:RunUnitTestApks \ + $ tools/scripts/xabuild /t:RunTestApks \ /p:TestFixture=Xamarin.Android.LocaleTests.SatelliteAssemblyTests \ tests/locales/Xamarin.Android.Locale-Tests/Xamarin.Android.Locale-Tests.csproj If using `Xamarin.Android.NUnitLite` for projects outside the `xamarin-android` -repository, such as NUnit tests for a custom app, the `RunUnitTestApks` target +repository, such as NUnit tests for a custom app, the `RunTestApks` target will not exist. In such scenarios, the [`adb shell am`][adb-shell-am] `instrument` command can be used instead. It follows the format: diff --git a/Xamarin.Android-Tests.sln b/Xamarin.Android-Tests.sln index 8aa0169dced..0c5521095a8 100644 --- a/Xamarin.Android-Tests.sln +++ b/Xamarin.Android-Tests.sln @@ -45,6 +45,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Xamarin.Android.BindingReso EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Xamarin.Android.BindingResolveImports-Tests", "tests\ResolveImports\Xamarin.Android.BindingResolveImports-Tests\Xamarin.Android.BindingResolveImports-Tests.csproj", "{B297008B-C313-455E-B230-E119589D2D79}" EndProject +Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Xamarin.Forms.Performance.Integration", "tests\Xamarin.Forms-Performance-Integration\Xamarin.Forms.Performance.Integration.shproj", "{195BE9C2-1F91-40DC-BD6D-DE860BF083FB}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Xamarin.Forms.Performance.Integration.Droid", "tests\Xamarin.Forms-Performance-Integration\Droid\Xamarin.Forms.Performance.Integration.Droid.csproj", "{576312CC-83FF-48B1-A473-488CDC7121AD}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -115,6 +119,10 @@ Global {B297008B-C313-455E-B230-E119589D2D79}.Debug|Any CPU.Build.0 = Debug|Any CPU {B297008B-C313-455E-B230-E119589D2D79}.Release|Any CPU.ActiveCfg = Release|Any CPU {B297008B-C313-455E-B230-E119589D2D79}.Release|Any CPU.Build.0 = Release|Any CPU + {576312CC-83FF-48B1-A473-488CDC7121AD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {576312CC-83FF-48B1-A473-488CDC7121AD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {576312CC-83FF-48B1-A473-488CDC7121AD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {576312CC-83FF-48B1-A473-488CDC7121AD}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {2305B00D-DE81-4744-B0DA-357835CAFE5A} = {43A4FB09-279A-4138-8027-EC1E1CED2E8A} diff --git a/build-tools/scripts/UnitTestApks.targets b/build-tools/scripts/TestApks.targets similarity index 83% rename from build-tools/scripts/UnitTestApks.targets rename to build-tools/scripts/TestApks.targets index 0bc00ec9c33..a5ecf7d2d04 100644 --- a/build-tools/scripts/UnitTestApks.targets +++ b/build-tools/scripts/TestApks.targets @@ -4,13 +4,14 @@ + - <_TestImageName>XamarinAndroidUnitTestRunner + <_TestImageName>XamarinAndroidTestRunner <_AdbEmulatorPort>5570 @@ -107,54 +108,62 @@ - + - + - + + + <_LogcatFilename>logcat-$(Configuration)$(_AotName).txt + ApplicationPackageName="%(TestApk.Package)" + ResultsFilename="%(TestApk.ResultsPath)" + DefinitionsFilename="%(TestApk.TimingDefinitionsFilename)" + AddResults="true" + Activity="%(TestApk.Activity)" /> diff --git a/build-tools/xa-prep-tasks/Xamarin.Android.BuildTools.PrepTasks/ProcessLogcatTiming.cs b/build-tools/xa-prep-tasks/Xamarin.Android.BuildTools.PrepTasks/ProcessLogcatTiming.cs index 195e9238b23..d40e34aa357 100644 --- a/build-tools/xa-prep-tasks/Xamarin.Android.BuildTools.PrepTasks/ProcessLogcatTiming.cs +++ b/build-tools/xa-prep-tasks/Xamarin.Android.BuildTools.PrepTasks/ProcessLogcatTiming.cs @@ -10,13 +10,16 @@ namespace Xamarin.Android.BuildTools.PrepTasks { public class ProcessLogcatTiming : ProcessPlotInput { + public string Activity { get; set; } + public override bool Execute () { LoadDefinitions (); using (var reader = new StreamReader (InputFilename)) { string line; int pid = -1; - var procStartRegex = new Regex ($@"^(?\d+-\d+\s+[\d:\.]+)\s+.*ActivityManager: Start proc.*for added application {ApplicationPackageName}: pid=(?\d+)"); + var procIdentification = string.IsNullOrEmpty (Activity) ? $"added application {ApplicationPackageName}" : $"activity {Activity}"; + var procStartRegex = new Regex ($@"^(?\d+-\d+\s+[\d:\.]+)\s+.*ActivityManager: Start proc.*for {procIdentification}: pid=(?\d+)"); Regex timingRegex = null; DateTime start = DateTime.Now; DateTime last = start; diff --git a/src/Mono.Android/Test/Mono.Android-Tests.projitems b/src/Mono.Android/Test/Mono.Android-Tests.projitems index 65b62e182ce..13bc4d032f6 100644 --- a/src/Mono.Android/Test/Mono.Android-Tests.projitems +++ b/src/Mono.Android/Test/Mono.Android-Tests.projitems @@ -8,14 +8,14 @@ <_MonoAndroidTestApkSizesInput>apk-sizes-$(_MonoAndroidTestPackage)-$(Configuration)$(_AotName).txt - + $(_MonoAndroidTestPackage) xamarin.android.runtimetests.TestInstrumentation $(_MonoAndroidTestResultsPath) $(MSBuildThisFileDirectory)timing-definitions-$(Configuration)$(_AotName).txt - + - + diff --git a/src/Mono.Android/Test/Mono.Android-Tests.targets b/src/Mono.Android/Test/Mono.Android-Tests.targets index 00d0be5a551..d1068ece991 100644 --- a/src/Mono.Android/Test/Mono.Android-Tests.targets +++ b/src/Mono.Android/Test/Mono.Android-Tests.targets @@ -8,7 +8,7 @@ - + - + + diff --git a/src/Xamarin.Android.Tools.BootstrapTasks/Xamarin.Android.Tools.BootstrapTasks/CreateAndroidEmulator.cs b/src/Xamarin.Android.Tools.BootstrapTasks/Xamarin.Android.Tools.BootstrapTasks/CreateAndroidEmulator.cs index 04e9453de95..463ba533070 100644 --- a/src/Xamarin.Android.Tools.BootstrapTasks/Xamarin.Android.Tools.BootstrapTasks/CreateAndroidEmulator.cs +++ b/src/Xamarin.Android.Tools.BootstrapTasks/Xamarin.Android.Tools.BootstrapTasks/CreateAndroidEmulator.cs @@ -21,7 +21,7 @@ public class CreateAndroidEmulator : Task public string TargetId {get; set;} - public string ImageName {get; set;} = "XamarinAndroidUnitTestRunner"; + public string ImageName {get; set;} = "XamarinAndroidTestRunner"; public override bool Execute () { diff --git a/src/Xamarin.Android.Tools.BootstrapTasks/Xamarin.Android.Tools.BootstrapTasks/RunUITests.cs b/src/Xamarin.Android.Tools.BootstrapTasks/Xamarin.Android.Tools.BootstrapTasks/RunUITests.cs new file mode 100644 index 00000000000..9841efb6542 --- /dev/null +++ b/src/Xamarin.Android.Tools.BootstrapTasks/Xamarin.Android.Tools.BootstrapTasks/RunUITests.cs @@ -0,0 +1,37 @@ +using Microsoft.Build.Framework; + +namespace Xamarin.Android.Tools.BootstrapTasks +{ + public class RunUITests : Adb + { + public string AdbTarget { get; set; } + public string AdbOptions { get; set; } + + [Required] + public string Activity { get; set; } + + protected override bool LogTaskMessages { + get { return false; } + } + + public override bool Execute () + { + Log.LogMessage (MessageImportance.Low, $"Task {nameof (RunUITests)}"); + Log.LogMessage (MessageImportance.Low, $" {nameof (AdbTarget)}: {AdbTarget}"); + Log.LogMessage (MessageImportance.Low, $" {nameof (AdbOptions)}: {AdbOptions}"); + Log.LogMessage (MessageImportance.Low, $" {nameof (Activity)}: {Activity}"); + + base.Execute (); + + Log.LogMessage(MessageImportance.Low, $" going to wait for 15 seconds"); + System.Threading.Thread.Sleep (15000); + + return !Log.HasLoggedErrors; + } + + protected override string GenerateCommandLineCommands () + { + return $"{AdbTarget} {AdbOptions} shell am start -n \"{Activity}\""; + } + } +} diff --git a/src/Xamarin.Android.Tools.BootstrapTasks/Xamarin.Android.Tools.BootstrapTasks/StartAndroidEmulator.cs b/src/Xamarin.Android.Tools.BootstrapTasks/Xamarin.Android.Tools.BootstrapTasks/StartAndroidEmulator.cs index 2689ef45199..3fd4142b8bf 100644 --- a/src/Xamarin.Android.Tools.BootstrapTasks/Xamarin.Android.Tools.BootstrapTasks/StartAndroidEmulator.cs +++ b/src/Xamarin.Android.Tools.BootstrapTasks/Xamarin.Android.Tools.BootstrapTasks/StartAndroidEmulator.cs @@ -19,7 +19,7 @@ public class StartAndroidEmulator : Task public string AndroidSdkHome {get; set;} public string Port {get; set;} - public string ImageName {get; set;} = "XamarinAndroidUnitTestRunner"; + public string ImageName {get; set;} = "XamarinAndroidTestRunner"; public string ToolPath {get; set;} public string ToolExe {get; set;} diff --git a/tests/CodeGen-Binding/Xamarin.Android.JcwGen-Tests/Xamarin.Android.JcwGen-Tests.projitems b/tests/CodeGen-Binding/Xamarin.Android.JcwGen-Tests/Xamarin.Android.JcwGen-Tests.projitems index 22e17d970c1..ea258a6242c 100644 --- a/tests/CodeGen-Binding/Xamarin.Android.JcwGen-Tests/Xamarin.Android.JcwGen-Tests.projitems +++ b/tests/CodeGen-Binding/Xamarin.Android.JcwGen-Tests/Xamarin.Android.JcwGen-Tests.projitems @@ -1,11 +1,11 @@ - + Xamarin.Android.JcwGen_Tests xamarin.android.jcwgentests.TestInstrumentation $(MSBuildThisFileDirectory)..\..\..\TestResult-Xamarin.Android.JcwGen_Tests.xml $(MSBuildThisFileDirectory)..\..\..\build-tools\scripts\TimingDefinitions.txt - + diff --git a/tests/CodeGen-Binding/Xamarin.Android.JcwGen-Tests/Xamarin.Android.JcwGen-Tests.targets b/tests/CodeGen-Binding/Xamarin.Android.JcwGen-Tests/Xamarin.Android.JcwGen-Tests.targets index 97813d56aea..616a3a8e166 100644 --- a/tests/CodeGen-Binding/Xamarin.Android.JcwGen-Tests/Xamarin.Android.JcwGen-Tests.targets +++ b/tests/CodeGen-Binding/Xamarin.Android.JcwGen-Tests/Xamarin.Android.JcwGen-Tests.targets @@ -1,7 +1,7 @@ - + -Aot - + + AcquireAndroidTarget; - UndeployUnitTestApks; - DeployUnitTestApks; - RunUnitTestApks; + UndeployTestApks; + DeployTestApks; + RunTestApks; ReleaseAndroidTarget diff --git a/tests/Xamarin.Forms-Performance-Integration/App.xaml b/tests/Xamarin.Forms-Performance-Integration/App.xaml new file mode 100644 index 00000000000..15677dfdfe9 --- /dev/null +++ b/tests/Xamarin.Forms-Performance-Integration/App.xaml @@ -0,0 +1,18 @@ + + + + + #2196F3 + #1976D2 + #96d1ff + #FAFAFA + #C0C0C0 + #4d4d4d + #999999 + + + + diff --git a/tests/Xamarin.Forms-Performance-Integration/App.xaml.cs b/tests/Xamarin.Forms-Performance-Integration/App.xaml.cs new file mode 100644 index 00000000000..d90dd6d63bc --- /dev/null +++ b/tests/Xamarin.Forms-Performance-Integration/App.xaml.cs @@ -0,0 +1,27 @@ +using System; + +using Xamarin.Forms; + +namespace Xamarin.Forms.Performance.Integration +{ + public partial class App : Application + { + public static bool UseMockDataStore = true; + public static string BackendUrl = "https://localhost:5000"; + + public App () + { + InitializeComponent (); + + if (UseMockDataStore) + DependencyService.Register (); + else + DependencyService.Register (); + + if (Device.RuntimePlatform == Device.iOS) + MainPage = new MainPage (); + else + MainPage = new NavigationPage (new MainPage ()); + } + } +} diff --git a/tests/Xamarin.Forms-Performance-Integration/Droid/Assets/AboutAssets.txt b/tests/Xamarin.Forms-Performance-Integration/Droid/Assets/AboutAssets.txt new file mode 100644 index 00000000000..a9b0638eb1b --- /dev/null +++ b/tests/Xamarin.Forms-Performance-Integration/Droid/Assets/AboutAssets.txt @@ -0,0 +1,19 @@ +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 "AndroidAsset". + +These files will be deployed with your package and will be accessible using Android's +AssetManager, like this: + +public class ReadAsset : Activity +{ + protected override void OnCreate (Bundle bundle) + { + base.OnCreate (bundle); + + InputStream input = Assets.Open ("my_asset.txt"); + } +} + +Additionally, some Android functions will automatically load asset files: + +Typeface tf = Typeface.CreateFromAsset (Context.Assets, "fonts/samplefont.ttf"); diff --git a/tests/Xamarin.Forms-Performance-Integration/Droid/MainActivity.cs b/tests/Xamarin.Forms-Performance-Integration/Droid/MainActivity.cs new file mode 100644 index 00000000000..6397bf0f32d --- /dev/null +++ b/tests/Xamarin.Forms-Performance-Integration/Droid/MainActivity.cs @@ -0,0 +1,71 @@ +using System; + +using Android.App; +using Android.Content; +using Android.Content.PM; +using Android.Runtime; +using Android.Views; +using Android.Widget; +using Android.OS; + +namespace Xamarin.Forms.Performance.Integration.Droid +{ + [Activity (Icon = "@drawable/icon", Theme = "@style/MyTheme", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation)] + public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity + { + bool firstOnCreate = true; + bool firstOnStart = true; + bool firstOnResume = true; + + protected override void OnCreate (Bundle bundle) + { + if (firstOnCreate) + Console.WriteLine ("startup-timing: OnCreate reached"); + + TabLayoutResource = Resource.Layout.Tabbar; + ToolbarResource = Resource.Layout.Toolbar; + + base.OnCreate (bundle); + + global::Xamarin.Forms.Forms.Init (this, bundle); + + LoadApplication (new App ()); + + if (firstOnCreate) { + Console.WriteLine ("startup-timing: OnCreate end reached"); + firstOnCreate = false; + } + } + + protected override void OnActivityResult (int requestCode, Result resultCode, Intent data) + { + base.OnActivityResult (requestCode, resultCode, data); + } + + protected override void OnStart () + { + if (firstOnStart) + Console.WriteLine ("startup-timing: OnStart reached"); + + base.OnStart (); + + if (firstOnStart) { + Console.WriteLine ("startup-timing: OnStart end reached"); + firstOnStart = false; + } + } + + protected override void OnResume () + { + if (firstOnResume) + Console.WriteLine ("startup-timing: OnResume reached"); + + base.OnResume (); + + if (firstOnResume) { + Console.WriteLine ("startup-timing: OnResume end reached"); + firstOnResume = false; + } + } + } +} diff --git a/tests/Xamarin.Forms-Performance-Integration/Droid/Properties/AndroidManifest.xml b/tests/Xamarin.Forms-Performance-Integration/Droid/Properties/AndroidManifest.xml new file mode 100644 index 00000000000..e9bec0891f9 --- /dev/null +++ b/tests/Xamarin.Forms-Performance-Integration/Droid/Properties/AndroidManifest.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/tests/Xamarin.Forms-Performance-Integration/Droid/Properties/AssemblyInfo.cs b/tests/Xamarin.Forms-Performance-Integration/Droid/Properties/AssemblyInfo.cs new file mode 100644 index 00000000000..691e0b192ee --- /dev/null +++ b/tests/Xamarin.Forms-Performance-Integration/Droid/Properties/AssemblyInfo.cs @@ -0,0 +1,27 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using Android.App; + +// Information about this assembly is defined by the following attributes. +// Change them to the values specific to your project. + +[assembly: AssemblyTitle ("Xamarin.Forms.Performance.Integration.Droid")] +[assembly: AssemblyDescription ("")] +[assembly: AssemblyConfiguration ("")] +[assembly: AssemblyCompany ("")] +[assembly: AssemblyProduct ("")] +[assembly: AssemblyCopyright ("${AuthorCopyright}")] +[assembly: AssemblyTrademark ("")] +[assembly: AssemblyCulture ("")] + +// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}". +// The form "{Major}.{Minor}.*" will automatically update the build and revision, +// and "{Major}.{Minor}.{Build}.*" will update just the revision. + +[assembly: AssemblyVersion ("1.0.0")] + +// The following attributes are used to specify the signing key for the assembly, +// if desired. See the Mono documentation for more information about signing. + +//[assembly: AssemblyDelaySign(false)] +//[assembly: AssemblyKeyFile("")] diff --git a/tests/Xamarin.Forms-Performance-Integration/Droid/Resources/AboutResources.txt b/tests/Xamarin.Forms-Performance-Integration/Droid/Resources/AboutResources.txt new file mode 100644 index 00000000000..10f52d46021 --- /dev/null +++ b/tests/Xamarin.Forms-Performance-Integration/Droid/Resources/AboutResources.txt @@ -0,0 +1,44 @@ +Images, layout descriptions, binary blobs and string dictionaries can be included +in your application as resource files. Various Android APIs are designed to +operate on the resource IDs instead of dealing with images, strings or binary blobs +directly. + +For example, a sample Android app that contains a user interface layout (main.axml), +an internationalization string table (strings.xml) and some icons (drawable-XXX/icon.png) +would keep its resources in the "Resources" directory of the application: + +Resources/ + drawable/ + icon.png + + layout/ + main.axml + + values/ + strings.xml + +In order to get the build system to recognize Android resources, set the build action to +"AndroidResource". The native Android APIs do not operate directly with filenames, but +instead operate on resource IDs. When you compile an Android application that uses resources, +the build system will package the resources for distribution and generate a class called "R" +(this is an Android convention) that contains the tokens for each one of the resources +included. For example, for the above Resources layout, this is what the R class would expose: + +public class R { + public class drawable { + public const int icon = 0x123; + } + + public class layout { + public const int main = 0x456; + } + + public class strings { + public const int first_string = 0xabc; + public const int second_string = 0xbcd; + } +} + +You would then use R.drawable.icon to reference the drawable/icon.png file, or R.layout.main +to reference the layout/main.axml file, or R.strings.first_string to reference the first +string in the dictionary file values/strings.xml. diff --git a/tests/Xamarin.Forms-Performance-Integration/Droid/Resources/drawable-hdpi/icon.png b/tests/Xamarin.Forms-Performance-Integration/Droid/Resources/drawable-hdpi/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..964f110abb68f1663c4fd164a0079ae9034ef96d GIT binary patch literal 1431 zcmV;I1!($-P)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGmbN~PnbOGLGA9w%&02p*dSaefwW^{L9 za%BK;VQFr3E^cLXAT%y8E;Eegmrwuz1qew*K~!i%?ON$?8$}f7kD?7FKsj0g5<=n- zM+Gf>0Ez%1ZXtwFr9!15B;W%|Ow*Rq5|WUX1lm&QL8?|MJ!wPRC_dsiwiDY)9N*X4 z;Wd6TGvnRanRRx8SNqfN+w9Dn-|ozt_ulMnw|99RNmp3A(G}KibcMAWU19A;SC|3s z+U47JD%Ll)ICy*oMIyDaNQcEedfJVXyrfL95I{Ip{;zMWP^cKbJ7`_g&Cbe*qQc(! zEsDeI0W^~jLu4!%Z7t+A2+hd8Cj%L5)59kxXUT^-6cPsBb~?rT2=J0$JVdP%m;ttN zvEy`{>;*@|G`FC^^ib8|SbF|N)Q)(~A_&D@9=vCRN*9(Ht72h?cDaJ4tE z`|<+W#fav@9^4nmm8&=^7a#sXg$OSXg|Wc^Zyt$|&%be4D4;26wMP#G$&R`-7dAYe z!XeGj93pTw%sF~n!l(1iiv~0&0vWQeE=`0zKNyno@L!EmA;PUBKyTAPV(?7=6#3+5 zBQb0f!z;sKvU@Xm!eGz*JyXDu#E>afH_+MPfvZVuBXEdLwAT0d=ksy_&BR=BE!Xw) zgne{e$g?qk=!6{P{yqN10ySyav9NE+XTTp|n+zC_%Fs|4oJF%4E17?#T6^+GYMFgb z$IVPc`^f&uqVb{3L9(FTx5v=+8w&GWoW}v=aXOeKhq}Y#3nuXxoXJ!=cTo=J$V9r{ zWC1^1OOiv8)E8#?EjIA#FVyCK`zQu3AD^H?hs_74TQSqTIvUg$wqI|@OJO|0Zte?_ z{PlQ|I>fuYSE70BU==^#)Gzqx3xhBM`?Y{=r()!on+8HJ4N;%*y<;=@XbXVwg&ea% zb;6$eDa0H;@2eMP$5Ol$&EHHEnY`n&;-%QS^Vmu|k9Uuu^I-Ch&61G~18L_0TtRog z&f{yOo{*=(2}!mpb@#wl4(KQ7xA?``1hy@s>%u(Qs``KiS_`TA#;A~Lxv)iLJ^6C= zp?y@zsppcykP2ua2u?Qz!M)dX{Ls)iwc{Zvp35l=f`PJW%a@}Gvisw`L0FmCMxgn& zVY|w-rA~OeIG7p2sq#8H(frf+?&5ryx^3v__Igz&nm|Y~vSOtH=JsIgut{m)t)o%$ z`PaqnI*Kb*8UTtEb6_`;U0?&i8I5fQm`s*BJk%FHtFo8_gau|&eyc4pSH=)eVH+1a z&rqK2slF-xbz(eQxqAi6Z$N&*l&Q>s?RZY;41}}jUbcSLB6<=B1iMmkMl)Ly2JE!q*V#{3{ny$5GhJcrMpsz7 l(G}KibcMAW9V)EL`!DSo$rUNEF*X1I002ovPDHLkV1lslsTBYK literal 0 HcmV?d00001 diff --git a/tests/Xamarin.Forms-Performance-Integration/Droid/Resources/drawable-xhdpi/icon.png b/tests/Xamarin.Forms-Performance-Integration/Droid/Resources/drawable-xhdpi/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..3c01e60ced0cd4ca7d47f6d4061603c5d0a4af95 GIT binary patch literal 1789 zcmeHH_g9k#6#Xb71c;|9U=LWV6)<2+O%MeK6$mn9grR^$A!q`XiUDH9QKVFMWS?jN z!wN%+VW}95kRU^uLJ1&*5fB0ykQg9j5E}jsPw%<+z4PAt?VfXAD#FtltZk?b007w4 z1@5EjA=}bYS5@Y@Hxys&*|8ci4#QD ziF}rAKI>E=>vSQ@?&;*Oq)B_yq+`*PQ^}Nb>D2kM>GR}ick;AH`3$0B#*lW2&7tc*Uw<)wrw)J=^mNCip+(8?l7z|K_{86s;7eHo>EyC>W<|yp%I3ewIAZytSk7Nl2-X!si9)!c z5KA{#Rg`T?k=3P0DExTQk%jYh*d4W z!#M@ud~afKw^7k(0E_x95@#8S#{F>2@~EX1e&I1f6{1jAxV>MvqktIXzZa^PsM+n2 zm7ov*Hm^Vr?sV20ZfT&Mf-sNPS4-dAYGvfSt#rra5Xlkq9oEx2jRWWqQ(|uJOgUMF zSmN(q-+a<_G-7+Ry{xm{>WkPTcq5uJ!;J(Ytom;xTuUN`b{RKIH(c;Mt-PzDNUvKZ-#UorE8;n)%0m)rQ_88m z;S4i8HHdqU)Yj-=ZgV3MN(kJ!H-i_}eUev~yb!T_#Kss2BVduI9{A=KM29mg*oD85)MD=yVzXTs?55TxPQVMTN6A*6+k z({6IJsa0YM@eSv8Y@FqOw{8zD5Y)9Pt29C5>4k(-1lO z1s5Re!MfRlCZi)5YzG(N8O=|f)9ZO5tx}HBVb&WT+sBdg7*=vpik8eLDn5P}R5*GW zv0m;F%qKAh2t&aLg!hY%eqM8^i`H@HGjZudnwyC*xI5Kz468lNyu9ei13NdO;~rXJ zxp&lhPM5>^gr!F?iQ$LpOgKD_8CqaY^@ypKSS4 zl25kij0_YpceNb?KO0no+c}7e^`7=}51w9khDegnO1c6#no$ye(4)&qG<3smN^J}) z*u}QhM{L~OqKAunzN8Etkun?PJyDWTdH(^Tu|3`|Y?odBQ0UAqoEhY3{(4 literal 0 HcmV?d00001 diff --git a/tests/Xamarin.Forms-Performance-Integration/Droid/Resources/drawable-xxhdpi/icon.png b/tests/Xamarin.Forms-Performance-Integration/Droid/Resources/drawable-xxhdpi/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..0d8c1c57dc5ca6268f2d5edae12f41a1a1dc3435 GIT binary patch literal 2353 zcmeHI`#Td18(yds%8X?LD*?0078A951+U zYwB-GOK$g`Y=y*a*@1F*umzC2)Tg%xsbKIWFaYp2MP@x=MLHBvF-BN_W5iFBHNM3h7gCH3x=Tu!!D$ee-)0n7LLG* zwlV5aH0oV4dZl#Cr)=zM*_a=BJb*k7uN)70I}uzpalLvXq-HX-X7X0;WOywHRmVZs zac+N@imacyOPPwMa_>{Qu^+h)KXD&@ntn{1e$qJov}p#@H1mwkd)~}TZsDc2^3vO8 zUod7f+h?=d=d!=dz3iAHbZ%olw`)GXd!E=cPx|_+@Y}DV-i4CBh4TId^7qBcfyHX( zQq7O0cY{k5)-rWyxq-d>kjS4VzrLp-`5o>&x5E{QoyYux3v8U7kius$nTpI=#D z5UwwZ)|W&Z{M8MCctg0hDO%fHUEf^a*xcOQ+Rp#~^*@-vy;*|Z_F8+w9j~AO00s1K z?qDOeqP9&Hw7oYPb_c4Tb7QI5>S=@)9QlBSf+7#AcLJZdzkL{H{OpESEu!uFh^0G3#pmL?T{mB2NY{3K z_31dA`4GDgFJQ*|#)Rd2<91GjR3B^{RJW_t-``W(8G#pwl_K_JgR7K3pDfbNH%h*) z5fTOu3e)y^Ts&}{6kt%b$O4}L?oWNr(Y9rn#fKj)KgK(a)hp8e*QR7Ols z?NS;$)1i}|VS#a|)WFi!ZLA2C7Mw??T$S$~JT*Fpd-lb<6~w6#ybZ%-v&d16I* z>)2(e+}84@BdY^c^dXT!v((2gXCF0iP>~&%=yj&*=cc7)F?Y#FbGe6V6~;bIc5}_E zzhHtVWT2A1Envi5m-j~MP3ow%AKi6+En7`}t?fwl46!`S`i|;;8otDe4o1}TOyCqT zuop0josB{6TT<1;M`t@(4$_L;*Urc(5ZW|@iE+X9fmB5p_Oa~DO|djCPDT_3;+rl* zed!QH-Pc&SBT4~>rCnH2=uIlS)$CkcUQu}fYE;mN3^vxxXmhEDdA>4n^;*l^amijn zGrf%+nEaf0-Hb1*Sk}<}&>eALPn3-gCFO({9TXv7m6O%<3WBXrd?L7%$&BVCskI8FXpveL8;+5R(hQQPcq zXWl`^XC=K(P3Xtz#LJUiaH4~8X^kH7%;M0fHzi|J z+J*|&_&f1LP@0^hXR}L2zQBnhJKeLB4@|T3fAc3Ctm<;A>o(H{Lce z{h!lN5F({W*7EenM}C?67u^v1=VdWb-L$Nz)x-5WQSfLPo*ia^X5$U|Ou zS6FHF#G*fC({`M)nktfszgmUr_|~!2zPr=y=|;GJ74xM}@Iu~#n<5&PcP%}K*&6#Q zgE`ySm~iI&RxDZ*TrZ3tuAd?dmxy!!)OdvdVE7{6+h8?ILpAy<6mgocwdb zipf77giuWfaL{mh%~+}f!#>04eSwVieaV_z;RAf-3N6FyE{GD^F&o(B@JJnU!|@MF z;#eWk-=L*bOK`61kXr=spCr3%Ur}VKuZe_we4k`?J*NDpYtvlo`#hn`ZKCK5Sv;8G zq^~3V8OKO|&{_?8v8n2C$=~aQlgTimN})%Q>~rH8K4?qsywuZF1yN*TCF-0b;Xwh%3<$an72)4H5DH9Z6J6WxLbEaWVtDo; zRh_{*{kJ8awWrtBh#T)ld`8Sqs98(6P73=PX zF3~)OaX&GwBY#q4!Ip1;#&d>=A*%cI`d*uR-iGZRg^i?@)ZZ9PtKeuP-ekRPx8Kg>*U5GY(igp;;-EBZmUC@o=G+g{zckmKk_=n4~ zpH~TmL}Jq0jpyS{NyxFwuYb@@<)MD4O!{9`QJx=u7Sv85?npXg@}JX*V^DWyh><3Q|PE|6`nJ^D8!9`HQ? literal 0 HcmV?d00001 diff --git a/tests/Xamarin.Forms-Performance-Integration/Droid/Resources/drawable/icon.png b/tests/Xamarin.Forms-Performance-Integration/Droid/Resources/drawable/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..b0ba7150f4d6c3bd6d95e68b7c89e39f1ef22bca GIT binary patch literal 1431 zcmV;I1!($-P)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGe?*IS_?*V;wibwze02p*dSaefwW^{L9 za%BK;VQFr3E^cLXAT%y8E;Eegmrwuz1qew*K~!i%?ON$?8$}f7kD?7FKsj0g5<=n- zM+Gf>0Ez%1ZXtwFr9!15B;W%|Ow*Rq5|WUX1lm&QL8?|MJ!wPRC_dsiwiDY)9N*X4 z;Wd6TGvnRanRRx8SNqfN+w9Dn-|ozt_ulMnw|99RNmp3A(G}KibcMAWU19A;SC|3s z+U47JD%Ll)ICy*oMIyDaNQcEedfJVXyrfL95I{Ip{;zMWP^cKbJ7`_g&Cbe*qQc(! zEsDeI0W^~jLu4!%Z7t+A2+hd8Cj%L5)59kxXUT^-6cPsBb~?rT2=J0$JVdP%m;ttN zvEy`{>;*@|G`FC^^ib8|SbF|N)Q)(~A_&D@9=vCRN*9(Ht72h?cDaJ4tE z`|<+W#fav@9^4nmm8&=^7a#sXg$OSXg|Wc^Zyt$|&%be4D4;26wMP#G$&R`-7dAYe z!XeGj93pTw%sF~n!l(1iiv~0&0vWQeE=`0zKNyno@L!EmA;PUBKyTAPV(?7=6#3+5 zBQb0f!z;sKvU@Xm!eGz*JyXDu#E>afH_+MPfvZVuBXEdLwAT0d=ksy_&BR=BE!Xw) zgne{e$g?qk=!6{P{yqN10ySyav9NE+XTTp|n+zC_%Fs|4oJF%4E17?#T6^+GYMFgb z$IVPc`^f&uqVb{3L9(FTx5v=+8w&GWoW}v=aXOeKhq}Y#3nuXxoXJ!=cTo=J$V9r{ zWC1^1OOiv8)E8#?EjIA#FVyCK`zQu3AD^H?hs_74TQSqTIvUg$wqI|@OJO|0Zte?_ z{PlQ|I>fuYSE70BU==^#)Gzqx3xhBM`?Y{=r()!on+8HJ4N;%*y<;=@XbXVwg&ea% zb;6$eDa0H;@2eMP$5Ol$&EHHEnY`n&;-%QS^Vmu|k9Uuu^I-Ch&61G~18L_0TtRog z&f{yOo{*=(2}!mpb@#wl4(KQ7xA?``1hy@s>%u(Qs``KiS_`TA#;A~Lxv)iLJ^6C= zp?y@zsppcykP2ua2u?Qz!M)dX{Ls)iwc{Zvp35l=f`PJW%a@}Gvisw`L0FmCMxgn& zVY|w-rA~OeIG7p2sq#8H(frf+?&5ryx^3v__Igz&nm|Y~vSOtH=JsIgut{m)t)o%$ z`PaqnI*Kb*8UTtEb6_`;U0?&i8I5fQm`s*BJk%FHtFo8_gau|&eyc4pSH=)eVH+1a z&rqK2slF-xbz(eQxqAi6Z$N&*l&Q>s?RZY;41}}jUbcSLB6<=B1iMmkMl)Ly2JE!q*V#{3{ny$5GhJcrMpsz7 l(G}KibcMAW9V)EL`!DSo$rUNEF*X1I002ovPDHLkV1oYMspS9w literal 0 HcmV?d00001 diff --git a/tests/Xamarin.Forms-Performance-Integration/Droid/Resources/drawable/xamarin_logo.png b/tests/Xamarin.Forms-Performance-Integration/Droid/Resources/drawable/xamarin_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..b36d00ed6970d202912eb38bb9027fec2ff8fc1b GIT binary patch literal 21481 zcmY&=WmH>Tur{P=fwp*oV#VDl?(Po7o#O5e4ek_ohXBRhodSiT!QBJFi~GlWzk6R< zYp*0f&e>=7%rkms60W2miToDtEgT#ivb2<#3LM-khnL^Oh;Lp#w^j4CUcP|l!t%m! za5b^-9*kbY!3n`hiwUcFz#nBHq|%Gegb3G)!Ba$}5KBp*i;*M61C>7jUnvI@z2DAs z9(&`20Th;&`JM|@jt@YLAeIh@{~Uk2cEaks(#HFFiQm@>+y*U!axd|&d@;y6cT4?I z3Cl*JbYO+YWd83e0fLa|HcWF9PCD<8#fE$vjbv*x5i&kyym5x1g62xDv<@8)ISx_h zt0*%adZAi8Y2T1i|Fzf<-^(Gy#4O46w-Yv!fSY3XO+8s=c-QStNy+0pL%oszUR{JZ-TF!rYRhspurLouTBLJp z!~&&gwJ>BgCurMV@cid6AAx{|qwYyhodiM;Mvfig!y)*HGfm@_keFrOS0Cy`+5dfT zPz}&(0aS2)Wtz{63VA1+36orx<8i>BVWOA(3ZOLnXJsL^SDo*|Y>UpXaNB3=rQKJ5 zG~L&FwUgZ6c9*ARjRWPn|5-4RTy-)BHdwLg;BxS&ehT3>7Y~MJQd~0~@9wF@f5{yJ;z9~xii{UMRdNale5 zYBj6r2ca*^lS066Wfm26mj7_b3A5w@k)Xg{6?CI-lk8N`=pK$#-b=mwCH$be{XZDE zND?BHSa%D|<+b$${OAQ|^{i|SbT-?OFbnoTM|?EWPN zx}BP=|B5CPNXs7_kFv}Zem%l%;&ye1;gLMCYec)_e2F|6_iu!}X9W$%{3Y@jLg?5M zF;NGZNlx~c`xxCL_WyDR6_7;mB5iOCG^hF}tOlPzoEX$Jp&g{$+=XDJJ^U{syMZ#o zxj)Dp)g#^{@H>nfK!HP7 zrM*YY?|(#4BS~`a@JU)#J4yz3p)8T^%IsYR;5+Lno zz>E4&Jh(w^hsMsk3NTV*2=1fPM{xfQTDe?OAT7&Z7m|9c#xUrjSGv{l0api-etryY zE8}Ba!2A!TdokbiTl9nLxfNQ^)r0xJ2oTNO3ci6LRwn!diuZi*ZzOjCz3KC`sZVq7 z08~G-dp`|U4l)kfZByQ~(Pzns^N`p@Szid1rgbG@`5Z^F;YDRGpbIb zUE2))Kg`Jm11(gEL@bJpR$NxaOhpg9T);C3#ZMU2mxu%izf9M)I=aGiy`vG^rReXp zKvdQr5=8&eB2XjHLeZKs!>nGsaE-JU&$g74tiODc5HuCj?O0v9wL7Ncem_&BjhPxU zgV4CT-S7{>l$ZeZ#e&jel5Zp$NVV-`UjvRLek8td%*Km%H*5Z31-QdXIRWbCwb9As zmGq*X`}GgvtYp7&Ts8okd>p=c<#Yq;i;hue8+0WAri7q-#;3OPWJf9<9Yz4p?qwZ7 z;a`u`d0|#EY4hG#hKGE_QHg)%ooKELAs0&HzHp6(zeDpZJV`ad;6m#wjIutq{{z}b zIe<0AdvAtCm7>gFDa^Z5QYn?M5Yis2Q4>SH6PD6}L|abf)aTX-QvMGe-QAd)G$f$3 zNqy?P&LUnk>MsZ}Z!Q5hty&2F9lwD2h*828_tY#0|9BapUx>ex=W|f0W}AHQ0Y$Lx zjRJkgA2_L8X+lhhhri_mwY-$$4*qd$pih`LN}G3PGUfKS96*`B?C0ZaAKjMd9DQ2= z4WeA|UJp%Bh2=js-k0*!YV%I@fXc0_^-s~>i$%!*28C+i6!2FugOItNF7Rpo%_u6s zvoyi)F3ba+PGpdZ%lhdN!hE}}&xIAHl(dk&(x36Sh2%E2|EDw{KeQX!UYRwC`{SUy zn_SzU>LOLAh40n=%H1?qHP zKehF@Sws==NGgJ`PqeF`&dn%q7QocEUOt=A zKKnmsGDJf>2^sfb=Gma;mZXf8Z!2 zj(Do~`Bi0hbj4!Ph*$D4zE834f~hCYJMxJt^YuoOH}0zd_rjuH#69o;i|*c?lQS88 z1jakZGyhlDjVGYUw(Hy&kuMIJqk-MvV6jD<&J=mcrnN(c|Igw;k#8sZ9V{w!1{aSR z(4Uki-=BRBF^}7Y^T-~y zqlZ@1;2AA57sAZtR?;A?U-mqcVxg_p|6mIgfwA6ZPOCdN+Q(jFTJx)BW1mr7H#qCJ zd$`#sS5laeAdf>a#xo^_@q~+bv@3st#&#O+neO`~ByT}=XyIeYz{bJr03RaLl!3-R zO4{6n{JpKPu@P}u)B_$0;%=Ow{2>^L1tCJ|l>A|zVlf6&zU)J*I`z|sxeeK{`$+Kz z=l`YS4s*RtDwcgTVBbCMGAvL6`qbw>^o~AD?pyX`+X-V#M|2W{9&U?2qTOii>yL-y z8$IoA@2;3+?4F0XD%j)Ucwzk|Su?^=XEN*KXS|1w`oM8qPwt<{70ww=f*LabWkcGk?hZSoo(+ z`&0SM$#kJykvDeCmK82Q;gWK!ujLK@cZ`yTzg@+hWUhG@4IcdgIv9_^RBnXI!3u%x zvUR4lh~JW+lYn5lIN&mWn~mzB`o%Iuk$2Y=;sZ)0c~YQv1>8@jG6)n-yXY>emQ+p) zwSQ}uCt{(;m9f|(TlO?sCj#BM#efVO<)un+wb-(tgiR!K&(phol3DTf53?-6aoJey zs2L5<8x5C+TEGwQx8L3=f7P&M{5i#T<{HOTBsCHCgX#?HMk z!>#{zH+u*m{pjxfq$1Pf_V7n^w(@+>!{Fmcj4J6x#@h2)!&ZB$YEeReBxsPa*~i=~ z;cz<8?Fys>E#>%wS3z;7qJd16lX zBn$gFSuU;c{zzg@`x7`m5`!u!{uZ>11hJ{8v><*ijqDn|*6*z4wCLzOG7+{P`l8M$ z3Q6jQOQrqjE1o*3&774&hw?b)yE!ZAMD0;j-7MfWFz@Zzm$|JIsbqOA^) zvdhSXMxS z<5uYpS)HPMRFaX}f2SU~nLw48Zr`l$WwV|TeU(0zi-ujc%4v&b^-V0f&`p0AsI4oP zTE#h7uegaKDC%9WiwnI2OTt zt49PQ45P&_`0^4Jv4jI=Z}|mZPT~6t^Gr^Z0=pIVCJ~3J$VzH{=3m zJTCE;a%w8pE!1@tPrwRTi;~3|Tt_rGP#@jgE+uM+|CL>yS$vA_(UlLZ;I+!wdQO>h z?FePKtgP$&UrkI*5VCj)myV`_TKu?0kA~?wV1AHFnMNA1f&*@jBZ!%^mijZEU{$zF zh>0kZLkTQWS3B>9nC{x9q36ID(c%4S$P_M%6~h+}JAxDjPHesKE6IuZ_7a82KiAdA z&DaMWeycIA= zdI>iEmOK_Q8aJQ`{cQFxl$Ox=d(p^h=ARXXJfj!|)QC{68@ND^wwjxT%QK3UR3#sV zR6JBvskpE&DW?N5LaqsUv_Fd<6mScs)5P4qgH3w|LW1V+YjA7QYWAyt)l_FhPTn_G zdNJpGfcT|;inPJMAjCb}OT9w_-+;S)e0JvPkj;1)&3fwD`UoB}8~lJ=2;hFRUbF5C z-YR}Y5*(JyD#!?p_7Si{!e1XxA+}+0{)nepPj+FajUgvDQ$LCs)lmSeR6esy)IL~h zG~Tk}Z5^HLuBz?w#?9pChI9@t$^2Xtljq>hON<<*?zx7*M_*+A$A=!#p4MO6>$y-d zoPO-y-Jch3n+tfdu9-{xOplC9`aRR2l>NnfIB*KxzPZ$=Z-IZfc8I&5QX>x|jUo*f zsTMgQCX0%9StDi`OE}E(xBeg6UbjiVarb5XLRu;9XaR3C8A8*^lDZkcZ?-fs9d$yY zE)e#J33;E7A8wzk{~pMj6?ksK^GP7Y_anrU7J_Uu>Sr;h{=l@ z3x-tEjGLeuwSr%?r1cizDrqeGbo#jP_ZM`~Cn=Dg6V?y?-Ouw#_>!&2O+P2wu-+}8 z!gn5tL?IyTMI{gqG~76xHDg^qK<$GX5D!-?%{?AtBepDj+qtOXsM0_2hdf@(1qpOo zQ3fzBPg)ZBEc0r!$;38ONggE3o8y6F0`LN9;_W!}LiIp;C^)`GJji6-0+a3|@$-ww z8Sa|Nb^=y1=cV(w4jNk>OyTp>)q1b)-(Gpz$k3Cngbp!P=b*3Yp$C{weoMp4f^Bn* zPb!{1FKb4iYpgi5BDE2;0g~NoUGOBn`R4oPx=sC=S4pib(38Mf+I1~jzw41sl}*A#J#p|w@hyQtE^yeL3Es_X|se6MT9{+9Ug_CUcL;L}l@ zU(bo$1xMKQp~`_lBEm;&tKbZ4z|D8i8T8~qt50RT7eN*JjRm|4D1gR#E@&|kD~zBiXnx)#x5YqcW2S4dgiK8b5Nq6vf`LkjcnL2O-zq zZ3w*|rslJ0f1RH`|HH(}mc7Z6QK>{Fd#e+T8=AoC?^>$xd6xu3DjRuvYm+9OwI?y* zmE6wnAJ>l53v+hXi_n2t1#~7b;*zbU*g}aznt97=pLQY?wu7KwlMh{%=g!?uWX3A5 z_*L`$TvKP1=dpDdRr9XWX2Hjii}s(q^`=~J_Z2*y?7Rbb1k>w&dn_1L1LzoW?M-V? zaW)_!D^lx`WABhf_9ebAR?sBaeIpFX3h%xw*jvd@(J(wS|LwWjmJ--CO^fVd|P;9jb+i7rsp|pa!kgJ zO=YXrCs1qBOvuK;5*;JJSHEVu^X!gt^lGRAANRPzG^fLC{#U1BQCDjtX zejE+X@k%~>H~(wCKyzVAhs)&?hkuup^7UDdQfE3lHtdn>YOedPi%olFy2yl-73-7d zU|JF>p~%6*B-i^j1zcYP1I$B7^%lvY=3fpue|%>3zExr-Sr2JIaNbZ@njcgeXyzk4 zQ5#DJA-Pc%lB`VZTRKefZFICc7snTY2M>H{_dUfkcXcNnz0^tFXFTtPzg-E#iEi>8^mZkAqzDFa5f^N)kbVMbc?mqw09#Bo`gvS5#VhW8pKoH=KyBkX!eOCq1NU z-^^5MZ6I9dtZd?5I?hHfxrxx2{M}wppQK8iZW5 zlNZg8=W1$o?cSUp#X^dBOIjiGBNvoON13_3uO`cfv+njzHMZt&eCkUs2gn>g=6u4n zmtPJ&n)hE}BjxB0e!aQ;vFrlqe~mP2LnpaB8kfoYt8lWhvXf|0eWD9F!o>ZM-gvt2 zfkyR0E~K#&W5j&kgN(y`N$znem&Vw?f6(*1RVWQrc~@Jmz}{3IrlucL6?r`+41=gi z`*uBHE&<9Q*N1cAl8|4+vqpmz#~3vw=WeeXZQS27VhMkHWM<%5L|qG;kS)wRDJ?j4 z%_zTH@e@86QNmdemXFYbIQL?>FRV$}v@!SChzNpL?`us*lOqG|k({i;yuL%?98$l2 zhcArEGP&y7D{ej`ZgRD=2m9AjUR@S$ukczw5!|{_&f++2Go`!g1KHv~D=X*4Cu_Vq zQ@PUE!SColH8pO*qjNTKmw0Fc7?_)-(ZspG$Mui(B~=AInc?x)28^FyOd#u7PhU-Z z*x&&dA;AXt2IYQ{5XAboPHs+90SIL4R4y;)&F{BGR;^;Yp>p<+-zy+d54(2U{(Z+j z_kiq_G?SZQ(j3cjJ!#Jsial|!7?~@1r8xZ|RwKX)tu)LWvr~s6ly0v~ztvflw99>c zIh#K8YImD3fy_U-oHJoRiXJzM3I{YEcQ1EkZ(5~%(}}^78TloTjEBk(HX4^sI?WPi z?#KE75Bc!(2RT2qg3}S&qW>Yh!knyKJdf6FIL(ua10qipN& zm3&e=iVnGT8pbxn9rEi&0Ji6Mj6%?Bm3zQAkxS+4`6(5y_sYVGjnQ>62W6`+F3F*L zzp}dcZ99T1lgA!Py($1e&}KDc(diNKX4GcSF8!zNRSh0)o9V9%vRLCgeX;?9pPB4r z@cad?QUmQp!vklH%qV)_zE}o5Z{6JYDG-Ot66TFmYw_&R?Kg^_(TVHierZnXIGdnN zMLA#tl;zOwgQFLP8#yyhy#=gb9>1>#5_&3veRDHtaf~_$NMZq%2lwM)d0X;Px>N|p zt>@Q2sZp(S&s|e}SXoi(g;3$ z8FX$bOw#mW1KgoJE?T6~RzE&-W$)TU&391<02xN4QqN z*(3a;!H08)hpGN5iHYp3s!OVu>fGQYWn2v0;GcpLqV$pwJ+8(E)FlenPsO%PH6xea z7!XyxWY-!WAX~nxW+$odNCyMEhObvEm^IqHFYx@pyZqj4tzySYKA|z*mkDIz z4i>qQSmTbn&Pl>C-|AY0rdzbm{pMLIF~az496cOfe8}`Kni`%WMW+tU`16}&G2tQt zkSg?4`p{tBWS&m*oNwl~jAm=}+~xiAc+4g3(YrZWZQW4sFaEP$Kkk0(&)y7YD>8l1 zDL+(j+sIj*$46%3g#G-MU?%YP5vxo!){QGPY2pY%*?QKV+gOBI%UNf(&%(erKgKEaSA9 zKB4{NnlbH=h4cKhP$5)D(8)CNJjW?`?O1w}M^}wdBZ+@SI)Z8p`!KxQqyBT+@z``q zzF)N(^PSoqhAGzCtNFfE9zrkdM%#QQ1RA?c7W5A`r8Gt~&3M zYMiu>JoBe*ZtlZT8)~2K(^o~I*%L=DQ!)olx7r0Km805)AD`Ig@)oRqXGh`*@>^Am zO^}}go=YaT6T{$qR0(6<-@B%HjoBSf=kCz$`FL0mKHd-!CGW9jyHmT2Ns%@xywl9s+Fxyy$n@3A+;`@n7nw-%`*{Zc#ic#IAQwrdEQ|>@niqS>y_OQH?uf zbdV8NCym;*qX8+XOxxqONF^zJf*oI1F5k-@B0L@|#)TH-fZHVy3=o~)l04kKid2-e zn$^fWgOSif$%6h|EGqhkIz_LvUg8a^{ zV}4}&l_j60Ci{?qVEgm22I#uCJpB@hHYoLuZ=uh(e;>LD=G@wvoWCD5$l6bkP$TMP z3!w6T52@j??c4p@LOpw&`tlCc!3mu5s|(fb{U9WRdn9IL%#rOi(N%xrAmyhfl2O6A zjf1yog^o7HhxVze0syEPAd?JpW<0PXZan(T$)8EmHLW(kl2<|D7SRG|R3rB-tCS9K zn)T}7>e#QigS*j0G{nGlc=~7KJxzgx)*7bY*B9!`->>i=d6MPuKzok|R0_XD90N09 zmx!4XD_4E3SsZMnU+XV8eim2P7Ot&_ftiBI>&<>vN)as6kc^>V_CU+Y9Gbqn>)A{h zVf+$Qfx65mX-3rRiNSGU^8>C=GJC9MduLuUL}gyz?KHFue2Ddqe`qPgq}(Tvb^K5x zU#Ly`TioOc2z^}F*Rsw7U3+B})xN;)o7Ng@`lYum){eB zCo}x0vPQ}_ROg>04jkFo3wCycdMeVD+7d1rY;=P^UXo(*Yb>4Pm@7liZ>rz8(P3u| zd&TUw^f>S+4Rr52&uh(>6$q1#x6MD!pQTp%r#n?OieYu~O1!JmSYi(-(|T*r$2R=e z;rFg&=J}2C$$}}3XJDclO^4l+N|PBOSD(@+pP{7R^Wj^FKJlfDL|(HV?BLqFDp#t; zozyv2D&V$PFo1TryE>=b$~eW)xIUt@Pvt{C^#pWnEVq+ueh$w3#Afippete}EjXE&AYr0QZl$=`EcI9r~PkOJ1l;D9>-GB51H!}1`#I^r;y}6ROSPoR4CvTA z#OmeHal+1`g3N3M&*S2TmW-?i+q5h)W+*Y{zw#Ayvt1Z=MDWAJWbczyCl{<;BqEE}CR)4v;#T@P04rib*;(<7d?&x04eJ5iiZk zMR8qalX;NHZyjg@N9}c=y>5iVdN#-(Mvzt)Jgz7WiIav{lXj=a1T<&-RMz2h}GFqsXv3{K}GwY*GfPF5^Y; zX&4FteoU`Qmwb_ye^!b!`tmjDtL<*3D!<~W^mLsHrsC>;P{GC>!;3QNSZH{RvCbn- ziI3M*;eSjwRv9Q{`qN&p-w`3o8I~NprjQEGO61tcId+q9XU1W$?LOfKD;MZ_?U-!w zOG_muBq@)trdHxiT0U6_Uu+jktjq>Z-ldxD{&g?U2Gtv0%`0aYi{{pB zSej8ZfcrAL8`bE!Y*mBz>i)3>c&QM!D3b&RYJo$c^s6fp*(B1w+wooQ0C$xgl#Z(8HABynmjWr} zQS-sD2O$?=rGuzvws{N{P3IyKHAoh6!n4A{7yoKc*)&SVMJz?!`iPqUAc~@zMc14& zvJmJd)*)vPQ-SUs7NWb;jInQd+DvoiyVB(Rm`6smHWN|PCYGTFjkzA?)M4zI#XV>x z%e_9g_*%+@%m|Y|}nMB2NghYLVz?<>z-konCvbxz9Q=-4*!cc4=ow7Q;ps zp+88fpv2lMdh-ddsJuEa=SpM=eTqE*0wnfWSfyeml9m2x?PAG4%UqLbCECJlKSm!c3zXFTJV&CZY*nfrN#>Z%Nm5hop2 zKBI9)P~ZgkDGg?6=B1)Ag;#Y+tq;=6k2;b$l;0RT7o}_fJ4Ff%)1u$-%$7{n@rP0+!?q7LZrkRTc8basV zPpNVApd38YsR%gPXkDW$Kj7Q+X3X=$-v2=O)qQ=HOb`)jasCk!YkSZlQ~ZS1oGZ3K(%$h?PJy`d-ce6Wsku+)CQ^wrL7Kov8tg(WFOy2y92E{3#l5NGA!C|KR8ziW z(lpbV2m#4KcH=spi2|SWPbY)&Ucfea_M(k(_s*F6y$qbKN}5shX7Lj8R_Do(qwY%m z_@TMjsO&&d``V?tJWq<8s<7$ezS37kYh*ZaBuQVaCu@1sak{uTFCG>+d?O@rPFdNM z?mzhbvC2WMG>S0n$ag|@dRWy_BhP)%vncfNI~UlzH~Ho-PNBk_omZmDPGfNGT;G!6 zH(v8#cpjL-PnFAY*1%uY;2iAkzMAQMFkL^&{@E_T48d+Wb9h;W@q1`D=vxlGBIv!R zB4zAxlwA&q{VKh;s$qJ2aHb$9e2#xh+zwMW&cS7(3=5SnoB0Jt?Oxp3B#fZ&XfnF5 z#vq0QP?Dm4$?y2yn)Z(<62auSZz2hQ6sDKA-dEbCnh&@-pDJ2k%mcPL$Mab&6VPs( z2aDgd#)8sdAyIqouv>nPG*IIw=EuN7FnLIkMBgbs{Y>~fSr<0Jka;ts0Bo38 zH`IWNr)J4b?D$giSB#YxezLyn$p^V_*3(h*#ozS=G+S-R9A0{}*gqCqL?s$s$ak$2 zX3(PoiDV}^Z30(h=>~7Pk*^%BZb`8hsu@iNHJOrl1sRmfVnxiIfT2$$D^#?CAHF~-VmJwLR`yK|0KC2v=P2xp%TNAYN=PRlPL zG87L?f>hz3dyCoMNknQ9rvUVMa?QtVjyCZ^-u9aDOn|O|6*`g*uezD7$X?^K_wV*k z!>RSzR}QG;G;|VWl_Hfk$?Yq?G^6}9G$>5T2SF+_6y0^Ij#?bx9ui{Z|O zeY}=D8Vz=bE{w??a;ChJSl*iVWhung>T@}=AIG~eZrHBgNYuT$J^^BBI+262EQiDu z*X};elx!4mp3r};9Tu^^D!K@nuMbw!^JAC;+%dBe1ZYl`%OI`R+l`1~K^pLOKu_3w4KxU$o@%`JJ)y)T$Hh-Jqvg&njfXVpa zuCyoB0h?)MlskLC=26>kqJ=o9xL1!b3%^$zC!1g&)U}k%28Bagv7klpE>!%^wgMe^ zkt|Yt0lX@B%P!%S^{NFZRt*cCa(WIS`%&3GU(vs*bCe><6f>eH#&r}Jm0xE zk3xVPiNjsPe(k*)!{}jx4p_pXSq2~Kmp^xZw?(KUVlwmZWcjK$=^rae*XnF88iPJ&f2k`7aG58UsCp40a^OCYln|yO}#C^7Rwi~-|w}&tNO;ER5qq$W+ zufzE3+KBHFpFokPrPjz}${mL$>~hfk%~T$HSU>lRd&8N#+S_lc`7~=#@tcs#_asyh zmFHsQYV+=7tyA3N6V){lnKiVxoJfDoI28;QvXC*=?6pUJJjS53$lCyZ`gW$*r*Y>@ z06&%#uEdK3sWp&xZ@%8s*ipXhb$Vl?ddSL_(?8bT8L_{+HgSR}!1Ef7dI@nk1D*vr z$pOKRI{Mo)H|JVxMPOFsvg&;BJmoxTDzCke)OWr}O8Du_c+->ao%iM!hoW5$<3#*z z&Mv}21xCE?%69dnq49Y$KRw(Tm(#Uz)5b&(!BWE3(PZoAb_5Y$pDniK$s8UPbe!#A z1F}09eoVookSn6Y4>qplGnb(=3LxO{Ea=^P+j6x99*3kXkzcpms8^~bKOo?fq|543 z<`)$7FPC-!^$+bY39S`b&&_lZVwAt>ls<+0B`;23Oz=_Q$_E>bkjVPg{Ocl)CH5kx zagL={?dWGO0jK-Qw{1#P0NC<#wdxytI*D)2rYIk-7{D0n+V+LKJaQEXBBwftZDBvy z0)?^~YaqwoN)2Tmu|gMN8Vk)5UDIV2KBiuIGGI+ELGzz6ROf3o_QogUY7=RsNQt&T zoWI)4(kc-Da`n+gsN7id`}tVkiw3}(;x`Xy#+OlNq;` zK|e^Yvo#CgQJEKE1O0xtvaSR_@j-Xf86N0oXnis9QmqOllDwv;V zU#octr%>!(gMz=~z2ONhw=TPZ<#KuCxOL*hNx^AFeHUu5J*;{q-1b}aHKDRdvfD4< zn;1A~2{>pCbo5Q0#Q|;@r<&JcXNjuex@0Eh+)eCJ{fJDFXx&#WkEj<#R=3_YZ;U6u ziQ_f}sf>Vxr{KDdi&4e-TfUoQhcUp9%%EY6bpl@^H#GQ%_va-#EmvPF{QUBDEso@Q0c1Q5(NXu+E@T~q4U=;Q zicopfr%(#U<)Cp{X&b#m7iC?*`}En2zJK5FIM%N8R`_Rdx$e zAV#JRX!;2ogER9yrl*4fFM_Klci^!;Z-m!=>sf@9Ds+idf7s2Ax1TPvm6-Ewbw1VD zD^Z^rGFlG~w8)ju7L1CaU}INP70ddAd86k773g|*$vnPwbDne0t)39LN_-t+WeZG@O&*`FhKY(SnLu>*=Newjz3R z^tFtedQ4-SAVidyFn#5Y(eGroDf5f^dKbdyCZXI#a^ znpg?;^?ZAOQ3}A@!V*6aVY4?h#g#xCIIrj@QV3WwkU2Cc(%bTw9uam!McE|U+lPKy zm3Y>CTI1IAXSrZoQ7u^Hm+wEPp*@7khEF5m&(YvtJ(DqOP>sDPK=KP9%Yps^vyBzW zAIBp1nhtXL`?+C~@E9j>BN;-x079m>nVA|+@V8jOT@wo32ZLk|AAUcvucikOt#{#J zlwl{Ph5WfU)WM#{)z?)Ow;!M+=(seg{~;5^W6WvfKX7b^cP23Y{8xkf5mM%FaN43h%2IwD z>xypsYY4YjN^)j_VztZ5Dw3CX*JE>R!_Y z2YzMq+NR@e$0TnpV2_8osGNO_at?aG4-(EZgs_kflZW=rC-~v;`&wE)xlz6hbdb&` zvxC%t5=Y(aw5zd2lBkO(!CiIxg;yEp_IYvaSVj3K7geC+h}#ZJgMfF>UGrXQ_XMi| z_(z*>$8J1DBo5@YpWnzD{1mu9a*&_x=x+@SsY1OepS;=vxYSCOi-K=!zy6MsRIQHb z*$thXL{{LA;4OR6{{*--$h*_$l=PfeDL4g}EMwy&zh;|q2Z5}v>s*rt-Bo)j zuP(Tz3ukOm3wE7Zm9dTjlpREq3I7 zNU|(}hDz57TO6QH^dZQ1!PiXBQ=Ez;I%hMwXqUWHM&T06q3BDLat=OuKW{!Ru2cB& zuhq`o%-moCr%M$Zh!8$8Lw{`xago4>opXbqrxj9cVTBgzA`vbh9p%$RG}A4ZhDg1B z?7AD~LoM!-*u3_?0vd?Bxp{CwJ``N3ecY1w{Hg;`%TtAhWkbxNWzg2PyNSzo%^#Gq zMxvJ1)AU!$srl71-tZll$B##x?hWFqQtV07`~E<`Ue5wfFYY3eTrK!>pzJ|(xRx1y zrc5V7z(Mk|jph$?gTk(WCmHBoejNFug}ckt3B@XBI<@D^TfZO5P}^|(<4K=Ar+f1@`neye zcCh`Uv_}P^!=_i2Yd`=}Ia&z?O$WRdXU2V+Ps~+9=RqX}NSCnr4LtjlK@~-`k8p>~ z75^WsUx_+QU&FHRSAT30T9A)TS&23s+{8C0*3P-;beip4sH=1wjnJgR4OWcBV%F^C zaNIJb-)}o;24Gt z41J&H1tdp23KxoHFo#w^TgJJTqL10KdT;hZdp6IcBrOapqF0^b(h2J?l1Dy_pILmZ zT64eW30g^zzg8Mi^iqKss@HNGF=Y{7m~a(;?n;Yp=NVI7?pL<3cH$3vLZ*(^9-}_X zy~rTW%4a2_rCt9+sq_YrdEV+-rSTSvsZCJ9WGeeK+~fDEOR-W4ocy$FdTLL2Ua%UO_C8!`b~(ez(DIQT6Et08s8h zsVe?wqGyKP<|ip0qcwnn6wNb+wtPtzV`-S;tj{i0$QIFM24pQ(rJO7{7b12gbWDD* zzfc#OogguUFw_9u<4)Mt48GHdR0A6omloxbCB{8bOiz_$fWQAW5#reTruKmH`|O#Q zZc{74NxF=OXw!})O%sBnB!3y+ZR0>K-&$#kfV|*aQF{*15412oNi65Jan(u#)h)^G z`iUQhviFt%X(X2wP{CwARIpJ7rpNuW$-4j3epYtDzB!p#7eCe`J_D@V)GwPF!;GjQ zW^2B=IQDX`(JE$~2I*Kb9I%}62*yeMAbq#7@v4BD04H>5HWZxo`P#p@Np~#1S)b1uZEf_U zv~F#k2mNd+d1%0B62)G5`%c-3pY!3UR|K0G^yja`8_-E>>Lm7~wiy8$NR>)n%@NmN zj@0T>s~l%Gp|6n$zxHzMEktrTmpPUVlybi(OTZgQcj7^`M8!L6$;rb5GK-sRH$MRJ z1gT%Ll?S`SrV^a7!&Puhei@b6Iv3Z64t+@ul#x^Qmgsd>Ftu(9CkO-m<*~(^=`7-c zCIx;STr?Nx(S8G4t@62Yzz7aRwzt>J@I@Zz5@}q&QWC;1?c)?29d+JxREb$(Po#&} z^5Ren=2N$`jk=*_Beg|xDH6bU)4j!9CzzaGO<-myfY>5FvC1TFJu1d|^*yLiHSSrc zZX>6sh2G9VVhM24 z{f5<8)5-=fT!Y@7iIk`bxJkWBGMNx#oriS(DJr~%JHqr4GIrUL26jiQpjn!i+U@GJ zr*xc~QC9~(efdF7R!b>^p>AOplf zOLX<~CH-PcLJs==lqpEn64{m)c?*q`+N_!G&V z<$!evQoAwb`75jupvE!L))>tA9@2Pl&Tzuxokda_b!(L|*b=G*7v|yz92aI^TkPSF zir*&MesMWcr&F!kYE}x>XOebe1+}7=jBx+JeS)jFcbg!FMtn}(gjy`{R$|U^sy88&8 zV)x#}tyR_(3jzLfxvG$9c(6ow>rJf$slQ*3ALQI#8e2=)p*E0B9)rYx?Z&|Yrow8)z}(}1p+IeXx+wJvBs|LR>TB@Ji@?#Bz=R7_ zjgqOGx8a2O&bMclV!sHOcQ%1|i4BeYer8-vI>+vDdx4diY3z3{d>1wtc?fuc}^l8bKBTUJJ@(WMY z`S~OF&y!zig0gP%mhEp1Tuf-CMZ7tJ?7P+FRY2nMxqQGU5Z$CLO2awh2A?+hmsAVI z@GJ58sH#tDM7-Z=<)%5pcayXU?(G^4c&cvdTv>JRg4*Ln zDunuvz6g$)i*@`gLRgM5^zzLaRByEgbL?_2XiLe`trky1C&%sy)=p>=Hg^}>_kLh< za*j*{fhCsh2C$K_8dH%Hzr(G?DX!0EA?ZS*lj*o~1PCjl!TPj?`1IBi);cfqMY;T- zQxx5TAAOnvQ=N-b_LJ`r-DK8xHY^kpg*cP8^>s7Zvlv*jf?ofE+{I7hr+P;#R> zoy+9|45*ATDgT^kUS&uOc*U0-{J%2JJRGXF4d6OWp|X`XvKASJWZ&1U*~gMn_T`nO zEMv(!ro|FQVX_Wmo2W@BA*t~yLe`NbVWOH0CNj3M%y+!k^_B1XuIv19{=O{W4MLsqFvI7KDj33LQ8iz z+ZNOor&JQNpMr@;dk>a|;}lp|?Zd9O>AE`feFMw6z{)W&ic4@>6I$O96%I`ql8OC( zj?O$lgC^Yq8 z-QTU+@1tG_uC{6icj{@ZM$JRIw)byBA5asoh>R4DfQb&`+bS{q{g|_o8*2k2*_F}~ zDs+Q~POayJebQfFP}pW+B0ucJN_9m&uQpuvq`&5`7!wz6$u8|ugGS$mAytI9mf_Oh zx{c{q(0$+52$$HYvG+%ZLM%T2YkPKb#(Jh3;qH|Julvzori;lR-dEMn6uCIn6W?0{`> zxAD|8U5Q-_15XrT^O$<+ahw*je7|=0TuyA|J9VE0BH&@)*7dx?688@2NM;#?syHq@#9?m%fNsfcjtmE<;PrM z*_(3m!KT9$IeB*D2Z5J%2G4o`(0(<*3=dZ=`(h9#!z>=yaz<4MP$qWT?Yb8mxotWR zPRu?E9ej?rF+NDF4Uvo1OBu2xtccRN%$+;*yAXlb&XZveyBE5bR;S`yE3Qvi*+g;V za&12eeVY?nu9PHr%4y}yqoTGDMU*DbXp;NnBU}9o{;b1n&4yJUoGdhCpw+N@`jDAFiMitl3N)3-z%2n~zd7n8aA}n}818tEYQ*bzJQW--_4s2@f z3Ky-go_*vUSdw)wM%^(~yI#j5Z&tG{Bo-#Yly{w_#`g~mEkB-nt)qsz4^cP$>3hv( zGHgr6b1cR?+tmwz!f)&U;uPX5Mb3~>#)b2?V+D&Vuin4B3hzmD#kh?s@OBKoeb(Fm zmoYP`0slw@?(mTH8`DVGcNJH3j$nXQ3!k>Dn9%5D#7hc_PM*}Cs5y_po2d68BAP2g z%adN_7rpW2nnPTG_FDj(c4Gl81o?9={>*E;)!|V2MMx$%h2PHb+<+X;j;9Un0nCSv$kcP0o<=syuey5v<=1)H7i#$q1wKca9G=>u$2s z%fbJ(gy!>FkWCR-BusZl!R}sSc08Yi&y4cm1mQ9wYbj>;+zo4_m6O#*Ed02d<~I#K z(V+S4*70A*$Xoq~Ezo^#iWgH{Mk6a`Z_K0$D8o$Cphisf(Z5@&=NNQu$NKW0S@q-% z{f|NoZ=mc~7Mw==pW(UV4_e>yWzm0X}rWu?@cD>=lDBi!UsuhkoQK zs8muPL_9>P#|6V->!yvBd~QFk@ zaK51bU2lOcVB48KmkF6O)hm_pW%VL?j{GWKDoU{|4P1vJ;_BU7q?+2+k4B$1|DgGk zhrD&^9@8!|EfOXkgqBQad$gRdD9ko9Tj!9V7nx-msyb2#A4Y_rdOaCAnB-7-A@&Y%#>lj5kN2(p0gEN%H>t)C)e z^l_jYPAtz}6%f2<G1w$O|6)=06A8k12I ztFK!3x7IS3gaW_Y9;U0dwK+6EbtgZL`@0mdXD6|AFoQHqRg5o_YWU7(_s-Qb*4ZoD5_Hfxxl7eeXUrmhGfqBxqiJA zF;q4q@d*}Du>*;oL9Qxc0h zH#uL3?QCULfPfbeOhK>M-_cpECUb41P2B~Uj>XQN)nd-B0c#jYc%+n!0R_7}A zw9|`+9!H#L`V;`LRhEQYO|X_s86qiZeVkZ$ftAXQ?xJiBEm2-^hNj17D#iq3k6)bd zyVzmAsi0Z4@5V8FPUVkCzNs!J6m7>;3W0p!muXR%afaZ8P^OLTY>`xnk!G~2 zb|%weUpy6WSZ;&lScGA2i*yd8c9&+x1C?*y*tZ*-a!pU7F)Ka3o?n&KA*d^w#%1sBI;x*y6$xJqX}rB`ZLhX#ibLS<2CQ((ca4|YOeDd=Ol6RH zdt0D67coRS-3*!agxNktDCWN*N%A{jgAM^9LBf{z~4zxDB%qh`k@MGwL_7o25Ml=|Qzkyb`yAZ~& zqgf@mtI7e=J-UV?8^~in6i`#m%fnvN)xCQqWq>$QRMyCmC7XW?3|E(_&p_(Qy###n znRBexi0$1b|6I5s2tyZ;)Ko5-@~*Y2G8=1pc~pG@+whWhxx?oVS!`u zO=eywC~i?ky@j}%Fw4ToTLyRD;$(yV$U?adalGQeh6V#t&XZFt0(PI7`TA!8*2d)r zsX0o2KrIRE^gR74Fsp-KVSFm4=cPOrJpynY{cd}U=TX^jNDjE^tUQdevnt_rz}r&Q z%<0d7%V5e-8gToP*m|Jq?UR5#ExUt=nrwfF^}fN%VJ}NoP)rH-5z39&chXECWsAD<;BhmL$kd8RJ zBs;LmWDMKTH7}#(zei9$O&?}T&ev5ux+b&Z_H1L!;+WKV@`0{C3szd^P0h2{_sF^f zai`#2JdLMLznDR8zi-H5H{u@c!#1#`4e!@{Hrqu~m72_4Ne^Zc^kG~jgIeLQKnU2BX+-}XNpMy%Pa~>$d7P#JJ8sRp zCgQ(CLAfPUJ^A+@Tf$WU z43#**|3;K(dXb}Ud*LuDrgFtJu4)XE|3CaMF#wZq06S<|R+xV$o)iF$i%>+VLUe7C zn7>rVUwzu6ClMlThO-k77PBndk~rRcA@c>6_7|#qB5Lk0{}FH%`P)ChWgZ4}4k42- zLrbqXHXA`lB60&kOU!@5$jWyHfAb*z4LN^f9LPN@wVBxxK7Qq+zm0mX6rdMMkv&uq zzSs0!Cb&%TV5{V`x7ZHmoT^@o#v+7{*vA-S*^W)v0}2BjUGRyUb(ODt|5_jF?x)th{d43rstbJQ(}5evoI6NI z_mf;-*wNvXYx0q=%2fZ0%#uf)I}K0eY5b1%I!$xfnJV8H>;81?MmC!YW7Ts(JUrn~ z*MNP*f_fQF#!En3(t{&@PM}WKNMjphSjFsE_wcL02e>QWfyE?6Cgl86AQ-TF3n`*%i@@g8iv>fm4e4N}{MK)r%x(yyjzIiX zfRVJE_U!{(ObJUp1+s%2&~X%9j3A8YhAZz?^aT4*D5Uy?mUu|&5)-5~_Jq4SJ|K4A zcrA-v&XWE=Hr-8#zXmYkZQ#Y^++ji@SXTWJEjbwRV3>&9eD9TSs|CV;>;CWuX0nz + diff --git a/tests/Xamarin.Forms-Performance-Integration/Droid/Resources/layout/Toolbar.axml b/tests/Xamarin.Forms-Performance-Integration/Droid/Resources/layout/Toolbar.axml new file mode 100644 index 00000000000..d685cbadba2 --- /dev/null +++ b/tests/Xamarin.Forms-Performance-Integration/Droid/Resources/layout/Toolbar.axml @@ -0,0 +1,2 @@ + + diff --git a/tests/Xamarin.Forms-Performance-Integration/Droid/Resources/values/styles.xml b/tests/Xamarin.Forms-Performance-Integration/Droid/Resources/values/styles.xml new file mode 100644 index 00000000000..7d6f9e578b4 --- /dev/null +++ b/tests/Xamarin.Forms-Performance-Integration/Droid/Resources/values/styles.xml @@ -0,0 +1,27 @@ + + + + + + + diff --git a/tests/Xamarin.Forms-Performance-Integration/Droid/Xamarin.Forms.Performance.Integration.Droid.csproj b/tests/Xamarin.Forms-Performance-Integration/Droid/Xamarin.Forms.Performance.Integration.Droid.csproj new file mode 100644 index 00000000000..49b90451ba8 --- /dev/null +++ b/tests/Xamarin.Forms-Performance-Integration/Droid/Xamarin.Forms.Performance.Integration.Droid.csproj @@ -0,0 +1,121 @@ + + + + Debug + AnyCPU + {576312CC-83FF-48B1-A473-488CDC7121AD} + {EFBA0AD7-5A72-4C68-AF49-83D382785DCF};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + Library + Xamarin.Forms.Performance.Integration.Droid + Xamarin.Forms.Performance.Integration.Droid + v7.1 + True + Resources\Resource.designer.cs + Resource + Properties\AndroidManifest.xml + Resources + Assets + true + + + true + full + false + ..\..\..\bin\TestDebug + DEBUG; + prompt + 4 + None + + + true + pdbonly + true + ..\..\..\bin\TestRelease + prompt + 4 + true + false + armeabi-v7a;x86 + + + + + + + + + ..\..\..\packages\Newtonsoft.Json.9.0.1\lib\portable-net45+wp80+win8+wpa81\Newtonsoft.Json.dll + + + ..\..\..\packages\Xam.Plugin.Connectivity.2.3.0\lib\MonoAndroid10\Plugin.Connectivity.Abstractions.dll + + + ..\..\..\packages\Xam.Plugin.Connectivity.2.3.0\lib\MonoAndroid10\Plugin.Connectivity.dll + + + ..\..\..\packages\Xamarin.Android.Support.v4.23.3.0\lib\MonoAndroid403\Xamarin.Android.Support.v4.dll + + + ..\..\..\packages\Xamarin.Android.Support.Vector.Drawable.23.3.0\lib\MonoAndroid403\Xamarin.Android.Support.Vector.Drawable.dll + + + ..\..\..\packages\Xamarin.Android.Support.Animated.Vector.Drawable.23.3.0\lib\MonoAndroid403\Xamarin.Android.Support.Animated.Vector.Drawable.dll + + + ..\..\..\packages\Xamarin.Android.Support.v7.RecyclerView.23.3.0\lib\MonoAndroid403\Xamarin.Android.Support.v7.RecyclerView.dll + + + ..\..\..\packages\Xamarin.Android.Support.v7.AppCompat.23.3.0\lib\MonoAndroid403\Xamarin.Android.Support.v7.AppCompat.dll + + + ..\..\..\packages\Xamarin.Android.Support.Design.23.3.0\lib\MonoAndroid43\Xamarin.Android.Support.Design.dll + + + ..\..\..\packages\Xamarin.Android.Support.v7.CardView.23.3.0\lib\MonoAndroid403\Xamarin.Android.Support.v7.CardView.dll + + + ..\..\..\packages\Xamarin.Android.Support.v7.MediaRouter.23.3.0\lib\MonoAndroid403\Xamarin.Android.Support.v7.MediaRouter.dll + + + ..\..\..\packages\Xamarin.Forms.2.3.4.247\lib\MonoAndroid10\FormsViewGroup.dll + + + ..\..\..\packages\Xamarin.Forms.2.3.4.247\lib\MonoAndroid10\Xamarin.Forms.Core.dll + + + ..\..\..\packages\Xamarin.Forms.2.3.4.247\lib\MonoAndroid10\Xamarin.Forms.Platform.Android.dll + + + ..\..\..\packages\Xamarin.Forms.2.3.4.247\lib\MonoAndroid10\Xamarin.Forms.Platform.dll + + + ..\..\..\packages\Xamarin.Forms.2.3.4.247\lib\MonoAndroid10\Xamarin.Forms.Xaml.dll + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/Xamarin.Forms-Performance-Integration/Droid/Xamarin.Forms.Performance.Integration.Droid.projitems b/tests/Xamarin.Forms-Performance-Integration/Droid/Xamarin.Forms.Performance.Integration.Droid.projitems new file mode 100644 index 00000000000..24015c3e338 --- /dev/null +++ b/tests/Xamarin.Forms-Performance-Integration/Droid/Xamarin.Forms.Performance.Integration.Droid.projitems @@ -0,0 +1,11 @@ + + + + + Xamarin.Forms_Performance_Integration + Xamarin.Forms_Performance_Integration/md52b709e14dec302485bbcaeac0bd817ce.MainActivity + $(MSBuildThisFileDirectory)..\..\..\TestResult-Xamarin.Forms_Test.xml + $(MSBuildThisFileDirectory)timing-definitions.txt + + + diff --git a/tests/Xamarin.Forms-Performance-Integration/Droid/packages.config b/tests/Xamarin.Forms-Performance-Integration/Droid/packages.config new file mode 100644 index 00000000000..dcda8dbc8f6 --- /dev/null +++ b/tests/Xamarin.Forms-Performance-Integration/Droid/packages.config @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tests/Xamarin.Forms-Performance-Integration/Droid/timing-definitions.txt b/tests/Xamarin.Forms-Performance-Integration/Droid/timing-definitions.txt new file mode 100644 index 00000000000..5305a2f82ba --- /dev/null +++ b/tests/Xamarin.Forms-Performance-Integration/Droid/timing-definitions.txt @@ -0,0 +1,14 @@ +# measure time of last monodroid-timing message appearance +last=monodroid-timing:\s+(?.*)$ + +# measure time of runtime and JNIEnv initialization end +init=monodroid-timing:\s+(?Runtime\.init: end native-to-managed.*)$ +JNI.init=monodroid-timing:\s+(?JNIEnv\.Initialize end:.*)$ + +# measure UI startup +OnCreateBegin=mono-stdout: startup-timing: (?OnCreate reached)$ +OnCreateEnd=mono-stdout: startup-timing: (?OnCreate end reached)$ +OnStartBegin=mono-stdout: startup-timing: (?OnStart reached)$ +OnStartEnd=mono-stdout: startup-timing: (?OnStart end reached)$ +OnResumeBegin=mono-stdout: startup-timing: (?OnResume reached)$ +OnResumeEnd=mono-stdout: startup-timing: (?OnResume end reached)$ diff --git a/tests/Xamarin.Forms-Performance-Integration/Models/Item.cs b/tests/Xamarin.Forms-Performance-Integration/Models/Item.cs new file mode 100644 index 00000000000..d21a2759fc9 --- /dev/null +++ b/tests/Xamarin.Forms-Performance-Integration/Models/Item.cs @@ -0,0 +1,11 @@ +using System; + +namespace Xamarin.Forms.Performance.Integration +{ + public class Item + { + public string Id { get; set; } + public string Text { get; set; } + public string Description { get; set; } + } +} diff --git a/tests/Xamarin.Forms-Performance-Integration/Services/CloudDataStore.cs b/tests/Xamarin.Forms-Performance-Integration/Services/CloudDataStore.cs new file mode 100644 index 00000000000..455557d5063 --- /dev/null +++ b/tests/Xamarin.Forms-Performance-Integration/Services/CloudDataStore.cs @@ -0,0 +1,81 @@ +using System; +using System.Collections.Generic; +using System.Net.Http; +using System.Text; +using System.Threading.Tasks; + +using Newtonsoft.Json; +using Plugin.Connectivity; + +namespace Xamarin.Forms.Performance.Integration +{ + public class CloudDataStore : IDataStore + { + HttpClient client; + IEnumerable items; + + public CloudDataStore () + { + client = new HttpClient (); + client.BaseAddress = new Uri ($"{App.BackendUrl}/"); + + items = new List (); + } + + public async Task> GetItemsAsync (bool forceRefresh = false) + { + if (forceRefresh && CrossConnectivity.Current.IsConnected) { + var json = await client.GetStringAsync ($"api/item"); + items = await Task.Run (() => JsonConvert.DeserializeObject> (json)); + } + + return items; + } + + public async Task GetItemAsync (string id) + { + if (id != null && CrossConnectivity.Current.IsConnected) { + var json = await client.GetStringAsync ($"api/item/{id}"); + return await Task.Run (() => JsonConvert.DeserializeObject (json)); + } + + return null; + } + + public async Task AddItemAsync (Item item) + { + if (item == null || !CrossConnectivity.Current.IsConnected) + return false; + + var serializedItem = JsonConvert.SerializeObject (item); + + var response = await client.PostAsync ($"api/item", new StringContent (serializedItem, Encoding.UTF8, "application/json")); + + return response.IsSuccessStatusCode; + } + + public async Task UpdateItemAsync (Item item) + { + if (item == null || item.Id == null || !CrossConnectivity.Current.IsConnected) + return false; + + var serializedItem = JsonConvert.SerializeObject (item); + var buffer = Encoding.UTF8.GetBytes (serializedItem); + var byteContent = new ByteArrayContent (buffer); + + var response = await client.PutAsync (new Uri ($"api/item/{item.Id}"), byteContent); + + return response.IsSuccessStatusCode; + } + + public async Task DeleteItemAsync (string id) + { + if (string.IsNullOrEmpty (id) && !CrossConnectivity.Current.IsConnected) + return false; + + var response = await client.DeleteAsync ($"api/item/{id}"); + + return response.IsSuccessStatusCode; + } + } +} diff --git a/tests/Xamarin.Forms-Performance-Integration/Services/IDataStore.cs b/tests/Xamarin.Forms-Performance-Integration/Services/IDataStore.cs new file mode 100644 index 00000000000..3857596c717 --- /dev/null +++ b/tests/Xamarin.Forms-Performance-Integration/Services/IDataStore.cs @@ -0,0 +1,14 @@ +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Xamarin.Forms.Performance.Integration +{ + public interface IDataStore + { + Task AddItemAsync (T item); + Task UpdateItemAsync (T item); + Task DeleteItemAsync (string id); + Task GetItemAsync (string id); + Task> GetItemsAsync (bool forceRefresh = false); + } +} diff --git a/tests/Xamarin.Forms-Performance-Integration/Services/MockDataStore.cs b/tests/Xamarin.Forms-Performance-Integration/Services/MockDataStore.cs new file mode 100644 index 00000000000..cd790ea261d --- /dev/null +++ b/tests/Xamarin.Forms-Performance-Integration/Services/MockDataStore.cs @@ -0,0 +1,64 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Xamarin.Forms.Performance.Integration +{ + public class MockDataStore : IDataStore + { + List items; + + public MockDataStore () + { + items = new List (); + var mockItems = new List + { + new Item { Id = Guid.NewGuid().ToString(), Text = "First item", Description="This is an item description." }, + new Item { Id = Guid.NewGuid().ToString(), Text = "Second item", Description="This is an item description." }, + new Item { Id = Guid.NewGuid().ToString(), Text = "Third item", Description="This is an item description." }, + new Item { Id = Guid.NewGuid().ToString(), Text = "Fourth item", Description="This is an item description." }, + new Item { Id = Guid.NewGuid().ToString(), Text = "Fifth item", Description="This is an item description." }, + new Item { Id = Guid.NewGuid().ToString(), Text = "Sixth item", Description="This is an item description." }, + }; + + foreach (var item in mockItems) { + items.Add (item); + } + } + + public async Task AddItemAsync (Item item) + { + items.Add (item); + + return await Task.FromResult (true); + } + + public async Task UpdateItemAsync (Item item) + { + var _item = items.Where ((Item arg) => arg.Id == item.Id).FirstOrDefault (); + items.Remove (_item); + items.Add (item); + + return await Task.FromResult (true); + } + + public async Task DeleteItemAsync (string id) + { + var _item = items.Where ((Item arg) => arg.Id == id).FirstOrDefault (); + items.Remove (_item); + + return await Task.FromResult (true); + } + + public async Task GetItemAsync (string id) + { + return await Task.FromResult (items.FirstOrDefault (s => s.Id == id)); + } + + public async Task> GetItemsAsync (bool forceRefresh = false) + { + return await Task.FromResult (items); + } + } +} diff --git a/tests/Xamarin.Forms-Performance-Integration/ViewModels/AboutViewModel.cs b/tests/Xamarin.Forms-Performance-Integration/ViewModels/AboutViewModel.cs new file mode 100644 index 00000000000..2a18cfd48d5 --- /dev/null +++ b/tests/Xamarin.Forms-Performance-Integration/ViewModels/AboutViewModel.cs @@ -0,0 +1,19 @@ +using System; +using System.Windows.Input; + +using Xamarin.Forms; + +namespace Xamarin.Forms.Performance.Integration +{ + public class AboutViewModel : BaseViewModel + { + public AboutViewModel () + { + Title = "About"; + + OpenWebCommand = new Command (() => Device.OpenUri (new Uri ("https://xamarin.com/platform"))); + } + + public ICommand OpenWebCommand { get; } + } +} diff --git a/tests/Xamarin.Forms-Performance-Integration/ViewModels/BaseViewModel.cs b/tests/Xamarin.Forms-Performance-Integration/ViewModels/BaseViewModel.cs new file mode 100644 index 00000000000..0a2a3323679 --- /dev/null +++ b/tests/Xamarin.Forms-Performance-Integration/ViewModels/BaseViewModel.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Runtime.CompilerServices; + +using Xamarin.Forms; + +namespace Xamarin.Forms.Performance.Integration +{ + public class BaseViewModel : INotifyPropertyChanged + { + public IDataStore DataStore => DependencyService.Get> () ?? new MockDataStore (); + + bool isBusy = false; + public bool IsBusy { + get { return isBusy; } + set { SetProperty (ref isBusy, value); } + } + + string title = string.Empty; + public string Title { + get { return title; } + set { SetProperty (ref title, value); } + } + + protected bool SetProperty (ref T backingStore, T value, + [CallerMemberName]string propertyName = "", + Action onChanged = null) + { + if (EqualityComparer.Default.Equals (backingStore, value)) + return false; + + backingStore = value; + onChanged?.Invoke (); + OnPropertyChanged (propertyName); + return true; + } + + #region INotifyPropertyChanged + public event PropertyChangedEventHandler PropertyChanged; + protected void OnPropertyChanged ([CallerMemberName] string propertyName = "") + { + var changed = PropertyChanged; + if (changed == null) + return; + + changed.Invoke (this, new PropertyChangedEventArgs (propertyName)); + } + #endregion + } +} diff --git a/tests/Xamarin.Forms-Performance-Integration/ViewModels/ItemDetailViewModel.cs b/tests/Xamarin.Forms-Performance-Integration/ViewModels/ItemDetailViewModel.cs new file mode 100644 index 00000000000..4239d93a849 --- /dev/null +++ b/tests/Xamarin.Forms-Performance-Integration/ViewModels/ItemDetailViewModel.cs @@ -0,0 +1,14 @@ +using System; + +namespace Xamarin.Forms.Performance.Integration +{ + public class ItemDetailViewModel : BaseViewModel + { + public Item Item { get; set; } + public ItemDetailViewModel (Item item = null) + { + Title = item?.Text; + Item = item; + } + } +} diff --git a/tests/Xamarin.Forms-Performance-Integration/ViewModels/ItemsViewModel.cs b/tests/Xamarin.Forms-Performance-Integration/ViewModels/ItemsViewModel.cs new file mode 100644 index 00000000000..a0ebba2fa6d --- /dev/null +++ b/tests/Xamarin.Forms-Performance-Integration/ViewModels/ItemsViewModel.cs @@ -0,0 +1,48 @@ +using System; +using System.Collections.ObjectModel; +using System.Diagnostics; +using System.Threading.Tasks; + +using Xamarin.Forms; + +namespace Xamarin.Forms.Performance.Integration +{ + public class ItemsViewModel : BaseViewModel + { + public ObservableCollection Items { get; set; } + public Command LoadItemsCommand { get; set; } + + public ItemsViewModel () + { + Title = "Browse"; + Items = new ObservableCollection (); + LoadItemsCommand = new Command (async () => await ExecuteLoadItemsCommand ()); + + MessagingCenter.Subscribe (this, "AddItem", async (obj, item) => { + var _item = item as Item; + Items.Add (_item); + await DataStore.AddItemAsync (_item); + }); + } + + async Task ExecuteLoadItemsCommand () + { + if (IsBusy) + return; + + IsBusy = true; + + try { + Items.Clear (); + var items = await DataStore.GetItemsAsync (true); + foreach (var item in items) { + Items.Add (item); + } + } catch (Exception ex) { + Debug.WriteLine (ex); + } finally { + IsBusy = false; + } + } + } +} diff --git a/tests/Xamarin.Forms-Performance-Integration/Views/AboutPage.xaml b/tests/Xamarin.Forms-Performance-Integration/Views/AboutPage.xaml new file mode 100644 index 00000000000..35ae053100c --- /dev/null +++ b/tests/Xamarin.Forms-Performance-Integration/Views/AboutPage.xaml @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + +