Account statuses and more packages
This commit is contained in:
parent
70d28e697c
commit
4c3809a95b
|
@ -10,6 +10,7 @@
|
|||
9F24EEB829360C330042359D /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 9F24EEB729360C330042359D /* Preview Assets.xcassets */; };
|
||||
9F24EEBB293619210042359D /* Routeur in Frameworks */ = {isa = PBXBuildFile; productRef = 9F24EEBA293619210042359D /* Routeur */; };
|
||||
9F295540292B6C3400E0E81B /* Timeline in Frameworks */ = {isa = PBXBuildFile; productRef = 9F29553F292B6C3400E0E81B /* Timeline */; };
|
||||
9F35DB44294F9A7D00B3281A /* Status in Frameworks */ = {isa = PBXBuildFile; productRef = 9F35DB43294F9A7D00B3281A /* Status */; };
|
||||
9F398AA62935FE8A00A889F2 /* AppRouteur.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F398AA52935FE8A00A889F2 /* AppRouteur.swift */; };
|
||||
9F398AA92935FFDB00A889F2 /* Account in Frameworks */ = {isa = PBXBuildFile; productRef = 9F398AA82935FFDB00A889F2 /* Account */; };
|
||||
9F398AAB2935FFDB00A889F2 /* Models in Frameworks */ = {isa = PBXBuildFile; productRef = 9F398AAA2935FFDB00A889F2 /* Models */; };
|
||||
|
@ -29,6 +30,8 @@
|
|||
9F24EEB92936185B0042359D /* Routeur */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = Routeur; path = Packages/Routeur; sourceTree = "<group>"; };
|
||||
9F29553D292B67B600E0E81B /* Network */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = Network; path = Packages/Network; sourceTree = "<group>"; };
|
||||
9F29553E292B6AF600E0E81B /* Timeline */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = Timeline; path = Packages/Timeline; sourceTree = "<group>"; };
|
||||
9F35DB42294F9A2900B3281A /* Status */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = Status; path = Packages/Status; sourceTree = "<group>"; };
|
||||
9F35DB45294FA04C00B3281A /* DesignSystem */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = DesignSystem; path = Packages/DesignSystem; sourceTree = "<group>"; };
|
||||
9F398AA32935F90100A889F2 /* Models */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = Models; path = Packages/Models; sourceTree = "<group>"; };
|
||||
9F398AA52935FE8A00A889F2 /* AppRouteur.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppRouteur.swift; sourceTree = "<group>"; };
|
||||
9F398AAC2936005300A889F2 /* Account */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = Account; path = Packages/Account; sourceTree = "<group>"; };
|
||||
|
@ -53,6 +56,7 @@
|
|||
9F398AA92935FFDB00A889F2 /* Account in Frameworks */,
|
||||
9FBFE64E292A72BD00C250E9 /* Network in Frameworks */,
|
||||
9F398AAB2935FFDB00A889F2 /* Models in Frameworks */,
|
||||
9F35DB44294F9A7D00B3281A /* Status in Frameworks */,
|
||||
9F24EEBB293619210042359D /* Routeur in Frameworks */,
|
||||
9F295540292B6C3400E0E81B /* Timeline in Frameworks */,
|
||||
);
|
||||
|
@ -105,10 +109,12 @@
|
|||
9FBFE63A292A715500C250E9 /* Products */,
|
||||
9FBFE64C292A72BD00C250E9 /* Frameworks */,
|
||||
9F398AAC2936005300A889F2 /* Account */,
|
||||
9F35DB45294FA04C00B3281A /* DesignSystem */,
|
||||
9F398AA32935F90100A889F2 /* Models */,
|
||||
9F29553D292B67B600E0E81B /* Network */,
|
||||
9F29553E292B6AF600E0E81B /* Timeline */,
|
||||
9F24EEB92936185B0042359D /* Routeur */,
|
||||
9F29553D292B67B600E0E81B /* Network */,
|
||||
9F35DB42294F9A2900B3281A /* Status */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
|
@ -171,6 +177,7 @@
|
|||
9F398AAA2935FFDB00A889F2 /* Models */,
|
||||
9F24EEBA293619210042359D /* Routeur */,
|
||||
9FAE4ACD29379A5A00772766 /* KeychainSwift */,
|
||||
9F35DB43294F9A7D00B3281A /* Status */,
|
||||
);
|
||||
productName = IceCubesApp;
|
||||
productReference = 9FBFE639292A715500C250E9 /* IceCubesApp.app */;
|
||||
|
@ -360,7 +367,7 @@
|
|||
ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = IceCubesApp/IceCubesApp.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
CURRENT_PROJECT_VERSION = 150;
|
||||
DEVELOPMENT_ASSET_PATHS = "\"IceCubesApp/Resources\"";
|
||||
DEVELOPMENT_TEAM = Z6P74P6T99;
|
||||
ENABLE_HARDENED_RUNTIME = YES;
|
||||
|
@ -381,7 +388,7 @@
|
|||
LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
|
||||
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
|
||||
MACOSX_DEPLOYMENT_TARGET = 13.0;
|
||||
MARKETING_VERSION = 0.0.1;
|
||||
MARKETING_VERSION = 0.0.2;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.thomasricouard.IceCubesApp;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = auto;
|
||||
|
@ -401,7 +408,7 @@
|
|||
ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = IceCubesApp/IceCubesApp.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
CURRENT_PROJECT_VERSION = 150;
|
||||
DEVELOPMENT_ASSET_PATHS = "\"IceCubesApp/Resources\"";
|
||||
DEVELOPMENT_TEAM = Z6P74P6T99;
|
||||
ENABLE_HARDENED_RUNTIME = YES;
|
||||
|
@ -422,7 +429,7 @@
|
|||
LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
|
||||
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
|
||||
MACOSX_DEPLOYMENT_TARGET = 13.0;
|
||||
MARKETING_VERSION = 0.0.1;
|
||||
MARKETING_VERSION = 0.0.2;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.thomasricouard.IceCubesApp;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = auto;
|
||||
|
@ -476,6 +483,10 @@
|
|||
isa = XCSwiftPackageProductDependency;
|
||||
productName = Timeline;
|
||||
};
|
||||
9F35DB43294F9A7D00B3281A /* Status */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
productName = Status;
|
||||
};
|
||||
9F398AA82935FFDB00A889F2 /* Account */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
productName = Account;
|
||||
|
|
|
@ -2,6 +2,7 @@ import SwiftUI
|
|||
import Timeline
|
||||
import Account
|
||||
import Routeur
|
||||
import Status
|
||||
|
||||
extension View {
|
||||
func withAppRouteur() -> some View {
|
||||
|
|
|
@ -16,13 +16,15 @@ let package = Package(
|
|||
dependencies: [
|
||||
.package(name: "Network", path: "../Network"),
|
||||
.package(name: "Models", path: "../Models"),
|
||||
.package(name: "Status", path: "../Status"),
|
||||
],
|
||||
targets: [
|
||||
.target(
|
||||
name: "Account",
|
||||
dependencies: [
|
||||
.product(name: "Network", package: "Network"),
|
||||
.product(name: "Models", package: "Models")
|
||||
.product(name: "Models", package: "Models"),
|
||||
.product(name: "Status", package: "Status"),
|
||||
]),
|
||||
.testTarget(
|
||||
name: "AccountTests",
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import SwiftUI
|
||||
import Models
|
||||
import DesignSystem
|
||||
|
||||
struct AccountDetailHeaderView: View {
|
||||
@Environment(\.redactionReasons) private var reasons
|
||||
|
@ -70,17 +71,17 @@ struct AccountDetailHeaderView: View {
|
|||
.font(.body)
|
||||
.padding(.top, 8)
|
||||
}
|
||||
.padding(.horizontal, 16)
|
||||
.padding(.horizontal, DS.Constants.layoutPadding)
|
||||
.offset(y: -40)
|
||||
}
|
||||
|
||||
private func makeCustomInfoLabel(title: String, count: Int) -> some View {
|
||||
VStack {
|
||||
Text("\(count)")
|
||||
.font(.headline)
|
||||
Text(title)
|
||||
.font(.footnote)
|
||||
.foregroundColor(.gray)
|
||||
Text("\(count)")
|
||||
.font(.headline)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
import SwiftUI
|
||||
import Models
|
||||
import Network
|
||||
import Status
|
||||
import Shimmer
|
||||
import DesignSystem
|
||||
|
||||
public struct AccountDetailView: View {
|
||||
@EnvironmentObject private var client: Client
|
||||
|
@ -17,21 +20,71 @@ public struct AccountDetailView: View {
|
|||
public var body: some View {
|
||||
ScrollView {
|
||||
LazyVStack {
|
||||
switch viewModel.state {
|
||||
case .loading:
|
||||
AccountDetailHeaderView(account: .placeholder())
|
||||
.redacted(reason: .placeholder)
|
||||
case let .data(account):
|
||||
AccountDetailHeaderView(account: account)
|
||||
case let .error(error):
|
||||
Text("Error: \(error.localizedDescription)")
|
||||
}
|
||||
headerView
|
||||
statusesView
|
||||
.padding(.horizontal, 16)
|
||||
}
|
||||
}
|
||||
.edgesIgnoringSafeArea(.top)
|
||||
.task {
|
||||
viewModel.client = client
|
||||
await viewModel.fetchAccount()
|
||||
await viewModel.fetchStatuses()
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private var headerView: some View {
|
||||
switch viewModel.state {
|
||||
case .loading:
|
||||
AccountDetailHeaderView(account: .placeholder())
|
||||
.redacted(reason: .placeholder)
|
||||
case let .data(account):
|
||||
AccountDetailHeaderView(account: account)
|
||||
case let .error(error):
|
||||
Text("Error: \(error.localizedDescription)")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private var statusesView: some View {
|
||||
switch viewModel.statusesState {
|
||||
case .loading:
|
||||
ForEach(Status.placeholders()) { status in
|
||||
StatusRowView(status: status)
|
||||
.redacted(reason: .placeholder)
|
||||
.shimmering()
|
||||
Divider()
|
||||
}
|
||||
case let .error(error):
|
||||
Text(error.localizedDescription)
|
||||
case let .display(statuses, nextPageState):
|
||||
ForEach(statuses) { status in
|
||||
StatusRowView(status: status)
|
||||
Divider()
|
||||
.padding(.bottom, DS.Constants.layoutPadding)
|
||||
}
|
||||
|
||||
switch nextPageState {
|
||||
case .hasNextPage:
|
||||
loadingRow
|
||||
.onAppear {
|
||||
Task {
|
||||
await viewModel.loadNextPage()
|
||||
}
|
||||
}
|
||||
case .loadingNextPage:
|
||||
loadingRow
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var loadingRow: some View {
|
||||
HStack {
|
||||
Spacer()
|
||||
ProgressView()
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,7 +11,19 @@ class AccountDetailViewModel: ObservableObject {
|
|||
case loading, data(account: Account), error(error: Error)
|
||||
}
|
||||
|
||||
enum StatusesState {
|
||||
enum PagingState {
|
||||
case hasNextPage, loadingNextPage
|
||||
}
|
||||
case loading
|
||||
case display(statuses: [Status], nextPageState: StatusesState.PagingState)
|
||||
case error(error: Error)
|
||||
}
|
||||
|
||||
@Published var state: State = .loading
|
||||
@Published var statusesState: StatusesState = .loading
|
||||
|
||||
private var statuses: [Status] = []
|
||||
|
||||
init(accountId: String) {
|
||||
self.accountId = accountId
|
||||
|
@ -29,4 +41,26 @@ class AccountDetailViewModel: ObservableObject {
|
|||
state = .error(error: error)
|
||||
}
|
||||
}
|
||||
|
||||
func fetchStatuses() async {
|
||||
do {
|
||||
statusesState = .loading
|
||||
statuses = try await client.get(endpoint: Accounts.statuses(id: accountId, sinceId: nil))
|
||||
statusesState = .display(statuses: statuses, nextPageState: .hasNextPage)
|
||||
} catch {
|
||||
statusesState = .error(error: error)
|
||||
}
|
||||
}
|
||||
|
||||
func loadNextPage() async {
|
||||
do {
|
||||
guard let lastId = statuses.last?.id else { return }
|
||||
statusesState = .display(statuses: statuses, nextPageState: .loadingNextPage)
|
||||
let newStatuses: [Status] = try await client.get(endpoint: Accounts.statuses(id: accountId, sinceId: lastId))
|
||||
statuses.append(contentsOf: newStatuses)
|
||||
statusesState = .display(statuses: statuses, nextPageState: .hasNextPage)
|
||||
} catch {
|
||||
statusesState = .error(error: error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
.DS_Store
|
||||
/.build
|
||||
/Packages
|
||||
/*.xcodeproj
|
||||
xcuserdata/
|
||||
DerivedData/
|
||||
.swiftpm/config/registries.json
|
||||
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
|
||||
.netrc
|
|
@ -0,0 +1,23 @@
|
|||
// swift-tools-version: 5.7
|
||||
// The swift-tools-version declares the minimum version of Swift required to build this package.
|
||||
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "DesignSystem",
|
||||
platforms: [
|
||||
.iOS(.v16),
|
||||
],
|
||||
products: [
|
||||
.library(
|
||||
name: "DesignSystem",
|
||||
targets: ["DesignSystem"]),
|
||||
],
|
||||
dependencies: [],
|
||||
targets: [
|
||||
.target(
|
||||
name: "DesignSystem",
|
||||
dependencies: []),
|
||||
]
|
||||
)
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
# DesignSystem
|
||||
|
||||
A description of this package.
|
|
@ -0,0 +1,7 @@
|
|||
import Foundation
|
||||
|
||||
public struct DS {
|
||||
public enum Constants {
|
||||
public static let layoutPadding: CGFloat = 16
|
||||
}
|
||||
}
|
|
@ -3,6 +3,7 @@ import Foundation
|
|||
public enum Accounts: Endpoint {
|
||||
case accounts(id: String)
|
||||
case verifyCredentials
|
||||
case statuses(id: String, sinceId: String?)
|
||||
|
||||
public func path() -> String {
|
||||
switch self {
|
||||
|
@ -10,10 +11,18 @@ public enum Accounts: Endpoint {
|
|||
return "accounts/\(id)"
|
||||
case .verifyCredentials:
|
||||
return "accounts/verify_credentials"
|
||||
case .statuses(let id, _):
|
||||
return "accounts/\(id)/statuses"
|
||||
}
|
||||
}
|
||||
|
||||
public func queryItems() -> [URLQueryItem]? {
|
||||
nil
|
||||
switch self {
|
||||
case .statuses(_, let sinceId):
|
||||
guard let sinceId else { return nil }
|
||||
return [.init(name: "max_id", value: sinceId)]
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,8 +16,10 @@ public enum Timelines: Endpoint {
|
|||
public func queryItems() -> [URLQueryItem]? {
|
||||
switch self {
|
||||
case .pub(let sinceId):
|
||||
guard let sinceId else { return nil }
|
||||
return [.init(name: "max_id", value: sinceId)]
|
||||
case .home(let sinceId):
|
||||
guard let sinceId else { return nil }
|
||||
return [.init(name: "max_id", value: sinceId)]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
.DS_Store
|
||||
/.build
|
||||
/Packages
|
||||
/*.xcodeproj
|
||||
xcuserdata/
|
||||
DerivedData/
|
||||
.swiftpm/config/registries.json
|
||||
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
|
||||
.netrc
|
|
@ -0,0 +1,31 @@
|
|||
// swift-tools-version: 5.7
|
||||
// The swift-tools-version declares the minimum version of Swift required to build this package.
|
||||
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "Status",
|
||||
platforms: [
|
||||
.iOS(.v16),
|
||||
],
|
||||
products: [
|
||||
.library(
|
||||
name: "Status",
|
||||
targets: ["Status"]),
|
||||
],
|
||||
dependencies: [
|
||||
.package(name: "Models", path: "../Models"),
|
||||
.package(name: "Routeur", path: "../Routeur"),
|
||||
.package(name: "DesignSystem", path: "../DesignSystem"),
|
||||
],
|
||||
targets: [
|
||||
.target(
|
||||
name: "Status",
|
||||
dependencies: [
|
||||
.product(name: "Models", package: "Models"),
|
||||
.product(name: "Routeur", package: "Routeur"),
|
||||
.product(name: "DesignSystem", package: "DesignSystem"),
|
||||
]),
|
||||
]
|
||||
)
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
# Status
|
||||
|
||||
A description of this package.
|
|
@ -30,6 +30,6 @@ struct StatusActionsView: View {
|
|||
} label: {
|
||||
Image(systemName: "square.and.arrow.up")
|
||||
}
|
||||
}
|
||||
}.tint(.black)
|
||||
}
|
||||
}
|
|
@ -31,7 +31,7 @@ public struct StatusMediaPreviewView: View {
|
|||
content: { image in
|
||||
image.resizable()
|
||||
.aspectRatio(contentMode: .fill)
|
||||
.frame(maxHeight: 200)
|
||||
.frame(maxHeight: attachements.count > 2 ? 100 : 200)
|
||||
.clipped()
|
||||
.cornerRadius(4)
|
||||
},
|
|
@ -2,13 +2,17 @@ import SwiftUI
|
|||
import Models
|
||||
import Routeur
|
||||
|
||||
struct StatusRowView: View {
|
||||
public struct StatusRowView: View {
|
||||
@Environment(\.redactionReasons) private var reasons
|
||||
@EnvironmentObject private var routeurPath: RouterPath
|
||||
|
||||
let status: Status
|
||||
private let status: Status
|
||||
|
||||
public init(status: Status) {
|
||||
self.status = status
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
public var body: some View {
|
||||
VStack(alignment: .leading) {
|
||||
reblogView
|
||||
statusView
|
|
@ -17,6 +17,7 @@ let package = Package(
|
|||
.package(name: "Network", path: "../Network"),
|
||||
.package(name: "Models", path: "../Models"),
|
||||
.package(name: "Routeur", path: "../Routeur"),
|
||||
.package(name: "Status", path: "../Status"),
|
||||
.package(url: "https://github.com/markiv/SwiftUI-Shimmer", exact: "1.1.0")
|
||||
],
|
||||
targets: [
|
||||
|
@ -26,6 +27,7 @@ let package = Package(
|
|||
.product(name: "Network", package: "Network"),
|
||||
.product(name: "Models", package: "Models"),
|
||||
.product(name: "Routeur", package: "Routeur"),
|
||||
.product(name: "Status", package: "Status"),
|
||||
.product(name: "Shimmer", package: "SwiftUI-Shimmer")
|
||||
]),
|
||||
.testTarget(
|
||||
|
@ -33,3 +35,4 @@ let package = Package(
|
|||
dependencies: ["Timeline"]),
|
||||
]
|
||||
)
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@ import SwiftUI
|
|||
import Network
|
||||
import Models
|
||||
import Shimmer
|
||||
import Status
|
||||
|
||||
public struct TimelineView: View {
|
||||
@EnvironmentObject private var client: Client
|
||||
|
|
|
@ -5,11 +5,11 @@ import Models
|
|||
@MainActor
|
||||
class TimelineViewModel: ObservableObject {
|
||||
enum State {
|
||||
enum PadingState {
|
||||
enum PagingState {
|
||||
case hasNextPage, loadingNextPage
|
||||
}
|
||||
case loading
|
||||
case display(statuses: [Status], nextPageState: State.PadingState)
|
||||
case display(statuses: [Status], nextPageState: State.PagingState)
|
||||
case error(error: Error)
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue