diff --git a/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewIOS.swift b/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewIOS.swift index 62bd31f04..4cfe091dd 100644 --- a/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewIOS.swift +++ b/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewIOS.swift @@ -26,16 +26,6 @@ private let enableGrid = UIDevice.isIPad || FeatureFlag.enableGridCardsOnPhone .refreshable { loadItems(isRefresh: true) } - .searchable( - text: $viewModel.searchTerm - ) { - if viewModel.searchTerm.isEmpty { - Text("Inbox").searchCompletion("in:inbox ") - Text("All").searchCompletion("in:all ") - Text("Archived").searchCompletion("in:archive ") - Text("Files").searchCompletion("type:file ") - } - } .onChange(of: viewModel.searchTerm) { _ in // Maybe we should debounce this, but // it feels like it works ok without @@ -44,7 +34,7 @@ private let enableGrid = UIDevice.isIPad || FeatureFlag.enableGridCardsOnPhone .onChange(of: viewModel.selectedLabels) { _ in loadItems(isRefresh: true) } - .onSubmit(of: .search) { + .onChange(of: viewModel.appliedFilter) { _ in loadItems(isRefresh: true) } .sheet(item: $viewModel.itemUnderLabelEdit) { item in @@ -135,9 +125,23 @@ private let enableGrid = UIDevice.isIPad || FeatureFlag.enableGridCardsOnPhone var body: some View { VStack(spacing: 0) { + SearchBar(searchTerm: $viewModel.searchTerm) + ZStack(alignment: .bottom) { ScrollView(.horizontal, showsIndicators: false) { HStack { + Menu( + content: { + ForEach(LinkedItemFilter.allCases, id: \.self) { filter in + Button(filter.displayName, action: { viewModel.appliedFilter = filter.rawValue }) + } + }, + label: { + TextChipButton.makeFilterButton( + title: LinkedItemFilter(rawValue: viewModel.appliedFilter)?.displayName ?? "Filter" + ) + } + ) TextChipButton.makeAddLabelButton { showLabelsSheet = true } diff --git a/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewModel.swift b/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewModel.swift index 1d46f3235..d16de27b1 100644 --- a/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewModel.swift +++ b/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewModel.swift @@ -19,6 +19,9 @@ import Views @Published var selectedLinkItem: LinkedItem? @Published var showLoadingBar = false + @AppStorage(UserDefaultKey.lastSelectedLinkedItemFilter.rawValue) + var appliedFilter = LinkedItemFilter.inbox.rawValue + var cursor: String? // These are used to make sure we handle search result @@ -84,11 +87,13 @@ import Views cursor = queryResult.cursor await dataService.prefetchPages(itemIDs: newItems.map(\.unwrappedID)) showLoadingBar = false - } else if searchTermIsEmpty { + } else if searchTerm.replacingOccurrences(of: " ", with: "").isEmpty { await dataService.viewContext.perform { let fetchRequest: NSFetchRequest = LinkedItem.fetchRequest() fetchRequest.sortDescriptors = [NSSortDescriptor(keyPath: \LinkedItem.savedAt, ascending: false)] - fetchRequest.predicate = self.itemRequestPredicate + if let predicate = LinkedItemFilter(rawValue: self.appliedFilter)?.predicate { + fetchRequest.predicate = predicate + } // // TODO: Filter on label if let fetchedItems = try? dataService.viewContext.fetch(fetchRequest) { @@ -101,49 +106,6 @@ import Views } } - private var itemRequestPredicate: NSPredicate { - let undeletedPredicate = NSPredicate( - format: "%K != %i", #keyPath(LinkedItem.serverSyncStatus), Int64(ServerSyncStatus.needsDeletion.rawValue) - ) - - if searchTerm.contains("in:all") { - // include everything undeleted - return undeletedPredicate - } - - if searchTerm.contains("in:archive") { - let inArchivePredicate = NSPredicate( - format: "%K == %@", #keyPath(LinkedItem.isArchived), Int(truncating: true) as NSNumber - ) - return NSCompoundPredicate(andPredicateWithSubpredicates: [undeletedPredicate, inArchivePredicate]) - } - - if searchTerm.contains("type:file") { - // include pdf only - let isPDFPredicate = NSPredicate( - format: "%K == %@", #keyPath(LinkedItem.contentReader), "PDF" - ) - return NSCompoundPredicate(andPredicateWithSubpredicates: [undeletedPredicate, isPDFPredicate]) - } - - // default to "in:inbox" (non-archived items) - let notInArchivePredicate = NSPredicate( - format: "%K == %@", #keyPath(LinkedItem.isArchived), Int(truncating: false) as NSNumber - ) - return NSCompoundPredicate(andPredicateWithSubpredicates: [undeletedPredicate, notInArchivePredicate]) - } - - // Exclude filters when testing if user has enetered a search term - private var searchTermIsEmpty: Bool { - searchTerm - .replacingOccurrences(of: "in:inbox", with: "") - .replacingOccurrences(of: "in:all", with: "") - .replacingOccurrences(of: "in:archive", with: "") - .replacingOccurrences(of: "type:file", with: "") - .replacingOccurrences(of: " ", with: "") - .isEmpty - } - func setLinkArchived(dataService: DataService, objectID: NSManagedObjectID, archived: Bool) { // TODO: remove this by making list always fetch from Coredata guard let itemIndex = items.firstIndex(where: { $0.objectID == objectID }) else { return } @@ -182,12 +144,13 @@ import Views isLoading = false } - private var searchQuery: String? { - if searchTerm.isEmpty, selectedLabels.isEmpty { - return nil - } + private var searchQuery: String { + let filter = LinkedItemFilter(rawValue: appliedFilter) ?? .inbox + var query = "\(filter.queryString)" - var query = searchTerm + if !searchTerm.isEmpty { + query.append(" \(searchTerm)") + } if !selectedLabels.isEmpty { query.append(" label:") diff --git a/apple/OmnivoreKit/Sources/App/Views/Labels/LabelsViewModel.swift b/apple/OmnivoreKit/Sources/App/Views/Labels/LabelsViewModel.swift index e0a7aa062..06b8ceefb 100644 --- a/apple/OmnivoreKit/Sources/App/Views/Labels/LabelsViewModel.swift +++ b/apple/OmnivoreKit/Sources/App/Views/Labels/LabelsViewModel.swift @@ -64,7 +64,7 @@ import Views } func saveItemLabelChanges(itemID: String, dataService: DataService) { - dataService.updateItemLabels(itemID: itemID, labelNames: selectedLabels.map(\.unwrappedName)) + dataService.updateItemLabels(itemID: itemID, labelIDs: selectedLabels.map(\.unwrappedID)) } func addLabelToItem(_ label: LinkedItemLabel) { diff --git a/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReader.swift b/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReader.swift index 7e219b424..c1001ca07 100644 --- a/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReader.swift +++ b/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReader.swift @@ -16,6 +16,7 @@ import WebKit @Binding var increaseFontActionID: UUID? @Binding var decreaseFontActionID: UUID? @Binding var annotationSaveTransactionID: UUID? + @Binding var showNavBarActionID: UUID? @Binding var annotation: String func makeCoordinator() -> WebReaderCoordinator { @@ -75,6 +76,11 @@ import WebKit (webView as? WebView)?.decreaseFontSize() } + if showNavBarActionID != context.coordinator.previousShowNavBarActionID { + context.coordinator.previousShowNavBarActionID = showNavBarActionID + context.coordinator.showNavBar() + } + // If the webview had been terminated `needsReload` will have been set to true if context.coordinator.needsReload { loadContent(webView: webView) diff --git a/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderContainer.swift b/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderContainer.swift index 2a102607a..6126708f2 100644 --- a/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderContainer.swift +++ b/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderContainer.swift @@ -18,6 +18,7 @@ import WebKit @State var increaseFontActionID: UUID? @State var decreaseFontActionID: UUID? @State var annotationSaveTransactionID: UUID? + @State var showNavBarActionID: UUID? @State var annotation = String() @EnvironmentObject var dataService: DataService @@ -154,8 +155,15 @@ import WebKit increaseFontActionID: $increaseFontActionID, decreaseFontActionID: $decreaseFontActionID, annotationSaveTransactionID: $annotationSaveTransactionID, + showNavBarActionID: $showNavBarActionID, annotation: $annotation ) + .onTapGesture { + withAnimation { + navBarVisibilityRatio = 1 + showNavBarActionID = UUID() + } + } .sheet(item: $safariWebLink) { SafariView(url: $0.url) } diff --git a/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderCoordinator.swift b/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderCoordinator.swift index 30743cb71..7ed3a19f6 100644 --- a/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderCoordinator.swift +++ b/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderCoordinator.swift @@ -17,6 +17,7 @@ final class WebReaderCoordinator: NSObject { var lastSavedAnnotationID: UUID? var previousIncreaseFontActionID: UUID? var previousDecreaseFontActionID: UUID? + var previousShowNavBarActionID: UUID? var updateNavBarVisibilityRatio: (Double) -> Void = { _ in } private var yOffsetAtStartOfDrag: Double? private var lastYOffset: Double = 0 @@ -33,6 +34,10 @@ final class WebReaderCoordinator: NSObject { updateNavBarVisibilityRatio(navBarVisibilityRatio) } } + + func showNavBar() { + isNavBarHidden = false + } } extension WebReaderCoordinator: WKScriptMessageHandler { diff --git a/apple/OmnivoreKit/Sources/Models/CoreData/CoreDataModel.xcdatamodeld/CoreDataModel.xcdatamodel/contents b/apple/OmnivoreKit/Sources/Models/CoreData/CoreDataModel.xcdatamodeld/CoreDataModel.xcdatamodel/contents index 8b34e1252..183be56df 100644 --- a/apple/OmnivoreKit/Sources/Models/CoreData/CoreDataModel.xcdatamodeld/CoreDataModel.xcdatamodel/contents +++ b/apple/OmnivoreKit/Sources/Models/CoreData/CoreDataModel.xcdatamodeld/CoreDataModel.xcdatamodel/contents @@ -41,7 +41,7 @@ - + @@ -55,6 +55,7 @@ + @@ -85,7 +86,7 @@ - + diff --git a/apple/OmnivoreKit/Sources/Models/LinkedItemFilter.swift b/apple/OmnivoreKit/Sources/Models/LinkedItemFilter.swift new file mode 100644 index 000000000..c257b92ef --- /dev/null +++ b/apple/OmnivoreKit/Sources/Models/LinkedItemFilter.swift @@ -0,0 +1,83 @@ +import Foundation + +public enum LinkedItemFilter: String, CaseIterable { + case inbox + case readlater + case newsletters + case all + case archived + case files +} + +public extension LinkedItemFilter { + var displayName: String { + switch self { + case .inbox: + return "Inbox" + case .readlater: + return "Read Later" + case .newsletters: + return "Newsletters" + case .all: + return "All" + case .archived: + return "Archived" + case .files: + return "Files" + } + } + + var queryString: String { + switch self { + case .inbox: + return "in:inbox" + case .readlater: + return "in:inbox -label:Newsletter" + case .newsletters: + return "in:inbox label:Newsletter" + case .all: + return "in:all" + case .archived: + return "in:archive" + case .files: + return "type:file" + } + } + + var predicate: NSPredicate { + let undeletedPredicate = NSPredicate( + format: "%K != %i", #keyPath(LinkedItem.serverSyncStatus), Int64(ServerSyncStatus.needsDeletion.rawValue) + ) + let notInArchivePredicate = NSPredicate( + format: "%K == %@", #keyPath(LinkedItem.isArchived), Int(truncating: false) as NSNumber + ) + + switch self { + case .inbox: + // non-archived items + return NSCompoundPredicate(andPredicateWithSubpredicates: [undeletedPredicate, notInArchivePredicate]) + case .readlater: + // non-archived or deleted items without the Newsletter label + let nonNewsletterLabelPredicate = NSPredicate(format: "NOT SUBQUERY(labels, $label, $label.name == \"Newsletter\") .@count > 0") + return NSCompoundPredicate(andPredicateWithSubpredicates: [undeletedPredicate, notInArchivePredicate, nonNewsletterLabelPredicate]) + case .newsletters: + // non-archived or deleted items with the Newsletter label + let newsletterLabelPredicate = NSPredicate(format: "SUBQUERY(labels, $label, $label.name == \"Newsletter\").@count > 0") + return NSCompoundPredicate(andPredicateWithSubpredicates: [notInArchivePredicate, newsletterLabelPredicate]) + case .all: + // include everything undeleted + return undeletedPredicate + case .archived: + let inArchivePredicate = NSPredicate( + format: "%K == %@", #keyPath(LinkedItem.isArchived), Int(truncating: true) as NSNumber + ) + return NSCompoundPredicate(andPredicateWithSubpredicates: [undeletedPredicate, inArchivePredicate]) + case .files: + // include pdf only + let isPDFPredicate = NSPredicate( + format: "%K == %@", #keyPath(LinkedItem.contentReader), "PDF" + ) + return NSCompoundPredicate(andPredicateWithSubpredicates: [undeletedPredicate, isPDFPredicate]) + } + } +} diff --git a/apple/OmnivoreKit/Sources/Services/DataService/DataService.swift b/apple/OmnivoreKit/Sources/Services/DataService/DataService.swift index 145e0dade..3528a3dab 100644 --- a/apple/OmnivoreKit/Sources/Services/DataService/DataService.swift +++ b/apple/OmnivoreKit/Sources/Services/DataService/DataService.swift @@ -3,6 +3,7 @@ import CoreData import Foundation import Models import OSLog +import Utils let logger = Logger(subsystem: "app.omnivore", category: "data-service") @@ -13,8 +14,8 @@ public final class DataService: ObservableObject { public let appEnvironment: AppEnvironment let networker: Networker - let persistentContainer: PersistentContainer - let backgroundContext: NSManagedObjectContext + var persistentContainer: PersistentContainer + var backgroundContext: NSManagedObjectContext var subscriptions = Set() public var viewContext: NSManagedObjectContext { @@ -28,9 +29,13 @@ public final class DataService: ObservableObject { self.backgroundContext = persistentContainer.newBackgroundContext() backgroundContext.mergePolicy = NSMergePolicy.mergeByPropertyObjectTrump - persistentContainer.loadPersistentStores { _, error in - if let error = error { - fatalError("Core Data store failed to load with error: \(error)") + if isFirstTimeRunningNewAppVersion() { + resetCoreData() + } else { + persistentContainer.loadPersistentStores { _, error in + if let error = error { + fatalError("Core Data store failed to load with error: \(error)") + } } } } @@ -49,4 +54,38 @@ public final class DataService: ObservableObject { fatalError("Unable to write to Keychain: \(error)") } } + + private func resetCoreData() { + let storeContainer = + persistentContainer.persistentStoreCoordinator + + do { + for store in storeContainer.persistentStores { + try storeContainer.destroyPersistentStore( + at: store.url!, + ofType: store.type, + options: nil + ) + } + persistentContainer = PersistentContainer.make() + persistentContainer.loadPersistentStores { _, error in + if let error = error { + fatalError("Core Data store failed to load with error: \(error)") + } + } + backgroundContext = persistentContainer.newBackgroundContext() + } catch { + logger.debug("Failed to reset core data stores") + } + } + + private func isFirstTimeRunningNewAppVersion() -> Bool { + let appVersion = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") + guard let appVersion = appVersion as? String else { return false } + + let lastUsedAppVersion = UserDefaults.standard.string(forKey: UserDefaultKey.lastUsedAppVersion.rawValue) + let isFirstRun = (lastUsedAppVersion ?? "unknown") != appVersion + UserDefaults.standard.set(appVersion, forKey: UserDefaultKey.lastUsedAppVersion.rawValue) + return isFirstRun + } } diff --git a/apple/OmnivoreKit/Sources/Services/DataService/Mutations/RemoveLabelPublisher.swift b/apple/OmnivoreKit/Sources/Services/DataService/Mutations/RemoveLabelPublisher.swift index 30e6844ce..fc7c02c2b 100644 --- a/apple/OmnivoreKit/Sources/Services/DataService/Mutations/RemoveLabelPublisher.swift +++ b/apple/OmnivoreKit/Sources/Services/DataService/Mutations/RemoveLabelPublisher.swift @@ -7,7 +7,7 @@ extension DataService { // Update CoreData backgroundContext.perform { [weak self] in guard let self = self else { return } - guard let label = LinkedItemLabel.lookup(byName: name, inContext: self.backgroundContext) else { return } + guard let label = LinkedItemLabel.lookup(byID: labelID, inContext: self.backgroundContext) else { return } label.remove(inContext: self.backgroundContext) // Send update to server @@ -15,7 +15,7 @@ extension DataService { } } - func syncLabelDeletion(labelID: String, labelName: String) { + func syncLabelDeletion(labelID: String, labelName _: String) { enum MutationResult { case success(labelID: String) case error(errorCode: Enums.DeleteLabelErrorCode) @@ -43,7 +43,7 @@ extension DataService { let isSyncSuccess = data != nil context.perform { - let label = LinkedItemLabel.lookup(byName: labelName, inContext: context) + let label = LinkedItemLabel.lookup(byID: labelID, inContext: context) guard let label = label else { return } if isSyncSuccess { diff --git a/apple/OmnivoreKit/Sources/Services/DataService/Mutations/UpdateArticleLabelsPublisher.swift b/apple/OmnivoreKit/Sources/Services/DataService/Mutations/UpdateArticleLabelsPublisher.swift index 9b4fa549d..cab778898 100644 --- a/apple/OmnivoreKit/Sources/Services/DataService/Mutations/UpdateArticleLabelsPublisher.swift +++ b/apple/OmnivoreKit/Sources/Services/DataService/Mutations/UpdateArticleLabelsPublisher.swift @@ -4,7 +4,7 @@ import Models import SwiftGraphQL extension DataService { - public func updateItemLabels(itemID: String, labelNames: [String]) { + public func updateItemLabels(itemID: String, labelIDs: [String]) { backgroundContext.perform { [weak self] in guard let self = self else { return } guard let linkedItem = LinkedItem.lookup(byID: itemID, inContext: self.backgroundContext) else { return } @@ -13,12 +13,9 @@ extension DataService { linkedItem.removeFromLabels(existingLabels) } - var labelIDs = [String]() - - for labelName in labelNames { - if let labelObject = LinkedItemLabel.lookup(byName: labelName, inContext: self.backgroundContext) { + for labelID in labelIDs { + if let labelObject = LinkedItemLabel.lookup(byID: labelID, inContext: self.backgroundContext) { linkedItem.addToLabels(labelObject) - labelIDs.append(labelObject.unwrappedID) } } diff --git a/apple/OmnivoreKit/Sources/Services/InternalModels/InternalLinkedItemLabel.swift b/apple/OmnivoreKit/Sources/Services/InternalModels/InternalLinkedItemLabel.swift index ca31ae33e..f54cf6f8b 100644 --- a/apple/OmnivoreKit/Sources/Services/InternalModels/InternalLinkedItemLabel.swift +++ b/apple/OmnivoreKit/Sources/Services/InternalModels/InternalLinkedItemLabel.swift @@ -29,8 +29,8 @@ struct InternalLinkedItemLabel { } func asManagedObject(inContext context: NSManagedObjectContext) -> LinkedItemLabel { - let existingItem = LinkedItemLabel.lookup(byName: name, inContext: context) - let label = existingItem ?? LinkedItemLabel(entity: LinkedItemLabel.entity(), insertInto: context) + let existingLabel = LinkedItemLabel.lookup(byID: id, inContext: context) + let label = existingLabel ?? LinkedItemLabel(entity: LinkedItemLabel.entity(), insertInto: context) label.id = id label.name = name label.color = color @@ -44,10 +44,10 @@ extension LinkedItemLabel { public var unwrappedID: String { id ?? "" } public var unwrappedName: String { name ?? "" } - static func lookup(byName name: String, inContext context: NSManagedObjectContext) -> LinkedItemLabel? { + static func lookup(byID id: String, inContext context: NSManagedObjectContext) -> LinkedItemLabel? { let fetchRequest: NSFetchRequest = LinkedItemLabel.fetchRequest() fetchRequest.predicate = NSPredicate( - format: "%K == %@", #keyPath(LinkedItemLabel.name), name + format: "id == %@", id ) var label: LinkedItemLabel? diff --git a/apple/OmnivoreKit/Sources/Utils/UserDefaultKeys.swift b/apple/OmnivoreKit/Sources/Utils/UserDefaultKeys.swift index 369dc232c..c32f00401 100644 --- a/apple/OmnivoreKit/Sources/Utils/UserDefaultKeys.swift +++ b/apple/OmnivoreKit/Sources/Utils/UserDefaultKeys.swift @@ -5,4 +5,6 @@ public enum UserDefaultKey: String { case userHasDeniedPushPrimer case firebasePushToken case homeFeedlayoutPreference + case lastSelectedLinkedItemFilter + case lastUsedAppVersion } diff --git a/apple/OmnivoreKit/Sources/Views/SearchBar.swift b/apple/OmnivoreKit/Sources/Views/SearchBar.swift new file mode 100644 index 000000000..7c7e02a4f --- /dev/null +++ b/apple/OmnivoreKit/Sources/Views/SearchBar.swift @@ -0,0 +1,59 @@ +import SwiftUI + +public struct SearchBar: View { + @Binding var searchTerm: String + @FocusState private var isFocused: Bool + + public init( + searchTerm: Binding + ) { + self._searchTerm = searchTerm + } + + public var body: some View { + HStack(spacing: 0) { + TextField("Search", text: $searchTerm) + .padding(7) + .padding(.horizontal, 25) + .background(Color(.systemGray6)) + .cornerRadius(8) + .focused($isFocused) + .overlay( + HStack { + Image(systemName: "magnifyingglass") + .foregroundColor(.gray) + .frame(minWidth: 0, maxWidth: .infinity, alignment: .leading) + .padding(.leading, 10) + + if self.searchTerm != "" { + Button( + action: { + self.searchTerm = "" + }, + label: { + Image(systemName: "multiply.circle.fill") + .foregroundColor(.gray) + .padding(.trailing, 8) + } + ) + } + } + ) + .padding(.horizontal, 10) + + if isFocused { + Button( + action: { + self.searchTerm = "" + self.isFocused = false + }, + label: { + Text("Cancel") + } + ) + .padding(.trailing, 10) + .transition(.move(edge: .trailing)) + } + } + } +} diff --git a/apple/OmnivoreKit/Sources/Views/TextChip.swift b/apple/OmnivoreKit/Sources/Views/TextChip.swift index 4e0ada9c4..2c2bd5c8b 100644 --- a/apple/OmnivoreKit/Sources/Views/TextChip.swift +++ b/apple/OmnivoreKit/Sources/Views/TextChip.swift @@ -34,6 +34,10 @@ public struct TextChipButton: View { TextChipButton(title: "Labels", color: .systemGray6, actionType: .show, onTap: onTap) } + public static func makeFilterButton(title: String) -> TextChipButton { + TextChipButton(title: title, color: .systemGray6, actionType: .show, onTap: {}) + } + public static func makeShowOptionsButton(title: String, onTap: @escaping () -> Void) -> TextChipButton { TextChipButton(title: title, color: .appButtonBackground, actionType: .add, onTap: onTap) } @@ -67,7 +71,7 @@ public struct TextChipButton: View { } } - init(title: String, color: Color, actionType: ActionType, onTap: @escaping () -> Void) { + public init(title: String, color: Color, actionType: ActionType, onTap: @escaping () -> Void) { self.text = title self.color = color self.onTap = onTap @@ -87,23 +91,21 @@ public struct TextChipButton: View { let foregroundColor: Color public var body: some View { - Button(action: onTap) { - VStack(spacing: 0) { - HStack { - Text(text) - .padding(.leading, 3) - Image(systemName: actionType.systemIconName) - } - .padding(.horizontal, 10) - .padding(.vertical, 8) - .font(.appFootnote) - .foregroundColor(foregroundColor) - .lineLimit(1) - .background(Capsule().fill(color)) - - Color.clear.contentShape(Rectangle()).frame(height: 15) + VStack(spacing: 0) { + HStack { + Text(text) + .padding(.leading, 3) + Image(systemName: actionType.systemIconName) } - .contentShape(Rectangle()) + .padding(.horizontal, 10) + .padding(.vertical, 8) + .font(.appFootnote) + .foregroundColor(foregroundColor) + .lineLimit(1) + .background(Capsule().fill(color)) } + .padding(.vertical, 12) + .contentShape(Rectangle()) + .onTapGesture { onTap() } } }