Compare commits

..

6 Commits

Author SHA1 Message Date
Ching
e08f5273d0 feat(view): 修改个人主页样式
修改个人主页样式

Signed-off-by: Ching <loooching@gmail.com>
2023-05-27 23:21:53 +08:00
Ching
1527be4329 feat(InstanceInfoView): 自动加载实例信息中的 thumbnail image
自动加载实例信息中的 thumbnail image

Signed-off-by: Ching <loooching@gmail.com>
2023-03-20 23:17:29 +08:00
Ching
d8ea4236cc feat(InstanceViewModel): 自动给用户输入的实例地址加上 https://
自动给用户输入的实例地址加上 https://

Signed-off-by: Ching <loooching@gmail.com>
2023-03-20 21:32:53 +08:00
Ching
0103281cd4 feat(InstanceInfoView, Instance Model, InstanceViewModel): 通过实例地址获取实例信息并展示
通过实例地址获取实例信息并展示

Signed-off-by: Ching <loooching@gmail.com>
2023-03-19 21:15:00 +08:00
Ching
872c69df6b feat(view model): 增加 network client
增加了一个第三方库 Moya,用于处理网络请求

Signed-off-by: Ching <loooching@gmail.com>
2023-03-04 22:57:54 +08:00
Ching
bea4505eaa feat(model, viewmodel): 增加model,增加InstanceApp 相关逻辑
1. 增加 InstanceApp model
2. 增加 AddAccountView 中创建 InstanceApp 的逻辑

Signed-off-by: Ching <loooching@gmail.com>
2023-03-02 23:51:42 +08:00
15 changed files with 596 additions and 12 deletions

View File

