Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -33,21 +33,51 @@ struct AddSubscriptionView: View {
colorScheme: colorScheme
)

InputFieldSection(
title: "키워드 필터",
placeholder: "장학금, 교직, 학생회",
description: "관심 키워드가 포함된 내용을 걸러낼 필요가 있으면 입력하세요.",
text: $viewModel.keywordsText,
colorScheme: colorScheme,
additionalContent: {
AnyView(
HStack(spacing: 8) {
KeywordBadge(text: "장학금", colorScheme: colorScheme)
KeywordBadge(text: "교직부공지사항", colorScheme: colorScheme)
VStack(alignment: .leading, spacing: 12) {
// 키워드 필터 섹션
VStack(alignment: .leading, spacing: 8) {
Text("키워드 필터")
.font(.system(size: 14, weight: .semibold))
.foregroundColor(Color.primaryGreen)

// 키워드 추가 버튼
Button(action: {
viewModel.showKeywordSelector = true
}) {
HStack {
Text("키워드 추가...")
.font(.system(size: 16))
.foregroundColor(Color.secondaryText(colorScheme))
Spacer()
}
)
.padding(.horizontal, 16)
.padding(.vertical, 12)
.background(
RoundedRectangle(cornerRadius: 8)
.fill(Color.secondaryBackground(colorScheme))
)
}

Text("관심 키워드가 포함된 내용을 걸러낼 필요가 있으면 입력하세요.")
.font(.system(size: 12))
.foregroundColor(Color.secondaryText(colorScheme))
.fixedSize(horizontal: false, vertical: true)
}
)

// 선택된 키워드 배지들
if !viewModel.selectedKeywords.isEmpty {
FlowLayout(spacing: 8) {
ForEach(viewModel.selectedKeywords, id: \.self) { keyword in
KeywordBadgeWithDelete(
text: keyword,
colorScheme: colorScheme
) {
viewModel.removeKeyword(keyword)
}
}
}
}
}

UrgentToggleRow(isOn: $viewModel.isUrgent, colorScheme: colorScheme)

Expand All @@ -72,6 +102,171 @@ struct AddSubscriptionView: View {
}
}
}
.sheet(isPresented: $viewModel.showKeywordSelector) {
KeywordSelectorSheet(viewModel: viewModel, colorScheme: colorScheme)
}
}
}

// 키워드 선택 시트
struct KeywordSelectorSheet: View {
@ObservedObject var viewModel: AddSubscriptionViewModel
let colorScheme: ColorScheme
@Environment(\.dismiss) var dismiss

var body: some View {
ZStack {
Color.background(colorScheme)
.ignoresSafeArea()

VStack(spacing: 0) {
// 헤더
HStack {
Spacer()
Text("구독 설정")
.font(.custom("KoddiUD OnGothic Bold", size: 24))
.foregroundColor(Color.text(colorScheme))
Spacer()
Button(action: {
dismiss()
}) {
Image(systemName: "xmark")
.font(.system(size: 20))
.foregroundColor(Color.text(colorScheme))
}
}
.padding(.horizontal, 20)
.padding(.top, 20)
.padding(.bottom, 32)

// 키워드 설정 섹션
VStack(alignment: .leading, spacing: 16) {
HStack {
Text("키워드 설정")
.font(.custom("KoddiUD OnGothic Bold", size: 20))
.foregroundColor(Color.primaryGreen)

Spacer()
}
.padding(.horizontal, 20)

// 키워드 체크박스 리스트
VStack(spacing: 0) {
ForEach(Array(viewModel.availableKeywords.enumerated()), id: \.offset) { index, keyword in
KeywordCheckboxRow(
keyword: keyword,
isSelected: viewModel.selectedKeywords.contains(keyword),
colorScheme: colorScheme
) {
viewModel.toggleKeyword(keyword)
}

if index < viewModel.availableKeywords.count - 1 {
Divider()
.background(Color.border(colorScheme))
.padding(.horizontal, 20)
}
}
}
}

Spacer()

// 저장하기 버튼
Button(action: {
dismiss()
}) {
Text("저장하기")
.font(.custom("KoddiUD OnGothic Bold", size: 18))
.foregroundColor(.white)
.frame(maxWidth: .infinity)
.frame(height: 56)
.background(Color.primaryGreen)
.cornerRadius(12)
}
.padding(.horizontal, 20)
.padding(.bottom, 34)
}
}
}
}

