Sfoglia il codice sorgente

Add files via upload

Machtergreifung 3 anni fa
parent
commit
e27bf373a8
37 ha cambiato i file con 1991 aggiunte e 0 eliminazioni
  1. 288 0
      ClashDotNetFramework/Models/Controls/DropShadowTextBlock.cs
  2. 32 0
      ClashDotNetFramework/Models/Converters/BoolToVisibilityConverter.cs
  3. 29 0
      ClashDotNetFramework/Models/Converters/CornerRadiusValueConverter.cs
  4. 25 0
      ClashDotNetFramework/Models/Converters/DateTimeToStringConverter.cs
  5. 32 0
      ClashDotNetFramework/Models/Converters/GroupCountToStringConverter.cs
  6. 31 0
      ClashDotNetFramework/Models/Converters/GroupNameToExpandedConverter.cs
  7. 34 0
      ClashDotNetFramework/Models/Converters/LatencyToColorConverter.cs
  8. 33 0
      ClashDotNetFramework/Models/Converters/LatencyToTextConverter.cs
  9. 35 0
      ClashDotNetFramework/Models/Converters/LevelToColorConverter.cs
  10. 33 0
      ClashDotNetFramework/Models/Converters/ProfileTypeToStringConverter.cs
  11. 32 0
      ClashDotNetFramework/Models/Converters/ProxyCountToStringConverter.cs
  12. 32 0
      ClashDotNetFramework/Models/Converters/RuleCountToStringConverter.cs
  13. 33 0
      ClashDotNetFramework/Models/Converters/ScalarValueConverter.cs
  14. 58 0
      ClashDotNetFramework/Models/Converters/StatusToColorConverter.cs
  15. 27 0
      ClashDotNetFramework/Models/Converters/StringToVisibilityConverter.cs
  16. 17 0
      ClashDotNetFramework/Models/Enums/LanguageType.cs
  17. 17 0
      ClashDotNetFramework/Models/Enums/LogLevel.cs
  18. 21 0
      ClashDotNetFramework/Models/Enums/ProfileType.cs
  19. 41 0
      ClashDotNetFramework/Models/Enums/State.cs
  20. 17 0
      ClashDotNetFramework/Models/Enums/ThemeType.cs
  21. 37 0
      ClashDotNetFramework/Models/GitHubRelease/Asset.cs
  22. 22 0
      ClashDotNetFramework/Models/GitHubRelease/GitHubRelease.cs
  23. 47 0
      ClashDotNetFramework/Models/GitHubRelease/GitHubUser.cs
  24. 47 0
      ClashDotNetFramework/Models/GitHubRelease/Release.cs
  25. 149 0
      ClashDotNetFramework/Models/GitHubRelease/SuffixVersion.cs
  26. 16 0
      ClashDotNetFramework/Models/GitHubRelease/VersionComparer.cs
  27. 36 0
      ClashDotNetFramework/Models/GitHubRelease/VersionUtil.cs
  28. 81 0
      ClashDotNetFramework/Models/Items/ConnectionItem.cs
  29. 17 0
      ClashDotNetFramework/Models/Items/LogItem.cs
  30. 267 0
      ClashDotNetFramework/Models/Items/ProfileItem.cs
  31. 105 0
      ClashDotNetFramework/Models/Items/ProxyItem.cs
  32. 68 0
      ClashDotNetFramework/Models/LanguageResourceDictionary.cs
  33. 16 0
      ClashDotNetFramework/Models/Responses/AdvertisementResponse.cs
  34. 104 0
      ClashDotNetFramework/Models/Setting.cs
  35. 18 0
      ClashDotNetFramework/Models/Share/AirportData.cs
  36. 26 0
      ClashDotNetFramework/Models/Structs/Range.cs
  37. 68 0
      ClashDotNetFramework/Models/ThemeResourceDictionary.cs

+ 288 - 0
ClashDotNetFramework/Models/Controls/DropShadowTextBlock.cs

