mirror of
https://github.com/BeyondDimension/SteamTools.git
synced 2026-10-28 05:56:52 +08:00
V1.2 新构架
This commit is contained in:
@@ -0,0 +1,69 @@
|
||||
using MessagePack;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
|
||||
namespace System.Application
|
||||
{
|
||||
[DebuggerDisplay("{DebuggerDisplay(),nq}")]
|
||||
[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
|
||||
public sealed class AppClientAttribute : Attribute
|
||||
{
|
||||
string DebuggerDisplay() => $"type: {type}";
|
||||
|
||||
readonly Type? type;
|
||||
string? str;
|
||||
|
||||
[NotNull, DisallowNull] // C# 8 not null
|
||||
public object? Value { get; private set; }
|
||||
|
||||
public AppClientAttribute(Type type, string str)
|
||||
{
|
||||
this.type = type;
|
||||
this.str = str;
|
||||
init();
|
||||
}
|
||||
|
||||
#pragma warning disable IDE1006 // 命名样式
|
||||
|
||||
void init()
|
||||
#pragma warning restore IDE1006 // 命名样式
|
||||
{
|
||||
ctor();
|
||||
|
||||
void ctor()
|
||||
{
|
||||
#if DEBUG
|
||||
var stopwatch = Stopwatch.StartNew();
|
||||
#endif
|
||||
var bytes = str.Base64UrlDecodeToByteArray_Nullable();
|
||||
Value = MessagePackSerializer.Deserialize(type, bytes);
|
||||
str = null;
|
||||
#if DEBUG
|
||||
stopwatch.Stop();
|
||||
Debug.WriteLine($"AppClientAttr init 耗时:{stopwatch.ElapsedMilliseconds}ms");
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
[return: MaybeNull]
|
||||
public static T Get<T>(Assembly? assembly = null)
|
||||
{
|
||||
#if DEBUG
|
||||
var stopwatch = Stopwatch.StartNew();
|
||||
#endif
|
||||
assembly ??= Assembly.GetCallingAssembly();
|
||||
var attrs = assembly.GetCustomAttributes<AppClientAttribute>();
|
||||
var type = typeof(T);
|
||||
var attr = attrs.FirstOrDefault(x => x.Value != null && x.type == type);
|
||||
T t = (attr != default && attr.Value is T value) ? value : default;
|
||||
#if DEBUG
|
||||
stopwatch.Stop();
|
||||
Debug.WriteLine($"AppClientAttr Get<{type.Name}> 耗时:{stopwatch.ElapsedMilliseconds}ms");
|
||||
#endif
|
||||
return t;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
namespace System.Application.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// 应用设置
|
||||
/// </summary>
|
||||
public interface IAppSettings
|
||||
{
|
||||
/// <summary>
|
||||
/// https://appcenter.ms
|
||||
/// </summary>
|
||||
string? AppSecretVisualStudioAppCenter { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
namespace System.Application.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// 页面跳转导航服务
|
||||
/// </summary>
|
||||
public interface INavigationService
|
||||
{
|
||||
/// <summary>
|
||||
/// 页面跳转
|
||||
/// </summary>
|
||||
/// <param name="type"></param>
|
||||
/// <param name="flags"></param>
|
||||
void Push(Type type, PushFlags flags = PushFlags.Empty);
|
||||
|
||||
/// <summary>
|
||||
/// 后退
|
||||
/// </summary>
|
||||
void Pop();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
namespace System.Application.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// 通知栏服务
|
||||
/// </summary>
|
||||
/// <typeparam name="TNotificationType"></typeparam>
|
||||
public interface INotificationService<TNotificationType> where TNotificationType : Enum
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取是否有通知权限
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
bool AreNotificationsEnabled();
|
||||
|
||||
/// <summary>
|
||||
/// 显示通知
|
||||
/// </summary>
|
||||
/// <param name="text">通知内容</param>
|
||||
/// <param name="notificationType">通知类型</param>
|
||||
/// <param name="autoCancel"></param>
|
||||
/// <param name="title">通知标题</param>
|
||||
/// <param name="entrance"></param>
|
||||
void Notify(
|
||||
string text,
|
||||
TNotificationType notificationType,
|
||||
bool autoCancel = true,
|
||||
string? title = default,
|
||||
Type? entrance = default);
|
||||
|
||||
/// <summary>
|
||||
/// 取消通知
|
||||
/// </summary>
|
||||
/// <param name="notificationType"></param>
|
||||
void Cancel(TNotificationType notificationType);
|
||||
|
||||
/// <summary>
|
||||
/// 取消所有通知
|
||||
/// </summary>
|
||||
void CancelAll();
|
||||
|
||||
/// <summary>
|
||||
/// 下载进度通知
|
||||
/// </summary>
|
||||
Progress<float> NotifyDownload(
|
||||
string text,
|
||||
TNotificationType notificationType,
|
||||
string? title = default);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
using System.Threading.Tasks;
|
||||
using Xamarin.Essentials;
|
||||
|
||||
namespace System.Application.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// 运行时权限
|
||||
/// </summary>
|
||||
public interface IPermissions
|
||||
{
|
||||
/// <inheritdoc cref="Permissions.BasePlatformPermission"/>
|
||||
public interface IPermission
|
||||
{
|
||||
/// <inheritdoc cref="Permissions.BasePlatformPermission.CheckStatusAsync"/>
|
||||
Task<PermissionStatus> CheckStatusAsync();
|
||||
|
||||
/// <inheritdoc cref="Permissions.BasePlatformPermission.RequestAsync"/>
|
||||
Task<PermissionStatus> RequestAsync();
|
||||
|
||||
/// <inheritdoc cref="Permissions.BasePlatformPermission.ShouldShowRationale"/>
|
||||
bool ShouldShowRationale();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取手机号码所需权限
|
||||
/// </summary>
|
||||
public interface IGetPhoneNumber : IPermission { }
|
||||
|
||||
/// <summary>
|
||||
/// 检查并申请一组权限
|
||||
/// </summary>
|
||||
/// <param name="permission"></param>
|
||||
/// <returns></returns>
|
||||
Task<PermissionStatus> CheckAndRequestAsync(IPermission permission);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace System.Application.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// 电话服务
|
||||
/// </summary>
|
||||
public interface ITelephonyService
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取当前设备的手机号码
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
Task<string?> GetPhoneNumberAsync();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
namespace System.Application.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// Toast属于一种轻量级的反馈,常常以小弹框的形式出现,一般出现1到2秒会自动消失,可以出现在屏幕上中下任意位置,但同个产品会模块尽量使用同一位置,让用户产生统一认知
|
||||
/// </summary>
|
||||
public interface IToast
|
||||
{
|
||||
/// <summary>
|
||||
/// 显示Toast
|
||||
/// </summary>
|
||||
/// <param name="text"></param>
|
||||
/// <param name="duration"></param>
|
||||
void Show(string text, int? duration = null);
|
||||
|
||||
/// <inheritdoc cref="Show(string, int?)"/>
|
||||
void Show(string text, ToastLength duration);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
namespace System.Application.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// Toast显示拦截
|
||||
/// </summary>
|
||||
public interface IToastIntercept
|
||||
{
|
||||
/// <summary>
|
||||
/// 显示Toast消息前拦截
|
||||
/// </summary>
|
||||
/// <param name="text"></param>
|
||||
/// <returns></returns>
|
||||
bool OnShowExecuting(string text);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
namespace System.Application.Services.Implementation
|
||||
{
|
||||
public sealed class NoneToastIntercept : IToastIntercept
|
||||
{
|
||||
bool IToastIntercept.OnShowExecuting(string text)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
using System.Threading.Tasks;
|
||||
using Xamarin.Essentials;
|
||||
|
||||
namespace System.Application.Services.Implementation
|
||||
{
|
||||
/// <inheritdoc cref="IPermissions"/>
|
||||
public class PermissionsImpl : IPermissions
|
||||
{
|
||||
/// <summary>
|
||||
/// 权限被拒绝,可提示用户在“设置”中启用,通常应有不再提示的选项
|
||||
/// </summary>
|
||||
/// <param name="permission"></param>
|
||||
protected virtual void PromptUserGoToSettings(IPermissions.IPermission permission)
|
||||
{
|
||||
// 可由平台重写此函数实现,通常为显示一个UI上的弹窗,上有复选框不再显示
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 提示用户需要权限的其他信息
|
||||
/// </summary>
|
||||
/// <param name="permission"></param>
|
||||
protected virtual void ShowRationale(IPermissions.IPermission permission)
|
||||
{
|
||||
// 可由平台重写此函数实现,通常为显示一个吐司消息,因调用此函数后会立即开始重新申请权限
|
||||
}
|
||||
|
||||
public async Task<PermissionStatus> CheckAndRequestAsync(IPermissions.IPermission permission)
|
||||
{
|
||||
// https://docs.microsoft.com/zh-cn/xamarin/essentials/permissions?context=xamarin%2Fandroid&tabs=android#general-usage
|
||||
var status = await permission.CheckStatusAsync();
|
||||
|
||||
if (status == PermissionStatus.Granted)
|
||||
return status;
|
||||
|
||||
if (status == PermissionStatus.Denied && DeviceInfo.Platform == DevicePlatform.iOS)
|
||||
{
|
||||
// Prompt the user to turn on in settings
|
||||
// 提示用户在“设置”中启用
|
||||
// On iOS once a permission has been denied it may not be requested again from the application
|
||||
// 在iOS上,一旦权限被拒绝,就不能再次从应用程序请求权限
|
||||
PromptUserGoToSettings(permission);
|
||||
return status;
|
||||
}
|
||||
|
||||
if (permission.ShouldShowRationale())
|
||||
{
|
||||
// Prompt the user with additional information as to why the permission is needed
|
||||
// 提示用户需要权限的其他信息
|
||||
ShowRationale(permission);
|
||||
}
|
||||
|
||||
status = await permission.RequestAsync();
|
||||
|
||||
if (status == PermissionStatus.Denied)
|
||||
{
|
||||
PromptUserGoToSettings(permission);
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace System.Application.Services.Implementation
|
||||
{
|
||||
/// <inheritdoc cref="ITelephonyService"/>
|
||||
public abstract class TelephonyServiceImpl : ITelephonyService
|
||||
{
|
||||
readonly IPermissions pf;
|
||||
readonly IPermissions.IGetPhoneNumber p1;
|
||||
|
||||
public TelephonyServiceImpl(IPermissions pf, IPermissions.IGetPhoneNumber p1)
|
||||
{
|
||||
this.pf = pf;
|
||||
this.p1 = p1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 由特定平台实现的获取手机号码
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
protected abstract string? PlatformGetPhoneNumber();
|
||||
|
||||
/// <summary>
|
||||
/// 由特定平台实现的检查权限后获取手机号码
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
protected virtual async Task<string?> PlatformGetPhoneNumberAsync()
|
||||
{
|
||||
var status = await pf.CheckAndRequestAsync(p1);
|
||||
if (status.IsOk())
|
||||
{
|
||||
return PlatformGetPhoneNumber();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public async Task<string?> GetPhoneNumberAsync()
|
||||
{
|
||||
var phoneNumber = await PlatformGetPhoneNumberAsync();
|
||||
return phoneNumber;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using Xamarin.Essentials;
|
||||
|
||||
namespace System.Application.Services.Implementation
|
||||
{
|
||||
public abstract class ToastImpl : IToast
|
||||
{
|
||||
protected readonly IToastIntercept intercept;
|
||||
|
||||
protected const string TAG = "Toast";
|
||||
|
||||
public ToastImpl(IToastIntercept intercept)
|
||||
{
|
||||
this.intercept = intercept;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据字符串长度计算持续时间
|
||||
/// </summary>
|
||||
/// <param name="len"></param>
|
||||
/// <returns></returns>
|
||||
protected virtual int CalcDurationByStringLength(int len)
|
||||
=> len > 9 ? ToDuration(ToastLength.Long) : ToDuration(ToastLength.Short);
|
||||
|
||||
public void Show(string text, int? duration)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (MainThread.IsMainThread)
|
||||
{
|
||||
Show_();
|
||||
}
|
||||
else
|
||||
{
|
||||
MainThread.BeginInvokeOnMainThread(Show_);
|
||||
}
|
||||
|
||||
void Show_()
|
||||
{
|
||||
if (intercept.OnShowExecuting(text)) return;
|
||||
var _duration = duration ?? CalcDurationByStringLength(text.Length);
|
||||
PlatformShow(text, _duration);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(TAG, e, "ShowToast Error, text: {0}", text);
|
||||
}
|
||||
}
|
||||
|
||||
public void Show(string text, ToastLength duration) => Show(text, ToDuration(duration));
|
||||
|
||||
protected abstract void PlatformShow(string text, int duration);
|
||||
|
||||
/// <summary>
|
||||
/// 将 <see cref="ToastLength"/> 转换为 持续时间
|
||||
/// </summary>
|
||||
/// <param name="toastLength"></param>
|
||||
/// <returns></returns>
|
||||
protected virtual int ToDuration(ToastLength toastLength) => (int)toastLength;
|
||||
|
||||
protected static IServiceCollection TryAddToast<TToastImpl>(IServiceCollection services) where TToastImpl : class, IToast
|
||||
{
|
||||
services.TryAddSingleton<IToastIntercept, NoneToastIntercept>();
|
||||
services.TryAddSingleton<IToast, TToastImpl>();
|
||||
return services;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
namespace System.Application.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// 通知重要性级别
|
||||
/// <para>Android: https://developer.android.google.cn/training/notify-user/channels#importance </para>
|
||||
/// </summary>
|
||||
public enum NotificationImportanceLevel : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// 低 没有声音,也没有出现在状态栏中。
|
||||
/// </summary>
|
||||
Low,
|
||||
|
||||
/// <summary>
|
||||
/// 中等 没有声音
|
||||
/// </summary>
|
||||
Medium,
|
||||
|
||||
/// <summary>
|
||||
/// 高 发出声音
|
||||
/// </summary>
|
||||
High,
|
||||
|
||||
/// <summary>
|
||||
/// 紧急 发出声音,并显示为浮动通知
|
||||
/// <para>浮动通知: 从 Android 5.0 开始,通知可以在称为浮动通知的浮动窗口中短暂显示。此行为通常针对用户应立即了解的重要通知,并且仅在设备解锁后才会出现。https://developer.android.google.cn/guide/topics/ui/notifiers/notifications?hl=zh-cn#Heads-up </para>
|
||||
/// </summary>
|
||||
Urgent,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
namespace System.Application.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// 页面跳转的逻辑参数
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum PushFlags
|
||||
{
|
||||
Empty = 0,
|
||||
|
||||
/// <summary>
|
||||
/// CurrentActivity.Finish()
|
||||
/// </summary>
|
||||
Finish = 0x00000001,
|
||||
|
||||
/// <summary>
|
||||
/// ActivityFlags.ClearTop
|
||||
/// </summary>
|
||||
ClearTop = 0x00000010,
|
||||
|
||||
/// <summary>
|
||||
/// ActivityFlags.SingleTop
|
||||
/// </summary>
|
||||
SingleTop = 0x00000100,
|
||||
|
||||
/// <summary>
|
||||
/// CurrentActivity.OverridePendingTransition(0, 0)
|
||||
/// </summary>
|
||||
OverridePendingTransitionZeroZero = 0x00001000,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace System.Application.UI.Adapters
|
||||
{
|
||||
/// <summary>
|
||||
/// 适配器
|
||||
/// </summary>
|
||||
/// <typeparam name="TViewModel"></typeparam>
|
||||
public interface IAdapter<TViewModel> : ICreateViewModels<TViewModel>
|
||||
{
|
||||
/// <summary>
|
||||
/// 视图模型
|
||||
/// </summary>
|
||||
IList<TViewModel> ViewModels { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 列表项点击事件
|
||||
/// </summary>
|
||||
event EventHandler<ItemClickEventArgs<TViewModel>>? ItemClick;
|
||||
|
||||
/// <summary>
|
||||
/// 列表项【长按】点击事件
|
||||
/// </summary>
|
||||
event EventHandler<ItemClickEventArgs<TViewModel>>? ItemLongClick;
|
||||
|
||||
void NotifyDataSetChanged();
|
||||
|
||||
/// <summary>
|
||||
/// 替换视图模型,默认替换后刷新
|
||||
/// </summary>
|
||||
/// <param name="newViewModels"></param>
|
||||
/// <param name="notify"></param>
|
||||
public void Replace(IEnumerable<TViewModel>? newViewModels, bool notify = true)
|
||||
{
|
||||
if (ViewModels.IsReadOnly)
|
||||
{
|
||||
if (newViewModels != null && newViewModels.Any())
|
||||
{
|
||||
ViewModels = CreateViewModels(newViewModels);
|
||||
}
|
||||
else
|
||||
{
|
||||
ViewModels = CreateViewModels();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ViewModels.Clear();
|
||||
if (newViewModels != null && newViewModels.Any())
|
||||
{
|
||||
ViewModels.AddRange(newViewModels);
|
||||
}
|
||||
}
|
||||
if (notify)
|
||||
{
|
||||
NotifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 添加新的视图模型,默认添加后刷新
|
||||
/// </summary>
|
||||
/// <param name="newViewModels"></param>
|
||||
/// <param name="notify"></param>
|
||||
public void AddRange(IEnumerable<TViewModel>? newViewModels, bool notify = true)
|
||||
{
|
||||
if (newViewModels != null && newViewModels.Any())
|
||||
{
|
||||
ViewModels.AddRange(newViewModels);
|
||||
if (notify)
|
||||
{
|
||||
NotifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据下标移除视图模型,默认移除后刷新
|
||||
/// </summary>
|
||||
/// <param name="index"></param>
|
||||
/// <param name="notify"></param>
|
||||
public void RemoveAt(int index, bool notify = true)
|
||||
{
|
||||
if (index >= 0 && index < ViewModels.Count)
|
||||
{
|
||||
ViewModels.RemoveAt(index);
|
||||
if (notify)
|
||||
{
|
||||
NotifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据值移除视图模型,默认移除后刷新
|
||||
/// </summary>
|
||||
/// <param name="vm"></param>
|
||||
/// <param name="notify"></param>
|
||||
public void Remove(TViewModel vm, bool notify = true)
|
||||
{
|
||||
if (ViewModels.Remove(vm))
|
||||
{
|
||||
if (notify)
|
||||
{
|
||||
NotifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据点击参数移除视图模型,默认移除后刷新
|
||||
/// </summary>
|
||||
/// <param name="args"></param>
|
||||
/// <param name="notify"></param>
|
||||
public void Remove(ItemClickEventArgs<TViewModel> args, bool notify = true)
|
||||
=> RemoveAt(args.Position, notify);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace System.Application.UI.Adapters
|
||||
{
|
||||
public interface ICreateViewModels<TViewModel>
|
||||
{
|
||||
public IList<TViewModel> CreateViewModels();
|
||||
|
||||
public IList<TViewModel> CreateViewModels(IEnumerable<TViewModel> newViewModels);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
namespace System.Application.UI.Adapters
|
||||
{
|
||||
/// <summary>
|
||||
/// 列表项点击事件参数
|
||||
/// </summary>
|
||||
/// <typeparam name="TViewModel"></typeparam>
|
||||
#pragma warning disable IDE1006 // 命名样式
|
||||
public interface ItemClickEventArgs<TViewModel>
|
||||
#pragma warning restore IDE1006 // 命名样式
|
||||
{
|
||||
/// <summary>
|
||||
/// 位置
|
||||
/// </summary>
|
||||
int Position { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 当前项
|
||||
/// </summary>
|
||||
TViewModel Current { get; }
|
||||
|
||||
bool Handled { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System.Text;
|
||||
|
||||
namespace System.Logging
|
||||
{
|
||||
/// <summary>
|
||||
/// 客户端日志
|
||||
/// <para>https://github.com/dotnet/extensions/blob/v3.1.5/src/Logging/Logging.Console/src/ConsoleLogger.cs</para>
|
||||
/// </summary>
|
||||
public abstract class ClientLogger : ILogger
|
||||
{
|
||||
static readonly string _messagePadding;
|
||||
static readonly string _newLineWithMessagePadding;
|
||||
|
||||
static ClientLogger()
|
||||
{
|
||||
_messagePadding = new string(' ', 6);
|
||||
_newLineWithMessagePadding = Environment.NewLine + _messagePadding;
|
||||
}
|
||||
|
||||
protected readonly string name;
|
||||
|
||||
public ClientLogger(string name) => this.name = name;
|
||||
|
||||
public virtual IDisposable BeginScope<TState>(TState state)
|
||||
{
|
||||
return NullScope.Instance;
|
||||
}
|
||||
|
||||
public virtual bool IsEnabled(LogLevel logLevel) => logLevel != LogLevel.None;
|
||||
|
||||
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> formatter)
|
||||
{
|
||||
if (!IsEnabled(logLevel))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (formatter == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(formatter));
|
||||
}
|
||||
|
||||
var message = formatter(state, exception);
|
||||
|
||||
if (!string.IsNullOrEmpty(message) || exception != null)
|
||||
{
|
||||
WriteMessage(logLevel, name, eventId.Id, message, exception);
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void WriteMessage(LogLevel logLevel, string logName, int eventId, string? message, Exception? exception)
|
||||
{
|
||||
var logBuilder = new StringBuilder();
|
||||
|
||||
CreateDefaultLogMessage(logBuilder, logName, eventId, message, exception);
|
||||
|
||||
var logMessage = logBuilder.ToString();
|
||||
WriteMessage(logLevel, logMessage);
|
||||
}
|
||||
|
||||
void CreateDefaultLogMessage(StringBuilder logBuilder, string logName, int eventId, string? message, Exception? exception)
|
||||
{
|
||||
// Example:
|
||||
// INFO: ConsoleApp.Program[10]
|
||||
// Request received
|
||||
|
||||
// category and event id
|
||||
logBuilder.Append(logName);
|
||||
logBuilder.Append("[");
|
||||
logBuilder.Append(eventId);
|
||||
logBuilder.AppendLine("]");
|
||||
|
||||
if (!string.IsNullOrEmpty(message))
|
||||
{
|
||||
// message
|
||||
logBuilder.Append(_messagePadding);
|
||||
|
||||
var len = logBuilder.Length;
|
||||
logBuilder.AppendLine(message);
|
||||
logBuilder.Replace(Environment.NewLine, _newLineWithMessagePadding, len, message.Length);
|
||||
}
|
||||
|
||||
// Example:
|
||||
// System.InvalidOperationException
|
||||
// at Namespace.Class.Function() in File:line X
|
||||
if (exception != null)
|
||||
{
|
||||
// exception message
|
||||
logBuilder.AppendLine(exception.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
public abstract void WriteMessage(LogLevel logLevel, string message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
namespace System.Logging
|
||||
{
|
||||
/// <summary>
|
||||
/// An empty scope without any logic
|
||||
/// <para>https://github.com/dotnet/extensions/blob/v3.1.5/src/Logging/shared/NullScope.cs</para>
|
||||
/// <para>https://github.com/dotnet/runtime/blob/v5.0.0-rtm.20519.4/src/libraries/Common/src/Extensions/Logging/NullScope.cs</para>
|
||||
/// </summary>
|
||||
public class NullScope : IDisposable
|
||||
{
|
||||
public static NullScope Instance { get; } = new NullScope();
|
||||
|
||||
private NullScope()
|
||||
{
|
||||
}
|
||||
|
||||
#pragma warning disable CA1816 // Dispose 方法应调用 SuppressFinalize
|
||||
public void Dispose()
|
||||
#pragma warning restore CA1816 // Dispose 方法应调用 SuppressFinalize
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
using System.Threading.Tasks;
|
||||
using Xamarin.Essentials;
|
||||
|
||||
namespace System.Security
|
||||
{
|
||||
/// <summary>
|
||||
/// https://docs.microsoft.com/zh-cn/xamarin/essentials/secure-storage
|
||||
/// </summary>
|
||||
internal sealed class ClientStorage : IStorage
|
||||
{
|
||||
string? IStorage.Get(string key)
|
||||
{
|
||||
// https://github.com/xamarin/Essentials/blob/1.5.3.2/Xamarin.Essentials/SecureStorage/SecureStorage.android.cs#L74
|
||||
// https://github.com/xamarin/Essentials/blob/1.5.3.2/Xamarin.Essentials/SecureStorage/SecureStorage.ios.tvos.watchos.cs#L26
|
||||
// https://github.com/xamarin/Essentials/blob/1.5.3.2/Xamarin.Essentials/SecureStorage/SecureStorage.uwp.cs#L23
|
||||
Func<Task<string?>> func = () => SecureStorage.GetAsync(key);
|
||||
var result = func.RunSync();
|
||||
return result;
|
||||
}
|
||||
|
||||
Task<string?> IStorage.GetAsync(string key)
|
||||
{
|
||||
return SecureStorage.GetAsync(key);
|
||||
}
|
||||
|
||||
void IStorage.Set(string key, string? value)
|
||||
{
|
||||
// https://github.com/xamarin/Essentials/blob/1.5.3.2/Xamarin.Essentials/SecureStorage/SecureStorage.android.cs#L93
|
||||
// https://github.com/xamarin/Essentials/blob/1.5.3.2/Xamarin.Essentials/SecureStorage/SecureStorage.ios.tvos.watchos.cs#L34
|
||||
// https://github.com/xamarin/Essentials/blob/1.5.3.2/Xamarin.Essentials/SecureStorage/SecureStorage.uwp.cs#L37
|
||||
if (string.IsNullOrEmpty(value))
|
||||
{
|
||||
SecureStorage.Remove(key);
|
||||
return;
|
||||
}
|
||||
Func<Task> func = () => SecureStorage.SetAsync(key, value);
|
||||
func.RunSync();
|
||||
}
|
||||
|
||||
Task IStorage.SetAsync(string key, string? value)
|
||||
{
|
||||
if (string.IsNullOrEmpty(value))
|
||||
{
|
||||
SecureStorage.Remove(key);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
return SecureStorage.SetAsync(key, value);
|
||||
}
|
||||
|
||||
bool IStorage.Remove(string key)
|
||||
{
|
||||
return SecureStorage.Remove(key);
|
||||
}
|
||||
|
||||
Task<bool> IStorage.RemoveAsync(string key)
|
||||
{
|
||||
var result = SecureStorage.Remove(key);
|
||||
return Task.FromResult(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using System.Application.Services;
|
||||
using System.Application.Services.Implementation;
|
||||
using System.Security;
|
||||
|
||||
// ReSharper disable once CheckNamespace
|
||||
namespace Microsoft.Extensions.DependencyInjection
|
||||
{
|
||||
public static class ServiceCollectionExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// 添加由 Xamarin.Essentials.SecureStorage 实现的 <see cref="IStorage"/>
|
||||
/// </summary>
|
||||
/// <param name="services"></param>
|
||||
/// <returns></returns>
|
||||
public static IServiceCollection TryAddStorage(this IServiceCollection services)
|
||||
{
|
||||
services.TryAddSingleton<IStorage, ClientStorage>();
|
||||
return services;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 添加运行时权限
|
||||
/// </summary>
|
||||
/// <param name="services"></param>
|
||||
/// <returns></returns>
|
||||
public static IServiceCollection TryAddPermissions(this IServiceCollection services)
|
||||
{
|
||||
services.TryAddSingleton<IPermissions, PermissionsImpl>();
|
||||
return services;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.1</TargetFramework>
|
||||
<LangVersion>9.0</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
<SignAssembly>true</SignAssembly>
|
||||
<DelaySign>false</DelaySign>
|
||||
<AssemblyOriginatorKeyFile>..\Unicorn.pfx</AssemblyOriginatorKeyFile>
|
||||
<RootNamespace>System</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Include="..\System.Common.CoreLib\Properties\AssemblyInfo.cs">
|
||||
<Link>Properties\AssemblyInfo.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\System.Common.CoreLib\Properties\InternalsVisibleTo.cs">
|
||||
<Link>Properties\InternalsVisibleTo.cs</Link>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\System.Application.Repositories.sqlite-net-pcl\System.Application.Repositories.sqlite-net-pcl.csproj" />
|
||||
<ProjectReference Include="..\System.Common.CoreLib\System.Common.CoreLib.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,22 @@
|
||||
using System.Application.Services;
|
||||
|
||||
namespace System
|
||||
{
|
||||
/// <inheritdoc cref="IToast"/>
|
||||
public static class Toast
|
||||
{
|
||||
/// <inheritdoc cref="IToast.Show(string, int?)"/>
|
||||
public static void Show(string text, int? duration = null)
|
||||
{
|
||||
var toast = DI.Get<IToast>();
|
||||
toast.Show(text, duration);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="IToast.Show(string, ToastLength)"/>
|
||||
public static void Show(string text, ToastLength duration)
|
||||
{
|
||||
var toast = DI.Get<IToast>();
|
||||
toast.Show(text, duration);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
// Length对应的毫秒数值参考文章 https://www.cnblogs.com/kborid/p/14078744.html
|
||||
|
||||
namespace System
|
||||
{
|
||||
public enum ToastLength
|
||||
{
|
||||
/// <summary>
|
||||
/// Show the view or text notification for a short period of time.
|
||||
/// </summary>
|
||||
Short = 2000,
|
||||
|
||||
/// <summary>
|
||||
/// Show the view or text notification for a long period of time.
|
||||
/// </summary>
|
||||
Long = 3500,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user