Start implementing the new share extension, mostly so we can show the sync status
This commit is contained in:
@ -10,8 +10,6 @@ import Models
|
|||||||
import Services
|
import Services
|
||||||
import Views
|
import Views
|
||||||
|
|
||||||
typealias UpdateStatusFunc = (ShareExtensionStatus) -> Void
|
|
||||||
|
|
||||||
class ExtensionSaveService {
|
class ExtensionSaveService {
|
||||||
let queue: OperationQueue
|
let queue: OperationQueue
|
||||||
|
|
||||||
@ -19,7 +17,7 @@ class ExtensionSaveService {
|
|||||||
self.queue = OperationQueue()
|
self.queue = OperationQueue()
|
||||||
}
|
}
|
||||||
|
|
||||||
private func queueSaveOperation(_ pageScrape: PageScrapePayload, updateStatusFunc: UpdateStatusFunc?) {
|
private func queueSaveOperation(_ pageScrape: PageScrapePayload, requestId: String, shareExtensionViewModel: ShareExtensionChildViewModel) {
|
||||||
ProcessInfo().performExpiringActivity(withReason: "app.omnivore.SaveActivity") { [self] expiring in
|
ProcessInfo().performExpiringActivity(withReason: "app.omnivore.SaveActivity") { [self] expiring in
|
||||||
guard !expiring else {
|
guard !expiring else {
|
||||||
self.queue.cancelAllOperations()
|
self.queue.cancelAllOperations()
|
||||||
@ -27,22 +25,47 @@ class ExtensionSaveService {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let operation = SaveOperation(pageScrapePayload: pageScrape, updateStatusFunc: updateStatusFunc)
|
let operation = SaveOperation(pageScrapePayload: pageScrape, requestId: requestId, shareExtensionViewModel: shareExtensionViewModel)
|
||||||
|
|
||||||
self.queue.addOperation(operation)
|
self.queue.addOperation(operation)
|
||||||
self.queue.waitUntilAllOperationsAreFinished()
|
self.queue.waitUntilAllOperationsAreFinished()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func save(_ extensionContext: NSExtensionContext, updateStatusFunc: UpdateStatusFunc?) {
|
public func save(_ extensionContext: NSExtensionContext, requestId: String, shareExtensionViewModel: ShareExtensionChildViewModel) {
|
||||||
PageScraper.scrape(extensionContext: extensionContext) { [weak self] result in
|
PageScraper.scrape(extensionContext: extensionContext) { [weak self] result in
|
||||||
guard let self = self else { return }
|
guard let self = self else { return }
|
||||||
|
|
||||||
switch result {
|
switch result {
|
||||||
case let .success(payload):
|
case let .success(payload):
|
||||||
self.queueSaveOperation(payload, updateStatusFunc: updateStatusFunc)
|
DispatchQueue.main.async {
|
||||||
|
shareExtensionViewModel.status = .saved
|
||||||
|
|
||||||
|
let url = URLComponents(string: payload.url)
|
||||||
|
let hostname = URL(string: payload.url)?.host ?? ""
|
||||||
|
|
||||||
|
switch payload.contentType {
|
||||||
|
case let .html(html: _, title: title, iconURL: iconURL):
|
||||||
|
shareExtensionViewModel.title = title
|
||||||
|
shareExtensionViewModel.iconURL = iconURL
|
||||||
|
shareExtensionViewModel.url = hostname
|
||||||
|
case .none:
|
||||||
|
shareExtensionViewModel.url = hostname
|
||||||
|
shareExtensionViewModel.title = "Saving: " + payload.url
|
||||||
|
if var url = url {
|
||||||
|
url.path = "/favicon.ico"
|
||||||
|
shareExtensionViewModel.iconURL = url.url?.absoluteString
|
||||||
|
}
|
||||||
|
case let .pdf(localUrl: _):
|
||||||
|
shareExtensionViewModel.title = "Saving: " + payload.url
|
||||||
|
shareExtensionViewModel.url = hostname
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.queueSaveOperation(payload, requestId: requestId, shareExtensionViewModel: shareExtensionViewModel)
|
||||||
case let .failure(error):
|
case let .failure(error):
|
||||||
print("failed", error)
|
DispatchQueue.main.async {
|
||||||
|
shareExtensionViewModel.status = .failed(error: .unknown(description: "Could not retrieve content"))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -51,7 +74,7 @@ class ExtensionSaveService {
|
|||||||
let requestId: String
|
let requestId: String
|
||||||
let services: Services
|
let services: Services
|
||||||
let pageScrapePayload: PageScrapePayload
|
let pageScrapePayload: PageScrapePayload
|
||||||
let updateStatusFunc: UpdateStatusFunc?
|
let shareExtensionViewModel: ShareExtensionChildViewModel
|
||||||
|
|
||||||
var queue: OperationQueue?
|
var queue: OperationQueue?
|
||||||
var uploadTask: URLSessionTask?
|
var uploadTask: URLSessionTask?
|
||||||
@ -62,13 +85,13 @@ class ExtensionSaveService {
|
|||||||
case finished
|
case finished
|
||||||
}
|
}
|
||||||
|
|
||||||
init(pageScrapePayload: PageScrapePayload, updateStatusFunc: UpdateStatusFunc? = nil) {
|
init(pageScrapePayload: PageScrapePayload, requestId: String, shareExtensionViewModel: ShareExtensionChildViewModel) {
|
||||||
self.pageScrapePayload = pageScrapePayload
|
self.pageScrapePayload = pageScrapePayload
|
||||||
self.updateStatusFunc = updateStatusFunc
|
self.requestId = requestId
|
||||||
|
self.shareExtensionViewModel = shareExtensionViewModel
|
||||||
|
|
||||||
self.state = .created
|
self.state = .created
|
||||||
self.services = Services()
|
self.services = Services()
|
||||||
self.requestId = UUID().uuidString.lowercased()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
open var state: State = .created {
|
open var state: State = .created {
|
||||||
@ -118,9 +141,7 @@ class ExtensionSaveService {
|
|||||||
|
|
||||||
private func updateStatus(newStatus: ShareExtensionStatus) {
|
private func updateStatus(newStatus: ShareExtensionStatus) {
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
if let updateStatusFunc = self.updateStatusFunc {
|
self.shareExtensionViewModel.status = newStatus
|
||||||
updateStatusFunc(newStatus)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -20,29 +20,27 @@ public extension PlatformViewController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final class ShareExtensionViewModel: ObservableObject {
|
public class ShareExtensionViewModel: ObservableObject {
|
||||||
@Published var title: String?
|
@Published var title: String?
|
||||||
@Published var status: ShareExtensionStatus = .processing
|
@Published var status: ShareExtensionStatus = .processing
|
||||||
@Published var debugText: String?
|
@Published var debugText: String?
|
||||||
|
|
||||||
var subscriptions = Set<AnyCancellable>()
|
|
||||||
var backgroundTask: UIBackgroundTaskIdentifier?
|
|
||||||
let requestID = UUID().uuidString.lowercased()
|
|
||||||
let saveService = ExtensionSaveService()
|
let saveService = ExtensionSaveService()
|
||||||
|
let requestId = UUID().uuidString.lowercased()
|
||||||
|
|
||||||
func handleReadNowAction(extensionContext: NSExtensionContext?) {
|
func handleReadNowAction(extensionContext: NSExtensionContext?) {
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
if let application = UIApplication.value(forKeyPath: #keyPath(UIApplication.shared)) as? UIApplication {
|
if let application = UIApplication.value(forKeyPath: #keyPath(UIApplication.shared)) as? UIApplication {
|
||||||
let deepLinkUrl = NSURL(string: "omnivore://shareExtensionRequestID/\(requestID)")
|
let deepLinkUrl = NSURL(string: "omnivore://shareExtensionRequestID/\(requestId)")
|
||||||
application.perform(NSSelectorFromString("openURL:"), with: deepLinkUrl)
|
application.perform(NSSelectorFromString("openURL:"), with: deepLinkUrl)
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
extensionContext?.completeRequest(returningItems: [], completionHandler: nil)
|
extensionContext?.completeRequest(returningItems: [], completionHandler: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func savePage(extensionContext: NSExtensionContext?) {
|
func savePage(extensionContext: NSExtensionContext?, shareExtensionViewModel: ShareExtensionChildViewModel) {
|
||||||
if let extensionContext = extensionContext {
|
if let extensionContext = extensionContext {
|
||||||
saveService.save(extensionContext, updateStatusFunc: updateStatus)
|
saveService.save(extensionContext, requestId: requestId, shareExtensionViewModel: shareExtensionViewModel)
|
||||||
} else {
|
} else {
|
||||||
updateStatus(.failed(error: .unknown(description: "Internal Error")))
|
updateStatus(.failed(error: .unknown(description: "Internal Error")))
|
||||||
}
|
}
|
||||||
@ -58,13 +56,12 @@ final class ShareExtensionViewModel: ObservableObject {
|
|||||||
struct ShareExtensionView: View {
|
struct ShareExtensionView: View {
|
||||||
let extensionContext: NSExtensionContext?
|
let extensionContext: NSExtensionContext?
|
||||||
@StateObject private var viewModel = ShareExtensionViewModel()
|
@StateObject private var viewModel = ShareExtensionViewModel()
|
||||||
|
@StateObject private var childViewModel = ShareExtensionChildViewModel()
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
ShareExtensionChildView(
|
ShareExtensionChildView(
|
||||||
debugText: viewModel.debugText,
|
viewModel: childViewModel,
|
||||||
title: viewModel.title,
|
onAppearAction: { viewModel.savePage(extensionContext: extensionContext, shareExtensionViewModel: childViewModel) },
|
||||||
status: viewModel.status,
|
|
||||||
onAppearAction: { viewModel.savePage(extensionContext: extensionContext) },
|
|
||||||
readNowButtonAction: { viewModel.handleReadNowAction(extensionContext: extensionContext) },
|
readNowButtonAction: { viewModel.handleReadNowAction(extensionContext: extensionContext) },
|
||||||
dismissButtonTappedAction: { _, _ in
|
dismissButtonTappedAction: { _, _ in
|
||||||
extensionContext?.completeRequest(returningItems: [], completionHandler: nil)
|
extensionContext?.completeRequest(returningItems: [], completionHandler: nil)
|
||||||
|
|||||||
@ -24,6 +24,8 @@ import Utils
|
|||||||
|
|
||||||
guard let username = username else { return }
|
guard let username = username else { return }
|
||||||
|
|
||||||
|
// If the page was locally created, make sure they are synced before we pull content
|
||||||
|
await dataService.syncUnsyncedArticleContent(itemID: requestID)
|
||||||
await fetchLinkedItem(dataService: dataService, requestID: requestID, username: username)
|
await fetchLinkedItem(dataService: dataService, requestID: requestID, username: username)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -308,7 +308,7 @@ extension DataService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func syncUnsyncedArticleContent(itemID: String) async {
|
public func syncUnsyncedArticleContent(itemID: String) async {
|
||||||
let linkedItemFetchRequest: NSFetchRequest<Models.LinkedItem> = LinkedItem.fetchRequest()
|
let linkedItemFetchRequest: NSFetchRequest<Models.LinkedItem> = LinkedItem.fetchRequest()
|
||||||
linkedItemFetchRequest.predicate = NSPredicate(
|
linkedItemFetchRequest.predicate = NSPredicate(
|
||||||
format: "id == %@", itemID
|
format: "id == %@", itemID
|
||||||
|
|||||||
@ -2,6 +2,15 @@ import Models
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
import Utils
|
import Utils
|
||||||
|
|
||||||
|
public class ShareExtensionChildViewModel: ObservableObject {
|
||||||
|
@Published public var status: ShareExtensionStatus = .processing
|
||||||
|
@Published public var title: String?
|
||||||
|
@Published public var url: String?
|
||||||
|
@Published public var iconURL: String?
|
||||||
|
|
||||||
|
public init() {}
|
||||||
|
}
|
||||||
|
|
||||||
public enum ShareExtensionStatus {
|
public enum ShareExtensionStatus {
|
||||||
case processing
|
case processing
|
||||||
case saved
|
case saved
|
||||||
@ -25,6 +34,32 @@ 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 {
|
private extension SaveArticleError {
|
||||||
var displayMessage: String {
|
var displayMessage: String {
|
||||||
switch self {
|
switch self {
|
||||||
@ -89,32 +124,26 @@ struct CheckmarkButtonView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public struct ShareExtensionChildView: View {
|
public struct ShareExtensionChildView: View {
|
||||||
let debugText: String?
|
let viewModel: ShareExtensionChildViewModel
|
||||||
let title: String?
|
|
||||||
let status: ShareExtensionStatus
|
|
||||||
let onAppearAction: () -> Void
|
let onAppearAction: () -> Void
|
||||||
let readNowButtonAction: () -> Void
|
let readNowButtonAction: () -> Void
|
||||||
let dismissButtonTappedAction: (ReminderTime?, Bool) -> Void
|
let dismissButtonTappedAction: (ReminderTime?, Bool) -> Void
|
||||||
|
|
||||||
|
@State var reminderTime: ReminderTime?
|
||||||
|
@State var hideUntilReminded = false
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
debugText: String?,
|
viewModel: ShareExtensionChildViewModel,
|
||||||
title: String?,
|
|
||||||
status: ShareExtensionStatus,
|
|
||||||
onAppearAction: @escaping () -> Void,
|
onAppearAction: @escaping () -> Void,
|
||||||
readNowButtonAction: @escaping () -> Void,
|
readNowButtonAction: @escaping () -> Void,
|
||||||
dismissButtonTappedAction: @escaping (ReminderTime?, Bool) -> Void
|
dismissButtonTappedAction: @escaping (ReminderTime?, Bool) -> Void
|
||||||
) {
|
) {
|
||||||
self.debugText = debugText
|
self.viewModel = viewModel
|
||||||
self.title = title
|
|
||||||
self.status = status
|
|
||||||
self.onAppearAction = onAppearAction
|
self.onAppearAction = onAppearAction
|
||||||
self.readNowButtonAction = readNowButtonAction
|
self.readNowButtonAction = readNowButtonAction
|
||||||
self.dismissButtonTappedAction = dismissButtonTappedAction
|
self.dismissButtonTappedAction = dismissButtonTappedAction
|
||||||
}
|
}
|
||||||
|
|
||||||
@State var reminderTime: ReminderTime?
|
|
||||||
@State var hideUntilReminded = false
|
|
||||||
|
|
||||||
private func handleReminderTimeSelection(_ selectedTime: ReminderTime) {
|
private func handleReminderTimeSelection(_ selectedTime: ReminderTime) {
|
||||||
if selectedTime == reminderTime {
|
if selectedTime == reminderTime {
|
||||||
reminderTime = nil
|
reminderTime = nil
|
||||||
@ -125,111 +154,117 @@ public struct ShareExtensionChildView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var titleText: String {
|
||||||
|
switch viewModel.status {
|
||||||
|
case .saved, .synced:
|
||||||
|
return "Saved to Omnivore"
|
||||||
|
case .processing:
|
||||||
|
return "Saving to Omnivore"
|
||||||
|
default:
|
||||||
|
return "Error saving to Omnivore"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var cloudIconName: String {
|
||||||
|
switch viewModel.status {
|
||||||
|
case .synced:
|
||||||
|
return "checkmark.icloud"
|
||||||
|
case .saved, .processing:
|
||||||
|
return "icloud"
|
||||||
|
case .failed(error: _), .syncFailed(error: _):
|
||||||
|
return "exclamationmark.icloud"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var cloudIconColor: Color {
|
||||||
|
switch viewModel.status {
|
||||||
|
case .saved, .processing:
|
||||||
|
return .appGrayText
|
||||||
|
case .failed(error: _), .syncFailed(error: _):
|
||||||
|
return .red
|
||||||
|
case .synced:
|
||||||
|
return .blue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public var previewCard: some View {
|
||||||
|
HStack {
|
||||||
|
if let iconURLStr = viewModel.iconURL, let iconURL = URL(string: iconURLStr) {
|
||||||
|
AsyncLoadingImage(url: iconURL) { imageStatus in
|
||||||
|
if case let AsyncImageStatus.loaded(image) = imageStatus {
|
||||||
|
image
|
||||||
|
.resizable()
|
||||||
|
.aspectRatio(contentMode: .fill)
|
||||||
|
.frame(width: 61, height: 61)
|
||||||
|
} else if case AsyncImageStatus.loading = imageStatus {
|
||||||
|
Color.appButtonBackground
|
||||||
|
.aspectRatio(contentMode: .fill)
|
||||||
|
.frame(width: 61, height: 61)
|
||||||
|
} else {
|
||||||
|
EmptyView()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
EmptyView()
|
||||||
|
.frame(width: 61, height: 61)
|
||||||
|
}
|
||||||
|
VStack(alignment: .leading) {
|
||||||
|
Text(viewModel.title ?? "")
|
||||||
|
.lineLimit(1)
|
||||||
|
.foregroundColor(.appGrayText)
|
||||||
|
.font(Font.system(size: 15, weight: .semibold))
|
||||||
|
Text(viewModel.url ?? "")
|
||||||
|
.lineLimit(1)
|
||||||
|
.foregroundColor(.appGrayText)
|
||||||
|
.font(Font.system(size: 12, weight: .regular))
|
||||||
|
}
|
||||||
|
Spacer()
|
||||||
|
VStack {
|
||||||
|
Spacer()
|
||||||
|
Image(systemName: cloudIconName)
|
||||||
|
.resizable()
|
||||||
|
.aspectRatio(contentMode: .fill)
|
||||||
|
.frame(width: 12, height: 12, alignment: .trailing)
|
||||||
|
.foregroundColor(cloudIconColor)
|
||||||
|
// .padding(.trailing, 6)
|
||||||
|
.padding(EdgeInsets(top: 0, leading: 0, bottom: 8, trailing: 8))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.background(Color(hex: "#363636"))
|
||||||
|
.frame(maxWidth: .infinity, maxHeight: 61)
|
||||||
|
.cornerRadius(8)
|
||||||
|
}
|
||||||
|
|
||||||
public var body: some View {
|
public var body: some View {
|
||||||
VStack(alignment: .leading) {
|
VStack(alignment: .leading) {
|
||||||
#if DEBUG
|
Text(titleText)
|
||||||
if let debugText = debugText {
|
.foregroundColor(.appGrayText)
|
||||||
Text(debugText)
|
.font(Font.system(size: 17, weight: .semibold))
|
||||||
}
|
.frame(maxWidth: .infinity, alignment: .center)
|
||||||
#endif
|
.padding(.top, 23)
|
||||||
|
.padding(.bottom, 16)
|
||||||
|
|
||||||
if let title = title {
|
Rectangle()
|
||||||
Text(title)
|
.foregroundColor(.appGrayText)
|
||||||
.font(.appHeadline)
|
.frame(maxWidth: .infinity, maxHeight: 1)
|
||||||
.lineLimit(1)
|
.opacity(0.06)
|
||||||
.padding(.trailing, 50)
|
.padding(.top, 0)
|
||||||
Divider()
|
.padding(.bottom, 16)
|
||||||
}
|
|
||||||
|
previewCard
|
||||||
|
.padding(EdgeInsets(top: 0, leading: 16, bottom: 0, trailing: 16))
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
switch status {
|
|
||||||
case .processing:
|
|
||||||
HStack {
|
|
||||||
Spacer()
|
|
||||||
Text("Saving...")
|
|
||||||
Spacer()
|
|
||||||
}
|
|
||||||
case .saved:
|
|
||||||
HStack {
|
|
||||||
Spacer()
|
|
||||||
Text("Syncing...")
|
|
||||||
Spacer()
|
|
||||||
}
|
|
||||||
case .synced:
|
|
||||||
HStack(spacing: 4) {
|
|
||||||
Text("Saved to Omnivore")
|
|
||||||
.font(.appTitleThree)
|
|
||||||
.foregroundColor(.appGrayText)
|
|
||||||
.padding(.trailing, 16)
|
|
||||||
.multilineTextAlignment(.center)
|
|
||||||
.fixedSize(horizontal: false, vertical: true)
|
|
||||||
.lineLimit(nil)
|
|
||||||
}
|
|
||||||
.padding()
|
|
||||||
case let .failed(error: error):
|
|
||||||
HStack {
|
|
||||||
Spacer()
|
|
||||||
Text("Failed to save:" + error.displayMessage)
|
|
||||||
Spacer()
|
|
||||||
}
|
|
||||||
case let .syncFailed(error: error):
|
|
||||||
HStack {
|
|
||||||
Spacer()
|
|
||||||
Text("Failed to sync:" + error.displayMessage)
|
|
||||||
Spacer()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ScrollView {
|
|
||||||
if FeatureFlag.enableRemindersFromShareExtension {
|
|
||||||
VStack(spacing: 0) {
|
|
||||||
CheckmarkButtonView(
|
|
||||||
titleText: "Remind me tonight",
|
|
||||||
isSelected: reminderTime == .tonight,
|
|
||||||
action: { handleReminderTimeSelection(.tonight) }
|
|
||||||
)
|
|
||||||
|
|
||||||
Divider()
|
|
||||||
|
|
||||||
CheckmarkButtonView(
|
|
||||||
titleText: "Remind me tomorrow",
|
|
||||||
isSelected: reminderTime == .tomorrow,
|
|
||||||
action: { handleReminderTimeSelection(.tomorrow) }
|
|
||||||
)
|
|
||||||
|
|
||||||
Divider()
|
|
||||||
|
|
||||||
CheckmarkButtonView(
|
|
||||||
titleText: "Remind me this weekend",
|
|
||||||
isSelected: reminderTime == .thisWeekend,
|
|
||||||
action: { handleReminderTimeSelection(.thisWeekend) }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
.cornerRadius(8)
|
|
||||||
}
|
|
||||||
|
|
||||||
if FeatureFlag.enableSnoozeFromShareExtension {
|
|
||||||
CheckmarkButtonView(
|
|
||||||
titleText: "Hide it until then",
|
|
||||||
isSelected: hideUntilReminded,
|
|
||||||
action: { hideUntilReminded.toggle() }
|
|
||||||
)
|
|
||||||
.cornerRadius(8)
|
|
||||||
.padding(.top, 16)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.padding(.horizontal)
|
|
||||||
|
|
||||||
HStack {
|
HStack {
|
||||||
if case ShareExtensionStatus.saved = status, FeatureFlag.enableReadNow {
|
// if case ShareExtensionStatus.saved = status || case ShareExtensionStatus.synced = status {
|
||||||
Button(
|
Button(
|
||||||
action: { readNowButtonAction() },
|
action: { readNowButtonAction() },
|
||||||
label: { Text("Read Now").frame(maxWidth: .infinity) }
|
label: { Text("Read Now").frame(maxWidth: .infinity) }
|
||||||
)
|
)
|
||||||
.buttonStyle(RoundedRectButtonStyle())
|
.buttonStyle(RoundedRectButtonStyle())
|
||||||
}
|
// }
|
||||||
if case ShareExtensionStatus.processing = status, FeatureFlag.enableReadNow {
|
if case ShareExtensionStatus.processing = viewModel.status, FeatureFlag.enableReadNow {
|
||||||
Button(action: {}, label: { ProgressView().frame(maxWidth: .infinity) })
|
Button(action: {}, label: { ProgressView().frame(maxWidth: .infinity) })
|
||||||
.buttonStyle(RoundedRectButtonStyle())
|
.buttonStyle(RoundedRectButtonStyle())
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,7 +12,7 @@ import Utils
|
|||||||
|
|
||||||
embed(
|
embed(
|
||||||
childViewController: UIViewController.makeShareExtensionController(extensionContext: extensionContext),
|
childViewController: UIViewController.makeShareExtensionController(extensionContext: extensionContext),
|
||||||
heightRatio: 0.3
|
heightRatio: 0.5
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user