@@ -0,0 +1,288 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Media;
+
+namespace ClashDotNetFramework.Models.Controls
+{
+    /// <summary>
+    /// A control that displays text, with a drop shadow.
+    /// </summary>
+    public class DropShadowTextBlock : Control
+    {
+        /// <summary>
+        /// The drop shadow color property.
+        /// </summary>
+        public static readonly DependencyProperty DropShadowColorProperty = DependencyProperty.Register("DropShadowColor", typeof(Color), typeof(DropShadowTextBlock), new PropertyMetadata(DropShadowColorChanged));
+
+        /// <summary>
+        /// The drop shadow opacity property.
+        /// </summary>
+        public static readonly DependencyProperty DropShadowOpacityProperty = DependencyProperty.Register("DropShadowOpacity", typeof(double), typeof(DropShadowTextBlock), new PropertyMetadata(DropShadowOpacityChanged));
+
+        /// <summary>
+        /// The text property.
+        /// </summary>
+        public static readonly DependencyProperty TextProperty = DependencyProperty.Register("Text", typeof(string), typeof(DropShadowTextBlock), null);
+
+        /// <summary>
+        /// The text decorations property.
+        /// </summary>
+        public static readonly DependencyProperty TextDecorationsProperty = DependencyProperty.Register("TextDecorations", typeof(TextDecorationCollection), typeof(DropShadowTextBlock), null);
+
+        /// <summary>
+        /// The text wrapping property.
+        /// </summary>
+        public static readonly DependencyProperty TextWrappingProperty = DependencyProperty.Register("TextWrapping", typeof(TextWrapping), typeof(DropShadowTextBlock), null);
+
+        /// <summary>
+        /// The drop shadow distance property.
+        /// </summary>
+        public static readonly DependencyProperty DropShadowDistanceProperty = DependencyProperty.Register("DropShadowDistance", typeof(double), typeof(DropShadowTextBlock), new PropertyMetadata(DropShadowDistanceChanged));
+
+        /// <summary>
+        /// The drop shadow angle property.
+        /// </summary>
+        public static readonly DependencyProperty DropShadowAngleProperty = DependencyProperty.Register("DropShadowAngle", typeof(double), typeof(DropShadowTextBlock), new PropertyMetadata(DropShadowAngleChanged));
+
+        /// <summary>
+        /// Stores the drop shadow brush.
+        /// </summary>
+        private SolidColorBrush _dropShadowBrush;
+
+        /// <summary>
+        /// Stores the drop shadow translate transform.
+        /// </summary>
+        private TranslateTransform _dropShadowTranslate;
+
+        /// <summary>
+        /// DropShadowTextBlock constructor.
+        /// </summary>
+        public DropShadowTextBlock()
+        {
+            DefaultStyleKey = typeof(DropShadowTextBlock);
+        }
+
+        /// <summary>
+        /// Gets or sets the drop shadow color.
+        /// </summary>
+        [Category("Appearance"), Description("The drop shadow color.")]
+        public Color DropShadowColor
+        {
+            get
+            {
+                return (Color)GetValue(DropShadowColorProperty);
+            }
+            set
+            {
+                SetValue(DropShadowColorProperty, value);
+            }
+        }
+
+        /// <summary>
+        /// Gets or sets the drop shadow opacity.
+        /// </summary>
+        [Category("Appearance"), Description("The drop shadow opacity.")]
+        public double DropShadowOpacity
+        {
+            get
+            {
+                return (double)GetValue(DropShadowOpacityProperty);
+            }
+            set
+            {
+                SetValue(DropShadowOpacityProperty, value);
+            }
+        }
+
+        /// <summary>
+        /// Gets or sets the link text.
+        /// </summary>
+        [Category("Common Properties"), Description("The text content.")]
+        public string Text
+        {
+            get
+            {
+                return (string)GetValue(TextProperty);
+            }
+            set
+            {
+                SetValue(TextProperty, value);
+            }
+        }
+
+        /// <summary>
+        /// Gets or sets the text decorations.
+        /// </summary>
+        [Category("Common Properties"), Description("The text decorations.")]
+        public TextDecorationCollection TextDecorations
+        {
+            get
+            {
+                return (TextDecorationCollection)GetValue(TextDecorationsProperty);
+            }
+            set
+            {
+                SetValue(TextDecorationsProperty, value);
+            }
+        }
+
+        /// <summary>
+        /// Gets or sets the text wrapping.
+        /// </summary>
+        [Category("Common Properties"), Description("Whether the text wraps.")]
+        public TextWrapping TextWrapping
+        {
+            get
+            {
+                return (TextWrapping)GetValue(TextWrappingProperty);
+            }
+            set
+            {
+                SetValue(TextWrappingProperty, value);
+            }
+        }
+
+        /// <summary>
+        /// Gets or sets the drop shadow distance.
+        /// </summary>
+        [Category("Appearance"), Description("The drop shadow distance.")]
+        public double DropShadowDistance
+        {
+            get
+            {
+                return (double)GetValue(DropShadowDistanceProperty);
+            }
+            set
+            {
+                SetValue(DropShadowDistanceProperty, value);
+            }
+        }
+
+        /// <summary>
+        /// Gets or sets the drop shadow angle.
+        /// </summary>
+        [Category("Appearance"), Description("The drop shadow angle.")]
+        public double DropShadowAngle
+        {
+            get
+            {
+                return (double)GetValue(DropShadowAngleProperty);
+            }
+            set
+            {
+                SetValue(DropShadowAngleProperty, value);
+            }
+        }
+
+        /// <summary>
+        /// Gets the UI elements out of the template.
+        /// </summary>
+        public override void OnApplyTemplate()
+        {
+            base.OnApplyTemplate();
+            _dropShadowTranslate = GetTemplateChild("PART_DropShadowTranslate") as TranslateTransform;
+            _dropShadowBrush = GetTemplateChild("PART_DropShadowBrush") as SolidColorBrush;
+            UpdateDropShadowPosition();
+            UpdateDropShadowBrush();
+        }
+
+        /// <summary>
+        /// Converts degrees into radians.
+        /// </summary>
+        /// <param name="degrees">The degree value.</param>
+        /// <returns>The degrees as radians.</returns>
+        private static double DegreesToRadians(double degrees)
+        {
+            return degrees * (Math.PI / 180);
+        }
+
+        /// <summary>
+        /// Gets a point offset by a distance and angle (in degrees).
+        /// </summary>
+        /// <param name="angle">The angle in degrees.</param>
+        /// <param name="distance">The distance.</param>
+        /// <returns>The offset point.</returns>
+        private static Point GetOffset(double angle, double distance)
+        {
+            double x = Math.Cos(DegreesToRadians(angle)) * distance;
+            double y = Math.Tan(DegreesToRadians(angle)) * x;
+            return new Point(x, y);
+        }
+
+        /// <summary>
+        /// Updates the drop shadow.
+        /// </summary>
+        internal void UpdateDropShadowPosition()
+        {
+            if (_dropShadowTranslate != null)
+            {
+                Point offset = GetOffset(DropShadowAngle, DropShadowDistance);
+
+                _dropShadowTranslate.X = offset.X;
+                _dropShadowTranslate.Y = offset.Y;
+            }
+        }
+
+        /// <summary>
+        /// Updates the drop shadow brush.
+        /// </summary>
+        internal void UpdateDropShadowBrush()
+        {
+            if (_dropShadowBrush != null)
+            {
+                _dropShadowBrush.Color = DropShadowColor;
+                _dropShadowBrush.Opacity = DropShadowOpacity;
+            }
+        }
+
+        /// <summary>
+        /// Updates the drop shadow.
+        /// </summary>
+        /// <param name="dependencyObject">The drop shadow text block.</param>
+        /// <param name="eventArgs">Dependency Property Changed Event Args</param>
+        private static void DropShadowDistanceChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs eventArgs)
+        {
+            var dropShadowTextBlock = (DropShadowTextBlock)dependencyObject;
+            dropShadowTextBlock.UpdateDropShadowPosition();
+        }
+
+        /// <summary>
+        /// Updates the drop shadow.
+        /// </summary>
+        /// <param name="dependencyObject">The drop shadow text block.</param>
+        /// <param name="eventArgs">Dependency Property Changed Event Args</param>
+        private static void DropShadowAngleChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs eventArgs)
+        {
+            var dropShadowTextBlock = (DropShadowTextBlock)dependencyObject;
+            dropShadowTextBlock.UpdateDropShadowPosition();
+        }
+
+        /// <summary>
+        /// Updates the drop shadow.
+        /// </summary>
+        /// <param name="dependencyObject">The drop shadow text block.</param>
+        /// <param name="eventArgs">Dependency Property Changed Event Args</param>
+        private static void DropShadowColorChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs eventArgs)
+        {
+            var dropShadowTextBlock = (DropShadowTextBlock)dependencyObject;
+            dropShadowTextBlock.UpdateDropShadowBrush();
+        }
+
+        /// <summary>
+        /// Updates the drop shadow.
+        /// </summary>
+        /// <param name="dependencyObject">The drop shadow text block.</param>
+        /// <param name="eventArgs">Dependency Property Changed Event Args</param>
+        private static void DropShadowOpacityChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs eventArgs)
+        {
+            var dropShadowTextBlock = (DropShadowTextBlock)dependencyObject;
+            dropShadowTextBlock.UpdateDropShadowBrush();
+        }
+    }
+}

