Skip to content

Commit 3a94117

Browse files
authored
Adding Login + Comment Upvoting (#265)
This sets up some of our web infrastructure. Luckily it seems like URLSession automagically pulls and stores the auth cookie for us, and we can use it in subsequent requests to the same domain. Mostly was just testing out that upvoting comments worked.
1 parent b4bbf1e commit 3a94117

File tree

14 files changed

+204
-137
lines changed

14 files changed

+204
-137
lines changed

ios/HackerNews.xcodeproj/project.pbxproj

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323
A42705982A4293B30057E439 /* Hacker_NewsUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A42705972A4293B30057E439 /* Hacker_NewsUITests.swift */; };
2424
A427059A2A4293B30057E439 /* Hacker_NewsUITestsLaunchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A42705992A4293B30057E439 /* Hacker_NewsUITestsLaunchTests.swift */; };
2525
A42705A72A42949D0057E439 /* AppViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = A42705A62A42949D0057E439 /* AppViewModel.swift */; };
26-
A42705A92A4294EB0057E439 /* LoginScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = A42705A82A4294EB0057E439 /* LoginScreen.swift */; };
2726
A42705AB2A4296BA0057E439 /* PostListScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = A42705AA2A4296BA0057E439 /* PostListScreen.swift */; };
2827
A42705AD2A429D2E0057E439 /* HNApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = A42705AC2A429D2E0057E439 /* HNApi.swift */; };
2928
A434C2E02A8E75960002F488 /* WebView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A434C2DF2A8E75960002F488 /* WebView.swift */; };
@@ -47,6 +46,7 @@
4746
EC29FDA72CFFD074007B1AE9 /* BookmarksScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC29FDA62CFFD074007B1AE9 /* BookmarksScreen.swift */; };
4847
EC29FDA92CFFD0B5007B1AE9 /* SettingsScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC29FDA82CFFD0B5007B1AE9 /* SettingsScreen.swift */; };
4948
EC70E1612D1DD82B00582023 /* HNWebClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC70E1602D1DD82B00582023 /* HNWebClient.swift */; };
49+
EC882BB22D23A8A70065FEC0 /* LoginScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC882BB12D23A8A70065FEC0 /* LoginScreen.swift */; };
5050
ECCE8F262D03815300349733 /* Pager.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECCE8F252D03815300349733 /* Pager.swift */; };
5151
/* End PBXBuildFile section */
5252

@@ -100,7 +100,6 @@
100100
A42705972A4293B30057E439 /* Hacker_NewsUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Hacker_NewsUITests.swift; sourceTree = "<group>"; };
101101
A42705992A4293B30057E439 /* Hacker_NewsUITestsLaunchTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Hacker_NewsUITestsLaunchTests.swift; sourceTree = "<group>"; };
102102
A42705A62A42949D0057E439 /* AppViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppViewModel.swift; sourceTree = "<group>"; };
103-
A42705A82A4294EB0057E439 /* LoginScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginScreen.swift; sourceTree = "<group>"; };
104103
A42705AA2A4296BA0057E439 /* PostListScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostListScreen.swift; sourceTree = "<group>"; };
105104
A42705AC2A429D2E0057E439 /* HNApi.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HNApi.swift; sourceTree = "<group>"; };
106105
A434C2DF2A8E75960002F488 /* WebView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebView.swift; sourceTree = "<group>"; };
@@ -147,6 +146,7 @@
147146
EC29FDA62CFFD074007B1AE9 /* BookmarksScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarksScreen.swift; sourceTree = "<group>"; };
148147
EC29FDA82CFFD0B5007B1AE9 /* SettingsScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsScreen.swift; sourceTree = "<group>"; };
149148
EC70E1602D1DD82B00582023 /* HNWebClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HNWebClient.swift; sourceTree = "<group>"; };
149+
EC882BB12D23A8A70065FEC0 /* LoginScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginScreen.swift; sourceTree = "<group>"; };
150150
ECCE8F252D03815300349733 /* Pager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Pager.swift; sourceTree = "<group>"; };
151151
/* End PBXFileReference section */
152152

