diff --git a/src/Common.ClientLib.Droid/Extensions/UriExtensions.cs b/src/Common.ClientLib.Droid/Extensions/UriExtensions.cs
new file mode 100644
index 000000000..2522c03d0
--- /dev/null
+++ b/src/Common.ClientLib.Droid/Extensions/UriExtensions.cs
@@ -0,0 +1,333 @@
+using Android;
+using Android.OS;
+using Android.Provider;
+using Android.Webkit;
+using System.IO;
+using Xamarin.Essentials;
+using AndroidUri = Android.Net.Uri;
+using XEPlatform = Xamarin.Essentials.Platform;
+
+// ReSharper disable once CheckNamespace
+namespace System
+{
+ public static class UriExtensions
+ {
+ // https://github.com/xamarin/Essentials/blob/main/Xamarin.Essentials/FileSystem/FileSystem.android.cs
+
+ const string storageTypePrimary = "primary";
+ const string storageTypeRaw = "raw";
+ const string storageTypeImage = "image";
+ const string storageTypeVideo = "video";
+ const string storageTypeAudio = "audio";
+ static readonly string[] contentUriPrefixes =
+ {
+ "content://downloads/public_downloads",
+ "content://downloads/my_downloads",
+ "content://downloads/all_downloads",
+ };
+
+ internal const string UriSchemeFile = "file";
+ internal const string UriSchemeContent = "content";
+
+ internal const string UriAuthorityExternalStorage = "com.android.externalstorage.documents";
+ internal const string UriAuthorityDownloads = "com.android.providers.downloads.documents";
+ internal const string UriAuthorityMedia = "com.android.providers.media.documents";
+
+ public static string? EnsurePhysicalPath(this AndroidUri? uri, bool requireExtendedAccess = true)
+ {
+ if (uri != null)
+ {
+ // if this is a file, use that
+ if (uri.Scheme!.Equals(UriSchemeFile, StringComparison.OrdinalIgnoreCase))
+ return uri.Path;
+
+ // try resolve using the content provider
+ var absolute = ResolvePhysicalPath(uri, requireExtendedAccess);
+ if (!string.IsNullOrWhiteSpace(absolute) && Path.IsPathRooted(absolute))
+ return absolute;
+
+ // fall back to just copying it
+ var cached = CacheContentFile(uri);
+ if (!string.IsNullOrWhiteSpace(cached) && Path.IsPathRooted(cached))
+ return cached;
+ }
+
+ return null;
+ }
+
+ static string? ResolvePhysicalPath(AndroidUri uri, bool requireExtendedAccess = true)
+ {
+ if (uri.Scheme!.Equals(UriSchemeFile, StringComparison.OrdinalIgnoreCase))
+ {
+ // if it is a file, then return directly
+
+ var resolved = uri.Path;
+ if (File.Exists(resolved))
+ return resolved;
+ }
+ else if (!requireExtendedAccess || !((int)Build.VERSION.SdkInt >= (int)BuildVersionCodes.Q))
+ {
+ // if this is on an older OS version, or we just need it now
+
+ if (DocumentsContract.IsDocumentUri(XEPlatform.AppContext, uri))
+ {
+ var resolved = ResolveDocumentPath(uri);
+ if (File.Exists(resolved))
+ return resolved;
+ }
+ else if (uri.Scheme!.Equals(UriSchemeContent, StringComparison.OrdinalIgnoreCase))
+ {
+ var resolved = ResolveContentPath(uri);
+ if (File.Exists(resolved))
+ return resolved;
+ }
+ }
+
+ return null;
+ }
+
+ static string? ResolveDocumentPath(AndroidUri uri)
+ {
+ var docId = DocumentsContract.GetDocumentId(uri);
+
+ var docIdParts = docId?.Split(':');
+ if (docIdParts == null || docIdParts.Length == 0)
+ return null;
+
+ if (uri.Authority!.Equals(UriAuthorityExternalStorage, StringComparison.OrdinalIgnoreCase))
+ {
+ if (docIdParts.Length == 2)
+ {
+ var storageType = docIdParts[0];
+ var uriPath = docIdParts[1];
+
+ // This is the internal "external" memory, NOT the SD Card
+ if (storageType.Equals(storageTypePrimary, StringComparison.OrdinalIgnoreCase))
+ {
+#pragma warning disable CS0618 // Type or member is obsolete
+ var root = Android.OS.Environment.ExternalStorageDirectory!.Path;
+#pragma warning restore CS0618 // Type or member is obsolete
+
+ return Path.Combine(root, uriPath);
+ }
+
+ // TODO: support other types, such as actual SD Cards
+ }
+ }
+ else if (uri.Authority.Equals(UriAuthorityDownloads, StringComparison.OrdinalIgnoreCase))
+ {
+ // NOTE: This only really applies to older Android vesions since the privacy changes
+
+ if (docIdParts.Length == 2)
+ {
+ var storageType = docIdParts[0];
+ var uriPath = docIdParts[1];
+
+ if (storageType.Equals(storageTypeRaw, StringComparison.OrdinalIgnoreCase))
+ return uriPath;
+ }
+
+ // ID could be "###" or "msf:###"
+ var fileId = docIdParts.Length == 2
+ ? docIdParts[1]
+ : docIdParts[0];
+
+ foreach (var prefix in contentUriPrefixes)
+ {
+ var uriString = prefix + "/" + fileId;
+ var contentUri = AndroidUri.Parse(uriString);
+
+ if (GetDataFilePath(contentUri!) is string filePath)
+ return filePath;
+ }
+ }
+ else if (uri.Authority.Equals(UriAuthorityMedia, StringComparison.OrdinalIgnoreCase))
+ {
+ if (docIdParts.Length == 2)
+ {
+ var storageType = docIdParts[0];
+ var uriPath = docIdParts[1];
+
+ AndroidUri? contentUri = null;
+ if (storageType.Equals(storageTypeImage, StringComparison.OrdinalIgnoreCase))
+ contentUri = MediaStore.Images.Media.ExternalContentUri!;
+ else if (storageType.Equals(storageTypeVideo, StringComparison.OrdinalIgnoreCase))
+ contentUri = MediaStore.Video.Media.ExternalContentUri!;
+ else if (storageType.Equals(storageTypeAudio, StringComparison.OrdinalIgnoreCase))
+ contentUri = MediaStore.Audio.Media.ExternalContentUri!;
+
+#pragma warning disable CS0618 // 类型或成员已过时
+ if (contentUri != null && GetDataFilePath(contentUri, $"{MediaStore.MediaColumns.Id!}=?", new[] { uriPath }) is string filePath)
+#pragma warning restore CS0618 // 类型或成员已过时
+ return filePath;
+ }
+ }
+
+ return null;
+ }
+
+ static string? ResolveContentPath(AndroidUri uri)
+ {
+ if (GetDataFilePath(uri) is string filePath)
+ return filePath;
+
+ // TODO: support some additional things, like Google Photos if that is possible
+
+ return null;
+ }
+
+ static string? GetDataFilePath(AndroidUri contentUri, string? selection = null, string[]? selectionArgs = null)
+ {
+#pragma warning disable CS0618 // Type or member is obsolete
+ const string column = MediaStore.Files.FileColumns.Data;
+#pragma warning restore CS0618 // Type or member is obsolete
+
+ // ask the content provider for the data column, which may contain the actual file path
+ var path = GetColumnValue(contentUri, column, selection, selectionArgs);
+ if (!string.IsNullOrEmpty(path) && Path.IsPathRooted(path))
+ return path;
+
+ return null;
+ }
+
+ static string? GetColumnValue(AndroidUri contentUri, string column, string? selection = null, string[]? selectionArgs = null)
+ {
+ try
+ {
+ var value = QueryContentResolverColumn(contentUri, column, selection, selectionArgs);
+ if (!string.IsNullOrEmpty(value))
+ return value;
+ }
+ catch
+ {
+ // Ignore all exceptions and use null for the error indicator
+ }
+
+ return null;
+ }
+
+ static string? QueryContentResolverColumn(AndroidUri contentUri, string columnName, string? selection = null, string[]? selectionArgs = null)
+ {
+ string? text = null;
+
+ var projection = new[] { columnName };
+ using var cursor = XEPlatform.AppContext.ContentResolver!.Query(contentUri, projection, selection, selectionArgs, null);
+ if (cursor?.MoveToFirst() == true)
+ {
+ var columnIndex = cursor.GetColumnIndex(columnName);
+ if (columnIndex != -1)
+ text = cursor.GetString(columnIndex);
+ }
+
+ return text;
+ }
+
+ static string? CacheContentFile(AndroidUri uri)
+ {
+ if (!uri.Scheme!.Equals(UriSchemeContent, StringComparison.OrdinalIgnoreCase))
+ return null;
+
+ // open the source stream
+ using var srcStream = OpenContentStream(uri, out var extension);
+ if (srcStream == null)
+ return null;
+
+ // resolve or generate a valid destination path
+#pragma warning disable CS0618 // 类型或成员已过时
+ var filename = GetColumnValue(uri, MediaStore.Files.FileColumns.DisplayName) ?? Guid.NewGuid().ToString("N");
+#pragma warning restore CS0618 // 类型或成员已过时
+ if (!Path.HasExtension(filename) && !string.IsNullOrEmpty(extension))
+ filename = Path.ChangeExtension(filename, extension);
+
+ // create a temporary file
+ var hasPermission = Permissions.IsDeclaredInManifest(Manifest.Permission.WriteExternalStorage);
+ var root = hasPermission
+ ? XEPlatform.AppContext.ExternalCacheDir
+ : XEPlatform.AppContext.CacheDir;
+ var tmpFile = GetEssentialsTemporaryFile(root, filename);
+
+ // copy to the destination
+ using var dstStream = File.Create(tmpFile.CanonicalPath);
+ srcStream.CopyTo(dstStream);
+
+ return tmpFile.CanonicalPath;
+ }
+
+ static Stream? OpenContentStream(AndroidUri uri, out string? extension)
+ {
+ var isVirtual = IsVirtualFile(uri);
+ if (isVirtual)
+ {
+ return GetVirtualFileStream(uri, out extension);
+ }
+
+ extension = GetFileExtension(uri);
+ return XEPlatform.AppContext.ContentResolver!.OpenInputStream(uri);
+ }
+
+ static string? GetFileExtension(AndroidUri uri)
+ {
+ var mimeType = XEPlatform.AppContext.ContentResolver!.GetType(uri);
+
+ return mimeType != null
+ ? MimeTypeMap.Singleton!.GetExtensionFromMimeType(mimeType)
+ : null;
+ }
+
+ static bool IsVirtualFile(AndroidUri uri)
+ {
+ if (!DocumentsContract.IsDocumentUri(XEPlatform.AppContext, uri))
+ return false;
+
+ var value = GetColumnValue(uri, DocumentsContract.Document.ColumnFlags);
+ if (!string.IsNullOrEmpty(value) && int.TryParse(value, out var flagsInt))
+ {
+ var flags = (DocumentContractFlags)flagsInt;
+ return flags.HasFlag(DocumentContractFlags.VirtualDocument);
+ }
+
+ return false;
+ }
+
+ static Stream? GetVirtualFileStream(AndroidUri uri, out string? extension)
+ {
+ var mimeTypes = XEPlatform.AppContext.ContentResolver!.GetStreamTypes(uri, MediaTypeNames.All);
+ if (mimeTypes?.Length >= 1)
+ {
+ var mimeType = mimeTypes[0];
+
+ var stream = XEPlatform.AppContext.ContentResolver!
+ .OpenTypedAssetFileDescriptor(uri, mimeType, null)!
+ .CreateInputStream();
+
+ extension = MimeTypeMap.Singleton!.GetExtensionFromMimeType(mimeType);
+
+ return stream;
+ }
+
+ extension = null;
+ return null;
+ }
+
+ internal static Java.IO.File? GetEssentialsTemporaryFile(Java.IO.File root, string fileName)
+ {
+ // create the directory for all Essentials files
+ var rootDir = new Java.IO.File(root, EssentialsFolderHash);
+ rootDir.Mkdirs();
+ rootDir.DeleteOnExit();
+
+ // create a unique directory just in case there are multiple file with the same name
+ var tmpDir = new Java.IO.File(rootDir, Guid.NewGuid().ToString("N"));
+ tmpDir.Mkdirs();
+ tmpDir.DeleteOnExit();
+
+ // create the new temporary file
+ var tmpFile = new Java.IO.File(tmpDir, fileName);
+ tmpFile.DeleteOnExit();
+
+ return tmpFile;
+ }
+
+ internal const string EssentialsFolderHash = "2203693cc04e0be7f4f024d5f9499e13";
+ }
+}
\ No newline at end of file
diff --git a/src/Common.ClientLib.Droid/Logging/PlatformLoggerProvider.cs b/src/Common.ClientLib.Droid/Logging/PlatformLoggerProvider.cs
index f4119371e..665324099 100644
--- a/src/Common.ClientLib.Droid/Logging/PlatformLoggerProvider.cs
+++ b/src/Common.ClientLib.Droid/Logging/PlatformLoggerProvider.cs
@@ -1,4 +1,4 @@
-using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Logging;
// ReSharper disable once CheckNamespace
namespace System.Logging
diff --git a/src/Common.ClientLib.Droid/ServiceCollectionExtensions.cs b/src/Common.ClientLib.Droid/ServiceCollectionExtensions.cs
index 6a5db31ce..ea0dab9e7 100644
--- a/src/Common.ClientLib.Droid/ServiceCollectionExtensions.cs
+++ b/src/Common.ClientLib.Droid/ServiceCollectionExtensions.cs
@@ -1,4 +1,4 @@
-using Microsoft.Extensions.DependencyInjection.Extensions;
+using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Logging;
using System.Application.Services;
using System.Application.Services.Implementation;
@@ -19,28 +19,6 @@ namespace Microsoft.Extensions.DependencyInjection
public static IServiceCollection TryAddToast(this IServiceCollection services)
=> PlatformToastImpl.TryAddToast(services);
- ///
- /// 添加客户端日志提供程序
- ///
- ///
- ///
- public static ILoggingBuilder AddClientProvider(this ILoggingBuilder builder)
- {
- builder.AddProvider(PlatformLoggerProvider.Instance);
- return builder;
- }
-
- ///
- /// 添加客户端日志
- ///
- ///
- ///
- public static IServiceCollection AddClientLogging(this IServiceCollection services)
- {
- services.AddLogging(l => l.AddClientProvider());
- return services;
- }
-
///
/// 添加调试模式显示
///
diff --git a/src/Common.CoreLib/IOPath.cs b/src/Common.CoreLib/IOPath.cs
index 91bb145ba..6a721baa6 100644
--- a/src/Common.CoreLib/IOPath.cs
+++ b/src/Common.CoreLib/IOPath.cs
@@ -104,6 +104,9 @@ namespace System
#region FileSystem
+ public const string DirName_AppData = "AppData";
+ public const string DirName_Cache = "Cache";
+
static Func? getAppDataDirectory;
static Func? getCacheDirectory;
@@ -125,7 +128,7 @@ namespace System
{
if (DI.DeviceIdiom == DeviceIdiom.Desktop)
{
- var r = Path.Combine(BaseDirectory, "AppData");
+ var r = Path.Combine(BaseDirectory, DirName_AppData);
DirCreateByNotExists(r);
return r;
}
@@ -153,7 +156,7 @@ namespace System
{
if (DI.DeviceIdiom == DeviceIdiom.Desktop)
{
- var r = Path.Combine(BaseDirectory, "Cache");
+ var r = Path.Combine(BaseDirectory, DirName_Cache);
DirCreateByNotExists(r);
return r;
}
@@ -237,14 +240,14 @@ namespace System
///
/// 判断路径是否为[文件夹(Dir)]
///
- ///
+ ///
///
- public static bool IsDirectory(string ioPath)
+ public static bool IsDirectory(string IOPath)
{
#if NET35
throw new PlatformNotSupportedException();
#else
- var attr = File.GetAttributes(ioPath);
+ var attr = File.GetAttributes(IOPath);
return attr.HasFlag(FileAttributes.Directory);
#endif
}
@@ -254,21 +257,21 @@ namespace System
/// 如果传入的参数为[文件(File)]路径,则返回当前所在[文件夹(Dir)]路径
/// 如果传入的参数为[文件夹(Dir)]路径,则直接返回参数
///
- ///
+ ///
///
- public static string GetDirectoryPath(string ioPath)
+ public static string GetDirectoryPath(string IOPath)
{
try
{
- if (!IsDirectory(ioPath))
+ if (!IsDirectory(IOPath))
{
- return Path.GetDirectoryName(ioPath) ?? string.Empty;
+ return Path.GetDirectoryName(IOPath) ?? string.Empty;
}
}
catch
{
}
- return ioPath;
+ return IOPath;
}
static FileStream OpenReadCore(string filePath) => new(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete);
diff --git a/src/Common.CoreLib/MediaTypeNames.cs b/src/Common.CoreLib/MediaTypeNames.cs
index a04b44e76..b0fa71942 100644
--- a/src/Common.CoreLib/MediaTypeNames.cs
+++ b/src/Common.CoreLib/MediaTypeNames.cs
@@ -76,5 +76,7 @@ namespace System
public const string VideoAll = "video/*";
public const string PDF = "application/pdf";
+
+ public const string All = "*/*";
}
}
\ No newline at end of file
diff --git a/src/ST.Client.Desktop.Avalonia.App/Program.InitLogDir.cs b/src/ST.Client.Desktop.Avalonia.App/Program.InitLogDir.cs
deleted file mode 100644
index 2f777d50d..000000000
--- a/src/ST.Client.Desktop.Avalonia.App/Program.InitLogDir.cs
+++ /dev/null
@@ -1,104 +0,0 @@
-using ArchiSteamFarm;
-using NLog;
-using NLog.Common;
-using NLog.Config;
-using NLog.Filters;
-using NLog.Targets;
-using System.Application.Services;
-using System.Application.UI;
-using System.IO;
-using NLogLevel = NLog.LogLevel;
-
-#if CONSOLEAPP
-namespace System.Application
-#else
-namespace System.Application.UI
-#endif
-{
- static partial class Program
- {
- public static Logger? logger;
-
- public static string InitLogDir(string? alias = null)
- {
- var logDirPath = Path.Combine(
-#if WINDOWS_DESKTOP_BRIDGE || MAC
- IOPath.CacheDirectory
-#else
- IOPath.BaseDirectory
-#endif
- , "Logs");
- IOPath.DirCreateByNotExists(logDirPath);
-#if StartupTrace
- StartupTrace.Restart("InitLogDir.IO");
-#endif
- var logDirPath_ = logDirPath + Path.DirectorySeparatorChar;
-
- InternalLogger.LogFile = logDirPath_ + "internal-nlog" + alias + ".txt";
- InternalLogger.LogLevel = NLogLevel.Error;
- var objConfig = new LoggingConfiguration();
- var defMinLevel = AppHelper.DefaultNLoggerMinLevel;
- var logfile = new FileTarget("logfile")
- {
- FileName = logDirPath_ + "nlog-all-${shortdate}" + alias + ".log",
- Layout = "${longdate}|${level}|${logger}|${message} |${all-event-properties} ${exception:format=tostring}",
- ArchiveAboveSize = 10485760,
- MaxArchiveFiles = 14,
- MaxArchiveDays = 7,
- };
- var asfLogDirPath = ASFPathHelper.GetLogDirectory(logDirPath_);
- FileTarget logfile_asf = new("File")
- {
- ArchiveFileName = ASFPathHelper.GetNLogArchiveFileName(asfLogDirPath),
- ArchiveNumbering = ArchiveNumberingMode.Rolling,
- ArchiveOldFileOnStartup = true,
- CleanupFileName = false,
- ConcurrentWrites = false,
- DeleteOldFileOnStartup = true,
- FileName = ASFPathHelper.GetNLogFileName(asfLogDirPath),
- Layout = ASFPathHelper.NLogGeneralLayout,
- ArchiveAboveSize = 10485760,
- MaxArchiveFiles = 10,
- MaxArchiveDays = 7,
- };
- objConfig.AddTarget(logfile);
- objConfig.AddTarget(logfile_asf);
-
- objConfig.AddRule(NLogLevel.Error, NLogLevel.Fatal, logfile, "Microsoft.*");
- objConfig.AddRule(NLogLevel.Error, NLogLevel.Fatal, logfile, "System.Net.Http.*");
- objConfig.LoggingRules.Add(new LoggingRule("ArchiSteamFarm*", NLogLevel.Off, logfile) { Final = true, });
- objConfig.AddRule(defMinLevel, NLogLevel.Fatal, logfile, "*");
- objConfig.LoggingRules.Add(new LoggingRule("ArchiSteamFarm*", defMinLevel, logfile_asf));
-#if StartupTrace
- StartupTrace.Restart("InitLogDir.CreateLoggingConfiguration");
-#endif
- NLog.LogManager.Configuration = objConfig;
-
- IArchiSteamFarmService.InitCoreLoggers = () =>
- {
- if (ArchiSteamFarm.LogManager.Configuration != null) return;
- LoggingConfiguration config = new();
- var logDirPath_ASF = ASFPathHelper.GetLogDirectory(logDirPath_);
- FileTarget fileTarget = new("File")
- {
- ArchiveFileName = ASFPathHelper.GetNLogArchiveFileName(logDirPath_ASF),
- ArchiveNumbering = ArchiveNumberingMode.Rolling,
- ArchiveOldFileOnStartup = true,
- CleanupFileName = false,
- ConcurrentWrites = false,
- DeleteOldFileOnStartup = true,
- FileName = ASFPathHelper.GetNLogFileName(logDirPath_ASF),
- Layout = ASFPathHelper.NLogGeneralLayout,
- ArchiveAboveSize = 10485760,
- MaxArchiveFiles = 10,
- MaxArchiveDays = 7,
- };
- config.AddTarget(fileTarget);
- config.LoggingRules.Add(new LoggingRule("*", defMinLevel, fileTarget));
- ArchiSteamFarm.LogManager.Configuration = config;
- };
-
- return logDirPath;
- }
- }
-}
\ No newline at end of file
diff --git a/src/ST.Client.Desktop.Avalonia.App/Program.cs b/src/ST.Client.Desktop.Avalonia.App/Program.cs
index 08b58a8eb..715621ea3 100644
--- a/src/ST.Client.Desktop.Avalonia.App/Program.cs
+++ b/src/ST.Client.Desktop.Avalonia.App/Program.cs
@@ -11,6 +11,7 @@ namespace System.Application.UI
{
static partial class Program
{
+ static Logger? logger;
static readonly HashSet exceptions = new();
static readonly object lock_global_ex_log = new();
@@ -39,12 +40,12 @@ namespace System.Application.UI
IsMainProcess = args.Length == 0;
IsCLTProcess = !IsMainProcess && args.FirstOrDefault() == "-clt";
- var logDirPath = InitLogDir();
+ AppHelper.InitLogDir();
#if StartupTrace
StartupTrace.Restart("InitLogDir");
#endif
- void InitCefNetApp() => CefNetApp.Init(logDirPath, args);
+ void InitCefNetApp() => CefNetApp.Init(AppHelper.LogDirPath, args);
void InitAvaloniaApp() => BuildAvaloniaAppAndStartWithClassicDesktopLifetime(args);
void InitStartup(DILevel level) => Startup.Init(level);
logger = LogManager.GetCurrentClassLogger();
diff --git a/src/ST.Client.Desktop.Avalonia.App/WebViewImplType.cs b/src/ST.Client.Desktop.Avalonia.App/WebViewImplType.cs
deleted file mode 100644
index f02b6ee71..000000000
--- a/src/ST.Client.Desktop.Avalonia.App/WebViewImplType.cs
+++ /dev/null
@@ -1,12 +0,0 @@
-//namespace System.Application.UI
-//{
-// public enum WebViewImplType : byte
-// {
-// ChromiumEmbeddedFramework,
-
-// ///
-// /// https://docs.microsoft.com/zh-cn/microsoft-edge/webview2
-// ///
-// MicrosoftEdgeWebView2InWPF,
-// }
-//}
\ No newline at end of file
diff --git a/src/ST.Client.Desktop/FileSystemDesktop.cs b/src/ST.Client.Desktop/FileSystemDesktop.cs
index 557c907ef..9c04ef4af 100644
--- a/src/ST.Client.Desktop/FileSystemDesktop.cs
+++ b/src/ST.Client.Desktop/FileSystemDesktop.cs
@@ -11,9 +11,6 @@ namespace System.Application
///
public static class FileSystemDesktop
{
- public const string AppDataDirName = "AppData";
- public const string CacheDirName = "Cache";
-
///
/// 初始化文件系统
///
@@ -35,8 +32,8 @@ namespace System.Application
IOPath.BaseDirectory;
#endif
- var appDataPath = Path.Combine(appDataRootPath, AppDataDirName);
- var cachePath = Path.Combine(appDataRootPath, CacheDirName);
+ var appDataPath = Path.Combine(appDataRootPath, IOPath.DirName_AppData);
+ var cachePath = Path.Combine(appDataRootPath, IOPath.DirName_Cache);
IOPath.DirCreateByNotExists(appDataPath);
IOPath.DirCreateByNotExists(cachePath);
IOPath.InitFileSystem(GetAppDataDirectory, GetCacheDirectory);
diff --git a/src/ST.Client.Desktop/ServiceCollectionExtensions.AddGeneralLogging.cs b/src/ST.Client.Desktop/ServiceCollectionExtensions.AddGeneralLogging.cs
new file mode 100644
index 000000000..813e9f686
--- /dev/null
+++ b/src/ST.Client.Desktop/ServiceCollectionExtensions.AddGeneralLogging.cs
@@ -0,0 +1,37 @@
+using Microsoft.Extensions.Logging;
+using System.Application.UI;
+using System.Logging;
+using System.Properties;
+
+// ReSharper disable once CheckNamespace
+namespace Microsoft.Extensions.DependencyInjection
+{
+ public static partial class ServiceCollectionExtensions
+ {
+ ///
+ /// 添加通用日志实现
+ ///
+ ///
+ ///
+ public static IServiceCollection AddGeneralLogging(this IServiceCollection services)
+ {
+ var (minLevel, action) = AppHelper.ConfigureLogging();
+ services.AddLogging(b =>
+ {
+ action(b);
+#if __ANDROID__
+ if (ThisAssembly.Debuggable)
+ {
+ // Android Logcat Provider Impl
+ b.AddProvider(PlatformLoggerProvider.Instance);
+ }
+#endif
+ });
+ services.Configure(o =>
+ {
+ o.MinLevel = minLevel;
+ });
+ return services;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ST.Client.Desktop/ServiceCollectionExtensions.cs b/src/ST.Client.Desktop/ServiceCollectionExtensions.cs
index dd34cd016..37883d8e7 100644
--- a/src/ST.Client.Desktop/ServiceCollectionExtensions.cs
+++ b/src/ST.Client.Desktop/ServiceCollectionExtensions.cs
@@ -18,22 +18,6 @@ namespace Microsoft.Extensions.DependencyInjection
public static IServiceCollection TryAddToast(this IServiceCollection services)
=> PlatformToastImpl.TryAddToast(services);
- ///
- /// 添加桌面端日志实现
- ///
- ///
- ///
- public static IServiceCollection AddDesktopLogging(this IServiceCollection services)
- {
- var (minLevel, cfg) = AppHelper.Configure();
- services.AddLogging(cfg);
- services.Configure(o =>
- {
- o.MinLevel = minLevel;
- });
- return services;
- }
-
///
/// 添加 hosts 文件助手服务
///
diff --git a/src/ST.Client.Desktop/Services/IDesktopPlatformService.cs b/src/ST.Client.Desktop/Services/IDesktopPlatformService.cs
index 6843ff8af..1a4cd7fa2 100644
--- a/src/ST.Client.Desktop/Services/IDesktopPlatformService.cs
+++ b/src/ST.Client.Desktop/Services/IDesktopPlatformService.cs
@@ -46,11 +46,8 @@ namespace System.Application.Services
void OpenProcess(string name, string arguments) => Process.Start(name, arguments);
void OpenProcess(string name) => Process.Start(name);
- ///
- /// 使用文本阅读器打开文件
- ///
- ///
- void OpenFileByTextReader(string filePath)
+
+ void IPlatformService.OpenFileByTextReader(string filePath)
{
TextReaderProvider? userProvider = null;
var p = GeneralSettings.TextReaderProvider.Value;
diff --git a/src/ST.Client.Desktop/UI/AppHelper.Logger.cs b/src/ST.Client.Desktop/UI/AppHelper.Logger.cs
new file mode 100644
index 000000000..89a53c869
--- /dev/null
+++ b/src/ST.Client.Desktop/UI/AppHelper.Logger.cs
@@ -0,0 +1,247 @@
+using ArchiSteamFarm;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
+using NLog.Common;
+using NLog.Config;
+using NLog.Extensions.Logging;
+using NLog.Targets;
+using System.Application.Services;
+using System.IO;
+using System.Properties;
+using LogLevel = Microsoft.Extensions.Logging.LogLevel;
+using NInternalLogger = NLog.Common.InternalLogger;
+using NLogLevel = NLog.LogLevel;
+using NLogManager = NLog.LogManager;
+
+namespace System.Application.UI
+{
+ partial class AppHelper
+ {
+ #region Logger
+
+ const LogLevel DefaultLoggerMinLevel = ThisAssembly.Debuggable ? LogLevel.Debug : LogLevel.Error;
+
+ public static readonly NLogLevel DefaultNLoggerMinLevel = ConvertLogLevel(DefaultLoggerMinLevel);
+
+ ///
+ /// Convert log level to NLog variant.
+ ///
+ /// level to be converted.
+ ///
+ static NLogLevel ConvertLogLevel(LogLevel logLevel) => logLevel switch
+ {
+ // https://github.com/NLog/NLog.Extensions.Logging/blob/v1.7.0/src/NLog.Extensions.Logging/Logging/NLogLogger.cs#L416
+ LogLevel.Trace => NLogLevel.Trace,
+ LogLevel.Debug => NLogLevel.Debug,
+ LogLevel.Information => NLogLevel.Info,
+ LogLevel.Warning => NLogLevel.Warn,
+ LogLevel.Error => NLogLevel.Error,
+ LogLevel.Critical => NLogLevel.Fatal,
+ LogLevel.None => NLogLevel.Off,
+ _ => NLogLevel.Debug,
+ };
+
+ static void SetNLoggerMinLevel(LogLevel logLevel) => SetNLoggerMinLevel(ConvertLogLevel(logLevel));
+
+ static void SetNLoggerMinLevel(NLogLevel logLevel)
+ {
+ NLogManager.GlobalThreshold = logLevel;
+ NInternalLogger.LogLevel = logLevel;
+ }
+
+ public static void TrySetLoggerMinLevel(LogLevel logLevel)
+ {
+ try
+ {
+ var o = LoggerFilterOptions;
+ if (o != null) o.MinLevel = logLevel;
+ }
+ catch
+ {
+ }
+ SetNLoggerMinLevel(ConvertLogLevel(logLevel));
+ }
+
+ static LoggerFilterOptions? _LoggerFilterOptions;
+ ///
+ /// 日志过滤选项
+ ///
+ static LoggerFilterOptions? LoggerFilterOptions
+ {
+ get
+ {
+ if (_LoggerFilterOptions != null) return _LoggerFilterOptions;
+ return DI.Get_Nullable>()?.Value;
+ }
+ set
+ {
+ _LoggerFilterOptions = value;
+ }
+ }
+
+ ///
+ /// 配置 NLog 提供程序,仅初始化时调用
+ ///
+ ///
+ public static (LogLevel minLevel, Action cfg) ConfigureLogging()
+ {
+ var minLevel = DefaultLoggerMinLevel;
+
+ void _(ILoggingBuilder builder)
+ {
+ builder.ClearProviders();
+
+ builder.SetMinimumLevel(minLevel);
+ SetNLoggerMinLevel(minLevel);
+
+ // 可以多个日志提供同时用,比如还可以在 Win 平台再添加一个 Windows 事件日志
+
+ builder.AddNLog(NLogManager.Configuration); // 添加 NLog 日志
+ }
+
+ return (minLevel, (Action)_);
+ }
+
+ ///
+ /// 日志最低等级
+ ///
+ public static LogLevel LoggerMinLevel
+ {
+ get
+ {
+ var o = LoggerFilterOptions;
+ if (o == null)
+ {
+ o = new();
+ LoggerFilterOptions = o;
+ }
+ return o.MinLevel;
+ }
+ set
+ {
+ var o = LoggerFilterOptions;
+ if (o != null)
+ {
+ o.MinLevel = value;
+ }
+ SetNLoggerMinLevel(value);
+ }
+ }
+
+ ///
+ /// 启用日志
+ ///
+ public static bool EnableLogger
+ {
+ get => LoggerMinLevel > LogLevel.None;
+ set
+ {
+ LoggerMinLevel = value ? DefaultLoggerMinLevel : LogLevel.None;
+ }
+ }
+
+ #endregion
+
+ ///
+ /// 日志存放文件夹路径
+ ///
+ public static string LogDirPath { get; private set; } = string.Empty;
+
+ ///
+ /// 日志文件夹是否存放在缓存文件夹中,通常日志文件夹将存放在基目录上,因某些平台基目录为只读,则只能放在缓存文件夹中
+ ///
+ public static bool LogUnderCache { get; private set; }
+
+ public const string LogDirName = "Logs";
+
+ public static void InitLogDir(string? alias = null)
+ {
+ if (!string.IsNullOrEmpty(LogDirPath)) return;
+
+ LogUnderCache =
+
+#if WINDOWS_DESKTOP_BRIDGE || MAC || __MOBILE__
+ true
+#else
+ false
+#endif
+ ;
+
+ var logDirPath = Path.Combine(LogUnderCache ?
+ IOPath.CacheDirectory :
+ IOPath.BaseDirectory,
+ LogDirName);
+ IOPath.DirCreateByNotExists(logDirPath);
+#if StartupTrace
+ StartupTrace.Restart("InitLogDir.IO");
+#endif
+ var logDirPath_ = logDirPath + Path.DirectorySeparatorChar;
+
+ InternalLogger.LogFile = logDirPath_ + "internal-nlog" + alias + ".txt";
+ InternalLogger.LogLevel = NLogLevel.Error;
+ var objConfig = new LoggingConfiguration();
+ var defMinLevel = AppHelper.DefaultNLoggerMinLevel;
+ var logfile = new FileTarget("logfile")
+ {
+ FileName = logDirPath_ + "nlog-all-${shortdate}" + alias + ".log",
+ Layout = "${longdate}|${level}|${logger}|${message} |${all-event-properties} ${exception:format=tostring}",
+ ArchiveAboveSize = 10485760,
+ MaxArchiveFiles = 14,
+ MaxArchiveDays = 7,
+ };
+ var asfLogDirPath = ASFPathHelper.GetLogDirectory(logDirPath_);
+ FileTarget logfile_asf = new("File")
+ {
+ ArchiveFileName = ASFPathHelper.GetNLogArchiveFileName(asfLogDirPath),
+ ArchiveNumbering = ArchiveNumberingMode.Rolling,
+ ArchiveOldFileOnStartup = true,
+ CleanupFileName = false,
+ ConcurrentWrites = false,
+ DeleteOldFileOnStartup = true,
+ FileName = ASFPathHelper.GetNLogFileName(asfLogDirPath),
+ Layout = ASFPathHelper.NLogGeneralLayout,
+ ArchiveAboveSize = 10485760,
+ MaxArchiveFiles = 10,
+ MaxArchiveDays = 7,
+ };
+ objConfig.AddTarget(logfile);
+ objConfig.AddTarget(logfile_asf);
+
+ objConfig.AddRule(NLogLevel.Error, NLogLevel.Fatal, logfile, "Microsoft.*");
+ objConfig.AddRule(NLogLevel.Error, NLogLevel.Fatal, logfile, "System.Net.Http.*");
+ objConfig.LoggingRules.Add(new LoggingRule("ArchiSteamFarm*", NLogLevel.Off, logfile) { Final = true, });
+ objConfig.AddRule(defMinLevel, NLogLevel.Fatal, logfile, "*");
+ objConfig.LoggingRules.Add(new LoggingRule("ArchiSteamFarm*", defMinLevel, logfile_asf));
+#if StartupTrace
+ StartupTrace.Restart("InitLogDir.CreateLoggingConfiguration");
+#endif
+ NLog.LogManager.Configuration = objConfig;
+
+ IArchiSteamFarmService.InitCoreLoggers = () =>
+ {
+ if (ArchiSteamFarm.LogManager.Configuration != null) return;
+ LoggingConfiguration config = new();
+ var logDirPath_ASF = ASFPathHelper.GetLogDirectory(logDirPath_);
+ FileTarget fileTarget = new("File")
+ {
+ ArchiveFileName = ASFPathHelper.GetNLogArchiveFileName(logDirPath_ASF),
+ ArchiveNumbering = ArchiveNumberingMode.Rolling,
+ ArchiveOldFileOnStartup = true,
+ CleanupFileName = false,
+ ConcurrentWrites = false,
+ DeleteOldFileOnStartup = true,
+ FileName = ASFPathHelper.GetNLogFileName(logDirPath_ASF),
+ Layout = ASFPathHelper.NLogGeneralLayout,
+ ArchiveAboveSize = 10485760,
+ MaxArchiveFiles = 10,
+ MaxArchiveDays = 7,
+ };
+ config.AddTarget(fileTarget);
+ config.LoggingRules.Add(new LoggingRule("*", defMinLevel, fileTarget));
+ ArchiSteamFarm.LogManager.Configuration = config;
+ };
+
+ LogDirPath = logDirPath;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ST.Client.Desktop/UI/AppHelper.cs b/src/ST.Client.Desktop/UI/AppHelper.cs
index adaa3a25e..6f02ed996 100644
--- a/src/ST.Client.Desktop/UI/AppHelper.cs
+++ b/src/ST.Client.Desktop/UI/AppHelper.cs
@@ -1,18 +1,11 @@
-using Microsoft.Extensions.Logging;
-using Microsoft.Extensions.Options;
-using NLog.Extensions.Logging;
using System.Application.Services;
using System.Diagnostics;
using System.IO;
using System.Properties;
-using LogLevel = Microsoft.Extensions.Logging.LogLevel;
-using NInternalLogger = NLog.Common.InternalLogger;
-using NLogLevel = NLog.LogLevel;
-using NLogManager = NLog.LogManager;
namespace System.Application.UI
{
- public static class AppHelper
+ public static partial class AppHelper
{
public static bool EnableDevtools { get; set; } = ThisAssembly.Debuggable;
@@ -63,127 +56,6 @@ namespace System.Application.UI
s.SetBootAutoStart(isAutoStart, Constants.HARDCODED_APP_NAME);
}
- #region Logger
-
- const LogLevel DefaultLoggerMinLevel = ThisAssembly.Debuggable ? LogLevel.Debug : LogLevel.Error;
-
- public static readonly NLogLevel DefaultNLoggerMinLevel = ConvertLogLevel(DefaultLoggerMinLevel);
-
- ///
- /// Convert log level to NLog variant.
- ///
- /// level to be converted.
- ///
- static NLogLevel ConvertLogLevel(LogLevel logLevel) => logLevel switch
- {
- // https://github.com/NLog/NLog.Extensions.Logging/blob/v1.7.0/src/NLog.Extensions.Logging/Logging/NLogLogger.cs#L416
- LogLevel.Trace => NLogLevel.Trace,
- LogLevel.Debug => NLogLevel.Debug,
- LogLevel.Information => NLogLevel.Info,
- LogLevel.Warning => NLogLevel.Warn,
- LogLevel.Error => NLogLevel.Error,
- LogLevel.Critical => NLogLevel.Fatal,
- LogLevel.None => NLogLevel.Off,
- _ => NLogLevel.Debug,
- };
-
- static void SetNLoggerMinLevel(LogLevel logLevel) => SetNLoggerMinLevel(ConvertLogLevel(logLevel));
-
- static void SetNLoggerMinLevel(NLogLevel logLevel)
- {
- NLogManager.GlobalThreshold = logLevel;
- NInternalLogger.LogLevel = logLevel;
- }
-
- public static void TrySetLoggerMinLevel(LogLevel logLevel)
- {
- try
- {
- var o = LoggerFilterOptions;
- if (o != null) o.MinLevel = logLevel;
- }
- catch
- {
- }
- SetNLoggerMinLevel(ConvertLogLevel(logLevel));
- }
-
- static LoggerFilterOptions? _LoggerFilterOptions;
- ///
- /// 日志过滤选项
- ///
- static LoggerFilterOptions? LoggerFilterOptions
- {
- get
- {
- if (_LoggerFilterOptions != null) return _LoggerFilterOptions;
- return DI.Get_Nullable>()?.Value;
- }
- set
- {
- _LoggerFilterOptions = value;
- }
- }
-
- internal static (LogLevel minLevel, Action cfg) Configure()
- {
- var minLevel = DefaultLoggerMinLevel;
-
- void _(ILoggingBuilder builder)
- {
- builder.ClearProviders();
-
- builder.SetMinimumLevel(minLevel);
- SetNLoggerMinLevel(minLevel);
-
- // 可以多个日志提供同时用,比如还可以在 Win 平台再添加一个 Windows 事件日志
-
- builder.AddNLog(NLogManager.Configuration); // 添加 NLog 日志
- }
-
- return (minLevel, (Action)_);
- }
-
- ///
- /// 日志最低等级
- ///
- public static LogLevel LoggerMinLevel
- {
- get
- {
- var o = LoggerFilterOptions;
- if (o == null)
- {
- o = new();
- LoggerFilterOptions = o;
- }
- return o.MinLevel;
- }
- set
- {
- var o = LoggerFilterOptions;
- if (o != null)
- {
- o.MinLevel = value;
- }
- SetNLoggerMinLevel(value);
- }
- }
-
- ///
- /// 启用日志
- ///
- public static bool EnableLogger
- {
- get => LoggerMinLevel > LogLevel.None;
- set
- {
- LoggerMinLevel = value ? DefaultLoggerMinLevel : LogLevel.None;
- }
- }
-
- #endregion
-
public static IDesktopAppService Current => DI.Get();
#if DEBUG
diff --git a/src/ST.Client.Desktop.Avalonia.App/ApplicationInstance.cs b/src/ST.Client.Desktop/UI/ApplicationInstance.cs
similarity index 100%
rename from src/ST.Client.Desktop.Avalonia.App/ApplicationInstance.cs
rename to src/ST.Client.Desktop/UI/ApplicationInstance.cs
diff --git a/src/ST.Client.Desktop/UI/Resx/AppResources.Designer.cs b/src/ST.Client.Desktop/UI/Resx/AppResources.Designer.cs
index cc09df07a..43bb095f3 100644
--- a/src/ST.Client.Desktop/UI/Resx/AppResources.Designer.cs
+++ b/src/ST.Client.Desktop/UI/Resx/AppResources.Designer.cs
@@ -458,6 +458,15 @@ namespace System.Application.UI.Resx {
}
}
+ ///
+ /// 查找类似 浏览基目录 的本地化字符串。
+ ///
+ public static string BrowseBaseDirectory {
+ get {
+ return ResourceManager.GetString("BrowseBaseDirectory", resourceCulture);
+ }
+ }
+
///
/// 查找类似 完成 的本地化字符串。
///
diff --git a/src/ST.Client.Desktop/UI/Resx/AppResources.resx b/src/ST.Client.Desktop/UI/Resx/AppResources.resx
index 77505a9d7..190075b2f 100644
--- a/src/ST.Client.Desktop/UI/Resx/AppResources.resx
+++ b/src/ST.Client.Desktop/UI/Resx/AppResources.resx
@@ -1499,4 +1499,7 @@
应用通知(系统设置)
-
+
+ 浏览基目录
+
+
\ No newline at end of file
diff --git a/src/ST.Client.Desktop/UI/ViewModels/Pages/ProxyScriptManagePageViewModel.cs b/src/ST.Client.Desktop/UI/ViewModels/Pages/ProxyScriptManagePageViewModel.cs
index a21c91c0f..e4b51d13e 100644
--- a/src/ST.Client.Desktop/UI/ViewModels/Pages/ProxyScriptManagePageViewModel.cs
+++ b/src/ST.Client.Desktop/UI/ViewModels/Pages/ProxyScriptManagePageViewModel.cs
@@ -175,22 +175,21 @@ namespace System.Application.UI.ViewModels
public void EditScriptItemButton(ScriptDTO script)
{
-
var url = Path.Combine(IOPath.AppDataDirectory, script.FilePath);
var fileInfo = new FileInfo(url);
if (fileInfo.Exists)
{
- DI.Get().OpenFileByTextReader(url);
+ IPlatformService.Instance.OpenFileByTextReader(url);
var result = MessageBoxCompat.ShowAsync(@AppResources.Script_EditTxt, ThisAssembly.AssemblyTrademark, MessageBoxButtonCompat.OKCancel).ContinueWith(async (s) =>
{
if (s.Result == MessageBoxResultCompat.OK)
{
- var item = await DI.Get().AddScriptAsync(url, script, build: script.IsBuild, order: script.Order, ignoreCache: true);
- if (item.state)
+ var (state, model, msg) = await DI.Get().AddScriptAsync(url, script, build: script.IsBuild, order: script.Order, ignoreCache: true);
+ if (state)
{
- if (ProxyService.Current.ProxyScripts.Items.Any() && item.model != null)
+ if (ProxyService.Current.ProxyScripts.Items.Any() && model != null)
{
- ProxyService.Current.ProxyScripts.Replace(script, item.model);
+ ProxyService.Current.ProxyScripts.Replace(script, model);
Toast.Show(AppResources.Success_.Format(AppResources.Script_Edit));
}
}
@@ -241,7 +240,7 @@ namespace System.Application.UI.ViewModels
var result = await MessageBoxCompat.ShowAsync(@AppResources.ScriptShop_NoLogin, ThisAssembly.AssemblyTrademark, MessageBoxButtonCompat.OKCancel);
if (result == MessageBoxResultCompat.OK)
{
- await IShowWindowService.Instance.Show(CustomWindow.LoginOrRegister, new LoginOrRegisterWindowViewModel(), string.Empty, ResizeModeCompat.CanResize);
+ await IShowWindowService.Instance.Show(CustomWindow.LoginOrRegister, new LoginOrRegisterWindowViewModel(), string.Empty, ResizeModeCompat.CanResize);
}
}
else
diff --git a/src/ST.Client.Mobile.Droid.Design/ui/src/main/res/drawable-hdpi/baseline_folder_black_24.png b/src/ST.Client.Mobile.Droid.Design/ui/src/main/res/drawable-hdpi/baseline_folder_black_24.png
new file mode 100644
index 000000000..c86c7dbe2
Binary files /dev/null and b/src/ST.Client.Mobile.Droid.Design/ui/src/main/res/drawable-hdpi/baseline_folder_black_24.png differ
diff --git a/src/ST.Client.Mobile.Droid.Design/ui/src/main/res/drawable-mdpi/baseline_folder_black_24.png b/src/ST.Client.Mobile.Droid.Design/ui/src/main/res/drawable-mdpi/baseline_folder_black_24.png
new file mode 100644
index 000000000..067f0ea22
Binary files /dev/null and b/src/ST.Client.Mobile.Droid.Design/ui/src/main/res/drawable-mdpi/baseline_folder_black_24.png differ
diff --git a/src/ST.Client.Mobile.Droid.Design/ui/src/main/res/drawable-xhdpi/baseline_folder_black_24.png b/src/ST.Client.Mobile.Droid.Design/ui/src/main/res/drawable-xhdpi/baseline_folder_black_24.png
new file mode 100644
index 000000000..863560d3d
Binary files /dev/null and b/src/ST.Client.Mobile.Droid.Design/ui/src/main/res/drawable-xhdpi/baseline_folder_black_24.png differ
diff --git a/src/ST.Client.Mobile.Droid.Design/ui/src/main/res/drawable-xxhdpi/baseline_folder_black_24.png b/src/ST.Client.Mobile.Droid.Design/ui/src/main/res/drawable-xxhdpi/baseline_folder_black_24.png
new file mode 100644
index 000000000..9c3ad1eb9
Binary files /dev/null and b/src/ST.Client.Mobile.Droid.Design/ui/src/main/res/drawable-xxhdpi/baseline_folder_black_24.png differ
diff --git a/src/ST.Client.Mobile.Droid.Design/ui/src/main/res/drawable-xxxhdpi/baseline_folder_black_24.png b/src/ST.Client.Mobile.Droid.Design/ui/src/main/res/drawable-xxxhdpi/baseline_folder_black_24.png
new file mode 100644
index 000000000..2a7c64f56
Binary files /dev/null and b/src/ST.Client.Mobile.Droid.Design/ui/src/main/res/drawable-xxxhdpi/baseline_folder_black_24.png differ
diff --git a/src/ST.Client.Mobile.Droid.Design/ui/src/main/res/drawable/baseline_folder_black_24.xml b/src/ST.Client.Mobile.Droid.Design/ui/src/main/res/drawable/baseline_folder_black_24.xml
new file mode 100644
index 000000000..dc6b08023
--- /dev/null
+++ b/src/ST.Client.Mobile.Droid.Design/ui/src/main/res/drawable/baseline_folder_black_24.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/src/ST.Client.Mobile.Droid.Design/ui/src/main/res/layout/activity_explorer.xml b/src/ST.Client.Mobile.Droid.Design/ui/src/main/res/layout/activity_explorer.xml
new file mode 100644
index 000000000..c1f03c3a7
--- /dev/null
+++ b/src/ST.Client.Mobile.Droid.Design/ui/src/main/res/layout/activity_explorer.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
diff --git a/src/ST.Client.Mobile.Droid.Design/ui/src/main/res/layout/activity_steam_auth_trade.xml b/src/ST.Client.Mobile.Droid.Design/ui/src/main/res/layout/activity_steam_auth_trade.xml
index 71d983b03..b78b95d71 100644
--- a/src/ST.Client.Mobile.Droid.Design/ui/src/main/res/layout/activity_steam_auth_trade.xml
+++ b/src/ST.Client.Mobile.Droid.Design/ui/src/main/res/layout/activity_steam_auth_trade.xml
@@ -59,7 +59,6 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:overScrollMode="never"
- android:paddingBottom="@dimen/fab_full_height"
tools:itemCount="4"
tools:listitem="@layout/layout_card_steam_auth_trade_confirmation"
xamarin:managedType="AndroidX.RecyclerView.Widget.RecyclerView" />
diff --git a/src/ST.Client.Mobile.Droid.Design/ui/src/main/res/layout/layout_file_or_dir.xml b/src/ST.Client.Mobile.Droid.Design/ui/src/main/res/layout/layout_file_or_dir.xml
new file mode 100644
index 000000000..fe8f6d729
--- /dev/null
+++ b/src/ST.Client.Mobile.Droid.Design/ui/src/main/res/layout/layout_file_or_dir.xml
@@ -0,0 +1,55 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/ST.Client.Mobile.Droid/GoToPlatformPages.cs b/src/ST.Client.Mobile.Droid/GoToPlatformPages.cs
index 060bfa334..bff7aa483 100644
--- a/src/ST.Client.Mobile.Droid/GoToPlatformPages.cs
+++ b/src/ST.Client.Mobile.Droid/GoToPlatformPages.cs
@@ -2,8 +2,8 @@ using Android.App;
using Android.Content;
using Android.OS;
using AndroidX.Core.Content;
+using AndroidUri = Android.Net.Uri;
using Fragment = AndroidX.Fragment.App.Fragment;
-using AUri = Android.Net.Uri;
using JFile = Java.IO.File;
using Settings = Android.Provider.Settings;
@@ -138,25 +138,27 @@ namespace System.Application
StartActivity(activity, viewModel);
}
- public static void InstallApk(Context context, JFile apkFile)
+ public static void OpenFile(Context context, JFile apkFile, string mime)
{
var sdkInt = (int)Build.VERSION.SdkInt;
var intent = new Intent(Intent.ActionView);
- AUri apkUri;
- if (sdkInt >= (int)BuildVersionCodes.N) // 7.0开始要用新的API!
+ AndroidUri apkUri;
+ if (sdkInt >= (int)BuildVersionCodes.N) // 7.0 FileProvider
{
apkUri = FileProvider.GetUriForFile(context, GetAuthority(context), apkFile);
intent.AddFlags(ActivityFlags.GrantReadUriPermission); // FLAG_GRANT_READ_URI_PERMISSION 添加这一句表示对目标应用临时授权该Uri所代表的文件
}
else
{
- apkUri = AUri.FromFile(apkFile)!;
+ apkUri = AndroidUri.FromFile(apkFile)!;
}
- intent.SetDataAndType(apkUri, MediaTypeNames.APK);
+ intent.SetDataAndType(apkUri, mime);
intent.AddFlags(ActivityFlags.NewTask);
context.StartActivity(intent);
}
+ public static void InstallApk(Context context, JFile apkFile) => OpenFile(context, apkFile, MediaTypeNames.APK);
+
public static void InstallApk(Context context, string apkFilePath)
{
JFile apkFile = new(apkFilePath);
@@ -194,7 +196,7 @@ namespace System.Application
{
intent.SetAction(Settings.ActionApplicationDetailsSettings);
intent.AddCategory(Intent.CategoryDefault);
- intent.SetData(AUri.FromParts("package", context.PackageName, null));
+ intent.SetData(AndroidUri.FromParts("package", context.PackageName, null));
}
else if (sdkInt <= 8)
{
diff --git a/src/ST.Client.Mobile.Droid/ST.Client.Mobile.Droid.csproj b/src/ST.Client.Mobile.Droid/ST.Client.Mobile.Droid.csproj
index b75a561fb..5c538c3b9 100644
--- a/src/ST.Client.Mobile.Droid/ST.Client.Mobile.Droid.csproj
+++ b/src/ST.Client.Mobile.Droid/ST.Client.Mobile.Droid.csproj
@@ -54,6 +54,9 @@
ServiceCollectionExtensions.Link.cs
+
+ Extensions
+
Extensions
@@ -102,6 +105,7 @@
+
@@ -238,6 +242,9 @@
Resources\drawable\baseline_female_black_24.xml
+
+ Resources\drawable\baseline_folder_black_24.xml
+
Resources\drawable\baseline_info_black_24.xml
@@ -373,6 +380,9 @@
Resources\drawable-hdpi\baseline_female_black_24.png
+
+ Resources\drawable-hdpi\baseline_folder_black_24.png
+
Resources\drawable-hdpi\baseline_info_black_24.png
@@ -478,6 +488,9 @@
Resources\drawable-mdpi\baseline_female_black_24.png
+
+ Resources\drawable-mdpi\baseline_folder_black_24.png
+
Resources\drawable-mdpi\baseline_info_black_24.png
@@ -583,6 +596,9 @@
Resources\drawable-xhdpi\baseline_female_black_24.png
+
+ Resources\drawable-xhdpi\baseline_folder_black_24.png
+
Resources\drawable-xhdpi\baseline_info_black_24.png
@@ -688,6 +704,9 @@
Resources\drawable-xxhdpi\baseline_female_black_24.png
+
+ Resources\drawable-xxhdpi\baseline_folder_black_24.png
+
Resources\drawable-xxhdpi\baseline_info_black_24.png
@@ -799,6 +818,9 @@
Resources\drawable-xxxhdpi\baseline_female_black_24.png
+
+ Resources\drawable-xxxhdpi\baseline_folder_black_24.png
+
Resources\drawable-xxxhdpi\baseline_info_black_24.png
@@ -871,6 +893,9 @@
Resources\layout\activity_encryption_auth.xml
+
+ Resources\layout\activity_explorer.xml
+
Resources\layout\activity_export_auth.xml
@@ -955,6 +980,9 @@
Resources\layout\layout_card_steam_auth_trade_confirmation_content.xml
+
+ Resources\layout\layout_file_or_dir.xml
+
Resources\layout\layout_login_and_register_by_fast_channel.xml
@@ -1079,3 +1107,5 @@
+
+
diff --git a/src/ST.Client.Mobile.Droid/Services/Implementation/MobilePlatformServiceImpl.cs b/src/ST.Client.Mobile.Droid/Services/Implementation/MobilePlatformServiceImpl.cs
index 63c8e9cd1..bff94bb2e 100644
--- a/src/ST.Client.Mobile.Droid/Services/Implementation/MobilePlatformServiceImpl.cs
+++ b/src/ST.Client.Mobile.Droid/Services/Implementation/MobilePlatformServiceImpl.cs
@@ -5,5 +5,11 @@ namespace System.Application.Services.Implementation
internal sealed class MobilePlatformServiceImpl : IMobilePlatformService
{
public object CurrentPlatformUIHost => XEPlatform.CurrentActivity;
+
+ void IPlatformService.OpenFileByTextReader(string filePath)
+ => GoToPlatformPages.OpenFile(
+ XEPlatform.CurrentActivity,
+ new(filePath),
+ MediaTypeNames.TXT);
}
}
\ No newline at end of file
diff --git a/src/ST.Client.Mobile.Droid/UI/Activities/AboutActivity.cs b/src/ST.Client.Mobile.Droid/UI/Activities/AboutActivity.cs
index 0a8f67af3..1ef90ab4a 100644
--- a/src/ST.Client.Mobile.Droid/UI/Activities/AboutActivity.cs
+++ b/src/ST.Client.Mobile.Droid/UI/Activities/AboutActivity.cs
@@ -127,6 +127,9 @@ namespace System.Application.UI.Activities
FontSizeResId = Resource.Dimension.translators_font_size,
});
break;
+ case PreferenceButton.浏览基目录:
+ this.StartActivity();
+ break;
}
};
var layout = new LinearLayoutManager(this, LinearLayoutManager.Vertical, false);
diff --git a/src/ST.Client.Mobile.Droid/UI/Activities/ExplorerActivity.cs b/src/ST.Client.Mobile.Droid/UI/Activities/ExplorerActivity.cs
new file mode 100644
index 000000000..ca925c885
--- /dev/null
+++ b/src/ST.Client.Mobile.Droid/UI/Activities/ExplorerActivity.cs
@@ -0,0 +1,45 @@
+using Android.App;
+using Android.Content.PM;
+using Android.OS;
+using Android.Runtime;
+using AndroidX.RecyclerView.Widget;
+using Binding;
+using ReactiveUI;
+using System.Application.UI.Adapters;
+using System.Application.UI.ViewModels;
+
+namespace System.Application.UI.Activities
+{
+ [Register(JavaPackageConstants.Activities + nameof(ExplorerActivity))]
+ [Activity(Theme = ManifestConstants.MainTheme_NoActionBar,
+ LaunchMode = LaunchMode.SingleTask,
+ ConfigurationChanges = ManifestConstants.ConfigurationChanges)]
+ internal sealed class ExplorerActivity : BaseActivity
+ {
+ protected override int? LayoutResource => Resource.Layout.activity_explorer;
+
+ protected override void OnCreate(Bundle? savedInstanceState)
+ {
+ base.OnCreate(savedInstanceState);
+
+ this.SetSupportActionBarWithNavigationClick(binding!.toolbar, true);
+
+ ViewModel!.WhenAnyValue(x => x.Title).SubscribeInMainThread(value =>
+ {
+ Title = value;
+ }).AddTo(this);
+
+ var adapter = new ExplorerAdapter(ViewModel!);
+ adapter.ItemClick += (_, e) => ViewModel!.OnItemClick(e.Current);
+ var layout = new LinearLayoutManager(this, LinearLayoutManager.Vertical, false);
+ binding.rvExplorer.SetLayoutManager(layout);
+ binding.rvExplorer.SetAdapter(adapter);
+ }
+
+ public override void OnBackPressed()
+ {
+ if (ViewModel!.OnBack()) return;
+ base.OnBackPressed();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ST.Client.Mobile.Droid/UI/Activities/TextBlockActivity.cs b/src/ST.Client.Mobile.Droid/UI/Activities/TextBlockActivity.cs
index 83f54033f..51e93a271 100644
--- a/src/ST.Client.Mobile.Droid/UI/Activities/TextBlockActivity.cs
+++ b/src/ST.Client.Mobile.Droid/UI/Activities/TextBlockActivity.cs
@@ -3,9 +3,12 @@ using Android.Content.PM;
using Android.OS;
using Android.Runtime;
using Android.Util;
+using AndroidX.Core.Net;
using Binding;
using System.Application.Models;
using System.Application.UI.ViewModels;
+using System.IO;
+using static Android.Content.Intent;
namespace System.Application.UI.Activities
{
@@ -13,6 +16,7 @@ namespace System.Application.UI.Activities
[Activity(Theme = ManifestConstants.MainTheme_NoActionBar,
LaunchMode = LaunchMode.SingleTask,
ConfigurationChanges = ManifestConstants.ConfigurationChanges)]
+ [IntentFilter(new[] { ActionView }, Categories = new[] { CategoryDefault }, DataMimeType = MediaTypeNames.TXT)]
internal sealed class TextBlockActivity : BaseActivity
{
protected override int? LayoutResource => Resource.Layout.activity_textblock;
@@ -22,6 +26,27 @@ namespace System.Application.UI.Activities
base.OnCreate(savedInstanceState);
var vm = this.GetViewModel();
+ if (vm == null)
+ {
+ if (Intent?.Action == ActionView && Intent.Data != null)
+ {
+ try
+ {
+ var filePath = Intent.Data.EnsurePhysicalPath();
+ var fileContent = File.ReadAllText(filePath);
+ vm = new TextBlockViewModel
+ {
+ Title = Path.GetFileName(filePath),
+ Content = fileContent,
+ };
+ }
+ catch (Exception e)
+ {
+ Log.Error(nameof(TextBlockActivity), nameof(OnCreate), e);
+ }
+ }
+ }
+
if (vm == null)
{
Finish();
diff --git a/src/ST.Client.Mobile.Droid/UI/Adapters/ExplorerAdapter.cs b/src/ST.Client.Mobile.Droid/UI/Adapters/ExplorerAdapter.cs
new file mode 100644
index 000000000..6c25d9e28
--- /dev/null
+++ b/src/ST.Client.Mobile.Droid/UI/Adapters/ExplorerAdapter.cs
@@ -0,0 +1,42 @@
+using Android.Views;
+using Binding;
+using System.Application.UI.ViewModels;
+using TViewHolder = System.Application.UI.Adapters.ExplorerViewHolder;
+using TViewModel = System.Application.UI.ViewModels.ExplorerViewModel.PathInfoViewModel;
+
+namespace System.Application.UI.Adapters
+{
+ internal sealed class ExplorerAdapter : BaseReactiveRecycleViewAdapter
+ {
+ public ExplorerAdapter(ExplorerViewModel viewModel) : base(viewModel.PathInfos)
+ {
+ }
+
+ public override int? GetLayoutResource(int viewType)
+ {
+ return Resource.Layout.layout_file_or_dir;
+ }
+ }
+
+ internal sealed class ExplorerViewHolder : BaseReactiveViewHolder
+ {
+ readonly layout_file_or_dir binding;
+
+ public ExplorerViewHolder(View itemView) : base(itemView)
+ {
+ binding = new(itemView);
+ }
+
+ public override void OnBind()
+ {
+ base.OnBind();
+
+ binding.ivIcon.SetImageResource(
+ ViewModel!.IsDirectory ?
+ Resource.Drawable.baseline_folder_black_24 :
+ Resource.Drawable.baseline_description_black_24);
+ binding.tvName.Text = ViewModel.Name;
+ binding.tvDesc.Text = ViewModel.Desc;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ST.Client.Mobile.Droid/UI/MainApplication.cs b/src/ST.Client.Mobile.Droid/UI/MainApplication.cs
index 053e8e134..a05ecbb59 100644
--- a/src/ST.Client.Mobile.Droid/UI/MainApplication.cs
+++ b/src/ST.Client.Mobile.Droid/UI/MainApplication.cs
@@ -58,6 +58,8 @@ namespace System.Application.UI
#endif
var stopwatch = Stopwatch.StartNew();
+ AppHelper.InitLogDir();
+
VisualStudioAppCenterSDK.Init();
XEFileProvider.TemporaryLocation = FileProviderLocation.Internal;
diff --git a/src/ST.Client.Mobile/ST.Client.Mobile.csproj b/src/ST.Client.Mobile/ST.Client.Mobile.csproj
index d763b9213..fca5ad14c 100644
--- a/src/ST.Client.Mobile/ST.Client.Mobile.csproj
+++ b/src/ST.Client.Mobile/ST.Client.Mobile.csproj
@@ -69,6 +69,9 @@
+
+ UI
+
Services
diff --git a/src/ST.Client.Mobile/UI/AppHelper.cs b/src/ST.Client.Mobile/UI/AppHelper.cs
new file mode 100644
index 000000000..1e749ea20
--- /dev/null
+++ b/src/ST.Client.Mobile/UI/AppHelper.cs
@@ -0,0 +1,11 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace System.Application.UI
+{
+ public static partial class AppHelper
+ {
+
+ }
+}
diff --git a/src/ST.Client.Mobile/UI/ViewModels/ExplorerViewModel.cs b/src/ST.Client.Mobile/UI/ViewModels/ExplorerViewModel.cs
new file mode 100644
index 000000000..0a641a6aa
--- /dev/null
+++ b/src/ST.Client.Mobile/UI/ViewModels/ExplorerViewModel.cs
@@ -0,0 +1,182 @@
+using ReactiveUI;
+using System.Application.Services;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.IO;
+using System.Linq;
+
+namespace System.Application.UI.ViewModels
+{
+ public class ExplorerViewModel : ViewModelBase
+ {
+ const double unit = 1024d;
+ static readonly string[] units = new[] { "B", "KB", "MB", "GB", "TB" };
+ public static string GetSize(double length)
+ {
+ for (int i = 0; i < units.Length; i++)
+ {
+ if (i > 0) length /= unit;
+ if (length < unit) return $"{length:0.00} {units[i]}";
+ }
+ return $"{length:0.00} {units.Last()}";
+ }
+
+ string _CurrentPath = string.Empty;
+ public string CurrentPath
+ {
+ get => _CurrentPath;
+ set
+ {
+ if (_CurrentPath == value) return;
+ _CurrentPath = value;
+ var title = DefaultTitle;
+ foreach (var item in GetRootPaths())
+ {
+ if (_CurrentPath.StartsWith(item.Key))
+ {
+ title = Path.DirectorySeparatorChar +
+ item.Value +
+ _CurrentPath.TrimStart(item.Key);
+ break;
+ }
+ }
+ Title = title;
+ foreach (var item in PathInfos.ToArray())
+ {
+ PathInfos.Remove(item);
+ }
+ if (string.IsNullOrEmpty(_CurrentPath))
+ {
+ PathInfos.AddRange(GetRootPathInfoViewModels>());
+ }
+ else
+ {
+ AddRange(PathInfos, _CurrentPath);
+ }
+ }
+ }
+
+ static readonly string DefaultTitle = Path.DirectorySeparatorChar.ToString();
+
+ string title = DefaultTitle;
+ public string Title
+ {
+ get => title;
+ set => this.RaiseAndSetIfChanged(ref title, value);
+ }
+
+ public ObservableCollection PathInfos { get; } = GetRootPathInfoViewModels>();
+
+ public class PathInfoViewModel : ReactiveObject
+ {
+ public PathInfoViewModel(FileSystemInfo fileSystemInfo, string? name = null)
+ {
+ Name = name ?? fileSystemInfo.Name;
+ FullName = fileSystemInfo.FullName;
+ Desc = $" | {fileSystemInfo.CreationTime.ToString(DateTimeFormat.Standard)}";
+ if (fileSystemInfo is FileInfo fileInfo)
+ {
+ IsDirectory = false;
+ var fileSize = GetSize(fileInfo.Length);
+ Desc = $"Size: {fileSize}{Desc}";
+ }
+ else if (fileSystemInfo is DirectoryInfo dirInfo)
+ {
+ IsDirectory = true;
+ var ignore_rule_log_under_cache_on_cache = AppHelper.LogUnderCache && dirInfo.FullName == IOPath.CacheDirectory;
+ var filesCount = dirInfo.GetFiles().Length;
+ var dirsCount = dirInfo.GetDirectories().Length;
+ Desc = $"Count: {filesCount + (ignore_rule_log_under_cache_on_cache ? dirsCount - 1 : dirsCount)}{Desc}";
+ }
+ else
+ {
+ throw new NotSupportedException();
+ }
+ }
+
+ public bool IsDirectory { get; }
+
+ public string Name { get; }
+
+ public string FullName { get; }
+
+ public string Desc { get; }
+ }
+
+ static IEnumerable> GetRootPaths()
+ {
+ yield return new(IOPath.AppDataDirectory, IOPath.DirName_AppData);
+ yield return new(AppHelper.LogDirPath, AppHelper.LogDirName);
+ yield return new(IOPath.CacheDirectory, IOPath.DirName_Cache);
+ }
+
+ static T GetRootPathInfoViewModels() where T : ICollection, new()
+ {
+ return new T()
+ {
+ new PathInfoViewModel(new DirectoryInfo(IOPath.AppDataDirectory), IOPath.DirName_AppData),
+ new PathInfoViewModel(new DirectoryInfo(IOPath.CacheDirectory), IOPath.DirName_Cache),
+ new PathInfoViewModel(new DirectoryInfo(AppHelper.LogDirPath)),
+ };
+ }
+
+ public static void AddRange(IList list, string dirPath)
+ {
+ var ignore_rule_log_under_cache_on_cache = AppHelper.LogUnderCache && dirPath == IOPath.CacheDirectory;
+ var dirInfo = new DirectoryInfo(dirPath);
+ Array.ForEach(dirInfo.GetDirectories(), x =>
+ {
+ if (ignore_rule_log_under_cache_on_cache && x.Name == AppHelper.LogDirName) return;
+ list.Add(new(x));
+ });
+ Array.ForEach(dirInfo.GetFiles(), x => list.Add(new(x)));
+ }
+
+ public void OnItemClick(PathInfoViewModel pathInfo)
+ {
+ if (pathInfo.IsDirectory)
+ {
+ CurrentPath = pathInfo.FullName;
+ }
+ else
+ {
+ var extension = Path.GetExtension(pathInfo.FullName);
+ if (string.Equals(extension, FileEx.JSON, StringComparison.OrdinalIgnoreCase) ||
+ string.Equals(extension, FileEx.TXT, StringComparison.OrdinalIgnoreCase) ||
+ string.Equals(extension, FileEx.LOG, StringComparison.OrdinalIgnoreCase))
+ {
+ IPlatformService.Instance.OpenFileByTextReader(pathInfo.FullName);
+ }
+ }
+ }
+
+ public bool OnBack()
+ {
+ if (Title != DefaultTitle)
+ {
+ var dirs = Title.Split(Path.DirectorySeparatorChar, StringSplitOptions.RemoveEmptyEntries);
+ if (dirs.Length == 1)
+ {
+ CurrentPath = string.Empty;
+ }
+ else if (dirs.Length > 0)
+ {
+ foreach (var item in GetRootPaths())
+ {
+ if (item.Value == dirs[0])
+ {
+ CurrentPath = Path.Combine(new[] { item.Key }.Concat(dirs.Skip(1).Take(dirs.Length - 2)).ToArray());
+ break;
+ }
+ }
+ }
+ else
+ {
+ return false;
+ }
+ return true;
+ }
+ return false;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ST.Client.Mobile/UI/ViewModels/Pages/About/AboutPageViewModel.cs b/src/ST.Client.Mobile/UI/ViewModels/Pages/About/AboutPageViewModel.cs
index f2b031b3b..a89a70125 100644
--- a/src/ST.Client.Mobile/UI/ViewModels/Pages/About/AboutPageViewModel.cs
+++ b/src/ST.Client.Mobile/UI/ViewModels/Pages/About/AboutPageViewModel.cs
@@ -34,6 +34,7 @@ namespace System.Application.UI.ViewModels
源码仓库,
产品官网,
开放源代码许可,
+ 浏览基目录,
账号注销,
}
@@ -67,6 +68,7 @@ namespace System.Application.UI.ViewModels
PreferenceButton.Bug反馈 => AppResources.BugFeedback,
PreferenceButton.账号注销 => AppResources.DelAccount,
PreferenceButton.社区翻译 => AppResources.Translators,
+ PreferenceButton.浏览基目录 => AppResources.BrowseBaseDirectory,
_ => id.ToString(),
};
return title;
diff --git a/src/ST.Client/AppFolder.cs b/src/ST.Client/AppFolder.cs
index dc04e65b5..358b628b5 100644
--- a/src/ST.Client/AppFolder.cs
+++ b/src/ST.Client/AppFolder.cs
@@ -1,4 +1,4 @@
-using System.IO;
+using System.IO;
namespace System.Application
{
diff --git a/src/ST.Client/Services/IPlatformService.cs b/src/ST.Client/Services/IPlatformService.cs
index 9e0fab936..0158da6b0 100644
--- a/src/ST.Client/Services/IPlatformService.cs
+++ b/src/ST.Client/Services/IPlatformService.cs
@@ -9,8 +9,14 @@ namespace System.Application.Services
public static IPlatformService Instance => DI.Get();
string[] GetMacNetworksetup() => throw new PlatformNotSupportedException();
- async void AdminShell(string str,bool admin=false) => await AdminShellAsync(str,admin);
+ async void AdminShell(string str, bool admin = false) => await AdminShellAsync(str, admin);
ValueTask AdminShellAsync(string str, bool admin = false) => throw new PlatformNotSupportedException();
+
+ ///
+ /// 使用文本阅读器打开文件
+ ///
+ ///
+ void OpenFileByTextReader(string filePath);
}
}
\ No newline at end of file
diff --git a/src/ST.Tools.AreaImport/ST.Tools.AreaImport.csproj b/src/ST.Tools.AreaImport/ST.Tools.AreaImport.csproj
index c94b11d01..bbb3f6131 100644
--- a/src/ST.Tools.AreaImport/ST.Tools.AreaImport.csproj
+++ b/src/ST.Tools.AreaImport/ST.Tools.AreaImport.csproj
@@ -1,4 +1,4 @@
-
+
Exe
diff --git a/src/ST.Tools.OpenSourceLibraryList/ST.Tools.OpenSourceLibraryList.csproj b/src/ST.Tools.OpenSourceLibraryList/ST.Tools.OpenSourceLibraryList.csproj
index 014881130..9845c7b84 100644
--- a/src/ST.Tools.OpenSourceLibraryList/ST.Tools.OpenSourceLibraryList.csproj
+++ b/src/ST.Tools.OpenSourceLibraryList/ST.Tools.OpenSourceLibraryList.csproj
@@ -1,4 +1,4 @@
-
+
Exe
diff --git a/src/ST.Tools.Packager/Application/UI/Views/Form1.cs b/src/ST.Tools.Packager/Application/UI/Views/Form1.cs
index ee90ab1d3..88defa435 100644
--- a/src/ST.Tools.Packager/Application/UI/Views/Form1.cs
+++ b/src/ST.Tools.Packager/Application/UI/Views/Form1.cs
@@ -1,4 +1,4 @@
-using ICSharpCode.SharpZipLib.Tar;
+using ICSharpCode.SharpZipLib.Tar;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
diff --git a/src/ST.Tools.Packager/ST.Tools.Packager.csproj b/src/ST.Tools.Packager/ST.Tools.Packager.csproj
index 0e522ecc5..befb5604e 100644
--- a/src/ST.Tools.Packager/ST.Tools.Packager.csproj
+++ b/src/ST.Tools.Packager/ST.Tools.Packager.csproj
@@ -1,4 +1,4 @@
-
+
WinExe
diff --git a/src/ST.Tools.Translate/ST.Tools.Translate.csproj b/src/ST.Tools.Translate/ST.Tools.Translate.csproj
index 446cf2375..d81fc219a 100644
--- a/src/ST.Tools.Translate/ST.Tools.Translate.csproj
+++ b/src/ST.Tools.Translate/ST.Tools.Translate.csproj
@@ -1,4 +1,4 @@
-
+
Exe
diff --git a/src/Startup2.cs b/src/Startup2.cs
index fbc88f43a..c1dc4b145 100644
--- a/src/Startup2.cs
+++ b/src/Startup2.cs
@@ -98,13 +98,7 @@ namespace System.Application
static void ConfigureRequiredServices(IServiceCollection services)
{
// 添加日志实现
-#if __ANDROID__
- services.AddClientLogging();
-#elif __IOS__
- services.AddLogging(cfg => cfg.AddProvider(NullLoggerProvider.Instance));
-#else
- services.AddDesktopLogging();
-#endif
+ services.AddGeneralLogging();
#if __MOBILE__
// 添加运行时权限
services.TryAddPermissions();