Procházet zdrojové kódy

Merge branch 'master' of https://gitlab.kdanmobile.com/windows-team/kdancommon

姜青儀 před 1 rokem
rodič
revize
0daafc1b33

+ 310 - 0
CMSCollection/CMSCollection.cs

@@ -0,0 +1,310 @@
+using KdanCommon.CMSCollection.Data;
+using KdanCommon.Helpers;
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using System.Net.NetworkInformation;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using Windows.Data.Json;
+using Windows.Globalization;
+using Windows.Networking.Connectivity;
+using Windows.UI.Popups;
+using Windows.Web.Http;
+using Windows.Web.Http.Filters;
+
+namespace KdanCommon.CMSCollection
+{
+    public class CMSCollection
+    {
+        public static string CMSDomain = "https://cms.kdanmobile.com";
+
+        private List<ISetting> _settingList = null;
+        private HttpClient _httpClient = null;
+        private Uri _windowsCardsUri = null;
+          private Uri _windowsEventbarUri = null;
+        private Uri _pdfViewerEventBarSettingUri = null;
+        private Uri _pdfOtherSettingUri = null;
+        private string _appType = null;
+        private bool _loadCardsOnly = false;
+        private SemaphoreSlim _loadSettingSS = new SemaphoreSlim(1);
+        private bool _isLoaded = false;
+        private object settingLock = new object();
+
+        public CMSCollection(string appType, bool loadCardOnly = true)
+        {
+            var filter = new HttpBaseProtocolFilter();
+            filter.CacheControl.ReadBehavior = HttpCacheReadBehavior.MostRecent;
+            _httpClient = new HttpClient(filter);
+            _windowsCardsUri = new Uri($"{CMSDomain}/items/windows_cards");
+            _pdfViewerEventBarSettingUri = new Uri($"{CMSDomain}/items/pdf_remote_config");
+            _pdfOtherSettingUri = new Uri($"{CMSDomain}/items/PDF_UWP_RemoteConfig");
+            _windowsEventbarUri = new Uri($"{CMSDomain}/items/windows_eventbar");
+            _appType = appType;
+            _loadCardsOnly = loadCardOnly;
+            _settingList = new List<ISetting>();
+
+            Windows.Networking.Connectivity.NetworkInformation.NetworkStatusChanged += NetworkInformation_NetworkStatusChanged;
+        }
+
+        ~CMSCollection()
+        {
+            _httpClient.Dispose();
+
+            Windows.Networking.Connectivity.NetworkInformation.NetworkStatusChanged -= NetworkInformation_NetworkStatusChanged;
+        }
+
+        private async void NetworkInformation_NetworkStatusChanged(object sender)
+        {
+            if (IsInternetAvailable())
+                await LoadSettings();
+        }
+
+        #region Public Method
+        public async Task<ISetting> GetSetting(string settingId)
+        {
+            await LoadSettings();
+            lock (settingLock)
+                return _settingList.FirstOrDefault(s => s.SettingName == settingId);
+        }
+
+        public async Task<List<WindowsCardSetting>> GetCardSettings()
+        {
+            await LoadSettings();
+            lock (settingLock)
+                return _settingList.Where(s => s is WindowsCardSetting).Select(s => s as WindowsCardSetting).OrderByDescending(w => w.Weight).ToList();
+        }
+        #endregion
+
+        #region Private Method
+        private async Task LoadSettings()
+        {
+            try
+            {
+                await _loadSettingSS.WaitAsync();
+                if (!_isLoaded)
+                {
+                    lock (settingLock)
+                        _settingList.Clear();
+
+                    if (!IsInternetAvailable())
+                        return;
+
+                    var allTask = new List<Task>();
+                    allTask.Add(GetWindowsCards());
+                    if (!_loadCardsOnly)
+                    {
+                        allTask.Add(GetViewerEventBarSetting());
+                        allTask.Add(GetOtherSetting());
+                    }
+                    await Task.WhenAll(allTask);
+                    _isLoaded = true;
+                }
+            }
+            catch
+            {
+
+            }
+            finally
+            {
+                _loadSettingSS.Release();
+            }
+        }
+
+        private async Task GetViewerEventBarSetting()
+        {
+            var region = GetSystemRegion();
+            var appLang = GetAppLanguage();
+            var querys = new List<KeyValuePair<string, string>>();
+            querys.Add(new KeyValuePair<string, string>("deep[evnet_bar_text_translations][_filter][languages_code][_eq]", appLang));
+            querys.Add(new KeyValuePair<string, string>("deep[dialog_text_translations][_filter][languages_code][_eq]", appLang));
+            querys.Add(new KeyValuePair<string, string>("deep[dialog_button][dialog_button_translations][_filter][languages_code][_eq]", appLang));
+            querys.Add(new KeyValuePair<string, string>("deep[do_not_show_translations][_filter][languages_code][_eq]", appLang));
+            querys.Add(new KeyValuePair<string, string>("filter[platform][_eq]", "windows"));
+            querys.Add(new KeyValuePair<string, string>("filter[event_visible][_eq]", "true"));
+            querys.Add(new KeyValuePair<string, string>("filter[regions][_contains]", region));
+            querys.Add(new KeyValuePair<string, string>("fields", "*,do_not_show_translations.*,evnet_bar_text_translations.*,dialog_text_translations.*,dialog_button.*,dialog_button.dialog_button_translations.*"));
+            var result = await _httpClient.GetRequest(_pdfViewerEventBarSettingUri, querys);
+            if (result.StatusCode == HttpStatusCode.Ok)
+            {
+                var jsonString = await result.Content.ReadAsStringAsync();
+                var response = JsonTool.DeserializeJSON<ViewerEventBarSettingResponse>(jsonString);
+                if (response != null && response.Data != null && response.Data.Length > 0)
+                {
+                    lock (settingLock)
+                        _settingList.Add(response.Data[0]);
+                }
+            }
+        }
+        private async Task GetOtherSetting()
+        {
+            var querys = new List<KeyValuePair<string, string>>();
+            querys.Add(new KeyValuePair<string, string>("filter[status][_eq]", "published"));
+            querys.Add(new KeyValuePair<string, string>("filter[appTypes][_contains]", _appType));
+            querys.Add(new KeyValuePair<string, string>("fields", "*"));
+            var result = await _httpClient.GetRequest(_pdfOtherSettingUri, querys);
+            if (result.StatusCode == HttpStatusCode.Ok)
+            {
+                var jsonString = await result.Content.ReadAsStringAsync();
+                dynamic dyna = DynamicJsonHelper.ToDynamicJson(jsonString);
+                if (dyna == null)
+                    return;
+                List<dynamic> settings = DynamicJsonHelper.GetArraySafety(dyna.data);
+                if (settings == null)
+                    return;
+                foreach (var setting in settings)
+                {
+                    string configName = DynamicJsonHelper.GetValueSafety<string>(setting.config_name, null);
+                    if (!string.IsNullOrEmpty(configName) && setting.json_data != null)
+                    {
+                        lock (settingLock)
+                            _settingList.Add(new JsonSetting(configName, setting.json_data));
+                    }
+                }
+            }
+        }
+        private async Task GetWindowsCards()
+        {
+            string region = GetSystemRegion();
+            string lang = GetAppLanguage();
+            var querys = new List<KeyValuePair<string, string>>();
+            querys.Add(new KeyValuePair<string, string>("deep[Information][_filter][languages_code][_eq]", lang));
+            querys.Add(new KeyValuePair<string, string>("filter[_or][0][include_regions][_eq]", "true"));
+            querys.Add(new KeyValuePair<string, string>("filter[_or][0][regions][_contains]", region));
+            querys.Add(new KeyValuePair<string, string>("filter[_or][1][include_regions][_eq]", "false"));
+            querys.Add(new KeyValuePair<string, string>("filter[_or][1][regions][_ncontains]", region));
+            querys.Add(new KeyValuePair<string, string>("filter[appTypes][_contains]", _appType));
+            querys.Add(new KeyValuePair<string, string>("filter[status][_eq]", "published"));
+            querys.Add(new KeyValuePair<string, string>("fields", "*,Information.*"));
+            var result = await _httpClient.GetRequest(_windowsCardsUri, querys);
+            if (result.StatusCode == HttpStatusCode.Ok)
+            {
+                var jsonString = await result.Content.ReadAsStringAsync();
+                var response = JsonTool.DeserializeJSON<WindowsCardsResponse>(jsonString);
+                if (response != null && response.Data != null && response.Data.Length > 0)
+                {
+                    foreach (var card in response.Data)
+                    {
+                        lock (settingLock)
+                            _settingList.Add(card);
+                    }
+                }
+            }
+        }
+
+        public async Task<List<WindowsEventbarSetting>> GetWindowsEventbarAsync()
+        {
+            var current = DateTime.UtcNow.ToString("s", CultureInfo.GetCultureInfo("en-us"));
+            string region = GetSystemRegion();
+            string lang = GetAppLanguage();
+            var querys = new List<KeyValuePair<string, string>>();
+            querys.Add(new KeyValuePair<string, string>("deep[event_information][_filter][languages_code][_eq]", lang));
+            querys.Add(new KeyValuePair<string, string>("filter[_or][0][include_regions][_eq]", "true"));
+            querys.Add(new KeyValuePair<string, string>("filter[_or][0][regions][_contains]", region));
+            querys.Add(new KeyValuePair<string, string>("filter[_or][1][include_regions][_eq]", "false"));
+            querys.Add(new KeyValuePair<string, string>("filter[_or][1][regions][_ncontains]", region));
+            querys.Add(new KeyValuePair<string, string>("filter[_or][2][include_regions][_eq]", "false"));
+            querys.Add(new KeyValuePair<string, string>("filter[_or][2][regions][_null]", "true"));
+            querys.Add(new KeyValuePair<string, string>("filter[start][_lte]", current));
+            querys.Add(new KeyValuePair<string, string>("filter[end][_gte]", current));
+            querys.Add(new KeyValuePair<string, string>("filter[app_types][_contains]", _appType));
+            querys.Add(new KeyValuePair<string, string>("filter[status][_eq]", "published"));
+            querys.Add(new KeyValuePair<string, string>("fields", "*,event_information.*"));
+            var result = await _httpClient.GetRequest(_windowsEventbarUri, querys);
+            if (result.StatusCode == HttpStatusCode.Ok)
+            {
+                var jsonString = await result.Content.ReadAsStringAsync();
+                var response = JsonTool.DeserializeJSON<WindowsEventbarResponse>(jsonString);
+                if (response != null && response.Data != null && response.Data.Length > 0)
+                {
+                    return response.Data.ToList();
+                }
+                else
+                    return new List<WindowsEventbarSetting>();
+            }
+            return null;
+        }
+
+        private string GetSystemRegion()
+        {
+            var geographyCountryCode = new GeographicRegion().CodeTwoLetter;
+            switch (geographyCountryCode)
+            {
+                case "GB":
+                case "US":
+                case "TW":
+                case "CN":
+                case "JP":
+                case "ES":
+                case "IT":
+                case "FR":
+                case "DE":
+                case "RU":
+                case "PT":
+                case "KR":
+                    return geographyCountryCode;
+                default:
+                    return "OTHER";
+            }
+        }
+
+        private string GetAppLanguage()
+        {
+            try
+            {
+                var currentLang = Windows.ApplicationModel.Resources.Core.ResourceContext.GetForCurrentView().Languages[0];
+                switch (currentLang)
+                {
+                    case "de-DE":
+                    case "de":
+                        return "de";
+                    case "es-ES":
+                    case "es":
+                        return "es";
+                    case "fr-FR":
+                    case "fr":
+                        return "fr";
+                    case "it-IT":
+                    case "it":
+                        return "it";
+                    case "ja-JP":
+                    case "ja":
+                        return "ja";
+                    case "zh-Hans-CN":
+                    case "zh-Hans":
+                    case "zh-CN":
+                        return "zh-cn";
+                    case "zh-Hant-TW":
+                    case "zh-Hant":
+                    case "zh-TW":
+                        return "zh-tw";
+                    case "ru-RU":
+                    case "ru":
+                        return "ru";
+                    case "ko-KR":
+                    case "ko":
+                        return "ko";
+                    case "pt-PT":
+                    case "pt":
+                        return "pt";
+                    default:
+                        return "en";
+                }
+            }
+            catch
+            {
+                return "en";
+            }
+        }
+
+        private bool IsInternetAvailable()
+        {
+            ConnectionProfile connections = NetworkInformation.GetInternetConnectionProfile();
+            bool isInternetAvailable = connections != null && connections.GetNetworkConnectivityLevel() == NetworkConnectivityLevel.InternetAccess;
+            return isInternetAvailable;
+        }
+        #endregion
+    }
+}

