Skip to content

Commit efbcb66

Browse files
committed
Create filtering for snippets
1 parent de6beea commit efbcb66

File tree

8 files changed

+212
-49
lines changed

8 files changed

+212
-49
lines changed

SnippetsLibrary.xcodeproj/project.pbxproj

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@
4444
B84BFEA626F56A6A007E5109 /* CrashlyticsService.swift in Sources */ = {isa = PBXBuildFile; fileRef = B84BFEA526F56A6A007E5109 /* CrashlyticsService.swift */; };
4545
B84BFEA926F57018007E5109 /* System.swift in Sources */ = {isa = PBXBuildFile; fileRef = B84BFEA826F57018007E5109 /* System.swift */; };
4646
B84BFEAB26F570CE007E5109 /* CrashlyticsError.swift in Sources */ = {isa = PBXBuildFile; fileRef = B84BFEAA26F570CE007E5109 /* CrashlyticsError.swift */; };
47+
B8546F38270771020043A99F /* SnippetCategory.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8546F37270771020043A99F /* SnippetCategory.swift */; };
48+
B8546F3A270773FE0043A99F /* SnippetsLibraryListFilterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8546F39270773FE0043A99F /* SnippetsLibraryListFilterView.swift */; };
49+
B8546F3C27077C310043A99F /* SnippetsLibraryListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8546F3B27077C310043A99F /* SnippetsLibraryListViewModel.swift */; };
4750
B856D65526F2A29600F60D09 /* DatabaseService.swift in Sources */ = {isa = PBXBuildFile; fileRef = B856D65426F2A29600F60D09 /* DatabaseService.swift */; };
4851
B85AC92E2702153E00A008A6 /* AppMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = B85AC92D2702153E00A008A6 /* AppMenu.swift */; };
4952
B85AC9302702158300A008A6 /* AppDelegate+NSMenuDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B85AC92F2702158300A008A6 /* AppDelegate+NSMenuDelegate.swift */; };
@@ -158,6 +161,9 @@
158161
B84BFEA526F56A6A007E5109 /* CrashlyticsService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CrashlyticsService.swift; sourceTree = "<group>"; };
159162
B84BFEA826F57018007E5109 /* System.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = System.swift; sourceTree = "<group>"; };
160163
B84BFEAA26F570CE007E5109 /* CrashlyticsError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CrashlyticsError.swift; sourceTree = "<group>"; };
164+
B8546F37270771020043A99F /* SnippetCategory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SnippetCategory.swift; sourceTree = "<group>"; };
165+
B8546F39270773FE0043A99F /* SnippetsLibraryListFilterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SnippetsLibraryListFilterView.swift; sourceTree = "<group>"; };
166+
B8546F3B27077C310043A99F /* SnippetsLibraryListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SnippetsLibraryListViewModel.swift; sourceTree = "<group>"; };
161167
B856D65426F2A29600F60D09 /* DatabaseService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DatabaseService.swift; sourceTree = "<group>"; };
162168
B85AC92D2702153E00A008A6 /* AppMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppMenu.swift; sourceTree = "<group>"; };
163169
B85AC92F2702158300A008A6 /* AppDelegate+NSMenuDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AppDelegate+NSMenuDelegate.swift"; sourceTree = "<group>"; };
@@ -583,6 +589,7 @@
583589
B8F9CA972700F7F40041CE3E /* DisabledCommandGroupButtonType.swift */,
584590
B8F9CA992700F80E0041CE3E /* UploadingStatus.swift */,
585591
B81B087A2702468C00E59F86 /* FileStatusCardType.swift */,
592+
B8546F37270771020043A99F /* SnippetCategory.swift */,
586593
);
587594
path = Enums;
588595
sourceTree = "<group>";
@@ -600,6 +607,8 @@
600607
children = (
601608
B82561EF26E8C8C20040A67E /* SnippetsLibraryListView.swift */,
602609
B85D1A9C26FA8EA50053FF3C /* SnippetsLibraryListSectionView.swift */,
610+
B8546F39270773FE0043A99F /* SnippetsLibraryListFilterView.swift */,
611+
B8546F3B27077C310043A99F /* SnippetsLibraryListViewModel.swift */,
603612
);
604613
path = SnippetLibraryList;
605614
sourceTree = "<group>";
@@ -843,9 +852,11 @@
843852
B8C549C426FFBA8600720E62 /* View+Skeletonable.swift in Sources */,
844853
B8F9CA982700F7F40041CE3E /* DisabledCommandGroupButtonType.swift in Sources */,
845854
B85AC92E2702153E00A008A6 /* AppMenu.swift in Sources */,
855+
B8546F38270771020043A99F /* SnippetCategory.swift in Sources */,
846856
B82561E926E8C7D60040A67E /* SnippetsLibraryView.swift in Sources */,
847857
B85AC934270215ED00A008A6 /* StatusViewModel.swift in Sources */,
848858
B85AC9302702158300A008A6 /* AppDelegate+NSMenuDelegate.swift in Sources */,
859+
B8546F3A270773FE0043A99F /* SnippetsLibraryListFilterView.swift in Sources */,
849860
B8EB5ADF26F00A4A00BE3EF6 /* SnippetDetailsViewType.swift in Sources */,
850861
B84BFEA926F57018007E5109 /* System.swift in Sources */,
851862
B8B6DE7B26ED9A3300E49C57 /* SnippetDetailsViewModel.swift in Sources */,
@@ -872,6 +883,7 @@
872883
B82561B426E81D570040A67E /* SnippetsLibraryApp.swift in Sources */,
873884
B88BB45626F55DDB00747631 /* LogsService.swift in Sources */,
874885
B88D7A6326F7A5C000B114F6 /* SnippetPlist+Dictonary.swift in Sources */,
886+
B8546F3C27077C310043A99F /* SnippetsLibraryListViewModel.swift in Sources */,
875887
B8CE1CAB26FD4193004AD5D5 /* DisabledCommandGroupButton.swift in Sources */,
876888
B85D1A9D26FA8EA50053FF3C /* SnippetsLibraryListSectionView.swift in Sources */,
877889
B82561E626E8C5490040A67E /* Mocks.swift in Sources */,