+ 32 - 0
ClashDotNetFramework/Models/Converters/BoolToVisibilityConverter.cs

@@ -0,0 +1,32 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Data;
+
+namespace ClashDotNetFramework.Models.Converters
+{
+    public class BoolToVisibilityConverter : IValueConverter
+    {
+        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            bool status = (bool)value;
+            if (status)
+            {
+                return Visibility.Visible;
+            }
+            else
+            {
+                return Visibility.Collapsed;
+            }
+        }
+
+        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            throw new NotImplementedException();
+        }
+    }
+}

+ 29 - 0
ClashDotNetFramework/Models/Converters/CornerRadiusValueConverter.cs

@@ -0,0 +1,29 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Data;
+
+namespace ClashDotNetFramework.Models.Converters
+{
+    public class CornerRadiusValueConverter : IValueConverter
+    {
+        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            var radius = Double.Parse(value.ToString(), culture);
+            if (parameter != null)
+            {
+                radius *= Double.Parse(parameter.ToString(), culture);
+            }
+            return new CornerRadius(radius);
+        }
+
+        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            throw new NotImplementedException();
+        }
+    }
+}

+ 25 - 0
ClashDotNetFramework/Models/Converters/DateTimeToStringConverter.cs

@@ -0,0 +1,25 @@
+using ClashDotNetFramework.Utils;
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows.Data;
+
+namespace ClashDotNetFramework.Models.Converters
+{
+    public class DateTimeToStringConverter : IValueConverter
+    {
+        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            DateTime dateTime = (DateTime)value;
+            return TimeConverter.ParseTime(dateTime);
+        }
+
+        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            throw new NotImplementedException();
+        }
+    }
+}

+ 32 - 0
ClashDotNetFramework/Models/Converters/GroupCountToStringConverter.cs

@@ -0,0 +1,32 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows.Data;
+
+namespace ClashDotNetFramework.Models.Converters
+{
+    public class GroupCountToStringConverter : IValueConverter
+    {
+        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            long count = (long)value;
+            if (count > 1)
+            {
+                return $"{count} Groups";
+            }
+            else if (count >= 0)
+            {
+                return $"{count} Group";
+            }
+            return "";
+        }
+
+        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            throw new NotImplementedException();
+        }
+    }
+}

+ 31 - 0
ClashDotNetFramework/Models/Converters/GroupNameToExpandedConverter.cs

@@ -0,0 +1,31 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows.Data;
+
+namespace ClashDotNetFramework.Models.Converters
+{
+    public class GroupNameToExpandedConverter : IValueConverter
+    {
+        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            string groupName = (string)value;
+            if (groupName.Equals("GLOBAL"))
+            {
+                return true;
+            }
+            else
+            {
+                return false;
+            }
+        }
+
+        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            throw new NotImplementedException();
+        }
+    }
+}

+ 34 - 0
ClashDotNetFramework/Models/Converters/LatencyToColorConverter.cs

@@ -0,0 +1,34 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows.Data;
+using System.Windows.Media;
+
+namespace ClashDotNetFramework.Models.Converters
+{
+    public class LatencyToColorConverter : IValueConverter
+    {
+        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            long latency = (long)value;
+            switch (latency)
+            {
+                case 0:
+                case -1:
+                    return new SolidColorBrush(Color.FromRgb(255, 0, 0));
+                case -2:
+                    return new SolidColorBrush(Color.FromRgb(255, 255, 255));
+                default:
+                    return new SolidColorBrush(Color.FromRgb(255, 255, 255));
+            }
+        }
+
+        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            throw new NotImplementedException();
+        }
+    }
+}

+ 33 - 0
ClashDotNetFramework/Models/Converters/LatencyToTextConverter.cs

@@ -0,0 +1,33 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows.Data;
+
+namespace ClashDotNetFramework.Models.Converters
+{
+    public class LatencyToTextConverter : IValueConverter
+    {
+        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            long latency = (long)value;
+            switch (latency)
+            {
+                case 0:
+                case -1:
+                    return "Timeout";
+                case -2:
+                    return "Check";
+                default:
+                    return $"{latency} ms";
+            }
+        }
+
+        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            throw new NotImplementedException();
+        }
+    }
+}

+ 35 - 0
ClashDotNetFramework/Models/Converters/LevelToColorConverter.cs

@@ -0,0 +1,35 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows.Data;
+
+namespace ClashDotNetFramework.Models.Converters
+{
+    public class LevelToColorConverter : IValueConverter
+    {
+        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            switch (value.ToString())
+            {
+                case "Debug":
+                    return "#428EE4";
+                case "Info":
+                    return "#097247";
+                case "Warning":
+                    return "#B99105";
+                case "Error":
+                    return "#C11C1C";
+                default:
+                    return "";
+            }
+        }
+
+        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            throw new NotImplementedException();
+        }
+    }
+}

+ 33 - 0
ClashDotNetFramework/Models/Converters/ProfileTypeToStringConverter.cs

@@ -0,0 +1,33 @@
+using ClashDotNetFramework.Models.Enums;
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows.Data;
+
+namespace ClashDotNetFramework.Models.Converters
+{
+    public class ProfileTypeToStringConverter : IValueConverter
+    {
+        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            ProfileType profileType = (ProfileType)value;
+            switch (profileType)
+            {
+                case ProfileType.Local:
+                    return "Local File";
+                case ProfileType.Remote:
+                    return "Remote File";
+                default:
+                    return "";
+            }
+        }
+
+        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            throw new NotImplementedException();
+        }
+    }
+}

+ 32 - 0
ClashDotNetFramework/Models/Converters/ProxyCountToStringConverter.cs

@@ -0,0 +1,32 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows.Data;
+
+namespace ClashDotNetFramework.Models.Converters
+{
+    public class ProxyCountToStringConverter : IValueConverter
+    {
+        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            long count = (long)value;
+            if (count > 1)
+            {
+                return $"{count} Proxies";
+            }
+            else if (count >= 0)
+            {
+                return $"{count} Proxy";
+            }
+            return "";
+        }
+
+        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            throw new NotImplementedException();
+        }
+    }
+}

