Skip to content

Commit

Permalink
Updating Comment Row (#260)
Browse files Browse the repository at this point in the history
A first pass at updating the comments feature, adding the new comment
row design, and adding the comment row header.
  • Loading branch information
Rahkeen authored Dec 24, 2024
1 parent 540b7a2 commit 23be7c5
Show file tree
Hide file tree
Showing 7 changed files with 220 additions and 47 deletions.
4 changes: 4 additions & 0 deletions ios/HackerNews.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
C3AC6AD62CB6E81B006BD22D /* HackerNewsSnapshotTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3AC6AD52CB6E816006BD22D /* HackerNewsSnapshotTest.swift */; };
C3AC6AD92CB6E8F7006BD22D /* SnapshottingTests in Frameworks */ = {isa = PBXBuildFile; productRef = C3AC6AD82CB6E8F7006BD22D /* SnapshottingTests */; };
EC072CAA2CEF02A500D00B8D /* StoryRowV2.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC072CA92CEF02A500D00B8D /* StoryRowV2.swift */; };
EC0B1C752D1A34110000C3AC /* CommentsHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC0B1C742D1A34110000C3AC /* CommentsHeader.swift */; };
EC29FDA72CFFD074007B1AE9 /* BookmarksScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC29FDA62CFFD074007B1AE9 /* BookmarksScreen.swift */; };
EC29FDA92CFFD0B5007B1AE9 /* SettingsScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC29FDA82CFFD0B5007B1AE9 /* SettingsScreen.swift */; };
ECCE8F262D03815300349733 /* Pager.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECCE8F252D03815300349733 /* Pager.swift */; };
Expand Down Expand Up @@ -141,6 +142,7 @@
C3AC6AD52CB6E816006BD22D /* HackerNewsSnapshotTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HackerNewsSnapshotTest.swift; sourceTree = "<group>"; };
C3AC6AD72CB6E854006BD22D /* HackerNews.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = HackerNews.xctestplan; sourceTree = "<group>"; };
EC072CA92CEF02A500D00B8D /* StoryRowV2.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoryRowV2.swift; sourceTree = "<group>"; };
EC0B1C742D1A34110000C3AC /* CommentsHeader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommentsHeader.swift; sourceTree = "<group>"; };
EC29FDA62CFFD074007B1AE9 /* BookmarksScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarksScreen.swift; sourceTree = "<group>"; };
EC29FDA82CFFD0B5007B1AE9 /* SettingsScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsScreen.swift; sourceTree = "<group>"; };
ECCE8F252D03815300349733 /* Pager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Pager.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -188,6 +190,7 @@
A434C2DF2A8E75960002F488 /* WebView.swift */,
A47309B52AA7D1F600201376 /* CommentRow.swift */,
EC072CA92CEF02A500D00B8D /* StoryRowV2.swift */,
EC0B1C742D1A34110000C3AC /* CommentsHeader.swift */,
);
path = Components;
sourceTree = "<group>";
Expand Down Expand Up @@ -579,6 +582,7 @@
A413E8572A8C40E800C0F867 /* Extensions.swift in Sources */,
EC29FDA72CFFD074007B1AE9 /* BookmarksScreen.swift in Sources */,
A42705A92A4294EB0057E439 /* LoginScreen.swift in Sources */,
EC0B1C752D1A34110000C3AC /* CommentsHeader.swift in Sources */,
A413E8592A8D868500C0F867 /* ThemedButtonStyle.swift in Sources */,
A427057D2A4293B10057E439 /* HNApp.swift in Sources */,
A434C2E02A8E75960002F488 /* WebView.swift in Sources */,
Expand Down
51 changes: 43 additions & 8 deletions ios/HackerNews/Components/CommentRow.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,21 +15,56 @@ struct CommentRow: View {

var body: some View {
VStack(alignment: .leading) {
if let by = comment.by {
Text(by).font(.caption).foregroundColor(.gray)
}
if let text = comment.text?.strippingHTML() {
Text(text)
// first row
HStack {
// author
let author = comment.by != nil ? comment.by! : ""
Text("@\(author)")
.font(.caption)
.fontWeight(.bold)
// time
HStack(alignment: .center, spacing: 4.0) {
Image(systemName: "clock")
Text(comment.displayableDate)
}
.font(.caption)
// collapse/expand
Image(systemName: "chevron.up.chevron.down")
.font(.caption)
// space between
Spacer()
// upvote
Button(action: {}) {
Image(systemName: "arrow.up")
.font(.caption2)
}
.padding(
EdgeInsets(
top: 4.0,
leading: 8.0,
bottom: 4.0,
trailing: 8.0
)
)
.background(HNColors.background)
.foregroundStyle(.black)
.clipShape(Capsule())
}

// Comment Body
let commentText = comment.text != nil ? comment.text! : ""
Text(commentText.strippingHTML())
.font(.caption)
}
.background(.clear)
.padding(8.0)
.background(HNColors.commentBackground)
.clipShape(RoundedRectangle(cornerRadius: 16.0))
.padding(
EdgeInsets(
top: 0,
leading: min(CGFloat(level * 20), CGFloat(maxIndentationLevel * 20)),
bottom: 0,
trailing: 0
)
trailing: 0)
)
}
}
Expand Down
84 changes: 84 additions & 0 deletions ios/HackerNews/Components/CommentsHeader.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
//
// CommentsHeader.swift
// HackerNews
//
// Created by Rikin Marfatia on 12/23/24.
//

