✨ iOS notification widget
This commit is contained in:
136
ios/SolianWidgetExtension/WidgetNetworking.swift
Normal file
136
ios/SolianWidgetExtension/WidgetNetworking.swift
Normal file
@@ -0,0 +1,136 @@
|
||||
//
|
||||
// Networking.swift
|
||||
// SolianWidgetExtensionExtension
|
||||
//
|
||||
// Created by LittleSheep on 2026/1/4.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
enum RemoteError: Error {
|
||||
case missingCredentials
|
||||
case invalidURL
|
||||
case invalidResponse
|
||||
case httpError(Int)
|
||||
case decodingError
|
||||
}
|
||||
|
||||
extension RemoteError: LocalizedError {
|
||||
var errorDescription: String? {
|
||||
switch self {
|
||||
case .missingCredentials:
|
||||
return "Please open the app to sign in."
|
||||
case .invalidURL:
|
||||
return "Invalid server configuration."
|
||||
case .invalidResponse:
|
||||
return "Server returned an invalid response."
|
||||
case .httpError(let code):
|
||||
return "Server error (\(code))."
|
||||
case .decodingError:
|
||||
return "Failed to read server data."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct TokenData: Codable {
|
||||
let token: String
|
||||
}
|
||||
|
||||
class WidgetNetworkService {
|
||||
private let appGroup = "group.solsynth.solian"
|
||||
private let tokenKey = "flutter.dyn_user_tk"
|
||||
private let urlKey = "flutter.app_server_url"
|
||||
|
||||
private lazy var session: URLSession = {
|
||||
let configuration = URLSessionConfiguration.ephemeral
|
||||
configuration.timeoutIntervalForRequest = 10.0
|
||||
configuration.timeoutIntervalForResource = 10.0
|
||||
configuration.waitsForConnectivity = false
|
||||
return URLSession(configuration: configuration)
|
||||
}()
|
||||
|
||||
private var userDefaults: UserDefaults? {
|
||||
UserDefaults(suiteName: appGroup)
|
||||
}
|
||||
|
||||
var token: String? {
|
||||
guard let tokenString = userDefaults?.string(forKey: tokenKey) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
guard let data = tokenString.data(using: .utf8) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
do {
|
||||
let tokenData = try JSONDecoder().decode(TokenData.self, from: data)
|
||||
return tokenData.token
|
||||
} catch {
|
||||
print("[WidgetKit] Failed to decode token: \(error)")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
var baseURL: String {
|
||||
return userDefaults?.string(forKey: urlKey) ?? "https://api.solian.app"
|
||||
}
|
||||
|
||||
func makeRequest<T: Codable>(
|
||||
path: String,
|
||||
method: String = "GET",
|
||||
headers: [String: String] = [:]
|
||||
) async throws -> T? {
|
||||
guard let token = token else {
|
||||
throw RemoteError.missingCredentials
|
||||
}
|
||||
|
||||
guard let url = URL(string: "\(baseURL)\(path)") else {
|
||||
throw RemoteError.invalidURL
|
||||
}
|
||||
|
||||
var request = URLRequest(url: url)
|
||||
request.httpMethod = method
|
||||
request.setValue("AtField \(token)", forHTTPHeaderField: "Authorization")
|
||||
request.setValue("application/json", forHTTPHeaderField: "Accept")
|
||||
|
||||
for (key, value) in headers {
|
||||
request.setValue(value, forHTTPHeaderField: key)
|
||||
}
|
||||
|
||||
request.timeoutInterval = 10.0
|
||||
|
||||
print("[WidgetKit] [Network] Requesting: \(baseURL)\(path)")
|
||||
|
||||
let (data, response) = try await session.data(for: request)
|
||||
|
||||
guard let httpResponse = response as? HTTPURLResponse else {
|
||||
throw RemoteError.invalidResponse
|
||||
}
|
||||
|
||||
print("[WidgetKit] [Network] Status: \(httpResponse.statusCode), Data length: \(data.count)")
|
||||
|
||||
if let jsonString = String(data: data, encoding: .utf8) {
|
||||
print("[WidgetKit] [Network] Response: \(jsonString.prefix(500))")
|
||||
}
|
||||
|
||||
switch httpResponse.statusCode {
|
||||
case 200...299:
|
||||
let decoder = JSONDecoder()
|
||||
do {
|
||||
let result = try decoder.decode(T.self, from: data)
|
||||
print("[WidgetKit] [Network] Successfully decoded response")
|
||||
return result
|
||||
} catch {
|
||||
print("[WidgetKit] [Network] Decoding error: \(error.localizedDescription)")
|
||||
print("[WidgetKit] [Network] Expected type: \(String(describing: T.self))")
|
||||
throw RemoteError.decodingError
|
||||
}
|
||||
case 404:
|
||||
print("[WidgetKit] [Network] Resource not found (404)")
|
||||
return nil
|
||||
default:
|
||||
print("[WidgetKit] [Network] HTTP Error: \(httpResponse.statusCode)")
|
||||
throw RemoteError.httpError(httpResponse.statusCode)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user