+ 32 - 0
ClashDotNetFramework/Models/Converters/RuleCountToStringConverter.cs

@@ -0,0 +1,32 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows.Data;
+
+namespace ClashDotNetFramework.Models.Converters
+{
+    public class RuleCountToStringConverter : IValueConverter
+    {
+        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            long count = (long)value;
+            if (count > 1)
+            {
+                return $"{count} Rules";
+            }
+            else if (count >= 0)
+            {
+                return $"{count} Rule";
+            }
+            return "";
+        }
+
+        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            throw new NotImplementedException();
+        }
+    }
+}

+ 33 - 0
ClashDotNetFramework/Models/Converters/ScalarValueConverter.cs

@@ -0,0 +1,33 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows.Data;
+
+namespace ClashDotNetFramework.Models.Converters
+{
+    public class ScalarValueConverter : IValueConverter
+    {
+        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            var oldValue = Double.Parse(value.ToString(), culture);
+            if (parameter != null)
+            {
+                oldValue *= Double.Parse(parameter.ToString(), culture);
+            }
+            return oldValue;
+        }
+
+        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            var oldValue = Double.Parse(value.ToString(), culture);
+            if (parameter != null)
+            {
+                oldValue /= Double.Parse(parameter.ToString(), culture);
+            }
+            return oldValue;
+        }
+    }
+}

+ 58 - 0
ClashDotNetFramework/Models/Converters/StatusToColorConverter.cs

@@ -0,0 +1,58 @@
+using ClashDotNetFramework.Models.Enums;
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Data;
+using System.Windows.Media;
+
+namespace ClashDotNetFramework.Models.Converters
+{
+    public class StatusToColorConverter : IValueConverter
+    {
+        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            bool status = (bool)value;
+            switch (Global.Settings.Theme)
+            {
+                case ThemeType.Classic:
+                    if (status)
+                    {
+                        return new SolidColorBrush(Color.FromArgb((byte)Math.Round(0.9 * 255), Global.ProxyColor.R, Global.ProxyColor.G, Global.ProxyColor.B));
+                    }
+                    else
+                    {
+                        return new SolidColorBrush(Color.FromArgb((byte)Math.Round(0.6 * 255), Global.ProxyColor.R, Global.ProxyColor.G, Global.ProxyColor.B));
+                    }
+                case ThemeType.Modern:
+                    if (status)
+                    {
+                        return new LinearGradientBrush(Color.FromRgb(87, 190, 252), Color.FromRgb(44, 138, 248), new Point(0, 0), new Point(1, 1));
+                    }
+                    else
+                    {
+                        return new LinearGradientBrush(Color.FromArgb((byte)Math.Round(0.5 * 255), 87, 190, 252), Color.FromArgb((byte)Math.Round(0.5 * 255), 44, 138, 248), new Point(0, 0), new Point(1, 1));
+                    }
+                case ThemeType.Dark:
+                    if (status)
+                    {
+                        return new SolidColorBrush(Color.FromRgb(66, 66, 78));
+                    }
+                    else
+                    {
+                        return new SolidColorBrush(Color.FromRgb(50, 50, 63));
+                    }
+                default:
+                    return null;
+            }
+        }
+
+        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            throw new NotImplementedException();
+        }
+    }
+}

+ 27 - 0
ClashDotNetFramework/Models/Converters/StringToVisibilityConverter.cs

@@ -0,0 +1,27 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Data;
+
+namespace ClashDotNetFramework.Models.Converters
+{
+    public class StringToVisibilityConverter : IValueConverter
+    {
+        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            if (string.IsNullOrWhiteSpace(value.ToString()))
+                return Visibility.Collapsed;
+            else
+                return Visibility.Visible;
+        }
+
+        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            throw new NotImplementedException();
+        }
+    }
+}

+ 17 - 0
ClashDotNetFramework/Models/Enums/LanguageType.cs

@@ -0,0 +1,17 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace ClashDotNetFramework.Models.Enums
+{
+    public enum LanguageType
+    {
+        English,
+
+        Chinese,
+
+        Japanese
+    }
+}

+ 17 - 0
ClashDotNetFramework/Models/Enums/LogLevel.cs

@@ -0,0 +1,17 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace ClashDotNetFramework.Models.Enums
+{
+    public enum LogLevel
+    {
+        INFO,
+
+        WARNING,
+
+        ERROR
+    }
+}

+ 21 - 0
ClashDotNetFramework/Models/Enums/ProfileType.cs

@@ -0,0 +1,21 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace ClashDotNetFramework.Models.Enums
+{
+    public enum ProfileType
+    {
+        /// <summary>
+        /// 本地配置文件
+        /// </summary>
+        Local,
+
+        /// <summary>
+        /// 远程配置文件
+        /// </summary>
+        Remote
+    }
+}

+ 41 - 0
ClashDotNetFramework/Models/Enums/State.cs

@@ -0,0 +1,41 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace ClashDotNetFramework.Models.Enums
+{
+    public enum State
+    {
+        /// <summary>
+        /// 等待命令中
+        /// </summary>
+        Waiting,
+
+        /// <summary>
+        /// 正在启动中
+        /// </summary>
+        Starting,
+
+        /// <summary>
+        /// 已启动
+        /// </summary>
+        Started,
+
+        /// <summary>
+        /// 正在停止中
+        /// </summary>
+        Stopping,
+
+        /// <summary>
+        /// 已停止
+        /// </summary>
+        Stopped,
+
+        /// <summary>
+        /// 退出中
+        /// </summary>
+        Terminating
+    }
+}

+ 17 - 0
ClashDotNetFramework/Models/Enums/ThemeType.cs

@@ -0,0 +1,17 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace ClashDotNetFramework.Models.Enums
+{
+    public enum ThemeType
+    {
+        Classic,
+
+        Modern,
+
+        Dark
+    }
+}

+ 37 - 0
ClashDotNetFramework/Models/GitHubRelease/Asset.cs

@@ -0,0 +1,37 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace ClashDotNetFramework.Models.GitHubRelease
+{
+    public class Asset
+    {
+        public string url { get; set; }
+
+        public int id { get; set; }
+
+        public string node_id { get; set; }
+
+        public string name { get; set; }
+
+        public object label { get; set; }
+
+        public GitHubUser uploader { get; set; }
+
+        public string content_type { get; set; }
+
+        public string state { get; set; }
+
+        public int size { get; set; }
+
+        public int download_count { get; set; }
+
+        public DateTime created_at { get; set; }
+
+        public DateTime updated_at { get; set; }
+
+        public string browser_download_url { get; set; }
+    }
+}

