feature: Use .NET 6, use new C# language features (#52)

* refactor: Use new C# features where possible
* refactor: Use file scoped namespaces in UWP project
* Use .NET 6, regenerate NukeBuild files
This commit is contained in:
Artyom V. Gorchakov 2022-01-08 01:33:42 +03:00 коммит произвёл GitHub
Родитель 874ce5d56d
Коммит ff18127f07
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
145 изменённых файлов: 5192 добавлений и 5134 удалений

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

114
.nuke/build.schema.json Normal file
Просмотреть файл

@ -0,0 +1,114 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "Build Schema",
"$ref": "#/definitions/build",
"definitions": {
"build": {
"type": "object",
"properties": {
"Configuration": {
"type": "string"
},
"Continue": {
"type": "boolean",
"description": "Indicates to continue a previously failed build attempt"
},
"Full": {
"type": "boolean"
},
"Help": {
"type": "boolean",
"description": "Shows the help text for this build assembly"
},
"Host": {
"type": "string",
"description": "Host for execution. Default is 'automatic'",
"enum": [
"AppVeyor",
"AzurePipelines",
"Bamboo",
"Bitrise",
"GitHubActions",
"GitLab",
"Jenkins",
"Rider",
"SpaceAutomation",
"TeamCity",
"Terminal",
"TravisCI",
"VisualStudio",
"VSCode"
]
},
"Interactive": {
"type": "boolean"
},
"NoLogo": {
"type": "boolean",
"description": "Disables displaying the NUKE logo"
},
"Partition": {
"type": "string",
"description": "Partition to use on CI"
},
"Plan": {
"type": "boolean",
"description": "Shows the execution plan (HTML)"
},
"Profile": {
"type": "array",
"description": "Defines the profiles to load",
"items": {
"type": "string"
}
},
"Root": {
"type": "string",
"description": "Root directory during build execution"
},
"Skip": {
"type": "array",
"description": "List of targets to be skipped. Empty list skips all dependencies",
"items": {
"type": "string",
"enum": [
"Clean",
"CompileAvaloniaApp",
"CompileUniversalWindowsApp",
"CompileWindowsPresentationApp",
"CompileXamarinAndroidApp",
"RunInteractive",
"RunUnitTests"
]
}
},
"Target": {
"type": "array",
"description": "List of targets to be invoked. Default is '{default_target}'",
"items": {
"type": "string",
"enum": [
"Clean",
"CompileAvaloniaApp",
"CompileUniversalWindowsApp",
"CompileWindowsPresentationApp",
"CompileXamarinAndroidApp",
"RunInteractive",
"RunUnitTests"
]
}
},
"Verbosity": {
"type": "string",
"description": "Logging verbosity during build execution. Default is 'Normal'",
"enum": [
"Minimal",
"Normal",
"Quiet",
"Verbose"
]
}
}
}
}
}

4
.nuke/parameters.json Normal file
Просмотреть файл

@ -0,0 +1,4 @@
{
"$schema": "./build.schema.json",
"Solution": "src/Camelotia.Xamarin.sln"
}

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

@ -1,10 +1,16 @@
jobs:
- job: Linux
pool:
vmImage: 'ubuntu-18.04'
vmImage: 'ubuntu-20.04'
variables:
buildConfiguration: 'Release'
steps:
- task: UseDotNet@2
displayName: 'Install .NET 6 SDK'
inputs:
packageType: 'sdk'
version: '6.0.x'
performMultiLevelLookup: true
- script: cd $(Build.SourcesDirectory) && bash ./build.sh
displayName: 'Linux Build and Tests'
- task: PublishTestResults@2
@ -14,7 +20,7 @@ jobs:
- job: Windows
pool:
vmImage: 'windows-2019'
vmImage: 'windows-2022'
variables:
buildConfiguration: 'Release'
steps:

7
build.cmd Executable file
Просмотреть файл

@ -0,0 +1,7 @@
:; set -eo pipefail
:; SCRIPT_DIR=$(cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd)
:; ${SCRIPT_DIR}/build.sh "$@"
:; exit $?
@ECHO OFF
powershell -ExecutionPolicy ByPass -NoProfile -File "%~dp0build.ps1" %*

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

@ -4,9 +4,9 @@ Param(
[string[]]$BuildArguments
)
Write-Output "Windows PowerShell $($Host.Version)"
Write-Output "PowerShell $($PSVersionTable.PSEdition) version $($PSVersionTable.PSVersion)"
Set-StrictMode -Version 2.0; $ErrorActionPreference = "Stop"; $ConfirmPreference = "None"; trap { exit 1 }
Set-StrictMode -Version 2.0; $ErrorActionPreference = "Stop"; $ConfirmPreference = "None"; trap { Write-Error $_ -ErrorAction Continue; exit 1 }
$PSScriptRoot = Split-Path $MyInvocation.MyCommand.Path -Parent
###########################################################################
@ -14,14 +14,15 @@ $PSScriptRoot = Split-Path $MyInvocation.MyCommand.Path -Parent
###########################################################################
$BuildProjectFile = "$PSScriptRoot\build\build.csproj"
$TempDirectory = "$PSScriptRoot\\.tmp"
$TempDirectory = "$PSScriptRoot\\.nuke\temp"
$DotNetGlobalFile = "$PSScriptRoot\\global.json"
$DotNetInstallUrl = "https://raw.githubusercontent.com/dotnet/cli/master/scripts/obtain/dotnet-install.ps1"
$DotNetInstallUrl = "https://dot.net/v1/dotnet-install.ps1"
$DotNetChannel = "Current"
$env:DOTNET_SKIP_FIRST_TIME_EXPERIENCE = 1
$env:DOTNET_CLI_TELEMETRY_OPTOUT = 1
$env:DOTNET_MULTILEVEL_LOOKUP = 0
###########################################################################
# EXECUTION
@ -32,37 +33,37 @@ function ExecSafe([scriptblock] $cmd) {
if ($LASTEXITCODE) { exit $LASTEXITCODE }
}
# If global.json exists, load expected version
if (Test-Path $DotNetGlobalFile) {
$DotNetGlobal = $(Get-Content $DotNetGlobalFile | Out-String | ConvertFrom-Json)
if ($DotNetGlobal.PSObject.Properties["sdk"] -and $DotNetGlobal.sdk.PSObject.Properties["version"]) {
$DotNetVersion = $DotNetGlobal.sdk.version
}
}
# If dotnet is installed locally, and expected version is not set or installation matches the expected version
if ((Get-Command "dotnet" -ErrorAction SilentlyContinue) -ne $null -and `
(!(Test-Path variable:DotNetVersion) -or $(& dotnet --version) -eq $DotNetVersion)) {
# If dotnet CLI is installed globally and it matches requested version, use for execution
if ($null -ne (Get-Command "dotnet" -ErrorAction SilentlyContinue) -and `
$(dotnet --version) -and $LASTEXITCODE -eq 0) {
$env:DOTNET_EXE = (Get-Command "dotnet").Path
}
else {
$DotNetDirectory = "$TempDirectory\dotnet-win"
$env:DOTNET_EXE = "$DotNetDirectory\dotnet.exe"
# Download install script
$DotNetInstallFile = "$TempDirectory\dotnet-install.ps1"
md -force $TempDirectory > $null
New-Item -ItemType Directory -Path $TempDirectory -Force | Out-Null
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
(New-Object System.Net.WebClient).DownloadFile($DotNetInstallUrl, $DotNetInstallFile)
# Install by channel or version
if (!(Test-Path variable:DotNetVersion)) {
ExecSafe { & $DotNetInstallFile -InstallDir $DotNetDirectory -Channel $DotNetChannel -NoPath }
} else {
ExecSafe { & $DotNetInstallFile -InstallDir $DotNetDirectory -Version $DotNetVersion -NoPath }
# If global.json exists, load expected version
if (Test-Path $DotNetGlobalFile) {
$DotNetGlobal = $(Get-Content $DotNetGlobalFile | Out-String | ConvertFrom-Json)
if ($DotNetGlobal.PSObject.Properties["sdk"] -and $DotNetGlobal.sdk.PSObject.Properties["version"]) {
$DotNetVersion = $DotNetGlobal.sdk.version
}
}
# Install by channel or version
$DotNetDirectory = "$TempDirectory\dotnet-win"
if (!(Test-Path variable:DotNetVersion)) {
ExecSafe { & powershell $DotNetInstallFile -InstallDir $DotNetDirectory -Channel $DotNetChannel -NoPath }
} else {
ExecSafe { & powershell $DotNetInstallFile -InstallDir $DotNetDirectory -Version $DotNetVersion -NoPath }
}
$env:DOTNET_EXE = "$DotNetDirectory\dotnet.exe"
}
Write-Output "Microsoft (R) .NET Core SDK version $(& $env:DOTNET_EXE --version)"
ExecSafe { & $env:DOTNET_EXE build $BuildProjectFile /nodeReuse:false }
ExecSafe { & $env:DOTNET_EXE build $BuildProjectFile /nodeReuse:false /p:UseSharedCompilation=false -nologo -clp:NoSummary --verbosity quiet }
ExecSafe { & $env:DOTNET_EXE run --project $BuildProjectFile --no-build -- $BuildArguments }

40
build.sh Normal file → Executable file
Просмотреть файл

@ -1,6 +1,6 @@
#!/usr/bin/env bash
echo $(bash --version 2>&1 | head -n 1)
bash --version 2>&1 | head -n 1
set -eo pipefail
SCRIPT_DIR=$(cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd)
@ -10,53 +10,53 @@ SCRIPT_DIR=$(cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd)
###########################################################################
BUILD_PROJECT_FILE="$SCRIPT_DIR/build/build.csproj"
TEMP_DIRECTORY="$SCRIPT_DIR//.tmp"
TEMP_DIRECTORY="$SCRIPT_DIR//.nuke/temp"
DOTNET_GLOBAL_FILE="$SCRIPT_DIR//global.json"
DOTNET_INSTALL_URL="https://raw.githubusercontent.com/dotnet/cli/master/scripts/obtain/dotnet-install.sh"
DOTNET_INSTALL_URL="https://dot.net/v1/dotnet-install.sh"
DOTNET_CHANNEL="Current"
export DOTNET_CLI_TELEMETRY_OPTOUT=1
export DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1
export DOTNET_MULTILEVEL_LOOKUP=0
###########################################################################
# EXECUTION
###########################################################################
function FirstJsonValue {
perl -nle 'print $1 if m{"'$1'": "([^"\-]+)",?}' <<< ${@:2}
perl -nle 'print $1 if m{"'"$1"'": "([^"]+)",?}' <<< "${@:2}"
}
# If global.json exists, load expected version
if [ -f "$DOTNET_GLOBAL_FILE" ]; then
DOTNET_VERSION=$(FirstJsonValue "version" $(cat "$DOTNET_GLOBAL_FILE"))
if [ "$DOTNET_VERSION" == "" ]; then
unset DOTNET_VERSION
fi
fi
# If dotnet is installed locally, and expected version is not set or installation matches the expected version
if [[ -x "$(command -v dotnet)" && (-z ${DOTNET_VERSION+x} || $(dotnet --version) == "$DOTNET_VERSION") ]]; then
# If dotnet CLI is installed globally and it matches requested version, use for execution
if [ -x "$(command -v dotnet)" ] && dotnet --version &>/dev/null; then
export DOTNET_EXE="$(command -v dotnet)"
else
DOTNET_DIRECTORY="$TEMP_DIRECTORY/dotnet-unix"
export DOTNET_EXE="$DOTNET_DIRECTORY/dotnet"
# Download install script
DOTNET_INSTALL_FILE="$TEMP_DIRECTORY/dotnet-install.sh"
mkdir -p "$TEMP_DIRECTORY"
curl -Lsfo "$DOTNET_INSTALL_FILE" "$DOTNET_INSTALL_URL"
chmod +x "$DOTNET_INSTALL_FILE"
# If global.json exists, load expected version
if [[ -f "$DOTNET_GLOBAL_FILE" ]]; then
DOTNET_VERSION=$(FirstJsonValue "version" "$(cat "$DOTNET_GLOBAL_FILE")")
if [[ "$DOTNET_VERSION" == "" ]]; then
unset DOTNET_VERSION
fi
fi
# Install by channel or version
if [ -z ${DOTNET_VERSION+x} ]; then
DOTNET_DIRECTORY="$TEMP_DIRECTORY/dotnet-unix"
if [[ -z ${DOTNET_VERSION+x} ]]; then
"$DOTNET_INSTALL_FILE" --install-dir "$DOTNET_DIRECTORY" --channel "$DOTNET_CHANNEL" --no-path
else
"$DOTNET_INSTALL_FILE" --install-dir "$DOTNET_DIRECTORY" --version "$DOTNET_VERSION" --no-path
fi
export DOTNET_EXE="$DOTNET_DIRECTORY/dotnet"
fi
echo "Microsoft (R) .NET Core SDK version $("$DOTNET_EXE" --version)"
"$DOTNET_EXE" build "$BUILD_PROJECT_FILE" /nodeReuse:false
"$DOTNET_EXE" build "$BUILD_PROJECT_FILE" /nodeReuse:false /p:UseSharedCompilation=false -nologo -clp:NoSummary --verbosity quiet
"$DOTNET_EXE" run --project "$BUILD_PROJECT_FILE" --no-build -- "$@"

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

@ -0,0 +1,11 @@
[*.cs]
dotnet_style_qualification_for_field = false:warning
dotnet_style_qualification_for_property = false:warning
dotnet_style_qualification_for_method = false:warning
dotnet_style_qualification_for_event = false:warning
dotnet_style_require_accessibility_modifiers = never:warning
csharp_style_expression_bodied_methods = true:silent
csharp_style_expression_bodied_properties = true:warning
csharp_style_expression_bodied_indexers = true:warning
csharp_style_expression_bodied_accessors = true:warning

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

@ -7,15 +7,14 @@ using Nuke.Common.Tools.DotNet;
using Nuke.Common.Tools.MSBuild;
using Nuke.Common.Utilities.Collections;
using static Nuke.Common.IO.FileSystemTasks;
using static Nuke.Common.IO.PathConstruction;
using static Nuke.Common.Tools.DotNet.DotNetTasks;
using static Nuke.Common.Tools.MSBuild.MSBuildTasks;
// ReSharper disable ArrangeTypeMemberModifiers
[CheckBuildProjectConfigurations]
[CheckBuildProjectConfigurations(TimeoutInMilliseconds = 2000)]
[UnsetVisualStudioEnvironmentVariables]
internal class Build : NukeBuild
class Build : NukeBuild
{
const string InteractiveProjectName = "Camelotia.Presentation.Avalonia";
const string CoverageFileName = "coverage.cobertura.xml";
@ -64,15 +63,15 @@ internal class Build : NukeBuild
.Executes(() =>
{
var execute = EnvironmentInfo.IsWin && Full;
Logger.Info($"Should compile for Universal Windows: {execute}");
Serilog.Log.Information($"Should compile for Universal Windows: {execute}");
if (!execute) return;
Logger.Normal("Restoring packages required by UAP...");
Serilog.Log.Information("Restoring packages required by UAP...");
var project = SourceDirectory.GlobFiles("**/*.Uwp.csproj").First();
MSBuild(settings => settings
.SetProjectFile(project)
.SetTargets("Restore"));
Logger.Success("Successfully restored UAP packages.");
Serilog.Log.Information("Successfully restored UAP packages.");
new[] { MSBuildTargetPlatform.x86,
MSBuildTargetPlatform.x64,
@ -81,13 +80,13 @@ internal class Build : NukeBuild
void BuildApp(MSBuildTargetPlatform platform)
{
Logger.Normal("Cleaning UAP project...");
Serilog.Log.Information("Cleaning UAP project...");
MSBuild(settings => settings
.SetProjectFile(project)
.SetTargets("Clean"));
Logger.Success("Successfully managed to clean UAP project.");
Serilog.Log.Information("Successfully managed to clean UAP project.");
Logger.Normal($"Building UAP project for {platform}...");
Serilog.Log.Information($"Building UAP project for {platform}...");
MSBuild(settings => settings
.SetProjectFile(project)
.SetTargets("Build")
@ -97,7 +96,7 @@ internal class Build : NukeBuild
.SetProperty("AppxPackageDir", ArtifactsDirectory)
.SetProperty("UapAppxPackageBuildMode", "CI")
.SetProperty("AppxBundle", "Always"));
Logger.Success($"Successfully built UAP project for {platform}.");
Serilog.Log.Information($"Successfully built UAP project for {platform}.");
}
});
@ -106,38 +105,38 @@ internal class Build : NukeBuild
.Executes(() =>
{
var execute = EnvironmentInfo.IsWin && Full;
Logger.Info($"Should compile for Android: {execute}");
Serilog.Log.Information($"Should compile for Android: {execute}");
if (!execute) return;
Logger.Normal("Restoring packages required by Xamarin Android...");
Serilog.Log.Information("Restoring packages required by Xamarin Android...");
var project = SourceDirectory.GlobFiles("**/*.Xamarin.Droid.csproj").First();
MSBuild(settings => settings
.SetProjectFile(project)
.SetTargets("Restore"));
Logger.Success("Successfully restored Xamarin Android packages.");
Serilog.Log.Information("Successfully restored Xamarin Android packages.");
Logger.Normal("Building Xamarin Android project...");
Serilog.Log.Information("Building Xamarin Android project...");
var java = Environment.GetEnvironmentVariable("JAVA_HOME");
MSBuild(settings => settings
.SetProjectFile(project)
.SetTargets("Build")
.SetConfiguration(Configuration)
.SetProperty("JavaSdkDirectory", java));
Logger.Success("Successfully built Xamarin Android project.");
Serilog.Log.Information("Successfully built Xamarin Android project.");
Logger.Normal("Signing Android package...");
Serilog.Log.Information("Signing Android package...");
MSBuild(settings => settings
.SetProjectFile(project)
.SetTargets("SignAndroidPackage")
.SetConfiguration(Configuration)
.SetProperty("JavaSdkDirectory", java));
Logger.Success("Successfully signed Xamarin Android APK.");
Serilog.Log.Information("Successfully signed Xamarin Android APK.");
Logger.Normal("Moving APK files to artifacts directory...");
Serilog.Log.Information("Moving APK files to artifacts directory...");
SourceDirectory
.GlobFiles("**/bin/**/*-Signed.apk")
.ForEach(file => MoveFileToDirectory(file, ArtifactsDirectory));
Logger.Success("Successfully moved APK files.");
Serilog.Log.Information("Successfully moved APK files.");
});
Target CompileWindowsPresentationApp => _ => _
@ -145,22 +144,22 @@ internal class Build : NukeBuild
.Executes(() =>
{
var execute = EnvironmentInfo.IsWin && Full;
Logger.Info($"Should compile for WPF: {execute}");
Serilog.Log.Information($"Should compile for WPF: {execute}");
if (!execute) return;
Logger.Normal("Restoring packages required by WPF app...");
Serilog.Log.Information("Restoring packages required by WPF app...");
var project = SourceDirectory.GlobFiles("**/*.Wpf.csproj").First();
MSBuild(settings => settings
.SetProjectFile(project)
.SetTargets("Restore"));
Logger.Success("Successfully restored Wpf packages.");
Serilog.Log.Information("Successfully restored Wpf packages.");
Logger.Normal("Building WPF project...");
Serilog.Log.Information("Building WPF project...");
MSBuild(settings => settings
.SetProjectFile(project)
.SetTargets("Build")
.SetConfiguration(Configuration));
Logger.Success("Successfully built WPF project.");
Serilog.Log.Information("Successfully built WPF project.");
});
Target RunInteractive => _ => _

16
build/Configuration.cs Normal file
Просмотреть файл

@ -0,0 +1,16 @@
using System;
using System.ComponentModel;
using System.Linq;
using Nuke.Common.Tooling;
[TypeConverter(typeof(TypeConverter<Configuration>))]
public class Configuration : Enumeration
{
public static Configuration Debug = new Configuration { Value = nameof(Debug) };
public static Configuration Release = new Configuration { Value = nameof(Release) };
public static implicit operator string(Configuration configuration)
{
return configuration.Value;
}
}

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

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<!-- This file prevents unintended imports of unrelated MSBuild files -->
<!-- Uncomment to include parent Directory.Build.props file -->
<!--<Import Project="$([MSBuild]::GetPathOfFileAbove('Directory.Build.props', '$(MSBuildThisFileDirectory)../'))" />-->
</Project>

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

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<!-- This file prevents unintended imports of unrelated MSBuild files -->
<!-- Uncomment to include parent Directory.Build.targets file -->
<!--<Import Project="$([MSBuild]::GetPathOfFileAbove('Directory.Build.targets', '$(MSBuildThisFileDirectory)../'))" />-->
</Project>

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

@ -2,33 +2,17 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<RootNamespace />
<IsPackable>False</IsPackable>
<TargetFramework>net6.0</TargetFramework>
<RootNamespace></RootNamespace>
<NoWarn>CS0649;CS0169</NoWarn>
<NukeRootDirectory>..</NukeRootDirectory>
<NukeScriptDirectory>..</NukeScriptDirectory>
<NukeTelemetryVersion>1</NukeTelemetryVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Nuke.Common" Version="5.3.0" />
</ItemGroup>
<ItemGroup>
<NukeSpecificationFiles Include="**\*.json" Exclude="bin\**;obj\**" />
<NukeExternalFiles Include="**\*.*.ext" Exclude="bin\**;obj\**" />
<None Remove="*.csproj.DotSettings;*.ref.*.txt" />
<!-- Common build related files -->
<None Include="..\build.ps1" />
<None Include="..\build.sh" />
<None Include="..\.nuke" />
<None Include="..\global.json" Condition="Exists('..\global.json')" />
<None Include="..\nuget.config" Condition="Exists('..\nuget.config')" />
<None Include="..\azure-pipelines.yml" Condition="Exists('..\azure-pipelines.yml')" />
<None Include="..\Jenkinsfile" Condition="Exists('..\Jenkinsfile')" />
<None Include="..\appveyor.yml" Condition="Exists('..\appveyor.yml')" />
<None Include="..\.travis.yml" Condition="Exists('..\.travis.yml')" />
<None Include="..\GitVersion.yml" Condition="Exists('..\GitVersion.yml')" />
<PackageReference Include="Nuke.Common" Version="6.0.0" />
<PackageDownload Include="GitVersion.Tool" Version="[5.8.0]" />
</ItemGroup>
</Project>

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

@ -0,0 +1,27 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=HeapView_002EDelegateAllocation/@EntryIndexedValue">DO_NOT_SHOW</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=VariableHidesOuterVariable/@EntryIndexedValue">DO_NOT_SHOW</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ClassNeverInstantiated_002EGlobal/@EntryIndexedValue">DO_NOT_SHOW</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=MemberCanBeMadeStatic_002ELocal/@EntryIndexedValue">DO_NOT_SHOW</s:String>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/DEFAULT_INTERNAL_MODIFIER/@EntryValue">Implicit</s:String>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/DEFAULT_PRIVATE_MODIFIER/@EntryValue">Implicit</s:String>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/METHOD_OR_OPERATOR_BODY/@EntryValue">ExpressionBody</s:String>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/ThisQualifier/INSTANCE_MEMBERS_QUALIFY_MEMBERS/@EntryValue">0</s:String>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/ANONYMOUS_METHOD_DECLARATION_BRACES/@EntryValue">NEXT_LINE</s:String>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/KEEP_USER_LINEBREAKS/@EntryValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_AFTER_INVOCATION_LPAR/@EntryValue">False</s:Boolean>
<s:Int64 x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/MAX_ATTRIBUTE_LENGTH_FOR_SAME_LINE/@EntryValue">120</s:Int64>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_FIELD_ATTRIBUTE_ON_SAME_LINE_EX/@EntryValue">IF_OWNER_IS_SINGLE_LINE</s:String>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_ARGUMENTS_STYLE/@EntryValue">WRAP_IF_LONG</s:String>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_SIMPLE_ANONYMOUSMETHOD_ON_SINGLE_LINE/@EntryValue">False</s:Boolean>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateInstanceFields/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateStaticFields/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /&gt;</s:String>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpAttributeForSingleLineMethodUpgrade/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpKeepExistingMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpPlaceEmbeddedOnSameLineMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpRenamePlacementToArrangementMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpUseContinuousIndentInsideBracesMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EAddAccessorOwnerDeclarationBracesMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002ECSharpPlaceAttributeOnSameLineMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateBlankLinesAroundFieldToBlankLinesAroundProperty/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateThisQualifierSettings/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>

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

@ -1,9 +1,7 @@
using System;
using System.Diagnostics;
using System.Reactive;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Layout;
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
using Camelotia.Presentation.AppState;
@ -14,74 +12,73 @@ using Camelotia.Presentation.ViewModels;
using Camelotia.Services;
using ReactiveUI;
namespace Camelotia.Presentation.Avalonia
namespace Camelotia.Presentation.Avalonia;
public class App : Application
{
public class App : Application
public override void Initialize() => AvaloniaXamlLoader.Load(this);
public override void OnFrameworkInitializationCompleted()
{
public override void Initialize() => AvaloniaXamlLoader.Load(this);
Akavache.BlobCache.ApplicationName = "CamelotiaV2";
var suspension = new AutoSuspendHelper(ApplicationLifetime);
RxApp.SuspensionHost.CreateNewAppState = () => new MainState();
RxApp.SuspensionHost.SetupDefaultSuspendResume(new NewtonsoftJsonSuspensionDriver("appstate.json"));
suspension.OnFrameworkInitializationCompleted();
public override void OnFrameworkInitializationCompleted()
var window = new Window
{
Akavache.BlobCache.ApplicationName = "CamelotiaV2";
var suspension = new AutoSuspendHelper(ApplicationLifetime);
RxApp.SuspensionHost.CreateNewAppState = () => new MainState();
RxApp.SuspensionHost.SetupDefaultSuspendResume(new NewtonsoftJsonSuspensionDriver("appstate.json"));
suspension.OnFrameworkInitializationCompleted();
Height = 590,
Width = 850,
MinHeight = 590,
MinWidth = 850,
};
var window = new Window
{
Height = 590,
Width = 850,
MinHeight = 590,
MinWidth = 850,
};
AttachDevTools(window);
window.Content = CreateView(window);
window.Show();
AttachDevTools(window);
window.Content = CreateView(window);
window.Show();
RxApp.DefaultExceptionHandler = Observer.Create<Exception>(Console.WriteLine);
base.OnFrameworkInitializationCompleted();
}
RxApp.DefaultExceptionHandler = Observer.Create<Exception>(Console.WriteLine);
base.OnFrameworkInitializationCompleted();
}
public object CreateView(Window window)
{
var view = new MainView();
var styles = new AvaloniaStyleManager(view);
view.SwitchThemeButton.Click += (_, _) => styles.UseNextTheme();
view.DataContext ??= CreateViewModel(window);
return view;
}
public object CreateView(Window window)
{
var view = new MainView();
var styles = new AvaloniaStyleManager(view);
view.SwitchThemeButton.Click += (_, _) => styles.UseNextTheme();
view.DataContext ??= CreateViewModel(window);
return view;
}
private static MainViewModel CreateViewModel(Window window)
{
var main = RxApp.SuspensionHost.GetAppState<MainState>();
return new MainViewModel(
main,
new CloudFactory(
main.CloudConfiguration,
new AvaloniaYandexAuthenticator(),
Akavache.BlobCache.UserAccount),
(state, provider) => new CloudViewModel(
state,
owner => new CreateFolderViewModel(state.CreateFolderState, owner, provider),
owner => new RenameFileViewModel(state.RenameFileState, owner, provider),
(file, owner) => new FileViewModel(owner, file),
(folder, owner) => new FolderViewModel(owner, folder),
new AuthViewModel(
new DirectAuthViewModel(state.AuthState.DirectAuthState, provider),
new HostAuthViewModel(state.AuthState.HostAuthState, provider),
new OAuthViewModel(provider),
provider),
new AvaloniaFileManager(window),
provider));
}
private static MainViewModel CreateViewModel(Window window)
{
var main = RxApp.SuspensionHost.GetAppState<MainState>();
return new MainViewModel(
main,
new CloudFactory(
main.CloudConfiguration,
new AvaloniaYandexAuthenticator(),
Akavache.BlobCache.UserAccount),
(state, provider) => new CloudViewModel(
state,
owner => new CreateFolderViewModel(state.CreateFolderState, owner, provider),
owner => new RenameFileViewModel(state.RenameFileState, owner, provider),
(file, owner) => new FileViewModel(owner, file),
(folder, owner) => new FolderViewModel(owner, folder),
new AuthViewModel(
new DirectAuthViewModel(state.AuthState.DirectAuthState, provider),
new HostAuthViewModel(state.AuthState.HostAuthState, provider),
new OAuthViewModel(provider),
provider),
new AvaloniaFileManager(window),
provider));
}
private static void AttachDevTools(TopLevel window)
{
private static void AttachDevTools(TopLevel window)
{
#if DEBUG
window.AttachDevTools();
window.AttachDevTools();
#endif
}
}
}

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

@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework>
<TargetFramework>net6</TargetFramework>
<LangVersion>latest</LangVersion>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
</PropertyGroup>

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

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

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

@ -5,35 +5,34 @@ using System.Threading.Tasks;
using Avalonia.Controls;
using Camelotia.Services.Interfaces;
namespace Camelotia.Presentation.Avalonia.Services
namespace Camelotia.Presentation.Avalonia.Services;
public sealed class AvaloniaFileManager : IFileManager
{
public sealed class AvaloniaFileManager : IFileManager
private readonly Window _window;
public AvaloniaFileManager(Window window) => _window = window;
public async Task<Stream> OpenWrite(string name)
{
private readonly Window _window;
var fileDialog = new OpenFolderDialog();
var folder = await fileDialog.ShowAsync(_window).ConfigureAwait(false);
var path = Path.Combine(folder, name);
return File.Create(path);
}
public AvaloniaFileManager(Window window) => _window = window;
public async Task<(string Name, Stream Stream)> OpenRead()
{
var fileDialog = new OpenFileDialog { AllowMultiple = false };
var files = await fileDialog.ShowAsync(_window).ConfigureAwait(false);
var path = files.First();
public async Task<Stream> OpenWrite(string name)
{
var fileDialog = new OpenFolderDialog();
var folder = await fileDialog.ShowAsync(_window).ConfigureAwait(false);
var path = Path.Combine(folder, name);
return File.Create(path);
}
var attributes = File.GetAttributes(path);
var isFolder = attributes.HasFlag(FileAttributes.Directory);
if (isFolder) throw new Exception("Folders are not supported.");
public async Task<(string Name, Stream Stream)> OpenRead()
{
var fileDialog = new OpenFileDialog { AllowMultiple = false };
var files = await fileDialog.ShowAsync(_window).ConfigureAwait(false);
var path = files.First();
var attributes = File.GetAttributes(path);
var isFolder = attributes.HasFlag(FileAttributes.Directory);
if (isFolder) throw new Exception("Folders are not supported.");
var stream = File.OpenRead(path);
var name = Path.GetFileName(path);
return (name, stream);
}
var stream = File.OpenRead(path);
var name = Path.GetFileName(path);
return (name, stream);
}
}

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

@ -2,68 +2,67 @@ using System;
using Avalonia.Markup.Xaml.Styling;
using Avalonia.Styling;
namespace Camelotia.Presentation.Avalonia.Services
namespace Camelotia.Presentation.Avalonia.Services;
public sealed class AvaloniaStyleManager
{
public sealed class AvaloniaStyleManager
public enum Theme
{
public enum Theme
{
Citrus,
Sea,
Rust,
Candy,
Magma
}
private readonly StyleInclude _magmaStyle = CreateStyle("avares://Citrus.Avalonia/Magma.xaml");
private readonly StyleInclude _candyStyle = CreateStyle("avares://Citrus.Avalonia/Candy.xaml");
private readonly StyleInclude _citrusStyle = CreateStyle("avares://Citrus.Avalonia/Citrus.xaml");
private readonly StyleInclude _rustStyle = CreateStyle("avares://Citrus.Avalonia/Rust.xaml");
private readonly StyleInclude _seaStyle = CreateStyle("avares://Citrus.Avalonia/Sea.xaml");
private readonly IStyleHost _window;
public AvaloniaStyleManager(IStyleHost window)
{
_window = window;
if (window.Styles.Count == 0)
window.Styles.Add(_seaStyle);
else window.Styles[0] = _seaStyle;
}
public Theme CurrentTheme { get; private set; } = Theme.Sea;
public void UseNextTheme() =>
UseTheme(CurrentTheme switch
{
Theme.Citrus => Theme.Sea,
Theme.Sea => Theme.Rust,
Theme.Rust => Theme.Candy,
Theme.Candy => Theme.Magma,
Theme.Magma => Theme.Citrus,
_ => throw new ArgumentOutOfRangeException(nameof(CurrentTheme))
});
private static StyleInclude CreateStyle(string url)
{
var self = new Uri("resm:Styles?assembly=Citrus.Avalonia.Sandbox");
return new StyleInclude(self)
{
Source = new Uri(url)
};
}
private void UseTheme(Theme theme)
{
CurrentTheme = theme;
_window.Styles[0] = CurrentTheme switch
{
Theme.Citrus => _citrusStyle,
Theme.Sea => _seaStyle,
Theme.Rust => _rustStyle,
Theme.Candy => _candyStyle,
Theme.Magma => _magmaStyle,
_ => throw new ArgumentOutOfRangeException(nameof(theme))
};
}
Citrus,
Sea,
Rust,
Candy,
Magma
}
}
private readonly StyleInclude _magmaStyle = CreateStyle("avares://Citrus.Avalonia/Magma.xaml");
private readonly StyleInclude _candyStyle = CreateStyle("avares://Citrus.Avalonia/Candy.xaml");
private readonly StyleInclude _citrusStyle = CreateStyle("avares://Citrus.Avalonia/Citrus.xaml");
private readonly StyleInclude _rustStyle = CreateStyle("avares://Citrus.Avalonia/Rust.xaml");
private readonly StyleInclude _seaStyle = CreateStyle("avares://Citrus.Avalonia/Sea.xaml");
private readonly IStyleHost _window;
public AvaloniaStyleManager(IStyleHost window)
{
_window = window;
if (window.Styles.Count == 0)
window.Styles.Add(_seaStyle);
else window.Styles[0] = _seaStyle;
}
public Theme CurrentTheme { get; private set; } = Theme.Sea;
public void UseNextTheme() =>
UseTheme(CurrentTheme switch
{
Theme.Citrus => Theme.Sea,
Theme.Sea => Theme.Rust,
Theme.Rust => Theme.Candy,
Theme.Candy => Theme.Magma,
Theme.Magma => Theme.Citrus,
_ => throw new ArgumentOutOfRangeException(nameof(CurrentTheme))
});
private static StyleInclude CreateStyle(string url)
{
var self = new Uri("resm:Styles?assembly=Citrus.Avalonia.Sandbox");
return new StyleInclude(self)
{
Source = new Uri(url)
};
}
private void UseTheme(Theme theme)
{
CurrentTheme = theme;
_window.Styles[0] = CurrentTheme switch
{
Theme.Citrus => _citrusStyle,
Theme.Sea => _seaStyle,
Theme.Rust => _rustStyle,
Theme.Candy => _candyStyle,
Theme.Magma => _magmaStyle,
_ => throw new ArgumentOutOfRangeException(nameof(theme))
};
}
}

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

@ -5,23 +5,23 @@ using System.Text;
using System.Threading.Tasks;
using Camelotia.Services.Interfaces;
namespace Camelotia.Presentation.Avalonia.Services
namespace Camelotia.Presentation.Avalonia.Services;
public sealed class AvaloniaYandexAuthenticator : IAuthenticator
{
public sealed class AvaloniaYandexAuthenticator : IAuthenticator
private const string SuccessContent = "<html><body>Please return to the app.</body></html>";
public GrantType GrantType => GrantType.AuthorizationCode;
public Task<string> ReceiveToken(Uri uri) => throw new PlatformNotSupportedException();
public async Task<string> ReceiveCode(Uri uri, Uri returnUrl)
{
private const string SuccessContent = "<html><body>Please return to the app.</body></html>";
public GrantType GrantType => GrantType.AuthorizationCode;
public Task<string> ReceiveToken(Uri uri) => throw new PlatformNotSupportedException();
public async Task<string> ReceiveCode(Uri uri, Uri returnUrl)
{
var server = returnUrl.ToString();
var listener = new HttpListener();
listener.Prefixes.Add(server);
listener.Start();
new Process
var server = returnUrl.ToString();
var listener = new HttpListener();
listener.Prefixes.Add(server);
listener.Start();
new Process
{
StartInfo = new ProcessStartInfo
{
@ -31,16 +31,15 @@ namespace Camelotia.Presentation.Avalonia.Services
}
.Start();
var context = await listener.GetContextAsync().ConfigureAwait(false);
var code = context.Request.QueryString["code"];
var context = await listener.GetContextAsync().ConfigureAwait(false);
var code = context.Request.QueryString["code"];
var buffer = Encoding.UTF8.GetBytes(SuccessContent);
context.Response.ContentLength64 = buffer.Length;
await context.Response.OutputStream.WriteAsync(buffer, 0, buffer.Length).ConfigureAwait(false);
var buffer = Encoding.UTF8.GetBytes(SuccessContent);
context.Response.ContentLength64 = buffer.Length;
await context.Response.OutputStream.WriteAsync(buffer, 0, buffer.Length).ConfigureAwait(false);
context.Response.Close();
listener.Close();
return code;
}
context.Response.Close();
listener.Close();
return code;
}
}
}

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

@ -6,37 +6,36 @@ using Avalonia.ReactiveUI;
using Camelotia.Presentation.Interfaces;
using ReactiveUI;
namespace Camelotia.Presentation.Avalonia.Views
{
public sealed partial class AuthView : ReactiveUserControl<IAuthViewModel>
{
public AuthView()
{
InitializeComponent();
this.WhenActivated(disposables =>
{
this.WhenAnyValue(x => x.ViewModel)
.Where(context => context != null)
.Select(ResolveControl)
.BindTo(this, x => x.Content)
.DisposeWith(disposables);
});
}
namespace Camelotia.Presentation.Avalonia.Views;
private static IControl ResolveControl(IAuthViewModel context)
public sealed partial class AuthView : ReactiveUserControl<IAuthViewModel>
{
public AuthView()
{
InitializeComponent();
this.WhenActivated(disposables =>
{
if (context.SupportsDirectAuth)
return new DirectAuthView { DataContext = context.DirectAuth };
if (context.SupportsOAuth)
return new OAuthView { DataContext = context.OAuth };
if (context.SupportsHostAuth)
return new HostAuthView { DataContext = context.HostAuth };
return new TextBlock
{
Text = "No supported authentication method found.",
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center
};
}
this.WhenAnyValue(x => x.ViewModel)
.Where(context => context != null)
.Select(ResolveControl)
.BindTo(this, x => x.Content)
.DisposeWith(disposables);
});
}
}
private static IControl ResolveControl(IAuthViewModel context)
{
if (context.SupportsDirectAuth)
return new DirectAuthView { DataContext = context.DirectAuth };
if (context.SupportsOAuth)
return new OAuthView { DataContext = context.OAuth };
if (context.SupportsHostAuth)
return new HostAuthView { DataContext = context.HostAuth };
return new TextBlock
{
Text = "No supported authentication method found.",
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center
};
}
}

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

@ -2,14 +2,13 @@ using Avalonia.ReactiveUI;
using Camelotia.Presentation.Interfaces;
using ReactiveUI;
namespace Camelotia.Presentation.Avalonia.Views
namespace Camelotia.Presentation.Avalonia.Views;
public sealed partial class CreateFolderView : ReactiveUserControl<ICreateFolderViewModel>
{
public sealed partial class CreateFolderView : ReactiveUserControl<ICreateFolderViewModel>
public CreateFolderView()
{
public CreateFolderView()
{
InitializeComponent();
this.WhenActivated(disposables => { });
}
InitializeComponent();
this.WhenActivated(disposables => { });
}
}
}

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

