diff --git a/README.md b/README.md index f7625a08b..4fbabf695 100644 --- a/README.md +++ b/README.md @@ -47,26 +47,26 @@ If ServerBox app has any bug, please open an [issue](https://github.com/lollipop
- + - + - +
- + - + - +
diff --git a/README_zh.md b/README_zh.md index a1058792b..5cfad8726 100644 --- a/README_zh.md +++ b/README_zh.md @@ -47,26 +47,26 @@
- + - + - +
- + - + - +
diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 46db87b9d..0a6fa9f6a 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -586,7 +586,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; - CURRENT_PROJECT_VERSION = 588; + CURRENT_PROJECT_VERSION = 589; DEVELOPMENT_TEAM = BA88US33G6; ENABLE_BITCODE = NO; INFOPLIST_FILE = "Runner/Info-$(CONFIGURATION).plist"; @@ -596,7 +596,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0.588; + MARKETING_VERSION = 1.0.589; PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; @@ -720,7 +720,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; - CURRENT_PROJECT_VERSION = 588; + CURRENT_PROJECT_VERSION = 589; DEVELOPMENT_TEAM = BA88US33G6; ENABLE_BITCODE = NO; INFOPLIST_FILE = "Runner/Info-$(CONFIGURATION).plist"; @@ -730,7 +730,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0.588; + MARKETING_VERSION = 1.0.589; PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; @@ -748,7 +748,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; - CURRENT_PROJECT_VERSION = 588; + CURRENT_PROJECT_VERSION = 589; DEVELOPMENT_TEAM = BA88US33G6; ENABLE_BITCODE = NO; INFOPLIST_FILE = "Runner/Info-$(CONFIGURATION).plist"; @@ -758,7 +758,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0.588; + MARKETING_VERSION = 1.0.589; PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; @@ -779,7 +779,7 @@ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 588; + CURRENT_PROJECT_VERSION = 589; DEVELOPMENT_TEAM = BA88US33G6; GCC_C_LANGUAGE_STANDARD = gnu11; GENERATE_INFOPLIST_FILE = YES; @@ -792,7 +792,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 1.0.588; + MARKETING_VERSION = 1.0.589; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.StatusWidget; @@ -818,7 +818,7 @@ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 588; + CURRENT_PROJECT_VERSION = 589; DEVELOPMENT_TEAM = BA88US33G6; GCC_C_LANGUAGE_STANDARD = gnu11; GENERATE_INFOPLIST_FILE = YES; @@ -831,7 +831,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 1.0.588; + MARKETING_VERSION = 1.0.589; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.StatusWidget; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -854,7 +854,7 @@ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 588; + CURRENT_PROJECT_VERSION = 589; DEVELOPMENT_TEAM = BA88US33G6; GCC_C_LANGUAGE_STANDARD = gnu11; GENERATE_INFOPLIST_FILE = YES; @@ -867,7 +867,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 1.0.588; + MARKETING_VERSION = 1.0.589; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.StatusWidget; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -890,7 +890,7 @@ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 588; + CURRENT_PROJECT_VERSION = 589; DEVELOPMENT_ASSET_PATHS = ""; DEVELOPMENT_TEAM = BA88US33G6; ENABLE_PREVIEWS = YES; @@ -902,7 +902,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0.588; + MARKETING_VERSION = 1.0.589; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.WatchEnd; @@ -931,7 +931,7 @@ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 588; + CURRENT_PROJECT_VERSION = 589; DEVELOPMENT_ASSET_PATHS = ""; DEVELOPMENT_TEAM = BA88US33G6; ENABLE_PREVIEWS = YES; @@ -943,7 +943,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0.588; + MARKETING_VERSION = 1.0.589; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.WatchEnd; PRODUCT_NAME = ServerBox; @@ -969,7 +969,7 @@ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 588; + CURRENT_PROJECT_VERSION = 589; DEVELOPMENT_ASSET_PATHS = ""; DEVELOPMENT_TEAM = BA88US33G6; ENABLE_PREVIEWS = YES; @@ -981,7 +981,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0.588; + MARKETING_VERSION = 1.0.589; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.WatchEnd; PRODUCT_NAME = ServerBox; diff --git a/ios/Runner/Utils.swift b/ios/Runner/Utils.swift index 814cb7a07..9df0472a9 100644 --- a/ios/Runner/Utils.swift +++ b/ios/Runner/Utils.swift @@ -7,6 +7,8 @@ import Foundation +let accessoryKey = "accessory_widget_url" + extension Date { func toStr() -> String { let formatter = DateFormatter() diff --git a/ios/StatusWidget/StatusWidget.swift b/ios/StatusWidget/StatusWidget.swift index 4900804ad..8743d9bdd 100644 --- a/ios/StatusWidget/StatusWidget.swift +++ b/ios/StatusWidget/StatusWidget.swift @@ -8,10 +8,11 @@ import WidgetKit import SwiftUI import Intents +import Foundation let demoStatus = Status(name: "Server", cpu: "31.7%", mem: "1.3g / 1.9g", disk: "7.1g / 30.0g", net: "712.3k / 1.2m") let domain = "com.lollipopkit.toolbox" -var url: String? +let bgColor = DynamicColor(dark: UIColor.black, light: UIColor.white) struct Provider: IntentTimelineProvider { func placeholder(in context: Context) -> SimpleEntry { @@ -24,11 +25,18 @@ struct Provider: IntentTimelineProvider { } func getTimeline(for configuration: ConfigurationIntent, in context: Context, completion: @escaping (Timeline) -> ()) { - url = configuration.url + var url = configuration.url + + @Environment(\.widgetFamily) var family: WidgetFamily + if #available(iOSApplicationExtension 16.0, *) { + if family == .accessoryInline || family == .accessoryRectangular { + url = UserDefaults.standard.string(forKey: accessoryKey) + } + } let currentDate = Date() let refreshDate = Calendar.current.date(byAdding: .minute, value: 30, to: currentDate)! - StatusLoader.fetch { result in + StatusLoader.fetch(url: url) { result in let entry: SimpleEntry switch result { case .success(let status): @@ -55,36 +63,61 @@ struct SimpleEntry: TimelineEntry { struct StatusWidgetEntryView : View { var entry: Provider.Entry + @Environment(\.widgetFamily) var family: WidgetFamily + var body: some View { switch entry.state { case .loading: - ProgressView().padding() + ProgressView().widgetBackground() case .error(let descriotion): - Text(descriotion).padding(.all, 13) + Text(descriotion).widgetBackground() case .normal(let data): let sumColor: Color = .primary.opacity(0.7) - VStack(alignment: .leading, spacing: 3.7) { - Text(data.name).font(.system(.title3, design: .monospaced)) - Spacer() - DetailItem(icon: "cpu", text: data.cpu, color: sumColor) - DetailItem(icon: "memorychip", text: data.mem, color: sumColor) - DetailItem(icon: "externaldrive", text: data.disk, color: sumColor) - DetailItem(icon: "network", text: data.net, color: sumColor) - Spacer() - DetailItem(icon: "clock", text: entry.date.toStr(), color: sumColor) + switch family { + case .accessoryRectangular: + VStack(alignment: .leading, spacing: 2) { + HStack { + Text(data.name) + .font(.system(size: 15, weight: .semibold, design: .monospaced)) + Spacer() + CirclePercent(percent: data.cpu) + .padding(.top, 3) + .padding(.trailing, 3) + } + .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading) + Spacer() + DetailItem(icon: "memorychip", text: data.mem, color: sumColor) + DetailItem(icon: "externaldrive", text: data.disk, color: sumColor) + DetailItem(icon: "network", text: data.net, color: sumColor) + } + .widgetBackground() + case .accessoryInline: + Text("\(data.name) \(data.cpu)").widgetBackground() + default: + VStack(alignment: .leading, spacing: 3.7) { + Text(data.name).font(.system(.title3, design: .monospaced)) + Spacer() + DetailItem(icon: "cpu", text: data.cpu, color: sumColor) + DetailItem(icon: "memorychip", text: data.mem, color: sumColor) + DetailItem(icon: "externaldrive", text: data.disk, color: sumColor) + DetailItem(icon: "network", text: data.net, color: sumColor) + Spacer() + DetailItem(icon: "clock", text: entry.date.toStr(), color: sumColor) + .padding(.bottom, 3) + } + .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading) + .autoPadding() + .widgetBackground() } - .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading) - .autoPadding() - .widgetBackground() } } } extension View { - // Set bg to Color.black in Night, Color.white in Day @ViewBuilder func widgetBackground() -> some View { - let backgroundView = Color(UIColor.systemBackground) + // Set bg to black in Night, white in Day + let backgroundView = Color(bgColor.resolve()) if #available(iOS 17.0, *) { @Environment(\.showsWidgetContainerBackground) var showsWidgetContainerBackground containerBackground(for: .widget) { @@ -103,10 +136,6 @@ extension View { // iOS 17 will auto add a SafeArea, so when iOS < 17, add .padding(.all, 17) func autoPadding() -> some View { if #available(iOS 17.0, *) { - @Environment(\.showsWidgetContainerBackground) var showsWidgetContainerBackground - if (showsWidgetContainerBackground) { - return self.padding(.all, 17) - } return self } else { return self.padding(.all, 17) @@ -123,11 +152,12 @@ struct StatusWidget: Widget { } .configurationDisplayName("Status") .description("Status of your servers.") - .supportedFamilies([.systemSmall]) - if #available(iOS 16.0, *) { - let _ = cfg.supportedFamilies([.accessoryInline, .accessoryCircular]) + + if #available(iOSApplicationExtension 16.0, *) { + return cfg.supportedFamilies([.systemSmall, .accessoryRectangular, .accessoryInline]) + } else { + return cfg.supportedFamilies([.systemSmall]) } - return cfg } } @@ -139,7 +169,7 @@ struct StatusWidget_Previews: PreviewProvider { } struct StatusLoader { - static func fetch(completion: @escaping (Result) -> Void) { + static func fetch(url: String?, completion: @escaping (Result) -> Void) { guard let url = url, url.count >= 12 else { completion(.failure(NSError(domain: domain, code: 0, userInfo: [NSLocalizedDescriptionKey: "https://github.com/lollipopkit/server_box_monitor/wiki"]))) return @@ -148,6 +178,9 @@ struct StatusLoader { completion(.failure(NSError(domain: domain, code: 1, userInfo: [NSLocalizedDescriptionKey: "url is invalid"]))) return } + + UserDefaults.standard.set(url.absoluteString, forKey: accessoryKey) + let task = URLSession.shared.dataTask(with: url) { (data, response, error) in if error != nil { completion(.failure(error!)) @@ -173,7 +206,7 @@ struct StatusLoader { if (code != 0) { switch (code) { default: - let msg = jsonAll["msg"] as? String ?? "" + let msg = jsonAll["msg"] as? String ?? "Empty err" return .failure(NSError(domain: domain, code: code, userInfo: [NSLocalizedDescriptionKey: msg])) } } @@ -202,3 +235,49 @@ struct DetailItem: View { } } } + +struct DetailItemSmall: View { + let icon: String + let text: String + let color: Color + + var body: some View { + HStack(spacing: 6.7) { + Image(systemName: icon).resizable().foregroundColor(color).frame(width: 11, height: 11, alignment: .center) + Text(text) + .font(.system(size: 11, design: .monospaced)) + .foregroundColor(color) + } + } +} + +// 空心圆,显示百分比 +struct CirclePercent: View { + // eg: 31.7% + let percent: String + + var body: some View { + // 31.7% -> 0.317 + let percentD = Double(percent.trimmingCharacters(in: .init(charactersIn: "%"))) + let double = (percentD ?? 0) / 100 + Circle() + .trim(from: 0, to: CGFloat(double)) + .stroke(Color.primary, lineWidth: 3) + .animation(.easeInOut(duration: 0.5)) + } +} + +struct DynamicColor { + let dark: UIColor + let light: UIColor + + func resolve() -> UIColor { + if #available(iOS 13, *) { // 版本号大于等于13 + return UIColor { (traitCollection: UITraitCollection) -> UIColor in + return traitCollection.userInterfaceStyle == UIUserInterfaceStyle.dark ? + self.dark : self.light + } + } + return self.light + } +} diff --git a/lib/data/model/server/system.dart b/lib/data/model/server/system.dart index ce41cada8..4455d7faf 100644 --- a/lib/data/model/server/system.dart +++ b/lib/data/model/server/system.dart @@ -9,15 +9,16 @@ enum SystemType { const SystemType._(this.value); - static SystemType? parse(String? value) { - if (value == null) return null; - switch (value) { + static SystemType parse(String value) { + switch (value.trim()) { case linuxSign: return SystemType.linux; case bsdSign: return SystemType.bsd; + default: + // Fallback to linux + return SystemType.linux; } - return null; } bool isSegmentsLenMatch(int len) => len == segmentsLen; diff --git a/lib/data/provider/server.dart b/lib/data/provider/server.dart index 639e49239..798394709 100644 --- a/lib/data/provider/server.dart +++ b/lib/data/provider/server.dart @@ -329,10 +329,10 @@ class ServerProvider extends ChangeNotifier { } final systemType = SystemType.parse(segments[0]); - if (systemType == null || !systemType.isSegmentsLenMatch(segments.length)) { + if (!systemType.isSegmentsLenMatch(segments.length)) { _limiter.inc(sid); s.status.failedInfo = - 'Segments not match: expect ${systemType?.segmentsLen}, got ${segments.length}'; + 'Segments not match: expect ${systemType.segmentsLen}, got ${segments.length}'; _setServerState(s, ServerState.failed); return; } diff --git a/lib/data/res/build_data.dart b/lib/data/res/build_data.dart index 17d2c5a97..7f9339e2b 100644 --- a/lib/data/res/build_data.dart +++ b/lib/data/res/build_data.dart @@ -2,9 +2,9 @@ class BuildData { static const String name = "ServerBox"; - static const int build = 588; + static const int build = 589; static const String engine = "3.13.6"; - static const String buildAt = "2023-10-13 14:38:17"; - static const int modifications = 5; + static const String buildAt = "2023-10-14 12:09:35"; + static const int modifications = 6; static const int script = 21; }