+ 22 - 0
ClashDotNetFramework/Models/GitHubRelease/GitHubRelease.cs

@@ -0,0 +1,22 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace ClashDotNetFramework.Models.GitHubRelease
+{
+    public class GitHubRelease
+    {
+        private readonly string _owner;
+        private readonly string _repo;
+
+        public GitHubRelease(string owner, string repo)
+        {
+            _owner = owner;
+            _repo = repo;
+        }
+
+        public string AllReleaseUrl => $@"https://api.github.com/repos/{_owner}/{_repo}/releases";
+    }
+}

+ 47 - 0
ClashDotNetFramework/Models/GitHubRelease/GitHubUser.cs

@@ -0,0 +1,47 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace ClashDotNetFramework.Models.GitHubRelease
+{
+    public class GitHubUser
+    {
+        public string login { get; set; }
+
+        public int id { get; set; }
+
+        public string node_id { get; set; }
+
+        public string avatar_url { get; set; }
+
+        public string gravatar_id { get; set; }
+
+        public string url { get; set; }
+
+        public string html_url { get; set; }
+
+        public string followers_url { get; set; }
+
+        public string following_url { get; set; }
+
+        public string gists_url { get; set; }
+
+        public string starred_url { get; set; }
+
+        public string subscriptions_url { get; set; }
+
+        public string organizations_url { get; set; }
+
+        public string repos_url { get; set; }
+
+        public string events_url { get; set; }
+
+        public string received_events_url { get; set; }
+
+        public string type { get; set; }
+
+        public bool site_admin { get; set; }
+    }
+}

+ 47 - 0
ClashDotNetFramework/Models/GitHubRelease/Release.cs

@@ -0,0 +1,47 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace ClashDotNetFramework.Models.GitHubRelease
+{
+    public class Release
+    {
+        public string url { get; set; }
+
+        public string assets_url { get; set; }
+
+        public string upload_url { get; set; }
+
+        public string html_url { get; set; }
+
+        public int id { get; set; }
+
+        public string node_id { get; set; }
+
+        public string tag_name { get; set; }
+
+        public string target_commitish { get; set; }
+
+        public string name { get; set; }
+
+        public bool draft { get; set; }
+
+        public GitHubUser author { get; set; }
+
+        public bool prerelease { get; set; }
+
+        public DateTime created_at { get; set; }
+
+        public DateTime published_at { get; set; }
+
+        public Asset[] assets { get; set; }
+
+        public string tarball_url { get; set; }
+
+        public string zipball_url { get; set; }
+
+        public string body { get; set; }
+    }
+}

+ 149 - 0
ClashDotNetFramework/Models/GitHubRelease/SuffixVersion.cs

@@ -0,0 +1,149 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace ClashDotNetFramework.Models.GitHubRelease
+{
+    [Serializable]
+    public struct SuffixVersion : ICloneable, IComparable, IComparable<SuffixVersion>, IEquatable<SuffixVersion>
+    {
+        public int Major { get; }
+
+        public int Minor { get; }
+
+        public int Patch { get; }
+
+        public string PreRelease { get; }
+
+        public int Build { get; }
+
+        public SuffixVersion(int major, int minor, int patch, string preRelease, int build)
+        {
+            Major = major;
+            Minor = minor;
+            Patch = patch;
+            PreRelease = preRelease;
+            Build = build;
+        }
+
+        public SuffixVersion(Version version, string preRelease, int build)
+        {
+            Major = version.Major;
+            Minor = version.Minor;
+            Patch = version.Build;
+            PreRelease = preRelease;
+            Build = build;
+        }
+
+        public static SuffixVersion Parse(string input)
+        {
+            if (input.StartsWith("v"))
+            {
+                input = input.Substring(1, input.Length - 1);
+            }
+            var splitStr = input.Split('-');
+            var dotNetVersion = Version.Parse(splitStr[0]);
+            var preRelease = new StringBuilder();
+            var build = 0;
+
+            if (splitStr.Length > 1)
+                foreach (var c in splitStr[1])
+                    if (int.TryParse(c.ToString(), out var n))
+                        build = build * 10 + n;
+                    else
+                        preRelease.Append(c);
+
+            return new SuffixVersion(dotNetVersion, preRelease.ToString(), build);
+        }
+
+        public static bool TryParse(string input, out SuffixVersion result)
+        {
+            try
+            {
+                result = Parse(input);
+                return true;
+            }
+            catch (Exception)
+            {
+                result = default;
+                return false;
+            }
+        }
+
+        public object Clone()
+        {
+            return new SuffixVersion(Major, Major, Patch, PreRelease, Build);
+        }
+
+        public int CompareTo(object obj)
+        {
+            if (obj is SuffixVersion version)
+                return CompareTo(version);
+
+            return -1;
+        }
+
+        /// <summary>
+        /// </summary>
+        /// <param name="other"></param>
+        /// <returns>
+        ///     greater than 0 newer
+        /// </returns>
+        public int CompareTo(SuffixVersion other)
+        {
+            var majorComparison = Major.CompareTo(other.Major);
+            if (majorComparison != 0)
+                return majorComparison;
+
+            var minorComparison = Minor.CompareTo(other.Minor);
+            if (minorComparison != 0)
+                return minorComparison;
+
+            var patchComparison = Patch.CompareTo(other.Patch);
+            if (patchComparison != 0)
+                return patchComparison;
+
+            if (PreRelease == string.Empty)
+                return other.PreRelease == string.Empty ? 0 : 1;
+
+            if (other.PreRelease == string.Empty)
+                return -1;
+
+            var suffixComparison = string.Compare(PreRelease, other.PreRelease, StringComparison.Ordinal);
+            if (suffixComparison != 0)
+                return suffixComparison;
+
+            return Build.CompareTo(other.Build);
+        }
+
+        public bool Equals(SuffixVersion other)
+        {
+            return Major == other.Major && Minor == other.Minor && Patch == other.Patch && PreRelease == other.PreRelease && Build == other.Build;
+        }
+
+        public override bool Equals(object obj)
+        {
+            return obj is SuffixVersion other && Equals(other);
+        }
+
+        public override int GetHashCode()
+        {
+            unchecked
+            {
+                var hashCode = Major;
+                hashCode = (hashCode * 397) ^ Minor;
+                hashCode = (hashCode * 397) ^ Patch;
+                hashCode = (hashCode * 397) ^ (PreRelease != null ? PreRelease.GetHashCode() : 0);
+                hashCode = (hashCode * 397) ^ Build;
+                return hashCode;
+            }
+        }
+
+        public override string ToString()
+        {
+            return $"{Major}.{Minor}.{Patch}{(string.IsNullOrEmpty(PreRelease) ? "" : "-")}{PreRelease}{(Build == 0 ? "" : Build.ToString())}";
+        }
+    }
+}