+ 13 - 0
CMSCollection/Data/ISetting.cs

@@ -0,0 +1,13 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace KdanCommon.CMSCollection.Data
+{
+    public interface ISetting
+    {
+        string SettingName { get; }
+    }
+}

+ 26 - 0
CMSCollection/Data/JsonSetting.cs

@@ -0,0 +1,26 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace KdanCommon.CMSCollection.Data
+{
+    public class JsonSetting : ISetting
+    {
+        public string SettingName
+        {
+            get
+            {
+                return Key;
+            }
+        }
+        public string Key { get; private set; }
+        public dynamic JsonData { get; private set; }
+        public JsonSetting(string key, dynamic jsonData)
+        {
+            Key = key;
+            JsonData = jsonData;
+        }
+    }
+}

+ 185 - 0
CMSCollection/Data/ViewerEventBarSettingResponse.cs

@@ -0,0 +1,185 @@
+using Newtonsoft.Json;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace KdanCommon.CMSCollection.Data
+{
+    public partial class ViewerEventBarSettingResponse
+    {
+        [JsonProperty("data")]
+        public ViewerEventBarSetting[] Data { get; set; }
+    }
+
+    public partial class ViewerEventBarSetting : ISetting
+    {
+        public string SettingName
+        {
+            get
+            {
+                return "ViewerEventBarSetting";
+            }
+        }
+
+        [JsonProperty("id")]
+        public long Id { get; set; }
+
+        [JsonProperty("event_name")]
+        public string EventName { get; set; }
+
+        [JsonProperty("target_user")]
+        public string TargetUser { get; set; }
+
+        [JsonProperty("start_time")]
+        public long StartTime { get; set; }
+
+        [JsonProperty("end_time")]
+        public long EndTime { get; set; }
+
+        [JsonProperty("event_bar_text_color")]
+        public string EventBarTextColor { get; set; }
+
+        [JsonProperty("event_bar_background_color")]
+        public string EventBarBackgroundColor { get; set; }
+
+        [JsonProperty("event_bar_link_url")]
+        public Uri EventBarLinkUrl { get; set; }
+
+        [JsonProperty("event_bar_icon_image")]
+        public string EventBarIconImage { get; set; }
+        public string EventBarIconImageUrlStr
+        {
+            get
+            {
+                if (string.IsNullOrEmpty(EventBarIconImage))
+                    return null;
+                return $"{CMSCollection.CMSDomain}/assets/{EventBarIconImage}";
+            }
+        }
+
+        [JsonProperty("event_bar_close_button_image")]
+        public string EventBarCloseButtonImage { get; set; }
+        public string EventBarCloseButtonImageUrlStr
+        {
+            get
+            {
+                if (string.IsNullOrEmpty(EventBarCloseButtonImage))
+                    return null;
+                return $"{CMSCollection.CMSDomain}/assets/{EventBarCloseButtonImage}";
+            }
+        }
+
+        [JsonProperty("event_bar_icon_visible")]
+        public bool EventBarIconVisible { get; set; }
+
+        [JsonProperty("event_bar_close_button_visible")]
+        public bool EventBarCloseButtonVisible { get; set; }
+
+        [JsonProperty("dialog_text_color")]
+        public string DialogTextColor { get; set; }
+
+        [JsonProperty("dialog_background_color")]
+        public string DialogBackgroundColor { get; set; }
+
+        [JsonProperty("dialog_image")]
+        public string DialogImage { get; set; }
+        public string DialogImageUrlStr
+        {
+            get
+            {
+                if (string.IsNullOrEmpty(DialogImage))
+                    return null;
+                return $"{CMSCollection.CMSDomain}/assets/{DialogImage}";
+            }
+        }
+
+        [JsonProperty("dialog_close_icon_image")]
+        public string DialogCloseIconImage { get; set; }
+        public string DialogCloseIconImageUrlStr
+        {
+            get
+            {
+                if (string.IsNullOrEmpty(DialogCloseIconImage))
+                    return null;
+                return $"{CMSCollection.CMSDomain}/assets/{DialogCloseIconImage}";
+            }
+        }
+
+        [JsonProperty("dialog_close_button_visible")]
+        public bool DialogCloseButtonVisible { get; set; }
+
+        [JsonProperty("do_not_show_checkbox_checked_color")]
+        public string DoNotShowCheckboxCheckedColor { get; set; }
+
+        [JsonProperty("do_not_show_checkbox_unchecked_color")]
+        public string DoNotShowCheckboxUncheckedColor { get; set; }
+
+        [JsonProperty("do_not_show_visible")]
+        public bool DoNotShowVisible { get; set; }
+
+        [JsonProperty("event_visible")]
+        public bool EventVisible { get; set; }
+
+        [JsonProperty("platform")]
+        public string Platform { get; set; }
+
+        [JsonProperty("do_not_show_checkbox_text_color")]
+        public string DoNotShowCheckboxTextColor { get; set; }
+
+        [JsonProperty("evnet_bar_text_translations")]
+        public Translation[] EvnetBarTextTranslations { get; set; }
+
+        [JsonProperty("dialog_text_translations")]
+        public Translation[] DialogTextTranslations { get; set; }
+
+        [JsonProperty("dialog_button")]
+        public DialogButton[] DialogButton { get; set; }
+
+        [JsonProperty("do_not_show_translations")]
+        public Translation[] DoNotShowTranslations { get; set; }
+
+        [JsonProperty("do_not_show_dialog")]
+        public bool DoNotShowDialog { get; set; }
+
+        [JsonProperty("regions")]
+        public string[] Regions { get; set; }
+    }
+
+    public partial class DialogButton
+    {
+        [JsonProperty("id")]
+        public long Id { get; set; }
+
+        [JsonProperty("dialog_button_text_color")]
+        public string DialogButtonTextColor { get; set; }
+
+        [JsonProperty("dialog_button_background_color")]
+        public string DialogButtonBackgroundColor { get; set; }
+
+        [JsonProperty("dialog_button_url")]
+        public Uri DialogButtonUrl { get; set; }
+
+        [JsonProperty("dialog_button_border_color")]
+        public string DialogButtonBorderColor { get; set; }
+
+        [JsonProperty("dialog_button_sort")]
+        public long DialogButtonSort { get; set; }
+
+        [JsonProperty("dialog_button_translations")]
+        public Translation[] DialogButtonTranslations { get; set; }
+    }
+
+    public partial class Translation
+    {
+        [JsonProperty("id")]
+        public long Id { get; set; }
+
+        [JsonProperty("languages_code")]
+        public string LanguagesCode { get; set; }
+
+        [JsonProperty("content")]
+        public string Content { get; set; }
+    }
+}