@ -4,22 +4,21 @@ using Camelotia.Presentation.Interfaces;
using ReactiveUI;
using ReactiveUI.Validation.Extensions;
namespace Camelotia.Presentation.Avalonia.Views
namespace Camelotia.Presentation.Avalonia.Views;
public sealed partial class DirectAuthView : ReactiveUserControl<IDirectAuthViewModel>
{
public sealed partial class DirectAuthView : ReactiveUserControl<IDirectAuthViewModel>
public DirectAuthView()
{
public DirectAuthView()
InitializeComponent();
this.WhenActivated(disposables =>
{
InitializeComponent();
this.WhenActivated(disposables =>
{
this.BindValidation(ViewModel, x => x.Username, x => x.UsernameValidation.Text)
.DisposeWith(disposables);
this.BindValidation(ViewModel, x => x.Password, x => x.PasswordValidation.Text)
.DisposeWith(disposables);
this.BindValidation(ViewModel, x => x.FormValidation.Text)
.DisposeWith(disposables);
});
}
this.BindValidation(ViewModel, x => x.Username, x => x.UsernameValidation.Text)
.DisposeWith(disposables);
this.BindValidation(ViewModel, x => x.Password, x => x.PasswordValidation.Text)
.DisposeWith(disposables);
this.BindValidation(ViewModel, x => x.FormValidation.Text)
.DisposeWith(disposables);
});
}
}
}

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

@ -2,35 +2,32 @@ using System;
using System.Reactive;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.ReactiveUI;
using Camelotia.Presentation.Interfaces;
using ReactiveMarbles.ObservableEvents;
using ReactiveUI;
namespace Camelotia.Presentation.Avalonia.Views
{
public sealed partial class FileView : ReactiveUserControl<IFileViewModel>
{
public FileView()
{
InitializeComponent();
this.WhenActivated(disposables =>
{
this.Events()
.DoubleTapped
.Do(args => ViewModel.Provider.SelectedFile = ViewModel)
.Select(args => Unit.Default)
.InvokeCommand(this, x => x.ViewModel.Provider.Open)
.DisposeWith(disposables);
namespace Camelotia.Presentation.Avalonia.Views;
this.ContextMenu
.Events()
.MenuOpened
.Subscribe(args => ViewModel.Provider.SelectedFile = ViewModel)
.DisposeWith(disposables);
});
}
public sealed partial class FileView : ReactiveUserControl<IFileViewModel>
{
public FileView()
{
InitializeComponent();
this.WhenActivated(disposables =>
{
this.Events()
.DoubleTapped
.Do(args => ViewModel.Provider.SelectedFile = ViewModel)
.Select(args => Unit.Default)
.InvokeCommand(this, x => x.ViewModel.Provider.Open)
.DisposeWith(disposables);
this.ContextMenu
.Events()
.MenuOpened
.Subscribe(args => ViewModel.Provider.SelectedFile = ViewModel)
.DisposeWith(disposables);
});
}
}

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

@ -4,24 +4,23 @@ using Avalonia.ReactiveUI;
using Camelotia.Presentation.Interfaces;
using ReactiveUI;
namespace Camelotia.Presentation.Avalonia.Views
{
public sealed partial class FolderView : ReactiveUserControl<IFolderViewModel>
{
public FolderView()
{
InitializeComponent();
this.WhenActivated(disposables =>
{
this.WhenAnyValue(x => x.TopLevelMenu.IsSubMenuOpen)
.BindTo(this, x => x.ArrowDown.IsVisible)
.DisposeWith(disposables);
namespace Camelotia.Presentation.Avalonia.Views;
this.WhenAnyValue(x => x.TopLevelMenu.IsSubMenuOpen)
.Select(menuOpen => !menuOpen)
.BindTo(this, x => x.ArrowRight.IsVisible)
.DisposeWith(disposables);
});
}
public sealed partial class FolderView : ReactiveUserControl<IFolderViewModel>
{
public FolderView()
{
InitializeComponent();
this.WhenActivated(disposables =>
{
this.WhenAnyValue(x => x.TopLevelMenu.IsSubMenuOpen)
.BindTo(this, x => x.ArrowDown.IsVisible)
.DisposeWith(disposables);
this.WhenAnyValue(x => x.TopLevelMenu.IsSubMenuOpen)
.Select(menuOpen => !menuOpen)
.BindTo(this, x => x.ArrowRight.IsVisible)
.DisposeWith(disposables);
});
}
}
}

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

@ -4,26 +4,25 @@ using Camelotia.Presentation.Interfaces;
using ReactiveUI;
using ReactiveUI.Validation.Extensions;
namespace Camelotia.Presentation.Avalonia.Views
namespace Camelotia.Presentation.Avalonia.Views;
public sealed partial class HostAuthView : ReactiveUserControl<IHostAuthViewModel>
{
public sealed partial class HostAuthView : ReactiveUserControl<IHostAuthViewModel>
public HostAuthView()
{
public HostAuthView()
InitializeComponent();
this.WhenActivated(disposables =>
{
InitializeComponent();
this.WhenActivated(disposables =>
{
this.BindValidation(ViewModel, x => x.Address, x => x.AddressValidation.Text)
.DisposeWith(disposables);
this.BindValidation(ViewModel, x => x.Port, x => x.PortValidation.Text)
.DisposeWith(disposables);
this.BindValidation(ViewModel, x => x.Username, x => x.UsernameValidation.Text)
.DisposeWith(disposables);
this.BindValidation(ViewModel, x => x.Password, x => x.PasswordValidation.Text)
.DisposeWith(disposables);
this.BindValidation(ViewModel, x => x.FormValidation.Text)
.DisposeWith(disposables);
});
}
this.BindValidation(ViewModel, x => x.Address, x => x.AddressValidation.Text)
.DisposeWith(disposables);
this.BindValidation(ViewModel, x => x.Port, x => x.PortValidation.Text)
.DisposeWith(disposables);
this.BindValidation(ViewModel, x => x.Username, x => x.UsernameValidation.Text)
.DisposeWith(disposables);
this.BindValidation(ViewModel, x => x.Password, x => x.PasswordValidation.Text)
.DisposeWith(disposables);
this.BindValidation(ViewModel, x => x.FormValidation.Text)
.DisposeWith(disposables);
});
}
}
}

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

@ -2,14 +2,13 @@ using Avalonia.ReactiveUI;
using Camelotia.Presentation.Interfaces;
using ReactiveUI;
namespace Camelotia.Presentation.Avalonia.Views
namespace Camelotia.Presentation.Avalonia.Views;
public sealed partial class MainView : ReactiveUserControl<IMainViewModel>
{
public sealed partial class MainView : ReactiveUserControl<IMainViewModel>
public MainView()
{
public MainView()
{
InitializeComponent();
this.WhenActivated(disposables => { });
}
InitializeComponent();
this.WhenActivated(disposables => { });
}
}
}

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

@ -2,14 +2,13 @@ using Avalonia.ReactiveUI;
using Camelotia.Presentation.Interfaces;
using ReactiveUI;
namespace Camelotia.Presentation.Avalonia.Views
namespace Camelotia.Presentation.Avalonia.Views;
public sealed partial class OAuthView : ReactiveUserControl<IOAuthViewModel>
{
public sealed partial class OAuthView : ReactiveUserControl<IOAuthViewModel>
public OAuthView()
{
public OAuthView()
{
InitializeComponent();
this.WhenActivated(disposables => { });
}
InitializeComponent();
this.WhenActivated(disposables => { });
}
}
}

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

@ -2,14 +2,13 @@ using Avalonia.ReactiveUI;
using Camelotia.Presentation.Interfaces;
using ReactiveUI;
namespace Camelotia.Presentation.Avalonia.Views
namespace Camelotia.Presentation.Avalonia.Views;
public sealed partial class ProviderView : ReactiveUserControl<ICloudViewModel>
{
public sealed partial class ProviderView : ReactiveUserControl<ICloudViewModel>
public ProviderView()
{
public ProviderView()
{
InitializeComponent();
this.WhenActivated(disposables => { });
}
InitializeComponent();
this.WhenActivated(disposables => { });
}
}
}

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

@ -2,14 +2,13 @@ using Avalonia.ReactiveUI;
using Camelotia.Presentation.Interfaces;
using ReactiveUI;
namespace Camelotia.Presentation.Avalonia.Views
namespace Camelotia.Presentation.Avalonia.Views;
public sealed partial class RenameFileView : ReactiveUserControl<IRenameFileViewModel>
{
public sealed partial class RenameFileView : ReactiveUserControl<IRenameFileViewModel>
public RenameFileView()
{
public RenameFileView()
{
InitializeComponent();
this.WhenActivated(disposables => { });
}
InitializeComponent();
this.WhenActivated(disposables => { });
}
}
}

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

@ -9,42 +9,41 @@ using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Navigation;
namespace Camelotia.Presentation.Uwp
namespace Camelotia.Presentation.Uwp;
public sealed partial class App : Application
{
public sealed partial class App : Application
public App() => InitializeComponent();
protected override async void OnLaunched(LaunchActivatedEventArgs e)
{
public App() => InitializeComponent();
var stateFile = await ApplicationData
.Current.LocalFolder
.CreateFileAsync("state.json", CreationCollisionOption.OpenIfExists);
protected override async void OnLaunched(LaunchActivatedEventArgs e)
var autoSuspendHelper = new AutoSuspendHelper(this);
RxApp.MainThreadScheduler = new SingleWindowDispatcherScheduler();
RxApp.SuspensionHost.CreateNewAppState = () => new MainState();
RxApp.SuspensionHost.SetupDefaultSuspendResume(new NewtonsoftJsonSuspensionDriver(stateFile.Path));
autoSuspendHelper.OnLaunched(e);
if (!(Window.Current.Content is Frame rootFrame))
{
var stateFile = await ApplicationData
.Current.LocalFolder
.CreateFileAsync("state.json", CreationCollisionOption.OpenIfExists);
var autoSuspendHelper = new AutoSuspendHelper(this);
RxApp.MainThreadScheduler = new SingleWindowDispatcherScheduler();
RxApp.SuspensionHost.CreateNewAppState = () => new MainState();
RxApp.SuspensionHost.SetupDefaultSuspendResume(new NewtonsoftJsonSuspensionDriver(stateFile.Path));
autoSuspendHelper.OnLaunched(e);
if (!(Window.Current.Content is Frame rootFrame))
{
rootFrame = new Frame();
rootFrame.NavigationFailed += OnNavigationFailed;
Window.Current.Content = rootFrame;
}
if (e.PrelaunchActivated == false)
{
if (rootFrame.Content == null)
rootFrame.Navigate(typeof(MainView), e.Arguments);
Window.Current.Activate();
}
rootFrame = new Frame();
rootFrame.NavigationFailed += OnNavigationFailed;
Window.Current.Content = rootFrame;
}
private void OnNavigationFailed(object sender, NavigationFailedEventArgs e)
if (e.PrelaunchActivated == false)
{
throw new Exception("Failed to load Page " + e.SourcePageType.FullName);
if (rootFrame.Content == null)
rootFrame.Navigate(typeof(MainView), e.Arguments);
Window.Current.Activate();
}
}
private void OnNavigationFailed(object sender, NavigationFailedEventArgs e)
{
throw new Exception("Failed to load Page " + e.SourcePageType.FullName);
}
}

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