+ 16 - 0
ClashDotNetFramework/Models/GitHubRelease/VersionComparer.cs

@@ -0,0 +1,16 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace ClashDotNetFramework.Models.GitHubRelease
+{
+    public class VersionComparer : IComparer<object>
+    {
+        public int Compare(object x, object y)
+        {
+            return VersionUtil.CompareVersion(x?.ToString(), y?.ToString());
+        }
+    }
+}

+ 36 - 0
ClashDotNetFramework/Models/GitHubRelease/VersionUtil.cs

@@ -0,0 +1,36 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace ClashDotNetFramework.Models.GitHubRelease
+{
+    public static class VersionUtil
+    {
+        public static Release GetLatestRelease(IEnumerable<Release> releases, bool isPreRelease)
+        {
+            if (!isPreRelease)
+                releases = releases.Where(release => !release.prerelease);
+
+            releases = releases.Where(release => IsVersionString(release.tag_name));
+            var ordered = releases.OrderByDescending(release => release.tag_name, new VersionComparer());
+            return ordered.ElementAt(0);
+        }
+
+        private static bool IsVersionString(string str)
+        {
+            return SuffixVersion.TryParse(str, out _);
+        }
+
+        /// <returns> =0:versions are equal</returns>
+        /// <returns> &gt;0:version1 is greater</returns>
+        /// <returns> &lt;0:version2 is greater</returns>
+        public static int CompareVersion(string v1, string v2)
+        {
+            var version1 = SuffixVersion.Parse(v1);
+            var version2 = SuffixVersion.Parse(v2);
+            return version1.CompareTo(version2);
+        }
+    }
+}

+ 81 - 0
ClashDotNetFramework/Models/Items/ConnectionItem.cs

@@ -0,0 +1,81 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace ClashDotNetFramework.Models.Items
+{
+    public class ConnectionItem
+    {
+        /// <summary>
+        /// UUID
+        /// </summary>
+        public string UUID { get; set; }
+
+        /// <summary>
+        /// 传输协议
+        /// </summary>
+        public string Network { get; set; }
+
+        /// <summary>
+        /// 协议具体类型
+        /// </summary>
+        public string Type { get; set; }
+
+        /// <summary>
+        /// 请求目标主机(自动合并Host和Destination)
+        /// </summary>
+        public string TargetHost { get; set; }
+
+        /// <summary>
+        /// 请求源IP
+        /// </summary>
+        public string Source { get; set; }
+
+        /// <summary>
+        /// 请求目标IP
+        /// </summary>
+        public string Destination { get; set; }
+
+        /// <summary>
+        /// 请求目标主机
+        /// </summary>
+        public string Host { get; set; }
+
+        /// <summary>
+        /// 请求代理链
+        /// </summary>
+        public List<string> Chains { get; set; }
+
+        /// <summary>
+        /// 请求出站节点
+        /// </summary>
+        public string FinalNode { get; set; }
+
+        /// <summary>
+        /// 请求代理规则
+        /// </summary>
+        public string Rule { get; set; }
+
+        /// <summary>
+        /// 请求时间
+        /// </summary>
+        public string Time { get; set; }
+
+        /// <summary>
+        /// 请求上传速度
+        /// </summary>
+        public string Upload { get; set; }
+
+        /// <summary>
+        /// 请求下载速度
+        /// </summary>
+        public string Download { get; set; }
+
+        /// <summary>
+        /// 请求速度概述
+        /// </summary>
+        public string Speed { get; set; }
+    }
+}

+ 17 - 0
ClashDotNetFramework/Models/Items/LogItem.cs

@@ -0,0 +1,17 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace ClashDotNetFramework.Models.Items
+{
+    public class LogItem
+    {
+        public string Time { get; set; }
+
+        public string Level { get; set; }
+
+        public string PayLoad { get; set; }
+    }
+}

+ 267 - 0
ClashDotNetFramework/Models/Items/ProfileItem.cs