// 삭제 가능한 키워드 배지
struct KeywordBadgeWithDelete: View {
let text: String
let colorScheme: ColorScheme
let onDelete: () -> Void

var body: some View {
HStack(spacing: 6) {
Text(text)
.font(.system(size: 14, weight: .medium))
.foregroundColor(.white)

Button(action: onDelete) {
Image(systemName: "xmark")
.font(.system(size: 10, weight: .bold))
.foregroundColor(.white)
}
}
.padding(.horizontal, 12)
.padding(.vertical, 8)
.background(
RoundedRectangle(cornerRadius: 16)
.fill(Color.primaryGreen)
)
}
}

// FlowLayout for keywords
struct FlowLayout: Layout {
var spacing: CGFloat = 8

func sizeThatFits(proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) -> CGSize {
let result = FlowResult(
in: proposal.replacingUnspecifiedDimensions().width,
subviews: subviews,
spacing: spacing
)
return result.size
}

func placeSubviews(in bounds: CGRect, proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) {
let result = FlowResult(
in: bounds.width,
subviews: subviews,
spacing: spacing
)
for (index, subview) in subviews.enumerated() {
subview.place(at: CGPoint(x: bounds.minX + result.positions[index].x, y: bounds.minY + result.positions[index].y), proposal: .unspecified)
}
}

struct FlowResult {
var size: CGSize = .zero
var positions: [CGPoint] = []

init(in maxWidth: CGFloat, subviews: Subviews, spacing: CGFloat) {
var currentX: CGFloat = 0
var currentY: CGFloat = 0
var lineHeight: CGFloat = 0

for subview in subviews {
let size = subview.sizeThatFits(.unspecified)

if currentX + size.width > maxWidth, currentX > 0 {
currentX = 0
currentY += lineHeight + spacing
lineHeight = 0
}

positions.append(CGPoint(x: currentX, y: currentY))
lineHeight = max(lineHeight, size.height)
currentX += size.width + spacing
}

size = CGSize(width: maxWidth, height: currentY + lineHeight)
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,28 @@ import Foundation
class AddSubscriptionViewModel: ObservableObject {
@Published var urlText: String = ""
@Published var nameText: String = ""
@Published var keywordsText: String = ""
@Published var isUrgent: Bool = false
@Published var selectedKeywords: [String] = []
@Published var showKeywordSelector: Bool = false

let availableKeywords = ["장애인", "긴급속보", "장학금", "교직부공지사항", "학생회", "도서관"]

func addKeyword(_ keyword: String) {
let trimmed = keyword.trimmingCharacters(in: .whitespaces)
if !trimmed.isEmpty, !selectedKeywords.contains(trimmed) {
selectedKeywords.append(trimmed)
}
}

func removeKeyword(_ keyword: String) {
selectedKeywords.removeAll { $0 == keyword }
}

func toggleKeyword(_ keyword: String) {
if selectedKeywords.contains(keyword) {
removeKeyword(keyword)
} else {
addKeyword(keyword)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
//
// KeywordCheckboxRow.swift
// today-s-sound
//
// Created by Assistant on 12/19/24.
//

import SwiftUI

// 키워드 체크박스 Row 컴포넌트
struct KeywordCheckboxRow: View {
let keyword: String
let isSelected: Bool
let colorScheme: ColorScheme
let action: () -> Void

var body: some View {
Button(action: action) {
HStack(spacing: 16) {
// 체크박스
ZStack {
RoundedRectangle(cornerRadius: 6)
.stroke(isSelected ? Color.primaryGreen : Color.border(colorScheme), lineWidth: 2)
.frame(width: 28, height: 28)

if isSelected {
RoundedRectangle(cornerRadius: 6)
.fill(Color.primaryGreen)
.frame(width: 28, height: 28)

Image(systemName: "checkmark")
.font(.system(size: 16, weight: .bold))
.foregroundColor(.white)
}
}

// 키워드 텍스트
Text(keyword)
.font(.custom("KoddiUD OnGothic Regular", size: 18))
.foregroundColor(Color.text(colorScheme))

Spacer()
}
.padding(.vertical, 16)
.padding(.horizontal, 20)
}
.buttonStyle(PlainButtonStyle())
}
}
Loading