@ -7,6 +7,7 @@
objects = {
/* Begin PBXBuildFile section */
24BB349029C5F28100A52D20 /* InstanceViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24BB348F29C5F28100A52D20 /* InstanceViewModel.swift */; };
24D4D3B529AB83E80064E566 /* DUDUJIApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24D4D3B429AB83E80064E566 /* DUDUJIApp.swift */; };
24D4D3B729AB83E80064E566 /* DUDUJIView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24D4D3B629AB83E80064E566 /* DUDUJIView.swift */; };
24D4D3B929AB83E90064E566 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 24D4D3B829AB83E90064E566 /* Assets.xcassets */; };
@ -17,9 +18,16 @@
24D4D3CC29ABBA800064E566 /* MastodonSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 24D4D3CB29ABBA800064E566 /* MastodonSwift */; };
24D4D3CE29ABBC030064E566 /* InstanceApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24D4D3CD29ABBC030064E566 /* InstanceApp.swift */; };
24D4D3D029ABC01E0064E566 /* AddAccountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24D4D3CF29ABC01E0064E566 /* AddAccountView.swift */; };
24D4D3D429AF97A40064E566 /* InstanceAppViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24D4D3D329AF97A40064E566 /* InstanceAppViewModel.swift */; };
24D4D3D629AFA1970064E566 /* InstanceAppManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24D4D3D529AFA1970064E566 /* InstanceAppManager.swift */; };
24D4D3DA29B2EB7B0064E566 /* Moya in Frameworks */ = {isa = PBXBuildFile; productRef = 24D4D3D929B2EB7B0064E566 /* Moya */; };
24D4D3DC29B2EC160064E566 /* MastodonService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24D4D3DB29B2EC160064E566 /* MastodonService.swift */; };
24D4D3DE29B6341A0064E566 /* Instance.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24D4D3DD29B6341A0064E566 /* Instance.swift */; };
24D4D3E029B640B20064E566 /* InstanceInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24D4D3DF29B640B20064E566 /* InstanceInfoView.swift */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
24BB348F29C5F28100A52D20 /* InstanceViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstanceViewModel.swift; sourceTree = "<group>"; };
24D4D3B129AB83E80064E566 /* DUDUJI.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = DUDUJI.app; sourceTree = BUILT_PRODUCTS_DIR; };
24D4D3B429AB83E80064E566 /* DUDUJIApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DUDUJIApp.swift; sourceTree = "<group>"; };
24D4D3B629AB83E80064E566 /* DUDUJIView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DUDUJIView.swift; sourceTree = "<group>"; };
@ -31,6 +39,11 @@
24D4D3C829ABB7590064E566 /* Const.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Const.swift; sourceTree = "<group>"; };
24D4D3CD29ABBC030064E566 /* InstanceApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstanceApp.swift; sourceTree = "<group>"; };
24D4D3CF29ABC01E0064E566 /* AddAccountView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddAccountView.swift; sourceTree = "<group>"; };
24D4D3D329AF97A40064E566 /* InstanceAppViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstanceAppViewModel.swift; sourceTree = "<group>"; };
24D4D3D529AFA1970064E566 /* InstanceAppManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = InstanceAppManager.swift; path = DUDUJI/Managers/InstanceAppManager.swift; sourceTree = SOURCE_ROOT; };
24D4D3DB29B2EC160064E566 /* MastodonService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = MastodonService.swift; path = DUDUJI/MastodonService.swift; sourceTree = SOURCE_ROOT; };
24D4D3DD29B6341A0064E566 /* Instance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Instance.swift; sourceTree = "<group>"; };
24D4D3DF29B640B20064E566 /* InstanceInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstanceInfoView.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@ -38,6 +51,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
24D4D3DA29B2EB7B0064E566 /* Moya in Frameworks */,
24D4D3CC29ABBA800064E566 /* MastodonSwift in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
@ -64,7 +78,10 @@
24D4D3B329AB83E80064E566 /* DUDUJI */ = {
isa = PBXGroup;
children = (
24D4D3DB29B2EC160064E566 /* MastodonService.swift */,
24D4D3D129ABC0280064E566 /* Views */,
24D4D3D229AF8E9D0064E566 /* ViewModels */,
24D4D3D729B10FB90064E566 /* Managers */,
24D4D3C529ABB5830064E566 /* Models */,
24D4D3C829ABB7590064E566 /* Const.swift */,
24D4D3B429AB83E80064E566 /* DUDUJIApp.swift */,
@ -90,6 +107,7 @@
24D4D3C329ABAE5A0064E566 /* AccountInfo.swift */,
24D4D3C629ABB6500064E566 /* OauthToken.swift */,
24D4D3CD29ABBC030064E566 /* InstanceApp.swift */,
24D4D3DD29B6341A0064E566 /* Instance.swift */,
);
path = Models;
sourceTree = "<group>";
@ -98,10 +116,28 @@
isa = PBXGroup;
children = (
24D4D3CF29ABC01E0064E566 /* AddAccountView.swift */,
24D4D3DF29B640B20064E566 /* InstanceInfoView.swift */,
);
path = Views;
sourceTree = "<group>";
};
24D4D3D229AF8E9D0064E566 /* ViewModels */ = {
isa = PBXGroup;
children = (
24D4D3D329AF97A40064E566 /* InstanceAppViewModel.swift */,
24BB348F29C5F28100A52D20 /* InstanceViewModel.swift */,
);
path = ViewModels;
sourceTree = "<group>";
};
24D4D3D729B10FB90064E566 /* Managers */ = {
isa = PBXGroup;
children = (
24D4D3D529AFA1970064E566 /* InstanceAppManager.swift */,
);
path = Managers;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
@ -120,6 +156,7 @@
name = DUDUJI;
packageProductDependencies = (
24D4D3CB29ABBA800064E566 /* MastodonSwift */,
24D4D3D929B2EB7B0064E566 /* Moya */,
);
productName = DUDUJI;
productReference = 24D4D3B129AB83E80064E566 /* DUDUJI.app */;
@ -151,6 +188,7 @@
mainGroup = 24D4D3A829AB83E80064E566;
packageReferences = (
24D4D3CA29ABBA800064E566 /* XCRemoteSwiftPackageReference "Mastodon" */,
24D4D3D829B2EB7B0064E566 /* XCRemoteSwiftPackageReference "Moya" */,
);
productRefGroup = 24D4D3B229AB83E80064E566 /* Products */;
projectDirPath = "";
@ -178,13 +216,19 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
24BB349029C5F28100A52D20 /* InstanceViewModel.swift in Sources */,
24D4D3DE29B6341A0064E566 /* Instance.swift in Sources */,
24D4D3C429ABAE5A0064E566 /* AccountInfo.swift in Sources */,
24D4D3C729ABB6500064E566 /* OauthToken.swift in Sources */,
24D4D3CE29ABBC030064E566 /* InstanceApp.swift in Sources */,
24D4D3B729AB83E80064E566 /* DUDUJIView.swift in Sources */,
24D4D3DC29B2EC160064E566 /* MastodonService.swift in Sources */,
24D4D3B529AB83E80064E566 /* DUDUJIApp.swift in Sources */,
24D4D3C929ABB7590064E566 /* Const.swift in Sources */,
24D4D3D029ABC01E0064E566 /* AddAccountView.swift in Sources */,
24D4D3E029B640B20064E566 /* InstanceInfoView.swift in Sources */,
24D4D3D629AFA1970064E566 /* InstanceAppManager.swift in Sources */,
24D4D3D429AF97A40064E566 /* InstanceAppViewModel.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -408,6 +452,14 @@
minimumVersion = 2.0.0;
};
};
24D4D3D829B2EB7B0064E566 /* XCRemoteSwiftPackageReference "Moya" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/Moya/Moya";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 15.0.0;
};
};
/* End XCRemoteSwiftPackageReference section */
/* Begin XCSwiftPackageProductDependency section */
@ -416,6 +468,11 @@
package = 24D4D3CA29ABBA800064E566 /* XCRemoteSwiftPackageReference "Mastodon" */;
productName = MastodonSwift;
};
24D4D3D929B2EB7B0064E566 /* Moya */ = {
isa = XCSwiftPackageProductDependency;
package = 24D4D3D829B2EB7B0064E566 /* XCRemoteSwiftPackageReference "Moya" */;
productName = Moya;
};
/* End XCSwiftPackageProductDependency section */
};
rootObject = 24D4D3A929AB83E80064E566 /* Project object */;

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PreviewsEnabled</key>
<false/>
</dict>
</plist>

