diff --git a/apple/OmnivoreKit/Sources/App/AppExtensions/Share/ShareExtensionViewModel.swift b/apple/OmnivoreKit/Sources/App/AppExtensions/Share/ShareExtensionViewModel.swift index 1602d0421..0dfd96a14 100644 --- a/apple/OmnivoreKit/Sources/App/AppExtensions/Share/ShareExtensionViewModel.swift +++ b/apple/OmnivoreKit/Sources/App/AppExtensions/Share/ShareExtensionViewModel.swift @@ -9,6 +9,7 @@ public class ShareExtensionViewModel: ObservableObject { @Published public var status: ShareExtensionStatus = .processing @Published public var title: String = "" @Published public var url: String? + @Published public var iconURL: URL? @Published public var highlightData: HighlightData? @Published public var linkedItem: LinkedItem? @Published public var requestId = UUID().uuidString.lowercased() @@ -88,9 +89,10 @@ public class ShareExtensionViewModel: ObservableObject { let hostname = URL(string: payload.url)?.host ?? "" switch payload.contentType { - case let .html(html: _, title: title, highlightData: highlightData): + case let .html(html: _, title: title, iconURL: iconURL, highlightData: highlightData): self.title = title ?? "" self.url = hostname + self.iconURL = iconURL self.highlightData = highlightData case .none: self.url = hostname @@ -145,7 +147,7 @@ public class ShareExtensionViewModel: ObservableObject { localPdfURL: localUrl, url: pageScrapePayload.url ) - case let .html(html, title, _): + case let .html(html, title, _, _): newRequestID = try await services.dataService.createPage( id: requestId, originalHtml: html, @@ -187,7 +189,11 @@ public class ShareExtensionViewModel: ObservableObject { if let title = self.linkedItem?.title { self.title = title } - self.url = self.linkedItem?.pageURLString + if let urlStr = self.linkedItem?.pageURLString, let hostname = URL(string: urlStr)?.host { + self.url = hostname + } else { + self.url = self.linkedItem?.pageURLString + } } } } diff --git a/apple/OmnivoreKit/Sources/App/AppExtensions/Share/Views/AddNoteSheet.swift b/apple/OmnivoreKit/Sources/App/AppExtensions/Share/Views/AddNoteSheet.swift new file mode 100644 index 000000000..e599eb797 --- /dev/null +++ b/apple/OmnivoreKit/Sources/App/AppExtensions/Share/Views/AddNoteSheet.swift @@ -0,0 +1,46 @@ +// +// AddNoteSheet.swift +// +// +// Created by Jackson Harper on 10/26/23. +// + +import Models +import Services +import SwiftUI +import Utils +import Views + +public struct AddNoteSheet: View { + @State var text = "" + + enum FocusField: Hashable { + case noteEditor + } + + @FocusState private var focusedField: FocusField? + + public init() { + UITextView.appearance().textContainerInset = UIEdgeInsets(top: 5, left: 2, bottom: 5, right: 2) + } + + public var body: some View { + NavigationView { + TextEditor(text: $text) + .frame(maxWidth: .infinity, maxHeight: .infinity) + .focused($focusedField, equals: .noteEditor) + .task { + self.focusedField = .noteEditor + } + .background(Color.extensionPanelBackground) + .navigationTitle("Add Note") + .navigationBarTitleDisplayMode(.inline) + .navigationBarItems(leading: Button(action: {}, label: { + Text("Cancel") + })) + .navigationBarItems(trailing: Button(action: {}, label: { + Text("Save").bold() + })) + } + } +} diff --git a/apple/OmnivoreKit/Sources/App/AppExtensions/Share/Views/ShareExtensionView.swift b/apple/OmnivoreKit/Sources/App/AppExtensions/Share/Views/ShareExtensionView.swift index 16f960923..f1741b345 100644 --- a/apple/OmnivoreKit/Sources/App/AppExtensions/Share/Views/ShareExtensionView.swift +++ b/apple/OmnivoreKit/Sources/App/AppExtensions/Share/Views/ShareExtensionView.swift @@ -10,8 +10,6 @@ public struct ShareExtensionView: View { @StateObject var labelsViewModel = LabelsViewModel() @StateObject private var viewModel = ShareExtensionViewModel() - @State var reminderTime: ReminderTime? - @State var hideUntilReminded = false @State var previousLabels: [LinkedItemLabel]? @State var messageText: String? @State var showSearchLabels = false @@ -19,6 +17,8 @@ public struct ShareExtensionView: View { @State var viewState = ViewState.mainView @State var showHighlightInstructionAlert = false + @State var showAddNoteModal = false + enum FocusField: Hashable { case titleEditor } @@ -32,16 +32,6 @@ public struct ShareExtensionView: View { @FocusState private var focusedField: FocusField? - private func handleReminderTimeSelection(_ selectedTime: ReminderTime) { - if selectedTime == reminderTime { - reminderTime = nil - hideUntilReminded = false - } else { - reminderTime = selectedTime - hideUntilReminded = true - } - } - private var titleText: String { switch viewModel.status { case .saved, .synced, .syncFailed(error: _): @@ -86,22 +76,22 @@ public struct ShareExtensionView: View { } } - var titleBar: some View { - HStack { - Spacer() - - Image(systemName: "checkmark.circle") - .frame(width: 15, height: 15) - .foregroundColor(.appGreenSuccess) - .opacity(isSynced ? 1.0 : 0.0) - - Text(messageText ?? titleText) - .font(.appSubheadline) - .foregroundColor(titleColor) - - Spacer() - } - } +// var titleBar: some View { +// HStack { +// Spacer() +// +// Image(systemName: "checkmark.circle") +// .frame(width: 15, height: 15) +// .foregroundColor(.appGreenSuccess) +// .opacity(isSynced ? 1.0 : 0.0) +// +// Text(messageText ?? titleText) +// .font(.appSubheadline) +// .foregroundColor(titleColor) +// +// Spacer() +// } +// } public var titleBox: some View { VStack(alignment: .trailing) { @@ -208,7 +198,7 @@ public struct ShareExtensionView: View { } } .padding(viewState == .editingLabels ? 0 : 16) - .background(viewState == .editingLabels ? Color.clear : Color.appButtonBackground) + .background(Color.extensionBackground) .frame(maxWidth: .infinity, maxHeight: viewState == .editingLabels ? .infinity : 60) .cornerRadius(8) } @@ -307,12 +297,12 @@ public struct ShareExtensionView: View { var moreActionsMenu: some View { Menu { - Button( - action: {}, - label: { - Button(LocalText.dismissButton, role: .cancel, action: {}) - } - ) + Button(action: {}, label: { + Label( + "Edit Info", + systemImage: "info.circle" + ) + }) Button(action: { if let linkedItem = self.viewModel.linkedItem { self.viewModel.setLinkArchived(dataService: self.viewModel.services.dataService, @@ -378,55 +368,191 @@ public struct ShareExtensionView: View { viewState = .mainView } - public var body: some View { - VStack(alignment: .leading) { - HStack { - Text("Saved to Omnivore") - .font(Font.system(size: 22, weight: .bold)) + var articleInfoBox: some View { + HStack(alignment: .top, spacing: 15) { + AsyncImage(url: self.viewModel.iconURL) + .frame(width: 56, height: 56).overlay( + RoundedRectangle(cornerRadius: 14) + .stroke(.white, lineWidth: 1) + ).cornerRadius(14) + VStack(alignment: .leading) { + Text(self.viewModel.url ?? "") + .font(Font.system(size: 12)) + .lineLimit(1) + .foregroundColor(Color(hex: "EBEBF5")?.opacity(0.85)) + .frame(height: 14) + + Text(self.viewModel.title) + .font(Font.system(size: 13, weight: .semibold)) + .lineSpacing(1.25) + .foregroundColor(.appGrayTextContrast) + .fixedSize(horizontal: false, vertical: true) + .lineLimit(2) + .frame(height: 33) .frame(maxWidth: .infinity, alignment: .leading) + }.padding(.vertical, 2) + // Spacer() + Image(systemName: "checkmark.circle") + .frame(width: 15, height: 15) + .foregroundColor(.appGreenSuccess) + // .opacity(isSynced ? 1.0 : 0.0) + } + } - Spacer() - Button(action: {}, label: { - ZStack { - Circle() - .foregroundColor(Color(hex: "#3D3D3D")) - .frame(width: 30, height: 30) + var noteBox: some View { + Button(action: { + NotificationCenter.default.post(name: Notification.Name("ExpandForm"), object: nil) + // showAddNoteModal = true + }, label: { Text("Add note...") }) + .foregroundColor(Color.extensionTextSubtle) + .font(Font.system(size: 13, weight: .semibold)) + .frame(height: 50, alignment: .top) + .frame(maxWidth: .infinity, alignment: .leading) + } - Image(systemName: "xmark") - .resizable(resizingMode: Image.ResizingMode.stretch) - .foregroundColor(Color(hex: "#D9D9D9")) - .aspectRatio(contentMode: .fit) - .font(Font.title.weight(.medium)) - .frame(width: 10, height: 10) + var labelsBox: some View { + Button(action: {}, label: { + Label { + Text("Add Labels").font(Font.system(size: 12, weight: .medium)).tint(Color.white) + } icon: { + Image.label.resizable(resizingMode: .stretch).frame(width: 17, height: 17).tint(Color.white) + }.padding(.leading, 10).padding(.trailing, 12) + }) + .frame(height: 28) + .background(Color.blue) + .cornerRadius(24) + } + + var infoBox: some View { + VStack(alignment: .leading, spacing: 15) { + articleInfoBox + + Divider() + .frame(maxWidth: .infinity) + .frame(height: 1) + .background(Color(hex: "545458")?.opacity(0.65)) + + noteBox + + labelsBox + }.padding(15) + .background(Color.extensionPanelBackground) + .cornerRadius(14) + } + + var moreMenuButton: some View { + Menu { + Button(action: {}, label: { + Label( + "Edit Info", + systemImage: "info.circle" + ) + }) + Button(action: { + if let linkedItem = self.viewModel.linkedItem { + self.viewModel.setLinkArchived(dataService: self.viewModel.services.dataService, + objectID: linkedItem.objectID, + archived: true) + messageText = "Link Archived" + DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(300)) { + extensionContext?.completeRequest(returningItems: [], completionHandler: nil) } - }) - Button(action: {}, label: { - ZStack { - Circle() - .foregroundColor(Color(hex: "#3D3D3D")) - .frame(width: 30, height: 30) - - Image(systemName: "xmark") - .resizable(resizingMode: Image.ResizingMode.stretch) - .foregroundColor(Color(hex: "#D9D9D9")) - .aspectRatio(contentMode: .fit) - .font(Font.title.weight(.medium)) - .frame(width: 10, height: 10) + } + }, label: { + Label( + "Archive", + systemImage: "archivebox" + ) + }) + Button( + action: { + if let linkedItem = self.viewModel.linkedItem { + self.viewModel.removeLink(dataService: self.viewModel.services.dataService, objectID: linkedItem.objectID) + messageText = "Link Removed" + DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(300)) { + extensionContext?.completeRequest(returningItems: [], completionHandler: nil) + } } - }) - }.padding(20) + }, + label: { + Label("Remove", systemImage: "trash") + } + ) + } label: { + ZStack { + Circle() + .foregroundColor(Color.circleButtonBackground) + .frame(width: 30, height: 30) + + Image(systemName: "ellipsis") + .resizable(resizingMode: Image.ResizingMode.stretch) + .foregroundColor(Color.circleButtonForeground) + .aspectRatio(contentMode: .fit) + .frame(width: 15, height: 15) + } + } + } + + var closeButton: some View { + Button(action: { + extensionContext?.completeRequest(returningItems: [], completionHandler: nil) + }, label: { + ZStack { + Circle() + .foregroundColor(Color.circleButtonBackground) + .frame(width: 30, height: 30) + + Image(systemName: "xmark") + .resizable(resizingMode: Image.ResizingMode.stretch) + .foregroundColor(Color.circleButtonForeground) + .aspectRatio(contentMode: .fit) + .font(Font.title.weight(.bold)) + .frame(width: 12, height: 12) + } + }) + } + + var titleBar: some View { + HStack { + Text("Saved to Omnivore") + .font(Font.system(size: 22, weight: .bold)) + .frame(maxWidth: .infinity, alignment: .leading) Spacer() + moreMenuButton + closeButton + } + } + + public var body: some View { + VStack(alignment: .leading, spacing: 15) { + titleBar + .padding(.top, 15) + + infoBox + + Spacer(minLength: 1) HStack { Spacer() - Button(action: {}, label: { Text("Read Now").font(Font.system(size: 17, weight: .semibold)).padding(20).tint(Color.white) }) + Button(action: { + viewModel.handleReadNowAction(extensionContext: extensionContext) + }, label: { + Text("Read Now") + .font(Font.system(size: 17, weight: .semibold)) + .tint(Color.white) + .padding(20) + }) .frame(height: 50) .background(Color.blue) .cornerRadius(24) + .padding(15) }.frame(maxWidth: .infinity) - .padding(20) - } + }.padding(.horizontal, 15) + .background(Color.extensionBackground) + .onAppear { + viewModel.savePage(extensionContext: extensionContext) + } } public var oldbody: some View { diff --git a/apple/OmnivoreKit/Sources/Models/PageScrapePayload.swift b/apple/OmnivoreKit/Sources/Models/PageScrapePayload.swift index 28004ce07..401a6a1ad 100644 --- a/apple/OmnivoreKit/Sources/Models/PageScrapePayload.swift +++ b/apple/OmnivoreKit/Sources/Models/PageScrapePayload.swift @@ -28,7 +28,7 @@ public struct PageScrapePayload { public enum ContentType { case none case pdf(localUrl: URL) - case html(html: String, title: String?, highlightData: HighlightData?) + case html(html: String, title: String?, iconURL: URL?, highlightData: HighlightData?) } public let url: String @@ -49,9 +49,9 @@ public struct PageScrapePayload { self.contentType = .pdf(localUrl: localUrl) } - init(url: String, title: String?, html: String, highlightData: HighlightData?) { + init(url: String, title: String?, html: String, iconURL: URL?, highlightData: HighlightData?) { self.url = url - self.contentType = .html(html: html, title: title, highlightData: highlightData) + self.contentType = .html(html: html, title: title, iconURL: iconURL, highlightData: highlightData) } } @@ -319,6 +319,11 @@ private extension PageScrapePayload { let html = results?["originalHTML"] as? String let title = results?["title"] as? String let contentType = results?["contentType"] as? String + var iconURL: URL? + + if let urlStr = results?["iconURL"] as? String { + iconURL = URL(string: urlStr) + } // If we were not able to capture any HTML, treat this as a URL and // see if the backend can do better. @@ -336,6 +341,7 @@ private extension PageScrapePayload { return PageScrapePayload(url: url, title: title, html: html, + iconURL: iconURL, highlightData: HighlightData.make(dict: results)) } diff --git a/apple/OmnivoreKit/Sources/Services/DataService/DataService.swift b/apple/OmnivoreKit/Sources/Services/DataService/DataService.swift index 5e3fe80f1..73f017d58 100644 --- a/apple/OmnivoreKit/Sources/Services/DataService/DataService.swift +++ b/apple/OmnivoreKit/Sources/Services/DataService/DataService.swift @@ -266,7 +266,7 @@ public final class DataService: ObservableObject { linkedItem.contentReader = "PDF" linkedItem.tempPDFURL = localUrl linkedItem.title = PDFUtils.titleFromPdfFile(pageScrape.url) - case let .html(html: html, title: title, highlightData: _): + case let .html(html: html, title: title, iconURL: _, highlightData: _): linkedItem.contentReader = "WEB" linkedItem.originalHtml = html linkedItem.title = title ?? PDFUtils.titleFromPdfFile(pageScrape.url) diff --git a/apple/OmnivoreKit/Sources/Views/Colors/Colors.swift b/apple/OmnivoreKit/Sources/Views/Colors/Colors.swift index 5fffcf5ac..76f11d506 100644 --- a/apple/OmnivoreKit/Sources/Views/Colors/Colors.swift +++ b/apple/OmnivoreKit/Sources/Views/Colors/Colors.swift @@ -44,6 +44,12 @@ public extension Color { static var thFeatureSeparator: Color { Color("featureSeparator", bundle: .module) } + static var circleButtonBackground: Color { Color("_circleButtonBackground", bundle: .module) } + static var circleButtonForeground: Color { Color("_circleButtonForeground", bundle: .module) } + static var extensionBackground: Color { Color("_extensionBackground", bundle: .module) } + static var extensionPanelBackground: Color { Color("_extensionPanelBackground", bundle: .module) } + static var extensionTextSubtle: Color { Color("_extensionTextSubtle", bundle: .module) } + // Apple system UIColor equivalents #if os(iOS) static var systemBackground: Color { Color(.systemBackground) } diff --git a/apple/OmnivoreKit/Sources/Views/Colors/ThemeColors.xcassets/_circleButtonBackground.colorset/Contents.json b/apple/OmnivoreKit/Sources/Views/Colors/ThemeColors.xcassets/_circleButtonBackground.colorset/Contents.json new file mode 100644 index 000000000..0488699a4 --- /dev/null +++ b/apple/OmnivoreKit/Sources/Views/Colors/ThemeColors.xcassets/_circleButtonBackground.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xE9", + "green" : "0xE8", + "red" : "0xE8" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x3E", + "green" : "0x3C", + "red" : "0x3B" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/apple/OmnivoreKit/Sources/Views/Colors/ThemeColors.xcassets/_circleButtonForeground.colorset/Contents.json b/apple/OmnivoreKit/Sources/Views/Colors/ThemeColors.xcassets/_circleButtonForeground.colorset/Contents.json new file mode 100644 index 000000000..2d6f53df7 --- /dev/null +++ b/apple/OmnivoreKit/Sources/Views/Colors/ThemeColors.xcassets/_circleButtonForeground.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x83", + "green" : "0x81", + "red" : "0x81" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xAB", + "green" : "0xA5", + "red" : "0xA5" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/apple/OmnivoreKit/Sources/Views/Colors/ThemeColors.xcassets/_extensionBackground.colorset/Contents.json b/apple/OmnivoreKit/Sources/Views/Colors/ThemeColors.xcassets/_extensionBackground.colorset/Contents.json new file mode 100644 index 000000000..b5472bb11 --- /dev/null +++ b/apple/OmnivoreKit/Sources/Views/Colors/ThemeColors.xcassets/_extensionBackground.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xF6", + "green" : "0xF6", + "red" : "0xF6" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x20", + "green" : "0x20", + "red" : "0x20" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/apple/OmnivoreKit/Sources/Views/Colors/ThemeColors.xcassets/_extensionPanelBackground.colorset/Contents.json b/apple/OmnivoreKit/Sources/Views/Colors/ThemeColors.xcassets/_extensionPanelBackground.colorset/Contents.json new file mode 100644 index 000000000..559b615ae --- /dev/null +++ b/apple/OmnivoreKit/Sources/Views/Colors/ThemeColors.xcassets/_extensionPanelBackground.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "display-p3", + "components" : { + "alpha" : "1.000", + "blue" : "0xF2", + "green" : "0xF2", + "red" : "0xF2" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "display-p3", + "components" : { + "alpha" : "1.000", + "blue" : "0x30", + "green" : "0x30", + "red" : "0x30" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/apple/OmnivoreKit/Sources/Views/Colors/ThemeColors.xcassets/_extensionTextSubtle.colorset/Contents.json b/apple/OmnivoreKit/Sources/Views/Colors/ThemeColors.xcassets/_extensionTextSubtle.colorset/Contents.json new file mode 100644 index 000000000..ac28096a8 --- /dev/null +++ b/apple/OmnivoreKit/Sources/Views/Colors/ThemeColors.xcassets/_extensionTextSubtle.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x68", + "green" : "0x69", + "red" : "0x69" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x89", + "green" : "0x89", + "red" : "0x89" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/apple/OmnivoreKit/Sources/Views/SyncingIcon.swift b/apple/OmnivoreKit/Sources/Views/SyncingIcon.swift index de0dda106..64e2432ba 100644 --- a/apple/OmnivoreKit/Sources/Views/SyncingIcon.swift +++ b/apple/OmnivoreKit/Sources/Views/SyncingIcon.swift @@ -13,7 +13,7 @@ import Utils public struct SyncStatusIcon: View { let status: ServerSyncStatus - init(status: ServerSyncStatus) { + public init(status: ServerSyncStatus) { self.status = status } diff --git a/apple/Sources/ShareExtension/ShareExtensionViewController.swift b/apple/Sources/ShareExtension/ShareExtensionViewController.swift index 7b1384a64..06275285b 100644 --- a/apple/Sources/ShareExtension/ShareExtensionViewController.swift +++ b/apple/Sources/ShareExtension/ShareExtensionViewController.swift @@ -1,20 +1,38 @@ import App +import SwiftUI import Utils +import Views #if os(iOS) import UIKit + final class SheetViewController: UIViewController {} + @objc(ShareExtensionViewController) final class ShareExtensionViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = .clear + NotificationCenter.default.addObserver(forName: Notification.Name("ExpandForm"), object: nil, queue: OperationQueue.main) { _ in + + self.openSheet() + } + embed( childViewController: UIViewController.makeShareExtensionController(extensionContext: extensionContext), - heightRatio: 0.50 + heightRatio: 0.60 ) } + + @IBAction func openSheet() { + let hostingController = UIHostingController(rootView: AddNoteSheet()) + + present(hostingController, animated: true, completion: nil) + + // Present it w/o any adjustments so it uses the default sheet presentation. + // present(sheetViewController., animated: true, completion: nil) + } } #elseif os(macOS)