Merge pull request #499 from omnivore-app/fix/double-nav-bar

Keep iOS NavBar hidden
This commit is contained in:
Satindar Dhillon 2022-04-27 13:26:34 -07:00 committed by GitHub
commit 6440709e20
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 120 additions and 119 deletions

View file

@ -11,9 +11,16 @@ struct FeedCardNavigationLink: View {
@ObservedObject var viewModel: HomeFeedViewModel
var body: some View {
ZStack {
let destination = LinkItemDetailView(viewModel: LinkItemDetailViewModel(item: item, homeFeedViewModel: viewModel))
#if os(iOS)
let modifiedDestination = destination.navigationBarHidden(true)
#else
let modifiedDestination = destination
#endif
return ZStack {
NavigationLink(
destination: LinkItemDetailView(viewModel: LinkItemDetailViewModel(item: item, homeFeedViewModel: viewModel)),
destination: modifiedDestination,
tag: item,
selection: $viewModel.selectedLinkItem
) {
@ -42,9 +49,16 @@ struct GridCardNavigationLink: View {
@ObservedObject var viewModel: HomeFeedViewModel
var body: some View {
ZStack {
let destination = LinkItemDetailView(viewModel: LinkItemDetailViewModel(item: item, homeFeedViewModel: viewModel))
#if os(iOS)
let modifiedDestination = destination.navigationBarHidden(true)
#else
let modifiedDestination = destination
#endif
return ZStack {
NavigationLink(
destination: LinkItemDetailView(viewModel: LinkItemDetailViewModel(item: item, homeFeedViewModel: viewModel)),
destination: modifiedDestination,
tag: item,
selection: $viewModel.selectedLinkItem
) {

View file

@ -14,6 +14,10 @@ import Views
@ObservedObject var viewModel: HomeFeedViewModel
func loadItems(isRefresh: Bool) {
Task { await viewModel.loadItems(dataService: dataService, isRefresh: isRefresh) }
}
var body: some View {
List {
Section {
@ -23,8 +27,18 @@ import Views
viewModel: viewModel
)
.contextMenu {
Button(
action: { viewModel.itemUnderLabelEdit = item },
label: { Label("Edit Labels", systemImage: "tag") }
)
Button(action: {
viewModel.setLinkArchived(dataService: dataService, linkId: item.id, archived: !item.isArchived)
withAnimation(.linear(duration: 0.4)) {
viewModel.setLinkArchived(
dataService: dataService,
objectID: item.objectID,
archived: !item.isArchived
)
}
}, label: {
Label(
item.isArchived ? "Unarchive" : "Archive",
@ -51,7 +65,7 @@ import Views
Button("Remove Link", role: .destructive) {
if let itemToRemove = itemToRemove {
withAnimation {
viewModel.removeLink(dataService: dataService, linkId: itemToRemove.id)
viewModel.removeLink(dataService: dataService, objectID: itemToRemove.objectID)
self.itemToRemove = nil
}
}
@ -81,16 +95,16 @@ import Views
.onChange(of: viewModel.searchTerm) { _ in
// Maybe we should debounce this, but
// it feels like it works ok without
viewModel.loadItems(dataService: dataService, isRefresh: true)
loadItems(isRefresh: true)
}
.onSubmit(of: .search) {
viewModel.loadItems(dataService: dataService, isRefresh: true)
loadItems(isRefresh: true)
}
.toolbar {
ToolbarItem {
Button(
action: {
viewModel.loadItems(dataService: dataService, isRefresh: true)
loadItems(isRefresh: true)
},
label: { Label("Refresh Feed", systemImage: "arrow.clockwise") }
)
@ -105,7 +119,7 @@ import Views
}
.onAppear {
if viewModel.items.isEmpty {
viewModel.loadItems(dataService: dataService, isRefresh: true)
loadItems(isRefresh: true)
}
}
}

View file

@ -140,10 +140,8 @@ struct LinkItemDetailView: View {
#if os(iOS)
if viewModel.item.isPDF {
fixedNavBarReader
} else if FeatureFlag.useLocalWebView {
WebReaderContainerView(item: viewModel.item, homeFeedViewModel: viewModel.homeFeedViewModel)
} else {
hidingNavBarReader
WebReaderContainerView(item: viewModel.item, homeFeedViewModel: viewModel.homeFeedViewModel)
}
#else
fixedNavBarReader
@ -250,9 +248,7 @@ struct LinkItemDetailView: View {
navBar
Spacer()
}
.navigationBarHidden(true)
}
} else {
VStack(spacing: 0) {
navBar
@ -264,7 +260,6 @@ struct LinkItemDetailView: View {
rawAuthCookie: authenticator.omnivoreAuthCookieString
)
}
.navigationBarHidden(true)
}
}
#endif

View file

@ -1,49 +1,36 @@
import Combine
import Models
import Services
import SwiftUI
import Views
final class NewsletterEmailsViewModel: ObservableObject {
private var hasLoadedInitialEmails = false
@MainActor final class NewsletterEmailsViewModel: ObservableObject {
@Published var isLoading = false
@Published var emails = [NewsletterEmail]()
var subscriptions = Set<AnyCancellable>()
func loadEmails(dataService: DataService) {
func loadEmails(dataService: DataService) async {
isLoading = true
dataService.newsletterEmailsPublisher().sink(
receiveCompletion: { _ in },
receiveValue: { [weak self] objectIDs in
self?.isLoading = false
dataService.viewContext.perform {
self?.emails = objectIDs.compactMap { dataService.viewContext.object(with: $0) as? NewsletterEmail }
}
self?.hasLoadedInitialEmails = true
if let objectIDs = try? await dataService.newsletterEmails() {
await dataService.viewContext.perform { [weak self] in
self?.emails = objectIDs.compactMap { dataService.viewContext.object(with: $0) as? NewsletterEmail }
}
)
.store(in: &subscriptions)
}
isLoading = false
}
func createEmail(dataService: DataService) {
func createEmail(dataService: DataService) async {
isLoading = true
dataService.createNewsletterEmailPublisher().sink(
receiveCompletion: { [weak self] _ in
self?.isLoading = false
},
receiveValue: { [weak self] objectID in
self?.isLoading = false
dataService.viewContext.perform {
if let item = dataService.viewContext.object(with: objectID) as? NewsletterEmail {
self?.emails.insert(item, at: 0)
}
if let objectID = try? await dataService.createNewsletter() {
await dataService.viewContext.perform { [weak self] in
if let item = dataService.viewContext.object(with: objectID) as? NewsletterEmail {
self?.emails.insert(item, at: 0)
}
}
)
.store(in: &subscriptions)
}
isLoading = false
}
}
@ -65,7 +52,7 @@ struct NewsletterEmailsView: View {
.listStyle(InsetListStyle())
#endif
}
.onAppear { viewModel.loadEmails(dataService: dataService) }
.task { await viewModel.loadEmails(dataService: dataService) }
}
private var innerBody: some View {
@ -73,7 +60,7 @@ struct NewsletterEmailsView: View {
Section(footer: Text(footerText)) {
Button(
action: {
viewModel.createEmail(dataService: dataService)
Task { await viewModel.createEmail(dataService: dataService) }
},
label: {
HStack {
@ -98,7 +85,7 @@ struct NewsletterEmailsView: View {
#if os(macOS)
let pasteBoard = NSPasteboard.general
pasteBoard.clearContents()
pasteBoard.writeObjects([newsletterEmail.email as NSString])
pasteBoard.writeObjects([newsletterEmail.unwrappedEmail as NSString])
#endif
Snackbar.show(message: "Email copied")

View file

@ -72,10 +72,8 @@ struct ProfileView: View {
}
Section {
if FeatureFlag.enableLabels {
NavigationLink(destination: LabelsView()) {
Text("Labels")
}
NavigationLink(destination: LabelsView()) {
Text("Labels")
}
NavigationLink(destination: NewsletterEmailsView()) {

View file

@ -222,13 +222,10 @@ import WebKit
navBar
Spacer()
}
.navigationBarHidden(true)
}.onDisappear {
// Clear the shared webview content when exiting
WebViewManager.shared().loadHTMLString("<html></html>", baseURL: nil)
}
.navigationBarHidden(true)
}
}
#endif

View file

@ -1,11 +1,23 @@
import CoreData
import Foundation
public extension NewsletterEmail {
var unwrappedEmailId: String {
emailId ?? ""
}
var unwrappedEmailId: String { emailId ?? "" }
var unwrappedEmail: String {
email ?? ""
var unwrappedEmail: String { email ?? "" }
static func lookup(byID emailID: String, inContext context: NSManagedObjectContext) -> NewsletterEmail? {
let fetchRequest: NSFetchRequest<Models.NewsletterEmail> = NewsletterEmail.fetchRequest()
fetchRequest.predicate = NSPredicate(
format: "%K == %@", #keyPath(NewsletterEmail.emailId), emailID
)
var email: NewsletterEmail?
context.performAndWait {
email = (try? context.fetch(fetchRequest))?.first
}
return email
}
}

View file

@ -5,7 +5,7 @@ import Models
import SwiftGraphQL
public extension DataService {
func createNewsletterEmailPublisher() -> AnyPublisher<NSManagedObjectID, BasicError> {
func createNewsletter() async throws -> NSManagedObjectID {
enum MutationResult {
case saved(newsletterEmail: InternalNewsletterEmail)
case error(errorCode: Enums.CreateNewsletterEmailErrorCode)
@ -34,32 +34,24 @@ public extension DataService {
let path = appEnvironment.graphqlPath
let headers = networker.defaultHeaders
return Deferred {
Future { promise in
send(mutation, to: path, headers: headers) { result in
switch result {
case let .success(payload):
if let graphqlError = payload.errors {
promise(.failure(.message(messageText: "graphql error: \(graphqlError)")))
}
return try await withCheckedThrowingContinuation { continuation in
send(mutation, to: path, headers: headers) { [weak self] queryResult in
guard let payload = try? queryResult.get(), let self = self else {
continuation.resume(throwing: BasicError.message(messageText: "network error"))
return
}
switch payload.data {
case let .saved(newsletterEmail: newsletterEmail):
if let newsletterEmailObjectID = newsletterEmail.persist(context: self.backgroundContext) {
promise(.success(newsletterEmailObjectID))
} else {
promise(.failure(.message(messageText: "coredata error")))
}
case let .error(errorCode: errorCode):
promise(.failure(.message(messageText: errorCode.rawValue)))
}
case .failure:
promise(.failure(.message(messageText: "graphql error")))
switch payload.data {
case let .saved(newsletterEmail: newsletterEmail):
if let newsletterEmailObjectID = newsletterEmail.persist(context: self.backgroundContext) {
continuation.resume(returning: newsletterEmailObjectID)
} else {
continuation.resume(throwing: BasicError.message(messageText: "CoreData error"))
}
case let .error(errorCode: errorCode):
continuation.resume(throwing: BasicError.message(messageText: errorCode.rawValue))
}
}
}
.receive(on: DispatchQueue.main)
.eraseToAnyPublisher()
}
}

View file

@ -1,11 +1,10 @@
import Combine
import CoreData
import Foundation
import Models
import SwiftGraphQL
public extension DataService {
func newsletterEmailsPublisher() -> AnyPublisher<[NSManagedObjectID], ServerError> {
func newsletterEmails() async throws -> [NSManagedObjectID] {
enum QueryResult {
case success(result: [InternalNewsletterEmail])
case error(error: String)
@ -36,29 +35,26 @@ public extension DataService {
let path = appEnvironment.graphqlPath
let headers = networker.defaultHeaders
let context = backgroundContext
return Deferred {
Future { promise in
send(query, to: path, headers: headers) { result in
switch result {
case let .success(payload):
switch payload.data {
case let .success(result: result):
if let newsletterEmailObjectIDs = result.persist(context: self.backgroundContext) {
promise(.success(newsletterEmailObjectIDs))
} else {
promise(.failure(.unknown))
}
case .error:
promise(.failure(.unknown))
}
case .failure:
promise(.failure(.unknown))
return try await withCheckedThrowingContinuation { continuation in
send(query, to: path, headers: headers) { queryResult in
guard let payload = try? queryResult.get() else {
continuation.resume(throwing: BasicError.message(messageText: "network request failed"))
return
}
switch payload.data {
case let .success(result: result):
if let newsletterEmailObjectIDs = result.persist(context: context) {
continuation.resume(returning: newsletterEmailObjectIDs)
} else {
continuation.resume(throwing: BasicError.message(messageText: "CoreData error"))
}
case .error:
continuation.resume(throwing: BasicError.message(messageText: "Newsletter Email fetch error"))
}
}
}
.receive(on: DispatchQueue.main)
.eraseToAnyPublisher()
}
}

View file

@ -27,7 +27,9 @@ struct InternalNewsletterEmail {
}
func asManagedObject(inContext context: NSManagedObjectContext) -> NewsletterEmail {
let newsletterEmail = NewsletterEmail(entity: NewsletterEmail.entity(), insertInto: context)
let existingEmail = NewsletterEmail.lookup(byID: emailId, inContext: context)
let newsletterEmail = existingEmail ?? NewsletterEmail(entity: NewsletterEmail.entity(), insertInto: context)
newsletterEmail.emailId = emailId
newsletterEmail.email = email
newsletterEmail.confirmationCode = confirmationCode

View file

@ -14,6 +14,4 @@ public enum FeatureFlag {
public static let enablePushNotifications = false
public static let enableShareButton = false
public static let enableSnooze = false
public static let enableLabels = true
public static let useLocalWebView = true
}

View file

@ -149,20 +149,16 @@ public struct GridCard: View {
.onTapGesture { tapHandler() }
// Category Labels
if FeatureFlag.enableLabels {
ScrollView(.horizontal, showsIndicators: false) {
HStack {
ForEach(item.labels.asArray(of: LinkedItemLabel.self), id: \.self) {
TextChip(feedItemLabel: $0)
}
Spacer()
ScrollView(.horizontal, showsIndicators: false) {
HStack {
ForEach(item.labels.asArray(of: LinkedItemLabel.self), id: \.self) {
TextChip(feedItemLabel: $0)
}
.frame(height: 30)
.padding(.horizontal)
.padding(.bottom, 8)
Spacer()
}
} else {
Spacer(minLength: 8)
.frame(height: 30)
.padding(.horizontal)
.padding(.bottom, 8)
}
}
.background(