View File

@ -1,5 +1,14 @@
{
"pins" : [
{
"identity" : "alamofire",
"kind" : "remoteSourceControl",
"location" : "https://github.com/Alamofire/Alamofire.git",
"state" : {
"revision" : "78424be314842833c04bc3bef5b72e85fff99204",
"version" : "5.6.4"
}
},
{
"identity" : "mastodon.swift",
"kind" : "remoteSourceControl",
@ -8,6 +17,33 @@
"revision" : "26d970a92d31cbb0b797f8031746e07f656aee2b",
"version" : "2.1.2"
}
},
{
"identity" : "moya",
"kind" : "remoteSourceControl",
"location" : "https://github.com/Moya/Moya",
"state" : {
"revision" : "c263811c1f3dbf002be9bd83107f7cdc38992b26",
"version" : "15.0.3"
}
},
{
"identity" : "reactiveswift",
"kind" : "remoteSourceControl",
"location" : "https://github.com/ReactiveCocoa/ReactiveSwift.git",
"state" : {
"revision" : "c43bae3dac73fdd3cb906bd5a1914686ca71ed3c",
"version" : "6.7.0"
}
},
{
"identity" : "rxswift",
"kind" : "remoteSourceControl",
"location" : "https://github.com/ReactiveX/RxSwift.git",
"state" : {
"revision" : "b4307ba0b6425c0ba4178e138799946c3da594f8",
"version" : "6.5.0"
}
}
],
"version" : 2

View File