@@ -223,11 +223,11 @@
223223
A413E8552A8C3E0A00C0F867 /* Screens */ = {
224224
isa = PBXGroup;
225225
children = (
226-
A42705A82A4294EB0057E439 /* LoginScreen.swift */,
227226
A42705AA2A4296BA0057E439 /* PostListScreen.swift */,
228227
A48C0DE62A9818A50034CC0A /* StoryScreen.swift */,
229228
EC29FDA62CFFD074007B1AE9 /* BookmarksScreen.swift */,
230229
EC29FDA82CFFD0B5007B1AE9 /* SettingsScreen.swift */,
230+
EC882BB12D23A8A70065FEC0 /* LoginScreen.swift */,
231231
);
232232
path = Screens;
233233
sourceTree = "<group>";
@@ -578,13 +578,13 @@
578578
1DF161FA2A4346F6001A3F76 /* StoryRow.swift in Sources */,
579579
1DF162002A4365A1001A3F76 /* Colors.swift in Sources */,
580580
A42705AB2A4296BA0057E439 /* PostListScreen.swift in Sources */,
581+
EC882BB22D23A8A70065FEC0 /* LoginScreen.swift in Sources */,
581582
A40DC1FA2C20D8B60055B920 /* Previews.swift in Sources */,
582583
A427057F2A4293B10057E439 /* ContentView.swift in Sources */,
583584
EC072CAA2CEF02A500D00B8D /* StoryRowV2.swift in Sources */,
584585
1DF162032A436E7B001A3F76 /* DateUtils.swift in Sources */,
585586
A413E8572A8C40E800C0F867 /* Extensions.swift in Sources */,
586587
EC29FDA72CFFD074007B1AE9 /* BookmarksScreen.swift in Sources */,
587-
A42705A92A4294EB0057E439 /* LoginScreen.swift in Sources */,
588588
EC0B1C752D1A34110000C3AC /* CommentsHeader.swift in Sources */,
589589
A413E8592A8D868500C0F867 /* ThemedButtonStyle.swift in Sources */,
590590
A427057D2A4293B10057E439 /* HNApp.swift in Sources */,

ios/HackerNews.xcodeproj/xcshareddata/xcschemes/HackerNews.xcscheme

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,13 @@
5555
ReferencedContainer = "container:HackerNews.xcodeproj">
5656
</BuildableReference>
5757
</BuildableProductRunnable>
58+
<EnvironmentVariables>
59+
<EnvironmentVariable
60+
key = "CFNETWORK_DIAGNOSTICS"
61+
value = "3"
62+
isEnabled = "YES">
63+
</EnvironmentVariable>
64+
</EnvironmentVariables>
5865
</LaunchAction>
5966
<ProfileAction
6067
buildConfiguration = "Release"

ios/HackerNews/Components/CommentRow.swift

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,22 +8,24 @@
88
import Foundation
99
import SwiftUI
1010

11+
private let maxIndentationLevel: Int = 5
12+
1113
struct CommentRow: View {
12-
let comment: CommentInfo
13-
let maxIndentationLevel: Int = 5
14-
14+
let state: CommentInfo
15+
let likeComment: (CommentInfo) -> Void
16+
1517
var body: some View {
1618
VStack(alignment: .leading) {
1719
// first row
1820
HStack {
1921
// author
20-
Text("@\(comment.user)")
22+
Text("@\(state.user)")
2123
.font(.caption)
2224
.fontWeight(.bold)
2325
// time
2426
HStack(alignment: .center, spacing: 4.0) {
2527
Image(systemName: "clock")
26-
Text(comment.age)
28+
Text(state.age)
2729
}
2830
.font(.caption)
2931
// collapse/expand
@@ -32,7 +34,9 @@ struct CommentRow: View {
3234
// space between
3335
Spacer()
3436
// upvote
35-
Button(action: {}) {
37+
Button(action: {
38+
likeComment(state)
39+
}) {
3640
Image(systemName: "arrow.up")
3741
.font(.caption2)
3842
}
@@ -50,7 +54,7 @@ struct CommentRow: View {
5054
}
5155

5256
// Comment Body
53-
Text(comment.text.strippingHTML())
57+
Text(state.text.strippingHTML())
5458
.font(.caption)
5559
}
5660
.padding(8.0)
@@ -59,7 +63,7 @@ struct CommentRow: View {
5963
.padding(
6064
EdgeInsets(
6165
top: 0,
62-
leading: min(CGFloat(comment.level * 20), CGFloat(maxIndentationLevel * 20)),
66+
leading: min(CGFloat(state.level * 20), CGFloat(maxIndentationLevel * 20)),
6367
bottom: 0,
6468
trailing: 0)
6569
)
@@ -69,7 +73,10 @@ struct CommentRow: View {
6973
struct CommentView_Preview: PreviewProvider {
7074
static var previews: some View {
7175
PreviewVariants {
72-
CommentRow(comment: PreviewHelpers.makeFakeComment())
76+
CommentRow(
77+
state: PreviewHelpers.makeFakeComment(),
78+
likeComment: {_ in}
79+
)
7380
}
7481
}
7582
}
@@ -78,7 +85,10 @@ struct CommentViewIndentation_Preview: PreviewProvider {
7885
static var previews: some View {
7986
Group {
8087
ForEach(0..<6) { index in
81-
CommentRow(comment: PreviewHelpers.makeFakeComment(level: index))
88+
CommentRow(
89+
state: PreviewHelpers.makeFakeComment(level: index),
90+
likeComment: {_ in}
91+
)
8292
.previewLayout(.sizeThatFits)
8393
.previewDisplayName("Indentation \(index)")
8494
}

ios/HackerNews/ContentView.swift

Lines changed: 18 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -8,50 +8,31 @@
88
import SwiftUI
99

1010
struct ContentView: View {
11-
12-
@ObservedObject var appState: AppViewModel
13-
14-
var body: some View {
15-
switch appState.authState {
16-
case .loggedIn:
17-
TabView {
18-
PostListScreen(appState: appState)
19-
.tag(1)
20-
.tabItem { Label("Feed", systemImage: "list.dash") }
21-
BookmarksScreen()
22-
.tag(2)
23-
.tabItem { Label("Bookmarks", systemImage: "book") }
24-
SettingsScreen()
25-
.tag(3)
26-
.tabItem { Label("Settings", systemImage: "gear") }
27-
}
28-
case .loggedOut:
29-
LoginScreen(appState: appState)
30-
}
31-
}
32-
33-
}
3411

35-
struct ContentView_LoggedOut_Previews: PreviewProvider {
36-
static var previews: some View {
37-
let appModel = AppViewModel()
38-
appModel.authState = .loggedOut
39-
return PreviewVariants {
40-
PreviewHelpers.withNavigationView {
41-
ContentView(appState: appModel)
42-
}
12+
@ObservedObject var model: AppViewModel
13+
14+
var body: some View {
15+
TabView {
16+
PostListScreen(model: model)
17+
.tag(1)
18+
.tabItem { Label("Feed", systemImage: "list.dash") }
19+
BookmarksScreen()
20+
.tag(2)
21+
.tabItem { Label("Bookmarks", systemImage: "book") }
22+
SettingsScreen(model: model)
23+
.tag(3)
24+
.tabItem { Label("Settings", systemImage: "gear") }
4325
}
4426
}
4527
}
4628

4729
struct ContentView_LoggedIn_Loading_Previews: PreviewProvider {
4830
static var previews: some View {
4931
let appModel = AppViewModel()
50-
appModel.authState = .loggedIn
5132
appModel.postListState = PostListState(stories: [])
5233
return PreviewVariants {
5334
PreviewHelpers.withNavigationView {
54-
ContentView(appState: appModel)
35+
ContentView(model: appModel)
5536
}
5637
}
5738
}
@@ -63,13 +44,12 @@ struct ContentView_LoggedIn_WithPosts_Previews: PreviewProvider {
6344
let fakeStories = PreviewHelpers
6445
.makeFakeStories()
6546
.map { StoryState.loaded(story: $0) }
66-
67-
appModel.authState = .loggedIn
47+
6848
appModel.postListState = PostListState(stories: fakeStories)
69-
49+
7050
return PreviewVariants {
7151
PreviewHelpers.withNavigationView {
72-
ContentView(appState: appModel)
52+
ContentView(model: appModel)
7353
}
7454
}
7555
}
@@ -78,11 +58,10 @@ struct ContentView_LoggedIn_WithPosts_Previews: PreviewProvider {
7858
struct ContentView_LoggedIn_EmptyPosts_Previews: PreviewProvider {
7959
static var previews: some View {
8060
let appModel = AppViewModel()
81-
appModel.authState = .loggedIn
8261
appModel.postListState = PostListState(stories: [])
8362
return PreviewVariants {
8463
PreviewHelpers.withNavigationView {
85-
ContentView(appState: appModel)
64+
ContentView(model: appModel)
8665
}
8766
}
8867
}

ios/HackerNews/HNApp.swift

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77

88
import Reaper
99
import Sentry
10-
1110
import SwiftUI
1211

1312
@main
@@ -39,7 +38,7 @@ struct Hacker_NewsApp: App {
3938
HNColors.background
4039
.ignoresSafeArea()
4140

42-
ContentView(appState: appState)
41+
ContentView(model: appState)
4342
}
4443
.toolbarColorScheme(.dark, for: .navigationBar)
4544
.toolbarBackground(HNColors.orange, for: .navigationBar)

ios/HackerNews/Models/AppViewModel.swift

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -73,35 +73,29 @@ class AppViewModel: ObservableObject {
7373
case loggedOut
7474
}
7575

76-
@Published var authState = AuthState.loggedOut
76+
@Published var loginState = LoginState()
7777
@Published var postListState = PostListState()
7878
@Published var navigationPath = NavigationPath()
7979

80-
private let hnApi = HNApi()
80+
private let api = HNApi()
81+
private let webClient = HNWebClient()
8182
private var pager = Pager()
8283

83-
init() {}
84-
85-
func performLogin() {
86-
authState = .loggedIn
87-
}
88-
89-
func performLogout() {
90-
authState = .loggedOut
84+
init() {
9185
}
9286

9387
func fetchInitialPosts(feedType: FeedType) async {
9488
postListState.selectedFeed = feedType
9589
postListState.stories = []
9690

97-
let idsToConsume = await hnApi.fetchStories(feedType: feedType)
91+
let idsToConsume = await api.fetchStories(feedType: feedType)
9892
pager.setIds(idsToConsume)
9993

10094
if pager.hasNextPage() {
10195
let nextPage = pager.nextPage()
10296
postListState.stories = nextPage.ids.map { StoryState.loading(id: $0) }
10397

104-
let items = await hnApi.fetchPage(page: nextPage)
98+
let items = await api.fetchPage(page: nextPage)
10599
postListState.stories = items.map { StoryState.loaded(story: $0 ) }
106100
pager.hasNextPage() ? postListState.stories.append(.nextPage) : ()
107101
}
@@ -112,9 +106,21 @@ class AppViewModel: ObservableObject {
112106
return
113107
}
114108
let nextPage = pager.nextPage()
115-
let items = await hnApi.fetchPage(page: nextPage)
109+
let items = await api.fetchPage(page: nextPage)
116110
postListState.stories.removeLast() // remove the loading view
117111
postListState.stories += items.map { StoryState.loaded(story: $0) }
118112
pager.hasNextPage() ? postListState.stories.append(.nextPage) : ()
119113
}
114+
115+
func login() async {
116+
let body = LoginBody(acct: loginState.username, pw: loginState.password)
117+
do {
118+
let (data, response) = try await webClient.login(with: body)
119+
let htmlString = String(data: data, encoding: .utf8)!
120+
print("HTML: ", htmlString)
121+
122+
} catch {
123+
print("Error:", error)
124+
}
125+
}
120126
}

ios/HackerNews/Models/StoryViewModel.swift

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ class StoryViewModel: ObservableObject {
3131
private let story: Story
3232

3333
private let webClient = HNWebClient()
34+
private let cookieStorage = HTTPCookieStorage.shared
3435

3536
init(story: Story) {
3637
self.story = story
@@ -55,4 +56,18 @@ class StoryViewModel: ObservableObject {
5556
state.comments = .loaded(comments: [])
5657
}
5758
}
59+
60+
private func isLoggedIn() -> Bool {
61+
return cookieStorage.cookies?.isEmpty == false
62+
}
63+
64+
func likeComment(comment: CommentInfo) async {
65+
print("DEBUG: like comment clicked for \(comment.id)")
66+
if (isLoggedIn()) {
67+
let success = await webClient.upvoteItem(upvoteUrl: comment.upvoteUrl!)
68+
print("DEBUG: upvoted comment \(success)")
69+
} else {
70+
// navigate to login modal
71+
}
72+
}
5873
}

ios/HackerNews/Network/HNApi.swift

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,12 @@ import Foundation
99

1010

1111
class HNApi {
12-
13-
let baseUrl = "https://hacker-news.firebaseio.com/v0/"
14-
let decoder = JSONDecoder()
15-
12+
private let baseUrl = "https://hacker-news.firebaseio.com/v0/"
13+
private let decoder = JSONDecoder()
14+
private let session = URLSession.shared
15+
1616
init() {}
1717

18-
19-
2018
func fetchStories(feedType: FeedType) async -> [Int64] {
2119
NotificationCenter.default.post(name: Notification.Name(rawValue: "EmergeMetricStarted"), object: nil, userInfo: [
2220
"metric": "FETCH_STORIES"

0 commit comments

Comments
 (0)