More on ios share extension improvements

This commit is contained in:
Jackson Harper
2023-10-27 09:56:41 +08:00
parent cf3427fdc9
commit 8fe51a5f80
13 changed files with 477 additions and 79 deletions

View File

@ -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
}
}
}
}

View File

@ -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()
}))
}
}
}

View File

@ -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 {

View File

@ -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))
}

View File

@ -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)

View File

@ -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) }

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -13,7 +13,7 @@ import Utils
public struct SyncStatusIcon: View {
let status: ServerSyncStatus
init(status: ServerSyncStatus) {
public init(status: ServerSyncStatus) {
self.status = status
}

View File

@ -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)