@ -9,6 +9,69 @@
<key>orderHint</key>
<integer>0</integer>
</dict>
<key>ReactiveSwift (Playground) 1.xcscheme</key>
<dict>
<key>isShown</key>
<false/>
<key>orderHint</key>
<integer>2</integer>
</dict>
<key>ReactiveSwift (Playground) 2.xcscheme</key>
<dict>
<key>isShown</key>
<false/>
<key>orderHint</key>
<integer>3</integer>
</dict>
<key>ReactiveSwift (Playground).xcscheme</key>
<dict>
<key>isShown</key>
<false/>
<key>orderHint</key>
<integer>0</integer>
</dict>
<key>ReactiveSwift-UIExamples (Playground) 1.xcscheme</key>
<dict>
<key>isShown</key>
<false/>
<key>orderHint</key>
<integer>5</integer>
</dict>
<key>ReactiveSwift-UIExamples (Playground) 2.xcscheme</key>
<dict>
<key>isShown</key>
<false/>
<key>orderHint</key>
<integer>6</integer>
</dict>
<key>ReactiveSwift-UIExamples (Playground).xcscheme</key>
<dict>
<key>isShown</key>
<false/>
<key>orderHint</key>
<integer>4</integer>
</dict>
<key>Rx (Playground) 1.xcscheme</key>
<dict>
<key>isShown</key>
<false/>
<key>orderHint</key>
<integer>8</integer>
</dict>
<key>Rx (Playground) 2.xcscheme</key>
<dict>
<key>isShown</key>
<false/>
<key>orderHint</key>
<integer>9</integer>
</dict>
<key>Rx (Playground).xcscheme</key>
<dict>
<key>isShown</key>
<false/>
<key>orderHint</key>
<integer>7</integer>
</dict>
</dict>
</dict>
</plist>

View File

