diff --git a/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/ArrowRight.imageset/Contents.json b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/ArrowRight.imageset/Contents.json new file mode 100644 index 0000000..2859e21 --- /dev/null +++ b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/ArrowRight.imageset/Contents.json @@ -0,0 +1,16 @@ +{ + "images" : [ + { + "filename" : "arrow_right.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true, + "template-rendering-intent" : "template" + } +} diff --git a/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/ArrowRight.imageset/arrow_right.svg b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/ArrowRight.imageset/arrow_right.svg new file mode 100644 index 0000000..b9d4778 --- /dev/null +++ b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/ArrowRight.imageset/arrow_right.svg @@ -0,0 +1,3 @@ + + + diff --git a/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/Contents.json b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/MissionLogo.imageset/Background.svg b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/MissionLogo.imageset/Background.svg new file mode 100644 index 0000000..3dd1231 --- /dev/null +++ b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/MissionLogo.imageset/Background.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/MissionLogo.imageset/Contents.json b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/MissionLogo.imageset/Contents.json new file mode 100644 index 0000000..6182ee5 --- /dev/null +++ b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/MissionLogo.imageset/Contents.json @@ -0,0 +1,16 @@ +{ + "images" : [ + { + "filename" : "Background.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true, + "template-rendering-intent" : "template" + } +} diff --git a/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/TeamAgreementLogo.imageset/Contents.json b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/TeamAgreementLogo.imageset/Contents.json new file mode 100644 index 0000000..5e1282e --- /dev/null +++ b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/TeamAgreementLogo.imageset/Contents.json @@ -0,0 +1,16 @@ +{ + "images" : [ + { + "filename" : "team_agreement_logo.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true, + "template-rendering-intent" : "original" + } +} diff --git a/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/TeamAgreementLogo.imageset/team_agreement_logo.svg b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/TeamAgreementLogo.imageset/team_agreement_logo.svg new file mode 100644 index 0000000..5641c87 --- /dev/null +++ b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/TeamAgreementLogo.imageset/team_agreement_logo.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/TeamBlogLogo.imageset/Contents.json b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/TeamBlogLogo.imageset/Contents.json new file mode 100644 index 0000000..0aab595 --- /dev/null +++ b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/TeamBlogLogo.imageset/Contents.json @@ -0,0 +1,16 @@ +{ + "images" : [ + { + "filename" : "team_blog_logo.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true, + "template-rendering-intent" : "original" + } +} diff --git a/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/TeamBlogLogo.imageset/team_blog_logo.svg b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/TeamBlogLogo.imageset/team_blog_logo.svg new file mode 100644 index 0000000..ce017f3 --- /dev/null +++ b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/TeamBlogLogo.imageset/team_blog_logo.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/TeamIntroductionLogo.imageset/Contents.json b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/TeamIntroductionLogo.imageset/Contents.json new file mode 100644 index 0000000..d73e4e2 --- /dev/null +++ b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/TeamIntroductionLogo.imageset/Contents.json @@ -0,0 +1,16 @@ +{ + "images" : [ + { + "filename" : "team_introduction_logo.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true, + "template-rendering-intent" : "original" + } +} diff --git a/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/TeamIntroductionLogo.imageset/team_introduction_logo.svg b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/TeamIntroductionLogo.imageset/team_introduction_logo.svg new file mode 100644 index 0000000..3ebaabf --- /dev/null +++ b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/TeamIntroductionLogo.imageset/team_introduction_logo.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/TeamIntroduce/TeamIntroduce/Sources/DesignSytstem/Color/Extension+ShapeStyle.swift b/TeamIntroduce/TeamIntroduce/Sources/DesignSytstem/Color/Extension+ShapeStyle.swift index 5841e07..15cfe68 100644 --- a/TeamIntroduce/TeamIntroduce/Sources/DesignSytstem/Color/Extension+ShapeStyle.swift +++ b/TeamIntroduce/TeamIntroduce/Sources/DesignSytstem/Color/Extension+ShapeStyle.swift @@ -16,8 +16,8 @@ extension ShapeStyle where Self == Color { // MARK: - Static Text - static var textPrimary: Color { .init(hex: "FFFFFF") } - static var textSecondary: Color { .init(hex: "EAEAEA") } + static var textPrimary: Color { .init(hex: "0A0A0A") } + static var textSecondary: Color { .init(hex: "717182") } static var textSecondary100: Color { .init(hex: "525252") } static var textInactive: Color { .init(hex: "70737C47").opacity(0.28) } diff --git a/TeamIntroduce/TeamIntroduce/Sources/DesignSytstem/Font/PretendardFontFamily.swift b/TeamIntroduce/TeamIntroduce/Sources/DesignSytstem/Font/PretendardFontFamily.swift index 5471098..215384a 100644 --- a/TeamIntroduce/TeamIntroduce/Sources/DesignSytstem/Font/PretendardFontFamily.swift +++ b/TeamIntroduce/TeamIntroduce/Sources/DesignSytstem/Font/PretendardFontFamily.swift @@ -8,35 +8,35 @@ import Foundation enum PretendardFontFamily: CustomStringConvertible { - case Black - case Bold - case ExtraBold - case ExtraLight - case Light - case Medium - case Regular - case SemiBold - case Thin + case black + case bold + case extraBold + case extraLight + case light + case medium + case regular + case semiBold + case thin public var description: String { switch self { - case .Black: + case .black: return "Black" - case .Bold: + case .bold: return "Bold" - case .ExtraBold: + case .extraBold: return "ExtraBold" - case .ExtraLight: + case .extraLight: return "ExtraLight" - case .Light: + case .light: return "Light" - case .Medium: + case .medium: return "Medium" - case .Regular: + case .regular: return "Regular" - case .SemiBold: + case .semiBold: return "SemiBold" - case .Thin: + case .thin: return "Thin" } } diff --git a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/View/Components/IntroductionRow/IntroductionRowModel.swift b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/View/Components/IntroductionRow/IntroductionRowModel.swift new file mode 100644 index 0000000..4a6a9b5 --- /dev/null +++ b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/View/Components/IntroductionRow/IntroductionRowModel.swift @@ -0,0 +1,62 @@ +// +// IntroductionRowModel.swift +// TeamIntroduce +// +// Created by 홍석현 on 8/11/25. +// + +import Foundation + +struct IntroductionRowModel: Identifiable, Hashable { + let id = UUID() + let name: String + let role: String // subTitle 대신 role로 더 명확하게 + let imageName: String + let mbti: MBTI + let introduction: String? // 간단한 소개글 추가 + let isLeader: Bool + + init( + name: String, + role: String, + imageName: String, + mbti: MBTI, + introduction: String? = nil, + isLeader: Bool = false + ) { + self.name = name + self.role = role + self.imageName = imageName + self.mbti = mbti + self.introduction = introduction + self.isLeader = isLeader + } +} + +// MARK: - Mock Data +extension IntroductionRowModel { + static let mockData: [IntroductionRowModel] = [ + IntroductionRowModel( + name: "김민희", + role: "iOS Developer", + imageName: "profile_kim", + mbti: .estp, + introduction: "서버 아키텍처에 관심이 많습니다", + isLeader: true + ), + IntroductionRowModel( + name: "서원지", + role: "iOS Developer", + imageName: "profile_lee", + mbti: .intp, + introduction: "사용자 경험을 최우선으로 생각합니다" + ), + IntroductionRowModel( + name: "홍석현", + role: "iOS Developer", + imageName: "profile_hong", + mbti: .enfj, + introduction: "SwiftUI와 iOS 개발을 좋아합니다" + ) + ] +} diff --git a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/View/Components/IntroductionRow/IntroductionRowView.swift b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/View/Components/IntroductionRow/IntroductionRowView.swift new file mode 100644 index 0000000..80652e8 --- /dev/null +++ b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/View/Components/IntroductionRow/IntroductionRowView.swift @@ -0,0 +1,55 @@ +// +// IntroductionRowView.swift +// TeamIntroduce +// +// Created by 홍석현 on 8/11/25. +// + +import SwiftUI + +struct IntroductionRowView: View { + private let model: IntroductionRowModel + + init(model: IntroductionRowModel) { + self.model = model + } + + var body: some View { + HStack { + Circle() + .frame(width: 42, height: 42) + VStack(alignment: .leading, spacing: 4) { + HStack(spacing: 4) { + Text(model.name) + .pretendardFont(family: .semiBold, size: 14) + .foregroundStyle(.textPrimary) + + if model.isLeader { + Image(systemName: "crown.fill") + .foregroundStyle(Color.yellow) + .font(.system(size: 12)) + } + } + + Text(model.role) + .pretendardFont(family: .regular, size: 12) + .foregroundStyle(.textSecondary) + + MBTILabel(mbti: model.mbti) + } + + Spacer() + + Image("ArrowRight") + .foregroundStyle(Color(hex: "717182")) + } + .padding(16) + .background(Color.staticWhite) + .clipShape(RoundedRectangle(cornerRadius: 12)) + .shadow(radius: 1) + } +} + +#Preview { + IntroductionRowView(model: IntroductionRowModel.mockData[0]) +} diff --git a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/View/Components/MBTILabel.swift b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/View/Components/MBTILabel.swift new file mode 100644 index 0000000..65c1055 --- /dev/null +++ b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/View/Components/MBTILabel.swift @@ -0,0 +1,70 @@ +// +// MBTILabel.swift +// TeamIntroduce +// +// Created by 홍석현 on 8/11/25. +// + +import SwiftUI + +public enum MBTI: String, CaseIterable { + case enfp = "ENFP" + case estp = "ESTP" + case infj = "INFJ" + case intj = "INTJ" + case isfj = "ISFJ" + case enfj = "ENFJ" + case infp = "INFP" + case intp = "INTP" + case isfp = "ISFP" +} + +struct MBTILabel: View { + + private let mbti: MBTI + + private var backgroundColor: Color { + switch mbti { + case .enfp: return Color(hex: "FF6B6B") + case .estp: return Color(hex: "E17055") + case .infj: return Color(hex: "4ECDC4") + case .intj: return Color(hex: "6C5CE7") + case .isfj: return Color(hex: "A8E6CF") + case .enfj: return Color(hex: "FFD93D") + case .infp: return Color(hex: "DDA0DD") + case .intp: return Color(hex: "74B9FF") + case .isfp: return Color(hex: "FDCB6E") + } + } + + private var textColor: Color { + switch mbti { + case .enfp, .estp, .infj, .intj, .intp: + return .white + case .isfj, .enfj, .infp, .isfp: + return Color(hex: "2D3436") + } + } + + init(mbti: MBTI) { + self.mbti = mbti + } + + var body: some View { + Text(mbti.rawValue) + .pretendardFont(family: .regular, size: 10) + .foregroundStyle(textColor) + .padding(.horizontal, 7) + .padding(.vertical, 5) + .background(backgroundColor) + .clipShape(RoundedRectangle(cornerRadius: 8)) + } +} + +#Preview { + VStack { + MBTILabel(mbti: .enfj) + MBTILabel(mbti: .estp) + MBTILabel(mbti: .intp) + } +} diff --git a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/View/Components/MissionView.swift b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/View/Components/MissionView.swift new file mode 100644 index 0000000..81bbc47 --- /dev/null +++ b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/View/Components/MissionView.swift @@ -0,0 +1,34 @@ +// +// MissionView.swift +// TeamIntroduce +// +// Created by 홍석현 on 8/11/25. +// + +import SwiftUI + +struct MissionView: View { + var body: some View { + VStack(alignment: .center, spacing: 12) { + Image("MissionLogo") + .renderingMode(.original) + + Text("우리의 미션") + .pretendardFont(family: .bold, size: 14) + .foregroundStyle(.textPrimary) + + Text("혁신적인 기술로 사용자의 삶을 더 편리하고 풍요롭게 만드는 것") + .pretendardFont(family: .regular, size: 12) + .foregroundStyle(.textSecondary) + } + .frame(maxWidth: .infinity) + .padding(16) + .background(Color(hex: "E9EBEF")) + .clipShape(RoundedRectangle(cornerRadius: 12)) + .shadow(radius: 1, x: 2, y: 2) + } +} + +#Preview { + MissionView() +} diff --git a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/View/Components/SkeletonRowView.swift b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/View/Components/SkeletonRowView.swift new file mode 100644 index 0000000..efa7ce5 --- /dev/null +++ b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/View/Components/SkeletonRowView.swift @@ -0,0 +1,58 @@ +// +// SkeletonRowView.swift +// TeamIntroduce +// +// Created by 홍석현 on 8/11/25. +// + +import SwiftUI + +struct SkeletonRowView: View { + @State private var isAnimating = false + + var body: some View { + HStack { + Circle() + .frame(width: 42, height: 42) + .foregroundColor(.gray.opacity(0.3)) + + VStack(alignment: .leading, spacing: 4) { + RoundedRectangle(cornerRadius: 4) + .frame(width: 80, height: 16) + .foregroundColor(.gray.opacity(0.3)) + + RoundedRectangle(cornerRadius: 4) + .frame(width: 120, height: 12) + .foregroundColor(.gray.opacity(0.2)) + + RoundedRectangle(cornerRadius: 8) + .frame(width: 50, height: 20) + .foregroundColor(.gray.opacity(0.2)) + } + + Spacer() + + RoundedRectangle(cornerRadius: 4) + .frame(width: 16, height: 16) + .foregroundColor(.gray.opacity(0.2)) + } + .padding(16) + .background(Color.staticWhite) + .clipShape(RoundedRectangle(cornerRadius: 12)) + .shadow(radius: 1) + .opacity(isAnimating ? 0.5 : 1.0) + .animation(.easeInOut(duration: 1.0).repeatForever(autoreverses: true), value: isAnimating) + .onAppear { + isAnimating = true + } + } +} + +#Preview { + VStack { + SkeletonRowView() + SkeletonRowView() + SkeletonRowView() + } + .padding() +} diff --git a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/View/Components/TeamExploreRowView.swift b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/View/Components/TeamExploreRowView.swift new file mode 100644 index 0000000..fa7649c --- /dev/null +++ b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/View/Components/TeamExploreRowView.swift @@ -0,0 +1,92 @@ +// +// TeamExploreRowView.swift +// TeamIntroduce +// +// Created by 홍석현 on 8/11/25. +// + +import SwiftUI + +enum TeamExploreItem: CaseIterable, Identifiable { + case introduction + case agreement + case blog + + var id: Self { self } + + + var title: String { + switch self { + case .introduction: + return "팀 소개" + case .agreement: + return "팀 약속" + case .blog: + return "팀 블로그" + } + } + + var subtitle: String { + switch self { + case .introduction: + return "우리 팀의 특징과 목표" + case .agreement: + return "함께 지켜나갈 소중한 약속들" + case .blog: + return "팀원들의 블로그 모음" + } + } + + var imageName: String { + switch self { + case .introduction: + return "TeamIntroductionLogo" + case .agreement: + return "TeamAgreementLogo" + case .blog: + return "TeamBlogLogo" + } + } +} + +struct TeamExploreRowView: View { + + private let item: TeamExploreItem + + init(item: TeamExploreItem) { + self.item = item + } + + var body: some View { + HStack { + Image(item.imageName) + .frame(width: 42, height: 42) + VStack(alignment: .leading, spacing: 4) { + Text(item.title) + .pretendardFont(family: .semiBold, size: 14) + .foregroundStyle(.textPrimary) + + Text(item.subtitle) + .pretendardFont(family: .regular, size: 12) + .foregroundStyle(.textSecondary) + } + + Spacer() + + Image("ArrowRight") + .foregroundStyle(Color(hex: "717182")) + } + .padding(16) + .background(Color.staticWhite) + .clipShape(RoundedRectangle(cornerRadius: 12)) + .shadow(radius: 1) + } +} + +#Preview { + VStack { + TeamExploreRowView(item: .introduction) + TeamExploreRowView(item: .agreement) + TeamExploreRowView(item: .blog) + } +} diff --git a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/View/IntroductionMainView.swift b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/View/IntroductionMainView.swift new file mode 100644 index 0000000..af42da3 --- /dev/null +++ b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/View/IntroductionMainView.swift @@ -0,0 +1,61 @@ +// +// IntroductionMainView.swift +// TeamIntroduce +// +// Created by 홍석현 on 8/11/25. +// + +import SwiftUI + +struct IntroductionMainView: View { + @ObservedObject var viewModel: IntroductionViewModel + + init(viewModel: IntroductionViewModel) { + self.viewModel = viewModel + } + + var body: some View { + ScrollView { + VStack(spacing: 16) { + MissionView() + + VStack { + Text("팀원 소개") + .pretendardFont(family: .bold, size: 14) + .foregroundStyle(.textPrimary) + .frame(maxWidth: .infinity, alignment: .leading) + .padding(.horizontal, 8) + if viewModel.isLoading { + ForEach(0..<3, id: \.self) { _ in + SkeletonRowView() + } + } else { + ForEach(viewModel.introductions) { model in + IntroductionRowView(model: model) + } + } + } + + VStack { + Text("더 알아보기") + .pretendardFont(family: .bold, size: 14) + .foregroundStyle(.textPrimary) + .frame(maxWidth: .infinity, alignment: .leading) + .padding(.horizontal, 8) + + ForEach(TeamExploreItem.allCases) { item in + TeamExploreRowView(item: item) + } + } + } + .padding(.horizontal, 16) + .onAppear { + viewModel.onAppear() + } + } + } +} + +#Preview { + IntroductionMainView(viewModel: IntroductionViewModel()) +} diff --git a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/View/IntroductionViewModel.swift b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/View/IntroductionViewModel.swift new file mode 100644 index 0000000..4fbf61a --- /dev/null +++ b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/View/IntroductionViewModel.swift @@ -0,0 +1,53 @@ +// +// IntroductionViewModel.swift +// TeamIntroduce +// +// Created by 홍석현 on 8/11/25. +// + +import Foundation +import SwiftUI + +@MainActor +final class IntroductionViewModel: ObservableObject { + + // MARK: - Published Properties + @Published private(set) var introductions: [IntroductionRowModel] = [] + @Published private(set) var isLoading = false + + // MARK: - Public Methods + + /// onAppear 시 호출되는 데이터 페치 메서드 + func onAppear() { + Task { + await fetchIntroductions() + } + } + + /// 데이터를 새로고침하는 메서드 + func refresh() { + Task { + await fetchIntroductions() + } + } + + // MARK: - Private Methods + + /// 팀원 소개 데이터를 가져오는 메서드 + private func fetchIntroductions() async { + isLoading = true + + do { + // 실제 API 호출 시뮬레이션 (2초 딜레이) + try await Task.sleep(nanoseconds: 2_000_000_000) + + // Mock 데이터 로드 + introductions = IntroductionRowModel.mockData + + } catch { + introductions = [] + } + + isLoading = false + } +}