Skip to content

Commit

Permalink
feat: 🎸 jira 2286 sort & filter for SwiftUI project
Browse files Browse the repository at this point in the history
A SwiftUI component for configuration criteria of performing sorting and
filter. SortFilterMenu and SortFilterFullCFG are provided.

✅ Closes: 1
  • Loading branch information
CharlesXu0488 committed Oct 19, 2023
1 parent 24ca365 commit c37aeea
Show file tree
Hide file tree
Showing 55 changed files with 4,049 additions and 28 deletions.
34 changes: 25 additions & 9 deletions Apps/Examples/Examples.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
archiveVersion = 1;
classes = {
};
objectVersion = 52;
objectVersion = 54;
objects = {

/* Begin PBXBuildFile section */
Expand Down Expand Up @@ -125,6 +125,8 @@
B8D4376F25F980340024EE7D /* ObjectCell_Spec_Jan2018.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8D4376E25F980340024EE7D /* ObjectCell_Spec_Jan2018.swift */; };
B8D4377125F983730024EE7D /* ObjectCell_Rules_Alignment.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8D4377025F983730024EE7D /* ObjectCell_Rules_Alignment.swift */; };
B8D437732609479E0024EE7D /* SingleActionFollowButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8D437722609479E0024EE7D /* SingleActionFollowButton.swift */; };
C1A0FDB32AD893FA0001738E /* View+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1A0FDB22AD893FA0001738E /* View+Extensions.swift */; };
C1C764882A818BEC00BCB0F7 /* SortFilterExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1C764872A818BEC00BCB0F7 /* SortFilterExample.swift */; };
/* End PBXBuildFile section */

/* Begin PBXContainerItemProxy section */
Expand Down Expand Up @@ -307,6 +309,8 @@
B8D4376E25F980340024EE7D /* ObjectCell_Spec_Jan2018.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ObjectCell_Spec_Jan2018.swift; sourceTree = "<group>"; };
B8D4377025F983730024EE7D /* ObjectCell_Rules_Alignment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ObjectCell_Rules_Alignment.swift; sourceTree = "<group>"; };
B8D437722609479E0024EE7D /* SingleActionFollowButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SingleActionFollowButton.swift; sourceTree = "<group>"; };
C1A0FDB22AD893FA0001738E /* View+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+Extensions.swift"; sourceTree = "<group>"; };
C1C764872A818BEC00BCB0F7 /* SortFilterExample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SortFilterExample.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
Expand Down Expand Up @@ -501,6 +505,7 @@
8A5579C824C1293C0098003A /* FioriSwiftUICore */ = {
isa = PBXGroup;
children = (
C1C764862A818BD600BCB0F7 /* SortFilter */,
B100639129C0623300AF0CA2 /* StepProgressIndicator */,
108E43D3292DAB3E006532F3 /* EmptyStateView */,
B1D41B1E291A2D2E004E64A5 /* Picker */,
Expand Down Expand Up @@ -700,6 +705,15 @@
path = ObjectItem;
sourceTree = "<group>";
};
C1C764862A818BD600BCB0F7 /* SortFilter */ = {
isa = PBXGroup;
children = (
C1C764872A818BEC00BCB0F7 /* SortFilterExample.swift */,
C1A0FDB22AD893FA0001738E /* View+Extensions.swift */,
);
path = SortFilter;
sourceTree = "<group>";
};
/* End PBXGroup section */

/* Begin PBXNativeTarget section */
Expand Down Expand Up @@ -889,6 +903,7 @@
8A557A2424C12F380098003A /* ChartDetailView.swift in Sources */,
8A5579D024C1293C0098003A /* SettingsLine.swift in Sources */,
1FC30412270540FB004BEE00 /* 72-Fonts.swift in Sources */,
C1A0FDB32AD893FA0001738E /* View+Extensions.swift in Sources */,
B84D24ED2652F343007F2373 /* HeaderChartExample.swift in Sources */,
B100639329C0624D00AF0CA2 /* StepProgressIndicatorExample.swift in Sources */,
B846F94626815CC90085044B /* ContactItemExample.swift in Sources */,
Expand Down Expand Up @@ -937,6 +952,7 @@
8A5579D524C1293C0098003A /* SettingsSeries.swift in Sources */,
8A557A2224C12C9B0098003A /* CoreContentView.swift in Sources */,
8A5579D224C1293C0098003A /* Color+Extensions.swift in Sources */,
C1C764882A818BEC00BCB0F7 /* SortFilterExample.swift in Sources */,
B84D24EF2652F343007F2373 /* ObjectHeaderTestApp.swift in Sources */,
B84D24EC2652F343007F2373 /* ObjectHeaderSpecCompact.swift in Sources */,
8A5579CD24C1293C0098003A /* SettingsLabel.swift in Sources */,
Expand Down Expand Up @@ -1116,7 +1132,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
Expand Down Expand Up @@ -1171,7 +1187,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
SDKROOT = iphoneos;
Expand All @@ -1188,10 +1204,10 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_ASSET_PATHS = "\"Examples/Preview Content\"";
DEVELOPMENT_TEAM = "";
DEVELOPMENT_TEAM = H8QB58T8ZU;
ENABLE_PREVIEWS = YES;
INFOPLIST_FILE = Examples/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
Expand All @@ -1210,10 +1226,10 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_ASSET_PATHS = "\"Examples/Preview Content\"";
DEVELOPMENT_TEAM = "";
DEVELOPMENT_TEAM = H8QB58T8ZU;
ENABLE_PREVIEWS = YES;
INFOPLIST_FILE = Examples/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
Expand All @@ -1232,7 +1248,7 @@
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
INFOPLIST_FILE = ExamplesTests/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
Expand All @@ -1253,7 +1269,7 @@
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
INFOPLIST_FILE = ExamplesTests/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
Expand Down
4 changes: 4 additions & 0 deletions Apps/Examples/Examples/FioriSwiftUICore/CoreContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,10 @@ struct CoreContentView: View {
destination: EmptyStateViewExample()) {
Text("EmptyStateViewExample")
}

NavigationLink(destination: SortFilterExample()) {
Text("SortFilterExample")
}
}
}.navigationBarTitle("FioriSwiftUICore")
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import FioriSwiftUICore
import SwiftUI