@ -15,3 +15,10 @@ enum Const {
enum DUDUConst {
static let name: String = "嘟嘟机"
}
enum HttpMethod {
static let get: String = "GET"
static let post: String = "POST"
static let put: String = "PUT"
}

View File

@ -10,11 +10,7 @@ import SwiftUI
struct DUDUJIView: View {
var body: some View {
VStack {
// TextField(Text("server address"))
AddAccountView()
Button("Login") {
print("123 \(Date().debugDescription)")
}
}
.padding()
}

View File

@ -0,0 +1,27 @@
//
// InstanceAppManager.swift
// DUDUJI
//
// Created by ching on 2023/3/1.
//
import Foundation
class InstanceAppManager {
func getClientIdAndClientSecret(at serverAddress: String) -> (clientId: String, clientSecret: String) {
return ("123", "321")
}
func createInstanceApp(at serverAddress: String) -> InstanceApp {
print("服务器地址是\(serverAddress)")
let instanceApp = InstanceApp(
// name: DUDUConst.name,
website: nil,
redirectUri: serverAddress,
clientId: "abc",
clientSecret: "cba")
// save the instanceApp to database
print("客户端名称:\(instanceApp.clientId)")
return instanceApp
}
}

View File

@ -0,0 +1,86 @@
//
// MastodonService.swift
// DUDUJI
//
// Created by ching on 2023/3/4.
//
import Foundation
import Moya
enum MastodonSevice {
case instance
}
extension MastodonSevice: TargetType {
var baseURL: URL {
switch self {
case .instance:
return URL(string: "")! // base URL will be set later
}
}
var path: String {
switch self {
case .instance:
return "/api/v1/instance"
}
}
var method: Moya.Method {
switch self {
case .instance:
return .get
}
}
var sampleData: Data {
return Data()
}
var task: Task {
switch self {
case .instance:
return .requestPlain
}
}
var headers: [String: String]? {
return nil
}
}
class MastodonClient {
private let service: MoyaProvider<MastodonSevice>
init(baseURL: String) {
service = MoyaProvider<MastodonSevice>(endpointClosure: { (target: MastodonSevice) -> Endpoint in
Endpoint(
url: baseURL + target.path,
sampleResponseClosure: { .networkResponse(200, target.sampleData) },
method: target.method,
task: target.task,
httpHeaderFields: target.headers
)
})
}
func getInstance() async throws -> Instance {
return try await withCheckedThrowingContinuation { continuation in
service.request(.instance) { result in
switch result {
case .success(let response):
do {
let instance = try JSONDecoder().decode(Instance.self, from: response.data)
print("instance: \(instance.title)")
continuation.resume(returning: instance)
} catch {
continuation.resume(throwing: error)
}
case .failure(let error):
continuation.resume(throwing: error)
}
}
}
}
}

View File

@ -7,8 +7,7 @@
import Foundation
public struct AccoutInfo {
public let id: String
public struct AccountInfo {
public let username: String
public let serverAddress: URL
public let oauthToken: OauthToken?

View File

@ -0,0 +1,115 @@
//
// Instance.swift
// DUDUJI
//
// Created by ching on 2023/3/6.
//
import Foundation
struct Instance: Decodable {
public let title: String
public let url: URL
public let userCount: Int?
public let statusCount: Int?
public let thumbnail: URL?
public let shortDescription: String?
public let description: String?
enum CodingKeys: String, CodingKey {
case title
case url = "uri"
case stats
case thumbnail
case shortDescription = "short_description"
case description
}
enum StatsCodingKeys: String, CodingKey {
case userCount = "user_count"
case statusCount = "status_count"
}
private struct Container: Decodable {
let title: String
let url: URL
let thumbnail: URL?
let shortDescription: String?
let description: String?
let stats: Stats?
private enum CodingKeys: String, CodingKey {
case title
case url = "uri"
case thumbnail
case shortDescription = "short_description"
case description
case stats
}
struct Stats: Decodable {
let userCount: Int?
let statusCount: Int?
private enum CodingKeys: String, CodingKey {
case userCount = "user_count"
case statusCount = "status_count"
}
}
}
init(from url: URL) throws {
let data = try Data(contentsOf: url)
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let container = try decoder.decode(Container.self, from: data)
title = container.title
self.url = container.url
thumbnail = container.thumbnail
shortDescription = container.shortDescription
description = container.description
userCount = container.stats?.userCount
statusCount = container.stats?.statusCount
}
init(from decoder: Decoder) throws {
print("into decoder")
let container = try decoder.container(keyedBy: CodingKeys.self)
title = try container.decode(String.self, forKey: .title)
url = try container.decode(URL.self, forKey: .url)
thumbnail = try container.decodeIfPresent(URL.self, forKey: .thumbnail)
shortDescription = try container.decodeIfPresent(String.self, forKey: .shortDescription)
description = try container.decodeIfPresent(String.self, forKey: .description)
if let statsContainer = try? container.nestedContainer(keyedBy: StatsCodingKeys.self, forKey: .stats) {
userCount = try statsContainer.decodeIfPresent(Int.self, forKey: .userCount)
statusCount = try statsContainer.decodeIfPresent(Int.self, forKey: .statusCount)
} else {
userCount = nil
statusCount = nil
}
print("title: \(title)")
}
init(title: String, url: URL, userCount: Int? = nil, statusCount: Int? = nil, thumbnail: URL? = nil, shortDescription: String? = nil, description: String? = nil) {
self.title = title
self.url = url
self.userCount = userCount
self.statusCount = statusCount
self.thumbnail = thumbnail
self.shortDescription = shortDescription
self.description = description
}
static func defaultInstance() -> Instance {
Instance(title: "Example", url: URL(string: "http://example.com")!, userCount: 100, statusCount: 200, thumbnail: nil, shortDescription: "This is an example.", description: "This is a longer description.")
}
static func instanceFromURL(url: String) async throws -> Instance {
let client = MastodonClient(baseURL: url)
let newInstance = try await client.getInstance()
return newInstance
}
}

View File

@ -7,9 +7,8 @@
import Foundation
public struct InstanceApp {
public let id: String
public let name: String
struct InstanceApp {
// public let name: String
public let website: URL?
public let redirectUri: String
public let clientId: String

View File

@ -0,0 +1,31 @@
//
// InstanceAppViewModel.swift
// DUDUJI
//
// Created by ching on 2023/3/1.
//
import SwiftUI
class InstanceAppViewModel: ObservableObject {
private let instanceAppManager = InstanceAppManager()
@Published private var instance: Instance?
// let instanceApp: InstanceApp
//
// init(url: String) {
// self.instanceAppManager = InstanceAppManager()
//
// // 使 instanceApp
// let instanceApp: InstanceApp = instanceAppManager.createInstanceApp(at: url)
// self.instanceApp = instanceApp
// }
///
/// App
/// - Parameter url:
func submitServerAddress(with url: String) -> InstanceApp {
instanceAppManager.createInstanceApp(at: url)
}
// MARK: intents
}

View File

@ -0,0 +1,41 @@
//
// InstanceViewModel.swift
// DUDUJI
//
// Created by ching on 2023/3/18.
//
import Foundation
class InstanceViewModel: ObservableObject {
@Published var instance: Instance = .defaultInstance()
@Published var isLoading: Bool = false
func addPrefixIfNecessary(_ str: String) -> String {
if str.hasPrefix("http://") || str.hasPrefix("https://") {
return str
} else {
return "https://" + str
}
}
func getInstance(from url: String) async {
DispatchQueue.main.async {
self.isLoading = true
}
do {
let url = addPrefixIfNecessary(url)
let newInstance = try await Instance.instanceFromURL(url: url)
DispatchQueue.main.async {
self.instance = newInstance
self.isLoading = false
}
} catch {
print("!!!Error: \(error)")
DispatchQueue.main.async {
self.instance = Instance.defaultInstance()
self.isLoading = false
}
}
}
}

View File

@ -8,17 +8,65 @@
import SwiftUI
struct AddAccountView: View {
@State private var serverAddress: String = ""
@State var serverAddress: String = ""
@ObservedObject var instanceViewModel = InstanceViewModel()
@State var showInstanceInfo = false
var body: some View {
VStack {
Form {
TextField("server address", text: $serverAddress)
serverAddressSection
submitButtonSection
if showInstanceInfo {
InstanceInfoSection
}
}
}
}
var serverAddressSection: some View {
Section(header: Text("服务器地址")) {
TextField("nofan.xyz", text: $serverAddress)
.autocorrectionDisabled(true)
.autocapitalization(.none)
}
.onChange(of: serverAddress) { newValue in
if newValue.isEmpty {
self.showInstanceInfo = false
}
}
}
var submitButtonSection: some View {
Section {
Button("提交") {
Task {
await instanceViewModel.getInstance(from: serverAddress)
showInstanceInfo = true
}
}
}
}
var InstanceInfoSection: some View {
Section {
if instanceViewModel.isLoading {
HStack {
Spacer()
ProgressView() //
Spacer()
}
} else {
InstanceInfoView(instance: instanceViewModel.instance)
}
}
}
}
struct AddAccountView_Previews: PreviewProvider {
static var previews: some View {
// FIXME: priview
// Text("Fixme")
AddAccountView()
}
}

View File

@ -0,0 +1,71 @@
//
// InstanceInfoView.swift
// DUDUJI
//
// Created by ching on 2023/3/6.
//
import SwiftUI
class ImageLoader: ObservableObject {
@Published var image: UIImage?
func loadImage(from url: URL) {
URLSession.shared.dataTask(with: url) { data, _, error in
guard let data = data, error == nil else {
return
}
DispatchQueue.main.async {
self.image = UIImage(data: data)
}
}.resume()
}
}
struct InstanceInfoView: View {
// @ObservedObject private var instanceViewModel = InstanceViewModel()
var instance: Instance
@StateObject private var imageLoader = ImageLoader()
var body: some View {
VStack(alignment: .leading) {
Text("实例名称: \(instance.title)")
Text("实例人数: \(instance.userCount != nil ? "\(instance.userCount!)" : "N/A")")
Text("嘟嘟数量: \(instance.statusCount != nil ? "\(instance.statusCount!)" : "N/A")")
// TODO: ->
Group {
if let imageURL = instance.thumbnail {
if let image = imageLoader.image {
Image(uiImage: image)
.resizable()
.scaledToFit()
.frame(maxWidth: .infinity)
.onAppear {
imageLoader.loadImage(from: imageURL)
}
} else {
VStack {
Text("缩略图: 加载中...")
ProgressView() //
}
.frame(maxWidth: .infinity)
.onAppear {
imageLoader.loadImage(from: imageURL)
}
}
} else {
Text("缩略图: N/A")
}
}
Text("简介: \(instance.shortDescription ?? "N/A")")
Text("介绍: \(instance.description ?? "N/A")")
}
}
}
struct InstanceInfoView_Previews: PreviewProvider {
static var previews: some View {
InstanceInfoView(instance: .defaultInstance())
}
}