import SwiftUI


struct CommentsHeader: View {
let state: CommentsHeaderState
let toggleBody: () -> Void

var body: some View {
VStack(alignment: .leading) {
// title
Text(state.story.title)
.font(.title2)
.fontWeight(.bold)
.frame(maxWidth: .infinity, alignment: .leading)

// body
if (state.story.text != nil) {
VStack(alignment: .leading, spacing: 8.0) {
Image(systemName: "chevron.up.chevron.down")
.font(.caption2)
Text(state.story.text!)
.frame(maxWidth: .infinity, alignment: .leading)
.font(.caption)
.lineLimit(state.expanded ? nil : 4)
}
.padding(8.0)
.background(Color.commentBackground)
.clipShape(RoundedRectangle(cornerRadius: 8.0, style: .continuous))
.onTapGesture {
toggleBody()
}
}

// actions
HStack {
// post author
let author = state.story.by != nil ? state.story.by! : ""
Text("@\(author)")
.font(.caption)
.fontWeight(.bold)
.foregroundColor(Color.hnOrange)
// post time
HStack(alignment: .center, spacing: 4.0) {
Image(systemName: "clock")
.font(.caption2)
.foregroundStyle(.purple)
Text(state.story.displayableDate)
.font(.caption)
}
Spacer()
// upvote button
Button(action: {}) {
Image(systemName: "arrow.up")
.font(.caption2)
}
.padding(
EdgeInsets(
top: 4.0,
leading: 8.0,
bottom: 4.0,
trailing: 8.0
)
)
.background(HNColors.background)
.foregroundStyle(.black)
.clipShape(Capsule())
}
}
}
}

#Preview {
CommentsHeader(
state: CommentsHeaderState(story: PreviewHelpers.makeFakeStory()),
toggleBody: {}
)
}
53 changes: 37 additions & 16 deletions ios/HackerNews/Models/StoryViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,25 +7,46 @@

import Foundation

struct StoryUiState {
var headerState: CommentsHeaderState
var comments: CommentsState
}

enum CommentsState {
case notStarted
case loading
case loaded(comments: [FlattenedComment])
}

struct CommentsHeaderState {
let story: Story
var expanded: Bool = false

}

@MainActor
class StoryViewModel: ObservableObject {

enum StoryState {
case notStarted
case loading
case loaded(comments: [FlattenedComment])
}

@Published var story: Story
@Published var state = StoryState.notStarted


@Published var state: StoryUiState

private let story: Story

init(story: Story) {
self.story = story
self.state = StoryUiState(
headerState: CommentsHeaderState(story: story),
comments: .notStarted
)
}


func toggleHeaderBody() {
state.headerState.expanded.toggle()
}


func fetchComments() async {
state = .loading
state.comments = .loading

var commentsToRequest = story.comments
var commentsById = [Int64 : Comment]()
while !commentsToRequest.isEmpty {
Expand All @@ -47,8 +68,8 @@ class StoryViewModel: ObservableObject {
flattened: &flattenedComments,
commentsById: &commentsById
)
state = .loaded(comments: flattenedComments)

state.comments = .loaded(comments: flattenedComments)
}

private func flattenComments(ids: [Int64], depth: Int = 0, flattened: inout [FlattenedComment], commentsById: inout [Int64 : Comment]) {
Expand All @@ -60,7 +81,7 @@ class StoryViewModel: ObservableObject {
print("Could not find comment \(id)")
}
}
}
}
}

