use env object for home feed view

This commit is contained in:
Satindar Dhillon
2022-02-23 23:27:26 -08:00
parent bb63806f08
commit d1f0e90e62
5 changed files with 53 additions and 112 deletions

View File

@ -1,9 +1,8 @@
import SwiftUI
import Views
// TODO: maybe this can be removed??
enum PrimaryContentCategory: Identifiable, Hashable, Equatable {
case feed(viewModel: HomeFeedViewModel)
case feed
case profile
static func == (lhs: PrimaryContentCategory, rhs: PrimaryContentCategory) -> Bool {
@ -38,8 +37,8 @@ enum PrimaryContentCategory: Identifiable, Hashable, Equatable {
@ViewBuilder var destinationView: some View {
switch self {
case let .feed(viewModel: viewModel):
HomeFeedView(viewModel: viewModel)
case .feed:
HomeFeedView()
case .profile:
ProfileContainerView()
}

View File

@ -145,7 +145,7 @@ public struct RootView: View {
@ViewBuilder private var innerBody: some View {
if authenticator.isLoggedIn {
PrimaryContentView(services: viewModel.services)
PrimaryContentView()
.onAppear {
viewModel.triggerPushNotificationRequestIfNeeded()
}

View File

@ -6,40 +6,39 @@ import UserNotifications
import Utils
import Views
extension HomeFeedViewModel {
static func make(services: Services) -> HomeFeedViewModel {
let viewModel = HomeFeedViewModel()
viewModel.bind(services: services)
viewModel.loadItems(dataService: services.dataService, searchQuery: nil, isRefresh: false)
return viewModel
}
final class HomeFeedViewModel: ObservableObject {
var currentDetailViewModel: LinkItemDetailViewModel?
func bind(services: Services) {
performActionSubject.sink { [weak self] action in
switch action {
case let .refreshItems(query: query):
self?.loadItems(dataService: services.dataService, searchQuery: query, isRefresh: true)
case let .loadItems(query):
self?.loadItems(dataService: services.dataService, searchQuery: query, isRefresh: false)
case let .archive(linkId):
self?.setLinkArchived(dataService: services.dataService, linkId: linkId, archived: true)
case let .unarchive(linkId):
self?.setLinkArchived(dataService: services.dataService, linkId: linkId, archived: false)
case let .remove(linkId):
self?.removeLink(dataService: services.dataService, linkId: linkId)
case let .snooze(linkId, until, successMessage):
self?.snoozeUntil(
dataService: services.dataService,
linkId: linkId,
until: until,
successMessage: successMessage
)
}
@Published var items = [FeedItem]()
@Published var isLoading = false
@Published var showPushNotificationPrimer = false
var cursor: String?
// These are used to make sure we handle search result
// responses in the right order
var searchIdx = 0
var receivedIdx = 0
var subscriptions = Set<AnyCancellable>()
init() {}
func itemAppeared(item: FeedItem, searchQuery: String, dataService: DataService) {
if isLoading { return }
let itemIndex = items.firstIndex(where: { $0.id == item.id })
let thresholdIndex = items.index(items.endIndex, offsetBy: -5)
// Check if user has scrolled to the last five items in the list
if let itemIndex = itemIndex, itemIndex > thresholdIndex, items.count < thresholdIndex + 10 {
loadItems(dataService: dataService, searchQuery: searchQuery, isRefresh: false)
}
.store(in: &subscriptions)
}
private func loadItems(dataService: DataService, searchQuery: String?, isRefresh: Bool) {
func pushFeedItem(item: FeedItem) {
items.insert(item, at: 0)
}
func loadItems(dataService: DataService, searchQuery: String?, isRefresh: Bool) {
// Clear offline highlights since we'll be populating new FeedItems with the correct highlights set
dataService.clearHighlights()
@ -90,7 +89,7 @@ extension HomeFeedViewModel {
.store(in: &subscriptions)
}
private func setLinkArchived(dataService: DataService, linkId: String, archived: Bool) {
func setLinkArchived(dataService: DataService, linkId: String, archived: Bool) {
isLoading = true
startNetworkActivityIndicator()
@ -120,7 +119,7 @@ extension HomeFeedViewModel {
.store(in: &subscriptions)
}
private func removeLink(dataService: DataService, linkId: String) {
func removeLink(dataService: DataService, linkId: String) {
isLoading = true
startNetworkActivityIndicator()
@ -146,7 +145,7 @@ extension HomeFeedViewModel {
.store(in: &subscriptions)
}
private func snoozeUntil(dataService: DataService, linkId: String, until: Date, successMessage: String?) {
func snoozeUntil(dataService: DataService, linkId: String, until: Date, successMessage: String?) {
isLoading = true
startNetworkActivityIndicator()
@ -178,55 +177,10 @@ extension HomeFeedViewModel {
}
}
// TODO: remove this view model
final class HomeFeedViewModel: ObservableObject {
var currentDetailViewModel: LinkItemDetailViewModel?
@Published var items = [FeedItem]()
@Published var isLoading = false
@Published var showPushNotificationPrimer = false
var cursor: String?
// These are used to make sure we handle search result
// responses in the right order
var searchIdx = 0
var receivedIdx = 0
enum Action {
case refreshItems(query: String)
case loadItems(query: String)
case archive(linkId: String)
case unarchive(linkId: String)
case remove(linkId: String)
case snooze(linkId: String, until: Date, successMessage: String?)
}
var subscriptions = Set<AnyCancellable>()
let performActionSubject = PassthroughSubject<Action, Never>()
init() {}
func itemAppeared(item: FeedItem, searchQuery: String) {
if isLoading { return }
let itemIndex = items.firstIndex(where: { $0.id == item.id })
let thresholdIndex = items.index(items.endIndex, offsetBy: -5)
// Check if user has scrolled to the last five items in the list
if let itemIndex = itemIndex, itemIndex > thresholdIndex, items.count < thresholdIndex + 10 {
performActionSubject.send(.loadItems(query: searchQuery))
}
}
func pushFeedItem(item: FeedItem) {
items.insert(item, at: 0)
}
}
struct HomeFeedView: View {
// @EnvironmentObject var authenticator: Authenticator
// @EnvironmentObject var dataService: DataService
@EnvironmentObject var dataService: DataService
@ObservedObject private var viewModel: HomeFeedViewModel
@ObservedObject private var viewModel = HomeFeedViewModel()
@State private var selectedLinkItem: FeedItem?
@State private var searchQuery = ""
@State private var itemToRemove: FeedItem?
@ -234,10 +188,6 @@ struct HomeFeedView: View {
@State private var snoozePresented = false
@State private var itemToSnooze: FeedItem?
init(viewModel: HomeFeedViewModel) {
self.viewModel = viewModel
}
@ViewBuilder var conditionalInnerBody: some View {
#if os(iOS)
if #available(iOS 15.0, *) {
@ -301,14 +251,14 @@ struct HomeFeedView: View {
.opacity(0)
.buttonStyle(PlainButtonStyle())
.onAppear {
viewModel.itemAppeared(item: item, searchQuery: searchQuery)
viewModel.itemAppeared(item: item, searchQuery: searchQuery, dataService: dataService)
}
FeedCard(item: item)
}.contextMenu {
if !item.isArchived {
Button(action: {
withAnimation(.linear(duration: 0.4)) {
viewModel.performActionSubject.send(.archive(linkId: item.id))
viewModel.setLinkArchived(dataService: dataService, linkId: item.id, archived: true)
if item == selectedLinkItem {
selectedLinkItem = nil
}
@ -317,7 +267,7 @@ struct HomeFeedView: View {
} else {
Button(action: {
withAnimation(.linear(duration: 0.4)) {
viewModel.performActionSubject.send(.unarchive(linkId: item.id))
viewModel.setLinkArchived(dataService: dataService, linkId: item.id, archived: false)
}
}, label: { Label("Unarchive", systemImage: "tray.and.arrow.down.fill") })
}
@ -335,7 +285,7 @@ struct HomeFeedView: View {
if !item.isArchived {
Button {
withAnimation(.linear(duration: 0.4)) {
viewModel.performActionSubject.send(.archive(linkId: item.id))
viewModel.setLinkArchived(dataService: dataService, linkId: item.id, archived: true)
}
} label: {
Label("Archive", systemImage: "archivebox")
@ -343,7 +293,7 @@ struct HomeFeedView: View {
} else {
Button {
withAnimation(.linear(duration: 0.4)) {
viewModel.performActionSubject.send(.unarchive(linkId: item.id))
viewModel.setLinkArchived(dataService: dataService, linkId: item.id, archived: false)
}
} label: {
Label("Unarchive", systemImage: "tray.and.arrow.down.fill")
@ -365,7 +315,7 @@ struct HomeFeedView: View {
Button("Remove Link", role: .destructive) {
if let itemToRemove = itemToRemove {
withAnimation {
viewModel.performActionSubject.send(.remove(linkId: itemToRemove.id))
viewModel.removeLink(dataService: dataService, linkId: itemToRemove.id)
}
}
self.itemToRemove = nil
@ -417,8 +367,11 @@ struct HomeFeedView: View {
}
.formSheet(isPresented: $snoozePresented) {
SnoozeView(snoozePresented: $snoozePresented, itemToSnooze: $itemToSnooze) {
viewModel.performActionSubject.send(
.snooze(linkId: $0.feedItemId, until: $0.snoozeUntilDate, successMessage: $0.successMessage)
viewModel.snoozeUntil(
dataService: dataService,
linkId: $0.feedItemId,
until: $0.snoozeUntilDate,
successMessage: $0.successMessage
)
}
}
@ -459,7 +412,7 @@ struct HomeFeedView: View {
}
private func refresh() {
viewModel.performActionSubject.send(.refreshItems(query: searchQuery))
viewModel.loadItems(dataService: dataService, searchQuery: searchQuery, isRefresh: true)
}
}

View File

@ -4,33 +4,22 @@ import SwiftUI
import Views
public struct PrimaryContentView: View {
let homeFeedViewModel: HomeFeedViewModel
public init(services: Services) {
self.homeFeedViewModel = HomeFeedViewModel.make(services: services)
}
public var body: some View {
#if os(iOS)
if UIDevice.isIPad {
regularView
} else {
compactView
HomeFeedView()
}
#elseif os(macOS)
regularView
#endif
}
// iphone view container
private var compactView: some View {
HomeFeedView(viewModel: homeFeedViewModel)
}
// ipad and mac view container
private var regularView: some View {
let categories = [
PrimaryContentCategory.feed(viewModel: homeFeedViewModel),
PrimaryContentCategory.feed,
PrimaryContentCategory.profile
]

View File

@ -67,7 +67,7 @@ struct ProfileContainerView: View {
#if os(iOS)
Button(
action: {
viewModel.performActionSubject.send(.showIntercomMessenger)
DataService.showIntercomMessenger?()
},
label: { Text("Feedback") }
)