opt.: ios home widget

This commit is contained in:
lollipopkit
2023-10-15 16:56:53 +08:00
parent 9cf9a6fbc5
commit 2e17054037
6 changed files with 149 additions and 141 deletions

View File

@@ -586,7 +586,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CURRENT_PROJECT_VERSION = 596; CURRENT_PROJECT_VERSION = 597;
DEVELOPMENT_TEAM = BA88US33G6; DEVELOPMENT_TEAM = BA88US33G6;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
INFOPLIST_FILE = "Runner/Info-$(CONFIGURATION).plist"; INFOPLIST_FILE = "Runner/Info-$(CONFIGURATION).plist";
@@ -596,7 +596,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 1.0.596; MARKETING_VERSION = 1.0.597;
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox; PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
@@ -720,7 +720,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CURRENT_PROJECT_VERSION = 596; CURRENT_PROJECT_VERSION = 597;
DEVELOPMENT_TEAM = BA88US33G6; DEVELOPMENT_TEAM = BA88US33G6;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
INFOPLIST_FILE = "Runner/Info-$(CONFIGURATION).plist"; INFOPLIST_FILE = "Runner/Info-$(CONFIGURATION).plist";
@@ -730,7 +730,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 1.0.596; MARKETING_VERSION = 1.0.597;
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox; PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
@@ -748,7 +748,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CURRENT_PROJECT_VERSION = 596; CURRENT_PROJECT_VERSION = 597;
DEVELOPMENT_TEAM = BA88US33G6; DEVELOPMENT_TEAM = BA88US33G6;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
INFOPLIST_FILE = "Runner/Info-$(CONFIGURATION).plist"; INFOPLIST_FILE = "Runner/Info-$(CONFIGURATION).plist";
@@ -758,7 +758,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 1.0.596; MARKETING_VERSION = 1.0.597;
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox; PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
@@ -779,7 +779,7 @@
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 596; CURRENT_PROJECT_VERSION = 597;
DEVELOPMENT_TEAM = BA88US33G6; DEVELOPMENT_TEAM = BA88US33G6;
GCC_C_LANGUAGE_STANDARD = gnu11; GCC_C_LANGUAGE_STANDARD = gnu11;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
@@ -792,7 +792,7 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
"@executable_path/../../Frameworks", "@executable_path/../../Frameworks",
); );
MARKETING_VERSION = 1.0.596; MARKETING_VERSION = 1.0.597;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.StatusWidget; PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.StatusWidget;
@@ -818,7 +818,7 @@
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 596; CURRENT_PROJECT_VERSION = 597;
DEVELOPMENT_TEAM = BA88US33G6; DEVELOPMENT_TEAM = BA88US33G6;
GCC_C_LANGUAGE_STANDARD = gnu11; GCC_C_LANGUAGE_STANDARD = gnu11;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
@@ -831,7 +831,7 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
"@executable_path/../../Frameworks", "@executable_path/../../Frameworks",
); );
MARKETING_VERSION = 1.0.596; MARKETING_VERSION = 1.0.597;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.StatusWidget; PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.StatusWidget;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
@@ -854,7 +854,7 @@
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 596; CURRENT_PROJECT_VERSION = 597;
DEVELOPMENT_TEAM = BA88US33G6; DEVELOPMENT_TEAM = BA88US33G6;
GCC_C_LANGUAGE_STANDARD = gnu11; GCC_C_LANGUAGE_STANDARD = gnu11;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
@@ -867,7 +867,7 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
"@executable_path/../../Frameworks", "@executable_path/../../Frameworks",
); );
MARKETING_VERSION = 1.0.596; MARKETING_VERSION = 1.0.597;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.StatusWidget; PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.StatusWidget;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
@@ -890,7 +890,7 @@
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 596; CURRENT_PROJECT_VERSION = 597;
DEVELOPMENT_ASSET_PATHS = ""; DEVELOPMENT_ASSET_PATHS = "";
DEVELOPMENT_TEAM = BA88US33G6; DEVELOPMENT_TEAM = BA88US33G6;
ENABLE_PREVIEWS = YES; ENABLE_PREVIEWS = YES;
@@ -902,7 +902,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 1.0.596; MARKETING_VERSION = 1.0.597;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.WatchEnd; PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.WatchEnd;
@@ -931,7 +931,7 @@
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 596; CURRENT_PROJECT_VERSION = 597;
DEVELOPMENT_ASSET_PATHS = ""; DEVELOPMENT_ASSET_PATHS = "";
DEVELOPMENT_TEAM = BA88US33G6; DEVELOPMENT_TEAM = BA88US33G6;
ENABLE_PREVIEWS = YES; ENABLE_PREVIEWS = YES;
@@ -943,7 +943,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 1.0.596; MARKETING_VERSION = 1.0.597;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.WatchEnd; PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.WatchEnd;
PRODUCT_NAME = ServerBox; PRODUCT_NAME = ServerBox;
@@ -969,7 +969,7 @@
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 596; CURRENT_PROJECT_VERSION = 597;
DEVELOPMENT_ASSET_PATHS = ""; DEVELOPMENT_ASSET_PATHS = "";
DEVELOPMENT_TEAM = BA88US33G6; DEVELOPMENT_TEAM = BA88US33G6;
ENABLE_PREVIEWS = YES; ENABLE_PREVIEWS = YES;
@@ -981,7 +981,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 1.0.596; MARKETING_VERSION = 1.0.597;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.WatchEnd; PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.WatchEnd;
PRODUCT_NAME = ServerBox; PRODUCT_NAME = ServerBox;

