diff --git a/.github/issue-branch.yml b/.github/issue-branch.yml index f56ee63..9535316 100644 --- a/.github/issue-branch.yml +++ b/.github/issue-branch.yml @@ -1,5 +1,5 @@ -branchName: short -branches: - - label: '*' - prefix: feature/ -autoCloseIssue: true +# branchName: short +# branches: +# - label: '*' +# prefix: feature/ +# autoCloseIssue: true diff --git a/.github/workflows/create_branch.yml b/.github/workflows/create_branch.yml index 9eab0fc..b388155 100644 --- a/.github/workflows/create_branch.yml +++ b/.github/workflows/create_branch.yml @@ -1,15 +1,15 @@ -name: Create Issue Branch -on: - issues: - types: [ assigned ] +# name: Create Issue Branch +# on: +# issues: +# types: [ assigned ] -jobs: - create_issue_branch_job: - runs-on: ubuntu-latest - steps: - - name: Create Branch From Issue - uses: robvanderleek/create-issue-branch@main - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - name: Echo branch name - run: echo ${{ steps.Create_Issue_Branch.outputs.branchName }} +# jobs: +# create_issue_branch_job: +# runs-on: ubuntu-latest +# steps: +# - name: Create Branch From Issue +# uses: robvanderleek/create-issue-branch@main +# env: +# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} +# - name: Echo branch name +# run: echo ${{ steps.Create_Issue_Branch.outputs.branchName }} diff --git a/Projects/App/Sources/ContentView.swift b/Projects/App/Sources/ContentView.swift index 46e5cbe..1ad63b8 100644 --- a/Projects/App/Sources/ContentView.swift +++ b/Projects/App/Sources/ContentView.swift @@ -11,9 +11,9 @@ import DesignSystem struct ContentView: View { var body: some View { - Text("Hello, World!") - .applyFont(font: .heading1) - Text("Hello, World!") + RouterView { + TabbarMainView() + } } } diff --git a/Projects/App/Sources/Extension/Common.swift b/Projects/App/Sources/Extension/Common.swift new file mode 100644 index 0000000..1b0862b --- /dev/null +++ b/Projects/App/Sources/Extension/Common.swift @@ -0,0 +1,15 @@ +// +// Common.swift +// App +// +// Created by 박서연 on 2024/06/08. +// Copyright © 2024 iOS. All rights reserved. +// + +import Foundation +import SwiftUI + +struct Size { + static let width = UIScreen.main.bounds.width + static let height = UIScreen.main.bounds.height +} diff --git a/Projects/App/Sources/Presentation/Categoery/CategoeryMain.swift b/Projects/App/Sources/Presentation/Categoery/CategoeryMain.swift new file mode 100644 index 0000000..0487ba1 --- /dev/null +++ b/Projects/App/Sources/Presentation/Categoery/CategoeryMain.swift @@ -0,0 +1,32 @@ +// +// CategoryMain.swift +// App +// +// Created by 박서연 on 2024/06/06. +// Copyright © 2024 iOS. All rights reserved. +// + +import DesignSystem +import SwiftUI + +struct CategoryMain: View { + + var body: some View { + ScrollView { + Text("카테고리") + CategoryGridView(data: ZeroDrinkSampleData.dirnkType, type: "카페 음료", + last: false, pageSpacing: 22, gridSpacing: 17) + CategoryGridView(data: ZeroDrinkSampleData.cafeType, type: "과자/아이스크림", + last: false, pageSpacing: 22, gridSpacing: 17) + CategoryGridView(data: ZeroDrinkSampleData.snackType, type: "과자/아이스크림", + last: false, pageSpacing: 22, gridSpacing: 17) + + + CategoryDetailView(data: ZeroDrinkSampleData.categoryDetail) + } + } +} + +#Preview { + CategoryMain() +} diff --git a/Projects/App/Sources/Presentation/Categoery/Component/CateogeoryComponent.swift b/Projects/App/Sources/Presentation/Categoery/Component/CateogeoryComponent.swift new file mode 100644 index 0000000..5316e8b --- /dev/null +++ b/Projects/App/Sources/Presentation/Categoery/Component/CateogeoryComponent.swift @@ -0,0 +1,71 @@ +// +// CateogeoryView.swift +// App +// +// Created by 박서연 on 2024/06/06. +// Copyright © 2024 iOS. All rights reserved. +// + +import SwiftUI + +// MARK: - 디자인시스엠으로 이동 완료 +//struct CateogeoryComponent: View { +// +// let columns: [GridItem] = Array(repeating: GridItem(.flexible()), count: 4) +// let data: [String] +// let type: String +// let last: Bool +// let pageSpacing: CGFloat +// let gridSpacing: CGFloat +// +// init(data: [String], +// type: String, +// last: Bool, +// pageSpacing: CGFloat, +// gridSpacing: CGFloat +// ) { +// self.data = data +// self.type = type +// self.last = last +// self.pageSpacing = pageSpacing +// self.gridSpacing = gridSpacing +// } +// +// var body: some View { +// VStack(alignment: .leading, spacing: 12) { +// let size = (UIScreen.main.bounds.width - (pageSpacing * 2) - (gridSpacing * 3)) / 4 +// +// Text(type) +// .applyFont(font: .heading2) +// +// LazyVGrid(columns: columns, spacing: 20) { +// ForEach(data, id: \.self) { type in +// VStack(spacing: 6) { +// Rectangle() +// .fill(Color.neutral50) +// .frame(width: size, height: size) +// .clipShape(RoundedRectangle(cornerRadius: 8)) +// Text(type) +// .applyFont(font: .body2) +// .foregroundStyle(Color.neutral900) +// } +// } +// } +// } +// .padding(.horizontal, 22) +// +// if last { +// EmptyView() +// } else { +// Rectangle() +// .frame(maxWidth: .infinity) +// .frame(height: 12) +// .foregroundStyle(Color.neutral50) +// .padding(.vertical, 30) +// } +// } +//} + +//#Preview { +// CateogeoryComponent(data: ZeroDrinkSampleData.cafeType, type: "카페", last: false, pageSpacing: 22, gridSpacing: 17) +//} diff --git a/Projects/App/Sources/Presentation/Detail/NoneZeroView.swift b/Projects/App/Sources/Presentation/Detail/NoneZeroView.swift new file mode 100644 index 0000000..1d8c401 --- /dev/null +++ b/Projects/App/Sources/Presentation/Detail/NoneZeroView.swift @@ -0,0 +1,52 @@ +// +// NoneZeroView.swift +// App +// +// Created by 박서연 on 2024/06/08. +// Copyright © 2024 iOS. All rights reserved. +// + +import SwiftUI + +struct NoneZeroView: View { + private let sample = ["영양성분1", "영양성분2", "영양성분3"] + + var body: some View { + VStack { + Text("제로가 아닌 상품을 먹었다면?") + .applyFont(font: .heading2) + .frame(maxWidth: .infinity, alignment: .leading) + Rectangle() + .fill(Color.neutral200) + .frame(width: 116, height: 116) + .padding(.vertical, 32) + .padding(.bottom, 54) + + LazyVStack(spacing: 10) { + ForEach(sample, id: \.self) { index in + HStack { + Text(index) + Spacer() + Text(index) + } + .applyFont(font: .body2) + .foregroundStyle(Color.neutral600) + .padding(.vertical, 14) + + Rectangle() + .fill(Color.neutral50) + .frame(maxWidth: .infinity) + .frame(height: 1) + .opacity(index == sample.last! ? 0 : 1) + } + .padding(.bottom, 6) + } + } + .padding(.top, 30) + .background(Color.neutral50) + } +} + +#Preview { + NoneZeroView() +} diff --git a/Projects/App/Sources/Presentation/Detail/ProductDetailView.swift b/Projects/App/Sources/Presentation/Detail/ProductDetailView.swift new file mode 100644 index 0000000..23de138 --- /dev/null +++ b/Projects/App/Sources/Presentation/Detail/ProductDetailView.swift @@ -0,0 +1,67 @@ +// +// ProductDetailView.swift +// App +// +// Created by 박서연 on 2024/06/08. +// Copyright © 2024 iOS. All rights reserved. +// + +import SwiftUI + +struct ProductDetailView: View { + private let storeSample = ["네이버 쇼핑", "쿠팡", "판매처명"] + var body: some View { + ScrollView { + Rectangle() + .fill(Color.neutral500) + .scaledToFit() + + Text("브랜드명브랜드명브랜드명") + Text("[상품명상품명상품명상품명상품명]") + + ForEach(0..<5) { index in + HStack{ + Text("영양성분명1") + Spacer() + Text("영양성분") + } + .padding(.vertical, 14) + + Rectangle() + .fill(Color.neutral100) + .frame(maxWidth: .infinity, maxHeight: 1) + .opacity(index == 4 ? 0 : 1) + } + + Text("영양 성분 모두 보기") + .padding(.init(top: 8, leading: 24, bottom: 8, trailing: 24)) + .applyFont(font: .body2) + .foregroundStyle(Color.neutral400) + .overlay { + RoundedRectangle(cornerRadius: 50) + .stroke(Color.neutral400, lineWidth: 1) + } + + Text("오프라인 판매처") + + Text("온라인 판매처") + LazyVStack(spacing: 10) { + ForEach(storeSample, id: \.self){ store in + Text(store) + .padding(.init(top: 10, leading: 16, bottom: 10, trailing: 16)) + .frame(maxWidth: .infinity, alignment: .leading) + .background(Color.neutral50) + .clipShape(RoundedRectangle(cornerRadius: 8)) + } + } + + Spacer().frame(height: 30) + + + } + } +} + +#Preview { + ProductDetailView() +} diff --git a/Projects/App/Sources/Presentation/Detail/Review/CreateReviewView.swift b/Projects/App/Sources/Presentation/Detail/Review/CreateReviewView.swift new file mode 100644 index 0000000..8ab29b3 --- /dev/null +++ b/Projects/App/Sources/Presentation/Detail/Review/CreateReviewView.swift @@ -0,0 +1,83 @@ +// +// CreateReviewView.swift +// App +// +// Created by 박서연 on 2024/06/09. +// Copyright © 2024 iOS. All rights reserved. +// + +import SwiftUI +import DesignSystem +import AutoHeightEditor + +struct SampleProduct { + let name: String + let brand: String + let content: String + + static let sampleProduct = SampleProduct(name: "파워에이드", brand: "노브랜드", content: "상품입니다상품입니다상품입니다상품입니다") +} + +struct CreateReviewView: View { + let data = SampleProduct.sampleProduct + @State var finish = false + @State var test = false + @State var text: String = "" + @State var dynamicHeight: CGFloat = 100 + + var body: some View { + ScrollView { + VStack(spacing: 30) { + Image(systemName: "heart") + .resizable() + .scaledToFit() + + VStack(spacing: 6) { + Text("[\(data.brand)]") + .applyFont(font: .body2) + .foregroundStyle(Color.neutral700) + Text(data.name) + .applyFont(font: .subtitle2) + .foregroundStyle(Color.neutral900) + .lineLimit(1) + } + .padding(.horizontal, 22) + + DivideRectangle(height: 1, color: Color.neutral100) + + VStack(spacing: 10){ + Text("상품은 어떠셨나요?") + .applyFont(font: .subtitle1) + .frame(maxWidth: .infinity, alignment: .center) + HStack(spacing: 2){ + ForEach(0..<5) { _ in + Image(systemName: "star") + .font(.system(size: 36)) + .foregroundStyle(Color.neutral200) + } + } + } + + DynamicHeightTextEditor(text: $text, dynamicHeight: $dynamicHeight, + initialHeight: 100, radius: 10, + font: .body2, backgroundColor: Color.neutral50, + fontColor: Color.neutral700, + placeholder: "리뷰를 남겨주세요", + placeholderColor: Color.neutral500) .padding(.horizontal, 22) + + + CommonButton(title: "작성 완료", font: .subtitle1) + .enable( + // TODO: - Button 조건 수정 + !text.isEmpty + ) + .padding(.horizontal, 22) + .padding(.top, -2) + } + } + } +} + +#Preview { + CreateReviewView() +} diff --git a/Projects/App/Sources/Presentation/Detail/Review/DetailReviewView.swift b/Projects/App/Sources/Presentation/Detail/Review/DetailReviewView.swift new file mode 100644 index 0000000..2e158bf --- /dev/null +++ b/Projects/App/Sources/Presentation/Detail/Review/DetailReviewView.swift @@ -0,0 +1,74 @@ +// +// DetailReviewView.swift +// App +// +// Created by 박서연 on 2024/06/08. +// Copyright © 2024 iOS. All rights reserved. +// + +import SwiftUI +import DesignSystem + +struct DetailReviewView: View { + let data = [2 : ["content1", "content2", "content3"]] + + var body: some View { + VStack { + Text("리뷰 2") + .applyFont(font: .heading2) + .frame(maxWidth: .infinity, alignment: .leading) + .padding(.bottom, 12) + + ReviewScoreComponent(background: Color.neutral50, + heightPadding: 18, radius: 0) + .padding(.bottom, 20) + + // TODO: - Carousel, component수정 + VStack(spacing: 16) { + HStack{ + Image(systemName: "star") + .padding(.trailing, 4) + Text("4.7") + .foregroundStyle(Color.neutral700) + Spacer() + Text("2024.06.05") + .foregroundStyle(Color.neutral400) + } + + Text("리뷰입니다리뷰는두줄까지노출합니다리뷰는두줄까지노출합니다리뷰는두줄까지노출합니다리뷰...") + .lineLimit(2) + .foregroundStyle(Color.neutral700) + .applyFont(font: .body2) + } + .padding(14) + .overlay { + RoundedRectangle(cornerRadius: 10) + .stroke(Color.neutral100, lineWidth: 1) + } + + CommonButton(title: "리뷰 작성", font: .subtitle1) + } + } +} + +struct ReviewScoreComponent: View { + let background: Color + let heightPadding: CGFloat + let radius: CGFloat + + var body: some View { + VStack(spacing:2) { + Text("4.3") + .applyFont(font: .heading2) + Image(systemName: "star") + } + .padding(.vertical, heightPadding) + .frame(maxWidth: .infinity) + .background(background) + .clipShape(RoundedRectangle(cornerRadius: 8)) + } +} + +#Preview { + DetailReviewView() +} diff --git a/Projects/App/Sources/Presentation/Detail/Review/ReviewListView.swift b/Projects/App/Sources/Presentation/Detail/Review/ReviewListView.swift new file mode 100644 index 0000000..b914cf1 --- /dev/null +++ b/Projects/App/Sources/Presentation/Detail/Review/ReviewListView.swift @@ -0,0 +1,100 @@ +// +// ReviewListView.swift +// App +// +// Created by 박서연 on 2024/06/08. +// Copyright © 2024 iOS. All rights reserved. +// + +import SwiftUI +import DesignSystem + +struct ReviewListView: View { + @State var plus: Bool = false + let data = ReviewSampleData.reivewSample + + var body: some View { + ScrollView { + ReviewScoreComponent(background: Color.neutral50, + heightPadding: 38, radius: 8) + .padding(.init(top: 10, leading: 0, bottom: 30, trailing: 0)) + + LazyVStack { + ForEach(data, id: \.id) { index in + VStack(alignment: .leading, spacing: 4) { + HStack { + Text(index.user) + .applyFont(font: .subtitle2) + .foregroundStyle(Color.neutral700) + Spacer() + Text(index.date) + .foregroundStyle(Color.neutral400) + .applyFont(font: .label1) + } + + HStack(spacing: 4) { + Image(systemName: index.star) + Text(index.score) + .applyFont(font: .label1) + } + + Text(index.content) + .foregroundStyle(Color.neutral700) + .applyFont(font: .body2) + .lineLimit(plus ? nil : 3) + .padding(.vertical, 12) + + Text("더보기") + .foregroundStyle(Color.neutral600) + .applyFont(font: .body3) + .onTapGesture { + print("더보기") + plus.toggle() + } + .frame(maxWidth: .infinity, alignment: .trailing) + + Text("신고") + .applyFont(font: .body3) + .foregroundStyle(Color.neutral300) + .onTapGesture { + print("더보기") + } + } + DivideRectangle(height: 1, color: Color.neutral50) + .opacity(index.id == data.last?.id ? 0 : 1) + } + } + .padding(.horizontal, 22) + } + } +} + +#Preview { + ReviewListView() +} + +struct ReviewSampleData { + let id = UUID().uuidString + let user: String + let star: String = "star.fill" + let score: String = "4.7" + let date: String = "2023.07.25" + let content: String = "리뷰입니다리뷰는세줄까지노출합니다리뷰는세줄까지노출합니다리뷰는세줄까지노출합니다리뷰는세줄까지노출합니다리뷰는세줄까지노출합니다리뷰는세줄까지노출합니다리뷰는리뷰입니다리뷰는세줄까지노출합니다리뷰는세줄까지노출합니다리뷰는세줄까지노출합니다리뷰는세줄까지노출합니다리뷰는세줄까지노출합니다리뷰는세줄까지노출합니다리뷰는" + + static let reivewSample = [ + ReviewSampleData(user: "user1"), + ReviewSampleData(user: "user2"), + ReviewSampleData(user: "user3"), + ReviewSampleData(user: "user4"), + ReviewSampleData(user: "user5"), + ReviewSampleData(user: "user6"), + ReviewSampleData(user: "user7"), + ReviewSampleData(user: "user8"), + ReviewSampleData(user: "user9"), + ReviewSampleData(user: "user10"), + ReviewSampleData(user: "user11"), + ReviewSampleData(user: "user12"), + ReviewSampleData(user: "user13"), + ReviewSampleData(user: "user14") + ] +} diff --git a/Projects/App/Sources/Presentation/Detail/SimiliarProductView.swift b/Projects/App/Sources/Presentation/Detail/SimiliarProductView.swift new file mode 100644 index 0000000..a4609e4 --- /dev/null +++ b/Projects/App/Sources/Presentation/Detail/SimiliarProductView.swift @@ -0,0 +1,27 @@ +// +// SimiliarProductView.swift +// App +// +// Created by 박서연 on 2024/06/08. +// Copyright © 2024 iOS. All rights reserved. +// + +import SwiftUI +import DesignSystem + +struct SimiliarProductView: View { + var body: some View { + VStack { + Text("이 상품과 비슷한 상품이에요") + .applyFont(font: .heading2) + .frame(maxWidth: .infinity, alignment: .leading) + + // TODO: - component 넣기 + CommonButton(title: "작성완료", font: .subtitle2) + } + } +} + +#Preview { + SimiliarProductView() +} diff --git a/Projects/App/Sources/Presentation/Home/Carousel/CustomCarouselView.swift b/Projects/App/Sources/Presentation/Home/Carousel/CustomCarouselView.swift new file mode 100644 index 0000000..d9fee97 --- /dev/null +++ b/Projects/App/Sources/Presentation/Home/Carousel/CustomCarouselView.swift @@ -0,0 +1,104 @@ +// +// CustomCarouselView.swift +// App +// +// Created by 박서연 on 2024/06/05. +// Copyright © 2024 iOS. All rights reserved. +// + +import SwiftUI + +struct CustomCarouselView: View { + + let pageCount: Int + let pageSpacing: CGFloat + let edgeSpacing: CGFloat + let cardSpacing: CGFloat + let content: (Int) -> Content + + @GestureState var offset: CGFloat = 0 + @State var currentIndex: Int = 0 + + init(pageCount: Int, + pageSpacing: CGFloat, + edgeSpacing: CGFloat, + cardSpacing: CGFloat, + @ViewBuilder content: @escaping (Int) -> Content) { + self.pageCount = pageCount + self.pageSpacing = pageSpacing + self.edgeSpacing = edgeSpacing + self.cardSpacing = cardSpacing + self.content = content + } + + var body: some View { + VStack { + GeometryReader { geometry in + let width = geometry.size.width + let contentWidth = width - (pageSpacing * 2) + + HStack(spacing: cardSpacing) { + ForEach(0..: View { + let pageCount: Int + let content: Content + + @State private var currentPage = 0 + + init(pageCount: Int, @ViewBuilder content: () -> Content) { + self.pageCount = pageCount + self.content = content() + } + + var body: some View { + VStack { + TabView(selection: $currentPage) { + content + .frame(maxWidth: .infinity, maxHeight: .infinity) + .tag(0...pageCount-1) + } + .tabViewStyle(PageTabViewStyle(indexDisplayMode: .never)) + .overlay(alignment: .bottom) { + PageControl(numberOfPages: pageCount, currentPage: $currentPage) + .padding(.bottom, 20) + } + } + } +} + +struct PageControl: View { + let numberOfPages: Int + @Binding var currentPage: Int + + var body: some View { + HStack(spacing: 6) { + ForEach(0.. Void) -> some View { + self.navigationBarBackButtonHidden() + .toolbar { + ToolbarItem(placement: .topBarLeading) { + Button(action: action, label: { + // TODO: - 백버튼 이미지 수정 + + Image("arrowBack") + .resizable() + .frame(width: 24, height: 24) + }) + } + } + } + + func navigationTitle(with text: Text) -> some View { + VStack(spacing: 31) { + HStack(spacing: 0) { + text + .applyFont(font: .heading1) + .frame(height: 47) + .frame(maxWidth: .infinity, alignment: .leading) + .padding(.init(top: 10, leading: 22, bottom: 10, trailing: 0)) + Spacer() + } + self + } + } +} diff --git a/Projects/App/Sources/Presentation/Tabbar+Navigation/Router/Router.swift b/Projects/App/Sources/Presentation/Tabbar+Navigation/Router/Router.swift new file mode 100644 index 0000000..97e6e96 --- /dev/null +++ b/Projects/App/Sources/Presentation/Tabbar+Navigation/Router/Router.swift @@ -0,0 +1,62 @@ +// +// Router.swift +// DesignSystem +// +// Created by 박서연 on 2024/06/12. +// Copyright © 2024 iOS. All rights reserved. +// + +import Foundation +import SwiftUI + +public class Router: ObservableObject { + public enum Route: Hashable, Identifiable { + public var id: Self { self } + + case tabView + case login + case home + case category + case mypage + case review + case setting + } + + @Published public var path: NavigationPath = NavigationPath() + + @ViewBuilder public func view(for route: Route) -> some View { + switch route { + case .tabView: + TabbarMainView() + case .login: + Text("login") + case .home: + HomeView() + case .category: + CategoryView() + case .mypage: + MypageView() + case .review: + AnotherView() + case .setting: + Text("setting") + } + } + + public func navigateTo(_ page: Route) { + path.append(page) + } + + public func navigateBack() { + path.removeLast() + } + + public func popToRoot() { + path.removeLast(path.count) + } + + public func replaceNavigationStack(_ page: Route) { + path.removeLast(path.count) + path.append(page) + } +} diff --git a/Projects/App/Sources/Presentation/Tabbar+Navigation/Router/RouterView.swift b/Projects/App/Sources/Presentation/Tabbar+Navigation/Router/RouterView.swift new file mode 100644 index 0000000..0ff8be9 --- /dev/null +++ b/Projects/App/Sources/Presentation/Tabbar+Navigation/Router/RouterView.swift @@ -0,0 +1,38 @@ +// +// RouterView.swift +// DesignSystem +// +// Created by 박서연 on 2024/06/12. +// Copyright © 2024 iOS. All rights reserved. +// + +import SwiftUI + +struct RouterView: View { + @StateObject var router: Router = Router() + private let content: Content + + init(@ViewBuilder content: @escaping () -> Content) { + self.content = content() + } + + var body: some View { + NavigationStack(path: $router.path) { + VStack { + content + } + .navigationDestination(for: Router.Route.self) { route in + router.view(for: route) + } + .onAppear { + router.navigateTo(.tabView) + } + .navigationBarTitle("", displayMode: .inline) + } + .environmentObject(router) + } +} + +//#Preview { +// RouterView() +//} diff --git a/Projects/App/Sources/Presentation/Tabbar+Navigation/Tabbar/Sample/AnotherView.swift b/Projects/App/Sources/Presentation/Tabbar+Navigation/Tabbar/Sample/AnotherView.swift new file mode 100644 index 0000000..4db7482 --- /dev/null +++ b/Projects/App/Sources/Presentation/Tabbar+Navigation/Tabbar/Sample/AnotherView.swift @@ -0,0 +1,19 @@ +// +// AnotherView.swift +// App +// +// Created by 박서연 on 2024/06/13. +// Copyright © 2024 iOS. All rights reserved. +// + +import SwiftUI + +struct AnotherView: View { + var body: some View { + Text("Saaaaample Viewwwwww") + } +} + +#Preview { + AnotherView() +} diff --git a/Projects/App/Sources/Presentation/Tabbar+Navigation/Tabbar/Sample/SampleView.swift b/Projects/App/Sources/Presentation/Tabbar+Navigation/Tabbar/Sample/SampleView.swift new file mode 100644 index 0000000..a4c8db8 --- /dev/null +++ b/Projects/App/Sources/Presentation/Tabbar+Navigation/Tabbar/Sample/SampleView.swift @@ -0,0 +1,62 @@ +// +// HomeView.swift +// DesignSystem +// +// Created by 박서연 on 2024/06/12. +// Copyright © 2024 iOS. All rights reserved. +// + +import SwiftUI + +struct HomeView: View { + @EnvironmentObject var router: Router + @EnvironmentObject var viewModel: TabbarViewModel + + var body: some View { + ZStack { + Color.red + .ignoresSafeArea() + Text("Go TO CATEGORY VIEW") + .onTapGesture { + viewModel.selected = .category + } + .foregroundStyle(.white) + } + } +} + +struct CategoryView: View { + @EnvironmentObject var router: Router + @EnvironmentObject var viewModel: TabbarViewModel + + var body: some View { + ZStack { + Color.yellow + .ignoresSafeArea() + Text("GO TO MYPAGEVIEW") + .onTapGesture { + viewModel.selected = .mypage + } + } + } +} + +struct MypageView: View { + @EnvironmentObject var router: Router + @EnvironmentObject var viewModel: TabbarViewModel + + var body: some View { + ZStack { + Color.green + .ignoresSafeArea() + Text("GO TO Another View") + .onTapGesture { + router.replaceNavigationStack(.review) + } + } + } +} + +#Preview { + HomeView() +} diff --git a/Projects/App/Sources/Presentation/Tabbar+Navigation/Tabbar/Tabbar.swift b/Projects/App/Sources/Presentation/Tabbar+Navigation/Tabbar/Tabbar.swift new file mode 100644 index 0000000..ded8fd4 --- /dev/null +++ b/Projects/App/Sources/Presentation/Tabbar+Navigation/Tabbar/Tabbar.swift @@ -0,0 +1,58 @@ +// +// Tabbar.swift +// DesignSystem +// +// Created by 박서연 on 2024/06/12. +// Copyright © 2024 iOS. All rights reserved. +// + +import SwiftUI + +enum Tabbar: CaseIterable { + case home, category, mypage + + @ViewBuilder + var view: some View { + switch self { + case .home: + HomeView() + case .category: + CategoryView() + case .mypage: + MypageView() + } + } + + var title: String { + switch self { + case .home: + "홈" + case .category: + "키테고리 검색" + case .mypage: + "마이페이지" + } + } + +// var image: Image { +// switch self { +// case .home: +// <#code#> +// case .category: +// <#code#> +// case .mypage: +// <#code#> +// } +// } + +// var image_fill: Image { +// switch self { +// case .home: +// <#code#> +// case .category: +// <#code#> +// case .mypage: +// <#code#> +// } +// } +} diff --git a/Projects/App/Sources/Presentation/Tabbar+Navigation/Tabbar/TabbarMainView.swift b/Projects/App/Sources/Presentation/Tabbar+Navigation/Tabbar/TabbarMainView.swift new file mode 100644 index 0000000..8a9bf04 --- /dev/null +++ b/Projects/App/Sources/Presentation/Tabbar+Navigation/Tabbar/TabbarMainView.swift @@ -0,0 +1,35 @@ +// +// TabbarMainView.swift +// DesignSystem +// +// Created by 박서연 on 2024/06/12. +// Copyright © 2024 iOS. All rights reserved. +// + +import SwiftUI + +struct TabbarMainView: View { + @StateObject public var viewModel = TabbarViewModel() + + var body: some View { + NavigationStack { + TabView(selection: $viewModel.selected) { + ForEach(Tabbar.allCases, id: \.self) { tab in + tab.view + } + .toolbarBackground(.hidden, for: .tabBar) + } + } + .overlay { + VStack { + Spacer() + TabbarView(viewModel: viewModel) + } + } + .environmentObject(viewModel) + } +} + +#Preview { + TabbarMainView() +} diff --git a/Projects/App/Sources/Presentation/Tabbar+Navigation/Tabbar/TabbarView.swift b/Projects/App/Sources/Presentation/Tabbar+Navigation/Tabbar/TabbarView.swift new file mode 100644 index 0000000..29db16c --- /dev/null +++ b/Projects/App/Sources/Presentation/Tabbar+Navigation/Tabbar/TabbarView.swift @@ -0,0 +1,40 @@ +// +// TabbarView.swift +// DesignSystem +// +// Created by 박서연 on 2024/06/12. +// Copyright © 2024 iOS. All rights reserved. +// + +import SwiftUI + +struct TabbarView: View { + @ObservedObject var viewModel: TabbarViewModel + + var body: some View { + HStack { + ForEach(Tabbar.allCases, id: \.self) { item in + Button { + viewModel.selected = item + } label: { + VStack(spacing: 0) { + Image(systemName: "house") + .frame(width: 39, height: 39) + Text(item.title) + .applyFont(font: .label1) + .foregroundStyle(Color.neutral400) + } + } + .frame(maxWidth: .infinity, alignment: .center) + .padding(.bottom, 10) + .onTapGesture { + viewModel.selected = item + } + } + } + } +} + +#Preview { + TabbarView(viewModel: TabbarViewModel()) +} diff --git a/Projects/App/Sources/Presentation/Tabbar+Navigation/Tabbar/ViewModel/TabbarViewModel.swift b/Projects/App/Sources/Presentation/Tabbar+Navigation/Tabbar/ViewModel/TabbarViewModel.swift new file mode 100644 index 0000000..d5f0869 --- /dev/null +++ b/Projects/App/Sources/Presentation/Tabbar+Navigation/Tabbar/ViewModel/TabbarViewModel.swift @@ -0,0 +1,13 @@ +// +// TabbarViewModel.swift +// App +// +// Created by 박서연 on 2024/06/13. +// Copyright © 2024 iOS. All rights reserved. +// + +import SwiftUI + +final class TabbarViewModel: ObservableObject { + @Published var selected: Tabbar = .home +} diff --git a/Projects/App/Support/Info.plist b/Projects/App/Support/Info.plist index d873087..4f3e0ca 100644 --- a/Projects/App/Support/Info.plist +++ b/Projects/App/Support/Info.plist @@ -2,6 +2,12 @@ + UIAppFonts + + Pretendard-Bold.otf + Pretendard-SemiBold.otf + Pretendard-Medium.otf + LSApplicationCategoryType CFBundleDevelopmentRegion diff --git a/Projects/DesignSystem/Sources/Component/Category/CategoryDetailView.swift b/Projects/DesignSystem/Sources/Component/Category/CategoryDetailView.swift new file mode 100644 index 0000000..a1bf787 --- /dev/null +++ b/Projects/DesignSystem/Sources/Component/Category/CategoryDetailView.swift @@ -0,0 +1,38 @@ +// +// CategoryDetailView.swift +// DesignSystem +// +// Created by 박서연 on 2024/06/07. +// Copyright © 2024 iOS. All rights reserved. +// + +import SwiftUI + +public struct CategoryDetailView: View { + public let rows = Array(repeating: GridItem(.flexible()), count: 1) + public let data: [String] + + public init(data: [String]) { + self.data = data + } + + public var body: some View { + ScrollView(.horizontal) { + LazyHGrid(rows: rows, spacing: 6) { + ForEach(data, id: \.self) { type in + HStack(spacing: 2) { + Text(type) + Image(systemName: "chevron.down") + } + .padding(.init(top: 6, leading: 12, bottom: 6, trailing: 12)) + .background(Color.neutral50) + .clipShape(RoundedRectangle(cornerRadius: 6)) + } + } + } + } +} + +#Preview { + CategoryDetailView(data: ["태그1", "태그2", "태그3"]) +} diff --git a/Projects/DesignSystem/Sources/Component/Category/CategoryGridView.swift b/Projects/DesignSystem/Sources/Component/Category/CategoryGridView.swift new file mode 100644 index 0000000..92b7b50 --- /dev/null +++ b/Projects/DesignSystem/Sources/Component/Category/CategoryGridView.swift @@ -0,0 +1,71 @@ +// +// CategoryGridView.swift +// DesignSystem +// +// Created by 박서연 on 2024/06/07. +// Copyright © 2024 iOS. All rights reserved. +// + +import SwiftUI + +public struct CategoryGridView: View { + + public let columns: [GridItem] = Array(repeating: GridItem(.flexible()), count: 4) + public let data: [String] + public let type: String + public let last: Bool + public let pageSpacing: CGFloat + public let gridSpacing: CGFloat + + public init(data: [String], + type: String, + last: Bool, + pageSpacing: CGFloat, + gridSpacing: CGFloat + ) { + self.data = data + self.type = type + self.last = last + self.pageSpacing = pageSpacing + self.gridSpacing = gridSpacing + } + + public var body: some View { + VStack(alignment: .leading, spacing: 12) { + let size = (UIScreen.main.bounds.width - (pageSpacing * 2) - (gridSpacing * 3)) / 4 + + Text(type) + .applyFont(font: .heading2) + + LazyVGrid(columns: columns, spacing: 20) { + ForEach(data, id: \.self) { type in + VStack(spacing: 6) { + Rectangle() + .fill(Color.neutral50) + .frame(width: size, height: size) + .clipShape(RoundedRectangle(cornerRadius: 8)) + Text(type) + .foregroundStyle(Color.neutral900) + .applyFont(font: .body2) + + } + } + } + } + .padding(.horizontal, 22) + + if last { + EmptyView() + } else { + Rectangle() + .foregroundStyle(Color.neutral50) + .frame(maxWidth: .infinity) + .frame(height: 12) + .padding(.vertical, 30) + } + } +} + +#Preview { + CategoryGridView(data: ["11", "22"], type: "카페", last: false, pageSpacing: 22, gridSpacing: 17) +} diff --git a/Projects/DesignSystem/Sources/Component/Common/CommonButton.swift b/Projects/DesignSystem/Sources/Component/Common/CommonButton.swift new file mode 100644 index 0000000..88fdc5b --- /dev/null +++ b/Projects/DesignSystem/Sources/Component/Common/CommonButton.swift @@ -0,0 +1,58 @@ +// +// CommonButton.swift +// DesignSystem +// +// Created by 박서연 on 2024/06/08. +// Copyright © 2024 iOS. All rights reserved. +// + +import SwiftUI + +public struct CommonButton: View { + public var title: String + public var isEnable: Bool = true + public var height: CGFloat = 52 + public var action: (() -> Void)? + public var font: ZSFont + + public init(title: String, font: ZSFont) { + self.title = title + self.font = font + } + + public var body: some View { + Text(title) + .applyFont(font: font) + .foregroundStyle(isEnable ? Color.white : Color.neutral300) + .frame(height: height) + .frame(maxWidth: .infinity) + .background( + RoundedRectangle(cornerRadius: 10) + .foregroundStyle(isEnable ? Color.primaryFF6972 : Color.neutral100) + ) + .onTapGesture { + action?() + } + } +} + +public extension CommonButton { + func enable(_ isEnable: Bool) -> Self { + var copy = self + copy.isEnable = isEnable + return copy + } + + func height(_ height: CGFloat) -> Self { + var copy = self + copy.height = height + return copy + } + + func tap(action: @escaping (() -> Void)) -> Self { + var copy = self + copy.action = action + return copy + } +} + diff --git a/Projects/DesignSystem/Sources/Component/Common/CustomTextEditor.swift b/Projects/DesignSystem/Sources/Component/Common/CustomTextEditor.swift new file mode 100644 index 0000000..0288c01 --- /dev/null +++ b/Projects/DesignSystem/Sources/Component/Common/CustomTextEditor.swift @@ -0,0 +1,157 @@ +// +// CustomTextEditor.swift +// DesignSystem +// +// Created by 박서연 on 2024/06/09. +// Copyright © 2024 iOS. All rights reserved. +// + +import SwiftUI +import UIKit + +public struct DynamicHeightTextEditor: View { + @Binding public var text: String + @Binding public var dynamicHeight: CGFloat + public let initialHeight: CGFloat + public let radius: CGFloat + public let font: ZSFont + public let backgroundColor: Color + public let fontColor: Color + public let placeholder: String + public let placeholderColor: Color + + public init(text: Binding, + dynamicHeight: Binding, + initialHeight: CGFloat, + radius: CGFloat, + font: ZSFont, + backgroundColor: Color, + fontColor: Color, + placeholder: String, + placeholderColor: Color) { + self._text = text + self._dynamicHeight = dynamicHeight + self.initialHeight = initialHeight + self.radius = radius + self.font = font + self.backgroundColor = backgroundColor + self.fontColor = fontColor + self.placeholder = placeholder + self.placeholderColor = placeholderColor + } + + public var body: some View { + VStack(spacing: 6) { + UITextViewWrapper(text: $text, + dynamicHeight: $dynamicHeight, + font: .body2, + backgroundColor: UIColor(Color.neutral50), + fontColor: UIColor(Color.neutral700), + placeholder: "제품에 대한 의견을 자유롭게 남겨주세요.", + placeholderColor: UIColor(Color.neutral500)) + .frame(minHeight: initialHeight, maxHeight: dynamicHeight) + .cornerRadius(8) + .clipShape(RoundedRectangle(cornerRadius: 8)) + + Text("\(text.count)/1000") + .frame(maxWidth: .infinity, alignment: .trailing) + .applyFont(font: .label1) + .foregroundStyle(Color.neutral400) + .onChange(of: text) { newValue in + if newValue.count > 1000 { + text = String(newValue.prefix(100)) + } + } + } + } +} + +fileprivate struct UITextViewWrapper: UIViewRepresentable { + @Binding fileprivate var text: String + @Binding fileprivate var dynamicHeight: CGFloat + fileprivate let font: ZSFont + fileprivate let backgroundColor: UIColor + fileprivate let fontColor: UIColor + fileprivate let placeholder: String + fileprivate let placeholderColor: UIColor + + fileprivate init(text: Binding, + dynamicHeight: Binding, + font: ZSFont, + backgroundColor: UIColor, + fontColor: UIColor, + placeholder: String, + placeholderColor: UIColor) { + self._text = text + self._dynamicHeight = dynamicHeight + self.font = font + self.backgroundColor = backgroundColor + self.fontColor = fontColor + self.placeholder = placeholder + self.placeholderColor = placeholderColor + } + + fileprivate func makeUIView(context: Context) -> UITextView { + let textView = UITextView() + textView.isScrollEnabled = true + textView.font = font.toUIFont + textView.delegate = context.coordinator + textView.backgroundColor = backgroundColor + textView.textColor = fontColor + textView.textContainerInset = UIEdgeInsets(top: 10, left: 12, bottom: 10, right: 12) + + let placeholderLabel = UILabel() + placeholderLabel.text = placeholder + placeholderLabel.font = font.toUIFont + placeholderLabel.textColor = placeholderColor + placeholderLabel.translatesAutoresizingMaskIntoConstraints = false + textView.addSubview(placeholderLabel) + textView.setValue(placeholderLabel, forKey: "_placeholderLabel") + + NSLayoutConstraint.activate([ + placeholderLabel.topAnchor.constraint(equalTo: textView.topAnchor, constant: 10), + placeholderLabel.leadingAnchor.constraint(equalTo: textView.leadingAnchor, constant: 12), + placeholderLabel.trailingAnchor.constraint(equalTo: textView.trailingAnchor, constant: -12) + ]) + updatePlaceholderVisibility(textView: textView) + + return textView + } + + fileprivate func updateUIView(_ uiView: UITextView, context: Context) { + uiView.text = text + recalculateHeight(view: uiView) + } + + fileprivate func recalculateHeight(view: UITextView) { + let size = view.sizeThatFits(CGSize(width: view.frame.width, height: CGFloat.greatestFiniteMagnitude)) + if dynamicHeight != size.height { + DispatchQueue.main.async { + self.dynamicHeight = size.height + } + } + } + + fileprivate func makeCoordinator() -> Coordinator { + Coordinator(self) + } + + fileprivate class Coordinator: NSObject, UITextViewDelegate { + var parent: UITextViewWrapper + + init(_ parent: UITextViewWrapper) { + self.parent = parent + } + + fileprivate func textViewDidChange(_ textView: UITextView) { + parent.text = textView.text + parent.recalculateHeight(view: textView) + } + } + + fileprivate func updatePlaceholderVisibility(textView: UITextView) { + if let placeholderLabel = textView.value(forKey: "_placeholderLabel") as? UILabel { + placeholderLabel.isHidden = !text.isEmpty + } + } +} diff --git a/Projects/DesignSystem/Sources/Component/Common/DivideRectangle.swift b/Projects/DesignSystem/Sources/Component/Common/DivideRectangle.swift new file mode 100644 index 0000000..d4bdd78 --- /dev/null +++ b/Projects/DesignSystem/Sources/Component/Common/DivideRectangle.swift @@ -0,0 +1,30 @@ +// +// DivideRectangle.swift +// DesignSystem +// +// Created by 박서연 on 2024/06/09. +// Copyright © 2024 iOS. All rights reserved. +// + +import SwiftUI + +public struct DivideRectangle: View { + public let height: CGFloat + public let color: Color + + public init(height: CGFloat, color: Color) { + self.height = height + self.color = color + } + + public var body: some View { + Rectangle() + .fill(color) + .frame(maxWidth: .infinity) + .frame(height: height) + } +} + +#Preview { + DivideRectangle(height: 1, color: Color.neutral50) +} diff --git a/Projects/DesignSystem/Sources/Component/Home/ProductInfoComponent.swift b/Projects/DesignSystem/Sources/Component/Home/ProductInfoComponent.swift new file mode 100644 index 0000000..6c7c459 --- /dev/null +++ b/Projects/DesignSystem/Sources/Component/Home/ProductInfoComponent.swift @@ -0,0 +1,55 @@ +// +// ProductInfoComponent.swift +// DesignSystem +// +// Created by 박서연 on 2024/06/07. +// Copyright © 2024 iOS. All rights reserved. +// + +import SwiftUI + +// 출시 예정 신상품 Carousel 내부뷰를 위한 컴포넌트 +public struct ProductInfoComponent: View { + public enum ScrollData { + case category + case store + } + + public let data: [String] + public let type: ScrollData + + public init(data: [String], type: ScrollData) { + self.data = data + self.type = type + } + + public var body: some View { + switch type { + case .category: + HStack(spacing: 8) { + ForEach(data, id: \.self) { index in + Text(index) + .foregroundStyle(Color.neutral500) + } + } + .frame(maxWidth: .infinity, alignment: .center) + case .store: + HStack(spacing: 6) { + ForEach(data, id: \.self) { index in + Text(index) + .padding(.init(top: 4, leading: 16, bottom: 4, trailing: 16)) + .background(Color.neutral50) + .clipShape(RoundedRectangle(cornerRadius: 4)) + .applyFont(font: .label1) + .foregroundStyle(Color.neutral700) + + } + } + } + + } +} + +#Preview { + ProductInfoComponent(data: ["11", "22"], type: .category) +} diff --git a/Projects/DesignSystem/Sources/Component/Home/ReleasedCarouselView.swift b/Projects/DesignSystem/Sources/Component/Home/ReleasedCarouselView.swift new file mode 100644 index 0000000..289a209 --- /dev/null +++ b/Projects/DesignSystem/Sources/Component/Home/ReleasedCarouselView.swift @@ -0,0 +1,44 @@ +// +// ReleasedCarouselView.swift +// DesignSystem +// +// Created by 박서연 on 2024/06/07. +// Copyright © 2024 iOS. All rights reserved. +// + +import SwiftUI + +public struct ReleasedCarouselView: View { + public let category = ["#생수/음료", "#탄산음료"] + public let store = ["쿠팡", "이마트", "판매처"] + + public init() {} + + public var body: some View { + VStack(spacing: 14) { + Rectangle() + .fill(Color.neutral200) + .frame(height: 216) + .frame(maxWidth: .infinity) + + VStack { + ProductInfoComponent(data: category, type: .category) + .padding(.bottom, 6) + + Text("상품명상품명상품명") + .applyFont(font: .subtitle1) + .lineLimit(1) + .padding(.bottom, 15) + + ProductInfoComponent(data: store, type: .store) + .padding(.bottom, 16) + } + .frame(alignment: .center) + } + .clipShape(RoundedRectangle(cornerRadius: 12)) + } +} + +#Preview { + ReleasedCarouselView() +} diff --git a/Projects/DesignSystem/Sources/Extension.swift b/Projects/DesignSystem/Sources/Extension.swift new file mode 100644 index 0000000..33078b1 --- /dev/null +++ b/Projects/DesignSystem/Sources/Extension.swift @@ -0,0 +1,9 @@ +// +// Extension.swift +// DesignSystem +// +// Created by 박서연 on 2024/06/06. +// Copyright © 2024 iOS. All rights reserved. +// + +import SwiftUI diff --git a/Projects/DesignSystem/Sources/Font/Font.swift b/Projects/DesignSystem/Sources/Font/Font.swift index f9eb902..50d7972 100644 --- a/Projects/DesignSystem/Sources/Font/Font.swift +++ b/Projects/DesignSystem/Sources/Font/Font.swift @@ -5,7 +5,6 @@ // Created by 박서연 on 2024/05/12. // Copyright © 2024 iOS. All rights reserved. // - import SwiftUI public enum ZSFont { diff --git a/Projects/SPM/Project.swift b/Projects/SPM/Project.swift index fa1f99b..17758f3 100644 --- a/Projects/SPM/Project.swift +++ b/Projects/SPM/Project.swift @@ -20,7 +20,8 @@ let spmTarget = Target.makeTarget( dependencies: [.SPM.Alamofire, .SPM.Kakao, .SPM.KingFisher, - .SPM.Lottie], + .SPM.Lottie, + .SPM.AutoHeight], infoPlistPath: "Support/Info.plist", // scripts: [.swiftLintPath], // -> lint 적용o scripts: [], // -> lint 적용x diff --git a/Tuist/Dependencies.swift b/Tuist/Dependencies.swift index 2467c47..7a98b09 100644 --- a/Tuist/Dependencies.swift +++ b/Tuist/Dependencies.swift @@ -20,7 +20,9 @@ let dependencies = Dependencies( .remote(url: "https://github.com/kakao/kakao-ios-sdk", requirement: .upToNextMajor(from: "2.0.0")), .remote(url: "https://github.com/airbnb/lottie-ios", - requirement: .upToNextMajor(from: "4.4.3")) + requirement: .upToNextMajor(from: "4.4.3")), + .remote(url: "https://github.com/wontaeyoung/AutoHeightEditor", + requirement: .upToNextMajor(from: "1.0.0")) ] ), diff --git a/Tuist/ProjectDescriptionHelpers/SPM+TargetDependency.swift b/Tuist/ProjectDescriptionHelpers/SPM+TargetDependency.swift index 3935ff3..8679e07 100644 --- a/Tuist/ProjectDescriptionHelpers/SPM+TargetDependency.swift +++ b/Tuist/ProjectDescriptionHelpers/SPM+TargetDependency.swift @@ -16,4 +16,5 @@ public extension TargetDependency.SPM { static let Alamofire = TargetDependency.external(name: "Alamofire") static let Kakao = TargetDependency.external(name: "KakaoSDK") static let Lottie = TargetDependency.external(name: "Lottie") + static let AutoHeight = TargetDependency.external(name: "AutoHeightEditor") }