Skip to content

Commit 074bc72

Browse files
authored
Merge pull request #8 from MarketPlace-O2O-Platform/Feature#7/AlertView
[Feature#7/alert view] 알림탭 UI
2 parents 332ef4d + ed21940 commit 074bc72

9 files changed

Lines changed: 296 additions & 2 deletions

File tree

.DS_Store

0 Bytes
Binary file not shown.

MarketPlace.xcodeproj/project.pbxproj

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,9 @@
4242
B2122E3D2D6F7C0000BD37F8 /* MarketResDtos.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2122E3C2D6F7C0000BD37F8 /* MarketResDtos.swift */; };
4343
B2122E3F2D6F7D4000BD37F8 /* MarketCategoryDetailViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2122E3E2D6F7D4000BD37F8 /* MarketCategoryDetailViewModel.swift */; };
4444
B2122E422D6F85F700BD37F8 /* MapStoreDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2122E412D6F85F700BD37F8 /* MapStoreDetailView.swift */; };
45+
B215F2912DE61C8400F53C51 /* AlertView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B215F2902DE61C8400F53C51 /* AlertView.swift */; };
46+
B215F2942DE61D9600F53C51 /* AlertButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B215F2932DE61D9600F53C51 /* AlertButtonView.swift */; };
47+
B215F2962DE6D77B00F53C51 /* AlertListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B215F2952DE6D77B00F53C51 /* AlertListView.swift */; };
4548
B21F3CEF2D267AF900B83B34 /* Coupon.swift in Sources */ = {isa = PBXBuildFile; fileRef = B21F3CDA2D267AF900B83B34 /* Coupon.swift */; };
4649
B21F3CF22D267AF900B83B34 /* ShopInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B21F3CDD2D267AF900B83B34 /* ShopInfoView.swift */; };
4750
B21F3CF32D267AF900B83B34 /* ShopListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B21F3CDE2D267AF900B83B34 /* ShopListView.swift */; };
@@ -162,6 +165,9 @@
162165
B2122E3C2D6F7C0000BD37F8 /* MarketResDtos.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MarketResDtos.swift; sourceTree = "<group>"; };
163166
B2122E3E2D6F7D4000BD37F8 /* MarketCategoryDetailViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MarketCategoryDetailViewModel.swift; sourceTree = "<group>"; };
164167
B2122E412D6F85F700BD37F8 /* MapStoreDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapStoreDetailView.swift; sourceTree = "<group>"; };
168+
B215F2902DE61C8400F53C51 /* AlertView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertView.swift; sourceTree = "<group>"; };
169+
B215F2932DE61D9600F53C51 /* AlertButtonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertButtonView.swift; sourceTree = "<group>"; };
170+
B215F2952DE6D77B00F53C51 /* AlertListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertListView.swift; sourceTree = "<group>"; };
165171
B21F3CDA2D267AF900B83B34 /* Coupon.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Coupon.swift; sourceTree = "<group>"; };
166172
B21F3CDD2D267AF900B83B34 /* ShopInfoView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ShopInfoView.swift; sourceTree = "<group>"; };
167173
B21F3CDE2D267AF900B83B34 /* ShopListView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ShopListView.swift; sourceTree = "<group>"; };
@@ -376,6 +382,24 @@
376382
path = Market;
377383
sourceTree = "<group>";
378384
};
385+
B215F28F2DE619DF00F53C51 /* Alert */ = {
386+
isa = PBXGroup;
387+
children = (
388+
B215F2922DE61D4D00F53C51 /* component */,
389+
B215F2902DE61C8400F53C51 /* AlertView.swift */,
390+
);
391+
path = Alert;
392+
sourceTree = "<group>";
393+
};
394+
B215F2922DE61D4D00F53C51 /* component */ = {
395+
isa = PBXGroup;
396+
children = (
397+
B215F2932DE61D9600F53C51 /* AlertButtonView.swift */,
398+
B215F2952DE6D77B00F53C51 /* AlertListView.swift */,
399+
);
400+
path = component;
401+
sourceTree = "<group>";
402+
};
379403
B21F3CDF2D267AF900B83B34 /* components */ = {
380404
isa = PBXGroup;
381405
children = (
@@ -426,6 +450,7 @@
426450
B21F3CEE2D267AF900B83B34 /* View */ = {
427451
isa = PBXGroup;
428452
children = (
453+
B215F28F2DE619DF00F53C51 /* Alert */,
429454
B21F3CDF2D267AF900B83B34 /* components */,
430455
B2C462962D8172AC007637DC /* login */,
431456
B21F3CE72D267AF900B83B34 /* Main */,
@@ -835,6 +860,7 @@
835860
B250AEF42DA53FDD00456626 /* CouponUsePutViewModel.swift in Sources */,
836861
B2F85BEA2D6FB4E50011DDF3 /* MapOptions.swift in Sources */,
837862
29BF18C12DE4950700836D7E /* APIResDto.swift in Sources */,
863+
B215F2912DE61C8400F53C51 /* AlertView.swift in Sources */,
838864
B28A22AF2D64F1ED00E86EF6 /* StoreImageSlider.swift in Sources */,
839865
B210EC242D3D686500B1551E /* MyHeaderView.swift in Sources */,
840866
B210EC282D3D797F00B1551E /* SearchHeader.swift in Sources */,
@@ -846,6 +872,7 @@
846872
B27E56D52D704438000D2881 /* HotCheerCardView.swift in Sources */,
847873
2941F7932DE5A2FD0063CEFE /* MemberCouponService.swift in Sources */,
848874
B293A1582D6FDFC6003E8505 /* MyCheerView.swift in Sources */,
875+
B215F2942DE61D9600F53C51 /* AlertButtonView.swift in Sources */,
849876
B2122E3D2D6F7C0000BD37F8 /* MarketResDtos.swift in Sources */,
850877
B2122E3F2D6F7D4000BD37F8 /* MarketCategoryDetailViewModel.swift in Sources */,
851878
B28349552D2FBC4B00EEDB02 /* PopularityBenefitView.swift in Sources */,
@@ -906,6 +933,7 @@
906933
B28889A32D4BC9370040EC3F /* CouponResDtos.swift in Sources */,
907934
B255A5CC2D62155700954837 /* MemberFavoriteMarketListModelView.swift in Sources */,
908935
B207A25F2D54B18C0099A8DB /* CouponItem.swift in Sources */,
936+
B215F2962DE6D77B00F53C51 /* AlertListView.swift in Sources */,
909937
B28349162D2E604800EEDB02 /* ShopModel.swift in Sources */,
910938
B22AB90C2D99BB15006E13B4 /* MarketFavoritePostViewModel.swift in Sources */,
911939
B21F3CF72D267AF900B83B34 /* NewEventView.swift in Sources */,

MarketPlace/UICommon/Color.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@ struct Colors {
66
static let grayscale_gray_400 = Color(hex: "#9B9B9B")
77
static let fontColor = Color(hex: "#303030")
88
static let backgroundColor = Color.white
9+
static let gray_50 = Color(hex: "#FAFAFA")
10+
static let gray_100 = Color(hex: "#EEEEEE")
11+
static let gray_700 = Color(hex: "#5E5E5E")
12+
static let gray_900 = Color(hex: "#333333")
13+
static let primary = Color(hex: "#303030")
914
}
1015

1116
// 상수 값들을 분리하여 관리
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
//
2+
// AlertView.swift
3+
// MarketPlace
4+
//
5+
// Created by 이예나 on 5/28/25.
6+
//
7+
8+
import SwiftUI
9+
10+
struct AlertView: View {
11+
@State private var selectedCategory: String = "전체"
12+
@State private var alerts: [AlertCardModel] = [
13+
AlertCardModel(
14+
chipTitle: "쿠폰 발급",
15+
boldText: "00매장에서 신규 쿠폰이 발행되었습니다.",
16+
subText: "마이페이지 받은 쿠폰함에서 확인하실 수 있습니다.",
17+
timeText: "1일 전"
18+
),
19+
AlertCardModel(
20+
chipTitle: "공지",
21+
boldText: "앱 점검 안내드립니다.",
22+
subText: "5월 30일 02:00~04:00까지 서비스 이용이 제한됩니다.",
23+
timeText: "2일 전"
24+
),
25+
AlertCardModel(
26+
chipTitle: "쿠폰 만료",
27+
boldText: "쿠폰이 곧 만료됩니다.",
28+
subText: "3일 후 만료되는 쿠폰이 있습니다. 서둘러 사용해주세요!",
29+
timeText: "3시간 전"
30+
),
31+
AlertCardModel(
32+
chipTitle: "쿠폰 발급",
33+
boldText: "XX매장에서 특별 쿠폰이 발행되었습니다.",
34+
subText: "한정 수량이니 서둘러 받아가세요!",
35+
timeText: "5시간 전"
36+
)
37+
]
38+
39+
var body: some View {
40+
VStack(alignment: .leading, spacing: 0) {
41+
HStack {
42+
AlertButtonGroup(selectedCategory: $selectedCategory)
43+
44+
Spacer()
45+
46+
Button(action: {
47+
markAllAsRead()
48+
}) {
49+
Text("전체 읽음")
50+
.font(.system(size: 12))
51+
.foregroundColor(Color.black)
52+
}
53+
}
54+
.padding(.horizontal, 20)
55+
.padding(.vertical, 12)
56+
57+
AlertCardListView(selectedCategory: selectedCategory, alerts: alerts)
58+
}
59+
}
60+
61+
func markAllAsRead() {
62+
for alert in alerts {
63+
alert.isRead = true
64+
}
65+
}
66+
}
67+
68+
#Preview("AlertView Preview") {
69+
AlertView()
70+
}
71+
72+
#Preview("AlertButtonGroup Preview") {
73+
@State var selectedCategory = "전체"
74+
return AlertButtonGroup(selectedCategory: $selectedCategory)
75+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
//
2+
// AlertButtonView.swift
3+
// MarketPlace
4+
//
5+
// Created by 이예나 on 5/28/25.
6+
//
7+
8+
import SwiftUI
9+
10+
struct AlertButtonView: View {
11+
var title: String
12+
var isSelected: Bool
13+
var onTap: () -> Void
14+
15+
var body: some View {
16+
Button(action: {
17+
onTap()
18+
}) {
19+
Text(title)
20+
.font(.system(size: 12, weight: .semibold))
21+
.foregroundColor(isSelected ? .white : Color.gray)
22+
.padding(.vertical, 8)
23+
.padding(.horizontal, 16)
24+
.background(
25+
RoundedRectangle(cornerRadius: 20)
26+
.fill(isSelected ? Color.black : Color.white)
27+
)
28+
.overlay(
29+
RoundedRectangle(cornerRadius: 20)
30+
.stroke(Color.gray.opacity(0.4), lineWidth: isSelected ? 0 : 1)
31+
)
32+
}
33+
}
34+
}
35+
36+
// 선택된 카테고리를 외부로 전달하도록 수정
37+
struct AlertButtonGroup: View {
38+
@Binding var selectedCategory: String
39+
let titles = ["전체", "쿠폰 발급", "쿠폰 만료", "공지"]
40+
41+
var body: some View {
42+
HStack(spacing: 8) {
43+
ForEach(titles, id: \.self) { title in
44+
AlertButtonView(title: title, isSelected: selectedCategory == title) {
45+
selectedCategory = title
46+
}
47+
}
48+
}
49+
}
50+
}
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
//
2+
// AlertListView.swift
3+
// MarketPlace
4+
//
5+
// Created by 이예나 on 5/28/25.
6+
//
7+
8+
import SwiftUI
9+
10+
class AlertCardModel: Identifiable, ObservableObject {
11+
let id = UUID()
12+
let chipTitle: String
13+
let boldText: String
14+
let subText: String
15+
let timeText: String
16+
17+
@Published var isRead: Bool
18+
19+
init(chipTitle: String, boldText: String, subText: String, timeText: String, isRead: Bool = false) {
20+
self.chipTitle = chipTitle
21+
self.boldText = boldText
22+
self.subText = subText
23+
self.timeText = timeText
24+
self.isRead = isRead
25+
}
26+
}
27+
28+
struct AlertCardListView: View {
29+
var selectedCategory: String
30+
var alerts: [AlertCardModel]
31+
32+
var filteredAlerts: [AlertCardModel] {
33+
if selectedCategory == "전체" {
34+
return alerts
35+
} else {
36+
return alerts.filter { $0.chipTitle == selectedCategory }
37+
}
38+
}
39+
40+
var body: some View {
41+
ScrollView {
42+
LazyVStack(alignment: .leading, spacing: 0) { // alignment: .leading 추가
43+
ForEach(filteredAlerts) { alert in
44+
AlertCardView(alert: alert) {
45+
alert.isRead = true
46+
}
47+
48+
// 카드 사이 구분선 추가 (선택사항)
49+
if alert.id != filteredAlerts.last?.id {
50+
Divider()
51+
.background(Color.gray.opacity(0.2))
52+
}
53+
}
54+
}
55+
.frame(maxWidth: .infinity, alignment: .leading) // 전체 프레임도 leading 정렬
56+
}
57+
}
58+
}
59+
60+
struct AlertChipView: View {
61+
var title: String
62+
63+
var body: some View {
64+
Text(title)
65+
.font(.system(size: 10))
66+
.foregroundColor(Color.gray)
67+
.padding(.vertical, 6)
68+
.padding(.horizontal, 10)
69+
.background(
70+
RoundedRectangle(cornerRadius: 50)
71+
.fill(Color.white)
72+
.stroke(Color.gray.opacity(0.3))
73+
)
74+
}
75+
}
76+
77+
struct AlertCardView: View {
78+
@ObservedObject var alert: AlertCardModel
79+
var onTap: () -> Void
80+
81+
var body: some View {
82+
VStack(alignment: .leading, spacing: 4) {
83+
AlertChipView(title: alert.chipTitle)
84+
.padding(.bottom, 12)
85+
86+
Text(alert.boldText)
87+
.font(.system(size: 16, weight: .semibold))
88+
.foregroundColor(.black)
89+
.frame(maxWidth: .infinity, alignment: .leading) // 텍스트 왼쪽 정렬 보장
90+
91+
Text(alert.subText)
92+
.font(.system(size: 14))
93+
.foregroundColor(Color.gray)
94+
.frame(maxWidth: .infinity, alignment: .leading) // 텍스트 왼쪽 정렬 보장
95+
96+
Text(alert.timeText)
97+
.font(.system(size: 12))
98+
.foregroundColor(Color.gray)
99+
.frame(maxWidth: .infinity, alignment: .leading) // 텍스트 왼쪽 정렬 보장
100+
}
101+
.padding(.horizontal, 20)
102+
.frame(maxWidth: .infinity, minHeight: 152, maxHeight: 152, alignment: .leading) // 전체 카드 왼쪽 정렬
103+
.background(alert.isRead ? Color.gray.opacity(0.1) : Color.white)
104+
.onTapGesture {
105+
onTap()
106+
}
107+
}
108+
}

MarketPlace/View/Main/Components/MainCategoryView.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ struct MainCategoryView: View {
2424
}) {
2525
CategoryButton(
2626
icon: category.toImageName(),
27-
text: category.toUIName(),
27+
text: category.toUIName()
2828
)
2929
}
3030
}
@@ -41,7 +41,7 @@ struct CategoryButton: View {
4141

4242
init(
4343
icon: String,
44-
text: String,
44+
text: String
4545
) {
4646
self.icon = icon
4747
self.text = text
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// API 엔드포인트 열거형에 추가
2+
enum APIEndpoint {
3+
static let baseURL = "https://marketplace.inuappcenter.kr"
4+
5+
// [로그인]
6+
static let loginEP = "/api/members"
7+
8+
// [홈] 인기쿠폰 top 20
9+
static let couponsPopular = "/api/coupons/popular?pageSize=10"
10+
static let couponNew = "/api/coupons/latest"
11+
static let couponValid = "/api/coupons"
12+
13+
//[회원] 회원의 쿠폰 발급 및 사용처리, 리스트 확인
14+
static let membersGetCoupons = "/api/members/coupons" // 회원 쿠폰 리스트 및 사용처리
15+
static let membersPostCouponCreate = "/api/members/coupons/{couponId}" //회원 쿠폰 발급
16+
static let membersGetCouponCheck = "/api/members/coupons/{memberCouponId}" // 회원의 발급 쿠폰 단일 조회
17+
// static let membersGetCouponList = "/api/members/coupons/valid" //회원의 쿠폰 리스트
18+
19+
// [매장]
20+
static let marketGetList = "/api/markets" // 전체&카테고리 매장 조회
21+
22+
// [찜]
23+
static let favoritesPostmarkets = "/api/favorites" // 찜하기
24+
25+
// [공감]
26+
static let cheerMarkets = "/api/tempMarkets" // 전체&카테고리 매장 조회
27+
static let hotCheerMarkets = "/api/tempMarkets/cheer" // 달성 임박 매장 조회
28+
}

0 commit comments

Comments
 (0)