+ 105 - 0
CMSCollection/Data/WindowsCardsResponse.cs

@@ -0,0 +1,105 @@
+using Newtonsoft.Json;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace KdanCommon.CMSCollection.Data
+{
+    public partial class WindowsCardsResponse
+    {
+        [JsonProperty("data")]
+        public WindowsCardSetting[] Data { get; set; }
+    }
+
+    public partial class WindowsCardSetting : ISetting
+    {
+        public string SettingName
+        {
+            get
+            {
+                return Id;
+            }
+        }
+
+        [JsonProperty("id")]
+        public string Id { get; set; }
+
+        [JsonProperty("status")]
+        public string Status { get; set; }
+
+        [JsonProperty("sort")]
+        public object Sort { get; set; }
+
+        [JsonProperty("user_created")]
+        public string UserCreated { get; set; }
+
+        [JsonProperty("date_created")]
+        public DateTimeOffset DateCreated { get; set; }
+
+        [JsonProperty("user_updated")]
+        public string UserUpdated { get; set; }
+
+        [JsonProperty("date_updated")]
+        public DateTimeOffset DateUpdated { get; set; }
+
+        [JsonProperty("Permission")]
+        public string Permission { get; set; }
+
+        [JsonProperty("Weight")]
+        public long Weight { get; set; }
+
+        [JsonProperty("appTypes")]
+        public string[] AppTypes { get; set; }
+
+        [JsonProperty("Start")]
+        public DateTimeOffset Start { get; set; }
+
+        [JsonProperty("End")]
+        public DateTimeOffset End { get; set; }
+
+        [JsonProperty("include_regions")]
+        public bool IncludeRegions { get; set; }
+
+        [JsonProperty("regions")]
+        public string[] Regions { get; set; }
+
+        [JsonProperty("Information")]
+        public Information[] Information { get; set; }
+    }
+
+    public partial class Information
+    {
+        [JsonProperty("id")]
+        public long Id { get; set; }
+
+        [JsonProperty("windows_cards_id")]
+        public string WindowsCardsId { get; set; }
+
+        [JsonProperty("languages_code")]
+        public string LanguagesCode { get; set; }
+
+        [JsonProperty("Title")]
+        public string Title { get; set; }
+
+        [JsonProperty("Description")]
+        public string Description { get; set; }
+
+        [JsonProperty("Link")]
+        public string Link { get; set; }
+
+        [JsonProperty("Image")]
+        public string Image { get; set; }
+
+        public string ImageUrlStr
+        {
+            get
+            {
+                if (string.IsNullOrEmpty(Image))
+                    return null;
+                return $"{CMSCollection.CMSDomain}/assets/{Image}";
+            }
+        }
+    }
+}

+ 92 - 0
CMSCollection/Data/WindowsEventbarResponse.cs

@@ -0,0 +1,92 @@
+using KdanCommon.Helpers;
+using Newtonsoft.Json;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Windows.System;
+using Windows.UI;
+using Windows.UI.Xaml.Media.Animation;
+
+namespace KdanCommon.CMSCollection.Data
+{
+    public partial class WindowsEventbarResponse
+    {
+        [JsonProperty("data")]
+        public WindowsEventbarSetting[] Data { get; set; }
+    }
+
+    public partial class WindowsEventbarSetting : ISetting
+    {
+        [JsonConstructor]
+        private WindowsEventbarSetting(string id, string app_type, DateTime start, DateTime end, string event_icon, string close_icon, string text_color, string background_color, EventbarInformation[] event_information, bool is_close_button_show)
+        {
+            if (close_icon != null)
+                CloseIcon = new Uri($"{CMSCollection.CMSDomain}/assets/{close_icon}");
+            if (event_icon != null)
+                EventIcon = new Uri($"{CMSCollection.CMSDomain}/assets/{event_icon}");
+            if (text_color != null)
+                TextColor = text_color.ToColor();
+            if (background_color != null)
+                BackgroundColor = background_color.ToColor();
+            Id = id;
+            Start = start;
+            End = end;
+            if (event_information != null)
+            {
+                EventbarInformation = event_information.FirstOrDefault();
+            }
+            IsCloseButtonShow = is_close_button_show;
+        }
+        public string SettingName
+        {
+            get
+            {
+                return Id;
+            }
+        }
+
+        public string Id { get; }
+
+        public DateTime Start { get; }
+
+        public DateTime End { get; }
+
+        public Color TextColor
+        {
+            get;
+        }
+        public Color BackgroundColor
+        {
+            get;
+        }
+
+        public EventbarInformation EventbarInformation { get; }
+
+        public Uri EventIcon
+        {
+            get;
+        }
+
+        public Uri CloseIcon
+        {
+            get;
+        }
+        public bool IsCloseButtonShow
+        {
+            get;
+        }
+    }
+
+    public partial class EventbarInformation
+    {
+
+        [JsonProperty("event_title")]
+        public string EventTitle { get; set; }
+
+        [JsonProperty("event_link")]
+        public string EventLink { get; set; }
+    }
+
+}