struct SortFilterExample: View {
@State private var items: [[SortFilterItem]] = [[
.picker(item: .init(value: [0], valueOptions: ["Received", "Started", "Hold", "Transfer", "Completed", "Pending Review", "Accepted", "Rejected"], name: "JIRA Status", allowsMultipleSelection: true, allowsEmptySelection: true, icon: "clock"), isShownOnMenu: true),
.picker(item: .init(value: [0], valueOptions: ["High", "Medium", "Low"], name: "Priority", allowsMultipleSelection: true, allowsEmptySelection: true, icon: "filemenu.and.cursorarrow"), isShownOnMenu: true),
.filterfeedback(item: .init(value: [0], valueOptions: ["Ascending", "Descending"], name: "Sort Order", allowsMultipleSelection: false, allowsEmptySelection: false, icon: "checkmark")),
.slider(item: .init(value: 10, minimumValue: 0, maximumValue: 100, name: "Number of User Stories", icon: "number"), isShownOnMenu: true),
.slider(item: .init(value: nil, minimumValue: 0, maximumValue: 100, name: "Number of Tasks"), isShownOnMenu: true),
.datetime(item: .init(value: Date(), name: "Start Date", icon: "calendar"), isShownOnMenu: true),
.datetime(item: .init(value: nil, name: "Completion Date"), isShownOnMenu: true),
.switch(item: .init(name: "Favorite", value: true, icon: "heart.fill"), isShownOnMenu: true),
.switch(item: .init(name: "Tagged", value: nil, icon: "tag"), isShownOnMenu: false)
]]

@State private var isShowingFullCFG: Bool = false
@State private var sortFilterList: [String] = []
@State private var sortFilterButtonLabel: String = "Sort & Filter"

var body: some View {
VStack {
SortFilterMenu(items: $items, onUpdate: performSortAndFilter)
// .trailingFullConfigurationMenuItem(icon: "command")
// .leadingFullConfigurationMenuItem(icon: "command")
// .leadingFullConfigurationMenuItem(name: "All")
// .sortFilterMenuItemStyle(font: .subheadline, foregroundColorSelected: .red, strokeColorSelected: .red, cornerRadius: 25)

List {
ForEach(sortFilterList, id: \.self) { line in
Text(line)
}
}
.listStyle(PlainListStyle())

Button("Print") {
for line in sortFilterList {
print(line)
}
}
}
.navigationTitle("Sort & Filter")
.toolbar {
Button(sortFilterButtonLabel) {
isShowingFullCFG.toggle()
}
}
.sheet(isPresented: $isShowingFullCFG) {
SortFilterFullCFG(
items: $items,
onUpdate: performSortAndFilter
)
}
.onAppear {
performSortAndFilter()
}
}

func numberOfItems() -> Int {
// randomly padding result to mimic impact of filterring
for i in 0 ... Int.random(in: 0 ... 5) {
self.sortFilterList.append("Non-SortFilterCFG: element \(i + 1)")
}
return self.sortFilterList.count
}

func performSortAndFilter() {
self.sortFilterList = self.items.joined().map { value(of: $0) }
self.sortFilterButtonLabel = "Sort & Filter (\(self.numberOfItems()))"
}
}

