Skip to content

Commit

Permalink
[2.0.0] 학과 선택 / 편집 UI 구현 (#58)
Browse files Browse the repository at this point in the history
  • Loading branch information
x-0o0 authored Nov 26, 2023
1 parent a7289c4 commit 05d7f66
Show file tree
Hide file tree
Showing 3 changed files with 202 additions and 64 deletions.
170 changes: 117 additions & 53 deletions KuringApp/KuringApp/Department/DepartmentEditor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,19 @@ struct DepartmentEditorFeature: Reducer {
@BindingState var searchText: String = ""
@BindingState var focus: Field? = .search

@BindingState var displayOption: Display = .myDepartment

enum Field {
case search
}

enum Display: Hashable {
/// 검색 결과 보여주기
case searchResult
/// 내 학과 보여주기
case myDepartment
}

@PresentationState var alert: AlertState<Action.Alert>?
}

Expand All @@ -35,6 +44,8 @@ struct DepartmentEditorFeature: Reducer {
case deleteMyDepartmentButtonTapped(id: NoticeProvider.ID)
/// 내 학과 전체삭제 버튼 눌렀을 때
case deleteAllMyDepartmentButtonTapped
/// 텍스트 필드의 xmark를 눌렀을 때
case clearTextFieldButtonTapped

/// 알림
enum Alert: Equatable {
Expand Down Expand Up @@ -99,6 +110,9 @@ struct DepartmentEditorFeature: Reducer {
}
}
return .none
case .clearTextFieldButtonTapped:
state.searchText.removeAll()
return .none

// MARK: Alert
case let .alert(.presented(alertAction)):
Expand Down Expand Up @@ -126,77 +140,85 @@ struct DepartmentEditor: View {

var body: some View {
WithViewStore(self.store, observe: { $0 }) { viewStore in
VStack {
List {
Text("학과를 추가하거나 삭제할 수 있어요")
VStack(alignment: .leading) {
Text("학과를 추가하거나\n삭제할 수 있어요")
.font(.system(size: 24, weight: .bold))
.foregroundColor(Color(red: 0.1, green: 0.12, blue: 0.15))
.padding(.top, 28)
.padding(.bottom, 24)

HStack(alignment: .center, spacing: 12) {
Image(systemName: "magnifyingglass")
.frame(width: 16, height: 16)
.foregroundStyle(Color(red: 0.21, green: 0.24, blue: 0.29).opacity(0.6))

/**
- `viewStore.$searchText`
- `bind(viewStore.$focus, to: $focus)`
*/
Section {
TextField("추가할 학과를 검색해 주세요", text: viewStore.$searchText)
.focused($focus, equals: .search)
.bind(viewStore.$focus, to: self.$focus)
}
TextField("추가할 학과를 검색해 주세요", text: viewStore.$searchText)
.focused($focus, equals: .search)
.bind(viewStore.$focus, to: self.$focus)

/**
- `viewStore.myDepartments`
- `.deleteMyDepartmentButtonTapped`
*/
Section {
if !viewStore.searchText.isEmpty {
Image(systemName: "xmark")
.frame(width: 16, height: 16)
.foregroundStyle(Color(red: 0.21, green: 0.24, blue: 0.29).opacity(0.6))
.onTapGesture {
viewStore.send(.clearTextFieldButtonTapped)
focus = nil
}
}
}
.padding(.horizontal, 16)
.padding(.vertical, 7)
.background(Color(red: 0.95, green: 0.95, blue: 0.96))
.cornerRadius(20)
.padding(.bottom, 16)

Text(viewStore.searchText.isEmpty ? "내 학과" : "검색 결과")
.font(.system(size: 14))
.foregroundStyle(Color(red: 0.21, green: 0.24, blue: 0.29).opacity(0.6))
.padding(.horizontal, 4)
.padding(.vertical, 10)

if viewStore.searchText.isEmpty {
// 내학과
ScrollView {
ForEach(viewStore.myDepartments) { myDepartment in
HStack {
Text(myDepartment.korName)

Spacer()

Button("삭제") {
viewStore.send(.deleteMyDepartmentButtonTapped(id: myDepartment.id))
}
DepartmentRow(
department: myDepartment,
style: .delete
) {
viewStore.send(.deleteMyDepartmentButtonTapped(id: myDepartment.id))
}
}
} header: {
Text("내 학과")
}

/**
- `viewStore.results`
- `addDepartmentButtonTapped`
- `cancelAdditionButtonTapped`
*/
Section {
} else {
// 검색결과
ScrollView {
ForEach(viewStore.results) { result in
HStack {
Text(result.korName)

Spacer()

Button {
if viewStore.myDepartments.contains(result) {
viewStore.send(.cancelAdditionButtonTapped(id: result.id))
} else {
viewStore.send(.addDepartmentButtonTapped(id: result.id))
}
} label: {
Image(
systemName: viewStore.myDepartments.contains(result)
? "checkmark.circle.fill"
: "plus.circle"
)
DepartmentRow(
department: result,
style: .radio(viewStore.myDepartments.contains(result))
) {
if viewStore.myDepartments.contains(result) {
viewStore.send(.cancelAdditionButtonTapped(id: result.id))
} else {
viewStore.send(.addDepartmentButtonTapped(id: result.id))
}
}
.padding(.horizontal, 4)
.padding(.vertical, 10)
}
} header: {
Text("검색 결과")
}
}

Spacer()
}
.padding(.horizontal, 20)
.toolbar {
ToolbarItem(placement: .topBarTrailing) {
Button("전체 삭제") {
viewStore.send(.deleteAllMyDepartmentButtonTapped)
}
.tint(.accentColor)
.disabled(viewStore.myDepartments.isEmpty)
}
}
Expand All @@ -210,6 +232,47 @@ struct DepartmentEditor: View {
}
}

struct DepartmentRow: View {
let department: NoticeProvider
let style: ButtonStyle
let action: () -> Void

enum ButtonStyle {
case delete
case radio(Bool)
}

var body: some View {
HStack(alignment: .center) {
Text(department.korName)

Spacer()

switch style {
case .delete:
Button(action: action) {
Text("삭제")
.foregroundStyle(Color(red: 0.21, green: 0.24, blue: 0.29).opacity(0.6))
}
case let .radio(isSelected):
Button(action: action) {
Image(
systemName: isSelected
? "checkmark.circle.fill"
: "plus.circle"
)
.foregroundStyle(
isSelected
? Color.accentColor
: Color.black.opacity(0.1)
)
}
}
}
.padding(.horizontal, 4)
.padding(.vertical, 10)
}
}

#Preview {
NavigationStack {
Expand All @@ -230,5 +293,6 @@ struct DepartmentEditor: View {
)
)
.navigationTitle("Department Editor")
// .toolbarTitleDisplayMode(.inline)
}
}
75 changes: 64 additions & 11 deletions KuringApp/KuringApp/Department/DepartmentSelector.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ struct DepartmentSelectorFeature: Reducer {
var addedDepartment: IdentifiedArrayOf<NoticeProvider>
}

enum Action {
enum Action: Equatable {
// TODO: String -> Department
case selectDepartment(id: NoticeProvider.ID)
case editDepartmentsButtonTapped
Expand All @@ -36,7 +36,7 @@ struct DepartmentSelectorFeature: Reducer {
}
state.currentDepartment = department
return .none

case .editDepartmentsButtonTapped:
return .send(.delegate(.editDepartment))

Expand All @@ -52,28 +52,81 @@ struct DepartmentSelector: View {

var body: some View {
WithViewStore(self.store, observe: { $0 }) { viewStore in
List {
Section {
VStack {
HStack(alignment: .center, spacing: 10) {
Text("대표 학과 선택")
.font(.system(size: 18, weight: .bold))
.foregroundStyle(Color.black)
}
.padding(.horizontal, 20)
.padding(.top, 24)
.padding(.bottom, 12)
.frame(width: 375, alignment: .leading)

ScrollView {
ForEach(viewStore.addedDepartment) { department in
Button {
viewStore.send(.selectDepartment(id: department.id))
} label: {
Label(
department.korName,
systemImage: department == viewStore.currentDepartment
HStack {
Text(department.korName)
.font(.system(size: 16, weight: .medium))
.foregroundColor(.black)

Spacer()

Image(
systemName: department == viewStore.currentDepartment
? "checkmark.circle.fill"
: "circle"
)
.foregroundStyle(
department == viewStore.currentDepartment
? Color.accentColor
: Color.black.opacity(0.1)
)
.frame(width: 20, height: 20)
}
.padding(.horizontal, 20)
.padding(.vertical, 12)
.background(.white)
.onTapGesture {
viewStore.send(.selectDepartment(id: department.id))
}
}
}

Button("내 학과 편집하기") {
Button {
viewStore.send(.editDepartmentsButtonTapped)
} label: {
topBlurButton(
"내 학과 편집하기",
fontColor: Color.accentColor,
backgroundColor: Color.accentColor.opacity(0.15)
)
}
.padding(.horizontal, 20)
}
}
}

// TODO: 디자인 시스템 분리 - 상단에 블러가 존재하는 버튼
@ViewBuilder
private func topBlurButton(_ title: String, fontColor: Color, backgroundColor: Color) -> some View {
HStack(alignment: .center, spacing: 10) {
Spacer()
Text(title)
.font(.system(size: 16, weight: .semibold))
.foregroundStyle(fontColor)
Spacer()
}
.padding(.horizontal, 50)
.padding(.vertical, 16)
.frame(height: 50, alignment: .center)
.background(backgroundColor)
.cornerRadius(100)
.background {
LinearGradient(gradient: Gradient(colors: [.white.opacity(0.1), .white]), startPoint: .top, endPoint: .bottom)
.offset(x: 0, y: -32)
}
}
}

#Preview {
Expand Down
21 changes: 21 additions & 0 deletions KuringApp/KuringApp/NoticeList/NoticeList.swift
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,27 @@ struct NoticeList: View {
EmptyView()
}
}
.sheet(
store: self.store.scope(
state: \.$changeDepartment,
action: { .changeDepartment($0) }
)
) { store in
NavigationStack {
DepartmentSelector(store: store)
}
.presentationDetents([.medium])
}
.sheet(
store: self.store.scope(
state: \.$changeSubscription,
action: { .changeSubscription($0) }
)
) { store in
NavigationStack {
SubscriptionApp(store: store)
}
}
}
}
}

0 comments on commit 05d7f66

Please sign in to comment.