+ 19 - 0
GoogleCloud/Data/Lang.cs

@@ -0,0 +1,19 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace KdanCommon.GoogleCloud.Data
+{
+    public class Lang
+    {
+        public string ISOCode { get; private set; }   // two letter ISO 639 code, allow exceptions :zh-CN, zh-TW
+        public string EnglishName { get; private set; }
+        public Lang(string name, string code)
+        {
+            ISOCode = code;
+            EnglishName = name;
+        }
+    }
+}

+ 71 - 0
GoogleCloud/Data/Vision/ImgesRequest.cs

@@ -0,0 +1,71 @@
+using Newtonsoft.Json;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace KdanCommon.GoogleCloud.Data.Vision
+{
+    public partial class ImgesRequest
+    {
+        [JsonProperty("requests")]
+        public ImageRequest[] Requests { get; set; }
+
+        public ImgesRequest(List<string> inputContents, string[] languageHints = null)
+        {
+            Requests = new ImageRequest[inputContents.Count];
+            for (int i = 0; i < inputContents.Count; i++)
+            {
+                Requests[i] = new ImageRequest()
+                {
+                    Image = new Image()
+                    {
+                        Content = inputContents[i]
+                    },
+                    Features = new Feature[]
+                    {
+                        new Feature()
+                        {
+                            Type = "TEXT_DETECTION"
+                        }
+                    },
+                    ImageContext = new ImageContext()
+                    {
+                        LanguageHints = languageHints
+                    }
+                };
+            }
+        }
+    }
+
+    public partial class ImageRequest
+    {
+        [JsonProperty("image")]
+        public Image Image { get; set; }
+
+        [JsonProperty("features")]
+        public Feature[] Features { get; set; }
+
+        [JsonProperty("imageContext")]
+        public ImageContext ImageContext { get; set; }
+    }
+
+    public partial class Feature
+    {
+        [JsonProperty("type")]
+        public string Type { get; set; }
+    }
+
+    public partial class Image
+    {
+        [JsonProperty("content")]
+        public string Content { get; set; }
+    }
+
+    public partial class ImageContext
+    {
+        [JsonProperty("languageHints")]
+        public string[] LanguageHints { get; set; }
+    }
+}

+ 147 - 0
GoogleCloud/Data/Vision/ImgesResponse.cs

@@ -0,0 +1,147 @@
+using Newtonsoft.Json;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace KdanCommon.GoogleCloud.Data.Vision
+{
+    public partial class ImagesResponse
+    {
+        [JsonProperty("responses")]
+        public ImageResponse[] Responses { get; set; }
+    }
+
+    public partial class ImageResponse
+    {
+        [JsonProperty("textAnnotations")]
+        public TextAnnotation[] TextAnnotations { get; set; }
+
+        [JsonProperty("fullTextAnnotation")]
+        public FullTextAnnotation FullTextAnnotation { get; set; }
+    }
+
+    public partial class FullTextAnnotation
+    {
+        [JsonProperty("pages")]
+        public Page[] Pages { get; set; }
+
+        [JsonProperty("text")]
+        public string Text { get; set; }
+    }
+
+    public partial class Page
+    {
+        [JsonProperty("property")]
+        public WordProperty Property { get; set; }
+
+        [JsonProperty("width")]
+        public long Width { get; set; }
+
+        [JsonProperty("height")]
+        public long Height { get; set; }
+
+        [JsonProperty("blocks")]
+        public Block[] Blocks { get; set; }
+    }
+
+    public partial class Block
+    {
+        [JsonProperty("boundingBox")]
+        public Bounding BoundingBox { get; set; }
+
+        [JsonProperty("paragraphs")]
+        public Paragraph[] Paragraphs { get; set; }
+
+        [JsonProperty("blockType")]
+        public string BlockType { get; set; }
+    }
+
+    public partial class Bounding
+    {
+        [JsonProperty("vertices")]
+        public Vertex[] Vertices { get; set; }
+    }
+
+    public partial class Vertex
+    {
+        [JsonProperty("x")]
+        public long X { get; set; }
+
+        [JsonProperty("y")]
+        public long Y { get; set; }
+    }
+
+    public partial class Paragraph
+    {
+        [JsonProperty("boundingBox")]
+        public Bounding BoundingBox { get; set; }
+
+        [JsonProperty("words")]
+        public Word[] Words { get; set; }
+    }
+
+    public partial class Word
+    {
+        [JsonProperty("boundingBox")]
+        public Bounding BoundingBox { get; set; }
+
+        [JsonProperty("symbols")]
+        public Symbol[] Symbols { get; set; }
+
+        [JsonProperty("property", NullValueHandling = NullValueHandling.Ignore)]
+        public WordProperty Property { get; set; }
+    }
+
+    public partial class WordProperty
+    {
+        [JsonProperty("detectedLanguages")]
+        public DetectedLanguage[] DetectedLanguages { get; set; }
+    }
+
+    public partial class DetectedLanguage
+    {
+        [JsonProperty("languageCode")]
+        public string LanguageCode { get; set; }
+
+        [JsonProperty("confidence")]
+        public double Confidence { get; set; }
+    }
+
+    public partial class Symbol
+    {
+        [JsonProperty("boundingBox")]
+        public Bounding BoundingBox { get; set; }
+
+        [JsonProperty("text")]
+        public string Text { get; set; }
+
+        [JsonProperty("property", NullValueHandling = NullValueHandling.Ignore)]
+        public SymbolProperty Property { get; set; }
+    }
+
+    public partial class SymbolProperty
+    {
+        [JsonProperty("detectedBreak")]
+        public DetectedBreak DetectedBreak { get; set; }
+    }
+
+    public partial class DetectedBreak
+    {
+        [JsonProperty("type")]
+        public string Type { get; set; }
+    }
+
+    public partial class TextAnnotation
+    {
+        [JsonProperty("locale", NullValueHandling = NullValueHandling.Ignore)]
+        public string Locale { get; set; }
+
+        [JsonProperty("description")]
+        public string Description { get; set; }
+
+        [JsonProperty("boundingPoly")]
+        public Bounding BoundingPoly { get; set; }
+    }
+}

+ 251 - 0
GoogleCloud/GoogleCloud.cs

