Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 58 additions & 14 deletions Noosh.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion Noosh.xcodeproj/xcshareddata/xcschemes/Noosh.xcscheme
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
shouldUseLaunchSchemeArgsEnv = "YES"
codeCoverageEnabled = "YES">
<Testables>
<TestableReference
skipped = "NO">
Expand Down
53 changes: 53 additions & 0 deletions Noosh/API/ListingsEndpoint.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import Foundation
import RxSwift
import reddift

class ListingsEndpoint {
let session: Session

init(session: Session) {
self.session = session
}

func getListing(
paginator: Paginator,
subreddit: SubredditURLPath?,
sort: LinkSortType,
timeFilterWithin: TimeFilterWithin,
limit: Int = 25
) -> Observable<Listing> {
return RxHelper().createObservable { [weak self] resultHandler in
guard let strongSelf = self else { return }

try strongSelf.session.getList(
paginator,
subreddit: subreddit,
sort: sort,
timeFilterWithin: timeFilterWithin,
limit: limit,
completion: resultHandler
)
}
}

func getComments(
linkId: String,
sort: CommentSort,
commentIds: [String]? = nil,
depth: Int? = nil,
limit: Int? = nil
) -> Observable<(Listing, Listing)> {
return RxHelper().createObservable { [weak self] resultHandler in
guard let strongSelf = self else { return }

try strongSelf.session.getArticles(
Link(id: linkId),
sort: sort,
comments: commentIds,
depth: depth,
limit: limit,
completion: resultHandler
)
}
}
}
50 changes: 0 additions & 50 deletions Noosh/API/SubredditEndpoint.swift

This file was deleted.

46 changes: 46 additions & 0 deletions Noosh/Core/CommentConversionService.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import Foundation
import reddift

class CommentConversionService {
private typealias ReplyLevels = [String : Int]

func process(commentsListing: Listing) -> [[CommentViewModel]] {
guard let comments = commentsListing.children.filter({ $0 is Comment }) as? [Comment] else {
// FIXME do not crash in production
fatalError("could not covert comment listings to [Comment]")
}

return comments.flatMap { buildViewModels(comment: $0) }
}


private func buildViewModels(comment: Comment, cache: ReplyLevels? = nil) -> [CommentViewModel] {
let replyLevels = cache ?? buildReplyLevels(comment: comment)
guard let replyLevel = replyLevels[comment.id] else {
// FIXME do not crash in production
fatalError("Could not determine reply level for comment \(comment.id)")
}

var comments: [CommentViewModel] =
[CommentViewModelImpl(comment: comment, replyLevel: replyLevel)]

let replies = (comment.replies.children as? [Comment] ?? []).flatMap {
buildViewModels(comment: $0, cache: replyLevels)
}

comments.append(contentsOf: replies)

return comments
}

private func buildReplyLevels(cache: ReplyLevels = [:], comment: Comment, currentLevel: Int = 0)
-> ReplyLevels {
var levels = cache
levels[comment.id] = currentLevel

let replies = comment.replies.children as? [Comment] ?? []
return replies.reduce(levels) {
buildReplyLevels(cache: $0, comment: $1, currentLevel: currentLevel + 1)
}
}
}
21 changes: 21 additions & 0 deletions Noosh/Core/RxHelper.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import Foundation
import reddift
import RxSwift

class RxHelper {
func createObservable<T>(request: @escaping (@escaping (Result<T>) -> Void) throws -> Void) -> Observable<T> {
return Observable.create { observer in
do {
try request { result in
guard let value = result.value else { return }
observer.onNext(value)
observer.onCompleted()
}
} catch {
observer.onError(error)
}

return Disposables.create()
}
}
}
50 changes: 50 additions & 0 deletions Noosh/Features/LinkDetails/ArticleViewModel.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import Foundation
import reddift

protocol ArticleViewModel {
var author: String { get }
var authorFlair: String? { get }
var body: String? { get }
var commentCount: String { get }
var createdAt: String { get }
var subreddit: String { get }
var title: String { get }
var voteCount: String { get }
}

struct ArticleViewModelImpl: ArticleViewModel {
let subreddit: String
let title: String
let body: String?
let author: String
let authorFlair: String?
let createdAt: String
let voteCount: String
let commentCount: String
}

extension ArticleViewModelImpl {
init(link: LinkCellViewModel) {
self.author = link.username
self.authorFlair = link.authorFlair
self.body = link.body
self.commentCount = link.commentCount
self.createdAt = link.createdAt
self.subreddit = link.subreddit
self.title = link.title
self.voteCount = link.voteCount
}
}