SnippetsLibrary.xcodeproj/xcshareddata/xcschemes/SnippetsLibrary.xcscheme

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,9 @@
5151
</Testables>
5252
</TestAction>
5353
<LaunchAction
54-
buildConfiguration = "Release"
55-
selectedDebuggerIdentifier = ""
56-
selectedLauncherIdentifier = "Xcode.IDEFoundation.Launcher.PosixSpawn"
54+
buildConfiguration = "Debug"
55+
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
56+
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
5757
launchStyle = "0"
5858
useCustomWorkingDirectory = "NO"
5959
ignoresPersistentStateOnLaunch = "NO"

SnippetsLibrary/Models/Snippet.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,18 @@ struct Snippet: Codable, Identifiable, Hashable {
2222
var platform: SnippetPlatform
2323
var availability: SnippetAvailability
2424

25+
// MARK: - Computed Properties
26+
2527
var type: SnippetType {
2628
title.contains("+") ? .extending : .snippets
2729
}
2830

31+
var category: SnippetCategory {
32+
return SnippetCategory.allCases.first(where: { title.lowercased().contains($0.rawValue) }) ?? .helper
33+
}
34+
35+
// MARK: - CodingKeys
36+
2937
enum CodingKeys: String, CodingKey {
3038
case id, title, summary, content, author, completion, platform, availability
3139
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
//
2+
// SnippetsLibraryListFilterView.swift
3+
// SnippetsLibrary
4+
//
5+
// Created by Krzysztof Łowiec on 01/10/2021.
6+
//
7+
8+
import SwiftUI
9+
10+
struct SnippetsLibraryListFilterView: View {
11+
12+
private enum Constants {
13+
static let dividerRotationDegree = 90.0
14+
static let viewHeight: CGFloat = 24.0
15+
}
16+
17+
// MARK: - Stored Properites
18+
19+
@Binding internal var selectedCategory: SnippetCategory?
20+
private let categories = SnippetCategory.allCases
21+
22+
// MARK: - Views
23+
24+
var body: some View {
25+
ScrollView(
26+
.horizontal,
27+
showsIndicators: false
28+
) {
29+
LazyHStack(spacing: .zero) {
30+
ForEach(categories, id: \.self) { category in
31+
Button {
32+
selectedCategory = category
33+
} label: {
34+
Text(category.rawValue.capitalized)
35+
.font(.system(size: 11.0))
36+
.foregroundColor(selectedCategory == category ? Color.white : Color.primary)
37+
}
38+
.buttonStyle(PlainButtonStyle())
39+
.padding(.horizontal, Layout.smallPadding)
40+
.padding(.vertical, Layout.smallPadding / 4)
41+
.background(
42+
Capsule()
43+
.foregroundColor(Color.accentColor)
44+
.makeVisible(selectedCategory == category)
45+
)
46+
.padding(
47+
.leading,
48+
category == .view ? Layout.standardPadding : .zero
49+
)
50+
51+
Divider()
52+
.rotationEffect(.degrees(Constants.dividerRotationDegree))
53+
}
54+
55+
Button {
56+
selectedCategory = nil
57+
} label: {
58+
Text("Remove all filters")
59+
.font(.system(size: 11.0))
60+
.foregroundColor(Color.red)
61+
}
62+
.buttonStyle(PlainButtonStyle())
63+
.padding(.leading, Layout.smallPadding)
64+
}
65+
}
66+
.frame(height: Constants.viewHeight)
67+
}
68+
69+
}
70+
71+
struct SnippetsLibraryListFilterView_Previews: PreviewProvider {
72+
static var previews: some View {
73+
SnippetsLibraryListFilterView(selectedCategory: .constant(nil))
74+
}
75+
}

SnippetsLibrary/Modules/SnippetsLibrary/SnippetLibraryList/SnippetsLibraryListView.swift

Lines changed: 31 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -15,58 +15,58 @@ struct SnippetsLibraryListView: View {
1515

1616
// MARK: - Stored Properties
1717

18-
@Binding internal var snippets: [Snippet]
19-
@Binding internal var selectedSnippetId: SnippetId?
20-
21-
@State private var searchedText = ""
22-
@State var shouldShowRemoveAlert = false
23-
24-
private(set) var onReload: () -> Void
25-
private(set) var onRemove: (_ snippetId: SnippetId?) -> Void
26-
27-
// MARK: - Computed Properties
28-
29-
private var snippetGroups: [[Snippet]] {
30-
return [
31-
snippets.filter({ $0.type == .snippets }).filter { matchingSnippet($0) },
32-
snippets.filter({ $0.type == .extending }).filter { matchingSnippet($0) }
33-
]
34-
}
35-
36-
private var hasAnyResults: Bool {
37-
(snippetGroups.first?.isEmpty == true) && (snippetGroups.last?.isEmpty == true)
38-
}
18+
@ObservedObject private(set) var viewModel: SnippetsLibraryListViewModel
3919

4020
// MARK: - Views
4121

4222
var body: some View {
4323
VStack {
4424
HStack {
4525
SearchBar {
46-
searchedText = $0
26+
viewModel.searchedText = $0
27+
}
28+
.padding(.leading)
29+
30+
Button {
31+
withAnimation {
32+
viewModel.shouldShowFilterView.toggle()
33+
}
34+
} label: {
35+
Image(systemName: "line.horizontal.3.decrease.circle")
36+
.font(.system(size: 15, weight: .light))
37+
.foregroundColor(Color.primary.opacity(Layout.mediumOpacity))
4738
}
39+
.buttonStyle(PlainButtonStyle())
40+
.help("Choose the filters.")
4841

4942
Button {
50-
onReload()
43+
viewModel.onReload()
5144
} label: {
5245
Image(systemName: "arrow.down.circle")
5346
.font(.system(size: 15, weight: .light))
5447
.foregroundColor(Color.primary.opacity(Layout.mediumOpacity))
5548
}
5649
.buttonStyle(PlainButtonStyle())
5750
.help("Pull all changes from remote.")
51+
.padding(.trailing, Layout.smallPadding)
5852
}
5953
.padding(.top, Layout.largePadding)
60-
.padding(.horizontal)
6154

62-
List(selection: $selectedSnippetId) {
55+
SnippetsLibraryListFilterView(selectedCategory: $viewModel.selectedCategory)
56+
.makeVisible(
57+
viewModel.shouldShowFilterView,
58+
removed: true
59+
)
60+
.animation(.default)
61+
62+
List(selection: $viewModel.selectedSnippetId) {
6363
ForEach(
64-
snippetGroups,
64+
viewModel.snippetGroups,
6565
id: \.self
6666
) {
6767
SnippetsLibraryListSectionView(
6868
snippets: $0,
69-
shouldShowRemoveAlert: $shouldShowRemoveAlert
69+
shouldShowRemoveAlert: $viewModel.shouldShowRemoveAlert
7070
)
7171
.makeVisible(
7272
!$0.isEmpty,
@@ -75,7 +75,7 @@ struct SnippetsLibraryListView: View {
7575
}
7676
}
7777
.overlay(
78-
EmptySnippetsListView(isListEmpty: .constant(snippets.isEmpty || hasAnyResults ))
78+
EmptySnippetsListView(isListEmpty: .constant(viewModel.snippets.isEmpty || viewModel.hasAnyResults ))
7979
)
8080
}
8181
.frame(minWidth: Constants.minWidth)
@@ -86,32 +86,24 @@ struct SnippetsLibraryListView: View {
8686
)
8787
)
8888
.edgesIgnoringSafeArea(.top)
89-
.alert(isPresented: $shouldShowRemoveAlert) {
89+
.alert(isPresented: $viewModel.shouldShowRemoveAlert) {
9090
Alert(
9191
title: Text("Confirm removing"),
9292
message: Text("You sure want to remove this snippet?"),
9393
primaryButton:
9494
.destructive(
9595
Text("Yes, remove"),
96-
action: { onRemove(selectedSnippetId) }
96+
action: { viewModel.onRemove(viewModel.selectedSnippetId) }
9797
),
9898
secondaryButton: .cancel()
9999
)
100100
}
101101
}
102102

103-
// MARK: - Methods
104-
105-
private func matchingSnippet(_ snippet: Snippet) -> Bool {
106-
snippet.title.lowercased().contains(searchedText.lowercased()) ||
107-
snippet.summary.lowercased().contains(searchedText.lowercased()) ||
108-
searchedText.isEmpty
109-
}
110-
111103
}
112104

113105
struct SnippetsLibraryListView_Previews: PreviewProvider {
114106
static var previews: some View {
115-
SnippetsLibraryListView(snippets: .constant([]), selectedSnippetId: .constant(nil), onReload: {}, onRemove: { _ in })
107+
SnippetsLibraryListView(viewModel: SnippetsLibraryListViewModel(snippets: .constant([]), selectedSnippetId: .constant(nil), onReload: {}, onRemove: { _ in }))
116108
}
117109
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
//
2+
// SnippetsLibraryListViewModel.swift
3+
// SnippetsLibrary
4+
//
5+
// Created by Krzysztof Łowiec on 01/10/2021.
6+
//
7+
8+
import SwiftUI
9+
10+
final class SnippetsLibraryListViewModel: ObservableObject {
11+
12+
// MARK: - Stored Properties
13+
14+
@Binding internal var snippets: [Snippet]
15+
@Binding internal var selectedSnippetId: SnippetId?
16+
17+
@Published internal var searchedText = ""
18+
@Published internal var shouldShowRemoveAlert = false
19+
@Published internal var shouldShowFilterView = false
20+
@Published internal var selectedCategory: SnippetCategory? = nil
21+
22+
private(set) var onReload: () -> Void
23+
private(set) var onRemove: (_ snippetId: SnippetId?) -> Void
24+
25+
// MARK: - Computed Properties
26+
27+
internal var snippetGroups: [[Snippet]] {
28+
return [
29+
snippets.filter({ $0.type == .snippets }).filter({ matchingSnippet($0) }).filter({ $0.category == selectedCategory || selectedCategory == nil }),
30+
snippets.filter({ $0.type == .extending }).filter { matchingSnippet($0) }
31+
]
32+
}
33+
34+
internal var hasAnyResults: Bool {
35+
(snippetGroups.first?.isEmpty == true) && (snippetGroups.last?.isEmpty == true)
36+
}
37+
38+
// MARK: - Initialization
39+
40+
init(
41+
snippets: Binding<[Snippet]>,
42+
selectedSnippetId: Binding<SnippetId?>,
43+
onReload: @escaping () -> Void,
44+
onRemove: @escaping (_ snippetId: SnippetId?) -> Void
45+
) {
46+
self._snippets = snippets
47+
self._selectedSnippetId = selectedSnippetId
48+
self.onReload = onReload
49+
self.onRemove = onRemove
50+
}
51+
52+
// MARK: - Methods
53+
54+
private func matchingSnippet(_ snippet: Snippet) -> Bool {
55+
snippet.title.lowercased().contains(searchedText.lowercased()) ||
56+
snippet.summary.lowercased().contains(searchedText.lowercased()) ||
57+
searchedText.isEmpty
58+
}
59+
60+
}

SnippetsLibrary/Modules/SnippetsLibrary/SnippetsLibraryView.swift

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,15 @@ struct SnippetsLibraryView: View {
2121
var body: some View {
2222
HSplitView {
2323
SnippetsLibraryListView(
24-
snippets: $viewModel.snippets,
25-
selectedSnippetId: $viewModel.selectedSnippetId
26-
) {
27-
viewModel.fetchSnippets()
28-
} onRemove: {
29-
viewModel.onRemove($0)
30-
}
24+
viewModel: SnippetsLibraryListViewModel(
25+
snippets: $viewModel.snippets,
26+
selectedSnippetId: $viewModel.selectedSnippetId
27+
) {
28+
viewModel.fetchSnippets()
29+
} onRemove: {
30+
viewModel.onRemove($0)
31+
}
32+
)
3133

3234
SnippetsLibraryPreviewView(
3335
snippets: $viewModel.snippets,
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
//
2+
// SnippetCategory.swift
3+
// SnippetsLibrary
4+
//
5+
// Created by Krzysztof Łowiec on 01/10/2021.
6+
//
7+
8+
import Foundation
9+
10+
enum SnippetCategory: String, CaseIterable {
11+
case view
12+
case helper
13+
case service
14+
}

0 commit comments

Comments
 (0)