Upgrade packages, refactor solution structure (#26)

* Upgrade most packages to latest versions
* Move solution files to /src
* Use .NET Core 2.2 SDK (MSBuild 16 included)
* Use separate sln file for each framework
This commit is contained in:
Artyom 2019-07-28 16:02:55 +03:00 коммит произвёл GitHub
Родитель d828c70a56
Коммит 67464995b8
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
178 изменённых файлов: 824 добавлений и 419 удалений

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

@ -1,14 +0,0 @@
using Avalonia;
using Avalonia.Markup.Xaml;
namespace Camelotia.Presentation.Avalonia
{
public class App : Application
{
public override void Initialize()
{
AvaloniaXamlLoader.Load(this);
base.Initialize();
}
}
}

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

@ -1,4 +1,4 @@
<p align="left"><img src="assets/horizontal.png" alt="Camelotia" height="80px"></p>
<p align="left"><img src="images/horizontal.png" alt="Camelotia" height="80px"></p>
[![Build Status](https://worldbeater.visualstudio.com/Camelotia/_apis/build/status/Camelotia-CI)](https://worldbeater.visualstudio.com/Camelotia/_build/latest?definitionId=1) [![Pull Requests](https://img.shields.io/github/issues-pr/worldbeater/camelotia.svg)](https://github.com/worldbeater/Camelotia/pulls) [![Issues](https://img.shields.io/github/issues/worldbeater/camelotia.svg)](https://github.com/worldbeater/Camelotia/issues) ![License](https://img.shields.io/github/license/worldbeater/camelotia.svg) ![Size](https://img.shields.io/github/repo-size/worldbeater/camelotia.svg) [![Code Coverage](https://img.shields.io/azure-devops/coverage/worldbeater/Camelotia/1.svg)](https://worldbeater.visualstudio.com/Camelotia/_build/latest?definitionId=1)
@ -8,12 +8,12 @@ File manager for cloud storages. Supports Yandex Disk, Google Drive, VK Document
You can compile .NET Standard libraries, run tests and run an Avalonia application on Windows, Linux and macOS operating systems. Make sure you have latest [.NET Core SDK](https://dot.net/) installed.
<img src="assets/UiAvalonia.png" width="550">
<img src="images/UiAvalonia.png" width="550">
```sh
# Linux or MacOS shell
git clone https://github.com/worldbeater/Camelotia
cd Camelotia/Camelotia.Presentation.Avalonia
cd src/Camelotia/Camelotia.Presentation.Avalonia
dotnet run
```
@ -23,7 +23,7 @@ On Windows, double-click the `./run.bat` file.
You can compile Universal Windows Platform Camelotia app only on latest Windows 10. Make sure you have latest [Microsoft Visual Studio](https://visualstudio.microsoft.com/) installed. Make sure the "Universal Application Development" section is checked in [Visual Studio Installer](https://visualstudio.microsoft.com/ru/vs/).
<img src="assets/UiWindows1.png" width="600">
<img src="images/UiWindows1.png" width="600">
Supports light and dark themes!
@ -31,11 +31,11 @@ Supports light and dark themes!
To compile the Xamarin Forms Android application, you need to install appropriate Android SDK v8.1. This can be achieved by using [Visual Studio Installer](https://visualstudio.microsoft.com/ru/vs/) and selecting "Mobile Development" section there.
<img src="assets/UiAndroid1.png" width="220"> <img src="assets/UiAndroid2.png" width="220"> <img src="assets/UiAndroid3.png" width="220">
<img src="images/UiAndroid1.png" width="220"> <img src="images/UiAndroid2.png" width="220"> <img src="images/UiAndroid3.png" width="220">
### Adding Custom Providers
File system providers are located at `./Camelotia.Services/Providers/`. To add a custom file system provider, you need to create a separate class and implement the [IProvider](https://github.com/worldbeater/Camelotia/blob/master/Camelotia.Services/Interfaces/IProvider.cs) interface. Then, add your provider to composition root for each platform that should support it.
File system providers are located at `./src/Camelotia.Services/Providers/`. To add a custom file system provider, you need to create a separate class and implement the [IProvider](https://github.com/worldbeater/Camelotia/blob/master/Camelotia.Services/Interfaces/IProvider.cs) interface. Then, add your provider to composition root for each platform that should support it.
### Technologies and Tools Used

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

@ -5,9 +5,12 @@ jobs:
variables:
buildConfiguration: 'Release'
steps:
- script: dotnet build --configuration $(buildConfiguration) $(Build.SourcesDirectory)/Camelotia.Presentation.Avalonia/Camelotia.Presentation.Avalonia.csproj
- task: DotNetCoreInstaller@0
inputs:
version: '2.2.203'
- script: dotnet build --configuration $(buildConfiguration) $(Build.SourcesDirectory)/src/Camelotia.Presentation.Avalonia/Camelotia.Presentation.Avalonia.csproj
displayName: 'Linux Build'
- script: dotnet test --logger trx Camelotia.Presentation.Tests
- script: dotnet test --logger trx src/Camelotia.Presentation.Tests
displayName: 'Linux Unit Tests'
- task: PublishTestResults@2
inputs:
@ -16,16 +19,19 @@ jobs:
- job: Windows
pool:
vmImage: 'vs2017-win2016'
vmImage: 'windows-2019'
variables:
buildConfiguration: 'Release'
steps:
- script: dotnet build --configuration $(buildConfiguration) $(Build.SourcesDirectory)/Camelotia.Presentation.Avalonia/Camelotia.Presentation.Avalonia.csproj
- task: DotNetCoreInstaller@0
inputs:
version: '2.2.203'
- script: dotnet build --configuration $(buildConfiguration) $(Build.SourcesDirectory)/src/Camelotia.Presentation.Avalonia/Camelotia.Presentation.Avalonia.csproj
displayName: 'Windows Build'
- script: |
mkdir $(Build.SourcesDirectory)\results
dotnet test --logger trx /p:CollectCoverage=true /p:CoverletOutputFormat=cobertura /p:Exclude=[xunit.*]* Camelotia.Presentation.Tests
copy $(Build.SourcesDirectory)\Camelotia.Presentation.Tests\coverage.cobertura.xml $(Build.SourcesDirectory)\results
dotnet test --logger trx /p:CollectCoverage=true /p:CoverletOutputFormat=cobertura /p:Exclude=[xunit.*]* src/Camelotia.Presentation.Tests
copy $(Build.SourcesDirectory)\src\Camelotia.Presentation.Tests\coverage.cobertura.xml $(Build.SourcesDirectory)\results
dotnet tool install dotnet-reportgenerator-globaltool --tool-path . --version 4.0.0-rc4
reportgenerator "-reports:$(Build.SourcesDirectory)\results\coverage.cobertura.xml" "-targetdir:results" "-reporttypes:HTMLInline;HTMLChart"
displayName: 'Windows Unit Tests'

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

До

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

После

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

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

До

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

После

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

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

До

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

После

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

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

До

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

После

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

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

До

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

После

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

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

До

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

После

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

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

До

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

После

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

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

До

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

После

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

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

@ -1,6 +1,6 @@
@echo off
echo Starting app...
cd Camelotia.Presentation.Avalonia
cd src\Camelotia.Presentation.Avalonia
dotnet clean
dotnet restore
dotnet build

2
run.sh
Просмотреть файл

@ -1,3 +1,3 @@
#!/bin/bash
cd Camelotia.Presentation.Avalonia
cd src/Camelotia.Presentation.Avalonia
dotnet run

115
src/Camelotia.Avalonia.sln Normal file
Просмотреть файл

@ -0,0 +1,115 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.28307.168
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Camelotia.Services", "Camelotia.Services\Camelotia.Services.csproj", "{283B8F56-AB9F-4556-AB93-AAC54171841C}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Camelotia.Presentation", "Camelotia.Presentation\Camelotia.Presentation.csproj", "{777FBFA6-ECFA-42F5-AC5F-EFC4B2D4AE6B}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Camelotia.Presentation.Avalonia", "Camelotia.Presentation.Avalonia\Camelotia.Presentation.Avalonia.csproj", "{557CB17D-8989-48E5-82FD-3C2106EE4BAE}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Camelotia.Presentation.Tests", "Camelotia.Presentation.Tests\Camelotia.Presentation.Tests.csproj", "{AE41666B-41D1-4044-9752-7CD4460C1A90}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Debug|ARM = Debug|ARM
Debug|ARM64 = Debug|ARM64
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
Release|Any CPU = Release|Any CPU
Release|ARM = Release|ARM
Release|ARM64 = Release|ARM64
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{283B8F56-AB9F-4556-AB93-AAC54171841C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{283B8F56-AB9F-4556-AB93-AAC54171841C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{283B8F56-AB9F-4556-AB93-AAC54171841C}.Debug|ARM.ActiveCfg = Debug|Any CPU
{283B8F56-AB9F-4556-AB93-AAC54171841C}.Debug|ARM.Build.0 = Debug|Any CPU
{283B8F56-AB9F-4556-AB93-AAC54171841C}.Debug|ARM64.ActiveCfg = Debug|Any CPU
{283B8F56-AB9F-4556-AB93-AAC54171841C}.Debug|ARM64.Build.0 = Debug|Any CPU
{283B8F56-AB9F-4556-AB93-AAC54171841C}.Debug|x64.ActiveCfg = Debug|Any CPU
{283B8F56-AB9F-4556-AB93-AAC54171841C}.Debug|x64.Build.0 = Debug|Any CPU
{283B8F56-AB9F-4556-AB93-AAC54171841C}.Debug|x86.ActiveCfg = Debug|Any CPU
{283B8F56-AB9F-4556-AB93-AAC54171841C}.Debug|x86.Build.0 = Debug|Any CPU
{283B8F56-AB9F-4556-AB93-AAC54171841C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{283B8F56-AB9F-4556-AB93-AAC54171841C}.Release|Any CPU.Build.0 = Release|Any CPU
{283B8F56-AB9F-4556-AB93-AAC54171841C}.Release|ARM.ActiveCfg = Release|Any CPU
{283B8F56-AB9F-4556-AB93-AAC54171841C}.Release|ARM.Build.0 = Release|Any CPU
{283B8F56-AB9F-4556-AB93-AAC54171841C}.Release|ARM64.ActiveCfg = Release|Any CPU
{283B8F56-AB9F-4556-AB93-AAC54171841C}.Release|ARM64.Build.0 = Release|Any CPU
{283B8F56-AB9F-4556-AB93-AAC54171841C}.Release|x64.ActiveCfg = Release|Any CPU
{283B8F56-AB9F-4556-AB93-AAC54171841C}.Release|x64.Build.0 = Release|Any CPU
{283B8F56-AB9F-4556-AB93-AAC54171841C}.Release|x86.ActiveCfg = Release|Any CPU
{283B8F56-AB9F-4556-AB93-AAC54171841C}.Release|x86.Build.0 = Release|Any CPU
{777FBFA6-ECFA-42F5-AC5F-EFC4B2D4AE6B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{777FBFA6-ECFA-42F5-AC5F-EFC4B2D4AE6B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{777FBFA6-ECFA-42F5-AC5F-EFC4B2D4AE6B}.Debug|ARM.ActiveCfg = Debug|Any CPU
{777FBFA6-ECFA-42F5-AC5F-EFC4B2D4AE6B}.Debug|ARM.Build.0 = Debug|Any CPU
{777FBFA6-ECFA-42F5-AC5F-EFC4B2D4AE6B}.Debug|ARM64.ActiveCfg = Debug|Any CPU
{777FBFA6-ECFA-42F5-AC5F-EFC4B2D4AE6B}.Debug|ARM64.Build.0 = Debug|Any CPU
{777FBFA6-ECFA-42F5-AC5F-EFC4B2D4AE6B}.Debug|x64.ActiveCfg = Debug|Any CPU
{777FBFA6-ECFA-42F5-AC5F-EFC4B2D4AE6B}.Debug|x64.Build.0 = Debug|Any CPU
{777FBFA6-ECFA-42F5-AC5F-EFC4B2D4AE6B}.Debug|x86.ActiveCfg = Debug|Any CPU
{777FBFA6-ECFA-42F5-AC5F-EFC4B2D4AE6B}.Debug|x86.Build.0 = Debug|Any CPU
{777FBFA6-ECFA-42F5-AC5F-EFC4B2D4AE6B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{777FBFA6-ECFA-42F5-AC5F-EFC4B2D4AE6B}.Release|Any CPU.Build.0 = Release|Any CPU
{777FBFA6-ECFA-42F5-AC5F-EFC4B2D4AE6B}.Release|ARM.ActiveCfg = Release|Any CPU
{777FBFA6-ECFA-42F5-AC5F-EFC4B2D4AE6B}.Release|ARM.Build.0 = Release|Any CPU
{777FBFA6-ECFA-42F5-AC5F-EFC4B2D4AE6B}.Release|ARM64.ActiveCfg = Release|Any CPU
{777FBFA6-ECFA-42F5-AC5F-EFC4B2D4AE6B}.Release|ARM64.Build.0 = Release|Any CPU
{777FBFA6-ECFA-42F5-AC5F-EFC4B2D4AE6B}.Release|x64.ActiveCfg = Release|Any CPU
{777FBFA6-ECFA-42F5-AC5F-EFC4B2D4AE6B}.Release|x64.Build.0 = Release|Any CPU
{777FBFA6-ECFA-42F5-AC5F-EFC4B2D4AE6B}.Release|x86.ActiveCfg = Release|Any CPU
{777FBFA6-ECFA-42F5-AC5F-EFC4B2D4AE6B}.Release|x86.Build.0 = Release|Any CPU
{557CB17D-8989-48E5-82FD-3C2106EE4BAE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{557CB17D-8989-48E5-82FD-3C2106EE4BAE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{557CB17D-8989-48E5-82FD-3C2106EE4BAE}.Debug|ARM.ActiveCfg = Debug|Any CPU
{557CB17D-8989-48E5-82FD-3C2106EE4BAE}.Debug|ARM.Build.0 = Debug|Any CPU
{557CB17D-8989-48E5-82FD-3C2106EE4BAE}.Debug|ARM64.ActiveCfg = Debug|Any CPU
{557CB17D-8989-48E5-82FD-3C2106EE4BAE}.Debug|ARM64.Build.0 = Debug|Any CPU
{557CB17D-8989-48E5-82FD-3C2106EE4BAE}.Debug|x64.ActiveCfg = Debug|Any CPU
{557CB17D-8989-48E5-82FD-3C2106EE4BAE}.Debug|x64.Build.0 = Debug|Any CPU
{557CB17D-8989-48E5-82FD-3C2106EE4BAE}.Debug|x86.ActiveCfg = Debug|Any CPU
{557CB17D-8989-48E5-82FD-3C2106EE4BAE}.Debug|x86.Build.0 = Debug|Any CPU
{557CB17D-8989-48E5-82FD-3C2106EE4BAE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{557CB17D-8989-48E5-82FD-3C2106EE4BAE}.Release|Any CPU.Build.0 = Release|Any CPU
{557CB17D-8989-48E5-82FD-3C2106EE4BAE}.Release|ARM.ActiveCfg = Release|Any CPU
{557CB17D-8989-48E5-82FD-3C2106EE4BAE}.Release|ARM.Build.0 = Release|Any CPU
{557CB17D-8989-48E5-82FD-3C2106EE4BAE}.Release|ARM64.ActiveCfg = Release|Any CPU
{557CB17D-8989-48E5-82FD-3C2106EE4BAE}.Release|ARM64.Build.0 = Release|Any CPU
{557CB17D-8989-48E5-82FD-3C2106EE4BAE}.Release|x64.ActiveCfg = Release|Any CPU
{557CB17D-8989-48E5-82FD-3C2106EE4BAE}.Release|x64.Build.0 = Release|Any CPU
{557CB17D-8989-48E5-82FD-3C2106EE4BAE}.Release|x86.ActiveCfg = Release|Any CPU
{557CB17D-8989-48E5-82FD-3C2106EE4BAE}.Release|x86.Build.0 = Release|Any CPU
{AE41666B-41D1-4044-9752-7CD4460C1A90}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{AE41666B-41D1-4044-9752-7CD4460C1A90}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AE41666B-41D1-4044-9752-7CD4460C1A90}.Debug|ARM.ActiveCfg = Debug|Any CPU
{AE41666B-41D1-4044-9752-7CD4460C1A90}.Debug|ARM.Build.0 = Debug|Any CPU
{AE41666B-41D1-4044-9752-7CD4460C1A90}.Debug|ARM64.ActiveCfg = Debug|Any CPU
{AE41666B-41D1-4044-9752-7CD4460C1A90}.Debug|ARM64.Build.0 = Debug|Any CPU
{AE41666B-41D1-4044-9752-7CD4460C1A90}.Debug|x64.ActiveCfg = Debug|Any CPU
{AE41666B-41D1-4044-9752-7CD4460C1A90}.Debug|x64.Build.0 = Debug|Any CPU
{AE41666B-41D1-4044-9752-7CD4460C1A90}.Debug|x86.ActiveCfg = Debug|Any CPU
{AE41666B-41D1-4044-9752-7CD4460C1A90}.Debug|x86.Build.0 = Debug|Any CPU
{AE41666B-41D1-4044-9752-7CD4460C1A90}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AE41666B-41D1-4044-9752-7CD4460C1A90}.Release|Any CPU.Build.0 = Release|Any CPU
{AE41666B-41D1-4044-9752-7CD4460C1A90}.Release|ARM.ActiveCfg = Release|Any CPU
{AE41666B-41D1-4044-9752-7CD4460C1A90}.Release|ARM.Build.0 = Release|Any CPU
{AE41666B-41D1-4044-9752-7CD4460C1A90}.Release|ARM64.ActiveCfg = Release|Any CPU
{AE41666B-41D1-4044-9752-7CD4460C1A90}.Release|ARM64.Build.0 = Release|Any CPU
{AE41666B-41D1-4044-9752-7CD4460C1A90}.Release|x64.ActiveCfg = Release|Any CPU
{AE41666B-41D1-4044-9752-7CD4460C1A90}.Release|x64.Build.0 = Release|Any CPU
{AE41666B-41D1-4044-9752-7CD4460C1A90}.Release|x86.ActiveCfg = Release|Any CPU
{AE41666B-41D1-4044-9752-7CD4460C1A90}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {0CB49554-419C-45F0-B5EB-B36357AF2018}
EndGlobalSection
EndGlobal

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

@ -17,5 +17,11 @@
<Setter Property="Padding" Value="7"/>
<Setter Property="Margin" Value="5"/>
</Style>
<Style Selector="Button.Rounded:pointerover">
<Setter Property="Background" Value="#002979"/>
</Style>
<Style Selector="Button.Rounded:pressed">
<Setter Property="Background" Value="#001261"/>
</Style>
</Application.Styles>
</Application>

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

@ -1,69 +1,71 @@
using System;
using System.Collections.Generic;
using System.Reactive.Concurrency;
using Camelotia.Presentation.Avalonia.Views;
using Camelotia.Presentation.Avalonia.Services;
using Camelotia.Presentation.Interfaces;
using Camelotia.Presentation.ViewModels;
using Camelotia.Services.Providers;
using Camelotia.Services.Storages;
using Camelotia.Services.Interfaces;
using Camelotia.Services.Models;
using ReactiveUI;
using Avalonia;
using Avalonia.ReactiveUI;
namespace Camelotia.Presentation.Avalonia
{
public sealed class Program
{
public static void Main(string[] args)
{
AppBuilder
.Configure<App>()
.UseReactiveUI()
.UsePlatformDetect()
.Start<MainView>(BuildMainViewModel);
}
private static IMainViewModel BuildMainViewModel()
{
var current = CurrentThreadScheduler.Instance;
var main = RxApp.MainThreadScheduler;
Akavache.BlobCache.ApplicationName = "Camelotia";
var cache = Akavache.BlobCache.UserAccount;
var login = new AvaloniaYandexAuthenticator();
var files = new AvaloniaFileManager();
return new MainViewModel(
(provider, auth) => new ProviderViewModel(
model => new CreateFolderViewModel(model, provider, current, main),
model => new RenameFileViewModel(model, provider, current, main),
(file, model) => new FileViewModel(model, file),
auth, files, provider, current, main
),
provider => new AuthViewModel(
new DirectAuthViewModel(provider, current, main),
new HostAuthViewModel(provider, current, main),
new OAuthViewModel(provider, current, main),
provider, current, main
),
new ProviderStorage(
new Dictionary<string, Func<ProviderModel, IProvider>>
{
["Local File System"] = id => new LocalProvider(id),
["Vkontakte Docs"] = id => new VkDocsProvider(id, cache),
["Yandex Disk"] = id => new YandexDiskProvider(id, login, cache),
["FTP"] = id => new FtpProvider(id),
["SFTP"] = id => new SftpProvider(id),
["GitHub"] = id => new GitHubProvider(id, cache),
["Google Drive"] = id => new GoogleDriveProvider(id, cache)
},
cache
),
current, main
);
}
}
}
using Avalonia;
using Avalonia.Markup.Xaml;
using Camelotia.Presentation.Avalonia.Services;
using Camelotia.Presentation.Avalonia.Views;
using Camelotia.Presentation.ViewModels;
using Camelotia.Services.Interfaces;
using Camelotia.Services.Models;
using Camelotia.Services.Providers;
using Camelotia.Services.Storages;
using ReactiveUI;
using System;
using System.Collections.Generic;
using System.Reactive.Concurrency;
namespace Camelotia.Presentation.Avalonia
{
public class App : Application
{
public override void Initialize()
{
AvaloniaXamlLoader.Load(this);
base.Initialize();
}
public override void OnFrameworkInitializationCompleted()
{
var current = CurrentThreadScheduler.Instance;
var main = RxApp.MainThreadScheduler;
Akavache.BlobCache.ApplicationName = "Camelotia";
var cache = Akavache.BlobCache.UserAccount;
var window = new MainView();
var login = new AvaloniaYandexAuthenticator();
var files = new AvaloniaFileManager(window);
var context = new MainViewModel(
(provider, auth) => new ProviderViewModel(
model => new CreateFolderViewModel(model, provider, current, main),
model => new RenameFileViewModel(model, provider, current, main),
(file, model) => new FileViewModel(model, file),
auth, files, provider, current, main
),
provider => new AuthViewModel(
new DirectAuthViewModel(provider, current, main),
new HostAuthViewModel(provider, current, main),
new OAuthViewModel(provider, current, main),
provider, current, main
),
new ProviderStorage(
new Dictionary<string, Func<ProviderModel, IProvider>>
{
["Local File System"] = id => new LocalProvider(id),
["Vkontakte Docs"] = id => new VkDocsProvider(id, cache),
["Yandex Disk"] = id => new YandexDiskProvider(id, login, cache),
["FTP"] = id => new FtpProvider(id),
["SFTP"] = id => new SftpProvider(id),
["GitHub"] = id => new GitHubProvider(id, cache),
["Google Drive"] = id => new GoogleDriveProvider(id, cache)
},
cache
),
current, main
);
window.DataContext = context;
window.Show();
base.OnFrameworkInitializationCompleted();
}
}
}

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

@ -1,27 +1,27 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Avalonia" Version="0.8.1-cibuild0002371-beta" />
<PackageReference Include="Avalonia.Desktop" Version="0.8.1-cibuild0002371-beta" />
<PackageReference Include="Avalonia.ReactiveUI" Version="0.8.1-cibuild0002371-beta" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Camelotia.Presentation\Camelotia.Presentation.csproj" />
</ItemGroup>
<ItemGroup>
<Compile Update="**\*.xaml.cs">
<DependentUpon>%(Filename)</DependentUpon>
</Compile>
<EmbeddedResource Include="**\*.xaml">
<SubType>Designer</SubType>
</EmbeddedResource>
</ItemGroup>
</Project>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Avalonia" Version="0.8.999-cibuild0003269-beta" />
<PackageReference Include="Avalonia.Desktop" Version="0.8.999-cibuild0003269-beta" />
<PackageReference Include="Avalonia.ReactiveUI" Version="0.8.999-cibuild0003269-beta" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Camelotia.Presentation\Camelotia.Presentation.csproj" />
</ItemGroup>
<ItemGroup>
<Compile Update="**\*.xaml.cs">
<DependentUpon>%(Filename)</DependentUpon>
</Compile>
<EmbeddedResource Include="**\*.xaml">
<SubType>Designer</SubType>
</EmbeddedResource>
</ItemGroup>
</Project>

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

@ -0,0 +1,17 @@
using Avalonia;
using Avalonia.ReactiveUI;
using Avalonia.Logging.Serilog;
namespace Camelotia.Presentation.Avalonia
{
public sealed class Program
{
public static void Main(string[] args) => BuildAvaloniaApp().StartWithClassicDesktopLifetime(args);
public static AppBuilder BuildAvaloniaApp()
=> AppBuilder.Configure<App>()
.UseReactiveUI()
.UsePlatformDetect()
.LogToDebug();
}
}

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

@ -2,7 +2,6 @@ using System;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Avalonia;
using Avalonia.Controls;
using Camelotia.Services.Interfaces;
@ -10,10 +9,14 @@ namespace Camelotia.Presentation.Avalonia.Services
{
public sealed class AvaloniaFileManager : IFileManager
{
private readonly Window _window;
public AvaloniaFileManager(Window window) => _window = window;
public async Task<Stream> OpenWrite(string name)
{
var fileDialog = new OpenFolderDialog();
var folder = await fileDialog.ShowAsync(Application.Current.MainWindow);
var folder = await fileDialog.ShowAsync(_window);
var path = Path.Combine(folder, name);
return File.Create(path);
}
@ -21,7 +24,7 @@ namespace Camelotia.Presentation.Avalonia.Services
public async Task<(string Name, Stream Stream)> OpenRead()
{
var fileDialog = new OpenFileDialog {AllowMultiple = false};
var files = await fileDialog.ShowAsync(Application.Current.MainWindow);
var files = await fileDialog.ShowAsync(_window);
var path = files.First();
var attributes = File.GetAttributes(path);

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

@ -1,62 +1,62 @@
using System.Reactive.Concurrency;
using System.Reactive.Subjects;
using Camelotia.Presentation.Interfaces;
using Camelotia.Presentation.ViewModels;
using Camelotia.Services.Interfaces;
using FluentAssertions;
using Microsoft.Reactive.Testing;
using NSubstitute;
using ReactiveUI.Testing;
using Xunit;
namespace Camelotia.Presentation.Tests
{
public sealed class AuthViewModelTests
{
private readonly IDirectAuthViewModel _directAuthViewModel = Substitute.For<IDirectAuthViewModel>();
private readonly IHostAuthViewModel _hostAuthViewModel = Substitute.For<IHostAuthViewModel>();
private readonly IOAuthViewModel _oAuthViewModel = Substitute.For<IOAuthViewModel>();
private readonly IProvider _provider = Substitute.For<IProvider>();
[Fact]
public void IsAuthenticatedPropertyShouldDependOnFileProvider() => new TestScheduler().With(scheduler =>
{
var authorized = new Subject<bool>();
_provider.IsAuthorized.Returns(authorized);
var model = BuildAuthViewModel(scheduler);
model.IsAuthenticated.Should().BeFalse();
authorized.OnNext(true);
scheduler.AdvanceBy(2);
model.IsAuthenticated.Should().BeTrue();
});
[Fact]
public void SupportsPropsShouldDependOnProvider() => new TestScheduler().With(scheduler =>
{
var model = BuildAuthViewModel(scheduler);
model.SupportsDirectAuth.Should().BeFalse();
model.SupportsOAuth.Should().BeFalse();
_provider.SupportsDirectAuth.Returns(true);
_provider.SupportsOAuth.Returns(true);
model.SupportsDirectAuth.Should().BeTrue();
model.SupportsOAuth.Should().BeTrue();
});
[Fact]
public void ShouldReturnInjectedAuthViewModelTypes() => new TestScheduler().With(scheduler =>
{
var model = BuildAuthViewModel(scheduler);
model.DirectAuth.Should().Be(_directAuthViewModel);
model.OAuth.Should().Be(_oAuthViewModel);
});
private AuthViewModel BuildAuthViewModel(IScheduler scheduler)
{
return new AuthViewModel(_directAuthViewModel, _hostAuthViewModel, _oAuthViewModel, _provider, scheduler, scheduler);
}
}
using System.Reactive.Concurrency;
using System.Reactive.Subjects;
using Camelotia.Presentation.Interfaces;
using Camelotia.Presentation.ViewModels;
using Camelotia.Services.Interfaces;
using FluentAssertions;
using Microsoft.Reactive.Testing;
using NSubstitute;
using ReactiveUI.Testing;
using Xunit;
namespace Camelotia.Presentation.Tests
{
public sealed class AuthViewModelTests
{
private readonly IDirectAuthViewModel _directAuthViewModel = Substitute.For<IDirectAuthViewModel>();
private readonly IHostAuthViewModel _hostAuthViewModel = Substitute.For<IHostAuthViewModel>();
private readonly IOAuthViewModel _oAuthViewModel = Substitute.For<IOAuthViewModel>();
private readonly IProvider _provider = Substitute.For<IProvider>();
[Fact]
public void IsAuthenticatedPropertyShouldDependOnFileProvider() => new TestScheduler().With(scheduler =>
{
var authorized = new Subject<bool>();
_provider.IsAuthorized.Returns(authorized);
var model = BuildAuthViewModel(scheduler);
model.IsAuthenticated.Should().BeFalse();
authorized.OnNext(true);
scheduler.AdvanceBy(2);
model.IsAuthenticated.Should().BeTrue();
});
[Fact]
public void SupportsPropsShouldDependOnProvider() => new TestScheduler().With(scheduler =>
{
var model = BuildAuthViewModel(scheduler);
model.SupportsDirectAuth.Should().BeFalse();
model.SupportsOAuth.Should().BeFalse();
_provider.SupportsDirectAuth.Returns(true);
_provider.SupportsOAuth.Returns(true);
model.SupportsDirectAuth.Should().BeTrue();
model.SupportsOAuth.Should().BeTrue();
});
[Fact]
public void ShouldReturnInjectedAuthViewModelTypes() => new TestScheduler().With(scheduler =>
{
var model = BuildAuthViewModel(scheduler);
model.DirectAuth.Should().Be(_directAuthViewModel);
model.OAuth.Should().Be(_oAuthViewModel);
});
private AuthViewModel BuildAuthViewModel(IScheduler scheduler)
{
return new AuthViewModel(_directAuthViewModel, _hostAuthViewModel, _oAuthViewModel, _provider, scheduler, scheduler);
}
}
}

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

@ -1,34 +1,34 @@
using Camelotia.Presentation.Extensions;
using FluentAssertions;
using Xunit;
namespace Camelotia.Presentation.Tests
{
public sealed class ByteConverterTests
{
[Theory]
[InlineData(0, "0B")]
[InlineData(520400, "520.4KB")]
[InlineData(520040000, "520MB")]
[InlineData(520068000, "520.1MB")]
[InlineData(520185000000, "520.2GB")]
public void ByteConverterShouldCalculateWithNoPrecisionSupplied(long byteCount, string expectedValue)
{
var stringValue = byteCount.ByteSizeToString();
stringValue.Should().Be(expectedValue);
}
[Theory]
[InlineData(115, 1, "115B")]
[InlineData(115, 3, "115B")]
[InlineData(520348, 3, "520.348KB")]
[InlineData(520462400, 3, "520.462MB")]
[InlineData(520573990000, 3, "520.574GB")]
[InlineData(520124960000, 3, "520.125GB")]
public void ByteConverterShouldCalculate(long byteCount, int precision, string expectedValue)
{
var stringValue = byteCount.ByteSizeToString(precision);
stringValue.Should().Be(expectedValue);
}
}
using Camelotia.Presentation.Extensions;
using FluentAssertions;
using Xunit;
namespace Camelotia.Presentation.Tests
{
public sealed class ByteConverterTests
{
[Theory]
[InlineData(0, "0B")]
[InlineData(520400, "520.4KB")]
[InlineData(520040000, "520MB")]
[InlineData(520068000, "520.1MB")]
[InlineData(520185000000, "520.2GB")]
public void ByteConverterShouldCalculateWithNoPrecisionSupplied(long byteCount, string expectedValue)
{
var stringValue = byteCount.ByteSizeToString();
stringValue.Should().Be(expectedValue);
}
[Theory]
[InlineData(115, 1, "115B")]
[InlineData(115, 3, "115B")]
[InlineData(520348, 3, "520.348KB")]
[InlineData(520462400, 3, "520.462MB")]
[InlineData(520573990000, 3, "520.574GB")]
[InlineData(520124960000, 3, "520.125GB")]
public void ByteConverterShouldCalculate(long byteCount, int precision, string expectedValue)
{
var stringValue = byteCount.ByteSizeToString(precision);
stringValue.Should().Be(expectedValue);
}
}
}

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

@ -5,14 +5,14 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="coverlet.msbuild" Version="2.6.1">
<PackageReference Include="coverlet.msbuild" Version="2.6.3">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
<PackageReference Include="FluentAssertions" Version="5.6.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.1.0" />
<PackageReference Include="ReactiveUI.Testing" Version="9.12.1" />
<PackageReference Include="NSubstitute" Version="4.2.0" />
<PackageReference Include="FluentAssertions" Version="5.7.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.2.0" />
<PackageReference Include="ReactiveUI.Testing" Version="9.19.5" />
<PackageReference Include="NSubstitute" Version="4.2.1" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1">
<PrivateAssets>all</PrivateAssets>

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

@ -1,76 +1,76 @@
using System;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Camelotia.Services.Interfaces;
using Camelotia.Services.Models;
using Camelotia.Services.Providers;
using FluentAssertions;
using Xunit;
namespace Camelotia.Presentation.Tests
{
public sealed class LocalFileSystemProviderTests
{
private static readonly Guid LocalIdentifier = Guid.NewGuid();
private static readonly string Separator = Path.DirectorySeparatorChar.ToString();
private readonly IProvider _provider = new LocalProvider(new ProviderModel
{
Id = LocalIdentifier,
Created = DateTime.Now,
Type = "Local"
});
[Fact]
public void ShouldExposeCorrectId()
{
_provider.Name.Should().Be("Local");
_provider.Id.Should().Be(LocalIdentifier);
}
[Fact]
public async Task LocalFileSystemShouldNotSupportAuth()
{
_provider.SupportsHostAuth.Should().BeFalse();
_provider.SupportsDirectAuth.Should().BeFalse();
_provider.SupportsOAuth.Should().BeFalse();
await _provider.DirectAuth(string.Empty, string.Empty);
await _provider.OAuth();
}
[Fact]
public async Task ShouldReturnFilesFromSpecificPath()
{
var real = await _provider.Get(Separator);
var expected = Directory.GetFileSystemEntries(Separator);
foreach (var model in real)
expected.Should().Contain(path =>
model.Path == path &&
model.Name == Path.GetFileName(path) &&
model.IsFolder == File
.GetAttributes(path)
.HasFlag(FileAttributes.Directory));
}
[Fact]
public async Task ShouldReturnDrivesFromAnEmptyPath()
{
var real = await _provider.Get(_provider.InitialPath);
var expected = DriveInfo
.GetDrives()
.Where(p => p.DriveType != DriveType.CDRom && p.IsReady)
.ToList();
foreach (var model in real)
expected.Should().Contain(drive =>
model.Name == drive.Name && model.IsFolder);
}
[Fact]
public void ShouldImplementNonNullInitialPath()
{
_provider.InitialPath.Should().NotBeNull();
_provider.InitialPath.Should().Be(string.Empty);
}
}
using System;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Camelotia.Services.Interfaces;
using Camelotia.Services.Models;
using Camelotia.Services.Providers;
using FluentAssertions;
using Xunit;
namespace Camelotia.Presentation.Tests
{
public sealed class LocalFileSystemProviderTests
{
private static readonly Guid LocalIdentifier = Guid.NewGuid();
private static readonly string Separator = Path.DirectorySeparatorChar.ToString();
private readonly IProvider _provider = new LocalProvider(new ProviderModel
{
Id = LocalIdentifier,
Created = DateTime.Now,
Type = "Local"
});
[Fact]
public void ShouldExposeCorrectId()
{
_provider.Name.Should().Be("Local");
_provider.Id.Should().Be(LocalIdentifier);
}
[Fact]
public async Task LocalFileSystemShouldNotSupportAuth()
{
_provider.SupportsHostAuth.Should().BeFalse();
_provider.SupportsDirectAuth.Should().BeFalse();
_provider.SupportsOAuth.Should().BeFalse();
await _provider.DirectAuth(string.Empty, string.Empty);
await _provider.OAuth();
}
[Fact]
public async Task ShouldReturnFilesFromSpecificPath()
{
var real = await _provider.Get(Separator);
var expected = Directory.GetFileSystemEntries(Separator);
foreach (var model in real)
expected.Should().Contain(path =>
model.Path == path &&
model.Name == Path.GetFileName(path) &&
model.IsFolder == File
.GetAttributes(path)
.HasFlag(FileAttributes.Directory));
}
[Fact]
public async Task ShouldReturnDrivesFromAnEmptyPath()
{
var real = await _provider.Get(_provider.InitialPath);
var expected = DriveInfo
.GetDrives()
.Where(p => p.DriveType != DriveType.CDRom && p.IsReady)
.ToList();
foreach (var model in real)
expected.Should().Contain(drive =>
model.Name == drive.Name && model.IsFolder);
}
[Fact]
public void ShouldImplementNonNullInitialPath()
{
_provider.InitialPath.Should().NotBeNull();
_provider.InitialPath.Should().Be(string.Empty);
}
}
}

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

@ -1,55 +1,55 @@
using System;
using System.Reactive.Concurrency;
using Camelotia.Presentation.ViewModels;
using Camelotia.Services.Interfaces;
using FluentAssertions;
using Microsoft.Reactive.Testing;
using NSubstitute;
using ReactiveUI.Testing;
using Xunit;
namespace Camelotia.Presentation.Tests
{
public sealed class OAuthViewModelTests
{
private readonly IProvider _provider = Substitute.For<IProvider>();
[Fact]
public void ShouldBeBusyWhenLoggingIn() => new TestScheduler().With(scheduler =>
{
var model = BuildOAuthViewModel(scheduler);
model.IsBusy.Should().BeFalse();
model.Login.CanExecute(null).Should().BeTrue();
model.Login.Execute(null);
scheduler.AdvanceBy(2);
model.IsBusy.Should().BeTrue();
scheduler.AdvanceBy(2);
model.IsBusy.Should().BeFalse();
});
[Fact]
public void HasErrorsShouldTriggerWhenProviderBreaks() => new TestScheduler().With(scheduler =>
{
_provider.OAuth().Returns(x => throw new Exception("example"));
var model = BuildOAuthViewModel(scheduler);
model.ErrorMessage.Should().BeNullOrEmpty();
model.HasErrors.Should().BeFalse();
model.Login.CanExecute(null).Should().BeTrue();
model.Login.Execute(null);
scheduler.AdvanceBy(2);
model.HasErrors.Should().BeTrue();
model.ErrorMessage.Should().NotBeNullOrEmpty();
model.ErrorMessage.Should().Be("example");
});
private OAuthViewModel BuildOAuthViewModel(IScheduler scheduler)
{
return new OAuthViewModel(_provider, scheduler, scheduler);
}
}
using System;
using System.Reactive.Concurrency;
using Camelotia.Presentation.ViewModels;
using Camelotia.Services.Interfaces;
using FluentAssertions;
using Microsoft.Reactive.Testing;
using NSubstitute;
using ReactiveUI.Testing;
using Xunit;
namespace Camelotia.Presentation.Tests
{
public sealed class OAuthViewModelTests
{
private readonly IProvider _provider = Substitute.For<IProvider>();
[Fact]
public void ShouldBeBusyWhenLoggingIn() => new TestScheduler().With(scheduler =>
{
var model = BuildOAuthViewModel(scheduler);
model.IsBusy.Should().BeFalse();
model.Login.CanExecute(null).Should().BeTrue();
model.Login.Execute(null);
scheduler.AdvanceBy(2);
model.IsBusy.Should().BeTrue();
scheduler.AdvanceBy(2);
model.IsBusy.Should().BeFalse();
});
[Fact]
public void HasErrorsShouldTriggerWhenProviderBreaks() => new TestScheduler().With(scheduler =>
{
_provider.OAuth().Returns(x => throw new Exception("example"));
var model = BuildOAuthViewModel(scheduler);
model.ErrorMessage.Should().BeNullOrEmpty();
model.HasErrors.Should().BeFalse();
model.Login.CanExecute(null).Should().BeTrue();
model.Login.Execute(null);
scheduler.AdvanceBy(2);
model.HasErrors.Should().BeTrue();
model.ErrorMessage.Should().NotBeNullOrEmpty();
model.ErrorMessage.Should().Be("example");
});
private OAuthViewModel BuildOAuthViewModel(IScheduler scheduler)
{
return new OAuthViewModel(_provider, scheduler, scheduler);
}
}
}

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

До

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

После

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

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

До

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

После

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

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

До

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

После

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

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

До

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

После

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

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

До

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

После

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

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

До

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

После

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

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

До

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

После

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

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

@ -35,7 +35,7 @@
<BundleAssemblies>false</BundleAssemblies>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugSymbols>false</DebugSymbols>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release</OutputPath>
@ -43,6 +43,10 @@
<WarningLevel>4</WarningLevel>
<AndroidManagedSymbols>true</AndroidManagedSymbols>
<AndroidUseSharedRuntime>false</AndroidUseSharedRuntime>
<EnableLLVM>false</EnableLLVM>
<BundleAssemblies>false</BundleAssemblies>
<AndroidLinkMode>SdkOnly</AndroidLinkMode>
<EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk>
</PropertyGroup>
<ItemGroup>
<Reference Include="Mono.Android" />
@ -52,18 +56,16 @@
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="System.Reactive" Version="4.1.3" />
<PackageReference Include="System.Reactive" Version="4.1.5" />
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="4.5.2" />
<PackageReference Include="ReactiveUI.AndroidSupport" Version="9.12.1" />
<PackageReference Include="Xamarin.Forms" Version="3.6.0.264807" />
<PackageReference Include="Xamarin.Android.Support.Design" Version="28.0.0" />
<PackageReference Include="Xamarin.Android.Support.v7.AppCompat" Version="28.0.0" />
<PackageReference Include="Xamarin.Android.Support.v4" Version="28.0.0" />
<PackageReference Include="Xamarin.Android.Support.v7.CardView" Version="28.0.0" />
<PackageReference Include="Xamarin.Android.Support.v7.MediaRouter" Version="28.0.0" />
<PackageReference Include="Xamarin.Plugin.FilePicker">
<Version>2.0.135</Version>
</PackageReference>
<PackageReference Include="ReactiveUI.AndroidSupport" Version="9.19.5" />
<PackageReference Include="Xamarin.Forms" Version="4.1.0.618606" />
<PackageReference Include="Xamarin.Android.Support.Design" Version="28.0.0.1" />
<PackageReference Include="Xamarin.Android.Support.v7.AppCompat" Version="28.0.0.1" />
<PackageReference Include="Xamarin.Android.Support.v4" Version="28.0.0.1" />
<PackageReference Include="Xamarin.Android.Support.v7.CardView" Version="28.0.0.1" />
<PackageReference Include="Xamarin.Android.Support.v7.MediaRouter" Version="28.0.0.1" />
<PackageReference Include="Xamarin.Plugin.FilePicker" Version="2.1.18" />
</ItemGroup>
<ItemGroup>
<Reference Include="System.Threading.Tasks.Extensions">

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

До

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

После

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

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

До

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

После

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

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

До

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

После

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

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