View File

@@ -4,22 +4,30 @@ import Flutter
@UIApplicationMain @UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate { @objc class AppDelegate: FlutterAppDelegate {
override func application( override func application(
_ application: UIApplication, _ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool { ) -> Bool {
GeneratedPluginRegistrant.register(with: self) GeneratedPluginRegistrant.register(with: self)
let controller : FlutterViewController = window?.rootViewController as! FlutterViewController let controller : FlutterViewController = window?.rootViewController as! FlutterViewController
let methodChannel = FlutterMethodChannel(name: "tech.lolli.toolbox/home_widget", binaryMessenger: controller.binaryMessenger) let methodChannel = FlutterMethodChannel(name: "tech.lolli.toolbox/home_widget", binaryMessenger: controller.binaryMessenger)
methodChannel.setMethodCallHandler({ methodChannel.setMethodCallHandler({(call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in
(call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in if call.method == "update" {
if call.method == "update" { if #available(iOS 14.0, *) {
if #available(iOS 14.0, *) { WidgetCenter.shared.reloadTimelines(ofKind: "StatusWidget")
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 true
}) }
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
} }

View File

@@ -9,6 +9,8 @@ import Foundation
let accessoryKey = "accessory_widget_url" let accessoryKey = "accessory_widget_url"
let helpUrl = URL(string: "https://github.com/lollipopkit/flutter_server_box/wiki#home-widget--watchos-app")!
extension Date { extension Date {
func toStr() -> String { func toStr() -> String {
let formatter = DateFormatter() let formatter = DateFormatter()
@@ -19,9 +21,14 @@ extension Date {
} }
} }
enum ErrType: Error {
case url(String)
case http(String)
}
enum ContentState { enum ContentState {
case loading case loading
case error(String) case error(ErrType)
case normal(Status) case normal(Status)
} }

View File

@@ -36,23 +36,58 @@ struct Provider: IntentTimelineProvider {
} }
let currentDate = Date() let currentDate = Date()
let refreshDate = Calendar.current.date(byAdding: .minute, value: 30, to: currentDate)! let refreshDate = Calendar.current.date(byAdding: .minute, value: 15, to: currentDate)!
StatusLoader.fetch(url: url) { result in fetch(url: url) { result in
let entry: SimpleEntry let entry: SimpleEntry = SimpleEntry(
switch result { date: currentDate,
case .success(let status): configuration: configuration,
entry = SimpleEntry( state: result
date: currentDate, )
configuration: configuration,
state: .normal(status)
)
case .failure(let err):
entry = SimpleEntry(date: currentDate, configuration: configuration, state: .error(err.localizedDescription))
}
let timeline = Timeline(entries: [entry], policy: .after(refreshDate)) let timeline = Timeline(entries: [entry], policy: .after(refreshDate))
completion(timeline) 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 { struct SimpleEntry: TimelineEntry {
@@ -70,8 +105,25 @@ struct StatusWidgetEntryView : View {
switch entry.state { switch entry.state {
case .loading: case .loading:
ProgressView().widgetBackground() ProgressView().widgetBackground()
case .error(let descriotion): case .error(let err):
Text(descriotion).widgetBackground() 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): case .normal(let data):
let sumColor: Color = .primary.opacity(0.7) let sumColor: Color = .primary.opacity(0.7)
switch family { switch family {
@@ -133,14 +185,8 @@ extension View {
// Set bg to black in Night, white in Day // Set bg to black in Night, white in Day
let backgroundView = Color(bgColor.resolve()) let backgroundView = Color(bgColor.resolve())
if #available(iOS 17.0, *) { if #available(iOS 17.0, *) {
@Environment(\.showsWidgetContainerBackground) var showsWidgetContainerBackground
containerBackground(for: .widget) { containerBackground(for: .widget) {
// If it's show in StandBy backgroundView
if showsWidgetContainerBackground {
backgroundView
} else {
self
}
} }
} else { } else {
background(backgroundView) background(backgroundView)
@@ -161,20 +207,15 @@ struct StatusWidget: Widget {
let kind: String = "StatusWidget" let kind: String = "StatusWidget"
var body: some WidgetConfiguration { var body: some WidgetConfiguration {
if #available(iOSApplicationExtension 16.0, *) { let cfg = IntentConfiguration(kind: kind, intent: ConfigurationIntent.self, provider: Provider()) { entry in
IntentConfiguration(kind: kind, intent: ConfigurationIntent.self, provider: Provider()) { entry in StatusWidgetEntryView(entry: entry)
StatusWidgetEntryView(entry: entry) }
} .configurationDisplayName("Status")
.configurationDisplayName("Status") .description("Status of your servers.")
.description("Status of your servers.") if #available(iOSApplicationExtension 16.0, *) {
.supportedFamilies([.systemSmall, .accessoryRectangular, .accessoryInline]) return cfg.supportedFamilies([.systemSmall, .accessoryRectangular, .accessoryInline])
} else { } else {
IntentConfiguration(kind: kind, intent: ConfigurationIntent.self, provider: Provider()) { entry in return cfg.supportedFamilies([.systemSmall])
StatusWidgetEntryView(entry: entry)
}
.configurationDisplayName("Status")
.description("Status of your servers.")
.supportedFamilies([.systemSmall])
} }
} }
} }
@@ -186,59 +227,6 @@ struct StatusWidget_Previews: PreviewProvider {
} }
} }
struct StatusLoader {
static func fetch(url: String?, completion: @escaping (Result<Status, Error>) -> 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<Status, Error> {
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 { struct DetailItem: View {
let icon: String let icon: String
let text: String let text: String
@@ -291,7 +279,6 @@ struct RefreshIntent: AppIntent {
static var description = IntentDescription("Refresh status.") static var description = IntentDescription("Refresh status.")
func perform() async throws -> some IntentResult { func perform() async throws -> some IntentResult {
return .result() return .result()
} }
} }

View File

@@ -41,13 +41,19 @@ struct PageView: View {
ProgressView().padding().onAppear { ProgressView().padding().onAppear {
getStatus(url: url!) getStatus(url: url!)
} }
case .error(let string): case .error(let err):
VStack { switch err {
Spacer() case .http(let description):
Image(systemName: "exclamationmark.triangle.fill").foregroundColor(.red) VStack(alignment: .center) {
Spacer() Text(description)
Text(string).padding() Button(action: {
Spacer() state = .loading
}){
Image(systemName: "arrow.clockwise")
}.buttonStyle(.plain)
}
case .emptyUrl, .invalidUrl:
Link("View help", destination: helpUrl)
} }
case .normal(let status): case .normal(let status):
VStack(alignment: .leading) { VStack(alignment: .leading) {
@@ -73,33 +79,33 @@ struct PageView: View {
func getStatus(url: String) { func getStatus(url: String) {
state = .loading state = .loading
if url.count < 12 { if url.count < 12 {
state = .error("url is too short") state = .error(.url("url len < 12"))
return return
} }
guard let url = URL(string: url) else { guard let url = URL(string: url) else {
state = .error("url is invalid") state = .error(.url("parse url failed"))
return return
} }
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
guard error == nil else { guard error == nil else {
state = .error(error!.localizedDescription) state = .error(.http(error!.localizedDescription))
return return
} }
guard let data = data else { guard let data = data else {
state = .error("data is nil") state = .error(.http("empty data"))
return return
} }
guard let jsonAll = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] else { 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 return
} }
guard let code = jsonAll["code"] as? Int else { guard let code = jsonAll["code"] as? Int else {
state = .error("code is nil") state = .error(.http("code is nil"))
return return
} }
if (code != 0) { if (code != 0) {
let msg = jsonAll["msg"] as? String ?? "" let msg = jsonAll["msg"] as? String ?? ""
state = .error(msg) state = .error(.http(msg))
return return
} }

View File

@@ -2,9 +2,9 @@
class BuildData { class BuildData {
static const String name = "ServerBox"; 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 engine = "3.13.7";
static const String buildAt = "2023-10-14 23:24:14"; static const String buildAt = "2023-10-15 13:38:49";
static const int modifications = 1; static const int modifications = 9;
static const int script = 21; static const int script = 21;
} }