@ -6,40 +6,39 @@ using Camelotia.Services;
using Camelotia.Services.Models;
using ReactiveUI;
namespace Camelotia.Presentation.Uwp
namespace Camelotia.Presentation.Uwp;
public sealed class Bootstrapper
{
public sealed class Bootstrapper
public static IMainViewModel BuildMainViewModel()
{
public static IMainViewModel BuildMainViewModel()
{
var mainState = RxApp.SuspensionHost.GetAppState<MainState>();
return new MainViewModel(
mainState,
new CloudFactory(
mainState.CloudConfiguration,
new UniversalWindowsYandexAuthenticator(),
Akavache.BlobCache.UserAccount,
new[]
{
CloudType.Yandex,
CloudType.VkDocs,
CloudType.Ftp,
CloudType.Sftp,
CloudType.GitHub
}),
(state, provider) => new CloudViewModel(
state,
owner => new CreateFolderViewModel(state.CreateFolderState, owner, provider),
owner => new RenameFileViewModel(state.RenameFileState, owner, provider),
(file, owner) => new FileViewModel(owner, file),
(folder, owner) => new FolderViewModel(owner, folder),
new AuthViewModel(
new DirectAuthViewModel(state.AuthState.DirectAuthState, provider),
new HostAuthViewModel(state.AuthState.HostAuthState, provider),
new OAuthViewModel(provider),
provider),
new UniversalWindowsFileManager(),
provider));
}
var mainState = RxApp.SuspensionHost.GetAppState<MainState>();
return new MainViewModel(
mainState,
new CloudFactory(
mainState.CloudConfiguration,
new UniversalWindowsYandexAuthenticator(),
Akavache.BlobCache.UserAccount,
new[]
{
CloudType.Yandex,
CloudType.VkDocs,
CloudType.Ftp,
CloudType.Sftp,
CloudType.GitHub
}),
(state, provider) => new CloudViewModel(
state,
owner => new CreateFolderViewModel(state.CreateFolderState, owner, provider),
owner => new RenameFileViewModel(state.RenameFileState, owner, provider),
(file, owner) => new FileViewModel(owner, file),
(folder, owner) => new FolderViewModel(owner, folder),
new AuthViewModel(
new DirectAuthViewModel(state.AuthState.DirectAuthState, provider),
new HostAuthViewModel(state.AuthState.HostAuthState, provider),
new OAuthViewModel(provider),
provider),
new UniversalWindowsFileManager(),
provider));
}
}

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

@ -11,12 +11,13 @@
<AssemblyName>Camelotia.Presentation.Uwp</AssemblyName>
<DefaultLanguage>en-us</DefaultLanguage>
<TargetPlatformIdentifier>UAP</TargetPlatformIdentifier>
<TargetPlatformVersion Condition=" '$(TargetPlatformVersion)' == '' ">10.0.18362.0</TargetPlatformVersion>
<TargetPlatformVersion Condition=" '$(TargetPlatformVersion)' == '' ">10.0.19041.0</TargetPlatformVersion>
<TargetPlatformMinVersion>10.0.18362.0</TargetPlatformMinVersion>
<MinimumVisualStudioVersion>14</MinimumVisualStudioVersion>
<FileAlignment>512</FileAlignment>
<ProjectTypeGuids>{A5A43C5B-DE2A-4C0C-9213-0A381AF9435A};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<WindowsXamlEnableOverview>true</WindowsXamlEnableOverview>
<LangVersion>latest</LangVersion>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x86'">
<DebugSymbols>true</DebugSymbols>

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

@ -6,30 +6,29 @@ using Camelotia.Services.Interfaces;
using Windows.Storage;
using Windows.Storage.Pickers;
namespace Camelotia.Presentation.Uwp.Services
{
public sealed class UniversalWindowsFileManager : IFileManager
{
public async Task<(string Name, Stream Stream)> OpenRead()
{
var picker = new FileOpenPicker();
picker.FileTypeFilter.Add("*");
var file = await picker.PickSingleFileAsync();
if (file == null) return (null, null);
var stream = await file.OpenStreamForReadAsync().ConfigureAwait(false);
return (file.Name, stream);
}
namespace Camelotia.Presentation.Uwp.Services;
public async Task<Stream> OpenWrite(string name)
{
var ext = Path.GetExtension(name);
var picker = new FileSavePicker { SuggestedFileName = name };
picker.FileTypeChoices.Add(ext, new List<string> { ext });
var file = await picker.PickSaveFileAsync();
if (file == null) return null;
await FileIO.WriteTextAsync(file, string.Empty);
var stream = await file.OpenStreamForWriteAsync().ConfigureAwait(false);
return stream;
}
public sealed class UniversalWindowsFileManager : IFileManager
{
public async Task<(string Name, Stream Stream)> OpenRead()
{
var picker = new FileOpenPicker();
picker.FileTypeFilter.Add("*");
var file = await picker.PickSingleFileAsync();
if (file == null) return (null, null);
var stream = await file.OpenStreamForReadAsync().ConfigureAwait(false);
return (file.Name, stream);
}
public async Task<Stream> OpenWrite(string name)
{
var ext = Path.GetExtension(name);
var picker = new FileSavePicker { SuggestedFileName = name };
picker.FileTypeChoices.Add(ext, new List<string> { ext });
var file = await picker.PickSaveFileAsync();
if (file == null) return null;
await FileIO.WriteTextAsync(file, string.Empty);
var stream = await file.OpenStreamForWriteAsync().ConfigureAwait(false);
return stream;
}
}

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

@ -6,70 +6,69 @@ using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media;
namespace Camelotia.Presentation.Uwp.Services
namespace Camelotia.Presentation.Uwp.Services;
public sealed class UniversalWindowsYandexAuthenticator : IAuthenticator
{
public sealed class UniversalWindowsYandexAuthenticator : IAuthenticator
private TaskCompletionSource<string> _taskCompletionSource;
public GrantType GrantType => GrantType.AccessToken;
public Task<string> ReceiveCode(Uri uri, Uri returnUri) => throw new PlatformNotSupportedException();
public async Task<string> ReceiveToken(Uri uri)
{
private TaskCompletionSource<string> _taskCompletionSource;
public GrantType GrantType => GrantType.AccessToken;
public Task<string> ReceiveCode(Uri uri, Uri returnUri) => throw new PlatformNotSupportedException();
public async Task<string> ReceiveToken(Uri uri)
_taskCompletionSource = new TaskCompletionSource<string>();
await WebView.ClearTemporaryWebDataAsync();
await Window.Current.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
_taskCompletionSource = new TaskCompletionSource<string>();
await WebView.ClearTemporaryWebDataAsync();
await Window.Current.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
var root = Window.Current.Content;
var web = FindControl<WebView>(root);
if (web == null) throw new Exception("WebView named AuthenticationWebView wasn't found.");
var root = Window.Current.Content;
var web = FindControl<WebView>(root);
if (web == null) throw new Exception("WebView named AuthenticationWebView wasn't found.");
web.Visibility = Visibility.Visible;
web.NavigationCompleted += OnNavigationCompleted;
web.Navigate(uri);
});
web.Visibility = Visibility.Visible;
web.NavigationCompleted += OnNavigationCompleted;
web.Navigate(uri);
});
return await _taskCompletionSource.Task.ConfigureAwait(false);
return await _taskCompletionSource.Task.ConfigureAwait(false);
}
private static TControl FindControl<TControl>(UIElement parent)
where TControl : FrameworkElement
{
var targetType = typeof(TControl);
if (parent == null) return null;
if (parent.GetType() == targetType)
return (TControl)parent;
var result = default(TControl);
var count = VisualTreeHelper.GetChildrenCount(parent);
for (var i = 0; i < count; i++)
{
var child = (UIElement)VisualTreeHelper.GetChild(parent, i);
var rec = FindControl<TControl>(child);
if (rec == null) continue;
result = rec;
break;
}
private static TControl FindControl<TControl>(UIElement parent)
where TControl : FrameworkElement
{
var targetType = typeof(TControl);
if (parent == null) return null;
if (parent.GetType() == targetType)
return (TControl)parent;
return result;
}
var result = default(TControl);
var count = VisualTreeHelper.GetChildrenCount(parent);
for (var i = 0; i < count; i++)
{
var child = (UIElement)VisualTreeHelper.GetChild(parent, i);
var rec = FindControl<TControl>(child);
if (rec == null) continue;
result = rec;
break;
}
private void OnNavigationCompleted(WebView sender, WebViewNavigationCompletedEventArgs args)
{
var currentUriString = args.Uri.ToString();
if (!currentUriString.Contains("#"))
return;
return result;
}
sender.NavigationCompleted -= OnNavigationCompleted;
var token = currentUriString
.Split('#')[1]
.Split('&')[0]
.Split('=')[1];
private void OnNavigationCompleted(WebView sender, WebViewNavigationCompletedEventArgs args)
{
var currentUriString = args.Uri.ToString();
if (!currentUriString.Contains("#"))
return;
sender.NavigationCompleted -= OnNavigationCompleted;
var token = currentUriString
.Split('#')[1]
.Split('&')[0]
.Split('=')[1];
_taskCompletionSource.SetResult(token);
sender.Visibility = Visibility.Collapsed;
}
_taskCompletionSource.SetResult(token);
sender.Visibility = Visibility.Collapsed;
}
}

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

@ -6,52 +6,51 @@ using ReactiveUI;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
namespace Camelotia.Presentation.Uwp.Views
namespace Camelotia.Presentation.Uwp.Views;
public sealed partial class AuthView : UserControl, IViewFor<IAuthViewModel>
{
public sealed partial class AuthView : UserControl, IViewFor<IAuthViewModel>
public static readonly DependencyProperty ViewModelProperty = DependencyProperty
.Register(nameof(ViewModel), typeof(IAuthViewModel), typeof(AuthView), null);
public AuthView()
{
public static readonly DependencyProperty ViewModelProperty = DependencyProperty
.Register(nameof(ViewModel), typeof(IAuthViewModel), typeof(AuthView), null);
public AuthView()
InitializeComponent();
this.WhenActivated(disposables =>
{
InitializeComponent();
this.WhenActivated(disposables =>
{
this.WhenAnyValue(x => x.ViewModel.SupportsDirectAuth)
.Where(supports => supports)
.Subscribe(supports => AuthorizationPivot.SelectedIndex = 0)
.DisposeWith(disposables);
this.WhenAnyValue(x => x.ViewModel.SupportsDirectAuth)
.Where(supports => supports)
.Subscribe(supports => AuthorizationPivot.SelectedIndex = 0)
.DisposeWith(disposables);
this.WhenAnyValue(x => x.ViewModel.SupportsOAuth)
.Where(supports => supports)
.Subscribe(supports => AuthorizationPivot.SelectedIndex = 1)
.DisposeWith(disposables);
this.WhenAnyValue(x => x.ViewModel.SupportsOAuth)
.Where(supports => supports)
.Subscribe(supports => AuthorizationPivot.SelectedIndex = 1)
.DisposeWith(disposables);
this.WhenAnyValue(x => x.ViewModel.SupportsHostAuth)
.Where(supports => supports)
.Subscribe(supports => AuthorizationPivot.SelectedIndex = 2)
.DisposeWith(disposables);
this.WhenAnyValue(x => x.ViewModel.SupportsHostAuth)
.Where(supports => supports)
.Subscribe(supports => AuthorizationPivot.SelectedIndex = 2)
.DisposeWith(disposables);
this.WhenAnyValue(
this.WhenAnyValue(
x => x.ViewModel.SupportsDirectAuth,
x => x.ViewModel.SupportsHostAuth,
x => x.ViewModel.SupportsOAuth)
.Subscribe(x => AuthorizationPivot.Visibility = Visibility.Visible)
.DisposeWith(disposables);
});
}
.Subscribe(x => AuthorizationPivot.Visibility = Visibility.Visible)
.DisposeWith(disposables);
});
}
public IAuthViewModel ViewModel
{
get => (IAuthViewModel)GetValue(ViewModelProperty);
set => SetValue(ViewModelProperty, value);
}
public IAuthViewModel ViewModel
{
get => (IAuthViewModel)GetValue(ViewModelProperty);
set => SetValue(ViewModelProperty, value);
}
object IViewFor.ViewModel
{
get => ViewModel;
set => ViewModel = (IAuthViewModel)value;
}
object IViewFor.ViewModel
{
get => ViewModel;
set => ViewModel = (IAuthViewModel)value;
}
}

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

@ -3,29 +3,28 @@ using ReactiveUI;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
namespace Camelotia.Presentation.Uwp.Views
namespace Camelotia.Presentation.Uwp.Views;
public sealed partial class CloudView : UserControl, IViewFor<ICloudViewModel>
{
public sealed partial class CloudView : UserControl, IViewFor<ICloudViewModel>
public static readonly DependencyProperty ViewModelProperty = DependencyProperty
.Register(nameof(ViewModel), typeof(ICloudViewModel), typeof(CloudView), null);
public CloudView()
{
public static readonly DependencyProperty ViewModelProperty = DependencyProperty
.Register(nameof(ViewModel), typeof(ICloudViewModel), typeof(CloudView), null);
InitializeComponent();
this.WhenActivated(disposables => { });
}
public CloudView()
{
InitializeComponent();
this.WhenActivated(disposables => { });
}
public ICloudViewModel ViewModel
{
get => (ICloudViewModel)GetValue(ViewModelProperty);
set => SetValue(ViewModelProperty, value);
}
public ICloudViewModel ViewModel
{
get => (ICloudViewModel)GetValue(ViewModelProperty);
set => SetValue(ViewModelProperty, value);
}
object IViewFor.ViewModel
{
get => ViewModel;
set => ViewModel = (ICloudViewModel)value;
}
object IViewFor.ViewModel
{
get => ViewModel;
set => ViewModel = (ICloudViewModel)value;
}
}

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

@ -7,42 +7,41 @@ using ReactiveUI.Validation.Formatters;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
namespace Camelotia.Presentation.Uwp.Views
namespace Camelotia.Presentation.Uwp.Views;
public sealed partial class CreateFolderView : UserControl, IViewFor<ICreateFolderViewModel>
{
public sealed partial class CreateFolderView : UserControl, IViewFor<ICreateFolderViewModel>
public static readonly DependencyProperty ViewModelProperty = DependencyProperty
.Register(nameof(ViewModel), typeof(ICreateFolderViewModel), typeof(CreateFolderView), null);
public CreateFolderView()
{
public static readonly DependencyProperty ViewModelProperty = DependencyProperty
.Register(nameof(ViewModel), typeof(ICreateFolderViewModel), typeof(CreateFolderView), null);
public CreateFolderView()
InitializeComponent();
this.WhenActivated(disposables =>
{
InitializeComponent();
this.WhenActivated(disposables =>
{
this.BindValidation(ViewModel, x => x.Name, x => x.FolderNameErrorLabel.Text)
.DisposeWith(disposables);
this.BindValidation(ViewModel, x => x.FormErrorLabel.Text, new SingleLineFormatter(Environment.NewLine))
.DisposeWith(disposables);
this.BindValidation(ViewModel, x => x.Name, x => x.FolderNameErrorLabel.Text)
.DisposeWith(disposables);
this.BindValidation(ViewModel, x => x.FormErrorLabel.Text, new SingleLineFormatter(Environment.NewLine))
.DisposeWith(disposables);
this.WhenAnyValue(x => x.FolderNameErrorLabel.Text, text => !string.IsNullOrWhiteSpace(text))
.BindTo(this, x => x.FolderNameErrorLabel.Visibility)
.DisposeWith(disposables);
this.WhenAnyValue(x => x.FormErrorLabel.Text, text => !string.IsNullOrWhiteSpace(text))
.BindTo(this, x => x.FormErrorLabel.Visibility)
.DisposeWith(disposables);
});
}
this.WhenAnyValue(x => x.FolderNameErrorLabel.Text, text => !string.IsNullOrWhiteSpace(text))
.BindTo(this, x => x.FolderNameErrorLabel.Visibility)
.DisposeWith(disposables);
this.WhenAnyValue(x => x.FormErrorLabel.Text, text => !string.IsNullOrWhiteSpace(text))
.BindTo(this, x => x.FormErrorLabel.Visibility)
.DisposeWith(disposables);
});
}
public ICreateFolderViewModel ViewModel
{
get => (ICreateFolderViewModel)GetValue(ViewModelProperty);
set => SetValue(ViewModelProperty, value);
}
public ICreateFolderViewModel ViewModel
{
get => (ICreateFolderViewModel)GetValue(ViewModelProperty);
set => SetValue(ViewModelProperty, value);
}
object IViewFor.ViewModel
{
get => ViewModel;
set => ViewModel = (ICreateFolderViewModel)value;
}
object IViewFor.ViewModel
{
get => ViewModel;
set => ViewModel = (ICreateFolderViewModel)value;
}
}

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

@ -7,47 +7,46 @@ using ReactiveUI.Validation.Formatters;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
namespace Camelotia.Presentation.Uwp.Views
namespace Camelotia.Presentation.Uwp.Views;
public sealed partial class DirectAuthView : UserControl, IViewFor<IDirectAuthViewModel>
{
public sealed partial class DirectAuthView : UserControl, IViewFor<IDirectAuthViewModel>
public static readonly DependencyProperty ViewModelProperty = DependencyProperty
.Register(nameof(ViewModel), typeof(IDirectAuthViewModel), typeof(DirectAuthView), null);
public DirectAuthView()
{
public static readonly DependencyProperty ViewModelProperty = DependencyProperty
.Register(nameof(ViewModel), typeof(IDirectAuthViewModel), typeof(DirectAuthView), null);
public DirectAuthView()
InitializeComponent();
this.WhenActivated(disposables =>
{
InitializeComponent();
this.WhenActivated(disposables =>
{
this.BindValidation(ViewModel, x => x.Username, x => x.UserNameErrorLabel.Text)
.DisposeWith(disposables);
this.BindValidation(ViewModel, x => x.Password, x => x.PasswordErrorLabel.Text)
.DisposeWith(disposables);
this.BindValidation(ViewModel, x => x.FormErrorLabel.Text, new SingleLineFormatter(Environment.NewLine))
.DisposeWith(disposables);
this.BindValidation(ViewModel, x => x.Username, x => x.UserNameErrorLabel.Text)
.DisposeWith(disposables);
this.BindValidation(ViewModel, x => x.Password, x => x.PasswordErrorLabel.Text)
.DisposeWith(disposables);
this.BindValidation(ViewModel, x => x.FormErrorLabel.Text, new SingleLineFormatter(Environment.NewLine))
.DisposeWith(disposables);
this.WhenAnyValue(x => x.UserNameErrorLabel.Text, text => !string.IsNullOrWhiteSpace(text))
.BindTo(this, x => x.UserNameErrorLabel.Visibility)
.DisposeWith(disposables);
this.WhenAnyValue(x => x.PasswordErrorLabel.Text, text => !string.IsNullOrWhiteSpace(text))
.BindTo(this, x => x.PasswordErrorLabel.Visibility)
.DisposeWith(disposables);
this.WhenAnyValue(x => x.FormErrorLabel.Text, text => !string.IsNullOrWhiteSpace(text))
.BindTo(this, x => x.FormErrorLabel.Visibility)
.DisposeWith(disposables);
});
}
this.WhenAnyValue(x => x.UserNameErrorLabel.Text, text => !string.IsNullOrWhiteSpace(text))
.BindTo(this, x => x.UserNameErrorLabel.Visibility)
.DisposeWith(disposables);
this.WhenAnyValue(x => x.PasswordErrorLabel.Text, text => !string.IsNullOrWhiteSpace(text))
.BindTo(this, x => x.PasswordErrorLabel.Visibility)
.DisposeWith(disposables);
this.WhenAnyValue(x => x.FormErrorLabel.Text, text => !string.IsNullOrWhiteSpace(text))
.BindTo(this, x => x.FormErrorLabel.Visibility)
.DisposeWith(disposables);
});
}
public IDirectAuthViewModel ViewModel
{
get => (IDirectAuthViewModel)GetValue(ViewModelProperty);
set => SetValue(ViewModelProperty, value);
}
public IDirectAuthViewModel ViewModel
{
get => (IDirectAuthViewModel)GetValue(ViewModelProperty);
set => SetValue(ViewModelProperty, value);
}
object IViewFor.ViewModel
{
get => ViewModel;
set => ViewModel = (IDirectAuthViewModel)value;
}
object IViewFor.ViewModel
{
get => ViewModel;
set => ViewModel = (IDirectAuthViewModel)value;
}
}

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

@ -8,45 +8,43 @@ using ReactiveUI;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Input;
namespace Camelotia.Presentation.Uwp.Views
namespace Camelotia.Presentation.Uwp.Views;
public sealed partial class FileView : UserControl, IViewFor<IFileViewModel>
{
public sealed partial class FileView : UserControl, IViewFor<IFileViewModel>
public static readonly DependencyProperty ViewModelProperty = DependencyProperty
.Register(nameof(ViewModel), typeof(IFileViewModel), typeof(FileView), null);
public FileView()
{
public static readonly DependencyProperty ViewModelProperty = DependencyProperty
.Register(nameof(ViewModel), typeof(IFileViewModel), typeof(FileView), null);
public FileView()
InitializeComponent();
this.WhenActivated(disposables =>
{
InitializeComponent();
this.WhenActivated(disposables =>
{
this.Events()
.RightTapped
.Select(args => this)
.Do(sender => sender.ViewModel.Provider.SelectedFile = sender.ViewModel)
.Subscribe(FlyoutBase.ShowAttachedFlyout)
.DisposeWith(disposables);
this.Events()
.RightTapped
.Select(args => this)
.Do(sender => sender.ViewModel.Provider.SelectedFile = sender.ViewModel)
.Subscribe(FlyoutBase.ShowAttachedFlyout)
.DisposeWith(disposables);
this.Events()
.DoubleTapped
.Select(args => Unit.Default)
.InvokeCommand(this, x => x.ViewModel.Provider.Open)
.DisposeWith(disposables);
});
}
this.Events()
.DoubleTapped
.Select(args => Unit.Default)
.InvokeCommand(this, x => x.ViewModel.Provider.Open)
.DisposeWith(disposables);
});
}
public IFileViewModel ViewModel
{
get => (IFileViewModel)GetValue(ViewModelProperty);
set => SetValue(ViewModelProperty, value);
}
public IFileViewModel ViewModel
{
get => (IFileViewModel)GetValue(ViewModelProperty);
set => SetValue(ViewModelProperty, value);
}
object IViewFor.ViewModel
{
get => ViewModel;
set => ViewModel = (IFileViewModel)value;
}
object IViewFor.ViewModel
{
get => ViewModel;
set => ViewModel = (IFileViewModel)value;
}
}

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

@ -7,57 +7,56 @@ using ReactiveUI.Validation.Formatters;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
namespace Camelotia.Presentation.Uwp.Views
namespace Camelotia.Presentation.Uwp.Views;
public sealed partial class HostAuthView : UserControl, IViewFor<IHostAuthViewModel>
{
public sealed partial class HostAuthView : UserControl, IViewFor<IHostAuthViewModel>
public static readonly DependencyProperty ViewModelProperty = DependencyProperty
.Register(nameof(ViewModel), typeof(IHostAuthViewModel), typeof(DirectAuthView), null);
public HostAuthView()
{
public static readonly DependencyProperty ViewModelProperty = DependencyProperty
.Register(nameof(ViewModel), typeof(IHostAuthViewModel), typeof(DirectAuthView), null);
public HostAuthView()
InitializeComponent();
this.WhenActivated(disposables =>
{
InitializeComponent();
this.WhenActivated(disposables =>
{
this.BindValidation(ViewModel, x => x.Address, x => x.HostNameErrorLabel.Text)
.DisposeWith(disposables);
this.BindValidation(ViewModel, x => x.Port, x => x.PortErrorLabel.Text)
.DisposeWith(disposables);
this.BindValidation(ViewModel, x => x.Username, x => x.UserNameErrorLabel.Text)
.DisposeWith(disposables);
this.BindValidation(ViewModel, x => x.Password, x => x.PasswordErrorLabel.Text)
.DisposeWith(disposables);
this.BindValidation(ViewModel, x => x.FormErrorLabel.Text, new SingleLineFormatter(Environment.NewLine))
.DisposeWith(disposables);
this.BindValidation(ViewModel, x => x.Address, x => x.HostNameErrorLabel.Text)
.DisposeWith(disposables);
this.BindValidation(ViewModel, x => x.Port, x => x.PortErrorLabel.Text)
.DisposeWith(disposables);
this.BindValidation(ViewModel, x => x.Username, x => x.UserNameErrorLabel.Text)
.DisposeWith(disposables);
this.BindValidation(ViewModel, x => x.Password, x => x.PasswordErrorLabel.Text)
.DisposeWith(disposables);
this.BindValidation(ViewModel, x => x.FormErrorLabel.Text, new SingleLineFormatter(Environment.NewLine))
.DisposeWith(disposables);
this.WhenAnyValue(x => x.HostNameErrorLabel.Text, text => !string.IsNullOrWhiteSpace(text))
.BindTo(this, x => x.HostNameErrorLabel.Visibility)
.DisposeWith(disposables);
this.WhenAnyValue(x => x.PortErrorLabel.Text, text => !string.IsNullOrWhiteSpace(text))
.BindTo(this, x => x.PortErrorLabel.Visibility)
.DisposeWith(disposables);
this.WhenAnyValue(x => x.UserNameErrorLabel.Text, text => !string.IsNullOrWhiteSpace(text))
.BindTo(this, x => x.UserNameErrorLabel.Visibility)
.DisposeWith(disposables);
this.WhenAnyValue(x => x.PasswordErrorLabel.Text, text => !string.IsNullOrWhiteSpace(text))
.BindTo(this, x => x.PasswordErrorLabel.Visibility)
.DisposeWith(disposables);
this.WhenAnyValue(x => x.FormErrorLabel.Text, text => !string.IsNullOrWhiteSpace(text))
.BindTo(this, x => x.FormErrorLabel.Visibility)
.DisposeWith(disposables);
});
}
this.WhenAnyValue(x => x.HostNameErrorLabel.Text, text => !string.IsNullOrWhiteSpace(text))
.BindTo(this, x => x.HostNameErrorLabel.Visibility)
.DisposeWith(disposables);
this.WhenAnyValue(x => x.PortErrorLabel.Text, text => !string.IsNullOrWhiteSpace(text))
.BindTo(this, x => x.PortErrorLabel.Visibility)
.DisposeWith(disposables);
this.WhenAnyValue(x => x.UserNameErrorLabel.Text, text => !string.IsNullOrWhiteSpace(text))
.BindTo(this, x => x.UserNameErrorLabel.Visibility)
.DisposeWith(disposables);
this.WhenAnyValue(x => x.PasswordErrorLabel.Text, text => !string.IsNullOrWhiteSpace(text))
.BindTo(this, x => x.PasswordErrorLabel.Visibility)
.DisposeWith(disposables);
this.WhenAnyValue(x => x.FormErrorLabel.Text, text => !string.IsNullOrWhiteSpace(text))
.BindTo(this, x => x.FormErrorLabel.Visibility)
.DisposeWith(disposables);
});
}
public IHostAuthViewModel ViewModel
{
get => (IHostAuthViewModel)GetValue(ViewModelProperty);
set => SetValue(ViewModelProperty, value);
}
public IHostAuthViewModel ViewModel
{
get => (IHostAuthViewModel)GetValue(ViewModelProperty);
set => SetValue(ViewModelProperty, value);
}
object IViewFor.ViewModel
{
get => ViewModel;
set => ViewModel = (IHostAuthViewModel)value;
}
object IViewFor.ViewModel
{
get => ViewModel;
set => ViewModel = (IHostAuthViewModel)value;
}
}

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

