From c85c244459926de33aeaff875063a3ec5c544cd4 Mon Sep 17 00:00:00 2001 From: Satindar Dhillon Date: Wed, 6 Apr 2022 08:28:37 -0700 Subject: [PATCH 1/8] use text chip to display labels in labels view --- apple/OmnivoreKit/Sources/App/Views/Profile/LabelsView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apple/OmnivoreKit/Sources/App/Views/Profile/LabelsView.swift b/apple/OmnivoreKit/Sources/App/Views/Profile/LabelsView.swift index 852b64e8c..7161f67ad 100644 --- a/apple/OmnivoreKit/Sources/App/Views/Profile/LabelsView.swift +++ b/apple/OmnivoreKit/Sources/App/Views/Profile/LabelsView.swift @@ -123,7 +123,7 @@ struct LabelsView: View { Section(header: Text("Labels")) { ForEach(viewModel.labels, id: \.id) { label in HStack { - Text(label.name) + TextChip(feedItemLabel: label) Spacer() Button( action: { From e059d2d6e7212aa1ca23dfb5a2e1ac53dce501ec Mon Sep 17 00:00:00 2001 From: Satindar Dhillon Date: Wed, 6 Apr 2022 08:53:18 -0700 Subject: [PATCH 2/8] use assigned and unassigned section for label asiignment modal --- .../Sources/App/Views/ApplyLabelsView.swift | 63 +++++++++++++++---- 1 file changed, 51 insertions(+), 12 deletions(-) diff --git a/apple/OmnivoreKit/Sources/App/Views/ApplyLabelsView.swift b/apple/OmnivoreKit/Sources/App/Views/ApplyLabelsView.swift index ffb6214fb..23e65a0b7 100644 --- a/apple/OmnivoreKit/Sources/App/Views/ApplyLabelsView.swift +++ b/apple/OmnivoreKit/Sources/App/Views/ApplyLabelsView.swift @@ -7,8 +7,8 @@ import Views final class ApplyLabelsViewModel: ObservableObject { private var hasLoadedInitialLabels = false @Published var isLoading = true - @Published var selectedLabels = Set() - @Published var labels = [FeedItemLabel]() + @Published var selectedLabels = [FeedItemLabel]() + @Published var unselectedLabels = [FeedItemLabel]() var subscriptions = Set() @@ -17,11 +17,11 @@ final class ApplyLabelsViewModel: ObservableObject { dataService.labelsPublisher().sink( receiveCompletion: { _ in }, - receiveValue: { [weak self] result in + receiveValue: { [weak self] allLabels in self?.isLoading = false - self?.labels = result self?.hasLoadedInitialLabels = true - self?.selectedLabels = Set(item.labels) + self?.selectedLabels = item.labels + self?.unselectedLabels = allLabels.filter { !item.labels.contains($0) } } ) .store(in: &subscriptions) @@ -34,6 +34,16 @@ final class ApplyLabelsViewModel: ObservableObject { ) .store(in: &subscriptions) } + + func addLabel(_ label: FeedItemLabel) { + selectedLabels.insert(label, at: 0) + unselectedLabels.removeAll { $0.id == label.id } + } + + func removeLabel(_ label: FeedItemLabel) { + unselectedLabels.insert(label, at: 0) + selectedLabels.removeAll { $0.id == label.id } + } } struct ApplyLabelsView: View { @@ -49,15 +59,44 @@ struct ApplyLabelsView: View { if viewModel.isLoading { EmptyView() } else { - List(viewModel.labels, id: \.self, selection: $viewModel.selectedLabels) { label in - if let textChip = TextChip(feedItemLabel: label) { - textChip - } else { - Text(label.name) + List { + Section(header: Text("Assigned Labels")) { + if viewModel.selectedLabels.isEmpty { + Text("No labels are currently assigned.") + } + ForEach(viewModel.selectedLabels, id: \.self) { label in + HStack { + TextChip(feedItemLabel: label) + Spacer() + Button( + action: { + withAnimation { + viewModel.removeLabel(label) + } + }, + label: { Image(systemName: "trash") } + ) + } + } + } + Section(header: Text("Available Labels")) { + ForEach(viewModel.unselectedLabels, id: \.self) { label in + HStack { + TextChip(feedItemLabel: label) + Spacer() + Button( + action: { + withAnimation { + viewModel.addLabel(label) + } + }, + label: { Image(systemName: "plus") } + ) + } + } } } - .environment(\.editMode, .constant(EditMode.active)) - .navigationTitle("Apply Labels") + .navigationTitle("Assign Labels") .navigationBarTitleDisplayMode(.inline) .toolbar { ToolbarItem(placement: .navigationBarLeading) { From 5cbfca1fa95b1a7b3d363bfa63912b28ee65dba8 Mon Sep 17 00:00:00 2001 From: Satindar Dhillon Date: Wed, 6 Apr 2022 09:09:12 -0700 Subject: [PATCH 3/8] add create label option to label assignment modal --- .../Views/{ => Labels}/ApplyLabelsView.swift | 61 +++-------- .../{Profile => Labels}/LabelsView.swift | 59 ---------- .../App/Views/Labels/LabelsViewModel.swift | 103 ++++++++++++++++++ 3 files changed, 120 insertions(+), 103 deletions(-) rename apple/OmnivoreKit/Sources/App/Views/{ => Labels}/ApplyLabelsView.swift (62%) rename apple/OmnivoreKit/Sources/App/Views/{Profile => Labels}/LabelsView.swift (70%) create mode 100644 apple/OmnivoreKit/Sources/App/Views/Labels/LabelsViewModel.swift diff --git a/apple/OmnivoreKit/Sources/App/Views/ApplyLabelsView.swift b/apple/OmnivoreKit/Sources/App/Views/Labels/ApplyLabelsView.swift similarity index 62% rename from apple/OmnivoreKit/Sources/App/Views/ApplyLabelsView.swift rename to apple/OmnivoreKit/Sources/App/Views/Labels/ApplyLabelsView.swift index 23e65a0b7..f49d8d582 100644 --- a/apple/OmnivoreKit/Sources/App/Views/ApplyLabelsView.swift +++ b/apple/OmnivoreKit/Sources/App/Views/Labels/ApplyLabelsView.swift @@ -1,58 +1,15 @@ -import Combine import Models import Services import SwiftUI import Views -final class ApplyLabelsViewModel: ObservableObject { - private var hasLoadedInitialLabels = false - @Published var isLoading = true - @Published var selectedLabels = [FeedItemLabel]() - @Published var unselectedLabels = [FeedItemLabel]() - - var subscriptions = Set() - - func load(item: FeedItem, dataService: DataService) { - guard !hasLoadedInitialLabels else { return } - - dataService.labelsPublisher().sink( - receiveCompletion: { _ in }, - receiveValue: { [weak self] allLabels in - self?.isLoading = false - self?.hasLoadedInitialLabels = true - self?.selectedLabels = item.labels - self?.unselectedLabels = allLabels.filter { !item.labels.contains($0) } - } - ) - .store(in: &subscriptions) - } - - func saveChanges(itemID: String, dataService: DataService, onComplete: @escaping ([FeedItemLabel]) -> Void) { - dataService.updateArticleLabelsPublisher(itemID: itemID, labelIDs: selectedLabels.map(\.id)).sink( - receiveCompletion: { _ in }, - receiveValue: { onComplete($0) } - ) - .store(in: &subscriptions) - } - - func addLabel(_ label: FeedItemLabel) { - selectedLabels.insert(label, at: 0) - unselectedLabels.removeAll { $0.id == label.id } - } - - func removeLabel(_ label: FeedItemLabel) { - unselectedLabels.insert(label, at: 0) - selectedLabels.removeAll { $0.id == label.id } - } -} - struct ApplyLabelsView: View { let item: FeedItem let commitLabelChanges: ([FeedItemLabel]) -> Void @EnvironmentObject var dataService: DataService @Environment(\.presentationMode) private var presentationMode - @StateObject var viewModel = ApplyLabelsViewModel() + @StateObject var viewModel = LabelsViewModel() var body: some View { NavigationView { @@ -95,6 +52,19 @@ struct ApplyLabelsView: View { } } } + Section { + Button( + action: { viewModel.showCreateEmailModal = true }, + label: { + HStack { + Image(systemName: "plus.circle.fill").foregroundColor(.green) + Text("Create a new Label") + Spacer() + } + } + ) + .disabled(viewModel.isLoading) + } } .navigationTitle("Assign Labels") .navigationBarTitleDisplayMode(.inline) @@ -117,6 +87,9 @@ struct ApplyLabelsView: View { ) } } + .sheet(isPresented: $viewModel.showCreateEmailModal) { + CreateLabelView(viewModel: viewModel) + } } } .onAppear { diff --git a/apple/OmnivoreKit/Sources/App/Views/Profile/LabelsView.swift b/apple/OmnivoreKit/Sources/App/Views/Labels/LabelsView.swift similarity index 70% rename from apple/OmnivoreKit/Sources/App/Views/Profile/LabelsView.swift rename to apple/OmnivoreKit/Sources/App/Views/Labels/LabelsView.swift index 7161f67ad..5d535b0d8 100644 --- a/apple/OmnivoreKit/Sources/App/Views/Profile/LabelsView.swift +++ b/apple/OmnivoreKit/Sources/App/Views/Labels/LabelsView.swift @@ -4,65 +4,6 @@ import Services import SwiftUI import Views -final class LabelsViewModel: ObservableObject { - private var hasLoadedInitialLabels = false - @Published var isLoading = false - @Published var labels = [FeedItemLabel]() - @Published var showCreateEmailModal = false - - var subscriptions = Set() - - func loadLabels(dataService: DataService) { - guard !hasLoadedInitialLabels else { return } - isLoading = true - - dataService.labelsPublisher().sink( - receiveCompletion: { _ in }, - receiveValue: { [weak self] result in - self?.isLoading = false - self?.labels = result - self?.hasLoadedInitialLabels = true - } - ) - .store(in: &subscriptions) - } - - func createLabel(dataService: DataService, name: String, color: Color, description: String?) { - isLoading = true - - dataService.createLabelPublisher( - name: name, - color: color.hex ?? "", - description: description - ).sink( - receiveCompletion: { [weak self] _ in - self?.isLoading = false - }, - receiveValue: { [weak self] result in - self?.isLoading = false - self?.labels.insert(result, at: 0) - self?.showCreateEmailModal = false - } - ) - .store(in: &subscriptions) - } - - func deleteLabel(dataService: DataService, labelID: String) { - isLoading = true - - dataService.removeLabelPublisher(labelID: labelID).sink( - receiveCompletion: { [weak self] _ in - self?.isLoading = false - }, - receiveValue: { [weak self] _ in - self?.isLoading = false - self?.labels.removeAll { $0.id == labelID } - } - ) - .store(in: &subscriptions) - } -} - struct LabelsView: View { @EnvironmentObject var dataService: DataService @StateObject var viewModel = LabelsViewModel() diff --git a/apple/OmnivoreKit/Sources/App/Views/Labels/LabelsViewModel.swift b/apple/OmnivoreKit/Sources/App/Views/Labels/LabelsViewModel.swift new file mode 100644 index 000000000..4d06f287c --- /dev/null +++ b/apple/OmnivoreKit/Sources/App/Views/Labels/LabelsViewModel.swift @@ -0,0 +1,103 @@ +import Combine +import Models +import Services +import SwiftUI +import Views + +final class LabelsViewModel: ObservableObject { + private var hasLoadedInitialLabels = false + @Published var isLoading = false + @Published var selectedLabels = [FeedItemLabel]() + @Published var unselectedLabels = [FeedItemLabel]() + @Published var labels = [FeedItemLabel]() + @Published var showCreateEmailModal = false + + var subscriptions = Set() + + func loadLabels(dataService: DataService) { + guard !hasLoadedInitialLabels else { return } + isLoading = true + + dataService.labelsPublisher().sink( + receiveCompletion: { _ in }, + receiveValue: { [weak self] result in + self?.isLoading = false + self?.labels = result + self?.hasLoadedInitialLabels = true + } + ) + .store(in: &subscriptions) + } + + func load(item: FeedItem, dataService: DataService) { + guard !hasLoadedInitialLabels else { return } + + dataService.labelsPublisher().sink( + receiveCompletion: { _ in }, + receiveValue: { [weak self] allLabels in + self?.isLoading = false + self?.hasLoadedInitialLabels = true + self?.selectedLabels = item.labels + self?.unselectedLabels = allLabels.filter { !item.labels.contains($0) } + } + ) + .store(in: &subscriptions) + } + + func createLabel(dataService: DataService, name: String, color: Color, description: String?) { + isLoading = true + + dataService.createLabelPublisher( + name: name, + color: color.hex ?? "", + description: description + ).sink( + receiveCompletion: { [weak self] _ in + self?.isLoading = false + }, + receiveValue: { [weak self] result in + self?.isLoading = false + self?.labels.insert(result, at: 0) + self?.unselectedLabels.insert(result, at: 0) + self?.showCreateEmailModal = false + } + ) + .store(in: &subscriptions) + } + + func deleteLabel(dataService: DataService, labelID: String) { + isLoading = true + + dataService.removeLabelPublisher(labelID: labelID).sink( + receiveCompletion: { [weak self] _ in + self?.isLoading = false + }, + receiveValue: { [weak self] _ in + self?.isLoading = false + self?.labels.removeAll { $0.id == labelID } + } + ) + .store(in: &subscriptions) + } + + func saveChanges(itemID: String, dataService: DataService, onComplete: @escaping ([FeedItemLabel]) -> Void) { + isLoading = true + dataService.updateArticleLabelsPublisher(itemID: itemID, labelIDs: selectedLabels.map(\.id)).sink( + receiveCompletion: { [weak self] _ in + self?.isLoading = false + }, + receiveValue: { onComplete($0) } + ) + .store(in: &subscriptions) + } + + func addLabel(_ label: FeedItemLabel) { + selectedLabels.insert(label, at: 0) + unselectedLabels.removeAll { $0.id == label.id } + } + + func removeLabel(_ label: FeedItemLabel) { + unselectedLabels.insert(label, at: 0) + selectedLabels.removeAll { $0.id == label.id } + } +} From e9589fd7f38b11f171e134854f8fc86a8f8f72a2 Mon Sep 17 00:00:00 2001 From: Satindar Dhillon Date: Wed, 6 Apr 2022 09:19:15 -0700 Subject: [PATCH 4/8] apply correct tint colors to label modal buttons --- .../App/Views/Labels/ApplyLabelsView.swift | 24 ++++++++-------- .../Sources/App/Views/Labels/LabelsView.swift | 2 +- .../App/Views/Labels/LabelsViewModel.swift | 28 +++++++++---------- 3 files changed, 27 insertions(+), 27 deletions(-) diff --git a/apple/OmnivoreKit/Sources/App/Views/Labels/ApplyLabelsView.swift b/apple/OmnivoreKit/Sources/App/Views/Labels/ApplyLabelsView.swift index f49d8d582..9b2dd7335 100644 --- a/apple/OmnivoreKit/Sources/App/Views/Labels/ApplyLabelsView.swift +++ b/apple/OmnivoreKit/Sources/App/Views/Labels/ApplyLabelsView.swift @@ -18,36 +18,36 @@ struct ApplyLabelsView: View { } else { List { Section(header: Text("Assigned Labels")) { - if viewModel.selectedLabels.isEmpty { + if viewModel.selectedLabelsForItemInContext.isEmpty { Text("No labels are currently assigned.") } - ForEach(viewModel.selectedLabels, id: \.self) { label in + ForEach(viewModel.selectedLabelsForItemInContext, id: \.self) { label in HStack { TextChip(feedItemLabel: label) Spacer() Button( action: { withAnimation { - viewModel.removeLabel(label) + viewModel.removeLabelFromItem(label) } }, - label: { Image(systemName: "trash") } + label: { Image(systemName: "trash").foregroundColor(.appGrayTextContrast) } ) } } } Section(header: Text("Available Labels")) { - ForEach(viewModel.unselectedLabels, id: \.self) { label in + ForEach(viewModel.unselectedLabelsForItemInContext, id: \.self) { label in HStack { TextChip(feedItemLabel: label) Spacer() Button( action: { withAnimation { - viewModel.addLabel(label) + viewModel.addLabelToItem(label) } }, - label: { Image(systemName: "plus") } + label: { Image(systemName: "plus").foregroundColor(.appGrayTextContrast) } ) } } @@ -58,7 +58,7 @@ struct ApplyLabelsView: View { label: { HStack { Image(systemName: "plus.circle.fill").foregroundColor(.green) - Text("Create a new Label") + Text("Create a new Label").foregroundColor(.appGrayTextContrast) Spacer() } } @@ -72,18 +72,18 @@ struct ApplyLabelsView: View { ToolbarItem(placement: .navigationBarLeading) { Button( action: { presentationMode.wrappedValue.dismiss() }, - label: { Text("Cancel") } + label: { Text("Cancel").foregroundColor(.appGrayTextContrast) } ) } ToolbarItem(placement: .navigationBarTrailing) { Button( action: { - viewModel.saveChanges(itemID: item.id, dataService: dataService) { labels in + viewModel.saveItemLabelChanges(itemID: item.id, dataService: dataService) { labels in commitLabelChanges(labels) presentationMode.wrappedValue.dismiss() } }, - label: { Text("Save") } + label: { Text("Save").foregroundColor(.appGrayTextContrast) } ) } } @@ -93,7 +93,7 @@ struct ApplyLabelsView: View { } } .onAppear { - viewModel.load(item: item, dataService: dataService) + viewModel.loadLabelForItem(item: item, dataService: dataService) } } } diff --git a/apple/OmnivoreKit/Sources/App/Views/Labels/LabelsView.swift b/apple/OmnivoreKit/Sources/App/Views/Labels/LabelsView.swift index 5d535b0d8..d727f5a32 100644 --- a/apple/OmnivoreKit/Sources/App/Views/Labels/LabelsView.swift +++ b/apple/OmnivoreKit/Sources/App/Views/Labels/LabelsView.swift @@ -52,7 +52,7 @@ struct LabelsView: View { label: { HStack { Image(systemName: "plus.circle.fill").foregroundColor(.green) - Text("Create a new Label") + Text("Create a new Label").foregroundColor(.appGrayTextContrast) Spacer() } } diff --git a/apple/OmnivoreKit/Sources/App/Views/Labels/LabelsViewModel.swift b/apple/OmnivoreKit/Sources/App/Views/Labels/LabelsViewModel.swift index 4d06f287c..8825a8cd7 100644 --- a/apple/OmnivoreKit/Sources/App/Views/Labels/LabelsViewModel.swift +++ b/apple/OmnivoreKit/Sources/App/Views/Labels/LabelsViewModel.swift @@ -7,8 +7,8 @@ import Views final class LabelsViewModel: ObservableObject { private var hasLoadedInitialLabels = false @Published var isLoading = false - @Published var selectedLabels = [FeedItemLabel]() - @Published var unselectedLabels = [FeedItemLabel]() + @Published var selectedLabelsForItemInContext = [FeedItemLabel]() + @Published var unselectedLabelsForItemInContext = [FeedItemLabel]() @Published var labels = [FeedItemLabel]() @Published var showCreateEmailModal = false @@ -29,7 +29,7 @@ final class LabelsViewModel: ObservableObject { .store(in: &subscriptions) } - func load(item: FeedItem, dataService: DataService) { + func loadLabelForItem(item: FeedItem, dataService: DataService) { guard !hasLoadedInitialLabels else { return } dataService.labelsPublisher().sink( @@ -37,8 +37,8 @@ final class LabelsViewModel: ObservableObject { receiveValue: { [weak self] allLabels in self?.isLoading = false self?.hasLoadedInitialLabels = true - self?.selectedLabels = item.labels - self?.unselectedLabels = allLabels.filter { !item.labels.contains($0) } + self?.selectedLabelsForItemInContext = item.labels + self?.unselectedLabelsForItemInContext = allLabels.filter { !item.labels.contains($0) } } ) .store(in: &subscriptions) @@ -58,7 +58,7 @@ final class LabelsViewModel: ObservableObject { receiveValue: { [weak self] result in self?.isLoading = false self?.labels.insert(result, at: 0) - self?.unselectedLabels.insert(result, at: 0) + self?.unselectedLabelsForItemInContext.insert(result, at: 0) self?.showCreateEmailModal = false } ) @@ -80,9 +80,9 @@ final class LabelsViewModel: ObservableObject { .store(in: &subscriptions) } - func saveChanges(itemID: String, dataService: DataService, onComplete: @escaping ([FeedItemLabel]) -> Void) { + func saveItemLabelChanges(itemID: String, dataService: DataService, onComplete: @escaping ([FeedItemLabel]) -> Void) { isLoading = true - dataService.updateArticleLabelsPublisher(itemID: itemID, labelIDs: selectedLabels.map(\.id)).sink( + dataService.updateArticleLabelsPublisher(itemID: itemID, labelIDs: selectedLabelsForItemInContext.map(\.id)).sink( receiveCompletion: { [weak self] _ in self?.isLoading = false }, @@ -91,13 +91,13 @@ final class LabelsViewModel: ObservableObject { .store(in: &subscriptions) } - func addLabel(_ label: FeedItemLabel) { - selectedLabels.insert(label, at: 0) - unselectedLabels.removeAll { $0.id == label.id } + func addLabelToItem(_ label: FeedItemLabel) { + selectedLabelsForItemInContext.insert(label, at: 0) + unselectedLabelsForItemInContext.removeAll { $0.id == label.id } } - func removeLabel(_ label: FeedItemLabel) { - unselectedLabels.insert(label, at: 0) - selectedLabels.removeAll { $0.id == label.id } + func removeLabelFromItem(_ label: FeedItemLabel) { + unselectedLabelsForItemInContext.insert(label, at: 0) + selectedLabelsForItemInContext.removeAll { $0.id == label.id } } } From 8329cc166cb8db171160d81133a7690e3d9096f1 Mon Sep 17 00:00:00 2001 From: Satindar Dhillon Date: Wed, 6 Apr 2022 09:36:37 -0700 Subject: [PATCH 5/8] fix filter application --- .../App/Views/Labels/ApplyLabelsView.swift | 2 +- .../Sources/App/Views/Labels/LabelsView.swift | 2 +- .../App/Views/Labels/LabelsViewModel.swift | 25 ++++++------------- 3 files changed, 10 insertions(+), 19 deletions(-) diff --git a/apple/OmnivoreKit/Sources/App/Views/Labels/ApplyLabelsView.swift b/apple/OmnivoreKit/Sources/App/Views/Labels/ApplyLabelsView.swift index 9b2dd7335..d2227a3ed 100644 --- a/apple/OmnivoreKit/Sources/App/Views/Labels/ApplyLabelsView.swift +++ b/apple/OmnivoreKit/Sources/App/Views/Labels/ApplyLabelsView.swift @@ -93,7 +93,7 @@ struct ApplyLabelsView: View { } } .onAppear { - viewModel.loadLabelForItem(item: item, dataService: dataService) + viewModel.loadLabels(dataService: dataService, item: item) } } } diff --git a/apple/OmnivoreKit/Sources/App/Views/Labels/LabelsView.swift b/apple/OmnivoreKit/Sources/App/Views/Labels/LabelsView.swift index d727f5a32..e89c6144f 100644 --- a/apple/OmnivoreKit/Sources/App/Views/Labels/LabelsView.swift +++ b/apple/OmnivoreKit/Sources/App/Views/Labels/LabelsView.swift @@ -41,7 +41,7 @@ struct LabelsView: View { .listStyle(InsetListStyle()) #endif } - .onAppear { viewModel.loadLabels(dataService: dataService) } + .onAppear { viewModel.loadLabels(dataService: dataService, item: nil) } } private var innerBody: some View { diff --git a/apple/OmnivoreKit/Sources/App/Views/Labels/LabelsViewModel.swift b/apple/OmnivoreKit/Sources/App/Views/Labels/LabelsViewModel.swift index 8825a8cd7..b38109c72 100644 --- a/apple/OmnivoreKit/Sources/App/Views/Labels/LabelsViewModel.swift +++ b/apple/OmnivoreKit/Sources/App/Views/Labels/LabelsViewModel.swift @@ -14,31 +14,22 @@ final class LabelsViewModel: ObservableObject { var subscriptions = Set() - func loadLabels(dataService: DataService) { + func loadLabels(dataService: DataService, item: FeedItem?) { guard !hasLoadedInitialLabels else { return } isLoading = true - dataService.labelsPublisher().sink( - receiveCompletion: { _ in }, - receiveValue: { [weak self] result in - self?.isLoading = false - self?.labels = result - self?.hasLoadedInitialLabels = true - } - ) - .store(in: &subscriptions) - } - - func loadLabelForItem(item: FeedItem, dataService: DataService) { - guard !hasLoadedInitialLabels else { return } - dataService.labelsPublisher().sink( receiveCompletion: { _ in }, receiveValue: { [weak self] allLabels in self?.isLoading = false + self?.labels = allLabels self?.hasLoadedInitialLabels = true - self?.selectedLabelsForItemInContext = item.labels - self?.unselectedLabelsForItemInContext = allLabels.filter { !item.labels.contains($0) } + if let item = item { + self?.selectedLabelsForItemInContext = item.labels + self?.unselectedLabelsForItemInContext = allLabels.filter { label in + !item.labels.contains(where: { $0.id == label.id }) + } + } } ) .store(in: &subscriptions) From 29d10feb61c64394b2854f9a07c23d607a5061af Mon Sep 17 00:00:00 2001 From: Satindar Dhillon Date: Wed, 6 Apr 2022 20:47:25 -0700 Subject: [PATCH 6/8] move edit label sheet so refreshable modifier doesn't get applied to it --- .../Sources/App/Views/Home/HomeFeedViewIOS.swift | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewIOS.swift b/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewIOS.swift index 535eb577e..a45f7daad 100644 --- a/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewIOS.swift +++ b/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewIOS.swift @@ -49,6 +49,11 @@ import Views .onSubmit(of: .search) { viewModel.loadItems(dataService: dataService, searchQuery: searchQuery, isRefresh: true) } + .sheet(item: $viewModel.itemUnderLabelEdit) { item in + ApplyLabelsView(item: item) { labels in + viewModel.updateLabels(itemID: item.id, labels: labels) + } + } } else { HomeFeedView( prefersListLayout: $prefersListLayout, @@ -58,6 +63,11 @@ import Views itemToSnooze: $itemToSnooze, viewModel: viewModel ) + .sheet(item: $viewModel.itemUnderLabelEdit) { item in + ApplyLabelsView(item: item) { labels in + viewModel.updateLabels(itemID: item.id, labels: labels) + } + } .toolbar { ToolbarItem { if viewModel.isLoading { @@ -167,11 +177,6 @@ import Views } } } - .sheet(item: $viewModel.itemUnderLabelEdit) { item in - ApplyLabelsView(item: item) { labels in - viewModel.updateLabels(itemID: item.id, labels: labels) - } - } } } } From 287da3d93954d81d447612782aea3c7c3df45d6e Mon Sep 17 00:00:00 2001 From: Satindar Dhillon Date: Wed, 6 Apr 2022 21:30:38 -0700 Subject: [PATCH 7/8] use searchable to filter labels --- .../App/Views/Labels/ApplyLabelsView.swift | 167 ++++++++++-------- 1 file changed, 95 insertions(+), 72 deletions(-) diff --git a/apple/OmnivoreKit/Sources/App/Views/Labels/ApplyLabelsView.swift b/apple/OmnivoreKit/Sources/App/Views/Labels/ApplyLabelsView.swift index d2227a3ed..95ff997dd 100644 --- a/apple/OmnivoreKit/Sources/App/Views/Labels/ApplyLabelsView.swift +++ b/apple/OmnivoreKit/Sources/App/Views/Labels/ApplyLabelsView.swift @@ -10,85 +10,99 @@ struct ApplyLabelsView: View { @EnvironmentObject var dataService: DataService @Environment(\.presentationMode) private var presentationMode @StateObject var viewModel = LabelsViewModel() + @State private var labelSearchFilter = "" + + var innerBody: some View { + List { + Section(header: Text("Assigned Labels")) { + if viewModel.selectedLabelsForItemInContext.isEmpty { + Text("No labels are currently assigned.") + } + ForEach(viewModel.selectedLabelsForItemInContext.applySearchFilter(labelSearchFilter), id: \.self) { label in + HStack { + TextChip(feedItemLabel: label) + Spacer() + Button( + action: { + withAnimation { + viewModel.removeLabelFromItem(label) + } + }, + label: { Image(systemName: "trash").foregroundColor(.appGrayTextContrast) } + ) + } + } + } + Section(header: Text("Available Labels")) { + ForEach(viewModel.unselectedLabelsForItemInContext.applySearchFilter(labelSearchFilter), id: \.self) { label in + HStack { + TextChip(feedItemLabel: label) + Spacer() + Button( + action: { + withAnimation { + viewModel.addLabelToItem(label) + } + }, + label: { Image(systemName: "plus").foregroundColor(.appGrayTextContrast) } + ) + } + } + } + Section { + Button( + action: { viewModel.showCreateEmailModal = true }, + label: { + HStack { + Image(systemName: "plus.circle.fill").foregroundColor(.green) + Text("Create a new Label").foregroundColor(.appGrayTextContrast) + Spacer() + } + } + ) + .disabled(viewModel.isLoading) + } + } + .navigationTitle("Assign Labels") + .navigationBarTitleDisplayMode(.inline) + .toolbar { + ToolbarItem(placement: .navigationBarLeading) { + Button( + action: { presentationMode.wrappedValue.dismiss() }, + label: { Text("Cancel").foregroundColor(.appGrayTextContrast) } + ) + } + ToolbarItem(placement: .navigationBarTrailing) { + Button( + action: { + viewModel.saveItemLabelChanges(itemID: item.id, dataService: dataService) { labels in + commitLabelChanges(labels) + presentationMode.wrappedValue.dismiss() + } + }, + label: { Text("Save").foregroundColor(.appGrayTextContrast) } + ) + } + } + .sheet(isPresented: $viewModel.showCreateEmailModal) { + CreateLabelView(viewModel: viewModel) + } + } var body: some View { NavigationView { if viewModel.isLoading { EmptyView() + } else { - List { - Section(header: Text("Assigned Labels")) { - if viewModel.selectedLabelsForItemInContext.isEmpty { - Text("No labels are currently assigned.") - } - ForEach(viewModel.selectedLabelsForItemInContext, id: \.self) { label in - HStack { - TextChip(feedItemLabel: label) - Spacer() - Button( - action: { - withAnimation { - viewModel.removeLabelFromItem(label) - } - }, - label: { Image(systemName: "trash").foregroundColor(.appGrayTextContrast) } - ) - } - } - } - Section(header: Text("Available Labels")) { - ForEach(viewModel.unselectedLabelsForItemInContext, id: \.self) { label in - HStack { - TextChip(feedItemLabel: label) - Spacer() - Button( - action: { - withAnimation { - viewModel.addLabelToItem(label) - } - }, - label: { Image(systemName: "plus").foregroundColor(.appGrayTextContrast) } - ) - } - } - } - Section { - Button( - action: { viewModel.showCreateEmailModal = true }, - label: { - HStack { - Image(systemName: "plus.circle.fill").foregroundColor(.green) - Text("Create a new Label").foregroundColor(.appGrayTextContrast) - Spacer() - } - } + if #available(iOS 15.0, *) { + innerBody + .searchable( + text: $labelSearchFilter, + placement: .navigationBarDrawer(displayMode: .always) ) - .disabled(viewModel.isLoading) - } - } - .navigationTitle("Assign Labels") - .navigationBarTitleDisplayMode(.inline) - .toolbar { - ToolbarItem(placement: .navigationBarLeading) { - Button( - action: { presentationMode.wrappedValue.dismiss() }, - label: { Text("Cancel").foregroundColor(.appGrayTextContrast) } - ) - } - ToolbarItem(placement: .navigationBarTrailing) { - Button( - action: { - viewModel.saveItemLabelChanges(itemID: item.id, dataService: dataService) { labels in - commitLabelChanges(labels) - presentationMode.wrappedValue.dismiss() - } - }, - label: { Text("Save").foregroundColor(.appGrayTextContrast) } - ) - } - } - .sheet(isPresented: $viewModel.showCreateEmailModal) { - CreateLabelView(viewModel: viewModel) + } else { + innerBody } } } @@ -97,3 +111,12 @@ struct ApplyLabelsView: View { } } } + +private extension Sequence where Element == FeedItemLabel { + func applySearchFilter(_ searchFilter: String) -> [FeedItemLabel] { + if searchFilter.isEmpty { + return map { $0 } // return the identity of the sequence + } + return filter { $0.name.lowercased().contains(searchFilter.lowercased()) } + } +} From 9fb0e1112480150f65ea72f7d84b6aceb912a594 Mon Sep 17 00:00:00 2001 From: Satindar Dhillon Date: Thu, 7 Apr 2022 13:30:18 -0700 Subject: [PATCH 8/8] udpate text chip colors --- apple/OmnivoreKit/Sources/Utils/ColorUtils.swift | 12 ++++++++++-- apple/OmnivoreKit/Sources/Views/TextChip.swift | 8 ++------ 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/apple/OmnivoreKit/Sources/Utils/ColorUtils.swift b/apple/OmnivoreKit/Sources/Utils/ColorUtils.swift index 7718fbf88..2c20306f6 100644 --- a/apple/OmnivoreKit/Sources/Utils/ColorUtils.swift +++ b/apple/OmnivoreKit/Sources/Utils/ColorUtils.swift @@ -45,8 +45,7 @@ public extension Color { } private func toHex() -> String? { - let uic = UIColor(self) - guard let components = uic.cgColor.components, components.count >= 3 else { + guard let components = UIColor(self).cgColor.components, components.count >= 3 else { return nil } let red = Float(components[0]) @@ -60,4 +59,13 @@ public extension Color { lroundf(blue * 255) ) } + + var isDark: Bool { + guard let components = UIColor(self).cgColor.components, components.count >= 3 else { + return false + } + + let lum = 0.2126 * Float(components[0]) + 0.7152 * Float(components[1]) + 0.0722 * Float(components[2]) + return lum < 0.50 + } } diff --git a/apple/OmnivoreKit/Sources/Views/TextChip.swift b/apple/OmnivoreKit/Sources/Views/TextChip.swift index b25bb7c94..cb55573e8 100644 --- a/apple/OmnivoreKit/Sources/Views/TextChip.swift +++ b/apple/OmnivoreKit/Sources/Views/TextChip.swift @@ -24,13 +24,9 @@ public struct TextChip: View { .padding(.horizontal, 10) .padding(.vertical, 5) .font(.appFootnote) - .foregroundColor(color) + .foregroundColor(color.isDark ? .white : .black) .lineLimit(1) - .background(color.opacity(0.1)) + .background(color) .cornerRadius(cornerRadius) - .overlay( - RoundedRectangle(cornerRadius: cornerRadius) - .stroke(color.opacity(0.3), lineWidth: 1) - ) } }