@@ -0,0 +1,251 @@
+using KdanCommon.GoogleCloud.Data;
+using KdanCommon.GoogleCloud.Data.Vision;
+using KdanCommon.Helpers;
+using Newtonsoft.Json;
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Linq;
+using System.Net.Http;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using Windows.Globalization;
+using Windows.Storage;
+using Windows.System;
+
+namespace KdanCommon.GoogleCloud
+{
+    public class GoogleCloud
+    {
+        public static string VisionDomain = "https://vision.googleapis.com";
+        private Uri _visionImagesUri = null;
+
+        private HttpClient _httpClient = null;
+        private string _key = null;
+
+        public GoogleCloud(string key)
+        {
+            _key = key;
+            _httpClient = new HttpClient();
+            _visionImagesUri = new Uri($"{VisionDomain}/v1/images:annotate?key={_key}");
+        }
+
+        ~GoogleCloud()
+        {
+            _httpClient.Dispose();
+        }
+
+        // Document : https://cloud.google.com/vision/docs/reference/rest/v1/images/annotate
+        // Image files sent to the Vision API should not exceed 20MB.
+        // Files exceeding 20MB generate an error.
+        // The Vision API does not resize files of this size. Reducing your file size can significantly improve throughput;
+        // however, be careful not to reduce image quality in the process.
+        // Note that the Vision API imposes a 10MB JSON request size limit;
+        // larger files should be hosted on Cloud Storage or on the web, rather than being passed as base64-encoded content in the JSON itself.
+        public async Task<ImagesResponse> GetVisionImages(List<StorageFile> images, string[] langHints = null)
+        {
+            ImagesResponse response = null;
+            var base64Contents = new List<string>();
+            foreach (var image in images)
+            {
+                var base64Content = await Base64Helper.StorageFileToBase64(image);
+                base64Contents.Add(base64Content);
+            }
+            var imagesRequest = new ImgesRequest(base64Contents, langHints);
+            string requestJsonStr = JsonConvert.SerializeObject(imagesRequest);
+            var content = new StringContent(requestJsonStr, System.Text.Encoding.UTF8, "application/json");
+            var result = await _httpClient.PostAsync(_visionImagesUri, content);
+            if (result.StatusCode == System.Net.HttpStatusCode.OK)
+            {
+                var jsonString = await result.Content.ReadAsStringAsync();
+                response = JsonTool.DeserializeJSON<ImagesResponse>(jsonString);
+            }
+            return response;
+        }
+
+        // source : https://cloud.google.com/vision/docs/languages
+        public static List<Lang> GetVisionSupportedLanguages()
+        {
+            var langs = new List<Lang>();
+            langs.Add(new Lang("Afrikaans", "af"));
+            langs.Add(new Lang("Albanian", "sq"));
+            langs.Add(new Lang("Arabic", "ar"));
+            langs.Add(new Lang("Armenian", "hy"));
+            langs.Add(new Lang("Belarusian", "be"));
+            langs.Add(new Lang("Bengali", "bn"));
+            langs.Add(new Lang("Bulgarian", "bg"));
+            langs.Add(new Lang("Catalan", "ca"));
+            langs.Add(new Lang("Chinese-Simplified", "zh"));
+            langs.Add(new Lang("Chinese-Traditional", "zh-Hant"));
+            langs.Add(new Lang("Croatian", "hr"));
+            langs.Add(new Lang("Czech", "cs"));
+            langs.Add(new Lang("Danish", "da"));
+            langs.Add(new Lang("Dutch", "nl"));
+            langs.Add(new Lang("English", "en"));
+            langs.Add(new Lang("Estonian", "et"));
+            langs.Add(new Lang("Filipino", "tl"));
+            langs.Add(new Lang("Finnish", "fi"));
+            langs.Add(new Lang("French", "fr"));
+            langs.Add(new Lang("German", "de"));
+            langs.Add(new Lang("Greek", "el"));
+            langs.Add(new Lang("Gujarati", "gu"));
+            langs.Add(new Lang("Hebrew", "iw"));
+            langs.Add(new Lang("Hindi", "hi"));
+            langs.Add(new Lang("Hungarian", "hu"));
+            langs.Add(new Lang("Icelandic", "is"));
+            langs.Add(new Lang("Indonesian", "id"));
+            langs.Add(new Lang("Italian", "it"));
+            langs.Add(new Lang("Japanese", "ja"));
+            langs.Add(new Lang("Kannada", "kn"));
+            langs.Add(new Lang("Khmer", "km"));
+            langs.Add(new Lang("Korean", "ko"));
+            langs.Add(new Lang("Lao", "lo"));
+            langs.Add(new Lang("Latvian", "lv"));
+            langs.Add(new Lang("Lithuanian", "lt"));
+            langs.Add(new Lang("Macedonian", "mk"));
+            langs.Add(new Lang("Malay", "ms"));
+            langs.Add(new Lang("Malayalam", "ml"));
+            langs.Add(new Lang("Marathi", "mr"));
+            langs.Add(new Lang("Nepali", "ne"));
+            langs.Add(new Lang("Norwegian", "no"));
+            langs.Add(new Lang("Persian", "fa"));
+            langs.Add(new Lang("Polish", "pl"));
+            langs.Add(new Lang("Portuguese", "pt"));
+            langs.Add(new Lang("Punjabi", "pa"));
+            langs.Add(new Lang("Romanian", "ro"));
+            langs.Add(new Lang("Russian", "ru"));
+            langs.Add(new Lang("Serbian", "sr"));
+            langs.Add(new Lang("Slovak", "sk"));
+            langs.Add(new Lang("Slovenian", "sl"));
+            langs.Add(new Lang("Spanish", "es"));
+            langs.Add(new Lang("Swedish", "sv"));
+            langs.Add(new Lang("Tamil", "ta"));
+            langs.Add(new Lang("Telugu", "te"));
+            langs.Add(new Lang("Thai", "th"));
+            langs.Add(new Lang("Turkish", "tr"));
+            langs.Add(new Lang("Ukrainian", "uk"));
+            langs.Add(new Lang("Vietnamese", "vi"));
+            langs.Add(new Lang("Yiddish", "yi"));
+            return langs;
+        }
+
+        // source : https://cloud.google.com/translate/docs/languages
+        public static List<Lang> GetTranslateSupportedLanguages()
+        {
+            var langs = new List<Lang>();
+            langs.Add(new Lang("Afrikaans", "af"));
+            langs.Add(new Lang("Albanian", "sq"));
+            langs.Add(new Lang("Amharic", "am"));
+            langs.Add(new Lang("Arabic", "ar"));
+            langs.Add(new Lang("Armenian", "hy"));
+            langs.Add(new Lang("Azerbaijani", "az"));
+            langs.Add(new Lang("Basque", "eu"));
+            langs.Add(new Lang("Belarusian", "be"));
+            langs.Add(new Lang("Bengali", "bn"));
+            langs.Add(new Lang("Bosnian", "bs"));
+            langs.Add(new Lang("Bulgarian", "bg"));
+            langs.Add(new Lang("Catalan", "ca"));
+            //langs.Add(new Lang("Cebuano", "ceb"));  // 文件說有支援但其實不能用
+            langs.Add(new Lang("Chinese-Simplified", "zh-CN"));
+            langs.Add(new Lang("Chinese-Traditional", "zh-TW"));
+            langs.Add(new Lang("Corsican", "co"));
+            langs.Add(new Lang("Croatian", "hr"));
+            langs.Add(new Lang("Czech", "cs"));
+            langs.Add(new Lang("Danish", "da"));
+            langs.Add(new Lang("Dutch", "nl"));
+            langs.Add(new Lang("English", "en"));
+            langs.Add(new Lang("Esperanto", "eo"));
+            langs.Add(new Lang("Estonian", "et"));
+            langs.Add(new Lang("Finnish", "fi"));
+            langs.Add(new Lang("French", "fr"));
+            langs.Add(new Lang("Frisian", "fy"));
+            langs.Add(new Lang("Galician", "gl"));
+            langs.Add(new Lang("Georgian", "ka"));
+            langs.Add(new Lang("German", "de"));
+            langs.Add(new Lang("Greek", "el"));
+            langs.Add(new Lang("Guarani", "gn"));
+            langs.Add(new Lang("Gujarati", "gu"));
+            langs.Add(new Lang("Haitian Creole", "ht"));
+            langs.Add(new Lang("Hausa", "ha"));
+            //langs.Add(new Lang("Hawaiian", "haw"));   // 文件說有支援但其實不能用
+            langs.Add(new Lang("Hebrew", "he"));
+            langs.Add(new Lang("Hindi", "hi"));
+            //langs.Add(new Lang("Hmong", "hmn"));   // 文件說有支援但其實不能用
+            langs.Add(new Lang("Hungarian", "hu"));
+            langs.Add(new Lang("Icelandic", "is"));
+            langs.Add(new Lang("Igbo", "ig"));
+            langs.Add(new Lang("Indonesian", "id"));
+            langs.Add(new Lang("Irish", "ga"));
+            langs.Add(new Lang("Italian", "it"));
+            langs.Add(new Lang("Japanese", "ja"));
+            langs.Add(new Lang("Javanese", "jv"));
+            langs.Add(new Lang("Kannada", "kn"));
+            langs.Add(new Lang("Kazakh", "kk"));
+            langs.Add(new Lang("Khmer", "km"));
+            langs.Add(new Lang("Kinyarwanda", "rw"));
+            langs.Add(new Lang("Korean", "ko"));
+            langs.Add(new Lang("Kurdish", "ku"));
+            langs.Add(new Lang("Kyrgyz", "ky"));
+            langs.Add(new Lang("Lao", "lo"));
+            langs.Add(new Lang("Latin", "la"));
+            langs.Add(new Lang("Latvian", "lv"));
+            langs.Add(new Lang("Lithuanian", "lt"));
+            langs.Add(new Lang("Luxembourgish", "lb"));
+            langs.Add(new Lang("Macedonian", "mk"));
+            langs.Add(new Lang("Malagasy", "mg"));
+            langs.Add(new Lang("Malay", "ms"));
+            langs.Add(new Lang("Malayalam", "ml"));
+            langs.Add(new Lang("Maltese", "mt"));
+            langs.Add(new Lang("Maori", "mi"));
+            langs.Add(new Lang("Marathi", "mr"));
+            langs.Add(new Lang("Mongolian", "mn"));
+            langs.Add(new Lang("Myanmar (Burmese)", "my"));
+            langs.Add(new Lang("Nepali", "ne"));
+            langs.Add(new Lang("Norwegian", "no"));
+            langs.Add(new Lang("Nyanja (Chichewa)", "ny"));
+            langs.Add(new Lang("Odia (Oriya)", "or"));
+            langs.Add(new Lang("Pashto", "ps"));
+            langs.Add(new Lang("Persian", "fa"));
+            langs.Add(new Lang("Polish", "pl"));
+            langs.Add(new Lang("Portuguese", "pt"));
+            langs.Add(new Lang("Punjabi", "pa"));
+            langs.Add(new Lang("Romanian", "ro"));
+            langs.Add(new Lang("Russian", "ru"));
+            langs.Add(new Lang("Samoan", "sm"));
+            langs.Add(new Lang("Sanskrit", "sa"));
+            langs.Add(new Lang("Scots Gaelic", "gd"));
+            langs.Add(new Lang("Serbian", "sr"));
+            langs.Add(new Lang("Sesotho", "st"));
+            langs.Add(new Lang("Shona", "sn"));
+            langs.Add(new Lang("Sindhi", "sd"));
+            langs.Add(new Lang("Sinhala (Sinhalese)	", "si"));
+            langs.Add(new Lang("Slovak", "sk"));
+            langs.Add(new Lang("Slovenian", "sl"));
+            langs.Add(new Lang("Somali", "so"));
+            langs.Add(new Lang("Spanish", "es"));
+            langs.Add(new Lang("Sundanese", "su"));
+            langs.Add(new Lang("Swahili", "sw"));
+            langs.Add(new Lang("Swedish", "sv"));
+            langs.Add(new Lang("Tagalog (Filipino)", "tl"));
+            langs.Add(new Lang("Tajik", "tg"));
+            langs.Add(new Lang("Tamil", "ta"));
+            langs.Add(new Lang("Tatar", "tt"));
+            langs.Add(new Lang("Telugu", "te"));
+            langs.Add(new Lang("Thai", "th"));
+            langs.Add(new Lang("Turkish", "tr"));
+            langs.Add(new Lang("Turkmen", "tk"));
+            langs.Add(new Lang("Ukrainian", "uk"));
+            langs.Add(new Lang("Urdu", "ur"));
+            langs.Add(new Lang("Uyghur", "ug"));
+            langs.Add(new Lang("Uzbek", "uz"));
+            langs.Add(new Lang("Vietnamese", "vi"));
+            langs.Add(new Lang("Welsh", "cy"));
+            langs.Add(new Lang("Xhosa", "xh"));
+            langs.Add(new Lang("Yiddish", "yi"));
+            langs.Add(new Lang("Yoruba", "yo"));
+            langs.Add(new Lang("Zulu", "zu"));
+            return langs;
+        }
+    }
+}