@ -3,29 +3,28 @@ using ReactiveUI;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
namespace Camelotia.Presentation.Uwp.Views
namespace Camelotia.Presentation.Uwp.Views;
public sealed partial class OAuthView : UserControl, IViewFor<IOAuthViewModel>
{
public sealed partial class OAuthView : UserControl, IViewFor<IOAuthViewModel>
public static readonly DependencyProperty ViewModelProperty = DependencyProperty
.Register(nameof(ViewModel), typeof(IOAuthViewModel), typeof(OAuthView), null);
public OAuthView()
{
public static readonly DependencyProperty ViewModelProperty = DependencyProperty
.Register(nameof(ViewModel), typeof(IOAuthViewModel), typeof(OAuthView), null);
InitializeComponent();
this.WhenActivated(disposables => { });
}
public OAuthView()
{
InitializeComponent();
this.WhenActivated(disposables => { });
}
public IOAuthViewModel ViewModel
{
get => (IOAuthViewModel)GetValue(ViewModelProperty);
set => SetValue(ViewModelProperty, value);
}
public IOAuthViewModel ViewModel
{
get => (IOAuthViewModel)GetValue(ViewModelProperty);
set => SetValue(ViewModelProperty, value);
}
object IViewFor.ViewModel
{
get => ViewModel;
set => ViewModel = (IOAuthViewModel)value;
}
object IViewFor.ViewModel
{
get => ViewModel;
set => ViewModel = (IOAuthViewModel)value;
}
}

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

@ -7,42 +7,41 @@ using ReactiveUI.Validation.Formatters;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
namespace Camelotia.Presentation.Uwp.Views
namespace Camelotia.Presentation.Uwp.Views;
public sealed partial class RenameFileView : UserControl, IViewFor<IRenameFileViewModel>
{
public sealed partial class RenameFileView : UserControl, IViewFor<IRenameFileViewModel>
public static readonly DependencyProperty ViewModelProperty = DependencyProperty
.Register(nameof(ViewModel), typeof(IRenameFileViewModel), typeof(RenameFileView), null);
public RenameFileView()
{
public static readonly DependencyProperty ViewModelProperty = DependencyProperty
.Register(nameof(ViewModel), typeof(IRenameFileViewModel), typeof(RenameFileView), null);
public RenameFileView()
InitializeComponent();
this.WhenActivated(disposables =>
{
InitializeComponent();
this.WhenActivated(disposables =>
{
this.BindValidation(ViewModel, x => x.NewName, x => x.FileNameErrorLabel.Text)
.DisposeWith(disposables);
this.BindValidation(ViewModel, x => x.FormErrorLabel.Text, new SingleLineFormatter(Environment.NewLine))
.DisposeWith(disposables);
this.BindValidation(ViewModel, x => x.NewName, x => x.FileNameErrorLabel.Text)
.DisposeWith(disposables);
this.BindValidation(ViewModel, x => x.FormErrorLabel.Text, new SingleLineFormatter(Environment.NewLine))
.DisposeWith(disposables);
this.WhenAnyValue(x => x.FileNameErrorLabel.Text, text => !string.IsNullOrWhiteSpace(text))
.BindTo(this, x => x.FileNameErrorLabel.Visibility)
.DisposeWith(disposables);
this.WhenAnyValue(x => x.FormErrorLabel.Text, text => !string.IsNullOrWhiteSpace(text))
.BindTo(this, x => x.FormErrorLabel.Visibility)
.DisposeWith(disposables);
});
}
this.WhenAnyValue(x => x.FileNameErrorLabel.Text, text => !string.IsNullOrWhiteSpace(text))
.BindTo(this, x => x.FileNameErrorLabel.Visibility)
.DisposeWith(disposables);
this.WhenAnyValue(x => x.FormErrorLabel.Text, text => !string.IsNullOrWhiteSpace(text))
.BindTo(this, x => x.FormErrorLabel.Visibility)
.DisposeWith(disposables);
});
}
public IRenameFileViewModel ViewModel
{
get => (IRenameFileViewModel)GetValue(ViewModelProperty);
set => SetValue(ViewModelProperty, value);
}
public IRenameFileViewModel ViewModel
{
get => (IRenameFileViewModel)GetValue(ViewModelProperty);
set => SetValue(ViewModelProperty, value);
}
object IViewFor.ViewModel
{
get => ViewModel;
set => ViewModel = (IRenameFileViewModel)value;
}
object IViewFor.ViewModel
{
get => ViewModel;
set => ViewModel = (IRenameFileViewModel)value;
}
}

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

@ -6,48 +6,47 @@ using Camelotia.Presentation.Wpf.Services;
using Camelotia.Services;
using ReactiveUI;
namespace Camelotia.Presentation.Wpf
namespace Camelotia.Presentation.Wpf;
public partial class App : Application
{
public partial class App : Application
private readonly AutoSuspendHelper _autoSuspendHelper;
public App()
{
private readonly AutoSuspendHelper _autoSuspendHelper;
InitializeComponent();
_autoSuspendHelper = new AutoSuspendHelper(this);
RxApp.SuspensionHost.CreateNewAppState = () => new MainState();
RxApp.SuspensionHost.SetupDefaultSuspendResume(new NewtonsoftJsonSuspensionDriver("appstate.json"));
}
public App()
{
InitializeComponent();
_autoSuspendHelper = new AutoSuspendHelper(this);
RxApp.SuspensionHost.CreateNewAppState = () => new MainState();
RxApp.SuspensionHost.SetupDefaultSuspendResume(new NewtonsoftJsonSuspensionDriver("appstate.json"));
}
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
Akavache.BlobCache.ApplicationName = "Camelotia";
var mainState = RxApp.SuspensionHost.GetAppState<MainState>();
var mainViewModel = new MainViewModel(
mainState,
new CloudFactory(
mainState.CloudConfiguration,
new WindowsPresentationYandexAuthenticator(),
Akavache.BlobCache.UserAccount),
(state, provider) => new CloudViewModel(
state,
owner => new CreateFolderViewModel(state.CreateFolderState, owner, provider),
owner => new RenameFileViewModel(state.RenameFileState, owner, provider),
(file, owner) => new FileViewModel(owner, file),
(folder, owner) => new FolderViewModel(owner, folder),
new AuthViewModel(
new DirectAuthViewModel(state.AuthState.DirectAuthState, provider),
new HostAuthViewModel(state.AuthState.HostAuthState, provider),
new OAuthViewModel(provider),
provider),
new WindowsPresentationFileManager(),
provider));
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
Akavache.BlobCache.ApplicationName = "Camelotia";
var mainState = RxApp.SuspensionHost.GetAppState<MainState>();
var mainViewModel = new MainViewModel(
mainState,
new CloudFactory(
mainState.CloudConfiguration,
new WindowsPresentationYandexAuthenticator(),
Akavache.BlobCache.UserAccount),
(state, provider) => new CloudViewModel(
state,
owner => new CreateFolderViewModel(state.CreateFolderState, owner, provider),
owner => new RenameFileViewModel(state.RenameFileState, owner, provider),
(file, owner) => new FileViewModel(owner, file),
(folder, owner) => new FolderViewModel(owner, folder),
new AuthViewModel(
new DirectAuthViewModel(state.AuthState.DirectAuthState, provider),
new HostAuthViewModel(state.AuthState.HostAuthState, provider),
new OAuthViewModel(provider),
provider),
new WindowsPresentationFileManager(),
provider));
var window = new MainView { DataContext = mainViewModel };
window.Closed += (sender, e) => Shutdown();
window.Show();
}
var window = new MainView { DataContext = mainViewModel };
window.Closed += (sender, e) => Shutdown();
window.Show();
}
}

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

@ -2,9 +2,11 @@
<PropertyGroup>
<OutputType>WinExe</OutputType>
<StartupObject>Camelotia.Presentation.Wpf.Program</StartupObject>
<TargetFramework>netcoreapp3.1</TargetFramework>
<TargetFramework>net6-windows</TargetFramework>
<IncludePackageReferencesDuringMarkupCompilation>true</IncludePackageReferencesDuringMarkupCompilation>
<UseWpf>true</UseWpf>
<UseWindowsForms>true</UseWindowsForms>
<LangVersion>latest</LangVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="akavache" Version="7.3.1" />

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

@ -6,46 +6,45 @@ using System.Windows.Controls;
using Camelotia.Presentation.Interfaces;
using ReactiveUI;
namespace Camelotia.Presentation.Wpf.Views
namespace Camelotia.Presentation.Wpf.Views;
public partial class AuthView : UserControl, IViewFor<IAuthViewModel>
{
public partial class AuthView : UserControl, IViewFor<IAuthViewModel>
public static readonly DependencyProperty ViewModelProperty = DependencyProperty
.Register(nameof(ViewModel), typeof(IAuthViewModel), typeof(AuthView), null);
public AuthView()
{
public static readonly DependencyProperty ViewModelProperty = DependencyProperty
.Register(nameof(ViewModel), typeof(IAuthViewModel), typeof(AuthView), null);
public AuthView()
InitializeComponent();
DataContextChanged += (sender, args) => ViewModel = DataContext as IAuthViewModel;
this.WhenActivated(disposable =>
{
InitializeComponent();
DataContextChanged += (sender, args) => ViewModel = DataContext as IAuthViewModel;
this.WhenActivated(disposable =>
{
this.WhenAnyValue(x => x.ViewModel.SupportsDirectAuth)
.Where(supports => supports)
.Subscribe(supports => AuthTabs.SelectedIndex = 0)
.DisposeWith(disposable);
this.WhenAnyValue(x => x.ViewModel.SupportsDirectAuth)
.Where(supports => supports)
.Subscribe(supports => AuthTabs.SelectedIndex = 0)
.DisposeWith(disposable);
this.WhenAnyValue(x => x.ViewModel.SupportsHostAuth)
.Where(supports => supports)
.Subscribe(supports => AuthTabs.SelectedIndex = 1)
.DisposeWith(disposable);
this.WhenAnyValue(x => x.ViewModel.SupportsHostAuth)
.Where(supports => supports)
.Subscribe(supports => AuthTabs.SelectedIndex = 1)
.DisposeWith(disposable);
this.WhenAnyValue(x => x.ViewModel.SupportsOAuth)
.Where(supports => supports)
.Subscribe(supports => AuthTabs.SelectedIndex = 2)
.DisposeWith(disposable);
});
}
this.WhenAnyValue(x => x.ViewModel.SupportsOAuth)
.Where(supports => supports)
.Subscribe(supports => AuthTabs.SelectedIndex = 2)
.DisposeWith(disposable);
});
}
public IAuthViewModel ViewModel
{
get => (IAuthViewModel)GetValue(ViewModelProperty);
set => SetValue(ViewModelProperty, value);
}
public IAuthViewModel ViewModel
{
get => (IAuthViewModel)GetValue(ViewModelProperty);
set => SetValue(ViewModelProperty, value);
}
object IViewFor.ViewModel
{
get => ViewModel;
set => ViewModel = (IAuthViewModel)value;
}
object IViewFor.ViewModel
{
get => ViewModel;
set => ViewModel = (IAuthViewModel)value;
}
}

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

@ -4,45 +4,44 @@ using System.Windows.Controls;
using Camelotia.Presentation.Interfaces;
using ReactiveUI;
namespace Camelotia.Presentation.Wpf.Views
{
public partial class CloudView : UserControl, IViewFor<ICloudViewModel>
{
public static readonly DependencyProperty ViewModelProperty = DependencyProperty
.Register(nameof(ViewModel), typeof(ICloudViewModel), typeof(CloudView), null);
namespace Camelotia.Presentation.Wpf.Views;
public CloudView()
public partial class CloudView : UserControl, IViewFor<ICloudViewModel>
{
public static readonly DependencyProperty ViewModelProperty = DependencyProperty
.Register(nameof(ViewModel), typeof(ICloudViewModel), typeof(CloudView), null);
public CloudView()
{
InitializeComponent();
DataContextChanged += (sender, args) => ViewModel = DataContext as ICloudViewModel;
this.WhenActivated(disposables =>
{
InitializeComponent();
DataContextChanged += (sender, args) => ViewModel = DataContext as ICloudViewModel;
this.WhenActivated(disposables =>
{
this.OneWayBind(
this.OneWayBind(
ViewModel,
vm => vm.ShowBreadCrumbs,
view => view.PathTextBlock.Visibility,
showBreadCrumbs => showBreadCrumbs ? Visibility.Collapsed : Visibility.Visible)
.DisposeWith(disposables);
.DisposeWith(disposables);
this.OneWayBind(
this.OneWayBind(
ViewModel,
vm => vm.ShowBreadCrumbs,
view => view.BreadCrumbsListBox.Visibility,
showBreadCrumbs => showBreadCrumbs ? Visibility.Visible : Visibility.Collapsed)
.DisposeWith(disposables);
});
}
.DisposeWith(disposables);
});
}
public ICloudViewModel ViewModel
{
get => (ICloudViewModel)GetValue(ViewModelProperty);
set => SetValue(ViewModelProperty, value);
}
public ICloudViewModel ViewModel
{
get => (ICloudViewModel)GetValue(ViewModelProperty);
set => SetValue(ViewModelProperty, value);
}
object IViewFor.ViewModel
{
get => ViewModel;
set => ViewModel = (ICloudViewModel)value;
}
object IViewFor.ViewModel
{
get => ViewModel;
set => ViewModel = (ICloudViewModel)value;
}
}

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

@ -3,30 +3,29 @@ using System.Windows.Controls;
using Camelotia.Presentation.Interfaces;
using ReactiveUI;
namespace Camelotia.Presentation.Wpf.Views
namespace Camelotia.Presentation.Wpf.Views;
public partial class CreateFolderView : UserControl, IViewFor<ICreateFolderViewModel>
{
public partial class CreateFolderView : UserControl, IViewFor<ICreateFolderViewModel>
public static readonly DependencyProperty ViewModelProperty = DependencyProperty
.Register(nameof(ViewModel), typeof(ICreateFolderViewModel), typeof(CreateFolderView), null);
public CreateFolderView()
{
public static readonly DependencyProperty ViewModelProperty = DependencyProperty
.Register(nameof(ViewModel), typeof(ICreateFolderViewModel), typeof(CreateFolderView), null);
InitializeComponent();
DataContextChanged += (sender, args) => ViewModel = DataContext as ICreateFolderViewModel;
this.WhenActivated(disposable => { });
}
public CreateFolderView()
{
InitializeComponent();
DataContextChanged += (sender, args) => ViewModel = DataContext as ICreateFolderViewModel;
this.WhenActivated(disposable => { });
}
public ICreateFolderViewModel ViewModel
{
get => (ICreateFolderViewModel)GetValue(ViewModelProperty);
set => SetValue(ViewModelProperty, value);
}
public ICreateFolderViewModel ViewModel
{
get => (ICreateFolderViewModel)GetValue(ViewModelProperty);
set => SetValue(ViewModelProperty, value);
}
object IViewFor.ViewModel
{
get => ViewModel;
set => ViewModel = (ICreateFolderViewModel)value;
}
object IViewFor.ViewModel
{
get => ViewModel;
set => ViewModel = (ICreateFolderViewModel)value;
}
}

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

@ -3,30 +3,29 @@ using System.Windows.Controls;
using Camelotia.Presentation.Interfaces;
using ReactiveUI;
namespace Camelotia.Presentation.Wpf.Views
namespace Camelotia.Presentation.Wpf.Views;
public partial class DirectAuthView : UserControl, IViewFor<IDirectAuthViewModel>
{
public partial class DirectAuthView : UserControl, IViewFor<IDirectAuthViewModel>
public static readonly DependencyProperty ViewModelProperty = DependencyProperty
.Register(nameof(ViewModel), typeof(IDirectAuthViewModel), typeof(DirectAuthView), null);
public DirectAuthView()
{
public static readonly DependencyProperty ViewModelProperty = DependencyProperty
.Register(nameof(ViewModel), typeof(IDirectAuthViewModel), typeof(DirectAuthView), null);
InitializeComponent();
DataContextChanged += (sender, args) => ViewModel = DataContext as IDirectAuthViewModel;
this.WhenActivated(disposable => { });
}
public DirectAuthView()
{
InitializeComponent();
DataContextChanged += (sender, args) => ViewModel = DataContext as IDirectAuthViewModel;
this.WhenActivated(disposable => { });
}
public IDirectAuthViewModel ViewModel
{
get => (IDirectAuthViewModel)GetValue(ViewModelProperty);
set => SetValue(ViewModelProperty, value);
}
public IDirectAuthViewModel ViewModel
{
get => (IDirectAuthViewModel)GetValue(ViewModelProperty);
set => SetValue(ViewModelProperty, value);
}
object IViewFor.ViewModel
{
get => ViewModel;
set => ViewModel = (IDirectAuthViewModel)value;
}
object IViewFor.ViewModel
{
get => ViewModel;
set => ViewModel = (IDirectAuthViewModel)value;
}
}

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

@ -14,7 +14,7 @@ namespace Camelotia.Presentation.Xamarin.Droid
{
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Xamarin.Android.Build.Tasks", "1.0.0.0")]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Xamarin.Android.Build.Tasks", "12.1.0.11")]
public partial class Resource
{

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

@ -6,19 +6,18 @@ using Xamarin.Forms.Xaml;
[assembly: XamlCompilation(XamlCompilationOptions.Compile)]
namespace Camelotia.Presentation.Xamarin
{
public partial class App : Application
{
public App(IMainViewModel viewModel)
{
Plugin.Iconize.Iconize
.With(new FontAwesomeRegularModule())
.With(new FontAwesomeSolidModule())
.With(new FontAwesomeBrandsModule());
namespace Camelotia.Presentation.Xamarin;
InitializeComponent();
MainPage = new MainView { ViewModel = viewModel };
}
public partial class App : Application
{
public App(IMainViewModel viewModel)
{
Plugin.Iconize.Iconize
.With(new FontAwesomeRegularModule())
.With(new FontAwesomeSolidModule())
.With(new FontAwesomeBrandsModule());
InitializeComponent();
MainPage = new MainView { ViewModel = viewModel };
}
}

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

@ -2,6 +2,7 @@
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<LangVersion>latest</LangVersion>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">

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

@ -1,8 +1,7 @@
using Xamarin.Forms;
namespace Camelotia.Presentation.Xamarin.Controls
namespace Camelotia.Presentation.Xamarin.Controls;
public class AccentButton : Button
{
public class AccentButton : Button
{
}
}

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

@ -6,37 +6,36 @@ using ReactiveUI;
using ReactiveUI.XamForms;
using Xamarin.Forms.Xaml;
namespace Camelotia.Presentation.Xamarin.Views
namespace Camelotia.Presentation.Xamarin.Views;
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class AuthView : ReactiveTabbedPage<IAuthViewModel>
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class AuthView : ReactiveTabbedPage<IAuthViewModel>
public AuthView()
{
public AuthView()
InitializeComponent();
this.WhenActivated(disposables =>
{
InitializeComponent();
this.WhenActivated(disposables =>
{
this.WhenAnyValue(x => x.ViewModel.SupportsDirectAuth)
.Where(supportsDirectAuth => supportsDirectAuth)
.Select(supports => new DirectAuthView { ViewModel = ViewModel.DirectAuth })
.Do(view => Children.Clear())
.Subscribe(view => Children.Add(view))
.DisposeWith(disposables);
this.WhenAnyValue(x => x.ViewModel.SupportsDirectAuth)
.Where(supportsDirectAuth => supportsDirectAuth)
.Select(supports => new DirectAuthView { ViewModel = ViewModel.DirectAuth })
.Do(view => Children.Clear())
.Subscribe(view => Children.Add(view))
.DisposeWith(disposables);
this.WhenAnyValue(x => x.ViewModel.SupportsOAuth)
.Where(supportsOAuth => supportsOAuth)
.Select(supports => new OAuthView { ViewModel = ViewModel.OAuth })
.Do(view => Children.Clear())
.Subscribe(view => Children.Add(view))
.DisposeWith(disposables);
this.WhenAnyValue(x => x.ViewModel.SupportsOAuth)
.Where(supportsOAuth => supportsOAuth)
.Select(supports => new OAuthView { ViewModel = ViewModel.OAuth })
.Do(view => Children.Clear())
.Subscribe(view => Children.Add(view))
.DisposeWith(disposables);
this.WhenAnyValue(x => x.ViewModel.SupportsHostAuth)
.Where(supportsHostAuth => supportsHostAuth)
.Select(supports => new HostAuthView { ViewModel = ViewModel.HostAuth })
.Do(view => Children.Clear())
.Subscribe(view => Children.Add(view))
.DisposeWith(disposables);
});
}
this.WhenAnyValue(x => x.ViewModel.SupportsHostAuth)
.Where(supportsHostAuth => supportsHostAuth)
.Select(supports => new HostAuthView { ViewModel = ViewModel.HostAuth })
.Do(view => Children.Clear())
.Subscribe(view => Children.Add(view))
.DisposeWith(disposables);
});
}
}
}

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

@ -6,26 +6,25 @@ using ReactiveUI.XamForms;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace Camelotia.Presentation.Xamarin.Views
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class CloudExplorerFileView : ReactiveViewCell<IFileViewModel>
{
public CloudExplorerFileView()
{
InitializeComponent();
this.WhenActivated(disposables =>
{
this.WhenAnyValue(x => x.ViewModel.IsFolder)
.Select(folder => folder ? "fas-folder" : "fas-file")
.BindTo(this, x => x.IconImage.Icon)
.DisposeWith(disposables);
namespace Camelotia.Presentation.Xamarin.Views;
this.WhenAnyValue(x => x.ViewModel.IsFile)
.Select(file => file ? Color.FromRgb(130, 113, 209) : Color.FromRgb(100, 83, 179))
.BindTo(this, x => x.IconImage.IconColor)
.DisposeWith(disposables);
});
}
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class CloudExplorerFileView : ReactiveViewCell<IFileViewModel>
{
public CloudExplorerFileView()
{
InitializeComponent();
this.WhenActivated(disposables =>
{
this.WhenAnyValue(x => x.ViewModel.IsFolder)
.Select(folder => folder ? "fas-folder" : "fas-file")
.BindTo(this, x => x.IconImage.Icon)
.DisposeWith(disposables);
this.WhenAnyValue(x => x.ViewModel.IsFile)
.Select(file => file ? Color.FromRgb(130, 113, 209) : Color.FromRgb(100, 83, 179))
.BindTo(this, x => x.IconImage.IconColor)
.DisposeWith(disposables);
});
}
}
}

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

@ -4,19 +4,18 @@ using ReactiveUI;
using ReactiveUI.XamForms;
using Xamarin.Forms.Xaml;
namespace Camelotia.Presentation.Xamarin.Views
namespace Camelotia.Presentation.Xamarin.Views;
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class CloudExplorerView : ReactiveContentPage<ICloudViewModel>
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class CloudExplorerView : ReactiveContentPage<ICloudViewModel>
public CloudExplorerView()
{
public CloudExplorerView()
InitializeComponent();
this.WhenActivated(disposables =>
{
InitializeComponent();
this.WhenActivated(disposables =>
{
this.OneWayBind(ViewModel, x => x.IsLoading, x => x.FilesListView.IsRefreshing)
.DisposeWith(disposables);
});
}
this.OneWayBind(ViewModel, x => x.IsLoading, x => x.FilesListView.IsRefreshing)
.DisposeWith(disposables);
});
}
}
}

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