extension ArticleViewModelImpl {
init(link: Link, prettyNumbers: PrettyNumbers = PrettyNumbers(dateProvider: DateProviderImpl())) {
self.author = link.author
self.authorFlair = link.authorFlairText
self.body = link.selftext
self.commentCount = prettyNumbers.commentCount(link.numComments)
self.createdAt = prettyNumbers.timeAgo(epochUtc: link.createdUtc)
self.subreddit = "r/\(link.subreddit)"
self.title = link.title
self.voteCount = prettyNumbers.voteCount(link.ups - link.downs)
}
}
48 changes: 48 additions & 0 deletions Noosh/Features/LinkDetails/CommentViewModel.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import Foundation
import reddift

protocol CommentViewModel {
var id: String { get }
var author: String { get }
var authorFlair: String? { get }
var body: String { get }
var createdAt: String { get }
var edited: Bool { get }
var parentId: String? { get }
var replyLevel: Int { get }
var score: Int? { get }
}

struct CommentViewModelImpl: CommentViewModel, Factory {
/// sourcery:begin: factoryFields
let id: String
let author: String
let authorFlair: String?
let body: String
let createdAt: String
let edited: Bool
let parentId: String?
let replyLevel: Int
let score: Int?
/// sourcery:end
}

extension CommentViewModelImpl {
init(
comment: Comment,
replyLevel: Int,
prettyNumbers: PrettyNumbers = PrettyNumbers(dateProvider: DateProviderImpl())
) {
self.init(
id: comment.id,
author: comment.author,
authorFlair: comment.authorFlairText,
body: comment.body,
createdAt: prettyNumbers.timeAgo(epochUtc: comment.createdUtc),
edited: comment.edited,
parentId: comment.parentId,
replyLevel: replyLevel,
score: comment.score
)
}
}
10 changes: 10 additions & 0 deletions Noosh/Features/LinkDetails/CommentsViewModel.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import Foundation
import RxSwift

protocol CommentsViewModel {
var threads: Variable<[[CommentViewModel]]> { get }
}

struct CommentsViewModelImpl: CommentsViewModel {
let threads: Variable<[[CommentViewModel]]>
}
19 changes: 18 additions & 1 deletion Noosh/Features/LinkDetails/LinkDetailCoordinator.swift
Original file line number Diff line number Diff line change
@@ -1,17 +1,34 @@
import Foundation
import UIKit
import reddift
import RxSwift

class LinkDetailCoordinator: Coordinator {
let navigationController: UINavigationController
private var linkDetailController: LinkDetailViewController?
let listingsEndpoint: ListingsEndpoint

init(navigationController: UINavigationController, session: Session) {
self.navigationController = navigationController
self.listingsEndpoint = ListingsEndpoint(session: session)
}

func start(link: LinkCellViewModel) {
let controller = LinkDetailViewController(link: LinkDetailViewModelImpl(link: link))
let article: Variable<ArticleViewModel> = Variable(ArticleViewModelImpl(link: link))
let comments = Variable<[[CommentViewModel]]>([])
let details =
LinkDetailViewModelImpl(article: article, comments: CommentsViewModelImpl(threads: comments))

_ = listingsEndpoint.getComments(linkId: link.id, sort: .hot)
.observeOn(MainScheduler.instance)
.subscribe(onNext: { (newArticle, newComments) in
guard let newArticle = newArticle.children.first as? Link else { return }
article.value = ArticleViewModelImpl(link: newArticle)
comments.value = CommentConversionService().process(commentsListing: newComments)
})

let controller = LinkDetailViewController(link: details)

linkDetailController = controller
navigationController.pushViewController(controller, animated: true)
}
Expand Down
22 changes: 14 additions & 8 deletions Noosh/Features/LinkDetails/LinkDetailViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,26 +19,32 @@ class LinkDetailViewController: UIViewController {
}

override func viewDidLoad() {
createdAt.text = link.createdAt
subreddit.text = link.subreddit
username.text = link.author
super.viewDidLoad()

if let authorFlair = link.authorFlair {
_ = link.article.asObservable().subscribe(onNext: { [weak self] in self?.update(article: $0) })
}

private func update(article: ArticleViewModel) {
createdAt.text = article.createdAt
subreddit.text = article.subreddit
username.text = article.author

if let authorFlair = article.authorFlair {
self.authorFlair.text = authorFlair
} else {
authorFlair.text = "hi"
}

linkTitle.text = link.title
linkTitle.text = article.title

if let body = link.body {
if let body = article.body {
self.body.text = body
} else {
self.body.text = "bod"
}

voteCount.text = link.voteCount
commentCount.text = link.commentCount
voteCount.text = article.voteCount
commentCount.text = article.commentCount
}

required init?(coder aDecoder: NSCoder) {
Expand Down
Loading