diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index a4e7d860..645a1981 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 = 596; + CURRENT_PROJECT_VERSION = 597; DEVELOPMENT_TEAM = BA88US33G6; ENABLE_BITCODE = NO; INFOPLIST_FILE = "Runner/Info-$(CONFIGURATION).plist"; @@ -596,7 +596,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0.596; + MARKETING_VERSION = 1.0.597; 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 = 596; + CURRENT_PROJECT_VERSION = 597; DEVELOPMENT_TEAM = BA88US33G6; ENABLE_BITCODE = NO; INFOPLIST_FILE = "Runner/Info-$(CONFIGURATION).plist"; @@ -730,7 +730,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0.596; + MARKETING_VERSION = 1.0.597; 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 = 596; + CURRENT_PROJECT_VERSION = 597; DEVELOPMENT_TEAM = BA88US33G6; ENABLE_BITCODE = NO; INFOPLIST_FILE = "Runner/Info-$(CONFIGURATION).plist"; @@ -758,7 +758,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0.596; + MARKETING_VERSION = 1.0.597; 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 = 596; + CURRENT_PROJECT_VERSION = 597; 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.596; + MARKETING_VERSION = 1.0.597; 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 = 596; + CURRENT_PROJECT_VERSION = 597; 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.596; + MARKETING_VERSION = 1.0.597; 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 = 596; + CURRENT_PROJECT_VERSION = 597; 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.596; + MARKETING_VERSION = 1.0.597; 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 = 596; + CURRENT_PROJECT_VERSION = 597; DEVELOPMENT_ASSET_PATHS = ""; DEVELOPMENT_TEAM = BA88US33G6; ENABLE_PREVIEWS = YES; @@ -902,7 +902,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0.596; + MARKETING_VERSION = 1.0.597; 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 = 596; + CURRENT_PROJECT_VERSION = 597; DEVELOPMENT_ASSET_PATHS = ""; DEVELOPMENT_TEAM = BA88US33G6; ENABLE_PREVIEWS = YES; @@ -943,7 +943,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0.596; + MARKETING_VERSION = 1.0.597; 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 = 596; + CURRENT_PROJECT_VERSION = 597; DEVELOPMENT_ASSET_PATHS = ""; DEVELOPMENT_TEAM = BA88US33G6; ENABLE_PREVIEWS = YES; @@ -981,7 +981,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0.596; + MARKETING_VERSION = 1.0.597; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.WatchEnd; PRODUCT_NAME = ServerBox; diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift index 54dd851b..cb9294fa 100644 --- a/ios/Runner/AppDelegate.swift +++ b/ios/Runner/AppDelegate.swift @@ -4,22 +4,30 @@ import Flutter @UIApplicationMain @objc class AppDelegate: FlutterAppDelegate { - override func application( - _ application: UIApplication, - didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? - ) -> Bool { - GeneratedPluginRegistrant.register(with: self) + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + GeneratedPluginRegistrant.register(with: self) - let controller : FlutterViewController = window?.rootViewController as! FlutterViewController - let methodChannel = FlutterMethodChannel(name: "tech.lolli.toolbox/home_widget", binaryMessenger: controller.binaryMessenger) - methodChannel.setMethodCallHandler({ - (call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in - if call.method == "update" { - if #available(iOS 14.0, *) { - WidgetCenter.shared.reloadTimelines(ofKind: "StatusWidget") + let controller : FlutterViewController = window?.rootViewController as! FlutterViewController + let methodChannel = FlutterMethodChannel(name: "tech.lolli.toolbox/home_widget", binaryMessenger: controller.binaryMessenger) + methodChannel.setMethodCallHandler({(call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in + if call.method == "update" { + if #available(iOS 14.0, *) { + WidgetCenter.shared.reloadTimelines(ofKind: "StatusWidget") + } + } + }) + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } + + override func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool { + if url.scheme == "https" || url.scheme == "http" { + UIApplication.shared.open(url) + } else { + // Pass } - } - }) - return super.application(application, didFinishLaunchingWithOptions: launchOptions) - } + return true + } } diff --git a/ios/Runner/Utils.swift b/ios/Runner/Utils.swift index 9df0472a..cf4e1512 100644 --- a/ios/Runner/Utils.swift +++ b/ios/Runner/Utils.swift @@ -9,6 +9,8 @@ import Foundation let accessoryKey = "accessory_widget_url" +let helpUrl = URL(string: "https://github.com/lollipopkit/flutter_server_box/wiki#home-widget--watchos-app")! + extension Date { func toStr() -> String { let formatter = DateFormatter() @@ -19,9 +21,14 @@ extension Date { } } +enum ErrType: Error { + case url(String) + case http(String) +} + enum ContentState { case loading - case error(String) + case error(ErrType) case normal(Status) } diff --git a/ios/StatusWidget/StatusWidget.swift b/ios/StatusWidget/StatusWidget.swift index d18d8ed8..b2dcc48f 100644 --- a/ios/StatusWidget/StatusWidget.swift +++ b/ios/StatusWidget/StatusWidget.swift @@ -36,23 +36,58 @@ struct Provider: IntentTimelineProvider { } let currentDate = Date() - let refreshDate = Calendar.current.date(byAdding: .minute, value: 30, to: currentDate)! - StatusLoader.fetch(url: url) { result in - let entry: SimpleEntry - switch result { - case .success(let status): - entry = SimpleEntry( - date: currentDate, - configuration: configuration, - state: .normal(status) - ) - case .failure(let err): - entry = SimpleEntry(date: currentDate, configuration: configuration, state: .error(err.localizedDescription)) - } + let refreshDate = Calendar.current.date(byAdding: .minute, value: 15, to: currentDate)! + fetch(url: url) { result in + let entry: SimpleEntry = SimpleEntry( + date: currentDate, + configuration: configuration, + state: result + ) let timeline = Timeline(entries: [entry], policy: .after(refreshDate)) completion(timeline) } } + + func fetch(url: String?, completion: @escaping (ContentState) -> Void) { + guard let url = url, url.count >= 12 else { + completion(.error(.url("url is nil OR len < 12"))) + return + } + guard let url = URL(string: url) else { + completion(.error(.url("parse url failed"))) + return + } + completion(.loading) + + UserDefaults.standard.set(url.absoluteString, forKey: accessoryKey) + + let task = URLSession.shared.dataTask(with: url) { (data, response, error) in + if error != nil { + completion(.error(.http(error?.localizedDescription ?? "unknown http err"))) + return + } + guard let data = data else { + completion(.error(.http("empty http data"))) + return + } + let jsonAll = try! JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] ?? [:] + let code = jsonAll["code"] as? Int ?? 1 + if (code != 0) { + let msg = jsonAll["msg"] as? String ?? "Empty err" + completion(.error(.http(msg))) + return + } + + let json = jsonAll["data"] as? [String: Any] ?? [:] + let name = json["name"] as? String ?? "" + let disk = json["disk"] as? String ?? "" + let cpu = json["cpu"] as? String ?? "" + let mem = json["mem"] as? String ?? "" + let net = json["net"] as? String ?? "" + completion(.normal(Status(name: name, cpu: cpu, mem: mem, disk: disk, net: net))) + } + task.resume() + } } struct SimpleEntry: TimelineEntry { @@ -70,8 +105,25 @@ struct StatusWidgetEntryView : View { switch entry.state { case .loading: ProgressView().widgetBackground() - case .error(let descriotion): - Text(descriotion).widgetBackground() + case .error(let err): + switch err { + case .http(let description): + VStack(alignment: .center) { + Text(description) + if #available(iOS 17.0, *) { + Button(intent: RefreshIntent()) { + Image(systemName: "arrow.clockwise") + .resizable() + .frame(width: 10, height: 12.7) + } + tint(.gray) + } + } + .widgetBackground() + case .url(let _): + Link("Open wiki ⬅️", destination: helpUrl) + .widgetBackground() + } case .normal(let data): let sumColor: Color = .primary.opacity(0.7) switch family { @@ -133,14 +185,8 @@ extension View { // 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) { - // If it's show in StandBy - if showsWidgetContainerBackground { - backgroundView - } else { - self - } + backgroundView } } else { background(backgroundView) @@ -161,20 +207,15 @@ struct StatusWidget: Widget { let kind: String = "StatusWidget" var body: some WidgetConfiguration { - if #available(iOSApplicationExtension 16.0, *) { - IntentConfiguration(kind: kind, intent: ConfigurationIntent.self, provider: Provider()) { entry in - StatusWidgetEntryView(entry: entry) - } - .configurationDisplayName("Status") - .description("Status of your servers.") - .supportedFamilies([.systemSmall, .accessoryRectangular, .accessoryInline]) + let cfg = IntentConfiguration(kind: kind, intent: ConfigurationIntent.self, provider: Provider()) { entry in + StatusWidgetEntryView(entry: entry) + } + .configurationDisplayName("Status") + .description("Status of your servers.") + if #available(iOSApplicationExtension 16.0, *) { + return cfg.supportedFamilies([.systemSmall, .accessoryRectangular, .accessoryInline]) } else { - IntentConfiguration(kind: kind, intent: ConfigurationIntent.self, provider: Provider()) { entry in - StatusWidgetEntryView(entry: entry) - } - .configurationDisplayName("Status") - .description("Status of your servers.") - .supportedFamilies([.systemSmall]) + return cfg.supportedFamilies([.systemSmall]) } } } @@ -186,59 +227,6 @@ struct StatusWidget_Previews: PreviewProvider { } } -struct StatusLoader { - 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 - } - guard let url = URL(string: url) else { - 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!)) - return - } - guard let data = data else { - completion(.failure(NSError(domain: domain, code: 2, userInfo: [NSLocalizedDescriptionKey: "empty network data."]))) - return - } - switch getStatus(fromData: data) { - case .success(let status): - completion(.success(status)) - case .failure(let error): - completion(.failure(error)) - } - } - task.resume() - } - - static func getStatus(fromData data: Foundation.Data) -> Result { - let jsonAll = try! JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] ?? [:] - let code = jsonAll["code"] as? Int ?? 1 - if (code != 0) { - switch (code) { - default: - let msg = jsonAll["msg"] as? String ?? "Empty err" - return .failure(NSError(domain: domain, code: code, userInfo: [NSLocalizedDescriptionKey: msg])) - } - } - - let json = jsonAll["data"] as? [String: Any] ?? [:] - let name = json["name"] as? String ?? "" - let disk = json["disk"] as? String ?? "" - let cpu = json["cpu"] as? String ?? "" - let mem = json["mem"] as? String ?? "" - let net = json["net"] as? String ?? "" - return .success(Status(name: name, cpu: cpu, mem: mem, disk: disk, net: net)) - } -} - struct DetailItem: View { let icon: String let text: String @@ -291,7 +279,6 @@ struct RefreshIntent: AppIntent { static var description = IntentDescription("Refresh status.") func perform() async throws -> some IntentResult { - return .result() } } diff --git a/ios/WatchApp/ContentView.swift b/ios/WatchApp/ContentView.swift index 2033d048..d6cd94f4 100644 --- a/ios/WatchApp/ContentView.swift +++ b/ios/WatchApp/ContentView.swift @@ -41,13 +41,19 @@ struct PageView: View { ProgressView().padding().onAppear { getStatus(url: url!) } - case .error(let string): - VStack { - Spacer() - Image(systemName: "exclamationmark.triangle.fill").foregroundColor(.red) - Spacer() - Text(string).padding() - Spacer() + case .error(let err): + switch err { + case .http(let description): + VStack(alignment: .center) { + Text(description) + Button(action: { + state = .loading + }){ + Image(systemName: "arrow.clockwise") + }.buttonStyle(.plain) + } + case .emptyUrl, .invalidUrl: + Link("View help", destination: helpUrl) } case .normal(let status): VStack(alignment: .leading) { @@ -73,33 +79,33 @@ struct PageView: View { func getStatus(url: String) { state = .loading if url.count < 12 { - state = .error("url is too short") + state = .error(.url("url len < 12")) return } guard let url = URL(string: url) else { - state = .error("url is invalid") + state = .error(.url("parse url failed")) return } let task = URLSession.shared.dataTask(with: url) { (data, response, error) in guard error == nil else { - state = .error(error!.localizedDescription) + state = .error(.http(error!.localizedDescription)) return } guard let data = data else { - state = .error("data is nil") + state = .error(.http("empty data")) return } guard let jsonAll = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] else { - state = .error("json parse fail") + state = .error(.http("json parse fail")) return } guard let code = jsonAll["code"] as? Int else { - state = .error("code is nil") + state = .error(.http("code is nil")) return } if (code != 0) { let msg = jsonAll["msg"] as? String ?? "" - state = .error(msg) + state = .error(.http(msg)) return } diff --git a/lib/data/res/build_data.dart b/lib/data/res/build_data.dart index 54af6069..d664d5f3 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 = 596; + static const int build = 597; static const String engine = "3.13.7"; - static const String buildAt = "2023-10-14 23:24:14"; - static const int modifications = 1; + static const String buildAt = "2023-10-15 13:38:49"; + static const int modifications = 9; static const int script = 21; }