@ -7,28 +7,27 @@ using ReactiveUI.Validation.Formatters;
using ReactiveUI.XamForms;
using Xamarin.Forms.Xaml;
namespace Camelotia.Presentation.Xamarin.Views
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class CreateFolderView : ReactiveContentPage<ICreateFolderViewModel>
{
public CreateFolderView()
{
InitializeComponent();
this.WhenActivated(disposables =>
{
this.BindValidation(ViewModel, x => x.Name, x => x.FolderNameErrorLabel.Text)
.DisposeWith(disposables);
this.BindValidation(ViewModel, x => x.FormErrorLabel.Text, new SingleLineFormatter(Environment.NewLine))
.DisposeWith(disposables);
namespace Camelotia.Presentation.Xamarin.Views;
this.WhenAnyValue(x => x.FolderNameErrorLabel.Text, text => !string.IsNullOrWhiteSpace(text))
.BindTo(this, x => x.FolderNameErrorLabel.IsVisible)
.DisposeWith(disposables);
this.WhenAnyValue(x => x.FormErrorLabel.Text, text => !string.IsNullOrWhiteSpace(text))
.BindTo(this, x => x.FormErrorLabel.IsVisible)
.DisposeWith(disposables);
});
}
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class CreateFolderView : ReactiveContentPage<ICreateFolderViewModel>
{
public CreateFolderView()
{
InitializeComponent();
this.WhenActivated(disposables =>
{
this.BindValidation(ViewModel, x => x.Name, x => x.FolderNameErrorLabel.Text)
.DisposeWith(disposables);
this.BindValidation(ViewModel, x => x.FormErrorLabel.Text, new SingleLineFormatter(Environment.NewLine))
.DisposeWith(disposables);
this.WhenAnyValue(x => x.FolderNameErrorLabel.Text, text => !string.IsNullOrWhiteSpace(text))
.BindTo(this, x => x.FolderNameErrorLabel.IsVisible)
.DisposeWith(disposables);
this.WhenAnyValue(x => x.FormErrorLabel.Text, text => !string.IsNullOrWhiteSpace(text))
.BindTo(this, x => x.FormErrorLabel.IsVisible)
.DisposeWith(disposables);
});
}
}
}

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

@ -7,33 +7,32 @@ using ReactiveUI.Validation.Formatters;
using ReactiveUI.XamForms;
using Xamarin.Forms.Xaml;
namespace Camelotia.Presentation.Xamarin.Views
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class DirectAuthView : ReactiveContentPage<IDirectAuthViewModel>
{
public DirectAuthView()
{
InitializeComponent();
this.WhenActivated(disposables =>
{
this.BindValidation(ViewModel, x => x.Username, x => x.UserNameErrorLabel.Text)
.DisposeWith(disposables);
this.BindValidation(ViewModel, x => x.Password, x => x.PasswordErrorLabel.Text)
.DisposeWith(disposables);
this.BindValidation(ViewModel, x => x.FormErrorLabel.Text, new SingleLineFormatter(Environment.NewLine))
.DisposeWith(disposables);
namespace Camelotia.Presentation.Xamarin.Views;
this.WhenAnyValue(x => x.UserNameErrorLabel.Text, text => !string.IsNullOrWhiteSpace(text))
.BindTo(this, x => x.UserNameErrorLabel.IsVisible)
.DisposeWith(disposables);
this.WhenAnyValue(x => x.PasswordErrorLabel.Text, text => !string.IsNullOrWhiteSpace(text))
.BindTo(this, x => x.PasswordErrorLabel.IsVisible)
.DisposeWith(disposables);
this.WhenAnyValue(x => x.FormErrorLabel.Text, text => !string.IsNullOrWhiteSpace(text))
.BindTo(this, x => x.FormErrorLabel.IsVisible)
.DisposeWith(disposables);
});
}
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class DirectAuthView : ReactiveContentPage<IDirectAuthViewModel>
{
public DirectAuthView()
{
InitializeComponent();
this.WhenActivated(disposables =>
{
this.BindValidation(ViewModel, x => x.Username, x => x.UserNameErrorLabel.Text)
.DisposeWith(disposables);
this.BindValidation(ViewModel, x => x.Password, x => x.PasswordErrorLabel.Text)
.DisposeWith(disposables);
this.BindValidation(ViewModel, x => x.FormErrorLabel.Text, new SingleLineFormatter(Environment.NewLine))
.DisposeWith(disposables);
this.WhenAnyValue(x => x.UserNameErrorLabel.Text, text => !string.IsNullOrWhiteSpace(text))
.BindTo(this, x => x.UserNameErrorLabel.IsVisible)
.DisposeWith(disposables);
this.WhenAnyValue(x => x.PasswordErrorLabel.Text, text => !string.IsNullOrWhiteSpace(text))
.BindTo(this, x => x.PasswordErrorLabel.IsVisible)
.DisposeWith(disposables);
this.WhenAnyValue(x => x.FormErrorLabel.Text, text => !string.IsNullOrWhiteSpace(text))
.BindTo(this, x => x.FormErrorLabel.IsVisible)
.DisposeWith(disposables);
});
}
}
}

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

@ -7,43 +7,42 @@ using ReactiveUI.Validation.Formatters;
using ReactiveUI.XamForms;
using Xamarin.Forms.Xaml;
namespace Camelotia.Presentation.Xamarin.Views
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class HostAuthView : ReactiveContentPage<IHostAuthViewModel>
{
public HostAuthView()
{
InitializeComponent();
this.WhenActivated(disposables =>
{
this.BindValidation(ViewModel, x => x.Address, x => x.HostNameErrorLabel.Text)
.DisposeWith(disposables);
this.BindValidation(ViewModel, x => x.Port, x => x.PortErrorLabel.Text)
.DisposeWith(disposables);
this.BindValidation(ViewModel, x => x.Username, x => x.UserNameErrorLabel.Text)
.DisposeWith(disposables);
this.BindValidation(ViewModel, x => x.Password, x => x.PasswordErrorLabel.Text)
.DisposeWith(disposables);
this.BindValidation(ViewModel, x => x.FormErrorLabel.Text, new SingleLineFormatter(Environment.NewLine))
.DisposeWith(disposables);
namespace Camelotia.Presentation.Xamarin.Views;
this.WhenAnyValue(x => x.HostNameErrorLabel.Text, text => !string.IsNullOrWhiteSpace(text))
.BindTo(this, x => x.HostNameErrorLabel.IsVisible)
.DisposeWith(disposables);
this.WhenAnyValue(x => x.PortErrorLabel.Text, text => !string.IsNullOrWhiteSpace(text))
.BindTo(this, x => x.PortErrorLabel.IsVisible)
.DisposeWith(disposables);
this.WhenAnyValue(x => x.UserNameErrorLabel.Text, text => !string.IsNullOrWhiteSpace(text))
.BindTo(this, x => x.UserNameErrorLabel.IsVisible)
.DisposeWith(disposables);
this.WhenAnyValue(x => x.PasswordErrorLabel.Text, text => !string.IsNullOrWhiteSpace(text))
.BindTo(this, x => x.PasswordErrorLabel.IsVisible)
.DisposeWith(disposables);
this.WhenAnyValue(x => x.FormErrorLabel.Text, text => !string.IsNullOrWhiteSpace(text))
.BindTo(this, x => x.FormErrorLabel.IsVisible)
.DisposeWith(disposables);
});
}
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class HostAuthView : ReactiveContentPage<IHostAuthViewModel>
{
public HostAuthView()
{
InitializeComponent();
this.WhenActivated(disposables =>
{
this.BindValidation(ViewModel, x => x.Address, x => x.HostNameErrorLabel.Text)
.DisposeWith(disposables);
this.BindValidation(ViewModel, x => x.Port, x => x.PortErrorLabel.Text)
.DisposeWith(disposables);
this.BindValidation(ViewModel, x => x.Username, x => x.UserNameErrorLabel.Text)
.DisposeWith(disposables);
this.BindValidation(ViewModel, x => x.Password, x => x.PasswordErrorLabel.Text)
.DisposeWith(disposables);
this.BindValidation(ViewModel, x => x.FormErrorLabel.Text, new SingleLineFormatter(Environment.NewLine))
.DisposeWith(disposables);
this.WhenAnyValue(x => x.HostNameErrorLabel.Text, text => !string.IsNullOrWhiteSpace(text))
.BindTo(this, x => x.HostNameErrorLabel.IsVisible)
.DisposeWith(disposables);
this.WhenAnyValue(x => x.PortErrorLabel.Text, text => !string.IsNullOrWhiteSpace(text))
.BindTo(this, x => x.PortErrorLabel.IsVisible)
.DisposeWith(disposables);
this.WhenAnyValue(x => x.UserNameErrorLabel.Text, text => !string.IsNullOrWhiteSpace(text))
.BindTo(this, x => x.UserNameErrorLabel.IsVisible)
.DisposeWith(disposables);
this.WhenAnyValue(x => x.PasswordErrorLabel.Text, text => !string.IsNullOrWhiteSpace(text))
.BindTo(this, x => x.PasswordErrorLabel.IsVisible)
.DisposeWith(disposables);
this.WhenAnyValue(x => x.FormErrorLabel.Text, text => !string.IsNullOrWhiteSpace(text))
.BindTo(this, x => x.FormErrorLabel.IsVisible)
.DisposeWith(disposables);
});
}
}
}

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

@ -3,15 +3,14 @@ using ReactiveUI;
using ReactiveUI.XamForms;
using Xamarin.Forms.Xaml;
namespace Camelotia.Presentation.Xamarin.Views
namespace Camelotia.Presentation.Xamarin.Views;
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class MainCloudView : ReactiveViewCell<ICloudViewModel>
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class MainCloudView : ReactiveViewCell<ICloudViewModel>
public MainCloudView()
{
public MainCloudView()
{
InitializeComponent();
this.WhenActivated(disposables => { });
}
InitializeComponent();
this.WhenActivated(disposables => { });
}
}
}

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

@ -1,5 +1,4 @@
using System;
using System.Linq;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using Camelotia.Presentation.Interfaces;
@ -9,92 +8,91 @@ using ReactiveUI.XamForms;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace Camelotia.Presentation.Xamarin.Views
namespace Camelotia.Presentation.Xamarin.Views;
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class MainMasterView : ReactiveContentPage<IMainViewModel>, IDisposable
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class MainMasterView : ReactiveContentPage<IMainViewModel>, IDisposable
private CompositeDisposable _listeners;
public MainMasterView()
{
private CompositeDisposable _listeners;
public MainMasterView()
InitializeComponent();
this.WhenActivated(disposables =>
{
InitializeComponent();
this.WhenActivated(disposables =>
{
OpenButton
.Events()
.Clicked
.Select(args => ViewModel.SelectedProvider)
.Where(provider => provider != null)
.Select(x => new CloudExplorerView { ViewModel = x })
.Subscribe(NavigateToProvider)
.DisposeWith(disposables);
});
}
OpenButton
.Events()
.Clicked
.Select(args => ViewModel.SelectedProvider)
.Where(provider => provider != null)
.Select(x => new CloudExplorerView { ViewModel = x })
.Subscribe(NavigateToProvider)
.DisposeWith(disposables);
});
}
public void Dispose()
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
_listeners.Dispose();
}
}
private void NavigateToProvider(CloudExplorerView view)
{
_listeners?.Dispose();
_listeners = new CompositeDisposable();
view.WhenAnyValue(x => x.ViewModel.Auth.IsAuthenticated)
.Where(authenticated => authenticated)
.Select(x => new CloudExplorerView { ViewModel = ViewModel.SelectedProvider })
.Subscribe(NavigateWithoutBackStack)
.DisposeWith(_listeners);
view.WhenAnyValue(x => x.ViewModel.Auth.IsAuthenticated)
.Where(authenticated => !authenticated)
.Select(x => new AuthView { ViewModel = ViewModel.SelectedProvider.Auth })
.Subscribe(NavigateWithoutBackStack)
.DisposeWith(_listeners);
view.WhenAnyValue(x => x.ViewModel.Folder.IsVisible)
.Where(visible => visible)
.Select(x => new CreateFolderView { ViewModel = ViewModel.SelectedProvider.Folder })
.Subscribe(NavigateWithoutBackStack)
.DisposeWith(_listeners);
view.WhenAnyValue(x => x.ViewModel.Folder.IsVisible)
.Where(visible => !visible)
.Skip(1)
.Select(x => new CloudExplorerView { ViewModel = ViewModel.SelectedProvider })
.Subscribe(NavigateWithoutBackStack)
.DisposeWith(_listeners);
view.WhenAnyValue(x => x.ViewModel.Rename.IsVisible)
.Where(visible => visible)
.Select(x => new RenameFileView { ViewModel = ViewModel.SelectedProvider.Rename })
.Subscribe(NavigateWithoutBackStack)
.DisposeWith(_listeners);
view.WhenAnyValue(x => x.ViewModel.Rename.IsVisible)
.Where(visible => !visible)
.Skip(1)
.Select(x => new CloudExplorerView { ViewModel = ViewModel.SelectedProvider })
.Subscribe(NavigateWithoutBackStack)
.DisposeWith(_listeners);
}
private async void NavigateWithoutBackStack(Page page)
{
while (Navigation.NavigationStack.Count > 1)
await Navigation.PopAsync(false).ConfigureAwait(false);
await Navigation.PushAsync(page).ConfigureAwait(false);
_listeners.Dispose();
}
}
private void NavigateToProvider(CloudExplorerView view)
{
_listeners?.Dispose();
_listeners = new CompositeDisposable();
view.WhenAnyValue(x => x.ViewModel.Auth.IsAuthenticated)
.Where(authenticated => authenticated)
.Select(x => new CloudExplorerView { ViewModel = ViewModel.SelectedProvider })
.Subscribe(NavigateWithoutBackStack)
.DisposeWith(_listeners);
view.WhenAnyValue(x => x.ViewModel.Auth.IsAuthenticated)
.Where(authenticated => !authenticated)
.Select(x => new AuthView { ViewModel = ViewModel.SelectedProvider.Auth })
.Subscribe(NavigateWithoutBackStack)
.DisposeWith(_listeners);
view.WhenAnyValue(x => x.ViewModel.Folder.IsVisible)
.Where(visible => visible)
.Select(x => new CreateFolderView { ViewModel = ViewModel.SelectedProvider.Folder })
.Subscribe(NavigateWithoutBackStack)
.DisposeWith(_listeners);
view.WhenAnyValue(x => x.ViewModel.Folder.IsVisible)
.Where(visible => !visible)
.Skip(1)
.Select(x => new CloudExplorerView { ViewModel = ViewModel.SelectedProvider })
.Subscribe(NavigateWithoutBackStack)
.DisposeWith(_listeners);
view.WhenAnyValue(x => x.ViewModel.Rename.IsVisible)
.Where(visible => visible)
.Select(x => new RenameFileView { ViewModel = ViewModel.SelectedProvider.Rename })
.Subscribe(NavigateWithoutBackStack)
.DisposeWith(_listeners);
view.WhenAnyValue(x => x.ViewModel.Rename.IsVisible)
.Where(visible => !visible)
.Skip(1)
.Select(x => new CloudExplorerView { ViewModel = ViewModel.SelectedProvider })
.Subscribe(NavigateWithoutBackStack)
.DisposeWith(_listeners);
}
private async void NavigateWithoutBackStack(Page page)
{
while (Navigation.NavigationStack.Count > 1)
await Navigation.PopAsync(false).ConfigureAwait(false);
await Navigation.PushAsync(page).ConfigureAwait(false);
}
}

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

@ -6,21 +6,20 @@ using ReactiveUI;
using ReactiveUI.XamForms;
using Xamarin.Forms.Xaml;
namespace Camelotia.Presentation.Xamarin.Views
namespace Camelotia.Presentation.Xamarin.Views;
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class MainView : ReactiveNavigationPage<IMainViewModel>
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class MainView : ReactiveNavigationPage<IMainViewModel>
public MainView()
{
public MainView()
InitializeComponent();
this.WhenActivated(disposables =>
{
InitializeComponent();
this.WhenActivated(disposables =>
{
this.WhenAnyValue(x => x.ViewModel)
.Select(x => new MainMasterView { ViewModel = x })
.Subscribe(view => PushAsync(view))
.DisposeWith(disposables);
});
}
this.WhenAnyValue(x => x.ViewModel)
.Select(x => new MainMasterView { ViewModel = x })
.Subscribe(view => PushAsync(view))
.DisposeWith(disposables);
});
}
}
}

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

@ -3,15 +3,14 @@ using ReactiveUI;
using ReactiveUI.XamForms;
using Xamarin.Forms.Xaml;
namespace Camelotia.Presentation.Xamarin.Views
namespace Camelotia.Presentation.Xamarin.Views;
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class OAuthView : ReactiveContentPage<IOAuthViewModel>
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class OAuthView : ReactiveContentPage<IOAuthViewModel>
public OAuthView()
{
public OAuthView()
{
InitializeComponent();
this.WhenActivated(disposables => { });
}
InitializeComponent();
this.WhenActivated(disposables => { });
}
}
}

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

@ -7,28 +7,27 @@ using ReactiveUI.Validation.Formatters;
using ReactiveUI.XamForms;
using Xamarin.Forms.Xaml;
namespace Camelotia.Presentation.Xamarin.Views
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class RenameFileView : ReactiveContentPage<IRenameFileViewModel>
{
public RenameFileView()
{
InitializeComponent();
this.WhenActivated(disposables =>
{
this.BindValidation(ViewModel, x => x.NewName, x => x.FileNameErrorLabel.Text)
.DisposeWith(disposables);
this.BindValidation(ViewModel, x => x.FormErrorLabel.Text, new SingleLineFormatter(Environment.NewLine))
.DisposeWith(disposables);
namespace Camelotia.Presentation.Xamarin.Views;
this.WhenAnyValue(x => x.FileNameErrorLabel.Text, text => !string.IsNullOrWhiteSpace(text))
.BindTo(this, x => x.FileNameErrorLabel.IsVisible)
.DisposeWith(disposables);
this.WhenAnyValue(x => x.FormErrorLabel.Text, text => !string.IsNullOrWhiteSpace(text))
.BindTo(this, x => x.FormErrorLabel.IsVisible)
.DisposeWith(disposables);
});
}
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class RenameFileView : ReactiveContentPage<IRenameFileViewModel>
{
public RenameFileView()
{
InitializeComponent();
this.WhenActivated(disposables =>
{
this.BindValidation(ViewModel, x => x.NewName, x => x.FileNameErrorLabel.Text)
.DisposeWith(disposables);
this.BindValidation(ViewModel, x => x.FormErrorLabel.Text, new SingleLineFormatter(Environment.NewLine))
.DisposeWith(disposables);
this.WhenAnyValue(x => x.FileNameErrorLabel.Text, text => !string.IsNullOrWhiteSpace(text))
.BindTo(this, x => x.FileNameErrorLabel.IsVisible)
.DisposeWith(disposables);
this.WhenAnyValue(x => x.FormErrorLabel.Text, text => !string.IsNullOrWhiteSpace(text))
.BindTo(this, x => x.FormErrorLabel.IsVisible)
.DisposeWith(disposables);
});
}
}
}

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

@ -1,14 +1,13 @@
using System.Runtime.Serialization;
namespace Camelotia.Presentation.AppState
{
[DataContract]
public class AuthState
{
[DataMember]
public DirectAuthState DirectAuthState { get; set; } = new DirectAuthState();
namespace Camelotia.Presentation.AppState;
[DataMember]
public HostAuthState HostAuthState { get; set; } = new HostAuthState();
}
}
[DataContract]
public class AuthState
{
[DataMember]
public DirectAuthState DirectAuthState { get; set; } = new();
[DataMember]
public HostAuthState HostAuthState { get; set; } = new();
}

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

@ -2,46 +2,45 @@ using System;
using System.Runtime.Serialization;
using Camelotia.Services.Models;
namespace Camelotia.Presentation.AppState
namespace Camelotia.Presentation.AppState;
[DataContract]
public class CloudState
{
[DataContract]
public class CloudState
[DataMember]
public CreateFolderState CreateFolderState { get; set; } = new();
[DataMember]
public RenameFileState RenameFileState { get; set; } = new();
[DataMember]
public AuthState AuthState { get; set; } = new();
[DataMember]
public string CurrentPath { get; set; }
[DataMember]
public Guid Id { get; set; } = Guid.NewGuid();
[DataMember]
public DateTime Created { get; set; } = DateTime.Now;
[DataMember]
public CloudType Type { get; set; } = CloudType.Local;
[DataMember]
public string User { get; set; }
[DataMember]
public string Token { get; set; }
[IgnoreDataMember]
public CloudParameters Parameters => new()
{
[DataMember]
public CreateFolderState CreateFolderState { get; set; } = new CreateFolderState();
[DataMember]
public RenameFileState RenameFileState { get; set; } = new RenameFileState();
[DataMember]
public AuthState AuthState { get; set; } = new AuthState();
[DataMember]
public string CurrentPath { get; set; }
[DataMember]
public Guid Id { get; set; } = Guid.NewGuid();
[DataMember]
public DateTime Created { get; set; } = DateTime.Now;
[DataMember]
public CloudType Type { get; set; } = CloudType.Local;
[DataMember]
public string User { get; set; }
[DataMember]
public string Token { get; set; }
[IgnoreDataMember]
public CloudParameters Parameters => new CloudParameters
{
Id = Id,
Created = Created,
Type = Type,
User = User,
Token = Token
};
}
}
Id = Id,
Created = Created,
Type = Type,
User = User,
Token = Token
};
}

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

