mirror of
https://github.com/omnivore-app/omnivore.git
synced 2026-03-11 08:54:26 +00:00
Merge pull request #499 from omnivore-app/fix/double-nav-bar
Keep iOS NavBar hidden
This commit is contained in:
commit
6440709e20
12 changed files with 120 additions and 119 deletions
|
|
@ -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
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -72,10 +72,8 @@ struct ProfileView: View {
|
|||
}
|
||||
|
||||
Section {
|
||||
if FeatureFlag.enableLabels {
|
||||
NavigationLink(destination: LabelsView()) {
|
||||
Text("Labels")
|
||||
}
|
||||
NavigationLink(destination: LabelsView()) {
|
||||
Text("Labels")
|
||||
}
|
||||
|
||||
NavigationLink(destination: NewsletterEmailsView()) {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
Loading…
Reference in a new issue