@@ -0,0 +1,267 @@
+using ClashDotNetFramework.Models.Enums;
+using ClashDotNetFramework.Utils;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.IO;
+using System.Linq;
+using System.Net;
+using System.Runtime.CompilerServices;
+using System.Text;
+using System.Threading.Tasks;
+using static ClashDotNetFramework.Utils.ProfileHelper;
+
+namespace ClashDotNetFramework.Models.Items
+{
+    public class ProfileItem : INotifyPropertyChanged
+    {
+        #region 属性改变事件
+
+        public event PropertyChangedEventHandler PropertyChanged;
+
+        private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
+        {
+            if (PropertyChanged != null)
+            {
+                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
+            }
+        }
+
+        #endregion
+
+        #region 内部变量
+
+        private string _name { get; set; } = "";
+
+        private bool _selected { get; set; } = false;
+
+        private long _proxyCount { get; set; } = 0;
+
+        private long _groupCount { get; set; } = 0;
+
+        private long _ruleCount { get; set; } = 0;
+
+        private DateTime _lastUpdate { get; set; } = DateTime.Now;
+
+        #endregion
+
+        #region 通用设置
+
+        public string Name
+        {
+            get
+            {
+                return _name;
+            }
+
+            set
+            {
+                if (value != _name)
+                {
+                    _name = value;
+                    NotifyPropertyChanged();
+                }
+            }
+        }
+
+        public string FileName { get; set; } = "";
+
+        public bool IsSelected
+        {
+            get
+            {
+                return _selected;
+            }
+
+            set
+            {
+                if (value != _selected)
+                {
+                    _selected = value;
+                    NotifyPropertyChanged();
+                }
+            }
+        }
+
+        public long ProxyCount
+        {
+            get
+            {
+                return _proxyCount;
+            }
+
+            set
+            {
+                if (value != _proxyCount)
+                {
+                    _proxyCount = value;
+                    NotifyPropertyChanged();
+                }
+            }
+        }
+
+        public long GroupCount
+        {
+            get
+            {
+                return _groupCount;
+            }
+
+            set
+            {
+                if (value != _groupCount)
+                {
+                    _groupCount = value;
+                    NotifyPropertyChanged();
+                }
+            }
+        }
+
+        public long RuleCount
+        {
+            get
+            {
+                return _ruleCount;
+            }
+
+            set
+            {
+                if (value != _ruleCount)
+                {
+                    _ruleCount = value;
+                    NotifyPropertyChanged();
+                }
+            }
+        }
+
+        public ProfileType Type { get; set; } = ProfileType.Local;
+
+        public DateTime LastUpdate
+        {
+            get
+            {
+                return _lastUpdate;
+            }
+
+            set
+            {
+                if (value != _lastUpdate)
+                {
+                    _lastUpdate = value;
+                    NotifyPropertyChanged();
+                }
+            }
+        }
+
+        #endregion
+
+        #region 远程配置文件相关设置
+
+        public bool IsRemote { get; set; } = false;
+
+        public string Host { get; set; } = "";
+
+        public string Url { get; set; } = "";
+
+        public int UpdateInterval { get; set; } = 0;
+
+        #endregion
+
+        #region 内置方法
+
+        /// <summary>
+        /// 更新配置文件
+        /// </summary>
+        public bool Update()
+        {
+            // 重新获取远程配置文件
+            if (Type == ProfileType.Remote)
+            {
+                try
+                {
+                    var req = WebUtil.CreateRequest(Url);
+                    string yamlText = string.Empty;
+                    var result = WebUtil.DownloadString(req, out var rep);
+                    if (rep.StatusCode == HttpStatusCode.OK)
+                    {
+                        if (string.IsNullOrWhiteSpace(Name) || string.IsNullOrWhiteSpace(FileName))
+                        {
+                            if (!string.IsNullOrWhiteSpace(rep.Headers["Content-Disposition"]))
+                            {
+                                Name = rep.Headers["Content-Disposition"].Replace("attachment; filename=", String.Empty).Replace("\"", String.Empty);
+                                FileName = Path.Combine(Utils.Utils.GetClashProfilesDir(), Name);
+                            }
+                            else
+                            {
+                                Name = $"Clash_{DateTimeOffset.Now.ToUnixTimeSeconds()}.yaml";
+                                FileName = Path.Combine(Utils.Utils.GetClashProfilesDir(), Name);
+                            }
+                        }
+                        yamlText = result;
+                        File.WriteAllText(FileName, yamlText);
+                        SetProfileDetail(GetProfileDetail(yamlText));
+                        LastUpdate = DateTime.Now;
+                        return true;
+                    }
+                    else
+                    {
+                        // 下载失败了呢
+                        return false;
+                    }
+                }
+                catch
+                {
+                    return false;
+                }
+            }
+            else
+            {
+                try
+                {
+                    // 对于本地文件,我们只重新读取详细统计数据
+                    SetProfileDetail(GetProfileDetail(File.ReadAllText(FileName)));
+                    // 设置上次更新时间
+                    LastUpdate = DateTime.Now;
+                    // 返回成功
+                    return true;
+                }
+                catch
+                {
+                    return false;
+                }
+            }
+        }
+
+        /// <summary>
+        /// 设置配置文件详细
+        /// </summary>
+        /// <param name="profileDetail"></param>
+        public void SetProfileDetail(ProfileDetail profileDetail)
+        {
+            ProxyCount = profileDetail.ProxyCount;
+            GroupCount = profileDetail.GroupCount;
+            RuleCount = profileDetail.RuleCount;
+        }
+
+        public ProfileItem Copy(string destFileName)
+        {
+            string destPath = Path.Combine(Utils.Utils.GetClashProfilesDir(), destFileName); ;
+            File.Copy(FileName, destPath);
+            ProfileItem profileItem = new ProfileItem
+            {
+                Name = destFileName,
+                FileName = destPath,
+                ProxyCount = ProxyCount,
+                GroupCount = GroupCount,
+                RuleCount = RuleCount,
+                Type = Type,
+                Host = Host,
+                Url = Url,
+                LastUpdate = DateTime.Now
+            };
+            return profileItem;
+        }
+
+        #endregion
+    }
+}

+ 105 - 0
ClashDotNetFramework/Models/Items/ProxyItem.cs

@@ -0,0 +1,105 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+using System.Runtime.CompilerServices;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace ClashDotNetFramework.Models.Items
+{
+    public class ProxyItem : INotifyPropertyChanged
+    {
+        #region 属性改变事件
+
+        public event PropertyChangedEventHandler PropertyChanged;
+
+        private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
+        {
+            if (PropertyChanged != null)
+            {
+                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
+            }
+        }
+
+        #endregion
+
+        #region 内部变量
+
+        private long _latency { get; set; } = -2;
+
+        private bool _selected { get; set; }
+
+        #endregion
+
+        #region 通用设置
+
+        /// <summary>
+        /// 代理名称
+        /// </summary>
+        public string Name { get; set; }
+
+        /// <summary>
+        /// 代理类型
+        /// </summary>
+        public string Type { get; set; }
+
+        /// <summary>
+        /// 属于哪个Group (Selector等等)
+        /// </summary>
+        public string Group { get; set; }
+
+        /// <summary>
+        /// 代理延迟
+        /// -1 代表超时
+        /// -2 代表未测试
+        /// </summary>
+        public long Latency
+        {
+            get
+            {
+                return _latency;
+            }
+
+            set
+            {
+                if (value != this._latency)
+                {
+                    this._latency = value;
+                    NotifyPropertyChanged();
+                }
+            }
+        }
+
+        /// <summary>
+        /// 是否被选择
+        /// </summary>
+        public bool IsSelected
+        {
+            get
+            {
+                return this._selected;
+            }
+
+            set
+            {
+                if (value != this._selected)
+                {
+                    this._selected = value;
+                    NotifyPropertyChanged();
+                }
+            }
+        }
+
+        #endregion
+
+        #region 公开函数
+
+        public void NotifyBackgroundChange()
+        {
+            NotifyPropertyChanged("IsSelected");
+        }
+
+        #endregion
+    }
+}

+ 68 - 0
ClashDotNetFramework/Models/LanguageResourceDictionary.cs