@ -1,14 +1,13 @@
using System.Runtime.Serialization;
namespace Camelotia.Presentation.AppState
{
[DataContract]
public class CreateFolderState
{
[DataMember]
public string Name { get; set; }
namespace Camelotia.Presentation.AppState;
[DataMember]
public bool IsVisible { get; set; }
}
[DataContract]
public class CreateFolderState
{
[DataMember]
public string Name { get; set; }
[DataMember]
public bool IsVisible { get; set; }
}

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

@ -1,14 +1,13 @@
using System.Runtime.Serialization;
namespace Camelotia.Presentation.AppState
{
[DataContract]
public class DirectAuthState
{
[DataMember]
public string Username { get; set; }
namespace Camelotia.Presentation.AppState;
[DataMember]
public string Password { get; set; }
}
[DataContract]
public class DirectAuthState
{
[DataMember]
public string Username { get; set; }
[DataMember]
public string Password { get; set; }
}

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

@ -1,20 +1,19 @@
using System.Runtime.Serialization;
namespace Camelotia.Presentation.AppState
namespace Camelotia.Presentation.AppState;
[DataContract]
public class HostAuthState
{
[DataContract]
public class HostAuthState
{
[DataMember]
public string Username { get; set; }
[DataMember]
public string Username { get; set; }
[DataMember]
public string Password { get; set; }
[DataMember]
public string Password { get; set; }
[DataMember]
public string Address { get; set; }
[DataMember]
public string Address { get; set; }
[DataMember]
public string Port { get; set; }
}
[DataMember]
public string Port { get; set; }
}

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

@ -6,28 +6,27 @@ using Camelotia.Services.Configuration;
using Camelotia.Services.Models;
using DynamicData;
namespace Camelotia.Presentation.AppState
namespace Camelotia.Presentation.AppState;
[DataContract]
public class MainState
{
[DataContract]
public class MainState
[IgnoreDataMember]
public SourceCache<CloudState, Guid> Clouds { get; } = new(x => x.Id);
[DataMember]
public IEnumerable<CloudState> CloudStates
{
[IgnoreDataMember]
public SourceCache<CloudState, Guid> Clouds { get; } = new SourceCache<CloudState, Guid>(x => x.Id);
[DataMember]
public IEnumerable<CloudState> CloudStates
{
get => Clouds.Items.ToList();
set => Clouds.AddOrUpdate(value);
}
[DataMember]
public CloudType? SelectedSupportedType { get; set; }
[DataMember]
public Guid SelectedProviderId { get; set; }
[DataMember]
public CloudConfiguration CloudConfiguration { get; set; } = new CloudConfiguration();
get => Clouds.Items.ToList();
set => Clouds.AddOrUpdate(value);
}
[DataMember]
public CloudType? SelectedSupportedType { get; set; }
[DataMember]
public Guid SelectedProviderId { get; set; }
[DataMember]
public CloudConfiguration CloudConfiguration { get; set; } = new();
}

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

@ -1,11 +1,10 @@
using System.Runtime.Serialization;
namespace Camelotia.Presentation.AppState
namespace Camelotia.Presentation.AppState;
[DataContract]
public class RenameFileState
{
[DataContract]
public class RenameFileState
{
[DataMember]
public string NewName { get; set; }
}
[DataMember]
public string NewName { get; set; }
}

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

@ -1,24 +1,23 @@
using Camelotia.Presentation.Interfaces;
using ReactiveUI;
namespace Camelotia.Presentation.DesignTime
namespace Camelotia.Presentation.DesignTime;
public class DesignTimeAuthViewModel : ReactiveObject, IAuthViewModel
{
public class DesignTimeAuthViewModel : ReactiveObject, IAuthViewModel
{
public IDirectAuthViewModel DirectAuth { get; } = new DesignTimeDirectAuthViewModel();
public IDirectAuthViewModel DirectAuth { get; } = new DesignTimeDirectAuthViewModel();
public IHostAuthViewModel HostAuth { get; } = new DesignTimeHostAuthViewModel();
public IHostAuthViewModel HostAuth { get; } = new DesignTimeHostAuthViewModel();
public IOAuthViewModel OAuth { get; } = new DesignTimeOAuthViewModel();
public IOAuthViewModel OAuth { get; } = new DesignTimeOAuthViewModel();
public bool SupportsDirectAuth { get; }
public bool SupportsDirectAuth { get; }
public bool SupportsHostAuth { get; }
public bool SupportsHostAuth { get; }
public bool SupportsOAuth { get; }
public bool SupportsOAuth { get; }
public bool IsAuthenticated { get; } = true;
public bool IsAuthenticated { get; } = true;
public bool IsAnonymous { get; }
}
public bool IsAnonymous { get; }
}

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

@ -6,96 +6,95 @@ using Camelotia.Presentation.Interfaces;
using Camelotia.Services.Models;
using ReactiveUI;
namespace Camelotia.Presentation.DesignTime
namespace Camelotia.Presentation.DesignTime;
public class DesignTimeCloudViewModel : ReactiveObject, ICloudViewModel
{
public class DesignTimeCloudViewModel : ReactiveObject, ICloudViewModel
public DesignTimeCloudViewModel()
{
public DesignTimeCloudViewModel()
Files = new[]
{
Files = new[]
{
new DesignTimeFileViewModel(this),
new DesignTimeFileViewModel(this)
};
SelectedFile = Files.FirstOrDefault();
}
public Guid Id { get; } = Guid.NewGuid();
public IAuthViewModel Auth { get; } = new DesignTimeAuthViewModel();
public IRenameFileViewModel Rename { get; } = new DesignTimeRenameFileViewModel();
public ICreateFolderViewModel Folder { get; } = new DesignTimeCreateFolderViewModel();
public IFileViewModel SelectedFile { get; set; }
public IEnumerable<IFileViewModel> Files { get; }
public ReactiveCommand<Unit, Unit> DownloadSelectedFile { get; }
public ReactiveCommand<Unit, Unit> UploadToCurrentPath { get; }
public ReactiveCommand<Unit, Unit> DeleteSelectedFile { get; }
public ReactiveCommand<Unit, Unit> UnselectFile { get; }
public ReactiveCommand<Unit, IEnumerable<FileModel>> Refresh { get; }
public ReactiveCommand<Unit, Unit> Logout { get; }
public ReactiveCommand<Unit, string> Back { get; }
public ReactiveCommand<Unit, string> Open { get; }
public ReactiveCommand<string, string> SetPath { get; }
public bool IsCurrentPathEmpty { get; }
public bool IsLoading { get; }
public bool IsReady { get; } = true;
public bool HasErrorMessage { get; }
public bool CanLogout { get; }
public bool CanInteract { get; }
public bool ShowBreadCrumbs { get; } = true;
public bool HideBreadCrumbs { get; }
public int RefreshingIn { get; } = 30;
public string CurrentPath { get; } = "/home/files";
public IEnumerable<IFolderViewModel> BreadCrumbs { get; } = new List<IFolderViewModel>
{
new DesignTimeFolderViewModel(
"home",
new[]
{
new DesignTimeFolderViewModel("home"),
new DesignTimeFolderViewModel("home1"),
new DesignTimeFolderViewModel("home2")
}),
new DesignTimeFolderViewModel(
"files",
new[]
{
new DesignTimeFolderViewModel("files"),
new DesignTimeFolderViewModel("files1"),
new DesignTimeFolderViewModel("files2")
})
new DesignTimeFileViewModel(this),
new DesignTimeFileViewModel(this)
};
public string Description { get; } = "Mock file system.";
public DateTime Created { get; } = DateTime.Now;
public string Name { get; } = "Awesome mock";
public string Size { get; } = "42MB";
SelectedFile = Files.FirstOrDefault();
}
public Guid Id { get; } = Guid.NewGuid();
public IAuthViewModel Auth { get; } = new DesignTimeAuthViewModel();
public IRenameFileViewModel Rename { get; } = new DesignTimeRenameFileViewModel();
public ICreateFolderViewModel Folder { get; } = new DesignTimeCreateFolderViewModel();
public IFileViewModel SelectedFile { get; set; }
public IEnumerable<IFileViewModel> Files { get; }
public ReactiveCommand<Unit, Unit> DownloadSelectedFile { get; }
public ReactiveCommand<Unit, Unit> UploadToCurrentPath { get; }
public ReactiveCommand<Unit, Unit> DeleteSelectedFile { get; }
public ReactiveCommand<Unit, Unit> UnselectFile { get; }
public ReactiveCommand<Unit, IEnumerable<FileModel>> Refresh { get; }
public ReactiveCommand<Unit, Unit> Logout { get; }
public ReactiveCommand<Unit, string> Back { get; }
public ReactiveCommand<Unit, string> Open { get; }
public ReactiveCommand<string, string> SetPath { get; }
public bool IsCurrentPathEmpty { get; }
public bool IsLoading { get; }
public bool IsReady { get; } = true;
public bool HasErrorMessage { get; }
public bool CanLogout { get; }
public bool CanInteract { get; }
public bool ShowBreadCrumbs { get; } = true;
public bool HideBreadCrumbs { get; }
public int RefreshingIn { get; } = 30;
public string CurrentPath { get; } = "/home/files";
public IEnumerable<IFolderViewModel> BreadCrumbs { get; } = new List<IFolderViewModel>
{
new DesignTimeFolderViewModel(
"home",
new[]
{
new DesignTimeFolderViewModel("home"),
new DesignTimeFolderViewModel("home1"),
new DesignTimeFolderViewModel("home2")
}),
new DesignTimeFolderViewModel(
"files",
new[]
{
new DesignTimeFolderViewModel("files"),
new DesignTimeFolderViewModel("files1"),
new DesignTimeFolderViewModel("files2")
})
};
public string Description { get; } = "Mock file system.";
public DateTime Created { get; } = DateTime.Now;
public string Name { get; } = "Awesome mock";
public string Size { get; } = "42MB";
}

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

@ -4,28 +4,27 @@ using ReactiveUI;
using ReactiveUI.Validation.Extensions;
using ReactiveUI.Validation.Helpers;
namespace Camelotia.Presentation.DesignTime
namespace Camelotia.Presentation.DesignTime;
public class DesignTimeCreateFolderViewModel : ReactiveValidationObject, ICreateFolderViewModel
{
public class DesignTimeCreateFolderViewModel : ReactiveValidationObject, ICreateFolderViewModel
{
public DesignTimeCreateFolderViewModel() => this.ValidationRule(x => x.Name, _ => false, "Validation error.");
public DesignTimeCreateFolderViewModel() => this.ValidationRule(x => x.Name, _ => false, "Validation error.");
public bool IsLoading { get; }
public bool IsLoading { get; }
public bool IsVisible { get; set; }
public bool IsVisible { get; set; }
public string Name { get; set; }
public string Name { get; set; }
public string Path { get; } = "/home/path";
public string Path { get; } = "/home/path";
public bool HasErrorMessage { get; } = true;
public bool HasErrorMessage { get; } = true;
public string ErrorMessage { get; } = "Error message example.";
public string ErrorMessage { get; } = "Error message example.";
public ReactiveCommand<Unit, Unit> Create { get; }
public ReactiveCommand<Unit, Unit> Create { get; }
public ReactiveCommand<Unit, Unit> Close { get; }
public ReactiveCommand<Unit, Unit> Close { get; }
public ReactiveCommand<Unit, Unit> Open { get; }
}
public ReactiveCommand<Unit, Unit> Open { get; }
}

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

@ -4,22 +4,21 @@ using ReactiveUI;
using ReactiveUI.Validation.Extensions;
using ReactiveUI.Validation.Helpers;
namespace Camelotia.Presentation.DesignTime
namespace Camelotia.Presentation.DesignTime;
public class DesignTimeDirectAuthViewModel : ReactiveValidationObject, IDirectAuthViewModel
{
public class DesignTimeDirectAuthViewModel : ReactiveValidationObject, IDirectAuthViewModel
{
public DesignTimeDirectAuthViewModel() => this.ValidationRule(x => x.Username, name => false, "Validation error.");
public DesignTimeDirectAuthViewModel() => this.ValidationRule(x => x.Username, name => false, "Validation error.");
public string Username { get; set; } = "Joseph";
public string Username { get; set; } = "Joseph";
public string Password { get; set; }
public string Password { get; set; }
public ReactiveCommand<Unit, Unit> Login { get; }
public ReactiveCommand<Unit, Unit> Login { get; }
public bool HasErrorMessage { get; } = true;
public bool HasErrorMessage { get; } = true;
public string ErrorMessage { get; } = "Error message example.";
public string ErrorMessage { get; } = "Error message example.";
public bool IsBusy { get; }
}
public bool IsBusy { get; }
}

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

@ -3,29 +3,28 @@ using System.Globalization;
using Camelotia.Presentation.Interfaces;
using ReactiveUI;
namespace Camelotia.Presentation.DesignTime
namespace Camelotia.Presentation.DesignTime;
public class DesignTimeFileViewModel : ReactiveObject, IFileViewModel
{
public class DesignTimeFileViewModel : ReactiveObject, IFileViewModel
public DesignTimeFileViewModel()
: this(null)
{
public DesignTimeFileViewModel()
: this(null)
{
}
public DesignTimeFileViewModel(DesignTimeCloudViewModel provider) => Provider = provider;
public string Name { get; } = "Awesome file.";
public ICloudViewModel Provider { get; }
public string Modified { get; } = DateTime.Now.ToString(CultureInfo.InvariantCulture);
public bool IsFolder { get; }
public bool IsFile { get; } = true;
public string Path { get; } = "/home/path/file";
public string Size { get; } = "42 KB";
}
public DesignTimeFileViewModel(DesignTimeCloudViewModel provider) => Provider = provider;
public string Name { get; } = "Awesome file.";
public ICloudViewModel Provider { get; }
public string Modified { get; } = DateTime.Now.ToString(CultureInfo.InvariantCulture);
public bool IsFolder { get; }
public bool IsFile { get; } = true;
public string Path { get; } = "/home/path/file";
public string Size { get; } = "42 KB";
}

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

@ -3,31 +3,30 @@ using System.Linq;
using Camelotia.Presentation.Interfaces;
using ReactiveUI;
namespace Camelotia.Presentation.DesignTime
namespace Camelotia.Presentation.DesignTime;
public class DesignTimeFolderViewModel : ReactiveObject, IFolderViewModel
{
public class DesignTimeFolderViewModel : ReactiveObject, IFolderViewModel
public DesignTimeFolderViewModel()
{
public DesignTimeFolderViewModel()
Name = "home";
Children = new List<IFolderViewModel>
{
Name = "home";
Children = new List<IFolderViewModel>
{
new DesignTimeFolderViewModel("home"),
new DesignTimeFolderViewModel("home1"),
new DesignTimeFolderViewModel("home2")
};
}
public DesignTimeFolderViewModel(string name, IEnumerable<IFolderViewModel> children = null)
{
Name = name;
Children = children ?? Enumerable.Empty<IFolderViewModel>();
}
public string Name { get; }
public string FullPath { get; }
public IEnumerable<IFolderViewModel> Children { get; }
new DesignTimeFolderViewModel("home"),
new DesignTimeFolderViewModel("home1"),
new DesignTimeFolderViewModel("home2")
};
}
}
public DesignTimeFolderViewModel(string name, IEnumerable<IFolderViewModel> children = null)
{
Name = name;
Children = children ?? Enumerable.Empty<IFolderViewModel>();
}
public string Name { get; }
public string FullPath { get; }
public IEnumerable<IFolderViewModel> Children { get; }
}

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

@ -4,26 +4,25 @@ using ReactiveUI;
using ReactiveUI.Validation.Extensions;
using ReactiveUI.Validation.Helpers;
namespace Camelotia.Presentation.DesignTime
namespace Camelotia.Presentation.DesignTime;
public class DesignTimeHostAuthViewModel : ReactiveValidationObject, IHostAuthViewModel
{
public class DesignTimeHostAuthViewModel : ReactiveValidationObject, IHostAuthViewModel
{
public DesignTimeHostAuthViewModel() => this.ValidationRule(x => x.Username, name => false, "Validation error.");
public DesignTimeHostAuthViewModel() => this.ValidationRule(x => x.Username, name => false, "Validation error.");
public string Username { get; set; } = "Jotaro";
public string Username { get; set; } = "Jotaro";
public string Password { get; set; } = "Qwerty";
public string Password { get; set; } = "Qwerty";
public ReactiveCommand<Unit, Unit> Login { get; }
public ReactiveCommand<Unit, Unit> Login { get; }
public bool HasErrorMessage { get; } = true;
public bool HasErrorMessage { get; } = true;
public string ErrorMessage { get; } = "Error message example.";
public string ErrorMessage { get; } = "Error message example.";
public bool IsBusy { get; }
public bool IsBusy { get; }
public string Address { get; set; } = "127.0.0.1";
public string Address { get; set; } = "127.0.0.1";
public string Port { get; set; } = "5001";
}
public string Port { get; set; } = "5001";
}

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

@ -5,39 +5,38 @@ using Camelotia.Presentation.Interfaces;
using Camelotia.Services.Models;
using ReactiveUI;
namespace Camelotia.Presentation.DesignTime
namespace Camelotia.Presentation.DesignTime;
public class DesignTimeMainViewModel : ReactiveObject, IMainViewModel
{
public class DesignTimeMainViewModel : ReactiveObject, IMainViewModel
{
public ReadOnlyObservableCollection<ICloudViewModel> Clouds { get; } =
new ReadOnlyObservableCollection<ICloudViewModel>(
new ObservableCollection<ICloudViewModel>(
new List<ICloudViewModel>
{
new DesignTimeCloudViewModel(),
new DesignTimeCloudViewModel()
}));
public ReadOnlyObservableCollection<ICloudViewModel> Clouds { get; } =
new(
new ObservableCollection<ICloudViewModel>(
new List<ICloudViewModel>
{
new DesignTimeCloudViewModel(),
new DesignTimeCloudViewModel()
}));
public ICloudViewModel SelectedProvider { get; set; } = new DesignTimeCloudViewModel();
public ICloudViewModel SelectedProvider { get; set; } = new DesignTimeCloudViewModel();
public IEnumerable<CloudType> SupportedTypes { get; } = new[] { CloudType.Ftp, CloudType.Sftp };
public IEnumerable<CloudType> SupportedTypes { get; } = new[] { CloudType.Ftp, CloudType.Sftp };
public CloudType SelectedSupportedType { get; set; } = CloudType.Sftp;
public CloudType SelectedSupportedType { get; set; } = CloudType.Sftp;
public bool WelcomeScreenCollapsed { get; } = true;
public bool WelcomeScreenCollapsed { get; } = true;
public bool WelcomeScreenVisible { get; }
public bool WelcomeScreenVisible { get; }
public ReactiveCommand<Unit, Unit> Unselect { get; }
public ReactiveCommand<Unit, Unit> Unselect { get; }
public ReactiveCommand<Unit, Unit> Refresh { get; }
public ReactiveCommand<Unit, Unit> Refresh { get; }
public ReactiveCommand<Unit, Unit> Remove { get; }
public ReactiveCommand<Unit, Unit> Remove { get; }
public ReactiveCommand<Unit, Unit> Add { get; }
public ReactiveCommand<Unit, Unit> Add { get; }
public bool IsLoading { get; }
public bool IsLoading { get; }
public bool IsReady { get; } = true;
}
}
public bool IsReady { get; } = true;
}

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

@ -2,16 +2,15 @@ using System.Reactive;
using Camelotia.Presentation.Interfaces;
using ReactiveUI;
namespace Camelotia.Presentation.DesignTime
namespace Camelotia.Presentation.DesignTime;
public class DesignTimeOAuthViewModel : ReactiveObject, IOAuthViewModel
{
public class DesignTimeOAuthViewModel : ReactiveObject, IOAuthViewModel
{
public ReactiveCommand<Unit, Unit> Login { get; }
public ReactiveCommand<Unit, Unit> Login { get; }
public bool HasErrorMessage { get; } = true;
public bool HasErrorMessage { get; } = true;
public string ErrorMessage { get; } = "Error message example.";
public string ErrorMessage { get; } = "Error message example.";
public bool IsBusy { get; }
}
public bool IsBusy { get; }
}

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

@ -4,28 +4,27 @@ using ReactiveUI;
using ReactiveUI.Validation.Extensions;
using ReactiveUI.Validation.Helpers;
namespace Camelotia.Presentation.DesignTime
namespace Camelotia.Presentation.DesignTime;
public class DesignTimeRenameFileViewModel : ReactiveValidationObject, IRenameFileViewModel
{
public class DesignTimeRenameFileViewModel : ReactiveValidationObject, IRenameFileViewModel
{
public DesignTimeRenameFileViewModel() => this.ValidationRule(x => x.NewName, name => false, "Validation error.");
public DesignTimeRenameFileViewModel() => this.ValidationRule(x => x.NewName, name => false, "Validation error.");
public bool IsLoading { get; }
public bool IsLoading { get; }
public bool IsVisible { get; set; }
public bool IsVisible { get; set; }
public string OldName { get; } = "file";
public string OldName { get; } = "file";
public string NewName { get; set; }
public string NewName { get; set; }
public bool HasErrorMessage { get; } = true;
public bool HasErrorMessage { get; } = true;
public string ErrorMessage { get; } = "Error message example.";
public string ErrorMessage { get; } = "Error message example.";
public ReactiveCommand<Unit, Unit> Rename { get; }
public ReactiveCommand<Unit, Unit> Rename { get; }
public ReactiveCommand<Unit, Unit> Close { get; }
public ReactiveCommand<Unit, Unit> Close { get; }
public ReactiveCommand<Unit, Unit> Open { get; }
}
public ReactiveCommand<Unit, Unit> Open { get; }
}

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

@ -1,21 +1,20 @@
using System;
using System.Globalization;
namespace Camelotia.Presentation.Extensions
{
public static class ByteConversionExtensions
{
public static string ByteSizeToString(this long byteCount, int decimalPrecision = 1)
{
string[] suf = { "B", "KB", "MB", "GB", "TB", "PB", "EB" };
if (byteCount == 0)
return "0" + suf[0];
namespace Camelotia.Presentation.Extensions;
var bytes = Math.Abs(byteCount);
var place = Convert.ToInt32(Math.Floor(Math.Log(bytes, 1000)));
var num = Math.Round(bytes / Math.Pow(1000, place), decimalPrecision);
var digit = Math.Sign(byteCount) * num;
return digit.ToString(CultureInfo.InvariantCulture) + suf[place];
}
public static class ByteConversionExtensions
{
public static string ByteSizeToString(this long byteCount, int decimalPrecision = 1)
{
string[] suf = { "B", "KB", "MB", "GB", "TB", "PB", "EB" };
if (byteCount == 0)
return "0" + suf[0];
var bytes = Math.Abs(byteCount);
var place = Convert.ToInt32(Math.Floor(Math.Log(bytes, 1000)));
var num = Math.Round(bytes / Math.Pow(1000, place), decimalPrecision);
var digit = Math.Sign(byteCount) * num;
return digit.ToString(CultureInfo.InvariantCulture) + suf[place];
}
}
}

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

@ -5,24 +5,23 @@ using Newtonsoft.Json;
using ReactiveUI;
using Splat;
namespace Camelotia.Presentation.Infrastructure
namespace Camelotia.Presentation.Infrastructure;
public class AkavacheSuspensionDriver<TAppState> : ISuspensionDriver
where TAppState : class
{
public class AkavacheSuspensionDriver<TAppState> : ISuspensionDriver
where TAppState : class
private const string Key = "camelotia-state";
static AkavacheSuspensionDriver() => Locator.CurrentMutable.RegisterConstant(new JsonSerializerSettings
{
private const string Key = "camelotia-state";
ObjectCreationHandling = ObjectCreationHandling.Replace
});
static AkavacheSuspensionDriver() => Locator.CurrentMutable.RegisterConstant(new JsonSerializerSettings
{
ObjectCreationHandling = ObjectCreationHandling.Replace
});
public AkavacheSuspensionDriver(string appName = "CamelotiaV2") => BlobCache.ApplicationName = appName;
public AkavacheSuspensionDriver(string appName = "CamelotiaV2") => BlobCache.ApplicationName = appName;
public IObservable<Unit> InvalidateState() => BlobCache.UserAccount.InvalidateObject<TAppState>(Key);
public IObservable<Unit> InvalidateState() => BlobCache.UserAccount.InvalidateObject<TAppState>(Key);
public IObservable<object> LoadState() => BlobCache.UserAccount.GetObject<TAppState>(Key);
public IObservable<object> LoadState() => BlobCache.UserAccount.GetObject<TAppState>(Key);
public IObservable<Unit> SaveState(object state) => BlobCache.UserAccount.InsertObject(Key, (TAppState)state);
}
public IObservable<Unit> SaveState(object state) => BlobCache.UserAccount.InsertObject(Key, (TAppState)state);
}

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

@ -6,45 +6,44 @@ using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using ReactiveUI;
namespace Camelotia.Presentation.Infrastructure
namespace Camelotia.Presentation.Infrastructure;
public sealed class NewtonsoftJsonSuspensionDriver : ISuspensionDriver
{
public sealed class NewtonsoftJsonSuspensionDriver : ISuspensionDriver
private readonly string _stateFilePath;
private readonly JsonSerializerSettings _settings = new()
{
private readonly string _stateFilePath;
private readonly JsonSerializerSettings _settings = new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.All,
NullValueHandling = NullValueHandling.Ignore,
ObjectCreationHandling = ObjectCreationHandling.Replace,
ContractResolver = new CamelCasePropertyNamesContractResolver(),
};
TypeNameHandling = TypeNameHandling.All,
NullValueHandling = NullValueHandling.Ignore,
ObjectCreationHandling = ObjectCreationHandling.Replace,
ContractResolver = new CamelCasePropertyNamesContractResolver(),
};
public NewtonsoftJsonSuspensionDriver(string stateFilePath) => _stateFilePath = stateFilePath;
public NewtonsoftJsonSuspensionDriver(string stateFilePath) => _stateFilePath = stateFilePath;
public IObservable<Unit> InvalidateState()
public IObservable<Unit> InvalidateState()
{
if (File.Exists(_stateFilePath))
File.Delete(_stateFilePath);
return Observable.Return(Unit.Default);
}
public IObservable<object> LoadState()
{
if (!File.Exists(_stateFilePath))
{
if (File.Exists(_stateFilePath))
File.Delete(_stateFilePath);
return Observable.Return(Unit.Default);
return Observable.Throw<object>(new FileNotFoundException(_stateFilePath));
}
public IObservable<object> LoadState()
{
if (!File.Exists(_stateFilePath))
{
return Observable.Throw<object>(new FileNotFoundException(_stateFilePath));
}
var lines = File.ReadAllText(_stateFilePath);
var state = JsonConvert.DeserializeObject<object>(lines, _settings);
return Observable.Return(state);
}
var lines = File.ReadAllText(_stateFilePath);
var state = JsonConvert.DeserializeObject<object>(lines, _settings);
return Observable.Return(state);
}
public IObservable<Unit> SaveState(object state)
{
var lines = JsonConvert.SerializeObject(state, Formatting.Indented, _settings);
File.WriteAllText(_stateFilePath, lines);
return Observable.Return(Unit.Default);
}
public IObservable<Unit> SaveState(object state)
{
var lines = JsonConvert.SerializeObject(state, Formatting.Indented, _settings);
File.WriteAllText(_stateFilePath, lines);
return Observable.Return(Unit.Default);
}
}

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

@ -1,23 +1,22 @@
using System.ComponentModel;
namespace Camelotia.Presentation.Interfaces
namespace Camelotia.Presentation.Interfaces;
public interface IAuthViewModel : INotifyPropertyChanged
{
public interface IAuthViewModel : INotifyPropertyChanged
{
IDirectAuthViewModel DirectAuth { get; }
IDirectAuthViewModel DirectAuth { get; }
IHostAuthViewModel HostAuth { get; }
IHostAuthViewModel HostAuth { get; }
IOAuthViewModel OAuth { get; }
IOAuthViewModel OAuth { get; }
bool SupportsDirectAuth { get; }
bool SupportsDirectAuth { get; }
bool SupportsHostAuth { get; }
bool SupportsHostAuth { get; }
bool SupportsOAuth { get; }
bool SupportsOAuth { get; }
bool IsAuthenticated { get; }
bool IsAuthenticated { get; }
bool IsAnonymous { get; }
}
bool IsAnonymous { get; }
}

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

@ -5,68 +5,67 @@ using System.Reactive;
using Camelotia.Services.Models;
using ReactiveUI;
namespace Camelotia.Presentation.Interfaces
namespace Camelotia.Presentation.Interfaces;
public interface ICloudViewModel : INotifyPropertyChanged
{
public interface ICloudViewModel : INotifyPropertyChanged
{
Guid Id { get; }
Guid Id { get; }
IAuthViewModel Auth { get; }
IAuthViewModel Auth { get; }
IRenameFileViewModel Rename { get; }
IRenameFileViewModel Rename { get; }
ICreateFolderViewModel Folder { get; }
ICreateFolderViewModel Folder { get; }
IFileViewModel SelectedFile { get; set; }
IFileViewModel SelectedFile { get; set; }
IEnumerable<IFileViewModel> Files { get; }
IEnumerable<IFileViewModel> Files { get; }
IEnumerable<IFolderViewModel> BreadCrumbs { get; }
IEnumerable<IFolderViewModel> BreadCrumbs { get; }
ReactiveCommand<Unit, Unit> DownloadSelectedFile { get; }
ReactiveCommand<Unit, Unit> DownloadSelectedFile { get; }
ReactiveCommand<Unit, Unit> UploadToCurrentPath { get; }
ReactiveCommand<Unit, Unit> UploadToCurrentPath { get; }
ReactiveCommand<Unit, Unit> DeleteSelectedFile { get; }
ReactiveCommand<Unit, Unit> DeleteSelectedFile { get; }
ReactiveCommand<Unit, Unit> UnselectFile { get; }
ReactiveCommand<Unit, Unit> UnselectFile { get; }
ReactiveCommand<Unit, IEnumerable<FileModel>> Refresh { get; }
ReactiveCommand<Unit, IEnumerable<FileModel>> Refresh { get; }
ReactiveCommand<Unit, Unit> Logout { get; }
ReactiveCommand<Unit, Unit> Logout { get; }
ReactiveCommand<Unit, string> Back { get; }
ReactiveCommand<Unit, string> Back { get; }
ReactiveCommand<Unit, string> Open { get; }
ReactiveCommand<Unit, string> Open { get; }
ReactiveCommand<string, string> SetPath { get; }
ReactiveCommand<string, string> SetPath { get; }
bool IsCurrentPathEmpty { get; }
bool IsCurrentPathEmpty { get; }
bool IsLoading { get; }
bool IsLoading { get; }
bool IsReady { get; }
bool IsReady { get; }
bool HasErrorMessage { get; }
bool HasErrorMessage { get; }
bool CanLogout { get; }
bool CanLogout { get; }
bool CanInteract { get; }
bool CanInteract { get; }
bool ShowBreadCrumbs { get; }
bool ShowBreadCrumbs { get; }
bool HideBreadCrumbs { get; }
bool HideBreadCrumbs { get; }
int RefreshingIn { get; }
int RefreshingIn { get; }
string CurrentPath { get; }
string CurrentPath { get; }
string Description { get; }
string Description { get; }
DateTime Created { get; }
DateTime Created { get; }
string Name { get; }
string Name { get; }
string Size { get; }
}
string Size { get; }
}

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

