diff --git a/apple/Omnivore.xcodeproj/project.pbxproj b/apple/Omnivore.xcodeproj/project.pbxproj index 0229d82b5..bb47dd924 100644 --- a/apple/Omnivore.xcodeproj/project.pbxproj +++ b/apple/Omnivore.xcodeproj/project.pbxproj @@ -1229,7 +1229,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 51; + CURRENT_PROJECT_VERSION = 52; DEVELOPMENT_TEAM = QJF2XZ86HB; ENABLE_HARDENED_RUNTIME = YES; INFOPLIST_FILE = InfoPlists/ShareExtensionMac.plist; @@ -1239,7 +1239,7 @@ "@executable_path/../../../../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 12.0; - MARKETING_VERSION = 1.8.0; + MARKETING_VERSION = 1.9.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = "app.omnivore.app.ShareExtension-Mac"; @@ -1260,7 +1260,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 51; + CURRENT_PROJECT_VERSION = 52; DEVELOPMENT_TEAM = QJF2XZ86HB; ENABLE_HARDENED_RUNTIME = YES; INFOPLIST_FILE = InfoPlists/ShareExtensionMac.plist; @@ -1270,7 +1270,7 @@ "@executable_path/../../../../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 12.0; - MARKETING_VERSION = 1.8.0; + MARKETING_VERSION = 1.9.0; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = "app.omnivore.app.ShareExtension-Mac"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -1341,7 +1341,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 51; + CURRENT_PROJECT_VERSION = 52; DEVELOPMENT_ASSET_PATHS = ""; DEVELOPMENT_TEAM = QJF2XZ86HB; ENABLE_HARDENED_RUNTIME = YES; @@ -1352,7 +1352,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 12.0; - MARKETING_VERSION = 1.8.0; + MARKETING_VERSION = 1.9.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = app.omnivore.app; @@ -1375,7 +1375,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 51; + CURRENT_PROJECT_VERSION = 52; DEVELOPMENT_ASSET_PATHS = ""; DEVELOPMENT_TEAM = QJF2XZ86HB; ENABLE_HARDENED_RUNTIME = YES; @@ -1386,7 +1386,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 12.0; - MARKETING_VERSION = 1.8.0; + MARKETING_VERSION = 1.9.0; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = app.omnivore.app; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -1441,7 +1441,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.8.0; + MARKETING_VERSION = 1.9.0; PRODUCT_BUNDLE_IDENTIFIER = app.omnivore.app; PRODUCT_NAME = Omnivore; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -1473,7 +1473,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 1.8.0; + MARKETING_VERSION = 1.9.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; OTHER_LDFLAGS = ( @@ -1512,7 +1512,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 1.8.0; + MARKETING_VERSION = 1.9.0; MTL_FAST_MATH = YES; OTHER_LDFLAGS = ( "-framework", @@ -1538,7 +1538,7 @@ CODE_SIGN_ENTITLEMENTS = "Entitlements/SafariExtension-Mac.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 51; + CURRENT_PROJECT_VERSION = 52; DEVELOPMENT_TEAM = QJF2XZ86HB; ENABLE_HARDENED_RUNTIME = YES; GENERATE_INFOPLIST_FILE = YES; @@ -1551,7 +1551,7 @@ "@executable_path/../../../../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 12.0; - MARKETING_VERSION = 1.8.0; + MARKETING_VERSION = 1.9.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; OTHER_LDFLAGS = ( @@ -1576,7 +1576,7 @@ CODE_SIGN_ENTITLEMENTS = "Entitlements/SafariExtension-Mac.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 51; + CURRENT_PROJECT_VERSION = 52; DEVELOPMENT_TEAM = QJF2XZ86HB; ENABLE_HARDENED_RUNTIME = YES; GENERATE_INFOPLIST_FILE = YES; @@ -1589,7 +1589,7 @@ "@executable_path/../../../../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 12.0; - MARKETING_VERSION = 1.8.0; + MARKETING_VERSION = 1.9.0; MTL_FAST_MATH = YES; OTHER_LDFLAGS = ( "-framework", @@ -1674,7 +1674,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 1.8.0; + MARKETING_VERSION = 1.9.0; PRODUCT_BUNDLE_IDENTIFIER = "app.omnivore.app.share-extension"; PRODUCT_NAME = ShareExtension; SDKROOT = iphoneos; @@ -1728,7 +1728,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.8.0; + MARKETING_VERSION = 1.9.0; PRODUCT_BUNDLE_IDENTIFIER = app.omnivore.app; PRODUCT_NAME = Omnivore; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -1756,7 +1756,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 1.8.0; + MARKETING_VERSION = 1.9.0; PRODUCT_BUNDLE_IDENTIFIER = "app.omnivore.app.share-extension"; PRODUCT_NAME = ShareExtension; SDKROOT = iphoneos; diff --git a/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewIOS.swift b/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewIOS.swift index b2dabb0e6..480e84012 100644 --- a/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewIOS.swift +++ b/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewIOS.swift @@ -55,6 +55,9 @@ import Views .sheet(item: $viewModel.itemUnderLabelEdit) { item in ApplyLabelsView(mode: .item(item), onSave: nil) } + .sheet(item: $viewModel.itemUnderTitleEdit) { item in + LinkedItemTitleEditView(item: item) + } .toolbar { ToolbarItem(placement: .barTrailing) { Button("", action: {}) @@ -230,6 +233,10 @@ import Views viewModel: viewModel ) .contextMenu { + Button( + action: { viewModel.itemUnderTitleEdit = item }, + label: { Label("Edit Title/Description", systemImage: "textbox") } + ) Button( action: { viewModel.itemUnderLabelEdit = item }, label: { Label("Edit Labels", systemImage: "tag") } @@ -344,6 +351,8 @@ import Views confirmationShown = true case .editLabels: viewModel.itemUnderLabelEdit = item + case .editTitle: + viewModel.itemUnderTitleEdit = item } } diff --git a/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewMac.swift b/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewMac.swift index 6f7113865..0dc402ac8 100644 --- a/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewMac.swift +++ b/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewMac.swift @@ -26,6 +26,10 @@ import Views viewModel: viewModel ) .contextMenu { + Button( + action: { viewModel.itemUnderTitleEdit = item }, + label: { Label("Edit Title/Description", systemImage: "textbox") } + ) Button( action: { viewModel.itemUnderLabelEdit = item }, label: { Label("Edit Labels", systemImage: "tag") } diff --git a/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewModel.swift b/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewModel.swift index 247ccf7db..2d104d0ee 100644 --- a/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewModel.swift +++ b/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewModel.swift @@ -14,6 +14,7 @@ import Views @Published var isLoading = false @Published var showPushNotificationPrimer = false @Published var itemUnderLabelEdit: LinkedItem? + @Published var itemUnderTitleEdit: LinkedItem? @Published var searchTerm = "" @Published var selectedLabels = [LinkedItemLabel]() @Published var negatedLabels = [LinkedItemLabel]() diff --git a/apple/OmnivoreKit/Sources/App/Views/LinkItemDetailView.swift b/apple/OmnivoreKit/Sources/App/Views/LinkItemDetailView.swift index 79eb3a002..7d8fc2a40 100644 --- a/apple/OmnivoreKit/Sources/App/Views/LinkItemDetailView.swift +++ b/apple/OmnivoreKit/Sources/App/Views/LinkItemDetailView.swift @@ -119,6 +119,7 @@ struct LinkItemDetailView: View { static let navBarHeight = 50.0 @ObservedObject private var viewModel: LinkItemDetailViewModel @State private var showFontSizePopover = false + @State private var showTitleEdit = false @State private var navBarVisibilityRatio = 1.0 @State private var showDeleteConfirmation = false @@ -206,6 +207,10 @@ struct LinkItemDetailView: View { Menu( content: { Group { + Button( + action: { showTitleEdit = true }, + label: { Label("Edit Title/Description", systemImage: "textbox") } + ) Button( action: { viewModel.handleArchiveAction(dataService: dataService) }, label: { @@ -240,6 +245,11 @@ struct LinkItemDetailView: View { } Button("Cancel", role: .cancel, action: {}) } + .sheet(isPresented: $showTitleEdit) { + if let item = viewModel.item { + LinkedItemTitleEditView(item: item) + } + } } #if os(iOS) diff --git a/apple/OmnivoreKit/Sources/App/Views/LinkedItemTitleEditView.swift b/apple/OmnivoreKit/Sources/App/Views/LinkedItemTitleEditView.swift new file mode 100644 index 000000000..e8911c89d --- /dev/null +++ b/apple/OmnivoreKit/Sources/App/Views/LinkedItemTitleEditView.swift @@ -0,0 +1,90 @@ +import Models +import Services +import SwiftUI +import Views + +@MainActor final class LinkedItemTitleEditViewModel: ObservableObject { + @Published var title = "" + @Published var description = "" + + func load(item: LinkedItem) { + title = item.unwrappedTitle + description = item.descriptionText ?? "" + } + + func submit(dataService: DataService, item: LinkedItem) { + dataService.updateLinkedItemTitleAndDescription( + itemID: item.unwrappedID, + title: title, + description: description + ) + } +} + +struct LinkedItemTitleEditView: View { + @EnvironmentObject var dataService: DataService + @Environment(\.presentationMode) private var presentationMode + @StateObject var viewModel = LinkedItemTitleEditViewModel() + + let item: LinkedItem + + var editForm: some View { + ScrollView(showsIndicators: false) { + VStack(alignment: .center, spacing: 16) { + VStack(alignment: .leading, spacing: 6) { + Text("Title") + .font(.appFootnote) + .foregroundColor(.appGrayTextContrast) + TextField("", text: $viewModel.title) + .textFieldStyle(StandardTextFieldStyle(textColor: .appGrayTextContrast)) + } + + VStack(alignment: .leading, spacing: 6) { + Text("Description") + .font(.appFootnote) + .foregroundColor(.appGrayTextContrast) + TextEditor(text: $viewModel.description) + .lineSpacing(6) + .accentColor(.appGraySolid) + .foregroundColor(.appGrayTextContrast) + .font(.appBody) + .padding(12) + .background( + RoundedRectangle(cornerRadius: 8) + .strokeBorder(Color.appGrayBorder, lineWidth: 1) + .background(RoundedRectangle(cornerRadius: 8).fill(Color.systemBackground)) + ) + .frame(height: 200) + } + } + .padding() + } + .frame(maxWidth: .infinity, maxHeight: .infinity) + } + + var body: some View { + NavigationView { + editForm + .navigationTitle("Edit Title and Description") + .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) } + ) + } + } + } + .task { viewModel.load(item: item) } + } +} diff --git a/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderContainer.swift b/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderContainer.swift index 52702ae06..5bc1db9ef 100644 --- a/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderContainer.swift +++ b/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderContainer.swift @@ -10,6 +10,7 @@ import WebKit @State private var showPreferencesPopover = false @State private var showLabelsModal = false + @State private var showTitleEdit = false @State var showHighlightAnnotationModal = false @State var safariWebLink: SafariWebLink? @State private var navBarVisibilityRatio = 1.0 @@ -82,6 +83,10 @@ import WebKit Menu( content: { Group { + Button( + action: { showTitleEdit = true }, + label: { Label("Edit Title/Description", systemImage: "textbox") } + ) Button( action: { showLabelsModal = true }, label: { Label("Edit Labels", systemImage: "tag") } @@ -130,6 +135,9 @@ import WebKit .sheet(isPresented: $showLabelsModal) { ApplyLabelsView(mode: .item(item), onSave: { _ in showLabelsModal = false }) } + .sheet(isPresented: $showTitleEdit) { + LinkedItemTitleEditView(item: item) + } } var body: some View { diff --git a/apple/OmnivoreKit/Sources/Models/DataModels/FeedItem.swift b/apple/OmnivoreKit/Sources/Models/DataModels/FeedItem.swift index d74f21aeb..1150a8632 100644 --- a/apple/OmnivoreKit/Sources/Models/DataModels/FeedItem.swift +++ b/apple/OmnivoreKit/Sources/Models/DataModels/FeedItem.swift @@ -111,7 +111,9 @@ public extension LinkedItem { inContext context: NSManagedObjectContext, newReadingProgress: Double? = nil, newAnchorIndex: Int? = nil, - newIsArchivedValue: Bool? = nil + newIsArchivedValue: Bool? = nil, + newTitle: String? = nil, + newDescription: String? = nil ) { context.perform { if let newReadingProgress = newReadingProgress { @@ -126,6 +128,14 @@ public extension LinkedItem { self.isArchived = newIsArchivedValue } + if let newTitle = newTitle { + self.title = newTitle + } + + if let newDescription = newDescription { + self.descriptionText = newDescription + } + guard context.hasChanges else { return } self.updatedAt = Date() diff --git a/apple/OmnivoreKit/Sources/Services/DataService/Mutations/UpdateLinkedItemTitle.swift b/apple/OmnivoreKit/Sources/Services/DataService/Mutations/UpdateLinkedItemTitle.swift new file mode 100644 index 000000000..3f2884436 --- /dev/null +++ b/apple/OmnivoreKit/Sources/Services/DataService/Mutations/UpdateLinkedItemTitle.swift @@ -0,0 +1,77 @@ +import CoreData +import Foundation +import Models +import SwiftGraphQL + +extension DataService { + public func updateLinkedItemTitleAndDescription(itemID: String, title: String, description: String) { + backgroundContext.perform { [weak self] in + guard let self = self else { return } + guard let linkedItem = LinkedItem.lookup(byID: itemID, inContext: self.backgroundContext) else { return } + + linkedItem.update( + inContext: self.backgroundContext, + newTitle: title, + newDescription: description + ) + + // Send update to server + self.syncLinkedItemTitleAndDescription( + itemID: itemID, + objectID: linkedItem.objectID, + title: title, + description: description + ) + } + } + + func syncLinkedItemTitleAndDescription( + itemID: String, + objectID: NSManagedObjectID, + title: String, + description: String + ) { + enum MutationResult { + case saved(title: String) + case error(errorMessage: String) + } + + let selection = Selection { + try $0.on( + updatePageError: .init { .error(errorMessage: try $0.errorCodes().first.toString()) }, + updatePageSuccess: .init { + .saved(title: try $0.updatedPage(selection: Selection.Article { try $0.title() })) + } + ) + } + + let mutation = Selection.Mutation { + try $0.updatePage( + input: .init(description: OptionalArgument(description), pageId: itemID, title: OptionalArgument(title)), + selection: selection + ) + } + + let path = appEnvironment.graphqlPath + let headers = networker.defaultHeaders + let context = backgroundContext + + send(mutation, to: path, headers: headers) { result in + let data = try? result.get() + let syncStatus: ServerSyncStatus = data == nil ? .needsUpdate : .isNSync + + context.perform { + guard let linkedItem = context.object(with: objectID) as? LinkedItem else { return } + linkedItem.serverSyncStatus = Int64(syncStatus.rawValue) + + do { + try context.save() + logger.debug("LinkedItem updated succesfully") + } catch { + context.rollback() + logger.debug("Failed to update LinkedItem: \(error.localizedDescription)") + } + } + } + } +} diff --git a/apple/OmnivoreKit/Sources/Views/FeedItem/GridCard.swift b/apple/OmnivoreKit/Sources/Views/FeedItem/GridCard.swift index bb736984f..1e8e46254 100644 --- a/apple/OmnivoreKit/Sources/Views/FeedItem/GridCard.swift +++ b/apple/OmnivoreKit/Sources/Views/FeedItem/GridCard.swift @@ -6,6 +6,7 @@ public enum GridCardAction { case toggleArchiveStatus case delete case editLabels + case editTitle } public struct GridCard: View { @@ -43,6 +44,10 @@ public struct GridCard: View { var contextMenuView: some View { Group { + Button( + action: { menuActionHandler(.editTitle) }, + label: { Label("Edit Title/Description", systemImage: "textbox") } + ) Button( action: { menuActionHandler(.editLabels) }, label: { Label("Edit Labels", systemImage: "tag") } diff --git a/apple/OmnivoreKit/Sources/Views/TextFields/TextFieldStyles.swift b/apple/OmnivoreKit/Sources/Views/TextFields/TextFieldStyles.swift index 6f4a54c78..4b94e60ee 100644 --- a/apple/OmnivoreKit/Sources/Views/TextFields/TextFieldStyles.swift +++ b/apple/OmnivoreKit/Sources/Views/TextFields/TextFieldStyles.swift @@ -1,13 +1,18 @@ import SwiftUI public struct StandardTextFieldStyle: TextFieldStyle { - public init() {} + let textColor: Color? + + public init(textColor: Color = .appGrayText) { + self.textColor = textColor + } + // swiftlint:disable:next identifier_name public func _body(configuration: TextField<_Label>) -> some View { configuration .textFieldStyle(PlainTextFieldStyle()) .multilineTextAlignment(.leading) - .foregroundColor(.appGrayText) + .foregroundColor(textColor) .font(.appBody) .padding(.vertical, 12) .padding(.horizontal, 16) @@ -15,9 +20,9 @@ public struct StandardTextFieldStyle: TextFieldStyle { } public var border: some View { - RoundedRectangle(cornerRadius: 16) + RoundedRectangle(cornerRadius: 8) .strokeBorder(Color.appGrayBorder, lineWidth: 1) - .background(RoundedRectangle(cornerRadius: 16).fill(Color.systemBackground)) + .background(RoundedRectangle(cornerRadius: 8).fill(Color.systemBackground)) } }