+ 33 - 0
Helpers/Base64Helper.cs

@@ -0,0 +1,33 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Windows.Storage.Streams;
+using Windows.Storage;
+
+namespace KdanCommon.Helpers
+{
+    public static class Base64Helper
+    {
+        // https://stackoverflow.com/questions/18553691/metro-getting-the-base64-string-of-a-storagefile/18555063
+        public static async Task<string> StorageFileToBase64(StorageFile file)
+        {
+            string base64String = "";
+            if (file != null)
+            {
+                using (IRandomAccessStream fileStream = await file.OpenAsync(FileAccessMode.Read))
+                {
+                    using(var reader = new DataReader(fileStream.GetInputStreamAt(0)))
+                    {
+                        await reader.LoadAsync((uint)fileStream.Size);
+                        byte[] byteArray = new byte[fileStream.Size];
+                        reader.ReadBytes(byteArray);
+                        base64String = Convert.ToBase64String(byteArray);
+                    }
+                }
+            }
+            return base64String;
+        }
+    }
+}

+ 38 - 0
Helpers/ColorCodeExtension.cs

@@ -0,0 +1,38 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Windows.UI;
+
+namespace KdanCommon.Helpers
+{
+    public static class ColorCodeExtension
+    {
+        public static Color ToColor(this string hex)
+        {
+            byte a = 255;
+            byte r = 0;
+            byte g = 0;
+            byte b = 0;
+            if (hex.StartsWith("#"))
+            {
+                hex = hex.Replace("#", string.Empty);
+                if (hex.Length == 8)
+                {
+                    a = (byte)(Convert.ToUInt32(hex.Substring(0, 2), 16));
+                    r = (byte)(Convert.ToUInt32(hex.Substring(2, 2), 16));
+                    g = (byte)(Convert.ToUInt32(hex.Substring(4, 2), 16));
+                    b = (byte)(Convert.ToUInt32(hex.Substring(6, 2), 16));
+                }
+                else if (hex.Length == 6)
+                {
+                    r = (byte)(Convert.ToUInt32(hex.Substring(0, 2), 16));
+                    g = (byte)(Convert.ToUInt32(hex.Substring(2, 2), 16));
+                    b = (byte)(Convert.ToUInt32(hex.Substring(4, 2), 16));
+                }
+            }
+            return Color.FromArgb(a, r, g, b);
+        }
+    }
+}

+ 53 - 0
Helpers/DynamicJsonHelper.cs

@@ -0,0 +1,53 @@
+using Newtonsoft.Json.Linq;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Xml.Linq;
+
+namespace KdanCommon.Helpers
+{
+    public static class DynamicJsonHelper
+    {
+        public static dynamic ToDynamicJson(string jsonString)
+        {
+            try
+            {
+                JObject jo = JObject.Parse(jsonString);
+                return jo as dynamic;
+            }
+            catch
+            {
+                return null;
+            }
+        }
+
+        public static T GetValueSafety<T>(dynamic value, T defaultValue)
+        {
+            try
+            {
+                var val = (T)value;
+                if (val == null)
+                    return defaultValue;
+                else
+                    return (T)value;
+            }
+            catch
+            {
+                return defaultValue;
+            }
+        }
+
+        public static List<dynamic> GetArraySafety(dynamic value)
+        {
+            try
+            {
+                if (value is JArray)
+                    return ((JArray)value).Cast<dynamic>().ToList();
+            }
+            catch { }
+            return null;
+        }
+    }
+}

+ 78 - 0
Helpers/HttpClientHelper.cs

@@ -0,0 +1,78 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Windows.Web.Http;
+
+namespace KdanCommon.Helpers
+{
+    public static class HttpClientHelper
+    {
+        public static KeyValuePair<string, string> CreateQuery(string param, string value)
+        {
+            return new KeyValuePair<string, string>(param, value);
+        }
+        public static async Task<HttpResponseMessage> GetRequest(this HttpClient httpClient, Uri requestUri, List<KeyValuePair<string, string>> querys)
+        {
+            requestUri = new Uri(requestUri.AbsoluteUri + querys.HttpQueryString());
+
+            var result = new HttpResponseMessage();
+            try
+            {
+                result = await httpClient.GetAsync(requestUri);
+
+            }
+            catch (Exception ex)
+            {
+                var ii = ex.Data;
+                result.StatusCode = HttpStatusCode.RequestTimeout;
+
+            }
+            return result;
+        }
+
+        public static async Task<HttpResponseMessage> PostRequest(this HttpClient httpClient, Uri requestUri, List<KeyValuePair<string, string>> querys)
+        {
+            var content = new HttpFormUrlEncodedContent(querys);
+            var result = new HttpResponseMessage();
+            try
+            {
+                result = await httpClient.PostAsync(requestUri, content);
+            }
+            catch (Exception ex)
+            {
+                var ii = ex.Data;
+                result.StatusCode = HttpStatusCode.RequestTimeout;
+            }
+            return result;
+        }
+
+        public static async Task<HttpResponseMessage> PutRequest(this HttpClient httpClient, Uri requestUri, List<KeyValuePair<string, string>> querys)
+        {
+            var content = new HttpFormUrlEncodedContent(querys);
+            var result = await httpClient.PutAsync(requestUri, content);
+            return result;
+        }
+
+        public static async Task<HttpResponseMessage> DeleteRequest(this HttpClient httpClient, Uri requestUri, List<KeyValuePair<string, string>> querys)
+        {
+            //  requestUri = new Uri(requestUri.AbsoluteUri + HttpQueryString(querys));
+            HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Delete, requestUri);
+
+            request.Content = new HttpFormUrlEncodedContent(querys);
+            var result = await httpClient.SendRequestAsync(request);
+            return result;
+        }
+        public static string HttpQueryString(this List<KeyValuePair<string, string>> querys)
+        {
+            var queryString = "?";
+            foreach (var q in querys)
+            {
+                queryString += q.Key + "=" + q.Value + "&";
+            }
+            queryString = queryString.Remove(queryString.Length - 1);
+            return queryString;
+        }
+    }
+}