@ -3,30 +3,29 @@ using System.Reactive;
using ReactiveUI;
using ReactiveUI.Validation.Abstractions;
namespace Camelotia.Presentation.Interfaces
namespace Camelotia.Presentation.Interfaces;
public interface ICreateFolderViewModel :
INotifyPropertyChanged,
INotifyDataErrorInfo,
IValidatableViewModel,
IReactiveObject
{
public interface ICreateFolderViewModel :
INotifyPropertyChanged,
INotifyDataErrorInfo,
IValidatableViewModel,
IReactiveObject
{
bool IsLoading { get; }
bool IsLoading { get; }
bool IsVisible { get; set; }
bool IsVisible { get; set; }
string Name { get; set; }
string Name { get; set; }
string Path { get; }
string Path { get; }
bool HasErrorMessage { get; }
bool HasErrorMessage { get; }
string ErrorMessage { get; }
string ErrorMessage { get; }
ReactiveCommand<Unit, Unit> Create { get; }
ReactiveCommand<Unit, Unit> Create { get; }
ReactiveCommand<Unit, Unit> Close { get; }
ReactiveCommand<Unit, Unit> Close { get; }
ReactiveCommand<Unit, Unit> Open { get; }
}
ReactiveCommand<Unit, Unit> Open { get; }
}

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

@ -3,24 +3,23 @@ using System.Reactive;
using ReactiveUI;
using ReactiveUI.Validation.Abstractions;
namespace Camelotia.Presentation.Interfaces
namespace Camelotia.Presentation.Interfaces;
public interface IDirectAuthViewModel :
INotifyPropertyChanged,
INotifyDataErrorInfo,
IValidatableViewModel,
IReactiveObject
{
public interface IDirectAuthViewModel :
INotifyPropertyChanged,
INotifyDataErrorInfo,
IValidatableViewModel,
IReactiveObject
{
string Username { get; set; }
string Username { get; set; }
string Password { get; set; }
string Password { get; set; }
ReactiveCommand<Unit, Unit> Login { get; }
ReactiveCommand<Unit, Unit> Login { get; }
bool HasErrorMessage { get; }
bool HasErrorMessage { get; }
string ErrorMessage { get; }
string ErrorMessage { get; }
bool IsBusy { get; }
}
bool IsBusy { get; }
}

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

@ -1,21 +1,20 @@
using System.ComponentModel;
namespace Camelotia.Presentation.Interfaces
namespace Camelotia.Presentation.Interfaces;
public interface IFileViewModel : INotifyPropertyChanged
{
public interface IFileViewModel : INotifyPropertyChanged
{
string Name { get; }
string Name { get; }
ICloudViewModel Provider { get; }
ICloudViewModel Provider { get; }
string Modified { get; }
string Modified { get; }
bool IsFolder { get; }
bool IsFolder { get; }
bool IsFile { get; }
bool IsFile { get; }
string Path { get; }
string Path { get; }
string Size { get; }
}
}
string Size { get; }
}

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

@ -1,14 +1,13 @@
using System.Collections.Generic;
using System.ComponentModel;
namespace Camelotia.Presentation.Interfaces
namespace Camelotia.Presentation.Interfaces;
public interface IFolderViewModel : INotifyPropertyChanged
{
public interface IFolderViewModel : INotifyPropertyChanged
{
string Name { get; }
string Name { get; }
string FullPath { get; }
string FullPath { get; }
IEnumerable<IFolderViewModel> Children { get; }
}
}
IEnumerable<IFolderViewModel> Children { get; }
}

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

