From 50f1213e931557b42702286998ef06fcbd1e075d Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Fri, 10 Nov 2023 20:33:05 +0800 Subject: [PATCH] Better handling of the label editor --- .../Share/Views/EditLabelsSheet.swift | 2 + .../Share/Views/ShareExtensionView.swift | 163 +++++++++++++++--- .../App/Views/Labels/ApplyLabelsView.swift | 2 + .../Sources/App/Views/LabelsEntryView.swift | 11 +- .../ShareExtensionViewController.swift | 33 +++- 5 files changed, 189 insertions(+), 22 deletions(-) diff --git a/apple/OmnivoreKit/Sources/App/AppExtensions/Share/Views/EditLabelsSheet.swift b/apple/OmnivoreKit/Sources/App/AppExtensions/Share/Views/EditLabelsSheet.swift index bc77f113a..b8291b19a 100644 --- a/apple/OmnivoreKit/Sources/App/AppExtensions/Share/Views/EditLabelsSheet.swift +++ b/apple/OmnivoreKit/Sources/App/AppExtensions/Share/Views/EditLabelsSheet.swift @@ -14,6 +14,7 @@ import Views @MainActor public struct EditLabelsSheet: View { @State var text = "" + @State var isLabelsEntryFocused = false @Environment(\.dismiss) private var dismiss @EnvironmentObject var dataService: DataService @@ -56,6 +57,7 @@ public struct EditLabelsSheet: View { VStack { LabelsEntryView( searchTerm: $labelsViewModel.labelSearchFilter, + isFocused: $isLabelsEntryFocused, viewModel: labelsViewModel ) .padding(.horizontal, 10) diff --git a/apple/OmnivoreKit/Sources/App/AppExtensions/Share/Views/ShareExtensionView.swift b/apple/OmnivoreKit/Sources/App/AppExtensions/Share/Views/ShareExtensionView.swift index 532bce4b6..97ed18cc4 100644 --- a/apple/OmnivoreKit/Sources/App/AppExtensions/Share/Views/ShareExtensionView.swift +++ b/apple/OmnivoreKit/Sources/App/AppExtensions/Share/Views/ShareExtensionView.swift @@ -20,7 +20,8 @@ public struct ShareExtensionView: View { @State var showAddNoteModal = false enum FocusField: Hashable { - case titleEditor + case noteEditor + case labelEditor } enum ViewState { @@ -39,6 +40,10 @@ public struct ShareExtensionView: View { _viewModel = StateObject(wrappedValue: viewModel) _labelsViewModel = StateObject(wrappedValue: labelsViewModel) self.extensionContext = extensionContext + + #if os(iOS) + UITextView.appearance().textContainerInset = UIEdgeInsets(top: 8, left: 4, bottom: 10, right: 4) + #endif } private func localImage(from url: URL) -> Image? { @@ -55,12 +60,13 @@ public struct ShareExtensionView: View { } var isSynced: Bool { - switch viewModel.status { - case .synced: - return true - default: - return false - } + true +// switch viewModel.status { +// case .synced: +// return true +// default: +// return false +// } } var articleInfoBox: some View { @@ -100,7 +106,7 @@ public struct ShareExtensionView: View { Image(systemName: "checkmark.circle") .frame(width: 15, height: 15) .foregroundColor(.appGreenSuccess) - // .opacity(isSynced ? 1.0 : 0.0) + .opacity(isSynced ? 1.0 : 0.0) } } @@ -237,22 +243,32 @@ public struct ShareExtensionView: View { Text("Saved to Omnivore") .font(Font.system(size: 22, weight: .bold)) .frame(maxWidth: .infinity, alignment: .leading) - - Spacer() - moreMenuButton - closeButton + #if os(iOS) + Spacer() + moreMenuButton + closeButton + #endif } } var displayDismiss: Bool { -#if os(iOS) - if UIDevice.isIPhone { - return true - } -#endif - return false + #if os(iOS) + if UIDevice.isIPhone { + return false + } + #endif + return true } + public var body: some View { + #if os(iOS) + iOSBody + #else + macOSBody + #endif + } + + var iOSBody: some View { VStack(alignment: .leading, spacing: 15) { titleBar .padding(.top, 15) @@ -262,32 +278,44 @@ public struct ShareExtensionView: View { Spacer(minLength: 1) HStack { + #if os(macOS) + moreMenuButton + .padding(.bottom, 15) + #endif Spacer() if displayDismiss { Button(action: { extensionContext?.completeRequest(returningItems: [], completionHandler: nil) }, label: { Text("Dismiss") + #if os(iOS) .font(Font.system(size: 17, weight: .semibold)) .tint(Color.appGrayText) .padding(20) + #endif }) + #if os(iOS) .frame(height: 50) .cornerRadius(24) - .padding(.bottom, 15) + #endif + .padding(.bottom, 15) } Button(action: { viewModel.handleReadNowAction(extensionContext: extensionContext) }, label: { Text("Read Now") + #if os(iOS) .font(Font.system(size: 17, weight: .semibold)) .tint(Color.white) .padding(20) + #endif }) + #if os(iOS) .frame(height: 50) .background(Color.blue) .cornerRadius(24) - .padding(.bottom, 15) + #endif + .padding(.bottom, 15) }.frame(maxWidth: .infinity) }.padding(.horizontal, 15) .background(Color.extensionBackground) @@ -295,4 +323,99 @@ public struct ShareExtensionView: View { viewModel.savePage(extensionContext: extensionContext) } } + + @State var notes = "" + @State var labelsSearch = ZWSP + @State var isLabelsEntryFocused = false + + var macOSBody: some View { + VStack(alignment: .leading, spacing: 0) { + HStack(spacing: 10) { + Text("Saved to Omnivore") + .font(Font.system(size: 17)) + Image(systemName: "checkmark.circle") + .foregroundColor(.appGreenSuccess) + .opacity(isSynced ? 1.0 : 0.0) + Spacer() + }.padding(15) + + Divider() + + ZStack(alignment: .topLeading) { + TextEditor(text: $notes) + .frame(maxWidth: .infinity) + .font(Font.system(size: 14)) + .accentColor(.blue) + .introspectTextView { textView in + textView.textContainerInset = NSSize(width: 10, height: 10) + } + .focused($focusedField, equals: .noteEditor) + if notes.isEmpty { + Text("Notes") + .fontWeight(.light) + .font(Font.system(size: 14)) + .foregroundColor(.black.opacity(0.25)) + .padding(.leading, 15) + .padding(.top, 10) + .allowsHitTesting(false) + } + } + + Divider() + + ZStack(alignment: .topLeading) { + LabelsEntryView(searchTerm: $labelsSearch, isFocused: $isLabelsEntryFocused, viewModel: labelsViewModel) + .frame(maxWidth: .infinity) + .padding(.horizontal, 10) + .focused($focusedField, equals: .labelEditor) + .onHover { isHovered in + DispatchQueue.main.async { + if isHovered { + NSCursor.iBeam.push() + } else { + NSCursor.pop() + } + } + } + if !isLabelsEntryFocused, labelsViewModel.selectedLabels.isEmpty, labelsSearch == ZWSP { + Text("Add Labels") + .fontWeight(.light) + .font(Font.system(size: 14)) + .foregroundColor(.black.opacity(0.25)) + .padding(.leading, 15) + .padding(.top, 10) + .allowsHitTesting(false) + } + } + + Divider() + + HStack { + moreMenuButton + .padding(.bottom, 15) + Spacer() + Button(action: { + extensionContext?.completeRequest(returningItems: [], completionHandler: nil) + }, label: { + Text("Dismiss") + }) + .padding(.bottom, 15) + Button(action: { + viewModel.handleReadNowAction(extensionContext: extensionContext) + }, label: { + Text("Read Now") + }) + .padding(.bottom, 15) + + }.padding(15) + + }.frame(maxWidth: .infinity) + .background(Color.isDarkMode ? Color.systemBackground : Color.white) + .onAppear { + viewModel.savePage(extensionContext: extensionContext) + DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(100)) { + focusedField = .noteEditor + } + } + } } diff --git a/apple/OmnivoreKit/Sources/App/Views/Labels/ApplyLabelsView.swift b/apple/OmnivoreKit/Sources/App/Views/Labels/ApplyLabelsView.swift index b259cdbf0..d1bfbced1 100644 --- a/apple/OmnivoreKit/Sources/App/Views/Labels/ApplyLabelsView.swift +++ b/apple/OmnivoreKit/Sources/App/Views/Labels/ApplyLabelsView.swift @@ -36,6 +36,7 @@ struct ApplyLabelsView: View { @EnvironmentObject var dataService: DataService @Environment(\.presentationMode) private var presentationMode @StateObject var viewModel = LabelsViewModel() + @State var isLabelsEntryFocused = false enum ViewState { case mainView @@ -52,6 +53,7 @@ struct ApplyLabelsView: View { VStack { LabelsEntryView( searchTerm: $viewModel.labelSearchFilter, + isFocused: $isLabelsEntryFocused, viewModel: viewModel ) .padding(.horizontal, 10) diff --git a/apple/OmnivoreKit/Sources/App/Views/LabelsEntryView.swift b/apple/OmnivoreKit/Sources/App/Views/LabelsEntryView.swift index 935b88f7e..841aa32b7 100644 --- a/apple/OmnivoreKit/Sources/App/Views/LabelsEntryView.swift +++ b/apple/OmnivoreKit/Sources/App/Views/LabelsEntryView.swift @@ -25,6 +25,7 @@ private struct LabelEntry: Entry { @MainActor public struct LabelsEntryView: View { @Binding var searchTerm: String + @Binding var isFocused: Bool @State var viewModel: LabelsViewModel @EnvironmentObject var dataService: DataService @@ -35,11 +36,13 @@ public struct LabelsEntryView: View { public init( searchTerm: Binding, + isFocused: Binding, viewModel: LabelsViewModel ) { self._searchTerm = searchTerm - self.viewModel = viewModel + self._isFocused = isFocused + self.viewModel = viewModel self.entries = Array(viewModel.selectedLabels.map { LabelEntry(label: $0) }) } @@ -100,6 +103,7 @@ public struct LabelsEntryView: View { .frame(height: 25) .frame(width: textWidth) .padding(5) + .accentColor(.blue) .font(Font.system(size: 14)) .multilineTextAlignment(.leading) .onChange(of: searchTerm, perform: { _ in @@ -121,6 +125,10 @@ public struct LabelsEntryView: View { .onSubmit { onTextSubmit() } + #if os(macOS) + .textFieldStyle(.plain) + .background(Color.clear) + #endif return result } @@ -157,6 +165,7 @@ public struct LabelsEntryView: View { textFieldFocused = true } .transaction { $0.animation = nil } + .onChange(of: textFieldFocused) { self.isFocused = $0 } } private func generateLabelsContent(in geom: GeometryProxy) -> some View { diff --git a/apple/Sources/ShareExtension/ShareExtensionViewController.swift b/apple/Sources/ShareExtension/ShareExtensionViewController.swift index e93dccaf0..bbf6dd0d6 100644 --- a/apple/Sources/ShareExtension/ShareExtensionViewController.swift +++ b/apple/Sources/ShareExtension/ShareExtensionViewController.swift @@ -66,11 +66,36 @@ import Views let viewModel = ShareExtensionViewModel() override func loadView() { - view = NSView(frame: NSRect(x: 0, y: 0, width: 400, height: 400)) + view = NSView(frame: NSRect(x: 0, y: 0, width: 400, height: 300)) } override func viewDidLoad() { super.viewDidLoad() + + NotificationCenter.default.addObserver( + forName: Notification.Name("ShowAddNoteSheet"), + object: nil, + queue: OperationQueue.main + ) { _ in + self.openSheet(AnyView(AddNoteSheet(viewModel: self.viewModel))) + } + + NotificationCenter.default.addObserver( + forName: Notification.Name("ShowEditLabelsSheet"), + object: nil, + queue: OperationQueue.main + ) { _ in + self.openSheet(AnyView(EditLabelsSheet(viewModel: self.viewModel, labelsViewModel: self.labelsViewModel))) + } + + NotificationCenter.default.addObserver( + forName: Notification.Name("ShowEditInfoSheet"), + object: nil, + queue: OperationQueue.main + ) { _ in + self.openSheet(AnyView(EditInfoSheet(viewModel: self.viewModel))) + } + embed( childViewController: NSViewController.makeShareExtensionController( viewModel: viewModel, @@ -79,6 +104,12 @@ import Views ) ) } + + func openSheet(_ rootView: AnyView) { + let hostingController = PlatformHostingController(rootView: rootView) + + presentAsSheet(hostingController) + } } #endif