+ 30 - 0
Helpers/JsonTool.cs

@@ -0,0 +1,30 @@
+using Newtonsoft.Json;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace KdanCommon.Helpers
+{
+    public static class JsonTool
+    {
+        /// <summary>
+        /// Deserialize json to target class
+        /// when jsonStr deserializes failed, put default value in there
+        /// </summary>
+        public static T DeserializeJSON<T>(string jsonStr)
+        {
+            T response = default(T);
+            response = JsonConvert.DeserializeObject<T>(jsonStr, new JsonSerializerSettings()
+            {
+                Error = (sender, errorEventArgs) =>
+                {
+                    System.Diagnostics.Debug.WriteLine(String.Format("error happen in {0} : {1}", errorEventArgs.CurrentObject.GetType().Name, errorEventArgs.ErrorContext.Error.Message));
+                    errorEventArgs.ErrorContext.Handled = true;
+                }
+            });
+            return response;
+        }
+    }
+}

+ 165 - 0
KdanCommon.csproj

@@ -0,0 +1,165 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
+  <PropertyGroup>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+    <ProjectGuid>{7B8ABCA1-8DA7-42AE-8AEA-C92053014AC0}</ProjectGuid>
+    <OutputType>Library</OutputType>
+    <AppDesignerFolder>Properties</AppDesignerFolder>
+    <RootNamespace>KdanCommon</RootNamespace>
+    <AssemblyName>KdanCommon</AssemblyName>
+    <DefaultLanguage>zh-TW</DefaultLanguage>
+    <TargetPlatformIdentifier>UAP</TargetPlatformIdentifier>
+    <TargetPlatformVersion Condition=" '$(TargetPlatformVersion)' == '' ">10.0.18362.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>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+    <PlatformTarget>AnyCPU</PlatformTarget>
+    <DebugSymbols>true</DebugSymbols>
+    <DebugType>full</DebugType>
+    <Optimize>false</Optimize>
+    <OutputPath>bin\Debug\</OutputPath>
+    <DefineConstants>DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+    <PlatformTarget>AnyCPU</PlatformTarget>
+    <DebugType>pdbonly</DebugType>
+    <Optimize>true</Optimize>
+    <OutputPath>bin\Release\</OutputPath>
+    <DefineConstants>TRACE;NETFX_CORE;WINDOWS_UWP</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x86'">
+    <PlatformTarget>x86</PlatformTarget>
+    <DebugSymbols>true</DebugSymbols>
+    <OutputPath>bin\x86\Debug\</OutputPath>
+    <DefineConstants>DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP</DefineConstants>
+    <NoWarn>;2008</NoWarn>
+    <DebugType>full</DebugType>
+    <UseVSHostingProcess>false</UseVSHostingProcess>
+    <ErrorReport>prompt</ErrorReport>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x86'">
+    <PlatformTarget>x86</PlatformTarget>
+    <OutputPath>bin\x86\Release\</OutputPath>
+    <DefineConstants>TRACE;NETFX_CORE;WINDOWS_UWP</DefineConstants>
+    <Optimize>true</Optimize>
+    <NoWarn>;2008</NoWarn>
+    <DebugType>pdbonly</DebugType>
+    <UseVSHostingProcess>false</UseVSHostingProcess>
+    <ErrorReport>prompt</ErrorReport>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|ARM'">
+    <PlatformTarget>ARM</PlatformTarget>
+    <DebugSymbols>true</DebugSymbols>
+    <OutputPath>bin\ARM\Debug\</OutputPath>
+    <DefineConstants>DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP</DefineConstants>
+    <NoWarn>;2008</NoWarn>
+    <DebugType>full</DebugType>
+    <UseVSHostingProcess>false</UseVSHostingProcess>
+    <ErrorReport>prompt</ErrorReport>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|ARM'">
+    <PlatformTarget>ARM</PlatformTarget>
+    <OutputPath>bin\ARM\Release\</OutputPath>
+    <DefineConstants>TRACE;NETFX_CORE;WINDOWS_UWP</DefineConstants>
+    <Optimize>true</Optimize>
+    <NoWarn>;2008</NoWarn>
+    <DebugType>pdbonly</DebugType>
+    <UseVSHostingProcess>false</UseVSHostingProcess>
+    <ErrorReport>prompt</ErrorReport>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|ARM64'">
+    <PlatformTarget>ARM64</PlatformTarget>
+    <DebugSymbols>true</DebugSymbols>
+    <OutputPath>bin\ARM64\Debug\</OutputPath>
+    <DefineConstants>DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP</DefineConstants>
+    <NoWarn>;2008</NoWarn>
+    <DebugType>full</DebugType>
+    <UseVSHostingProcess>false</UseVSHostingProcess>
+    <ErrorReport>prompt</ErrorReport>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|ARM64'">
+    <PlatformTarget>ARM64</PlatformTarget>
+    <OutputPath>bin\ARM64\Release\</OutputPath>
+    <DefineConstants>TRACE;NETFX_CORE;WINDOWS_UWP</DefineConstants>
+    <Optimize>true</Optimize>
+    <NoWarn>;2008</NoWarn>
+    <DebugType>pdbonly</DebugType>
+    <UseVSHostingProcess>false</UseVSHostingProcess>
+    <ErrorReport>prompt</ErrorReport>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
+    <PlatformTarget>x64</PlatformTarget>
+    <DebugSymbols>true</DebugSymbols>
+    <OutputPath>bin\x64\Debug\</OutputPath>
+    <DefineConstants>DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP</DefineConstants>
+    <NoWarn>;2008</NoWarn>
+    <DebugType>full</DebugType>
+    <UseVSHostingProcess>false</UseVSHostingProcess>
+    <ErrorReport>prompt</ErrorReport>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
+    <PlatformTarget>x64</PlatformTarget>
+    <OutputPath>bin\x64\Release\</OutputPath>
+    <DefineConstants>TRACE;NETFX_CORE;WINDOWS_UWP</DefineConstants>
+    <Optimize>true</Optimize>
+    <NoWarn>;2008</NoWarn>
+    <DebugType>pdbonly</DebugType>
+    <UseVSHostingProcess>false</UseVSHostingProcess>
+    <ErrorReport>prompt</ErrorReport>
+  </PropertyGroup>
+  <PropertyGroup>
+    <RestoreProjectStyle>PackageReference</RestoreProjectStyle>
+  </PropertyGroup>
+  <ItemGroup>
+    <Compile Include="CMSCollection\CMSCollection.cs" />
+    <Compile Include="CMSCollection\Data\ISetting.cs" />
+    <Compile Include="CMSCollection\Data\JsonSetting.cs" />
+    <Compile Include="CMSCollection\Data\ViewerEventBarSettingResponse.cs" />
+    <Compile Include="CMSCollection\Data\WindowsCardsResponse.cs" />
+    <Compile Include="CMSCollection\Data\WindowsEventbarResponse.cs" />
+    <Compile Include="GoogleCloud\Data\Lang.cs" />
+    <Compile Include="GoogleCloud\Data\Vision\ImgesRequest.cs" />
+    <Compile Include="GoogleCloud\Data\Vision\ImgesResponse.cs" />
+    <Compile Include="GoogleCloud\GoogleCloud.cs" />
+    <Compile Include="Helpers\Base64Helper.cs" />
+    <Compile Include="Helpers\ColorCodeExtension.cs" />
+    <Compile Include="Helpers\DynamicJsonHelper.cs" />
+    <Compile Include="Helpers\HttpClientHelper.cs" />
+    <Compile Include="Helpers\JsonTool.cs" />
+    <Compile Include="Log\MetroLogService.cs" />
+    <Compile Include="Properties\AssemblyInfo.cs" />
+    <EmbeddedResource Include="Properties\KdanCommon.rd.xml" />
+  </ItemGroup>
+  <ItemGroup>
+    <PackageReference Include="MetroLog">
+      <Version>1.0.1</Version>
+    </PackageReference>
+    <PackageReference Include="Microsoft.NETCore.UniversalWindowsPlatform">
+      <Version>6.2.13</Version>
+    </PackageReference>
+    <PackageReference Include="Newtonsoft.Json">
+      <Version>13.0.1</Version>
+    </PackageReference>
+  </ItemGroup>
+  <ItemGroup />
+  <PropertyGroup Condition=" '$(VisualStudioVersion)' == '' or '$(VisualStudioVersion)' &lt; '14.0' ">
+    <VisualStudioVersion>14.0</VisualStudioVersion>
+  </PropertyGroup>
+  <Import Project="$(MSBuildExtensionsPath)\Microsoft\WindowsXaml\v$(VisualStudioVersion)\Microsoft.Windows.UI.Xaml.CSharp.targets" />
+  <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 
+       Other similar extension points exist, see Microsoft.Common.targets.
+  <Target Name="BeforeBuild">
+  </Target>
+  <Target Name="AfterBuild">
+  </Target>
+  -->
+</Project>

