generated from GSM-MSG/MSG-Repository-Generator
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
💄 :: [#334] GSMAuthenticationFormBuilderView / UIModel에 따라 UI 추가
- Loading branch information
Showing
12 changed files
with
468 additions
and
16 deletions.
There are no files selected for viewing
72 changes: 58 additions & 14 deletions
72
Projects/Feature/GSMAuthenticationFormFeature/Demo/Sources/AppDelegate.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,19 +1,63 @@ | ||
import UIKit | ||
import SwiftUI | ||
@testable import GSMAuthenticationFormFeature | ||
|
||
@main | ||
final class AppDelegate: UIResponder, UIApplicationDelegate { | ||
var window: UIWindow? | ||
struct GSMAuthenticationFormDemoApp: App { | ||
var body: some Scene { | ||
WindowGroup { | ||
let uiModel = GSMAuthenticationFormUIModel( | ||
areas: [ | ||
.init( | ||
title: "Area", | ||
files: [], | ||
sections: [ | ||
.init( | ||
title: "Section1", | ||
description: "Description", | ||
currentFieldCount: 2, | ||
fields: [ | ||
.init( | ||
key: "text", | ||
type: .text(value: nil), | ||
placeholder: "text placeholder" | ||
), | ||
.init( | ||
key: "number", | ||
type: .number(value: nil), | ||
placeholder: "number placeholder" | ||
), | ||
.init( | ||
key: "file", | ||
type: .file(fileName: nil), | ||
placeholder: "file placeholder" | ||
) | ||
] | ||
), | ||
.init( | ||
title: "Section2", | ||
description: "Description", | ||
currentFieldCount: 3, | ||
fields: [ | ||
.init( | ||
key: "boolean", | ||
type: .boolean(isSelcted: false), | ||
placeholder: nil | ||
), | ||
.init( | ||
key: "select", | ||
type: .select(selectedValue: nil, values: ["a", "b", "c"]), | ||
placeholder: "select placeholder" | ||
) | ||
] | ||
) | ||
] | ||
) | ||
] | ||
) | ||
|
||
func application( | ||
_ application: UIApplication, | ||
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil | ||
) -> Bool { | ||
window = UIWindow(frame: UIScreen.main.bounds) | ||
let viewController = UIViewController() | ||
viewController.view.backgroundColor = .yellow | ||
window?.rootViewController = viewController | ||
window?.makeKeyAndVisible() | ||
|
||
return true | ||
GSMAuthenticationFormBuilderView(uiModel: uiModel) { interaction in | ||
|
||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
7 changes: 7 additions & 0 deletions
7
...cts/Feature/GSMAuthenticationFormFeature/Sources/Intent/GSMAuthenticationFormIntent.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
final class GSMAuthenticationFormIntent: GSMAuthenticationFormIntentProtocol { | ||
weak var model: (any GSMAuthenticationFormActionProtocol)? | ||
|
||
init(model: any GSMAuthenticationFormActionProtocol) { | ||
self.model = model | ||
} | ||
} |
3 changes: 3 additions & 0 deletions
3
...ure/GSMAuthenticationFormFeature/Sources/Intent/GSMAuthenticationFormIntentProtocol.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
protocol GSMAuthenticationFormIntentProtocol { | ||
|
||
} |
7 changes: 7 additions & 0 deletions
7
Projects/Feature/GSMAuthenticationFormFeature/Sources/Model/GSMAuthenticationFormModel.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
final class GSMAuthenticationFormModel: GSMAuthenticationFormStateProtocol { | ||
|
||
} | ||
|
||
extension GSMAuthenticationFormModel: GSMAuthenticationFormActionProtocol { | ||
|
||
} |
8 changes: 8 additions & 0 deletions
8
...ature/GSMAuthenticationFormFeature/Sources/Model/GSMAuthenticationFormModelProtocol.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
|
||
protocol GSMAuthenticationFormStateProtocol { | ||
|
||
} | ||
|
||
protocol GSMAuthenticationFormActionProtocol: AnyObject { | ||
|
||
} |
39 changes: 39 additions & 0 deletions
39
...cts/Feature/GSMAuthenticationFormFeature/Sources/Model/GSMAuthenticationFormUIModel.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
import Foundation | ||
|
||
// swiftlint: disable nesting | ||
struct GSMAuthenticationFormUIModel { | ||
let areas: [Area] | ||
|
||
struct Area { | ||
let title: String | ||
let files: [File] | ||
let sections: [Section] | ||
|
||
struct File { | ||
let name: String | ||
let url: String | ||
} | ||
|
||
struct Section { | ||
let title: String | ||
let description: String | ||
let currentFieldCount: Int | ||
let fields: [Field] | ||
|
||
struct Field { | ||
let key: String | ||
let type: FieldType | ||
let placeholder: String? | ||
|
||
enum FieldType { | ||
case text(value: String?) | ||
case number(value: Int?) | ||
case boolean(isSelcted: Bool?) | ||
case file(fileName: String?) | ||
case select(selectedValue: String?, values: [String]) | ||
} | ||
} | ||
} | ||
} | ||
} | ||
// swiftlint: enable nesting |
240 changes: 240 additions & 0 deletions
240
...AuthenticationFormFeature/Sources/Scene/Components/GSMAuthenticationFormBuilderView.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,240 @@ | ||
import DesignSystem | ||
import Foundation | ||
import SwiftUI | ||
|
||
enum FieldChanges { | ||
case text(String) | ||
case number(Int) | ||
case boolean(Bool) | ||
case file(URL) | ||
case select(String) | ||
} | ||
|
||
enum FieldInteraction { | ||
case fieldChanges(key: String, fieldChanges: FieldChanges) | ||
case fieldAdd(area: Int, section: Int, field: Int) | ||
} | ||
|
||
struct GSMAuthenticationFormBuilderView: View { | ||
@Environment(\.fileFieldPresenter) var fileFieldPresenter | ||
@Environment(\.optionPickerPresenter) var optionPickerPresenter | ||
private let uiModel: GSMAuthenticationFormUIModel | ||
private let onFieldInteraction: (FieldInteraction) -> Void | ||
private typealias Area = GSMAuthenticationFormUIModel.Area | ||
private typealias Section = Area.Section | ||
private typealias Field = Section.Field | ||
|
||
init( | ||
uiModel: GSMAuthenticationFormUIModel, | ||
onFieldInteraction: @escaping (FieldInteraction) -> Void | ||
) { | ||
self.uiModel = uiModel | ||
self.onFieldInteraction = onFieldInteraction | ||
} | ||
|
||
var body: some View { | ||
ScrollView { | ||
areaList(areas: uiModel.areas) | ||
} | ||
} | ||
|
||
@ViewBuilder | ||
private func areaList(areas: [Area]) -> some View { | ||
LazyVStack(spacing: 16) { | ||
ForEach(uiModel.areas, id: \.title) { area in | ||
HStack(spacing: 16) { | ||
SMSText(area.title, font: .title1) | ||
|
||
Spacer() | ||
|
||
SMSIcon(.downChevron) | ||
.rotationEffect(false ? .degrees(90) : .degrees(0)) | ||
.buttonWrapper { | ||
} | ||
|
||
SMSIcon(.xmarkOutline) | ||
.buttonWrapper { | ||
} | ||
} | ||
|
||
sectionList(sections: area.sections) | ||
} | ||
} | ||
.padding(20) | ||
} | ||
|
||
@ViewBuilder | ||
private func sectionList(sections: [Section]) -> some View { | ||
LazyVStack(spacing: 16) { | ||
ForEach(sections, id: \.title) { section in | ||
VStack { | ||
ForEach(0..<section.currentFieldCount, id: \.self) { index in | ||
fieldList(fields: section.fields) | ||
|
||
if section.currentFieldCount != 1 { | ||
HStack { | ||
SMSChip("추가") { | ||
} | ||
|
||
Spacer() | ||
|
||
SMSIcon(.trash) | ||
} | ||
} | ||
} | ||
} | ||
.titleWrapper(section.title) | ||
.frame(maxWidth: .infinity) | ||
} | ||
} | ||
} | ||
|
||
@ViewBuilder | ||
private func fieldList(fields: [Field]) -> some View { | ||
LazyVStack(spacing: 16) { | ||
ForEach(fields, id: \.key) { field in | ||
fieldView(field: field) | ||
} | ||
} | ||
} | ||
|
||
@ViewBuilder | ||
private func fieldView(field: Field) -> some View { | ||
switch field.type { | ||
case let .text(value): | ||
textTypeFieldView(key: field.key, placeholder: field.placeholder, text: value) | ||
|
||
case let .number(value): | ||
numberTypeFieldView(key: field.key, placeholder: field.placeholder, number: value) | ||
|
||
case let .boolean(isSelected): | ||
booleanTypeFieldView(key: field.key, isSelected: isSelected) | ||
|
||
case let .file(fileName): | ||
fileTypeFieldView(key: field.key, placeholder: field.placeholder, fileName: fileName) | ||
|
||
case let .select(selectedValue, values): | ||
selectTypeFieldView( | ||
key: field.key, | ||
placeholder: field.placeholder, | ||
selectedValue: selectedValue, | ||
values: values | ||
) | ||
} | ||
} | ||
|
||
@ViewBuilder | ||
private func textTypeFieldView( | ||
key: String, | ||
placeholder: String?, | ||
text: String? | ||
) -> some View { | ||
SMSTextField( | ||
placeholder ?? "", | ||
text: Binding( | ||
get: { text ?? "" }, | ||
set: { newValue in | ||
onFieldInteraction(.fieldChanges(key: key, fieldChanges: .text(newValue))) | ||
} | ||
) | ||
) | ||
} | ||
|
||
@ViewBuilder | ||
private func numberTypeFieldView( | ||
key: String, | ||
placeholder: String?, | ||
number: Int? | ||
) -> some View { | ||
SMSTextField( | ||
placeholder ?? "", | ||
text: Binding( | ||
get: { | ||
if let number { | ||
"\(number)" | ||
} else { | ||
"" | ||
} | ||
}, | ||
set: { newValue in | ||
guard let numberValue = Int(newValue) else { return } | ||
onFieldInteraction(.fieldChanges(key: key, fieldChanges: .number(numberValue))) | ||
} | ||
) | ||
) | ||
} | ||
|
||
@ViewBuilder | ||
private func booleanTypeFieldView( | ||
key: String, | ||
isSelected: Bool? | ||
) -> some View { | ||
SMSSegmentedControl( | ||
options: ["True", "False"], | ||
selectedOption: (isSelected ?? false) ? "True" : "False" | ||
) { option in | ||
switch option { | ||
case "True": | ||
onFieldInteraction(.fieldChanges(key: key, fieldChanges: .boolean(true))) | ||
|
||
case "False": | ||
onFieldInteraction(.fieldChanges(key: key, fieldChanges: .boolean(false))) | ||
|
||
default: | ||
return | ||
} | ||
} | ||
} | ||
|
||
@ViewBuilder | ||
private func fileTypeFieldView( | ||
key: String, | ||
placeholder: String?, | ||
fileName: String? | ||
) -> some View { | ||
SMSTextField( | ||
placeholder ?? "", | ||
text: Binding( | ||
get: { fileName ?? "" }, | ||
set: { _ in } | ||
) | ||
) | ||
.disabled(true) | ||
.overlay(alignment: .trailing) { | ||
SMSIcon(.downChevron) | ||
.padding(.trailing, 12) | ||
} | ||
} | ||
|
||
@ViewBuilder | ||
private func selectTypeFieldView( | ||
key: String, | ||
placeholder: String?, | ||
selectedValue: String?, | ||
values: [String] | ||
) -> some View { | ||
SMSTextField( | ||
placeholder ?? "", | ||
text: Binding( | ||
get: { selectedValue ?? "" }, | ||
set: { _ in } | ||
) | ||
) | ||
.disabled(true) | ||
.overlay(alignment: .trailing) { | ||
SMSIcon(.downChevron) | ||
.padding(.trailing, 12) | ||
} | ||
.simultaneousGesture( | ||
TapGesture() | ||
.onEnded { | ||
optionPickerPresenter.presentOptionPicker( | ||
options: values, | ||
onOptionSelect: { selectedOption in | ||
onFieldInteraction(.fieldChanges(key: key, fieldChanges: .select(selectedOption))) | ||
} | ||
) | ||
} | ||
) | ||
} | ||
} |
Oops, something went wrong.