struct FlattenedComment: Identifiable {
Expand Down
8 changes: 8 additions & 0 deletions ios/HackerNews/Network/HNApi.swift
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,14 @@ struct Comment: HNItem, Identifiable {
var replies: [Int64]? {
kids
}

var displayableDate: String {
if ProcessInfo.processInfo.environment["EMERGE_IS_RUNNING_FOR_SNAPSHOTS"] == "1" {
return "10 minutes ago"
}
let date = Date(timeIntervalSince1970: TimeInterval(time))
return date.timeAgoDisplay()
}
}

struct Job: HNItem {
Expand Down
6 changes: 3 additions & 3 deletions ios/HackerNews/Screens/PostListScreen.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ struct PostListScreen: View {
}
}
.padding(8)

List(appState.postListState.stories, id: \.id) { storyState in
StoryRow(
model: appState,
Expand Down Expand Up @@ -70,7 +70,7 @@ struct PostListScreen: View {
let appModel = AppViewModel()
appModel.authState = .loggedIn
appModel.postListState = PostListState()

return PostListScreen(appState: appModel)
}

Expand All @@ -81,6 +81,6 @@ struct PostListScreen: View {
.map { StoryState.loaded(story: $0) }
appModel.authState = .loggedIn
appModel.postListState = PostListState(stories: fakeStories)

return PostListScreen(appState: appModel)
}
61 changes: 41 additions & 20 deletions ios/HackerNews/Screens/StoryScreen.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,35 +9,56 @@ import Foundation
import SwiftUI

struct StoryScreen: View {

@ObservedObject var storyModel: StoryViewModel

var body: some View {
Group {
switch storyModel.state {
case .notStarted, .loading:
ProgressView()
.progressViewStyle(CircularProgressViewStyle())
.scaleEffect(2)
case .loaded(let comments):
List(comments, id: \.id) { flattenedComment in
CommentRow(comment: flattenedComment.comment, level: flattenedComment.depth)
.listRowBackground(Color.clear)
.listRowSeparator(.hidden)
VStack {
// Header
CommentsHeader(
state: storyModel.state.headerState,
toggleBody: { storyModel.toggleHeaderBody() }
)

// Seperator
Rectangle()
.fill()
.frame(maxWidth: .infinity, maxHeight: 1)

// Comments
ZStack {
switch storyModel.state.comments {
case .notStarted, .loading:
ProgressView()
.progressViewStyle(CircularProgressViewStyle())
.scaleEffect(2)
case .loaded(let comments):
VStack {
List(comments, id: \.id) { flattenedComment in
CommentRow(
comment: flattenedComment.comment,
level: flattenedComment.depth
)
.listRowBackground(Color.clear)
.listRowSeparator(.hidden)
.listRowInsets(.init(top: 0, leading: 0, bottom: 0, trailing: 0))
}
.padding(EdgeInsets(top: 4, leading: 0, bottom: 4, trailing: 0))
.listStyle(.plain)
.listRowSpacing(4.0)
}
}
.listStyle(.plain)
}
.frame(maxHeight: .infinity)
}
.background(HNColors.background)
.navigationTitle(storyModel.story.title)
.navigationBarTitleDisplayMode(.inline)
.toolbarColorScheme(.dark, for: .navigationBar)
.padding(8.0)
.navigationTitle(storyModel.state.headerState.story.title)
.toolbarBackground(HNColors.orange, for: .navigationBar)
.toolbarBackground(.visible, for: .navigationBar)
.toolbar {
if let url = storyModel.story.makeUrl() {
if let url = storyModel.state.headerState.story.makeUrl() {
ToolbarItemGroup(placement: .navigationBarTrailing) {
NavigationLink(value: AppViewModel.AppNavigation.webLink(url: url, title: storyModel.story.title)) {
NavigationLink(value: AppViewModel.AppNavigation.webLink(url: url, title: storyModel.state.headerState.story.title)) {
Image(systemName: "globe")
.foregroundColor(.white)
}
Expand All @@ -56,7 +77,7 @@ struct StoryScreen_Preview: PreviewProvider {
PreviewHelpers.makeFakeFlattenedComment()
]
let viewModel = StoryViewModel(story: PreviewHelpers.makeFakeStory(kids: comments.map { $0.comment.id }))
viewModel.state = .loaded(comments: comments)
viewModel.state.comments = .loaded(comments: comments)
return PreviewVariants {
PreviewHelpers.withNavigationView {
StoryScreen(storyModel: viewModel)
Expand Down

0 comments on commit 23be7c5

Please sign in to comment.