+ 131 - 0
Log/MetroLogService.cs

@@ -0,0 +1,131 @@
+using MetroLog;
+using MetroLog.Layouts;
+using MetroLog.Targets;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Windows.Storage;
+using Windows.System;
+
+namespace KdanCommon.Log
+{
+    public sealed class MetroLogService
+    {
+        private static MetroLogService _instance = new MetroLogService();
+        public static MetroLogService Instance
+        {
+            get
+            {
+                return _instance;
+            }
+        }
+
+        public LogLevel MinLevel = LogLevel.Trace;
+        public LogLevel MaxLevel = LogLevel.Fatal;
+        private ILogManager currentLogManager = null;
+        public bool IsWriteLog = true;
+
+        private MetroLogService()
+        {
+
+        }
+
+        public void Init(string appName, bool isWriteLog = true)
+        {
+            IsWriteLog = isWriteLog;
+            var customLogLayout = new CustomLogLayout(appName);
+            var loggingConfiguration = new LoggingConfiguration { IsEnabled = true };
+            loggingConfiguration.AddTarget(MinLevel, MaxLevel, new StreamingFileTarget(customLogLayout));
+            currentLogManager = LogManagerFactory.CreateLogManager(loggingConfiguration);
+        }
+
+        private ILogger GetCurrentLogger<T>()
+        {
+            return currentLogManager.GetLogger<T>();
+        }
+
+        public void WriteTraceLog<T>(string msg, Exception ex = null)
+        {
+            System.Diagnostics.Debug.Write(msg + " " + ex);
+            if (!IsWriteLog)
+                return;
+
+            var log = GetCurrentLogger<T>();
+            log.Trace(msg, ex);
+        }
+
+        public void WriteDebugLog<T>(string msg, Exception ex = null)
+        {
+            System.Diagnostics.Debug.Write(msg + " " + ex);
+            if (!IsWriteLog)
+                return;
+
+            var log = GetCurrentLogger<T>();
+            log.Debug(msg, ex);
+        }
+
+
+        public void WriteInfoLog<T>(string msg, Exception ex = null)
+        {
+            System.Diagnostics.Debug.Write(msg + " " + ex);
+            if (!IsWriteLog)
+                return;
+
+            var log = GetCurrentLogger<T>();
+            log.Info(msg, ex);
+        }
+
+        public void WriteWarnLog<T>(string msg, Exception ex = null)
+        {
+            System.Diagnostics.Debug.Write(msg + " " + ex);
+            if (!IsWriteLog)
+                return;
+
+            var log = GetCurrentLogger<T>();
+            log.Warn(msg, ex);
+        }
+
+        public void WriteErrorLog<T>(string msg, Exception ex = null)
+        {
+            System.Diagnostics.Debug.Write(msg + " " + ex);
+            if (!IsWriteLog)
+                return;
+
+            var log = GetCurrentLogger<T>();
+            log.Error(msg, ex);
+        }
+
+        public void WriteFatalLog<T>(string msg, Exception ex = null)
+        {
+            System.Diagnostics.Debug.Write(msg + " " + ex);
+            if (!IsWriteLog)
+                return;
+
+            var log = GetCurrentLogger<T>();
+            log.Fatal(msg, ex);
+        }
+
+        public async Task OpenLogFolder()
+        {
+            var tmpFolder = await ApplicationData.Current.LocalFolder.CreateFolderAsync("MetroLogs", CreationCollisionOption.OpenIfExists);
+            if (tmpFolder != null)
+                await Launcher.LaunchFolderAsync(tmpFolder);
+        }
+    }
+
+    public class CustomLogLayout : Layout
+    {
+        private string _appName;
+        public CustomLogLayout(string appName)
+        {
+            _appName = appName;
+        }
+
+        public override string GetFormattedString(LogWriteContext context, LogEventInfo info)
+        {
+            return $"{_appName}|{info.TimeStamp.LocalDateTime}|{info.Level}|{info.Logger}|{info.Message}|{info.Exception}";
+        }
+    }
+}

+ 29 - 0
Properties/AssemblyInfo.cs

@@ -0,0 +1,29 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// 組件的一般資訊是由下列的屬性集控制。
+// 變更這些屬性的值即可修改組件的相關
+// 資訊。
+[assembly: AssemblyTitle("KdanCommon")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("KdanCommon")]
+[assembly: AssemblyCopyright("Copyright ©  2023")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// 組件的版本資訊由下列四個值所組成:
+//
+//      主要版本
+//      次要版本 
+//      組建編號
+//      修訂
+//
+// 您可以指定所有的值,也可以使用 '*' 將組建和修訂編號 
+// 設為預設,如下所示:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
+[assembly: ComVisible(false)]

+ 33 - 0
Properties/KdanCommon.rd.xml

@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    這個檔案包含執行階段指示詞、應用程式透過反射和其他動態程式碼模式所存取的類型相關規格。
+    執行階段指示詞的用途是控制
+    .NET Native 最佳化程式,以及確認未移除程式庫所存取的程式碼。如果您的程式庫
+    未執行任何反射,您通常就不需要編輯這個檔案。不過,
+    如果您的程式庫透過類型 (尤其是傳遞給它或衍生自其類型的類型) 進行反射,
+    您就應該撰寫執行階段指示詞。
+
+    程式庫中的最常用反射是探索傳遞給程式庫的類型相關資訊。
+    執行階段指示詞能用三種方式表達傳遞給程式庫的類型
+    需求。
+
+    1.  Parameter、GenericParameter、TypeParameter、TypeEnumerableParameter
+        使用這些指示詞透過傳遞為參數的類型進行反射。
+
+    2.  SubTypes
+        使用 SubTypes 指示詞透過衍生自另一種類型的類型進行反射。
+
+    3.  AttributeImplies
+        使用 AttributeImplies 指示詞指出程式庫需要透過
+        利用屬性所裝飾的類型或方法進行反射。
+
+    如需為程式庫撰寫執行階段指示詞的詳細資訊,請前往
+    https://go.microsoft.com/fwlink/?LinkID=391919
+-->
+<Directives xmlns="http://schemas.microsoft.com/netfx/2013/01/metadata">
+  <Library Name="KdanCommon">
+
+  	<!-- 請在這裡新增程式庫的指示詞 -->
+
+  </Library>
+</Directives>

+ 1 - 0
Readme.txt

@@ -0,0 +1 @@
+20230204