#if DEBUG
@available(iOS 16.0, *)
struct SortFilterExample_Previews: PreviewProvider {
static var previews: some View {
SortFilterExample()
}
}
#endif
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import FioriSwiftUICore
import SwiftUI

extension View {
func value(of item: SortFilterItem) -> String {
switch item {
case .picker(let v, _):
return self.json(item: v)
case .filterfeedback(let v):
return self.json(item: v)
case .slider(let v, _):
return self.json(item: v)
case .datetime(let v, _):
return self.json(item: v)
case .switch(let v, _):
return self.json(item: v)
}
}

func json(item: PickerItem) -> String {
"SortFilterCFG: {name: \(item.name), value: \(item.value)}"
}

func json(item: SliderItem) -> String {
"SortFilterCFG: {name: \(item.name), value: \(String(describing: item.value))}"
}

func json(item: DateTimeItem) -> String {
"SortFilterCFG: {name: \(item.name), value: \(String(describing: item.value))}"
}

func json(item: SwitchItem) -> String {
"SortFilterCFG: {name: \(item.name), value: \(String(describing: item.value))}"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"images" : [
{
"filename" : "icon.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import PackageDescription
let package = Package(
name: "FioriSwiftUI",
defaultLocalization: "en",
platforms: [.iOS(.v15), .watchOS(.v7)],
platforms: [.iOS(.v16), .watchOS(.v7)],
products: [
.library(
name: "FioriSwiftUI",
Expand Down
109 changes: 109 additions & 0 deletions Sources/FioriSwiftUICore/Components/CancellableResettableForm.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import Foundation
import SwiftUI

struct CancellableResettableDialogForm<Title: View, CancelAction: View, ResetAction: View, ApplyAction: View, Components: View>: View {
let title: Title

let components: Components

var cancelAction: CancelAction
var resetAction: ResetAction
var applyAction: ApplyAction

public init(@ViewBuilder title: () -> Title,
@ViewBuilder cancelAction: () -> CancelAction,
@ViewBuilder resetAction: () -> ResetAction,
@ViewBuilder applyAction: () -> ApplyAction,
@ViewBuilder components: () -> Components)
{
self.title = title()
self.cancelAction = cancelAction()
self.resetAction = resetAction()
self.applyAction = applyAction()
self.components = components()
}

var body: some View {
VStack(spacing: UIDevice.current.userInterfaceIdiom == .pad ? 8 : 16) {
HStack {
cancelAction
Spacer()
title
Spacer()
resetAction
}
components
applyAction
}
.frame(minWidth: 375)
.padding(UIDevice.current.userInterfaceIdiom == .pad ? 13 : 16)
}
}

// fileprivate extension View {
// func readHeight() -> some View {
// self.modifier(ReadHeightModifier())
// }
// }
//
// private struct ReadHeightModifier: ViewModifier {
// private var sizeView: some View {
// GeometryReader { geometry in
// Color.clear.preference(key: HeightPreferenceKey.self, value: geometry.size.height)
// }
// }
//
// func body(content: Content) -> some View {
// content.background(sizeView)
// }
// }
//
//

struct ApplyButtonStyle: PrimitiveButtonStyle {
func makeBody(configuration: Configuration) -> some View {
// GeometryReader { geometry in
// HStack {
// Spacer()
configuration.label
.frame(minWidth: 375, maxWidth: .infinity)
// Spacer()
// }
.onTapGesture {
configuration.trigger()
}
.padding(15)
.font(.system(size: 16, weight: .bold))
.background(RoundedRectangle(cornerRadius: 5).fill(Color.preferredColor(.tintColor)))
.foregroundColor(.white)
}

// }
}

#Preview {
VStack {
Spacer()
CancellableResettableDialogForm {
Text("Date of Completion")
} cancelAction: {
Action(actionText: "Cancel", didSelectAction: nil)
} resetAction: {
Action(actionText: "Reset", didSelectAction: nil)
} applyAction: {
Button {} label: {
Text("Apply")
.frame(width: 375)
}
// Action(actionText: "Apply", didSelectAction: nil)
// .buttonStyle(ApplyButtonStyle())
} components: {
DatePicker(
"date",
selection: Binding<Date>(get: { Date() }, set: { print($0) }),
displayedComponents: [.date]
)
.datePickerStyle(.graphical)
}
}
}
Loading

0 comments on commit c37aeea

Please sign in to comment.