@@ -0,0 +1,68 @@
+using ClashDotNetFramework.Models.Enums;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+
+namespace ClashDotNetFramework.Models
+{
+    public class LanguageResourceDictionary : ResourceDictionary
+    {
+        private Uri _englishSource;
+        private Uri _chineseSource;
+        private Uri _japaneseSource;
+
+        public Uri EnglishSource
+        {
+            get { return _englishSource; }
+            set
+            {
+                _englishSource = value;
+                UpdateSource();
+            }
+        }
+
+        public Uri ChineseSource
+        {
+            get { return _chineseSource; }
+            set
+            {
+                _chineseSource = value;
+                UpdateSource();
+            }
+        }
+
+        public Uri JapaneseSource
+        {
+            get { return _japaneseSource; }
+            set
+            {
+                _japaneseSource = value;
+                UpdateSource();
+            }
+        }
+
+        public void UpdateSource()
+        {
+            Uri source = null;
+            switch (Global.Settings.Language)
+            {
+                case LanguageType.English:
+                    source = EnglishSource;
+                    break;
+                case LanguageType.Chinese:
+                    source = ChineseSource;
+                    break;
+                case LanguageType.Japanese:
+                    source = JapaneseSource;
+                    break;
+                default:
+                    break;
+            }
+            if (source != null && base.Source != source)
+                base.Source = source;
+        }
+    }
+}

+ 16 - 0
ClashDotNetFramework/Models/Responses/AdvertisementResponse.cs

@@ -0,0 +1,16 @@
+using ClashDotNetFramework.Models.Share;
+using Newtonsoft.Json;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace ClashDotNetFramework.Models.Responses
+{
+    public class AdvertisementResponse
+    {
+        [JsonProperty("airports")]
+        public List<AirportData> Airports { get; set; }
+    }
+}

+ 104 - 0
ClashDotNetFramework/Models/Setting.cs

@@ -0,0 +1,104 @@
+using ClashDotNetFramework.Models.Enums;
+using ClashDotNetFramework.Models.Items;
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace ClashDotNetFramework.Models
+{
+    public class Setting
+    {
+        /// <summary>
+        /// 配置列表
+        /// </summary>
+        public ObservableCollection<ProfileItem> Profile = new ObservableCollection<ProfileItem>();
+
+        #region General
+
+        /// <summary>
+        /// 语言类型
+        /// </summary>
+        public LanguageType Language = LanguageType.English;
+
+        /// <summary>
+        /// 主题类型
+        /// </summary>
+        public ThemeType Theme = ThemeType.Modern;
+
+        /// <summary>
+        /// 开机启动
+        /// </summary>
+        public bool StartAfterSystemBoot = false;
+
+        /// <summary>
+        /// 系统代理
+        /// </summary>
+        public bool SystemProxy = false;
+
+        /// <summary>
+        /// 进程代理
+        /// </summary>
+        public bool ProcessProxy = false;
+
+        /// <summary>
+        /// Http请求超时时间
+        /// </summary>
+        public int RequestTimeout = 20000;
+
+        /// <summary>
+        /// 最多记录多少条日志
+        /// </summary>
+        public int MaximumLog = 25;
+
+        /// <summary>
+        /// MMDB更新Url
+        /// </summary>
+        public string MMDBUpdateUrl = "https://github.com/alecthw/mmdb_china_ip_list/releases/download/20210315/china_ip_list.mmdb";
+
+        #endregion
+
+        #region Proxies
+
+        /// <summary>
+        /// 延迟测试Url
+        /// </summary>
+        public string LatencyTestUrl = "http://www.gstatic.com/generate_204";
+
+        /// <summary>
+        /// 延迟测试超时时间 (ms)
+        /// </summary>
+        public int LatencyTestTimeout = 3000;
+
+        #endregion
+
+        #region NetFilter
+
+        /// <summary>
+        /// 重定向流量类型
+        /// 取值 TCP, UDP
+        /// </summary>
+        public List<string> RedirectTraffic { get; } = new List<string>();
+
+        /// <summary>
+        /// 进程代理模式
+        /// 取值 White(白名单), Black(黑名单)
+        /// </summary>
+        public readonly string ProcessMode = "White";
+
+        /// <summary>
+        /// 进程模式代理软件
+        /// </summary>
+        public List<string> Processes { get; } = new List<string>();
+
+        /// <summary>
+        /// 进程模式代理绕过类型
+        /// 取值 LOCAL(本地回环), LAN(局域网)
+        /// </summary>
+        public List<string> BypassType { get; } = new List<string>();
+
+        #endregion
+    }
+}

+ 18 - 0
ClashDotNetFramework/Models/Share/AirportData.cs

@@ -0,0 +1,18 @@
+using Newtonsoft.Json;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace ClashDotNetFramework.Models.Share
+{
+    public class AirportData
+    {
+        [JsonProperty("url")]
+        public string Url { get; set; }
+
+        [JsonProperty("image")]
+        public string Image { get; set; }
+    }
+}

+ 26 - 0
ClashDotNetFramework/Models/Structs/Range.cs

@@ -0,0 +1,26 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace ClashDotNetFramework.Models.Structs
+{
+    public readonly struct Range
+    {
+        public int Start { get; }
+
+        public int End { get; }
+
+        public Range(int start, int end)
+        {
+            Start = start;
+            End = end;
+        }
+
+        public bool InRange(int num)
+        {
+            return Start <= num && num <= End;
+        }
+    }
+}

+ 68 - 0
ClashDotNetFramework/Models/ThemeResourceDictionary.cs

@@ -0,0 +1,68 @@
+using ClashDotNetFramework.Models.Enums;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+
+namespace ClashDotNetFramework.Models
+{
+    public class ThemeResourceDictionary : ResourceDictionary
+    {
+        private Uri _classicSource;
+        private Uri _modernSource;
+        private Uri _darkSource;
+
+        public Uri ClassicSource
+        {
+            get { return _classicSource; }
+            set
+            {
+                _classicSource = value;
+                UpdateSource();
+            }
+        }
+
+        public Uri ModernSource
+        {
+            get { return _modernSource; }
+            set
+            {
+                _modernSource = value;
+                UpdateSource();
+            }
+        }
+
+        public Uri DarkSource
+        {
+            get { return _darkSource; }
+            set
+            {
+                _darkSource = value;
+                UpdateSource();
+            }
+        }
+
+        public void UpdateSource()
+        {
+            Uri source = null;
+            switch (Global.Settings.Theme)
+            {
+                case ThemeType.Classic:
+                    source = ClassicSource;
+                    break;
+                case ThemeType.Modern:
+                    source = ModernSource;
+                    break;
+                case ThemeType.Dark:
+                    source = DarkSource;
+                    break;
+                default:
+                    break;
+            }
+            if (source != null && base.Source != source)
+                base.Source = source;
+        }
+    }
+}