Merge branch 'main' of github.com:omnivore-app/omnivore into issue-835

This commit is contained in:
Rupin Khandelwal
2022-06-23 11:50:59 -07:00
21 changed files with 181 additions and 130 deletions

View File

@ -159,7 +159,7 @@
"location" : "https://github.com/PSPDFKit/PSPDFKit-SP",
"state" : {
"branch" : "master",
"revision" : "0e18629c443e3f39ecfee0f600d9ef5551ecf488"
"revision" : "b7e5465ab62f5b48735145756e371502fa2a24f0"
}
},
{

View File

@ -1,10 +1,3 @@
//
// File.swift
//
//
// Created by Jackson Harper on 6/1/22.
//
import Foundation
import Models
import Services
@ -18,23 +11,25 @@ class ExtensionSaveService {
self.queue = OperationQueue()
}
private func queueSaveOperation(
_ pageScrape: PageScrapePayload,
shareExtensionViewModel: ShareExtensionChildViewModel
) {
ProcessInfo().performExpiringActivity(withReason: "app.omnivore.SaveActivity") { [self] expiring in
guard !expiring else {
self.queue.cancelAllOperations()
#if os(iOS)
private func queueSaveOperation(
_ pageScrape: PageScrapePayload,
shareExtensionViewModel: ShareExtensionChildViewModel
) {
ProcessInfo().performExpiringActivity(withReason: "app.omnivore.SaveActivity") { [self] expiring in
guard !expiring else {
self.queue.cancelAllOperations()
self.queue.waitUntilAllOperationsAreFinished()
return
}
let operation = SaveOperation(pageScrapePayload: pageScrape, shareExtensionViewModel: shareExtensionViewModel)
self.queue.addOperation(operation)
self.queue.waitUntilAllOperationsAreFinished()
return
}
let operation = SaveOperation(pageScrapePayload: pageScrape, shareExtensionViewModel: shareExtensionViewModel)
self.queue.addOperation(operation)
self.queue.waitUntilAllOperationsAreFinished()
}
}
#endif
public func save(_ extensionContext: NSExtensionContext, shareExtensionViewModel: ShareExtensionChildViewModel) {
PageScraper.scrape(extensionContext: extensionContext) { [weak self] result in
@ -71,7 +66,10 @@ class ExtensionSaveService {
}
}
}
self.queueSaveOperation(payload, shareExtensionViewModel: shareExtensionViewModel)
#if os(iOS)
// TODO: need alternative call for macos
self.queueSaveOperation(payload, shareExtensionViewModel: shareExtensionViewModel)
#endif
case .failure:
DispatchQueue.main.async {
shareExtensionViewModel.status = .failed(error: .unknown(description: "Could not retrieve content"))

View File

@ -66,24 +66,26 @@ struct LinkedItemTitleEditView: View {
NavigationView {
editForm
.navigationTitle("Edit Title and Description")
#if os(iOS)
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .barTrailing) {
Button(
action: {
viewModel.submit(dataService: dataService, item: item)
presentationMode.wrappedValue.dismiss()
},
label: { Text("Save").foregroundColor(.appGrayTextContrast) }
)
}
ToolbarItem(placement: .barLeading) {
Button(
action: { presentationMode.wrappedValue.dismiss() },
label: { Text("Cancel").foregroundColor(.appGrayTextContrast) }
)
}
#endif
.toolbar {
ToolbarItem(placement: .barTrailing) {
Button(
action: {
viewModel.submit(dataService: dataService, item: item)
presentationMode.wrappedValue.dismiss()
},
label: { Text("Save").foregroundColor(.appGrayTextContrast) }
)
}
ToolbarItem(placement: .barLeading) {
Button(
action: { presentationMode.wrappedValue.dismiss() },
label: { Text("Cancel").foregroundColor(.appGrayTextContrast) }
)
}
}
}
.task { viewModel.load(item: item) }
}

View File

@ -59,8 +59,7 @@ import Views
}
func handleGoogleAuth(authenticator: Authenticator) async {
guard let presentingViewController = presentingViewController() else { return }
let googleAuthResponse = await authenticator.handleGoogleAuth(presenting: presentingViewController)
let googleAuthResponse = await authenticator.handleGoogleAuth()
switch googleAuthResponse {
case let .loginError(error):
@ -72,15 +71,3 @@ import Views
}
}
}
private func presentingViewController() -> PlatformViewController? {
#if os(iOS)
let scene = UIApplication.shared.connectedScenes.first as? UIWindowScene
return scene?.windows
.filter(\.isKeyWindow)
.first?
.rootViewController
#elseif os(macOS)
return nil
#endif
}

View File

@ -139,6 +139,16 @@ import WebKit
func loadContent(webView: WKWebView) {
let fontFamilyValue = UserDefaults.standard.string(forKey: UserDefaultKey.preferredWebFont.rawValue)
let prefersHighContrastText: Bool = {
let key = UserDefaultKey.prefersHighContrastWebFont.rawValue
if UserDefaults.standard.object(forKey: key) != nil {
return UserDefaults.standard.bool(forKey: key)
} else {
return true
}
}()
let fontFamily = fontFamilyValue.flatMap { WebFont(rawValue: $0) } ?? .inter
webView.loadHTMLString(
@ -149,7 +159,8 @@ import WebKit
fontSize: fontSize(),
lineHeight: lineHeight(),
maxWidthPercentage: maxWidthPercentage(),
fontFamily: fontFamily
fontFamily: fontFamily,
prefersHighContrastText: prefersHighContrastText
)
.styledContent,
baseURL: ViewsPackage.bundleURL

View File

@ -11,6 +11,7 @@ struct WebReaderContent {
let themeKey: String
let fontFamily: WebFont
let articleContent: ArticleContent
let prefersHighContrastText: Bool
init(
item: LinkedItem,
@ -19,7 +20,8 @@ struct WebReaderContent {
fontSize: Int,
lineHeight: Int,
maxWidthPercentage: Int,
fontFamily: WebFont
fontFamily: WebFont,
prefersHighContrastText: Bool
) {
self.textFontSize = fontSize
self.lineHeight = lineHeight
@ -28,6 +30,7 @@ struct WebReaderContent {
self.themeKey = isDark ? "Gray" : "LightGray"
self.fontFamily = fontFamily
self.articleContent = articleContent
self.prefersHighContrastText = prefersHighContrastText
}
// swiftlint:disable line_length
@ -82,6 +85,7 @@ struct WebReaderContent {
window.maxWidthPercentage = \(maxWidthPercentage)
window.lineHeight = \(lineHeight)
window.localStorage.setItem("theme", "\(themeKey)")
window.prefersHighContrastFont = \(prefersHighContrastText)
</script>
<script src="bundle.js"></script>
<script src="mathJaxConfiguration.js" id="MathJax-script"></script>

View File

@ -14,9 +14,14 @@ public class PersistentContainer: NSPersistentContainer {
// Store the sqlite file in the app group container.
// This allows shared access for app and app extensions.
let appGroupID = "group.app.omnivoreapp"
#if os(iOS)
let appGroupID = "group.app.omnivoreapp"
#else
let appGroupID = "QJF2XZ86HB.app.omnivore.app"
#endif
let appGroupContainer = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroupID)
let appGroupContainerURL = appGroupContainer?.appendingPathComponent("store.sqlite")
container.persistentStoreDescriptions.first!.url = appGroupContainerURL
container.viewContext.automaticallyMergesChangesFromParent = true

View File

@ -10,8 +10,11 @@ public enum GoogleAuthResponse {
}
extension Authenticator {
public func handleGoogleAuth(presenting: PlatformViewController) async -> GoogleAuthResponse {
let idToken = try? await googleSignIn(presenting: presenting)
public func handleGoogleAuth() async -> GoogleAuthResponse {
let idToken = await withCheckedContinuation { continuation in
googleSignIn { continuation.resume(returning: $0) }
}
guard let idToken = idToken else { return .loginError(error: .unauthorized) }
do {
@ -47,27 +50,47 @@ extension Authenticator {
}
}
func googleSignIn(presenting: PlatformViewController) async throws -> String {
try await withCheckedThrowingContinuation { continuation in
let clientID = "\(AppKeys.sharedInstance?.iosClientGoogleId ?? "").apps.googleusercontent.com"
GIDSignIn.sharedInstance.signIn(
with: GIDConfiguration(clientID: clientID),
presenting: presenting
) { user, error in
guard let user = user, error == nil else {
continuation.resume(throwing: LoginError.unauthorized)
func googleSignIn(completion: @escaping (String?) -> Void) {
#if os(iOS)
let presenting = presentingViewController()
#else
let presenting = NSApplication.shared.windows.first
#endif
guard let presenting = presenting else {
completion(nil)
return
}
let clientID = "\(AppKeys.sharedInstance?.iosClientGoogleId ?? "").apps.googleusercontent.com"
GIDSignIn.sharedInstance.signIn(
with: GIDConfiguration(clientID: clientID),
presenting: presenting
) { user, error in
guard let user = user, error == nil else {
completion(nil)
return
}
user.authentication.do { authentication, error in
guard let idToken = authentication?.idToken, error == nil else {
completion(nil)
return
}
user.authentication.do { authentication, error in
guard let idToken = authentication?.idToken, error == nil else {
continuation.resume(throwing: LoginError.unauthorized)
return
}
continuation.resume(returning: idToken)
}
completion(idToken)
}
}
}
}
private func presentingViewController() -> PlatformViewController? {
#if os(iOS)
let scene = UIApplication.shared.connectedScenes.first as? UIWindowScene
return scene?.windows
.filter(\.isKeyWindow)
.first?
.rootViewController
#elseif os(macOS)
return nil
#endif
}

View File

@ -4,9 +4,14 @@ import Foundation
import Models
import OSLog
import QuickLookThumbnailing
import UIKit
import Utils
#if os(iOS)
import UIKit
#else
import AppKit
#endif
let logger = Logger(subsystem: "app.omnivore", category: "data-service")
public final class DataService: ObservableObject {

View File

@ -1,7 +1,11 @@
import CoreImage
import Foundation
import QuickLookThumbnailing
import UIKit
#if os(iOS)
import UIKit
#else
import AppKit
#endif
public enum PDFUtils {
public static func copyToLocal(url: URL) throws -> String {
@ -64,7 +68,11 @@ public enum PDFUtils {
public static func createThumbnailFor(inputUrl: URL) async throws -> URL? {
let size = CGSize(width: 80, height: 80)
let scale = await UIScreen.main.scale
#if os(iOS)
let scale = await UIScreen.main.scale
#else
let scale = NSScreen.main?.backingScaleFactor ?? 1
#endif
let outputUrl = thumbnailUrl(localUrl: inputUrl)
// Create the thumbnail request.

View File

@ -129,7 +129,7 @@ public enum WebViewManager {
}
func fontSize() -> Int {
let storedSize = UserDefaults.standard.integer(forKey: "preferredWebFontSize")
let storedSize = UserDefaults.standard.integer(forKey: UserDefaultKey.preferredWebFontSize.rawValue)
return storedSize <= 1 ? Int(NSFont.userFont(ofSize: 16)?.pointSize ?? 16) : storedSize
}
@ -168,14 +168,19 @@ public enum WebViewManager {
context.coordinator.needsReload = false
}
if annotationSaveTransactionID != context.coordinator.lastSavedAnnotationID {
context.coordinator.lastSavedAnnotationID = annotationSaveTransactionID
(webView as? WebView)?.dispatchEvent(.saveAnnotation(annotation: annotation))
}
if sendIncreaseFontSignal {
sendIncreaseFontSignal = false
(webView as? WebView)?.increaseFontSize()
(webView as? WebView)?.updateFontSize()
}
if sendDecreaseFontSignal {
sendDecreaseFontSignal = false
(webView as? WebView)?.decreaseFontSize()
(webView as? WebView)?.updateFontSize()
}
}
}

View File

@ -86,9 +86,9 @@ public final class WebView: WKWebView {
super.viewDidChangeEffectiveAppearance()
switch effectiveAppearance.bestMatch(from: [.aqua, .darkAqua]) {
case .some(.darkAqua):
dispatchEvent("switchToDarkMode")
dispatchEvent(.updateColorMode(isDark: true))
default:
dispatchEvent("switchToLightMode")
dispatchEvent(.updateColorMode(isDark: false))
}
}
#endif

View File

@ -42,7 +42,7 @@ public struct WebPreferencesPopoverView: View {
@AppStorage(UserDefaultKey.preferredWebLineSpacing.rawValue) var storedLineSpacing = 150
@AppStorage(UserDefaultKey.preferredWebMaxWidthPercentage.rawValue) var storedMaxWidthPercentage = 100
@AppStorage(UserDefaultKey.preferredWebFont.rawValue) var preferredFont = WebFont.inter.rawValue
@AppStorage(UserDefaultKey.prefersHighContrastWebFont.rawValue) var prefersHighContrastText = false
@AppStorage(UserDefaultKey.prefersHighContrastWebFont.rawValue) var prefersHighContrastText = true
public init(
updateFontFamilyAction: @escaping () -> Void,
@ -81,7 +81,9 @@ public struct WebPreferencesPopoverView: View {
}
}
.listStyle(.plain)
.navigationBarTitleDisplayMode(.inline)
#if os(iOS)
.navigationBarTitleDisplayMode(.inline)
#endif
.navigationTitle("Reader Font")
}
@ -148,9 +150,11 @@ public struct WebPreferencesPopoverView: View {
}
.padding()
.navigationTitle("Reader Preferences")
.navigationBarTitleDisplayMode(.inline)
#if os(iOS)
.navigationBarTitleDisplayMode(.inline)
#endif
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
ToolbarItem(placement: .barTrailing) {
Button(
action: dismissAction,
label: { Text("Done").foregroundColor(.appGrayTextContrast).padding() }
@ -158,7 +162,9 @@ public struct WebPreferencesPopoverView: View {
}
}
}
.navigationViewStyle(.stack)
#if os(iOS)
.navigationViewStyle(.stack)
#endif
.accentColor(.appGrayTextContrast)
}
}

File diff suppressed because one or more lines are too long

View File

@ -37,36 +37,6 @@ public enum ShareExtensionStatus {
}
}
struct CornerRadiusStyle: ViewModifier {
var radius: CGFloat
var corners: UIRectCorner
struct CornerRadiusShape: Shape {
var radius = CGFloat.infinity
var corners = UIRectCorner.allCorners
func path(in rect: CGRect) -> Path {
let path = UIBezierPath(
roundedRect: rect,
byRoundingCorners: corners,
cornerRadii: CGSize(width: radius, height: radius)
)
return Path(path.cgPath)
}
}
func body(content: Content) -> some View {
content
.clipShape(CornerRadiusShape(radius: radius, corners: corners))
}
}
extension View {
func cornerRadius(_ radius: CGFloat, corners: UIRectCorner) -> some View {
ModifiedContent(content: self, modifier: CornerRadiusStyle(radius: radius, corners: corners))
}
}
private extension SaveArticleError {
var displayMessage: String {
switch self {
@ -197,9 +167,15 @@ public struct ShareExtensionChildView: View {
}
private func localImage(from url: URL) -> Image? {
if let data = try? Data(contentsOf: url), let img = UIImage(data: data) {
return Image(uiImage: img)
}
#if os(iOS)
if let data = try? Data(contentsOf: url), let img = UIImage(data: data) {
return Image(uiImage: img)
}
#else
if let data = try? Data(contentsOf: url), let img = NSImage(data: data) {
return Image(nsImage: img)
}
#endif
return nil
}

View File

@ -48,8 +48,8 @@ const appendReadFilter = (body: SearchBody, filter: ReadFilter): void => {
case ReadFilter.UNREAD:
body.query.bool.filter.push({
range: {
readingProgress: {
gte: 98,
readingProgressPercent: {
lt: 98,
},
},
})
@ -57,8 +57,8 @@ const appendReadFilter = (body: SearchBody, filter: ReadFilter): void => {
case ReadFilter.READ:
body.query.bool.filter.push({
range: {
readingProgress: {
lt: 98,
readingProgressPercent: {
gte: 98,
},
},
})

View File

@ -14,7 +14,7 @@ export interface SearchBody {
| { exists: { field: string } }
| {
range: {
readingProgress: { gte: number } | { lt: number }
readingProgressPercent: { gte: number } | { lt: number }
}
}
| {

View File

@ -1003,5 +1003,22 @@ describe('Article API', () => {
expect(res.body.data.search.edges[4].node.id).to.eq(highlights[0].id)
})
})
context('when is:unread is in the query', () => {
before(() => {
keyword = 'search is:unread'
})
it('should return unread articles in descending order', async () => {
const res = await graphqlRequest(query, authToken).expect(200)
expect(res.body.data.search.edges.length).to.eq(5)
expect(res.body.data.search.edges[0].node.id).to.eq(pages[4].id)
expect(res.body.data.search.edges[1].node.id).to.eq(pages[3].id)
expect(res.body.data.search.edges[2].node.id).to.eq(pages[2].id)
expect(res.body.data.search.edges[3].node.id).to.eq(pages[1].id)
expect(res.body.data.search.edges[4].node.id).to.eq(pages[0].id)
})
})
})
})

View File

@ -44,6 +44,7 @@ const App = () => {
margin={window.margin}
maxWidthPercentage={window.maxWidthPercentage}
lineHeight={window.lineHeight}
highContrastFont={window.prefersHighContrastFont ?? true}
articleMutations={{
createHighlightMutation: (input) =>
mutation('createHighlight', input),

View File

@ -82,8 +82,8 @@ export function PrimaryLayout(props: PrimaryLayoutProps): JSX.Element {
/>
<Box
css={{
minHeight: '100%',
minWidth: '100vw',
height: '100%',
width: '100vw',
bg: '$grayBase',
}}
>

View File

@ -230,6 +230,9 @@ export const lighterTheme = createTheme(ThemeId.Lighter, {})
// Apply global styles in here
export const globalStyles = globalCss({
'body': {
backgroundColor: '$grayBase'
},
'*': {
'&:focus': {
outline: 'none',