@ -1,15 +1,14 @@
using ReactiveUI;
using ReactiveUI.Validation.Abstractions;
namespace Camelotia.Presentation.Interfaces
{
public interface IHostAuthViewModel :
IDirectAuthViewModel,
IValidatableViewModel,
IReactiveObject
{
string Address { get; set; }
namespace Camelotia.Presentation.Interfaces;
string Port { get; set; }
}
public interface IHostAuthViewModel :
IDirectAuthViewModel,
IValidatableViewModel,
IReactiveObject
{
string Address { get; set; }
string Port { get; set; }
}

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

@ -5,32 +5,31 @@ using System.Reactive;
using Camelotia.Services.Models;
using ReactiveUI;
namespace Camelotia.Presentation.Interfaces
namespace Camelotia.Presentation.Interfaces;
public interface IMainViewModel : INotifyPropertyChanged
{
public interface IMainViewModel : INotifyPropertyChanged
{
ReadOnlyObservableCollection<ICloudViewModel> Clouds { get; }
ReadOnlyObservableCollection<ICloudViewModel> Clouds { get; }
ICloudViewModel SelectedProvider { get; set; }
ICloudViewModel SelectedProvider { get; set; }
IEnumerable<CloudType> SupportedTypes { get; }
IEnumerable<CloudType> SupportedTypes { get; }
CloudType SelectedSupportedType { get; set; }
CloudType SelectedSupportedType { get; set; }
bool WelcomeScreenCollapsed { get; }
bool WelcomeScreenCollapsed { get; }
bool WelcomeScreenVisible { get; }
bool WelcomeScreenVisible { get; }
ReactiveCommand<Unit, Unit> Unselect { get; }
ReactiveCommand<Unit, Unit> Unselect { get; }
ReactiveCommand<Unit, Unit> Refresh { get; }
ReactiveCommand<Unit, Unit> Refresh { get; }
ReactiveCommand<Unit, Unit> Remove { get; }
ReactiveCommand<Unit, Unit> Remove { get; }
ReactiveCommand<Unit, Unit> Add { get; }
ReactiveCommand<Unit, Unit> Add { get; }
bool IsLoading { get; }
bool IsLoading { get; }
bool IsReady { get; }
}
bool IsReady { get; }
}

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

@ -2,16 +2,15 @@ using System.ComponentModel;
using System.Reactive;
using ReactiveUI;
namespace Camelotia.Presentation.Interfaces
namespace Camelotia.Presentation.Interfaces;
public interface IOAuthViewModel : INotifyPropertyChanged
{
public interface IOAuthViewModel : INotifyPropertyChanged
{
ReactiveCommand<Unit, Unit> Login { get; }
ReactiveCommand<Unit, Unit> Login { get; }
bool HasErrorMessage { get; }
bool HasErrorMessage { get; }
string ErrorMessage { get; }
string ErrorMessage { get; }
bool IsBusy { get; }
}
bool IsBusy { get; }
}

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

@ -3,30 +3,29 @@ using System.Reactive;
using ReactiveUI;
using ReactiveUI.Validation.Abstractions;
namespace Camelotia.Presentation.Interfaces
namespace Camelotia.Presentation.Interfaces;
public interface IRenameFileViewModel :
INotifyPropertyChanged,
INotifyDataErrorInfo,
IValidatableViewModel,
IReactiveObject
{
public interface IRenameFileViewModel :
INotifyPropertyChanged,
INotifyDataErrorInfo,
IValidatableViewModel,
IReactiveObject
{
bool IsLoading { get; }
bool IsLoading { get; }
bool IsVisible { get; set; }
bool IsVisible { get; set; }
string OldName { get; }
string OldName { get; }
string NewName { get; set; }
string NewName { get; set; }
bool HasErrorMessage { get; }
bool HasErrorMessage { get; }
string ErrorMessage { get; }
string ErrorMessage { get; }
ReactiveCommand<Unit, Unit> Rename { get; }
ReactiveCommand<Unit, Unit> Rename { get; }
ReactiveCommand<Unit, Unit> Close { get; }
ReactiveCommand<Unit, Unit> Close { get; }
ReactiveCommand<Unit, Unit> Open { get; }
}
ReactiveCommand<Unit, Unit> Open { get; }
}

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

@ -3,52 +3,51 @@ using Camelotia.Presentation.Interfaces;
using Camelotia.Services.Interfaces;
using ReactiveUI;
namespace Camelotia.Presentation.ViewModels
namespace Camelotia.Presentation.ViewModels;
public sealed class AuthViewModel : ReactiveObject, IAuthViewModel
{
public sealed class AuthViewModel : ReactiveObject, IAuthViewModel
private readonly ObservableAsPropertyHelper<bool> _isAuthenticated;
private readonly ObservableAsPropertyHelper<bool> _isAnonymous;
private readonly ICloud _provider;
public AuthViewModel(
IDirectAuthViewModel direct,
IHostAuthViewModel host,
IOAuthViewModel open,
ICloud provider)
{
private readonly ObservableAsPropertyHelper<bool> _isAuthenticated;
private readonly ObservableAsPropertyHelper<bool> _isAnonymous;
private readonly ICloud _provider;
OAuth = open;
HostAuth = host;
DirectAuth = direct;
_provider = provider;
public AuthViewModel(
IDirectAuthViewModel direct,
IHostAuthViewModel host,
IOAuthViewModel open,
ICloud provider)
{
OAuth = open;
HostAuth = host;
DirectAuth = direct;
_provider = provider;
_isAuthenticated = _provider
.IsAuthorized
.DistinctUntilChanged()
.Log(this, $"Authentication state changed for {provider.Name}")
.ObserveOn(RxApp.MainThreadScheduler)
.ToProperty(this, x => x.IsAuthenticated);
_isAuthenticated = _provider
.IsAuthorized
.DistinctUntilChanged()
.Log(this, $"Authentication state changed for {provider.Name}")
.ObserveOn(RxApp.MainThreadScheduler)
.ToProperty(this, x => x.IsAuthenticated);
_isAnonymous = this
.WhenAnyValue(x => x.IsAuthenticated)
.Select(authenticated => !authenticated)
.ToProperty(this, x => x.IsAnonymous);
}
public bool IsAnonymous => _isAnonymous.Value;
public bool IsAuthenticated => _isAuthenticated.Value;
public bool SupportsDirectAuth => _provider.SupportsDirectAuth;
public bool SupportsHostAuth => _provider.SupportsHostAuth;
public bool SupportsOAuth => _provider.SupportsOAuth;
public IDirectAuthViewModel DirectAuth { get; }
public IHostAuthViewModel HostAuth { get; }
public IOAuthViewModel OAuth { get; }
_isAnonymous = this
.WhenAnyValue(x => x.IsAuthenticated)
.Select(authenticated => !authenticated)
.ToProperty(this, x => x.IsAnonymous);
}
public bool IsAnonymous => _isAnonymous.Value;
public bool IsAuthenticated => _isAuthenticated.Value;
public bool SupportsDirectAuth => _provider.SupportsDirectAuth;
public bool SupportsHostAuth => _provider.SupportsHostAuth;
public bool SupportsOAuth => _provider.SupportsOAuth;
public IDirectAuthViewModel DirectAuth { get; }
public IHostAuthViewModel HostAuth { get; }
public IOAuthViewModel OAuth { get; }
}

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

@ -14,340 +14,339 @@ using Camelotia.Services.Models;
using ReactiveUI;
using ReactiveUI.Fody.Helpers;
namespace Camelotia.Presentation.ViewModels
namespace Camelotia.Presentation.ViewModels;
public delegate ICloudViewModel CloudViewModelFactory(CloudState state, ICloud provider);
public sealed class CloudViewModel : ReactiveObject, ICloudViewModel, IActivatableViewModel
{
public delegate ICloudViewModel CloudViewModelFactory(CloudState state, ICloud provider);
private readonly ObservableAsPropertyHelper<IEnumerable<IFolderViewModel>> _breadCrumbs;
private readonly ObservableAsPropertyHelper<IEnumerable<IFileViewModel>> _files;
private readonly ObservableAsPropertyHelper<bool> _isCurrentPathEmpty;
private readonly ObservableAsPropertyHelper<bool> _showBreadCrumbs;
private readonly ObservableAsPropertyHelper<bool> _hasErrorMessage;
private readonly ObservableAsPropertyHelper<bool> _hideBreadCrumbs;
private readonly ObservableAsPropertyHelper<string> _currentPath;
private readonly ObservableAsPropertyHelper<bool> _canInteract;
private readonly ObservableAsPropertyHelper<bool> _isLoading;
private readonly ObservableAsPropertyHelper<bool> _canLogout;
private readonly ObservableAsPropertyHelper<bool> _isReady;
private readonly ICloud _cloud;
public sealed class CloudViewModel : ReactiveObject, ICloudViewModel, IActivatableViewModel
public CloudViewModel(
CloudState state,
CreateFolderViewModelFactory createFolderFactory,
RenameFileViewModelFactory renameFactory,
FileViewModelFactory fileFactory,
FolderViewModelFactory folderFactory,
IAuthViewModel auth,
IFileManager files,
ICloud cloud)
{
private readonly ObservableAsPropertyHelper<IEnumerable<IFolderViewModel>> _breadCrumbs;
private readonly ObservableAsPropertyHelper<IEnumerable<IFileViewModel>> _files;
private readonly ObservableAsPropertyHelper<bool> _isCurrentPathEmpty;
private readonly ObservableAsPropertyHelper<bool> _showBreadCrumbs;
private readonly ObservableAsPropertyHelper<bool> _hasErrorMessage;
private readonly ObservableAsPropertyHelper<bool> _hideBreadCrumbs;
private readonly ObservableAsPropertyHelper<string> _currentPath;
private readonly ObservableAsPropertyHelper<bool> _canInteract;
private readonly ObservableAsPropertyHelper<bool> _isLoading;
private readonly ObservableAsPropertyHelper<bool> _canLogout;
private readonly ObservableAsPropertyHelper<bool> _isReady;
private readonly ICloud _cloud;
public CloudViewModel(
CloudState state,
CreateFolderViewModelFactory createFolderFactory,
RenameFileViewModelFactory renameFactory,
FileViewModelFactory fileFactory,
FolderViewModelFactory folderFactory,
IAuthViewModel auth,
IFileManager files,
ICloud cloud)
{
_cloud = cloud;
Folder = createFolderFactory(this);
Rename = renameFactory(this);
Auth = auth;
var canInteract = this
.WhenAnyValue(
x => x.Folder.IsVisible,
x => x.Rename.IsVisible,
(folder, rename) => !folder && !rename);
_canInteract = canInteract
.ToProperty(this, x => x.CanInteract);
var canRefresh = this
.WhenAnyValue(
x => x.Folder.IsVisible,
x => x.Rename.IsVisible,
x => x.Auth.IsAuthenticated,
(folder, rename, authenticated) => !folder && !rename && authenticated);
Refresh = ReactiveCommand.CreateFromTask(
() => cloud.GetFiles(CurrentPath),
canRefresh);
_files = Refresh
.Select(
items => items
.Select(file => fileFactory(file, this))
.OrderByDescending(file => file.IsFolder)
.ThenBy(file => file.Name)
.ToList())
.Where(items => Files == null || !items.SequenceEqual(Files))
.ToProperty(this, x => x.Files);
_isLoading = Refresh
.IsExecuting
.ToProperty(this, x => x.IsLoading);
_isReady = Refresh
.IsExecuting
.Skip(1)
.Select(executing => !executing)
.ToProperty(this, x => x.IsReady);
var canOpenCurrentPath = this
.WhenAnyValue(x => x.SelectedFile)
.Select(file => file != null && file.IsFolder)
.CombineLatest(Refresh.IsExecuting, canInteract, (folder, busy, ci) => folder && ci && !busy);
Open = ReactiveCommand.Create(
() => Path.Combine(CurrentPath, SelectedFile.Name),
canOpenCurrentPath);
var canCurrentPathGoBack = this
.WhenAnyValue(x => x.CurrentPath)
.Where(path => path != null)
.Select(path => path.Length > cloud.InitialPath.Length)
.CombineLatest(Refresh.IsExecuting, canInteract, (valid, busy, ci) => valid && ci && !busy);
Back = ReactiveCommand.Create(
() => Path.GetDirectoryName(CurrentPath),
canCurrentPathGoBack);
SetPath = ReactiveCommand.Create<string, string>(path => path);
_currentPath = Open
.Merge(Back)
.Merge(SetPath)
.Select(path => path ?? cloud.InitialPath)
.DistinctUntilChanged()
.Log(this, $"Current path changed in {cloud.Name}")
.ToProperty(this, x => x.CurrentPath, state.CurrentPath ?? cloud.InitialPath);
var getBreadCrumbs = ReactiveCommand.CreateFromTask(
() => cloud.GetBreadCrumbs(CurrentPath));
_breadCrumbs = getBreadCrumbs
.Where(items => items != null && items.Any())
.Select(items => items.Select(folder => folderFactory(folder, this)))
.ToProperty(this, x => x.BreadCrumbs);
_showBreadCrumbs = getBreadCrumbs
.ThrownExceptions
.Select(exception => false)
.Merge(getBreadCrumbs.Select(items => items != null && items.Any()))
.ObserveOn(RxApp.MainThreadScheduler)
.ToProperty(this, x => x.ShowBreadCrumbs);
_hideBreadCrumbs = this
.WhenAnyValue(x => x.ShowBreadCrumbs)
.Select(show => !show)
.ToProperty(this, x => x.HideBreadCrumbs);
this.WhenAnyValue(x => x.CurrentPath, x => x.IsReady)
.Where(x => x.Item1 != null && x.Item2)
.Select(_ => Unit.Default)
.InvokeCommand(getBreadCrumbs);
this.WhenAnyValue(x => x.CurrentPath)
.Skip(1)
.Select(_ => Unit.Default)
.InvokeCommand(Refresh);
this.WhenAnyValue(x => x.CurrentPath)
.Subscribe(_ => SelectedFile = null);
_isCurrentPathEmpty = this
.WhenAnyValue(x => x.Files)
.Skip(1)
.Where(items => items != null)
.Select(items => !items.Any())
.ToProperty(this, x => x.IsCurrentPathEmpty);
_hasErrorMessage = Refresh
.ThrownExceptions
.Select(exception => true)
.ObserveOn(RxApp.MainThreadScheduler)
.Merge(Refresh.Select(x => false))
.ToProperty(this, x => x.HasErrorMessage);
var canUploadToCurrentPath = this
.WhenAnyValue(x => x.CurrentPath)
.Select(path => path != null)
.CombineLatest(Refresh.IsExecuting, canInteract, (up, loading, can) => up && can && !loading);
UploadToCurrentPath = ReactiveCommand.CreateFromObservable(
() => Observable
.FromAsync(files.OpenRead)
.Where(response => response.Name != null && response.Stream != null)
.Select(args => _cloud.UploadFile(CurrentPath, args.Stream, args.Name))
.SelectMany(task => task.ToObservable()),
canUploadToCurrentPath);
UploadToCurrentPath.InvokeCommand(Refresh);
var canDownloadSelectedFile = this
.WhenAnyValue(x => x.SelectedFile)
.Select(file => file != null && !file.IsFolder)
.CombineLatest(Refresh.IsExecuting, canInteract, (down, loading, can) => down && !loading && can);
_cloud = cloud;
Folder = createFolderFactory(this);
Rename = renameFactory(this);
Auth = auth;
var canInteract = this
.WhenAnyValue(
x => x.Folder.IsVisible,
x => x.Rename.IsVisible,
(folder, rename) => !folder && !rename);
_canInteract = canInteract
.ToProperty(this, x => x.CanInteract);
var canRefresh = this
.WhenAnyValue(
x => x.Folder.IsVisible,
x => x.Rename.IsVisible,
x => x.Auth.IsAuthenticated,
(folder, rename, authenticated) => !folder && !rename && authenticated);
Refresh = ReactiveCommand.CreateFromTask(
() => cloud.GetFiles(CurrentPath),
canRefresh);
_files = Refresh
.Select(
items => items
.Select(file => fileFactory(file, this))
.OrderByDescending(file => file.IsFolder)
.ThenBy(file => file.Name)
.ToList())
.Where(items => Files == null || !items.SequenceEqual(Files))
.ToProperty(this, x => x.Files);
_isLoading = Refresh
.IsExecuting
.ToProperty(this, x => x.IsLoading);
_isReady = Refresh
.IsExecuting
.Skip(1)
.Select(executing => !executing)
.ToProperty(this, x => x.IsReady);
var canOpenCurrentPath = this
.WhenAnyValue(x => x.SelectedFile)
.Select(file => file != null && file.IsFolder)
.CombineLatest(Refresh.IsExecuting, canInteract, (folder, busy, ci) => folder && ci && !busy);
Open = ReactiveCommand.Create(
() => Path.Combine(CurrentPath, SelectedFile.Name),
canOpenCurrentPath);
var canCurrentPathGoBack = this
.WhenAnyValue(x => x.CurrentPath)
.Where(path => path != null)
.Select(path => path.Length > cloud.InitialPath.Length)
.CombineLatest(Refresh.IsExecuting, canInteract, (valid, busy, ci) => valid && ci && !busy);
Back = ReactiveCommand.Create(
() => Path.GetDirectoryName(CurrentPath),
canCurrentPathGoBack);
SetPath = ReactiveCommand.Create<string, string>(path => path);
_currentPath = Open
.Merge(Back)
.Merge(SetPath)
.Select(path => path ?? cloud.InitialPath)
.DistinctUntilChanged()
.Log(this, $"Current path changed in {cloud.Name}")
.ToProperty(this, x => x.CurrentPath, state.CurrentPath ?? cloud.InitialPath);
var getBreadCrumbs = ReactiveCommand.CreateFromTask(
() => cloud.GetBreadCrumbs(CurrentPath));
_breadCrumbs = getBreadCrumbs
.Where(items => items != null && items.Any())
.Select(items => items.Select(folder => folderFactory(folder, this)))
.ToProperty(this, x => x.BreadCrumbs);
_showBreadCrumbs = getBreadCrumbs
.ThrownExceptions
.Select(exception => false)
.Merge(getBreadCrumbs.Select(items => items != null && items.Any()))
.ObserveOn(RxApp.MainThreadScheduler)
.ToProperty(this, x => x.ShowBreadCrumbs);
_hideBreadCrumbs = this
.WhenAnyValue(x => x.ShowBreadCrumbs)
.Select(show => !show)
.ToProperty(this, x => x.HideBreadCrumbs);
this.WhenAnyValue(x => x.CurrentPath, x => x.IsReady)
.Where(x => x.Item1 != null && x.Item2)
.Select(_ => Unit.Default)
.InvokeCommand(getBreadCrumbs);
this.WhenAnyValue(x => x.CurrentPath)
.Skip(1)
.Select(_ => Unit.Default)
.InvokeCommand(Refresh);
this.WhenAnyValue(x => x.CurrentPath)
.Subscribe(_ => SelectedFile = null);
_isCurrentPathEmpty = this
.WhenAnyValue(x => x.Files)
.Skip(1)
.Where(items => items != null)
.Select(items => !items.Any())
.ToProperty(this, x => x.IsCurrentPathEmpty);
_hasErrorMessage = Refresh
.ThrownExceptions
.Select(exception => true)
.ObserveOn(RxApp.MainThreadScheduler)
.Merge(Refresh.Select(x => false))
.ToProperty(this, x => x.HasErrorMessage);
var canUploadToCurrentPath = this
.WhenAnyValue(x => x.CurrentPath)
.Select(path => path != null)
.CombineLatest(Refresh.IsExecuting, canInteract, (up, loading, can) => up && can && !loading);
UploadToCurrentPath = ReactiveCommand.CreateFromObservable(
() => Observable
.FromAsync(files.OpenRead)
.Where(response => response.Name != null && response.Stream != null)
.Select(args => _cloud.UploadFile(CurrentPath, args.Stream, args.Name))
.SelectMany(task => task.ToObservable()),
canUploadToCurrentPath);
UploadToCurrentPath.InvokeCommand(Refresh);
var canDownloadSelectedFile = this
.WhenAnyValue(x => x.SelectedFile)
.Select(file => file != null && !file.IsFolder)
.CombineLatest(Refresh.IsExecuting, canInteract, (down, loading, can) => down && !loading && can);
DownloadSelectedFile = ReactiveCommand.CreateFromObservable(
() => Observable
.FromAsync(() => files.OpenWrite(SelectedFile.Name))
.Where(stream => stream != null)
.Select(stream => _cloud.DownloadFile(SelectedFile.Path, stream))
.SelectMany(task => task.ToObservable()),
canDownloadSelectedFile);
DownloadSelectedFile = ReactiveCommand.CreateFromObservable(
() => Observable
.FromAsync(() => files.OpenWrite(SelectedFile.Name))
.Where(stream => stream != null)
.Select(stream => _cloud.DownloadFile(SelectedFile.Path, stream))
.SelectMany(task => task.ToObservable()),
canDownloadSelectedFile);
var canLogout = cloud
.IsAuthorized
.DistinctUntilChanged()
.Select(loggedIn => loggedIn && (
cloud.SupportsDirectAuth ||
cloud.SupportsOAuth ||
cloud.SupportsHostAuth))
.CombineLatest(canInteract, (logout, interact) => logout && interact)
.ObserveOn(RxApp.MainThreadScheduler);
var canLogout = cloud
.IsAuthorized
.DistinctUntilChanged()
.Select(loggedIn => loggedIn && (
cloud.SupportsDirectAuth ||
cloud.SupportsOAuth ||
cloud.SupportsHostAuth))
.CombineLatest(canInteract, (logout, interact) => logout && interact)
.ObserveOn(RxApp.MainThreadScheduler);
Logout = ReactiveCommand.CreateFromTask(cloud.Logout, canLogout);
Logout = ReactiveCommand.CreateFromTask(cloud.Logout, canLogout);
_canLogout = canLogout
.ToProperty(this, x => x.CanLogout);
_canLogout = canLogout
.ToProperty(this, x => x.CanLogout);
var canDeleteSelection = this
.WhenAnyValue(x => x.SelectedFile)
.Select(file => file != null && !file.IsFolder)
.CombineLatest(Refresh.IsExecuting, canInteract, (del, loading, ci) => del && !loading && ci);
var canDeleteSelection = this
.WhenAnyValue(x => x.SelectedFile)
.Select(file => file != null && !file.IsFolder)
.CombineLatest(Refresh.IsExecuting, canInteract, (del, loading, ci) => del && !loading && ci);
DeleteSelectedFile = ReactiveCommand.CreateFromTask(
() => cloud.Delete(SelectedFile.Path, SelectedFile.IsFolder),
canDeleteSelection);
DeleteSelectedFile = ReactiveCommand.CreateFromTask(
() => cloud.Delete(SelectedFile.Path, SelectedFile.IsFolder),
canDeleteSelection);
DeleteSelectedFile.InvokeCommand(Refresh);
DeleteSelectedFile.InvokeCommand(Refresh);
var canUnselectFile = this
.WhenAnyValue(x => x.SelectedFile)
.Select(selection => selection != null)
.CombineLatest(Refresh.IsExecuting, canInteract, (sel, loading, ci) => sel && !loading && ci);
var canUnselectFile = this
.WhenAnyValue(x => x.SelectedFile)
.Select(selection => selection != null)
.CombineLatest(Refresh.IsExecuting, canInteract, (sel, loading, ci) => sel && !loading && ci);
UnselectFile = ReactiveCommand.Create(
() => { SelectedFile = null; },
canUnselectFile);
UnselectFile = ReactiveCommand.Create(
() => { SelectedFile = null; },
canUnselectFile);
UploadToCurrentPath.ThrownExceptions
.Merge(DeleteSelectedFile.ThrownExceptions)
.Merge(DownloadSelectedFile.ThrownExceptions)
.Merge(Refresh.ThrownExceptions)
.Merge(getBreadCrumbs.ThrownExceptions)
.Log(this, $"Exception occured in provider {cloud.Name}")
.Subscribe();
UploadToCurrentPath.ThrownExceptions
.Merge(DeleteSelectedFile.ThrownExceptions)
.Merge(DownloadSelectedFile.ThrownExceptions)
.Merge(Refresh.ThrownExceptions)
.Merge(getBreadCrumbs.ThrownExceptions)
.Log(this, $"Exception occured in provider {cloud.Name}")
.Subscribe();
this.WhenAnyValue(x => x.CurrentPath)
.Subscribe(path => state.CurrentPath = path);
this.WhenAnyValue(x => x.CurrentPath)
.Subscribe(path => state.CurrentPath = path);
this.WhenAnyValue(x => x.Auth.IsAuthenticated)
.Select(authenticated => authenticated ? _cloud.Parameters?.Token : null)
.Subscribe(token => state.Token = token);
this.WhenAnyValue(x => x.Auth.IsAuthenticated)
.Select(authenticated => authenticated ? _cloud.Parameters?.Token : null)
.Subscribe(token => state.Token = token);
this.WhenAnyValue(x => x.Auth.IsAuthenticated)
.Select(authenticated => authenticated ? _cloud.Parameters?.User : null)
.Subscribe(user => state.User = user);
this.WhenAnyValue(x => x.Auth.IsAuthenticated)
.Select(authenticated => authenticated ? _cloud.Parameters?.User : null)
.Subscribe(user => state.User = user);
this.WhenActivated(ActivateAutoRefresh);
}
this.WhenActivated(ActivateAutoRefresh);
}
[Reactive]
public int RefreshingIn { get; private set; }
[Reactive]
public int RefreshingIn { get; private set; }
[Reactive]
public IFileViewModel SelectedFile { get; set; }
[Reactive]
public IFileViewModel SelectedFile { get; set; }
public bool IsCurrentPathEmpty => _isCurrentPathEmpty.Value;
public bool IsCurrentPathEmpty => _isCurrentPathEmpty.Value;
public IEnumerable<IFileViewModel> Files => _files.Value;
public IEnumerable<IFileViewModel> Files => _files.Value;
public IEnumerable<IFolderViewModel> BreadCrumbs => _breadCrumbs.Value;
public IEnumerable<IFolderViewModel> BreadCrumbs => _breadCrumbs.Value;
public bool ShowBreadCrumbs => _showBreadCrumbs.Value;
public bool ShowBreadCrumbs => _showBreadCrumbs.Value;
public bool HideBreadCrumbs => _hideBreadCrumbs.Value;
public bool HideBreadCrumbs => _hideBreadCrumbs.Value;
public string CurrentPath => _currentPath?.Value;
public string CurrentPath => _currentPath?.Value;
public bool CanLogout => _canLogout.Value;
public bool CanLogout => _canLogout.Value;
public bool IsLoading => _isLoading.Value;
public bool IsLoading => _isLoading.Value;
public bool HasErrorMessage => _hasErrorMessage.Value;
public bool HasErrorMessage => _hasErrorMessage.Value;
public bool IsReady => _isReady.Value;
public bool IsReady => _isReady.Value;
public bool CanInteract => _canInteract?.Value ?? false;
public bool CanInteract => _canInteract?.Value ?? false;
public IAuthViewModel Auth { get; }
public IAuthViewModel Auth { get; }
public IRenameFileViewModel Rename { get; }
public IRenameFileViewModel Rename { get; }
public ICreateFolderViewModel Folder { get; }
public ICreateFolderViewModel Folder { get; }
public ViewModelActivator Activator { get; } = new ViewModelActivator();
public ViewModelActivator Activator { get; } = new();
public Guid Id => _cloud.Id;
public Guid Id => _cloud.Id;
public string Name => _cloud.Name;
public string Name => _cloud.Name;
public DateTime Created => _cloud.Created;
public DateTime Created => _cloud.Created;
public string Size => _cloud.Size?.ByteSizeToString() ?? "Unknown";
public string Size => _cloud.Size?.ByteSizeToString() ?? "Unknown";
public string Description => $"{_cloud.Name} file system.";
public string Description => $"{_cloud.Name} file system.";
public ReactiveCommand<Unit, Unit> DownloadSelectedFile { get; }
public ReactiveCommand<Unit, Unit> DownloadSelectedFile { get; }
public ReactiveCommand<Unit, Unit> UploadToCurrentPath { get; }
public ReactiveCommand<Unit, Unit> UploadToCurrentPath { get; }
public ReactiveCommand<Unit, Unit> DeleteSelectedFile { get; }
public ReactiveCommand<Unit, Unit> DeleteSelectedFile { get; }
public ReactiveCommand<Unit, Unit> UnselectFile { get; }
public ReactiveCommand<Unit, Unit> UnselectFile { get; }
public ReactiveCommand<Unit, IEnumerable<FileModel>> Refresh { get; }
public ReactiveCommand<Unit, IEnumerable<FileModel>> Refresh { get; }
public ReactiveCommand<Unit, Unit> Logout { get; }
public ReactiveCommand<Unit, Unit> Logout { get; }
public ReactiveCommand<Unit, string> Back { get; }
public ReactiveCommand<Unit, string> Back { get; }
public ReactiveCommand<Unit, string> Open { get; }
public ReactiveCommand<Unit, string> Open { get; }
public ReactiveCommand<string, string> SetPath { get; }
public ReactiveCommand<string, string> SetPath { get; }
private void ActivateAutoRefresh(CompositeDisposable disposable)
{
this.WhenAnyValue(x => x.Auth.IsAuthenticated)
.Where(authenticated => authenticated)
.Select(_ => Unit.Default)
.InvokeCommand(Refresh)
.DisposeWith(disposable);
private void ActivateAutoRefresh(CompositeDisposable disposable)
{
this.WhenAnyValue(x => x.Auth.IsAuthenticated)
.Where(authenticated => authenticated)
.Select(_ => Unit.Default)
.InvokeCommand(Refresh)
.DisposeWith(disposable);
Observable.Timer(TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(1))
.Select(_ => RefreshingIn - 1)
.Where(value => value >= 0)
.ObserveOn(RxApp.MainThreadScheduler)
.Subscribe(x => RefreshingIn = x)
.DisposeWith(disposable);
Observable.Timer(TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(1))
.Select(_ => RefreshingIn - 1)
.Where(value => value >= 0)
.ObserveOn(RxApp.MainThreadScheduler)
.Subscribe(x => RefreshingIn = x)
.DisposeWith(disposable);
this.WhenAnyValue(x => x.RefreshingIn)
.Skip(1)
.Where(refreshing => refreshing == 0)
.Log(this, $"Refreshing provider {_cloud.Name} path {CurrentPath}")
.Select(_ => Unit.Default)
.InvokeCommand(Refresh)
.DisposeWith(disposable);
this.WhenAnyValue(x => x.RefreshingIn)
.Skip(1)
.Where(refreshing => refreshing == 0)
.Log(this, $"Refreshing provider {_cloud.Name} path {CurrentPath}")
.Select(_ => Unit.Default)
.InvokeCommand(Refresh)
.DisposeWith(disposable);
Refresh.Select(_ => 30)
.StartWith(30)
.ObserveOn(RxApp.MainThreadScheduler)
.Subscribe(x => RefreshingIn = x)
.DisposeWith(disposable);
Refresh.Select(_ => 30)
.StartWith(30)
.ObserveOn(RxApp.MainThreadScheduler)
.Subscribe(x => RefreshingIn = x)
.DisposeWith(disposable);
this.WhenAnyValue(x => x.CanInteract)
.Skip(1)
.Where(interact => interact)
.Select(x => Unit.Default)
.InvokeCommand(Refresh)
.DisposeWith(disposable);
}
this.WhenAnyValue(x => x.CanInteract)
.Skip(1)
.Where(interact => interact)
.Select(x => Unit.Default)
.InvokeCommand(Refresh)
.DisposeWith(disposable);
}
}

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

@ -9,107 +9,106 @@ using ReactiveUI.Fody.Helpers;
using ReactiveUI.Validation.Extensions;
using ReactiveUI.Validation.Helpers;
namespace Camelotia.Presentation.ViewModels
namespace Camelotia.Presentation.ViewModels;
public delegate ICreateFolderViewModel CreateFolderViewModelFactory(ICloudViewModel providerViewModel);
public sealed class CreateFolderViewModel : ReactiveValidationObject, ICreateFolderViewModel
{
public delegate ICreateFolderViewModel CreateFolderViewModelFactory(ICloudViewModel providerViewModel);
private readonly ObservableAsPropertyHelper<string> _errorMessage;
private readonly ObservableAsPropertyHelper<bool> _hasErrorMessage;
private readonly ObservableAsPropertyHelper<bool> _isLoading;
private readonly ObservableAsPropertyHelper<string> _path;
public sealed class CreateFolderViewModel : ReactiveValidationObject, ICreateFolderViewModel
public CreateFolderViewModel(CreateFolderState state, ICloudViewModel owner, ICloud provider)
{
private readonly ObservableAsPropertyHelper<string> _errorMessage;
private readonly ObservableAsPropertyHelper<bool> _hasErrorMessage;
private readonly ObservableAsPropertyHelper<bool> _isLoading;
private readonly ObservableAsPropertyHelper<string> _path;
_path = owner
.WhenAnyValue(x => x.CurrentPath)
.ToProperty(this, x => x.Path);
public CreateFolderViewModel(CreateFolderState state, ICloudViewModel owner, ICloud provider)
{
_path = owner
.WhenAnyValue(x => x.CurrentPath)
.ToProperty(this, x => x.Path);
this.ValidationRule(
x => x.Name,
name => !string.IsNullOrWhiteSpace(name),
"Folder name shouldn't be empty.");
this.ValidationRule(
x => x.Name,
name => !string.IsNullOrWhiteSpace(name),
"Folder name shouldn't be empty.");
var pathRule = this.ValidationRule(
x => x.Path,
path => !string.IsNullOrWhiteSpace(path),
"Path shouldn't be empty");
var pathRule = this.ValidationRule(
x => x.Path,
path => !string.IsNullOrWhiteSpace(path),
"Path shouldn't be empty");
Create = ReactiveCommand.CreateFromTask(
() => provider.CreateFolder(Path, Name),
this.IsValid());
Create = ReactiveCommand.CreateFromTask(
() => provider.CreateFolder(Path, Name),
this.IsValid());
_isLoading = Create.IsExecuting
.ToProperty(this, x => x.IsLoading);
_isLoading = Create.IsExecuting
.ToProperty(this, x => x.IsLoading);
var canInteract = owner
.WhenAnyValue(x => x.CanInteract)
.Skip(1)
.StartWith(true);
var canInteract = owner
.WhenAnyValue(x => x.CanInteract)
.Skip(1)
.StartWith(true);
var canOpen = this
.WhenAnyValue(x => x.IsVisible)
.Select(visible => !visible)
.CombineLatest(
canInteract,
pathRule.WhenAnyValue(x => x.IsValid),
(visible, interact, path) => visible && provider.CanCreateFolder && interact && path);
var canOpen = this
.WhenAnyValue(x => x.IsVisible)
.Select(visible => !visible)
.CombineLatest(
canInteract,
pathRule.WhenAnyValue(x => x.IsValid),
(visible, interact, path) => visible && provider.CanCreateFolder && interact && path);
var canClose = this
.WhenAnyValue(x => x.IsVisible)
.Select(visible => visible);
var canClose = this
.WhenAnyValue(x => x.IsVisible)
.Select(visible => visible);
Open = ReactiveCommand.Create(() => { }, canOpen);
Close = ReactiveCommand.Create(() => { }, canClose);
Open = ReactiveCommand.Create(() => { }, canOpen);
Close = ReactiveCommand.Create(() => { }, canClose);
Open.Select(unit => true)
.Merge(Close.Select(unit => false))
.Subscribe(visible => IsVisible = visible);
Open.Select(unit => true)
.Merge(Close.Select(unit => false))
.Subscribe(visible => IsVisible = visible);
Close.Subscribe(x => Name = string.Empty);
Create.InvokeCommand(Close);
Close.Subscribe(x => Name = string.Empty);
Create.InvokeCommand(Close);
_hasErrorMessage = Create
.ThrownExceptions
.Select(exception => true)
.Merge(Close.Select(unit => false))
.ToProperty(this, x => x.HasErrorMessage);
_hasErrorMessage = Create
.ThrownExceptions
.Select(exception => true)
.Merge(Close.Select(unit => false))
.ToProperty(this, x => x.HasErrorMessage);
_errorMessage = Create
.ThrownExceptions
.Select(exception => exception.Message)
.Log(this, $"Create folder error occured in {provider.Name}")
.Merge(Close.Select(unit => string.Empty))
.ToProperty(this, x => x.ErrorMessage);
_errorMessage = Create
.ThrownExceptions
.Select(exception => exception.Message)
.Log(this, $"Create folder error occured in {provider.Name}")
.Merge(Close.Select(unit => string.Empty))
.ToProperty(this, x => x.ErrorMessage);
Name = state.Name;
IsVisible = state.IsVisible;
Name = state.Name;
IsVisible = state.IsVisible;
this.WhenAnyValue(x => x.Name)
.Subscribe(name => state.Name = name);
this.WhenAnyValue(x => x.IsVisible)
.Subscribe(visible => state.IsVisible = visible);
}
[Reactive]
public string Name { get; set; }
[Reactive]
public bool IsVisible { get; set; }
public string ErrorMessage => _errorMessage.Value;
public bool HasErrorMessage => _hasErrorMessage.Value;
public bool IsLoading => _isLoading.Value;
public string Path => _path.Value;
public ReactiveCommand<Unit, Unit> Create { get; }
public ReactiveCommand<Unit, Unit> Close { get; }
public ReactiveCommand<Unit, Unit> Open { get; }
this.WhenAnyValue(x => x.Name)
.Subscribe(name => state.Name = name);
this.WhenAnyValue(x => x.IsVisible)
.Subscribe(visible => state.IsVisible = visible);
}
[Reactive]
public string Name { get; set; }
[Reactive]
public bool IsVisible { get; set; }
public string ErrorMessage => _errorMessage.Value;
public bool HasErrorMessage => _hasErrorMessage.Value;
public bool IsLoading => _isLoading.Value;
public string Path => _path.Value;
public ReactiveCommand<Unit, Unit> Create { get; }
public ReactiveCommand<Unit, Unit> Close { get; }
public ReactiveCommand<Unit, Unit> Open { get; }
}

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

@ -9,67 +9,66 @@ using ReactiveUI.Fody.Helpers;
using ReactiveUI.Validation.Extensions;
using ReactiveUI.Validation.Helpers;
namespace Camelotia.Presentation.ViewModels
namespace Camelotia.Presentation.ViewModels;
public sealed class DirectAuthViewModel : ReactiveValidationObject, IDirectAuthViewModel
{
public sealed class DirectAuthViewModel : ReactiveValidationObject, IDirectAuthViewModel
private readonly ObservableAsPropertyHelper<string> _errorMessage;
private readonly ObservableAsPropertyHelper<bool> _hasErrorMessage;
private readonly ObservableAsPropertyHelper<bool> _isBusy;
public DirectAuthViewModel(DirectAuthState state, ICloud provider)
{
private readonly ObservableAsPropertyHelper<string> _errorMessage;
private readonly ObservableAsPropertyHelper<bool> _hasErrorMessage;
private readonly ObservableAsPropertyHelper<bool> _isBusy;
this.ValidationRule(
x => x.Username,
name => !string.IsNullOrWhiteSpace(name),
"User name shouldn't be null or white space.");
public DirectAuthViewModel(DirectAuthState state, ICloud provider)
{
this.ValidationRule(
x => x.Username,
name => !string.IsNullOrWhiteSpace(name),
"User name shouldn't be null or white space.");
this.ValidationRule(
x => x.Password,
pass => !string.IsNullOrWhiteSpace(pass),
"Password shouldn't be null or white space.");
this.ValidationRule(
x => x.Password,
pass => !string.IsNullOrWhiteSpace(pass),
"Password shouldn't be null or white space.");
Login = ReactiveCommand.CreateFromTask(
() => provider.DirectAuth(Username, Password),
this.IsValid());
Login = ReactiveCommand.CreateFromTask(
() => provider.DirectAuth(Username, Password),
this.IsValid());
_isBusy = Login
.IsExecuting
.ToProperty(this, x => x.IsBusy);
_isBusy = Login
.IsExecuting
.ToProperty(this, x => x.IsBusy);
_errorMessage = Login
.ThrownExceptions
.Select(exception => exception.Message)
.Log(this, $"Direct auth error occured in {provider.Name}")
.ToProperty(this, x => x.ErrorMessage);
_errorMessage = Login
.ThrownExceptions
.Select(exception => exception.Message)
.Log(this, $"Direct auth error occured in {provider.Name}")
.ToProperty(this, x => x.ErrorMessage);
_hasErrorMessage = Login
.ThrownExceptions
.Select(exception => true)
.Merge(Login.Select(unit => false))
.ToProperty(this, x => x.HasErrorMessage);
_hasErrorMessage = Login
.ThrownExceptions
.Select(exception => true)
.Merge(Login.Select(unit => false))
.ToProperty(this, x => x.HasErrorMessage);
Username = state.Username;
Password = state.Password;
Username = state.Username;
Password = state.Password;
this.WhenAnyValue(x => x.Username)
.Subscribe(name => state.Username = name);
this.WhenAnyValue(x => x.Password)
.Subscribe(pass => state.Password = pass);
}
[Reactive]
public string Username { get; set; }
[Reactive]
public string Password { get; set; }
public bool IsBusy => _isBusy.Value;
public string ErrorMessage => _errorMessage.Value;
public bool HasErrorMessage => _hasErrorMessage.Value;
public ReactiveCommand<Unit, Unit> Login { get; }
this.WhenAnyValue(x => x.Username)
.Subscribe(name => state.Username = name);
this.WhenAnyValue(x => x.Password)
.Subscribe(pass => state.Password = pass);
}
[Reactive]
public string Username { get; set; }
[Reactive]
public string Password { get; set; }
public bool IsBusy => _isBusy.Value;
public string ErrorMessage => _errorMessage.Value;
public bool HasErrorMessage => _hasErrorMessage.Value;
public ReactiveCommand<Unit, Unit> Login { get; }
}

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

@ -4,55 +4,54 @@ using Camelotia.Presentation.Interfaces;
using Camelotia.Services.Models;
using ReactiveUI;
namespace Camelotia.Presentation.ViewModels
namespace Camelotia.Presentation.ViewModels;
public delegate IFileViewModel FileViewModelFactory(FileModel file, ICloudViewModel provider);
public sealed class FileViewModel : ReactiveObject, IFileViewModel
{
public delegate IFileViewModel FileViewModelFactory(FileModel file, ICloudViewModel provider);
private readonly FileModel _file;
public sealed class FileViewModel : ReactiveObject, IFileViewModel
public FileViewModel(ICloudViewModel provider, FileModel file)
{
private readonly FileModel _file;
Provider = provider;
_file = file;
}
public FileViewModel(ICloudViewModel provider, FileModel file)
public ICloudViewModel Provider { get; }
public string Modified => _file.Modified?.ToString(CultureInfo.InvariantCulture) ?? "A long time ago";
public string Size => _file.Size == 0 ? "Unknown" : _file.Size.ByteSizeToString();
public bool IsFolder => _file.IsFolder;
public bool IsFile => !_file.IsFolder;
public string Name => _file.Name;
public string Path => _file.Path;
public override bool Equals(object obj)
{
if (obj is FileViewModel other)
{
Provider = provider;
_file = file;
return Equals(_file.Path, other._file.Path) &&
Equals(_file.Modified, other._file.Modified) &&
Equals(Provider, other.Provider);
}
public ICloudViewModel Provider { get; }
return false;
}
public string Modified => _file.Modified?.ToString(CultureInfo.InvariantCulture) ?? "A long time ago";
public string Size => _file.Size == 0 ? "Unknown" : _file.Size.ByteSizeToString();
public bool IsFolder => _file.IsFolder;
public bool IsFile => !_file.IsFolder;
public string Name => _file.Name;
public string Path => _file.Path;
public override bool Equals(object obj)
public override int GetHashCode()
{
unchecked
{
if (obj is FileViewModel other)
{
return Equals(_file.Path, other._file.Path) &&
Equals(_file.Modified, other._file.Modified) &&
Equals(Provider, other.Provider);
}
return false;
}
public override int GetHashCode()
{
unchecked
{
var hashCode = _file.Path.GetHashCode();
hashCode = (hashCode * 397) ^ _file.Modified.GetHashCode();
hashCode = (hashCode * 397) ^ Provider.GetHashCode();
return hashCode;
}
var hashCode = _file.Path.GetHashCode();
hashCode = (hashCode * 397) ^ _file.Modified.GetHashCode();
hashCode = (hashCode * 397) ^ Provider.GetHashCode();
return hashCode;
}
}
}
}

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

@ -4,48 +4,47 @@ using Camelotia.Presentation.Interfaces;
using Camelotia.Services.Models;
using ReactiveUI;
namespace Camelotia.Presentation.ViewModels
namespace Camelotia.Presentation.ViewModels;
public delegate IFolderViewModel FolderViewModelFactory(FolderModel folder, ICloudViewModel provider);
public sealed class FolderViewModel : ReactiveObject, IFolderViewModel
{
public delegate IFolderViewModel FolderViewModelFactory(FolderModel folder, ICloudViewModel provider);
private readonly FolderModel _folder;
public sealed class FolderViewModel : ReactiveObject, IFolderViewModel
public FolderViewModel(ICloudViewModel provider, FolderModel folder)
{
private readonly FolderModel _folder;
Provider = provider;
_folder = folder;
Children = folder.Children?.Any() == true
? folder.Children.Select(f => new FolderViewModel(provider, f))
: Enumerable.Empty<IFolderViewModel>();
}
public FolderViewModel(ICloudViewModel provider, FolderModel folder)
public ICloudViewModel Provider { get; }
public string Name => _folder.Name;
public string FullPath => _folder.FullPath;
public IEnumerable<IFolderViewModel> Children { get; }
public override bool Equals(object obj)
{
return obj is FolderViewModel other &&
Equals(_folder.Name, other._folder.Name) &&
Equals(_folder.FullPath, other._folder.FullPath) &&
Equals(Provider, other.Provider);
}
public override int GetHashCode()
{
unchecked
{
Provider = provider;
_folder = folder;
Children = folder.Children?.Any() == true
? folder.Children.Select(f => new FolderViewModel(provider, f))
: Enumerable.Empty<IFolderViewModel>();
}
public ICloudViewModel Provider { get; }
public string Name => _folder.Name;
public string FullPath => _folder.FullPath;
public IEnumerable<IFolderViewModel> Children { get; }
public override bool Equals(object obj)
{
return obj is FolderViewModel other &&
Equals(_folder.Name, other._folder.Name) &&
Equals(_folder.FullPath, other._folder.FullPath) &&
Equals(Provider, other.Provider);
}
public override int GetHashCode()
{
unchecked
{
var hashCode = _folder.Name.GetHashCode();
hashCode = (hashCode * 397) ^ _folder.FullPath.GetHashCode();
hashCode = (hashCode * 397) ^ Provider.GetHashCode();
return hashCode;
}
var hashCode = _folder.Name.GetHashCode();
hashCode = (hashCode * 397) ^ _folder.FullPath.GetHashCode();
hashCode = (hashCode * 397) ^ Provider.GetHashCode();
return hashCode;
}
}
}
}

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