Skip to content

Commit a6aaa70

Browse files
committed
Like + Profile WIP
1 parent 3d74c77 commit a6aaa70

15 files changed

+263
-86
lines changed

App/AppRouter.swift

+3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import FeedUI
22
import NotificationsUI
33
import PostUI
4+
import ProfileUI
45
import Router
56
import SwiftUI
67

@@ -15,6 +16,8 @@ public struct AppRouter: ViewModifier {
1516
PostDetailView(post: post)
1617
case .timeline:
1718
PostsTimelineView()
19+
case .profile(let profile):
20+
ProfileView(profile: profile)
1821
}
1922
}
2023
}

App/AppTabsRoot.swift

+2-3
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import Models
55
import Network
66
import NotificationsUI
77
import PostUI
8+
import ProfileUI
89
import Router
910
import SettingsUI
1011
import SwiftUI
@@ -37,9 +38,7 @@ extension AppTab {
3738
case .feed:
3839
FeedsListView()
3940
case .profile:
40-
HStack {
41-
Text("Profile view")
42-
}
41+
CurrentUserView()
4342
case .messages:
4443
HStack {
4544
Text("Messages view")

App/IcySkyApp.swift

+3-3
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,14 @@ import VariableBlur
1414

1515
@main
1616
struct IcySkyApp: App {
17+
@Environment(\.scenePhase) var scenePhase
18+
1719
@State var client: BSkyClient?
1820
@State var auth: Auth = .init()
1921
@State var currentUser: CurrentUser?
2022
@State var router: Router = .init()
2123
@State var isLoadingInitialSession: Bool = true
2224

23-
@Environment(\.scenePhase) var scenePhase
24-
2525
init() {
2626
ImagePipeline.shared = ImagePipeline(configuration: .withDataCache)
2727
}
@@ -143,7 +143,7 @@ struct IcySkyApp: App {
143143
}
144144

145145
private func refreshEnvWith(session: UserSession) async {
146-
let client = BSkyClient(session: session, protoClient: ATProtoKit(session: session))
146+
let client = BSkyClient(session: session)
147147
self.client = client
148148
self.currentUser = await CurrentUser(client: client)
149149
}

IcySky.xcodeproj/project.pbxproj

+7
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
9F1552F12CEF35C000DD9F6E /* FeedUI in Frameworks */ = {isa = PBXBuildFile; productRef = 9F1552F02CEF35C000DD9F6E /* FeedUI */; };
1616
9F1552F32CEF35C000DD9F6E /* PostUI in Frameworks */ = {isa = PBXBuildFile; productRef = 9F1552F22CEF35C000DD9F6E /* PostUI */; };
1717
9F21F9B72CFDC288003B9250 /* MediaUI in Frameworks */ = {isa = PBXBuildFile; productRef = 9F21F9B62CFDC288003B9250 /* MediaUI */; };
18+
9F21FACB2D00411D003B9250 /* ProfileUI in Frameworks */ = {isa = PBXBuildFile; productRef = 9F21FACA2D00411D003B9250 /* ProfileUI */; };
1819
9F5892032CEB2E2D00798943 /* Network in Frameworks */ = {isa = PBXBuildFile; productRef = 9F5892022CEB2E2D00798943 /* Network */; };
1920
9F5892972CEC703B00798943 /* DesignSystem in Frameworks */ = {isa = PBXBuildFile; productRef = 9F5892962CEC703B00798943 /* DesignSystem */; };
2021
9F58939D2CEDF04200798943 /* Auth in Frameworks */ = {isa = PBXBuildFile; productRef = 9F58939C2CEDF04200798943 /* Auth */; };
@@ -59,6 +60,7 @@
5960
9F1552E82CEF35B200DD9F6E /* Models in Frameworks */,
6061
9F21F9B72CFDC288003B9250 /* MediaUI in Frameworks */,
6162
9F58939D2CEDF04200798943 /* Auth in Frameworks */,
63+
9F21FACB2D00411D003B9250 /* ProfileUI in Frameworks */,
6264
9FD63DE02CEF1F5B00243D9A /* PostUI in Frameworks */,
6365
9F5892972CEC703B00798943 /* DesignSystem in Frameworks */,
6466
9F1552E62CEF35B200DD9F6E /* Auth in Frameworks */,
@@ -135,6 +137,7 @@
135137
9FAEE3B92CF09BB400F86CDC /* SettingsUI */,
136138
9FA557072CF61EE4008A62B4 /* NotificationsUI */,
137139
9F21F9B62CFDC288003B9250 /* MediaUI */,
140+
9F21FACA2D00411D003B9250 /* ProfileUI */,
138141
);
139142
productName = IcySky;
140143
productReference = 9F2142232CE9DAA1004167D7 /* IcySky.app */;
@@ -457,6 +460,10 @@
457460
isa = XCSwiftPackageProductDependency;
458461
productName = MediaUI;
459462
};
463+
9F21FACA2D00411D003B9250 /* ProfileUI */ = {
464+
isa = XCSwiftPackageProductDependency;
465+
productName = ProfileUI;
466+
};
460467
9F5892022CEB2E2D00798943 /* Network */ = {
461468
isa = XCSwiftPackageProductDependency;
462469
productName = Network;

Packages/Features/Package.swift

+5
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ let package = Package(
1818
products: [
1919
.library(name: "FeedUI", targets: ["FeedUI"]),
2020
.library(name: "PostUI", targets: ["PostUI"]),
21+
.library(name: "ProfileUI", targets: ["ProfileUI"]),
2122
.library(name: "AuthUI", targets: ["AuthUI"]),
2223
.library(name: "SettingsUI", targets: ["SettingsUI"]),
2324
.library(name: "NotificationsUI", targets: ["NotificationsUI"]),
@@ -50,6 +51,10 @@ let package = Package(
5051
.product(name: "NukeUI", package: "Nuke"),
5152
]
5253
),
54+
.target(
55+
name: "ProfileUI",
56+
dependencies: baseDeps + ["FeedUI"]
57+
),
5358
.target(
5459
name: "AuthUI",
5560
dependencies: baseDeps

Packages/Features/Sources/PostUI/Row/PostRowActionsView.swift

+20-5
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import Models
2+
import Network
23
import SwiftUI
34

45
extension EnvironmentValues {
@@ -7,6 +8,7 @@ extension EnvironmentValues {
78

89
public struct PostRowActionsView: View {
910
@Environment(\.hideMoreActions) var hideMoreActions
11+
@Environment(PostDataController.self) var dataController
1012

1113
let post: PostItem
1214

@@ -29,10 +31,14 @@ public struct PostRowActionsView: View {
2931
)
3032

3133
Button(action: {}) {
32-
Label("\(post.repostCount)", systemImage: "quote.bubble")
34+
Label("\(dataController.repostCount)", systemImage: "quote.bubble")
35+
.contentTransition(.numericText(value: Double(dataController.repostCount)))
36+
.monospacedDigit()
37+
.lineLimit(1)
38+
.animation(.smooth, value: dataController.repostCount)
3339
}
3440
.buttonStyle(.plain)
35-
.symbolVariant(post.isReposted ? .fill : .none)
41+
.symbolVariant(dataController.isReposted ? .fill : .none)
3642
.foregroundStyle(
3743
LinearGradient(
3844
colors: [.purple, .indigo],
@@ -41,11 +47,20 @@ public struct PostRowActionsView: View {
4147
)
4248
)
4349

44-
Button(action: {}) {
45-
Label("\(post.likeCount)", systemImage: "heart")
50+
Button(action: {
51+
Task {
52+
await dataController.toggleLike()
53+
}
54+
}) {
55+
Label("\(dataController.likeCount)", systemImage: "heart")
56+
.lineLimit(1)
4657
}
4758
.buttonStyle(.plain)
48-
.symbolVariant(post.isLiked ? .fill : .none)
59+
.symbolVariant(dataController.isLiked ? .fill : .none)
60+
.symbolEffect(.bounce, value: dataController.isLiked)
61+
.contentTransition(.numericText(value: Double(dataController.likeCount)))
62+
.monospacedDigit()
63+
.animation(.smooth, value: dataController.likeCount)
4964
.foregroundStyle(
5065
LinearGradient(
5166
colors: [.red, .purple],

Packages/Features/Sources/PostUI/Row/PostRowView.swift

+19-8
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ public struct PostRowView: View {
1616
@Environment(\.sizeCategory) var sizeCategory
1717

1818
@Environment(Router.self) var router
19+
@Environment(BSkyClient.self) var client
1920

2021
let post: PostItem
2122

@@ -34,6 +35,7 @@ public struct PostRowView: View {
3435
mainView
3536
.padding(.bottom, 18)
3637
}
38+
.environment(PostDataController(post: post, client: client))
3739
.listRowSeparator(.hidden)
3840
.listRowInsets(.init(top: 0, leading: 18, bottom: 0, trailing: 18))
3941
}
@@ -60,12 +62,12 @@ public struct PostRowView: View {
6062
image
6163
.resizable()
6264
.scaledToFit()
63-
.frame(width: isQuote ? 16 : 32, height: isQuote ? 16 : 32)
65+
.frame(width: isQuote ? 16 : 40, height: isQuote ? 16 : 40)
6466
.clipShape(Circle())
6567
default:
6668
Circle()
6769
.fill(.gray.opacity(0.2))
68-
.frame(width: isQuote ? 16 : 32, height: isQuote ? 16 : 32)
70+
.frame(width: isQuote ? 16 : 40, height: isQuote ? 16 : 40)
6971
}
7072
}
7173
.overlay {
@@ -79,6 +81,9 @@ public struct PostRowView: View {
7981
lineWidth: 1)
8082
}
8183
.shadow(color: .shadowPrimary.opacity(0.3), radius: 2)
84+
.onTapGesture {
85+
router.navigateTo(.profile(post.author))
86+
}
8287
}
8388

8489
private var authorView: some View {
@@ -99,6 +104,9 @@ public struct PostRowView: View {
99104
.foregroundStyle(.secondary)
100105
}
101106
.lineLimit(1)
107+
.onTapGesture {
108+
router.navigateTo(.profile(post.author))
109+
}
102110
}
103111

104112
@ViewBuilder
@@ -124,6 +132,7 @@ public struct PostRowView: View {
124132
PostRowView(
125133
post: .init(
126134
uri: "",
135+
cid: "",
127136
indexedAt: Date(),
128137
author: .init(
129138
did: "",
@@ -134,13 +143,14 @@ public struct PostRowView: View {
134143
replyCount: 10,
135144
repostCount: 150,
136145
likeCount: 38,
137-
isLiked: false,
138-
isReposted: false,
146+
likeURI: nil,
147+
repostURI: nil,
139148
embed: nil,
140149
replyRef: nil))
141150
PostRowView(
142151
post: .init(
143152
uri: "",
153+
cid: "",
144154
indexedAt: Date(),
145155
author: .init(
146156
did: "",
@@ -151,13 +161,14 @@ public struct PostRowView: View {
151161
replyCount: 10,
152162
repostCount: 150,
153163
likeCount: 38,
154-
isLiked: true,
155-
isReposted: false,
164+
likeURI: nil,
165+
repostURI: nil,
156166
embed: nil,
157167
replyRef: nil))
158168
PostRowEmbedQuoteView(
159169
post: .init(
160170
uri: "",
171+
cid: "",
161172
indexedAt: Date(),
162173
author: .init(
163174
did: "",
@@ -168,8 +179,8 @@ public struct PostRowView: View {
168179
replyCount: 10,
169180
repostCount: 150,
170181
likeCount: 38,
171-
isLiked: true,
172-
isReposted: true,
182+
likeURI: "",
183+
repostURI: "",
173184
embed: nil,
174185
replyRef: nil))
175186
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import SwiftUI
2+
import User
3+
4+
public struct CurrentUserView: View {
5+
@Environment(CurrentUser.self) private var currentUser
6+
7+
public init() {}
8+
9+
public var body: some View {
10+
if let profile = currentUser.profile {
11+
ProfileView(profile: profile.profile)
12+
} else {
13+
ProgressView()
14+
}
15+
}
16+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import DesignSystem
2+
import Models
3+
import SwiftUI
4+
5+
public struct ProfileView: View {
6+
public let profile: Profile
7+
8+
public init(profile: Profile) {
9+
self.profile = profile
10+
}
11+
12+
public var body: some View {
13+
List {
14+
HeaderView(title: profile.displayName ?? profile.handle)
15+
.padding(.bottom)
16+
}
17+
.listStyle(.plain)
18+
}
19+
}

Packages/Model/Package.swift

+2-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@ let package = Package(
2828
.target(
2929
name: "Models",
3030
dependencies: [
31-
.product(name: "ATProtoKit", package: "ATProtoKit")
31+
.product(name: "ATProtoKit", package: "ATProtoKit"),
32+
"Network",
3233
]
3334
),
3435
.target(
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import ATProtoKit
2+
import Foundation
3+
import Network
4+
import SwiftUI
5+
6+
@MainActor
7+
@Observable
8+
public class PostDataControllerProvider {
9+
private var dataControllers: [String: PostDataController] = [:]
10+
11+
public init() {}
12+
13+
public func getController(for post: PostItem, client: BSkyClient) -> PostDataController {
14+
if let controller = dataControllers[post.uri] {
15+
return controller
16+
} else {
17+
let controller = PostDataController(post: post, client: client)
18+
dataControllers[post.uri] = controller
19+
return controller
20+
}
21+
}
22+
}
23+
24+
@MainActor
25+
@Observable
26+
public class PostDataController {
27+
private var post: PostItem
28+
private let client: BSkyClient
29+
30+
public var isLiked: Bool { likeURI != nil }
31+
public var isReposted: Bool { repostURI != nil }
32+
33+
public var likeCount: Int { post.likeCount + (isLiked ? 1 : 0) }
34+
public var repostCount: Int { post.repostCount + (isReposted ? 1 : 0) }
35+
36+
private var likeURI: String?
37+
private var repostURI: String?
38+
39+
public init(post: PostItem, client: BSkyClient) {
40+
self.post = post
41+
self.client = client
42+
43+
likeURI = post.likeURI
44+
repostURI = post.repostURI
45+
}
46+
47+
public func update(with post: PostItem) {
48+
self.post = post
49+
50+
likeURI = post.likeURI
51+
repostURI = post.repostURI
52+
}
53+
54+
public func toggleLike() async {
55+
let previousState = likeURI
56+
do {
57+
if let likeURI {
58+
self.likeURI = nil
59+
try await client.blueskyClient.deleteLikeRecord(.recordURI(atURI: likeURI))
60+
} else {
61+
self.likeURI = try await client.blueskyClient.createLikeRecord(
62+
.init(recordURI: post.uri, cidHash: post.cid)
63+
).recordURI
64+
}
65+
} catch {
66+
self.likeURI = previousState
67+
}
68+
}
69+
70+
public func toggleRepost() async {
71+
// TODO: Implement
72+
}
73+
}

0 commit comments

Comments
 (0)