This commit is contained in:
Michael Scherotter 2020-11-12 11:15:03 -08:00
Родитель 6aa1525f7c
Коммит 070103024c
310 изменённых файлов: 550303 добавлений и 3 удалений

Просмотреть файл

@ -1,6 +1,7 @@
{
"ExpandedNodes": [
""
"\\Source",
"\\Source\\Sketch360.XPlat.Android"
],
"PreviewInSolutionExplorer": false
}

Просмотреть файл

@ -1,9 +1,31 @@
# Sketch 360
# Sketch 360 a Microsoft Garage Project
A Cross Platform 360 Degree Panoramic Sketching App
- Install from the [Google Play Marketplace](https://play.google.com/store/apps/details?id=com.microsoft.garage.sketch360app)
- Install from the [Microsoft Store](https://www.microsoft.com/en-us/p/sketch-360/9p89s2qlh11t)
Sketch 360 was built by [Michael Scherotter](https://github.com/mscherotter) and [originally released](https://www.microsoft.com/en-us/garage/blog/2018/11/finally-a-way-to-sketch-360-degree-vr-scenes/) as a Microsoft Garage project in 2018 as a UWP app for Microsoft Windows as is available today for free in the [Microsoft Store](https://www.microsoft.com/en-us/p/sketch-360/9p89s2qlh11t). This is a new version of Sketch 360 built to take advantage of pen-enabled devices that have two screens like the [Microsoft Surface Duo](https://www.microsoft.com/en-us/surface/devices/surface-duo) but it should work on most Android devices as well.
## Examples of Sketches created with Sketch 360
- [360 Drawings](https://lightroom.adobe.com/shares/21b9e652ff4e46ef86130478cbb50abf)
- [360 Videos](https://studio.youtube.com/video/AImRmQYN_hk/edit)
- [Virtual Flight 360 Sketch](https://youtu.be/pTFVXD1v3zQ)
## Sketch 360 on the Microsoft Surface Duo
![Sketch 360 on the Microsoft Surface Duo](mobile.jpg)
## Platforms
- ***Android**: Complete with enhancements for Microsoft Surface Duo (dual screen & pressure-sensitive pen support)
- **iOS**: In-progress
- **Windows**: In-progress but not prioritized as the native Windows version is already available.
## Technology
- Xamarin Forms
- SkiaSharp
- Babylon JS
## Contributing
[How to contribute](Contributing.md)
## Open Source
[Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct)
## Trademarks

17
Source/.editorconfig Normal file
Просмотреть файл

@ -0,0 +1,17 @@
# EditorConfig is awesome: http://EditorConfig.org
# top-most EditorConfig file
root = true
# Don't use tabs for indentation.
[*]
indent_style = space
# (Please don't specify an indent_size here; that has too many unintended consequences.)
# Code files
[*.{cs,csx,vb,vbx}]
indent_style = space
indent_size = 4
insert_final_newline = false
charset = utf-8-bom
trim_trailing_whitespace = true

199
Source/.gitignore поставляемый Normal file
Просмотреть файл

@ -0,0 +1,199 @@
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
# Visual Studio (>=2015) project-specific, machine local files
.vs/
# User-specific files
*.suo
*.user
*.sln.docstates
*.userprefs
*.ide-shm
*.ide-wal
*.dll
*.pdb
classes.zip
# ignore Xamarin.Android Resource.Designer.cs files
**/*.Droid/**/[Rr]esource.[Dd]esigner.cs
**/*.Android/**/[Rr]esource.[Dd]esigner.cs
**/Android/**/[Rr]esource.[Dd]esigner.cs
**/Droid/**/[Rr]esource.[Dd]esigner.cs
# Xamarin Components
Components/
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
x64/
build/
bld/
[Bb]in/
[Oo]bj/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
#NUNIT
*.VisualState.xml
TestResult.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
*_i.c
*_p.c
*_i.h
*.ilk
*.meta
*.obj
*.pch
*.pdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opensdf
*.sdf
*.cachefile
# Visual Studio profiler
*.psess
*.vsp
*.vspx
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# JustCode is a .NET coding addin-in
.JustCode
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# NCrunch
*.ncrunch*
_NCrunch_*
.*crunch*.local.xml
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# NuGet Packages Directory
packages/
*.nuget.targets
*.lock.json
*.nuget.props
## TODO: If the tool you use requires repositories.config uncomment the next line
#!packages/repositories.config
# Enable "build/" folder in the NuGet Packages folder since NuGet packages use it for MSBuild targets
# This line needs to be after the ignore of the build folder (and the packages folder if the line above has been uncommented)
!packages/build/
# Windows Azure Build Output
csx/
*.build.csdef
# Windows Store app package directory
AppPackages/
# Others
sql/
*.Cache
ClientBin/
[Ss]tyle[Cc]op.*
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.pfx
*.publishsettings
node_modules/
.DS_Store
*.bak
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file to a newer
# Visual Studio version. Backup files are not needed, because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
# SQL Server files
*.mdf
*.ldf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
# Microsoft Fakes
FakesAssemblies/
.vscode/settings.json

Двоичные данные
Source/EquirectangularGrid.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 841 KiB

6
Source/NuGet.config Normal file
Просмотреть файл

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<add key="nugetLocal" value="nuget" />
</packageSources>
</configuration>

Просмотреть файл

@ -0,0 +1,30 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System.IO;
using System.Reflection;
[assembly: Xamarin.Forms.Dependency(typeof(Sketch360.XPlat.Droid.BaseUrl))]
namespace Sketch360.XPlat.Droid
{
public class BaseUrl : IBaseUrl
{
public string GetBase()
{
return "file:///android_asset/";
}
public Stream GetDrawableImageStream(string filename)
{
string resourceID = "Sketch360.XPlat.Droid.Resources.drawable." + filename;
var assembly = typeof(BaseUrl).GetTypeInfo().Assembly;
//var names = assembly.GetManifestResourceNames();
return assembly.GetManifestResourceStream(resourceID);
}
}
}

Просмотреть файл

@ -0,0 +1,30 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using Android.Content;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
[assembly: ExportRenderer(typeof(Xamarin.Forms.WebView), typeof(Sketch360.XPlat.Droid.CustomWebViewRenderer))]
namespace Sketch360.XPlat.Droid
{
/// <summary>
/// Custom WebView renderer that allows local file URLs
/// </summary>
public class CustomWebViewRenderer : WebViewRenderer
{
public CustomWebViewRenderer(Context context) : base(context)
{
}
protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.WebView> e)
{
base.OnElementChanged(e);
if (Control != null)
Control.Settings.AllowUniversalAccessFromFileURLs = true;
}
}
}

Двоичные данные
Source/Sketch360.XPlat.Android/Equirectangular Grid.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 841 KiB

Просмотреть файл

@ -0,0 +1,33 @@
using Android.Content;
using Android.Graphics;
using System;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
[assembly: ExportRenderer(typeof(Sketch360.XPlat.InkCanvas), typeof(Sketch360.XPlat.Droid.InkCanvasRenderer))]
namespace Sketch360.XPlat.Droid
{
/// <summary>
/// InkCanvas renderer for Android
/// </summary>
public class InkCanvasRenderer : ViewRenderer
{
Paint _inkPaint = new Paint();
/// <summary>
/// Initializes a new instance of the InkCanvasRenderer class
/// </summary>
/// <param name="context">the context</param>
public InkCanvasRenderer(Context context) : base(context)
{
}
protected override void OnDraw(Canvas canvas)
{
if (canvas == null) throw new ArgumentNullException(nameof(canvas));
canvas.DrawText("InkCanvas", 100, 100, _inkPaint);
}
}
}

Просмотреть файл

@ -0,0 +1,22 @@
using Xamarin.Duo.Forms.Samples;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
[assembly: Dependency(typeof(LayoutService))]
namespace Xamarin.Duo.Forms.Samples
{
public class LayoutService : LayoutServiceBase, ILayoutService
{
public override Point? GetLocationOnScreen(VisualElement visualElement)
{
var view = Platform.GetRenderer(visualElement);
if (view?.View == null)
return null;
int[] location = new int[2];
view.View.GetLocationOnScreen(location);
return new Point(view.View.Context.FromPixels(location[0]), view.View.Context.FromPixels(location[1]));
}
}
}

Просмотреть файл

@ -0,0 +1,111 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using Android.App;
using Android.Content;
using Android.Content.PM;
using Android.OS;
using Android.Runtime;
//using Microsoft.AppCenter.Distribute;
using System.Threading.Tasks;
//using Xamarin.Duo.Forms.Samples;
namespace Sketch360.XPlat.Droid
{
/// <summary>
/// Android main activity
/// </summary>
[Activity(Label = "Sketch 360", Icon = "@drawable/logo", Theme = "@style/MainTheme", MainLauncher = true,
ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation | ConfigChanges.ScreenLayout | ConfigChanges.SmallestScreenSize)]
[IntentFilter(new[] { Intent.ActionView, Intent.ActionMain, Intent.ActionEdit },
//Categories = new[] { Intent.CategoryBrowsable, Intent.CategoryDefault, Intent.CategoryOpenable, Intent.CategoryLauncher},
Categories = new[] { Intent.CategoryDefault, Intent.CategoryBrowsable },
DataMimeTypes = new[] { "application/x-sketch360", "application/octet-stream" },
DataScheme = "file",
Label = "Sketch 360",
DataPathPatterns = new[]
{
"*.sketch360",
".*\\.sketch360",
".*\\..*\\.sketch360",
".*\\..*\\..*\\.sketch360",
".*\\..*\\..*\\..*\\.sketch360"
},
Icon = "@drawable/logo",
DataHost = "*")]
public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity
{
internal static MainActivity Instance { get; private set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", Justification = "<Pending>")]
protected override void OnCreate(Bundle savedInstanceState)
{
global::Xamarin.Forms.DualScreen.DualScreenService.Init(this);
Remote.MainActivity = this;
TabLayoutResource = Resource.Layout.Tabbar;
ToolbarResource = Resource.Layout.Toolbar;
Instance = this;
base.OnCreate(savedInstanceState);
Xamarin.Essentials.Platform.Init(this, savedInstanceState);
global::Xamarin.Forms.Forms.Init(this, savedInstanceState);
//Distribute.SetEnabledForDebuggableBuild(true);
LoadApplication(new App());
//enable Android webview debugging
global::Android.Webkit.WebView.SetWebContentsDebuggingEnabled(true);
}
public override void OnRequestPermissionsResult(int requestCode, string[] permissions, [GeneratedEnum] Android.Content.PM.Permission[] grantResults)
{
Xamarin.Essentials.Platform.OnRequestPermissionsResult(requestCode, permissions, grantResults);
base.OnRequestPermissionsResult(requestCode, permissions, grantResults);
}
protected override void OnActivityResult(int requestCode, Result resultCode, Intent data)
{
base.OnActivityResult(requestCode, resultCode, data);
if (requestCode == 101)
{
switch (resultCode)
{
case Result.Ok:
if (data != null && data.Data != null)
{
_fileName?.SetResult(data.Data);
}
else
_fileName?.SetResult(null);
break;
default:
_fileName?.SetResult(null);
break;
}
}
}
TaskCompletionSource<Android.Net.Uri> _fileName;
[System.Diagnostics.CodeAnalysis.SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", Justification = "<Pending>")]
public Task<Android.Net.Uri> OpenSaveFileDialog(string currentFileName)
{
Intent intent = new Intent(Intent.ActionCreateDocument);
intent.AddCategory(Intent.CategoryOpenable);
intent.SetType("application/octet-stream");
intent.PutExtra(Intent.ExtraTitle, currentFileName);
_fileName = new TaskCompletionSource<Android.Net.Uri>();
StartActivityForResult(intent, 101);
return _fileName.Task;
}
}
}

Просмотреть файл

@ -0,0 +1,40 @@
using Android.Graphics;
using Sketch360.XPlat.Data;
using SkiaSharp;
using System;
using System.Collections.Generic;
using Xamarin.Forms;
[assembly: Dependency(typeof(Sketch360.XPlat.Droid.OffscreenRenderer))]
namespace Sketch360.XPlat.Droid
{
public class OffscreenRenderer : IOffscreenRenderer
{
public void Render(float width, float height, IEnumerable<InkStroke> strokes)
{
using (var bitmap = Bitmap.CreateBitmap((int) width, (int) height, Bitmap.Config.Argb8888))
{
try
{
using (var surface = SKSurface.Create((int) width, (int) height, SKColorType.Rgba8888, SKAlphaType.Premul, bitmap.LockPixels(), (int) width * 4))
{
var skcanvas = surface.Canvas;
skcanvas.Draw(strokes);
}
}
catch(Exception e)
{
}
finally
{
bitmap.UnlockPixels();
}
//bitmap.
}
}
}
}

Просмотреть файл

@ -0,0 +1,315 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using Android;
using Android.Content;
using Android.Content.PM;
using Android.Media;
using Android.Provider;
using Android.Support.V4.App;
using Sketch360.XPlat.Interfaces;
using System;
using System.IO;
using System.Threading.Tasks;
using Xamarin.Essentials;
using Xamarin.Forms;
using System.Linq;
using Microsoft.AppCenter.Crashes;
[assembly: Dependency(typeof(Sketch360.XPlat.Droid.PhotoLibrary))]
namespace Sketch360.XPlat.Droid
{
/// <summary>
/// Android Photo library
/// </summary>
public class PhotoLibrary : IPhotoLibrary
{
#region Constants
/// <summary>
/// XMP Metadata with equirectangular Projection property
/// </summary>
private const string Xmp = "<x:xmpmeta xmlns:x=\"adobe:ns:meta/\"><rdf:RDF xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\"><rdf:Description xmlns:prefix0=\"http://ns.google.com/photos/1.0/panorama/\"><prefix0:ProjectionType>equirectangular</prefix0:ProjectionType></rdf:Description></rdf:RDF></x:xmpmeta>";
#endregion
#region Methods
/// <summary>
/// Save a photo to the photo library with equirectangular metadata.
/// </summary>
/// <param name="data"></param>
/// <param name="folder"></param>
/// <param name="filename"></param>
/// <returns></returns>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "<Pending>")]
public async Task<bool> SavePhotoAsync(byte[] data, string folder, string filename, Page page)
{
try
{
if (!(await RequestPermissionsAsync(page).ConfigureAwait(false)))
{
return false;
}
string tempFilename = await SaveToTempFileAsync(data, filename).ConfigureAwait(false);
AddExifData(tempFilename);
var uri = await WriteContentValuesAsync(tempFilename, filename, "image/jpeg").ConfigureAwait(false);
System.IO.File.Delete(tempFilename);
var newFilename = GetPath(uri);
await LaunchFileAsync(newFilename).ConfigureAwait(false);
}
catch (Exception e)
{
Crashes.TrackError(e);
return false;
}
return true;
}
private static async Task<bool> RequestPermissionsAsync(Page page)
{
Task<PermissionStatus>[] permissions = new[]
{
new Permissions.StorageWrite().RequestAsync(),
new Permissions.Photos().RequestAsync(),
};
var results = await Task.WhenAll(permissions).ConfigureAwait(false);
if(results.Any(x => x != PermissionStatus.Granted))
{
if (page != null)
{
await page.DisplayAlert(
Resources.AndroidResources.PermissionsTitle,
Resources.AndroidResources.PermssionsMessage,
Resources.AndroidResources.OK).ConfigureAwait(false);
}
return false;
}
return true;
}
static string GetPath(Android.Net.Uri uri)
{
string path = null;
#pragma warning disable CS0618 // Type or member is obsolete
String[] projection = { MediaStore.MediaColumns.Data };
#pragma warning restore CS0618 // Type or member is obsolete
var metaCursor = MainActivity.Instance.ContentResolver.Query(uri, projection, null, null, null);
if (metaCursor != null)
{
using (metaCursor)
{
if (metaCursor.MoveToFirst())
{
path = metaCursor.GetString(0);
}
}
}
return path;
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "<Pending>")]
public async Task<bool> SaveSketchAsync(byte[] data, string folder, string filename, Page page)
{
bool result;
try
{
if (!(await RequestPermissionsAsync(page).ConfigureAwait(false)))
{
return false;
}
var tempFilename = await SaveToTempFileAsync(data, filename).ConfigureAwait(false);
var urilocation = await WriteDownloadValuesAsync(tempFilename, filename, "application/x-sketch360").ConfigureAwait(false);
result = urilocation != null;
//var documentsDirectory = Android.OS.Environment.GetExternalStoragePublicDirectory(Android.OS.Environment.DirectoryDownloads);
//var fullPath = await WriteToFolderAsync(data, filename, documentsDirectory).ConfigureAwait(false);
//MediaScannerConnection.ScanFile(MainActivity.Instance,
// new string[] { fullPath },
// new string[] { "application/x-sketch360" }, null);
//var fullPath = await WriteToSpecialFolderAsync(tempFilename, filename, Environment.SpecialFolder.MyDocuments).ConfigureAwait(false);
System.IO.File.Delete(tempFilename);
//await LaunchFileAsync(fullPath).ConfigureAwait(false);
}
catch (Exception e)
{
Crashes.TrackError(e);
return false;
}
return result;
}
#endregion
#region Implementation
//private static async Task<string> WriteToSpecialFolderAsync(string tempFilename, string filename, Java.IO.File folder)
//{
// folder.Mkdirs();
// var filePath = Path.Combine(folder.AbsolutePath, filename);
// var data = System.IO.File.ReadAllBytes(tempFilename);
// using (var stream = new Java.IO.FileOutputStream(filePath))
// {
// await stream.WriteAsync(data).ConfigureAwait(false);
// }
// return filePath;
// //var folder = System.Environment.GetFolderPath(specialFolder);
// // System.IO.Directory.CreateDirectory(folder);
// // var myPicturesFilename = Path.Combine(folder, filename);
// // using (var stream = System.IO.File.Create(myPicturesFilename))
// // using (var tempStream = System.IO.File.OpenRead(tempFilename))
// // {
// // await tempStream.CopyToAsync(stream).ConfigureAwait(false);
// // }
// // return myPicturesFilename;
//}
//private static async Task<string> WriteToSpecialFolderAsync(string tempFilename, string filename, Environment.SpecialFolder specialFolder)
//{
// var folder = System.Environment.GetFolderPath(specialFolder);
// System.IO.Directory.CreateDirectory(folder);
// var myPicturesFilename = Path.Combine(folder, filename);
// using (var stream = System.IO.File.Create(myPicturesFilename))
// using (var tempStream = System.IO.File.OpenRead(tempFilename))
// {
// await tempStream.CopyToAsync(stream).ConfigureAwait(false);
// }
// return myPicturesFilename;
//}
//private static async Task<string> WriteToFolderAsync(byte[] data, string filename, Java.IO.File folder)
//{
// folder.Mkdirs();
// var filePath = Path.Combine(folder.AbsolutePath, filename);
// using (var stream = new Java.IO.FileOutputStream(filePath))
// {
// await stream.WriteAsync(data).ConfigureAwait(false);
// }
// return filePath;
//}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "<Pending>")]
private static async Task LaunchFileAsync(string filename)
{
//var fullPath = $"/storage/emulated/0/DCIM/Sketch360/{filename}";
var request = new OpenFileRequest
{
File = new ReadOnlyFile(filename)
};
try
{
await Launcher.OpenAsync(request).ConfigureAwait(false);
}
catch (Exception e)
{
Crashes.TrackError(e);
}
}
private static async Task<string> SaveToTempFileAsync(byte[] data, string filename)
{
var tempFilename = Path.Combine(System.Environment.GetFolderPath(System.Environment.SpecialFolder.LocalApplicationData), filename);
using (var stream = System.IO.File.Create(tempFilename))
{
await stream.WriteAsync(data).ConfigureAwait(false);
}
return tempFilename;
}
public void AddExifData(string filename)
{
using var exif = new ExifInterface(filename);
exif.SetAttribute(ExifInterface.TagXmp, Xmp);
exif.SetAttribute(ExifInterface.TagSoftware, "Sketch 360");
exif.SaveAttributes();
}
private static async Task<Android.Net.Uri> WriteDownloadValuesAsync(string tempfilename, string filename, string mimeType)
{
using var stream = System.IO.File.OpenRead(tempfilename);
using var contentValues = new ContentValues();
contentValues.Put(MediaStore.DownloadColumns.DisplayName, filename);
contentValues.Put(MediaStore.DownloadColumns.MimeType, mimeType);
var uri = await MainActivity.Instance.OpenSaveFileDialog(filename).ConfigureAwait(true);
//var uri = MainActivity.Instance.ContentResolver.Insert(MediaStore.Downloads.ExternalContentUri, contentValues);
if (uri == null) return null;
using (var outputStream = MainActivity.Instance.ContentResolver.OpenOutputStream(uri))
{
await stream.CopyToAsync(outputStream).ConfigureAwait(false);
}
return uri;
}
private static async Task<Android.Net.Uri> WriteContentValuesAsync(string tempfilename, string filename, string mimeType)
{
using var stream = System.IO.File.OpenRead(tempfilename);
using var contentValues = new ContentValues();
contentValues.Put(MediaStore.MediaColumns.DisplayName, filename);
contentValues.Put(MediaStore.MediaColumns.MimeType, mimeType);
var uri = MainActivity.Instance.ContentResolver.Insert(MediaStore.Images.Media.ExternalContentUri, contentValues);
if (uri == null) return null;
using (var outputStream = MainActivity.Instance.ContentResolver.OpenOutputStream(uri))
{
await stream.CopyToAsync(outputStream).ConfigureAwait(false);
}
return uri;
}
#endregion
}
}

Просмотреть файл

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionName="2.1" package="com.microsoft.garage.sketch360app" android:installLocation="auto" android:versionCode="2">
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="29" />
<application android:label="Sketch 360" android:icon="@mipmap/launcher_foreground" android:requestLegacyExternalStorage="true"></application>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.INTERNET" />
</manifest>

Просмотреть файл

@ -0,0 +1,36 @@
using Android.App;
using System.Reflection;
using System.Resources;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("Sketch 360")]
[assembly: AssemblyDescription("360 sketching")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("Microsoft Garage")]
[assembly: AssemblyProduct("Sketch360.XPlat.Android")]
[assembly: AssemblyCopyright("Copyright © 2020 Microsoft Corporation")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
[assembly: ComVisible(false)]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
// Add some common permissions, these can be removed if not needed
[assembly: UsesPermission(Android.Manifest.Permission.Internet)]
[assembly: UsesPermission(Android.Manifest.Permission.WriteExternalStorage)]
[assembly: UsesPermission(Android.Manifest.Permission.ReadExternalStorage)]
[assembly: NeutralResourcesLanguage("en-US")]

Просмотреть файл

@ -0,0 +1,174 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using Android.App;
using Android.Content;
using Android.Webkit;
using Android.Widget;
using Microsoft.ConnectedDevices;
using Sketch360.XPlat.Data;
using Sketch360.XPlat.Interfaces;
using System;
using System.Collections.ObjectModel;
using System.Linq;
using System.Threading.Tasks;
[assembly: Xamarin.Forms.Dependency(typeof(Sketch360.XPlat.Droid.Remote))]
namespace Sketch360.XPlat.Droid
{
/// <summary>
/// Remote system using Project Rome to send data to other systems
/// </summary>
public sealed class Remote : IRemote, IDisposable
{
const string AppId = "f2d1007c-dbd7-4d32-a488-93a6bdbc12b9";
private WebView _webView;
internal Dialog _authDialog;
private RemoteSystemWatcher _remoteSystemWatcher;
public Remote()
{
RemoteSystems = new ObservableCollection<RemoteSystemInfo>();
}
public static MainActivity MainActivity { get; set; }
public ObservableCollection<RemoteSystemInfo> RemoteSystems { get; private set; }
public async Task<bool> ConnectAsync()
{
Platform.FetchAuthCode += Platform_FetchAuthCode;
if (await Platform.InitializeAsync(MainActivity, AppId).ConfigureAwait(false))
{
DiscoverDevices();
return true;
}
return false;
}
public void DiscoverDevices()
{
_remoteSystemWatcher = RemoteSystem.CreateWatcher();
_remoteSystemWatcher.RemoteSystemAdded += RemoteSystemAdded;
_remoteSystemWatcher.RemoteSystemRemoved += RemoteSystemRemoved;
_remoteSystemWatcher.RemoteSystemUpdated += RemoteSystemUpdated;
_remoteSystemWatcher.Start();
}
private void RemoteSystemUpdated(RemoteSystemWatcher watcher, RemoteSystemUpdatedEventArgs args)
{
}
private void RemoteSystemRemoved(RemoteSystemWatcher watcher, RemoteSystemRemovedEventArgs args)
{
MainActivity.RunOnUiThread(delegate
{
var itemToRemove = (from item in RemoteSystems
where item.Id == args.P0
select item).FirstOrDefault();
if (itemToRemove != null)
{
RemoteSystems.Remove(itemToRemove);
}
});
}
private void RemoteSystemAdded(RemoteSystemWatcher watcher, RemoteSystemAddedEventArgs args)
{
MainActivity.RunOnUiThread(delegate
{
RemoteSystems.Add(new RemoteSystemInfo
{
DisplayName = args.P0.DisplayName,
Id = args.P0.Id,
Kind = args.P0.Kind.ToString()
});
});
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", Justification = "<Pending>")]
private void Platform_FetchAuthCode(string oauthUrl)
{
_authDialog = new Dialog(MainActivity);
var linearLayout = new LinearLayout(_authDialog.Context);
_webView = new WebView(_authDialog.Context);
linearLayout.AddView(_webView);
_authDialog.SetContentView(linearLayout);
_webView.SetWebChromeClient(new WebChromeClient());
_webView.Settings.JavaScriptEnabled = true;
_webView.Settings.DomStorageEnabled = true;
_webView.LoadUrl(oauthUrl);
_webView.SetWebViewClient(new MsaWebViewClient(MainActivity, _authDialog));
_authDialog.Show();
_authDialog.SetCancelable(true);
}
/// <summary>
/// Dispose of the auth dialog and webview
/// </summary>
public void Dispose()
{
if (_authDialog != null)
{
_authDialog.Dispose();
_authDialog = null;
}
if (_webView != null)
{
_webView.Dispose();
_webView = null;
}
}
internal class MsaWebViewClient : WebViewClient
{
bool authComplete;
private readonly MainActivity _parentActivity;
private readonly Dialog _authDialog;
public MsaWebViewClient(MainActivity activity, Dialog authDialog)
{
_parentActivity = activity;
_authDialog = authDialog;
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", Justification = "<Pending>")]
[System.Diagnostics.CodeAnalysis.SuppressMessage("Globalization", "CA1303:Do not pass literals as localized parameters", Justification = "<Pending>")]
public override void OnPageFinished(WebView view, string url)
{
base.OnPageFinished(view, url);
if (url.Contains("?code=", StringComparison.InvariantCultureIgnoreCase) && !authComplete)
{
authComplete = true;
var uri = Android.Net.Uri.Parse(url);
string token = uri.GetQueryParameter("code");
_authDialog.Dismiss();
Platform.SetAuthCode(token);
}
else if (url.Contains("error=access_denied", StringComparison.InvariantCultureIgnoreCase))
{
authComplete = true;
//Console.WriteLine("Page finished failed with ACCESS_DENIED_HERE");
Intent resultIntent = new Intent();
_parentActivity.SetResult(0, resultIntent);
_authDialog.Dismiss();
}
}
}
}
}

90
Source/Sketch360.XPlat.Android/Resources/AndroidResources.Designer.cs сгенерированный Normal file
Просмотреть файл

@ -0,0 +1,90 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace Sketch360.XPlat.Droid.Resources {
using System;
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
// This class was auto-generated by the StronglyTypedResourceBuilder
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
public class AndroidResources {
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal AndroidResources() {
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
public static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Sketch360.XPlat.Droid.Resources.AndroidResources", typeof(AndroidResources).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
public static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
/// <summary>
/// Looks up a localized string similar to OK.
/// </summary>
public static string OK {
get {
return ResourceManager.GetString("OK", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Sketch 360 Permissions.
/// </summary>
public static string PermissionsTitle {
get {
return ResourceManager.GetString("PermissionsTitle", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to In Settings/Apps &amp; notifications/Sketch 360/Permissions, select the Storage permission and check Allow to enable Sketch 360 to save files on this device, then try saving again..
/// </summary>
public static string PermssionsMessage {
get {
return ResourceManager.GetString("PermssionsMessage", resourceCulture);
}
}
}
}

Просмотреть файл

@ -0,0 +1,129 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="OK" xml:space="preserve">
<value>OK</value>
</data>
<data name="PermissionsTitle" xml:space="preserve">
<value>Sketch 360 Permissions</value>
</data>
<data name="PermssionsMessage" xml:space="preserve">
<value>In Settings/Apps &amp; notifications/Sketch 360/Permissions, select the Storage permission and check Allow to enable Sketch 360 to save files on this device, then try saving again.</value>
</data>
</root>

Двоичные данные
Source/Sketch360.XPlat.Android/Resources/drawable/BackButton.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 474 B

Двоичные данные
Source/Sketch360.XPlat.Android/Resources/drawable/DrawButton.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 690 B

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 1.0 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 1008 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 841 KiB

Двоичные данные
Source/Sketch360.XPlat.Android/Resources/drawable/EraserButton.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 808 B

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 546 B

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 586 B

Двоичные данные
Source/Sketch360.XPlat.Android/Resources/drawable/Logo.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 12 KiB

Двоичные данные
Source/Sketch360.XPlat.Android/Resources/drawable/MenuButton.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 226 B

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 993 B

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 741 B

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 907 B

Двоичные данные
Source/Sketch360.XPlat.Android/Resources/drawable/RedoButton.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 780 B

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 717 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 775 B

Двоичные данные
Source/Sketch360.XPlat.Android/Resources/drawable/TouchDrawing.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 910 B

Двоичные данные
Source/Sketch360.XPlat.Android/Resources/drawable/UndoButton.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 784 B

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 289 B

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 1.0 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 298 KiB

Просмотреть файл

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.TabLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/sliding_tabs"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/colorPrimary"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
app:tabIndicatorColor="@android:color/white"
app:tabGravity="fill"
app:tabMode="fixed" />

Просмотреть файл

@ -0,0 +1,9 @@
<android.support.v7.widget.Toolbar
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/colorPrimary"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
android:popupTheme="@style/ThemeOverlay.AppCompat.Light" />

Просмотреть файл

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/launcher_background" />
<foreground android:drawable="@mipmap/launcher_foreground" />
</adaptive-icon>

Просмотреть файл

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/launcher_background" />
<foreground android:drawable="@mipmap/launcher_foreground" />
</adaptive-icon>

Двоичные данные
Source/Sketch360.XPlat.Android/Resources/mipmap-hdpi/icon.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 2.9 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 8.1 KiB

Двоичные данные
Source/Sketch360.XPlat.Android/Resources/mipmap-mdpi/icon.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 1.8 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 5.0 KiB

Двоичные данные
Source/Sketch360.XPlat.Android/Resources/mipmap-xhdpi/icon.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 4.2 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 12 KiB

Двоичные данные
Source/Sketch360.XPlat.Android/Resources/mipmap-xxhdpi/icon.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 6.8 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 8.4 KiB

Двоичные данные
Source/Sketch360.XPlat.Android/Resources/mipmap-xxxhdpi/icon.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 10 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 13 KiB

Просмотреть файл

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="launcher_background">#FFFFFF</color>
<color name="colorPrimary">#3F51B5</color>
<color name="colorPrimaryDark">#303F9F</color>
<color name="colorAccent">#FF4081</color>
</resources>

Просмотреть файл

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8" ?>
<resources>
<style name="MainTheme" parent="MainTheme.Base">
</style>
<!-- Base theme applied no matter what API -->
<style name="MainTheme.Base" parent="Theme.AppCompat.Light.DarkActionBar">
<!--If you are using revision 22.1 please use just windowNoTitle. Without android:-->
<item name="windowNoTitle">true</item>
<!--We will be using the toolbar so no need to show ActionBar-->
<item name="windowActionBar">false</item>
<!-- Set theme colors from https://aka.ms/material-colors -->
<!-- colorPrimary is used for the default action bar background -->
<item name="colorPrimary">#2196F3</item>
<!-- colorPrimaryDark is used for the status bar -->
<item name="colorPrimaryDark">#1976D2</item>
<!-- colorAccent is used as the default value for colorControlActivated
which is used to tint widgets -->
<item name="colorAccent">#FF4081</item>
<!-- You can also set colorControlNormal, colorControlActivated
colorControlHighlight and colorSwitchThumbNormal. -->
<item name="windowActionModeOverlay">true</item>
<item name="android:datePickerDialogTheme">@style/AppCompatDialogStyle</item>
</style>
<style name="AppCompatDialogStyle" parent="Theme.AppCompat.Light.Dialog">
<item name="colorAccent">#FF4081</item>
</style>
</resources>

Просмотреть файл

@ -0,0 +1,271 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{319EA186-FA3E-49FB-9919-E48FBB2E35E6}</ProjectGuid>
<ProjectTypeGuids>{EFBA0AD7-5A72-4C68-AF49-83D382785DCF};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<TemplateGuid>{c9e5eea5-ca05-42a1-839b-61506e0a37df}</TemplateGuid>
<OutputType>Library</OutputType>
<RootNamespace>Sketch360.XPlat.Droid</RootNamespace>
<AssemblyName>Sketch360.XPlat.Android</AssemblyName>
<AndroidApplication>True</AndroidApplication>
<AndroidResgenFile>Resources\Resource.designer.cs</AndroidResgenFile>
<AndroidResgenClass>Resource</AndroidResgenClass>
<AndroidManifest>Properties\AndroidManifest.xml</AndroidManifest>
<MonoAndroidResourcePrefix>Resources</MonoAndroidResourcePrefix>
<MonoAndroidAssetsPrefix>Assets</MonoAndroidAssetsPrefix>
<AndroidUseLatestPlatformSdk>false</AndroidUseLatestPlatformSdk>
<TargetFrameworkVersion>v10.0</TargetFrameworkVersion>
<AndroidEnableSGenConcurrent>true</AndroidEnableSGenConcurrent>
<AndroidUseAapt2>true</AndroidUseAapt2>
<AndroidHttpClientHandlerType>Xamarin.Android.Net.AndroidClientHandler</AndroidHttpClientHandlerType>
<NuGetPackageImportStamp>
</NuGetPackageImportStamp>
<LangVersion>8.0</LangVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>portable</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug</OutputPath>
<DefineConstants>DEBUG;</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<AndroidLinkMode>None</AndroidLinkMode>
<AndroidSupportedAbis />
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>portable</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release</OutputPath>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<AndroidManagedSymbols>true</AndroidManagedSymbols>
<AndroidUseSharedRuntime>false</AndroidUseSharedRuntime>
<AndroidSupportedAbis>armeabi-v7a;x86_64;arm64-v8a</AndroidSupportedAbis>
<AndroidKeyStore>false</AndroidKeyStore>
</PropertyGroup>
<ItemGroup>
<Reference Include="Mono.Android" />
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Xml" />
<Reference Include="System.Numerics" />
<Reference Include="System.Numerics.Vectors" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AppCenter.Analytics">
<Version>3.4.3</Version>
</PackageReference>
<PackageReference Include="Microsoft.AppCenter.Crashes">
<Version>3.4.3</Version>
</PackageReference>
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers">
<Version>3.3.1</Version>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Microsoft.ConnectedDevices.Xamarin.Droid">
<Version>0.8.0</Version>
</PackageReference>
<PackageReference Include="SkiaSharp">
<Version>2.80.2</Version>
</PackageReference>
<PackageReference Include="Xamarin.Android.Support.Compat">
<Version>28.0.0.3</Version>
</PackageReference>
<PackageReference Include="Xamarin.DuoSdk">
<Version>0.0.3.4</Version>
</PackageReference>
<PackageReference Include="Xamarin.Forms" Version="4.8.0.1560" />
<PackageReference Include="Xamarin.Android.Support.Core.Utils" Version="28.0.0.3" />
<PackageReference Include="Xamarin.Essentials" Version="1.5.3-preview1304" />
<PackageReference Include="Xamarin.Forms.DualScreen">
<Version>4.8.0.1560</Version>
</PackageReference>
</ItemGroup>
<ItemGroup>
<Compile Include="BaseUrl.cs" />
<Compile Include="MainActivity.cs" />
<Compile Include="PhotoLibrary.cs" />
<Compile Include="Remote.cs" />
<Compile Include="Resources\Resource.designer.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="CustomWebViewRenderer.cs" />
<Compile Include="Resources\AndroidResources.Designer.cs">
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
<DependentUpon>AndroidResources.resx</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<None Include="Properties\AndroidManifest.xml" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\layout\Tabbar.xml" />
<AndroidResource Include="Resources\layout\Toolbar.xml" />
<AndroidResource Include="Resources\values\styles.xml" />
<AndroidResource Include="Resources\values\colors.xml" />
<AndroidResource Include="Resources\mipmap-anydpi-v26\icon.xml" />
<AndroidResource Include="Resources\mipmap-anydpi-v26\icon_round.xml" />
<AndroidResource Include="Resources\mipmap-hdpi\icon.png" />
<AndroidResource Include="Resources\mipmap-hdpi\launcher_foreground.png" />
<AndroidResource Include="Resources\mipmap-mdpi\icon.png" />
<AndroidResource Include="Resources\mipmap-mdpi\launcher_foreground.png" />
<AndroidResource Include="Resources\mipmap-xhdpi\icon.png" />
<AndroidResource Include="Resources\mipmap-xhdpi\launcher_foreground.png" />
<AndroidResource Include="Resources\mipmap-xxhdpi\icon.png" />
<AndroidResource Include="Resources\mipmap-xxhdpi\launcher_foreground.png" />
<AndroidResource Include="Resources\mipmap-xxxhdpi\icon.png" />
<AndroidResource Include="Resources\mipmap-xxxhdpi\launcher_foreground.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable\EraserButton.png">
<Generator>MSBuild:UpdateGeneratedFiles</Generator>
</AndroidResource>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Sketch360.XPlat\Sketch360.XPlat.csproj">
<Project>{DA1D6F34-3FD1-4842-8FBF-D73F820D04FC}</Project>
<Name>Sketch360.XPlat</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<AndroidAsset Include="..\Sketch360.XPlat.UWP\html\babylon.gui.js">
<Link>Assets\babylon.gui.js</Link>
</AndroidAsset>
<AndroidAsset Include="..\Sketch360.XPlat.UWP\html\spherical.css">
<Link>Assets\spherical.css</Link>
</AndroidAsset>
<AndroidAsset Include="..\Sketch360.XPlat.UWP\html\spherical.gui.js">
<Link>Assets\spherical.gui.js</Link>
</AndroidAsset>
<AndroidAsset Include="..\Sketch360.XPlat.UWP\html\spherical.html">
<Link>Assets\spherical.html</Link>
</AndroidAsset>
<AndroidAsset Include="..\Sketch360.XPlat.UWP\html\spherical.js">
<Link>Assets\spherical.js</Link>
</AndroidAsset>
<AndroidAsset Include="..\Sketch360.XPlat.UWP\html\TiltRotate.png">
<Link>Assets\TiltRotate.png</Link>
</AndroidAsset>
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable\EquirectangularGrid.png" />
<EmbeddedResource Include="Resources\AndroidResources.resx">
<Generator>PublicResXFileCodeGenerator</Generator>
<LastGenOutput>AndroidResources.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable\DrawButton.png">
<Generator>MSBuild:UpdateGeneratedFiles</Generator>
</AndroidResource>
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable\RedoButton.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable\UndoButton.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable\PaletteButton.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable\MenuButton.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable\DrawingViewButton.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable\View360Button.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable\PenSizeButton.png">
<Generator>MSBuild:UpdateGeneratedFiles</Generator>
</AndroidResource>
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable\PanZoomButton.png">
<Generator>MSBuild:UpdateGeneratedFiles</Generator>
</AndroidResource>
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable\FrontBackButton.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable\LeftRightButton.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable\VerticalLinesButton.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable\StencilButton.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable\logo.png" />
</ItemGroup>
<ItemGroup>
<AndroidAsset Include="..\Sketch360.XPlat.UWP\html\babylon.4.1.gui.js">
<Link>Assets\babylon.4.1.gui.js</Link>
</AndroidAsset>
<AndroidAsset Include="..\Sketch360.XPlat.UWP\html\babylon.4.1.js">
<Link>Assets\babylon.4.1.js</Link>
</AndroidAsset>
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable\DrawingViewCarousel.png" />
<AndroidResource Include="Resources\drawable\SphericalViewCarousel.png" />
<AndroidResource Include="Resources\drawable\export360Degree.png" />
<AndroidResource Include="Resources\drawable\TouchDrawing.png">
<Generator>MSBuild:UpdateGeneratedFiles</Generator>
</AndroidResource>
</ItemGroup>
<ItemGroup>
<AndroidAsset Include="..\about.html">
<Link>Assets\about.html</Link>
</AndroidAsset>
</ItemGroup>
<ItemGroup>
<AndroidAsset Include="..\EquirectangularGrid.png">
<Link>Assets\EquirectangularGrid.png</Link>
</AndroidAsset>
</ItemGroup>
<ItemGroup>
<AndroidAsset Include="..\garage.png">
<Link>Assets\garage.png</Link>
</AndroidAsset>
</ItemGroup>
<ItemGroup>
<AndroidAsset Include="..\ui.png">
<Link>Assets\ui.png</Link>
</AndroidAsset>
</ItemGroup>
<ItemGroup>
<AndroidAsset Include="..\about.css">
<Link>Assets\about.css</Link>
</AndroidAsset>
</ItemGroup>
<ItemGroup>
<AndroidAsset Include="..\about.js">
<Link>Assets\about.js</Link>
</AndroidAsset>
</ItemGroup>
<ItemGroup>
<AndroidAsset Include="..\grid.jpg">
<Link>Assets\grid.jpg</Link>
</AndroidAsset>
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable\BackButton.png" />
</ItemGroup>
<Import Project="$(MSBuildExtensionsPath)\Xamarin\Android\Xamarin.Android.CSharp.targets" />
<ProjectExtensions>
<VisualStudio>
<UserProperties XamarinHotReloadDebuggerTimeoutExceptionSketch360XPlatAndroidHideInfoBar="True" />
</VisualStudio>
</ProjectExtensions>
</Project>

Просмотреть файл

@ -0,0 +1,72 @@
using Android.Content;
using Android.Widget;
using Sketch360.XPlat;
using System;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
[assembly: ExportRenderer(
typeof(ZoomableScrollView),
typeof(Sketch360.XPlat.Droid.ZoomableScrollViewerRenderer))]
namespace Sketch360.XPlat.Droid
{
/// <summary>
/// ZoomableScrollView UWP Renderer
/// </summary>
public class ZoomableScrollViewerRenderer : ScrollViewRenderer
{
public ZoomableScrollViewerRenderer(Context context) :
base(context)
{
}
#region Methods
/// <summary>
/// On element changed
/// </summary>
/// <param name="e">the element changed event arguments</param>
protected override void OnElementChanged(VisualElementChangedEventArgs e)
{
if (e == null) throw new ArgumentNullException(nameof(e));
base.OnElementChanged(e);
if (e.OldElement != null)
{
e.OldElement.PropertyChanged -= NewElement_PropertyChanged;
}
if (e.NewElement != null)
{
if (e.NewElement is IZoomableScrollView zoomable)
{
e.NewElement.PropertyChanged += NewElement_PropertyChanged;
UpdateZoomMode(zoomable);
}
}
}
#endregion
#region Implementation
private void NewElement_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(IZoomableScrollView.IsZoomEnabled))
{
UpdateZoomMode(sender as IZoomableScrollView);
}
}
private void UpdateZoomMode(IZoomableScrollView zoomableScrollView)
{
if (zoomableScrollView.IsZoomEnabled)
{
}
else
{
}
}
#endregion
}
}

Просмотреть файл

@ -0,0 +1 @@
MONO_LOG_LEVEL=error

Просмотреть файл

@ -0,0 +1,8 @@
<Application
x:Class="Sketch360.XPlat.UWP.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:Sketch360.XPlat.UWP"
RequestedTheme="Light">
</Application>

Просмотреть файл

@ -0,0 +1,90 @@
using System;
using Windows.ApplicationModel;
using Windows.ApplicationModel.Activation;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Navigation;
namespace Sketch360.XPlat.UWP
{
/// <summary>
/// Provides application-specific behavior to supplement the default Application class.
/// </summary>
sealed partial class App : Application
{
/// <summary>
/// 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().
/// </summary>
public App()
{
this.InitializeComponent();
this.Suspending += OnSuspending;
}
/// <summary>
/// Invoked when the application is launched normally by the end user. Other entry points
/// will be used such as when the application is launched to open a specific file.
/// </summary>
/// <param name="e">Details about the launch request and process.</param>
protected override void OnLaunched(LaunchActivatedEventArgs e)
{
if (e == null) throw new ArgumentNullException(nameof(e));
// Do not repeat app initialization when the Window already has content,
// just ensure that the window is active
if (!(Window.Current.Content is Frame rootFrame))
{
// Create a Frame to act as the navigation context and navigate to the first page
rootFrame = new Frame();
rootFrame.NavigationFailed += OnNavigationFailed;
Xamarin.Forms.Forms.Init(e);
if (e.PreviousExecutionState == ApplicationExecutionState.Terminated)
{
//TODO: Load state from previously suspended application
}
// Place the frame in the current Window
Window.Current.Content = rootFrame;
}
if (rootFrame.Content == null)
{
// When the navigation stack isn't restored navigate to the first page,
// configuring the new page by passing required information as a navigation
// parameter
rootFrame.Navigate(typeof(MainPage), e.Arguments);
}
// Ensure the current window is active
Window.Current.Activate();
}
/// <summary>
/// Invoked when Navigation to a certain page fails
/// </summary>
/// <param name="sender">The Frame which failed navigation</param>
/// <param name="e">Details about the navigation failure</param>
void OnNavigationFailed(object sender, NavigationFailedEventArgs e)
{
throw new Exception("Failed to load Page " + e.SourcePageType.FullName);
}
/// <summary>
/// Invoked when application execution is being suspended. Application state is saved
/// without knowing whether the application will be terminated or resumed with the contents
/// of memory still intact.
/// </summary>
/// <param name="sender">The source of the suspend request.</param>
/// <param name="e">Details about the suspend request.</param>
private void OnSuspending(object sender, SuspendingEventArgs e)
{
var deferral = e.SuspendingOperation.GetDeferral();
//TODO: Save application state and stop any background activity
deferral.Complete();
}
}
}

Двоичные данные
Source/Sketch360.XPlat.UWP/Assets/LargeTile.scale-100.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 6.0 KiB

Двоичные данные
Source/Sketch360.XPlat.UWP/Assets/LargeTile.scale-200.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 14 KiB

Двоичные данные
Source/Sketch360.XPlat.UWP/Assets/LargeTile.scale-400.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 31 KiB

Двоичные данные
Source/Sketch360.XPlat.UWP/Assets/SmallTile.scale-100.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 1.2 KiB

Двоичные данные
Source/Sketch360.XPlat.UWP/Assets/SmallTile.scale-200.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 2.5 KiB

Двоичные данные
Source/Sketch360.XPlat.UWP/Assets/SmallTile.scale-400.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 5.4 KiB

Двоичные данные
Source/Sketch360.XPlat.UWP/Assets/SplashScreen.scale-100.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 6.4 KiB

Двоичные данные
Source/Sketch360.XPlat.UWP/Assets/SplashScreen.scale-200.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 15 KiB

Двоичные данные
Source/Sketch360.XPlat.UWP/Assets/SplashScreen.scale-400.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 39 KiB

Двоичные данные
Source/Sketch360.XPlat.UWP/Assets/Square150x150Logo.scale-100.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 2.7 KiB

Двоичные данные
Source/Sketch360.XPlat.UWP/Assets/Square150x150Logo.scale-200.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 5.8 KiB

Двоичные данные
Source/Sketch360.XPlat.UWP/Assets/Square150x150Logo.scale-400.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 13 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 394 B

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 9.5 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 1.2 KiB

Двоичные данные
Source/Sketch360.XPlat.UWP/Assets/Square44x44Logo.scale-100.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 1.1 KiB

Двоичные данные
Source/Sketch360.XPlat.UWP/Assets/Square44x44Logo.scale-200.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 2.4 KiB

Двоичные данные
Source/Sketch360.XPlat.UWP/Assets/Square44x44Logo.scale-400.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 4.6 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 394 B

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 9.5 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 1.2 KiB

Двоичные данные
Source/Sketch360.XPlat.UWP/Assets/StoreLogo.backup.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 392 B

Двоичные данные
Source/Sketch360.XPlat.UWP/Assets/StoreLogo.scale-100.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 836 B

Двоичные данные
Source/Sketch360.XPlat.UWP/Assets/StoreLogo.scale-200.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 1.7 KiB

Двоичные данные
Source/Sketch360.XPlat.UWP/Assets/StoreLogo.scale-400.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 3.6 KiB

Двоичные данные
Source/Sketch360.XPlat.UWP/Assets/Wide310x150Logo.scale-100.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 2.9 KiB

Двоичные данные
Source/Sketch360.XPlat.UWP/Assets/Wide310x150Logo.scale-200.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 6.4 KiB

Двоичные данные
Source/Sketch360.XPlat.UWP/Assets/Wide310x150Logo.scale-400.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 15 KiB

Просмотреть файл

@ -0,0 +1,32 @@
using System.IO;
using Xamarin.Forms;
[assembly: Dependency(typeof(Sketch360.XPlat.UWP.BaseUrl))]
namespace Sketch360.XPlat.UWP
{
/// <summary>
/// UWP Base URL
/// </summary>
public class BaseUrl : IBaseUrl
{
/// <summary>
/// Gets an appx package url scheme
/// </summary>
/// <returns>an appx url scheme</returns>
public string GetBase()
{
return "ms-appx-web:///html/";
}
/// <summary>
/// Gets the drawable image stream for UWP.
/// </summary>
/// <param name="filename">the filename</param>
/// <returns>a image stream</returns>
public Stream GetDrawableImageStream(string filename)
{
throw new System.NotImplementedException();
}
}
}

Двоичные данные
Source/Sketch360.XPlat.UWP/Equirectangular Grid.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 841 KiB

Просмотреть файл

@ -0,0 +1,71 @@
using System;
using Xamarin.Forms.Inking.Views;
using Xamarin.Forms.Platform.UWP;
[assembly: ExportRenderer(
typeof(InkCanvasView),
typeof(Sketch360.XPlat.UWP.InkCanvasViewRenderer))]
namespace Sketch360.XPlat.UWP
{
/// <summary>
/// UWP InkCanvasView renderer using the native UWP InkCanvas
/// </summary>
public class InkCanvasViewRenderer : ViewRenderer<InkCanvasView, ScrollingInkCanvas>
{
#region Methods
/// <summary>
/// Create the UWP InkCanvas
/// </summary>
/// <param name="e">the element changed event arguments</param>
protected override void OnElementChanged(ElementChangedEventArgs<InkCanvasView> e)
{
if (e == null) throw new ArgumentNullException(nameof(e));
base.OnElementChanged(e);
if (e.OldElement != null)
{
Control.InkCanvas.InkPresenter.StrokesCollected -= InkPresenter_StrokesCollected;
Control.InkCanvas.InkPresenter.StrokesErased -= InkPresenter_StrokesErased;
}
if (e.NewElement != null)
{
if (Control == null)
{
var scrollingInkCanvas = new ScrollingInkCanvas();
scrollingInkCanvas.InkCanvas.InkPresenter.InputDeviceTypes = Windows.UI.Core.CoreInputDeviceTypes.Pen | Windows.UI.Core.CoreInputDeviceTypes.Touch | Windows.UI.Core.CoreInputDeviceTypes.Mouse;
scrollingInkCanvas.InkCanvas.InkPresenter.StrokesCollected += InkPresenter_StrokesCollected;
scrollingInkCanvas.InkCanvas.InkPresenter.StrokesErased += InkPresenter_StrokesErased;
SetNativeControl(scrollingInkCanvas);
if (e.NewElement.InkPresenter is UWPInkPresenter uwp)
{
uwp.InkCanvas = scrollingInkCanvas.InkCanvas;
}
}
}
}
#endregion
#region Implementation
private void InkPresenter_StrokesErased(Windows.UI.Input.Inking.InkPresenter sender, Windows.UI.Input.Inking.InkStrokesErasedEventArgs args)
{
var strokes = args.Strokes.ToXInkStrokeList();
Element.InkPresenter.TriggerStrokesErased(strokes);
}
private void InkPresenter_StrokesCollected(Windows.UI.Input.Inking.InkPresenter sender, Windows.UI.Input.Inking.InkStrokesCollectedEventArgs args)
{
var strokes = args.Strokes.ToXInkStrokeList();
Element.InkPresenter.TriggerStrokesCollected(strokes);
}
#endregion
}
}

Просмотреть файл

@ -0,0 +1,15 @@
<forms:WindowsPage
x:Class="Sketch360.XPlat.UWP.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:forms="using:Xamarin.Forms.Platform.UWP"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:Sketch360.XPlat.UWP"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
</Grid>
</forms:WindowsPage>

Просмотреть файл

@ -0,0 +1,21 @@
namespace Sketch360.XPlat.UWP
{
/// <summary>
/// Xamarin Forms MainPage class
/// </summary>
public sealed partial class MainPage
{
/// <summary>
/// Initializes a new instance of the MainPage class.
/// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", Justification = "<Pending>")]
public MainPage()
{
this.InitializeComponent();
var app = new Sketch360.XPlat.App();
LoadApplication(app);
}
}
}

Просмотреть файл

@ -0,0 +1,55 @@
<?xml version="1.0" encoding="utf-8"?>
<Package
xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
xmlns:mp="http://schemas.microsoft.com/appx/2014/phone/manifest"
xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
IgnorableNamespaces="uap mp">
<Identity
Name="75c5f008-7954-47cd-8922-030290a51106"
Publisher="CN=110fce08-04b0-4755-a169-63d8e7b7b04b"
Version="1.0.0.0" />
<mp:PhoneIdentity PhoneProductId="ec0cc741-fd3e-485c-81be-68815c480690" PhonePublisherId="00000000-0000-0000-0000-000000000000"/>
<Properties>
<DisplayName>Sketch360.XPlat.UWP</DisplayName>
<PublisherDisplayName>110fce08-04b0-4755-a169-63d8e7b7b04b</PublisherDisplayName>
<Logo>Assets\StoreLogo.png</Logo>
</Properties>
<Dependencies>
<TargetDeviceFamily Name="Windows.Universal" MinVersion="10.0.0.0" MaxVersionTested="10.0.0.0" />
</Dependencies>
<Resources>
<Resource Language="x-generate"/>
</Resources>
<Applications>
<Application Id="App"
Executable="$targetnametoken$.exe"
EntryPoint="Sketch360.XPlat.UWP.App">
<uap:VisualElements
DisplayName="Sketch 360"
Square150x150Logo="Assets\Square150x150Logo.png"
Square44x44Logo="Assets\Square44x44Logo.png"
Description="360 Sketching app"
BackgroundColor="transparent">
<uap:DefaultTile Wide310x150Logo="Assets\Wide310x150Logo.png" Square310x310Logo="Assets\LargeTile.png" Square71x71Logo="Assets\SmallTile.png">
<uap:ShowNameOnTiles>
<uap:ShowOn Tile="square150x150Logo" />
<uap:ShowOn Tile="wide310x150Logo" />
<uap:ShowOn Tile="square310x310Logo" />
</uap:ShowNameOnTiles>
</uap:DefaultTile>
<uap:SplashScreen Image="Assets\SplashScreen.png" />
</uap:VisualElements>
</Application>
</Applications>
<Capabilities>
<Capability Name="internetClient" />
</Capabilities>
</Package>

Просмотреть файл

@ -0,0 +1,31 @@
using System.Resources;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("Sketch 360")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("Microsoft Garage")]
[assembly: AssemblyProduct("Sketch360.XPlat.UWP")]
[assembly: AssemblyCopyright("Copyright © Microsoft Copr. 2020")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: ComVisible(false)]
[assembly: NeutralResourcesLanguage("en-US")]

Просмотреть файл

@ -0,0 +1,31 @@
<!--
This file contains Runtime Directives used by .NET Native. The defaults here are suitable for most
developers. However, you can modify these parameters to modify the behavior of the .NET Native
optimizer.
Runtime Directives are documented at https://go.microsoft.com/fwlink/?LinkID=391919
To fully enable reflection for App1.MyClass and all of its public/private members
<Type Name="App1.MyClass" Dynamic="Required All"/>
To enable dynamic creation of the specific instantiation of AppClass<T> over System.Int32
<TypeInstantiation Name="App1.AppClass" Arguments="System.Int32" Activate="Required Public" />
Using the Namespace directive to apply reflection policy to all the types in a particular namespace
<Namespace Name="DataClasses.ViewModels" Seralize="All" />
-->
<Directives xmlns="http://schemas.microsoft.com/netfx/2013/01/metadata">
<Application>
<!--
An Assembly element with Name="*Application*" applies to all assemblies in
the application package. The asterisks are not wildcards.
-->
<Assembly Name="*Application*" Dynamic="Required All" />
<!-- Add your application specific runtime directives here. -->
</Application>
</Directives>

Просмотреть файл

@ -0,0 +1,33 @@
<UserControl
x:Class="Sketch360.XPlat.UWP.ScrollingInkCanvas"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:Sketch360.XPlat.UWP"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="300"
d:DesignWidth="400">
<UserControl.Resources>
<Style TargetType="ScrollViewer" x:Key="InkScrollViewerStyle">
<Setter Property="ZoomMode" Value="Enabled"/>
<Setter Property="HorizontalScrollMode" Value="Enabled"/>
<Setter Property="VerticalScrollMode" Value="Enabled"/>
<Setter Property="HorizontalScrollBarVisibility" Value="Auto"/>
<Setter Property="VerticalScrollBarVisibility" Value="Auto"/>
</Style>
<Style TargetType="InkCanvas" x:Key="InkCanvasStyle">
<Setter Property="Width" Value="2000"/>
<Setter Property="Height" Value="1000"/>
</Style>
</UserControl.Resources>
<Grid>
<ScrollViewer Style="{StaticResource InkScrollViewerStyle}">
<Grid>
<InkCanvas x:Name="InkCanvasControl" Style="{StaticResource InkCanvasStyle}"/>
</Grid>
</ScrollViewer>
</Grid>
</UserControl>

Просмотреть файл

@ -0,0 +1,23 @@
using Windows.UI.Xaml.Controls;
namespace Sketch360.XPlat.UWP
{
/// <summary>
/// Scrolling ink canvas
/// </summary>
public sealed partial class ScrollingInkCanvas : UserControl
{
/// <summary>
/// Initializes a new instance of the ScrollingInkCanvas class.
/// </summary>
public ScrollingInkCanvas()
{
this.InitializeComponent();
}
/// <summary>
/// Gets the ink canvas
/// </summary>
public InkCanvas InkCanvas => InkCanvasControl;
}
}

Просмотреть файл

@ -0,0 +1,28 @@
using System;
using System.ComponentModel;
using Xamarin.Duo.Forms.Samples;
using Xamarin.Forms;
[assembly: Dependency(typeof(Sketch360.XPlat.UWP.Services.HingeService))]
namespace Sketch360.XPlat.UWP.Services
{
public sealed class HingeService : IHingeService
{
public bool IsSpanned => false;
public bool IsLandscape => true;
public event EventHandler<HingeEventArgs> OnHingeUpdated;
public event PropertyChangedEventHandler PropertyChanged;
public void Dispose()
{
}
public Rectangle GetHinge()
{
return Rectangle.Zero;
}
}
}

Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше