feat: 포인트 내역 UI 추가
This commit is contained in:
@@ -157,4 +157,6 @@ enum AppStep {
|
|||||||
case introduceCreatorAll
|
case introduceCreatorAll
|
||||||
|
|
||||||
case message
|
case message
|
||||||
|
|
||||||
|
case pointStatus(refresh: () -> Void)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -242,6 +242,9 @@ struct ContentView: View {
|
|||||||
case .message:
|
case .message:
|
||||||
MessageView()
|
MessageView()
|
||||||
|
|
||||||
|
case .pointStatus(let refresh):
|
||||||
|
PointStatusView(refresh: refresh)
|
||||||
|
|
||||||
default:
|
default:
|
||||||
EmptyView()
|
EmptyView()
|
||||||
.frame(width: 0, height: 0, alignment: .topLeading)
|
.frame(width: 0, height: 0, alignment: .topLeading)
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ struct CanStatusView: View {
|
|||||||
.resizable()
|
.resizable()
|
||||||
.frame(width: 26.7, height: 26.7)
|
.frame(width: 26.7, height: 26.7)
|
||||||
|
|
||||||
Text("\(viewModel.totalCan) 캔")
|
Text("\(viewModel.totalCan)")
|
||||||
.font(.custom(Font.bold.rawValue, size: 18.3))
|
.font(.custom(Font.bold.rawValue, size: 18.3))
|
||||||
.foregroundColor(Color(hex: "eeeeee"))
|
.foregroundColor(Color(hex: "eeeeee"))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -124,14 +124,23 @@ struct MyPageView: View {
|
|||||||
.frame(width: screenSize().width - 26.7)
|
.frame(width: screenSize().width - 26.7)
|
||||||
.padding(.top, 26.7)
|
.padding(.top, 26.7)
|
||||||
|
|
||||||
HStack(spacing: 6.7) {
|
HStack {
|
||||||
Text("\(data.point)")
|
HStack(spacing: 6.7) {
|
||||||
.font(.custom(Font.bold.rawValue, size: 18.3))
|
Text("\(data.point)")
|
||||||
.foregroundColor(.grayee)
|
.font(.custom(Font.bold.rawValue, size: 18.3))
|
||||||
|
.foregroundColor(.grayee)
|
||||||
|
|
||||||
Image("ic_point")
|
Image("ic_point")
|
||||||
.resizable()
|
.resizable()
|
||||||
.frame(width: 20, height: 20)
|
.frame(width: 20, height: 20)
|
||||||
|
|
||||||
|
Image("ic_forward")
|
||||||
|
.resizable()
|
||||||
|
.frame(width: 20, height: 20)
|
||||||
|
}
|
||||||
|
.onTapGesture {
|
||||||
|
AppState.shared.setAppStep(step: .pointStatus(refresh: { viewModel.getMypage() }))
|
||||||
|
}
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
}
|
}
|
||||||
|
|||||||
10
SodaLive/Sources/MyPage/Point/GetPointStatusResponse.swift
Normal file
10
SodaLive/Sources/MyPage/Point/GetPointStatusResponse.swift
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
//
|
||||||
|
// GetPointStatusResponse.swift
|
||||||
|
// SodaLive
|
||||||
|
//
|
||||||
|
// Created by klaus on 5/20/25.
|
||||||
|
//
|
||||||
|
|
||||||
|
struct GetPointStatusResponse: Decodable {
|
||||||
|
let point: Int
|
||||||
|
}
|
||||||
55
SodaLive/Sources/MyPage/Point/PointStatusApi.swift
Normal file
55
SodaLive/Sources/MyPage/Point/PointStatusApi.swift
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
//
|
||||||
|
// PointStatusApi.swift
|
||||||
|
// SodaLive
|
||||||
|
//
|
||||||
|
// Created by klaus on 5/20/25.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import Moya
|
||||||
|
|
||||||
|
enum PointStatusApi {
|
||||||
|
case getPointStatus
|
||||||
|
case getPointRewardStatus
|
||||||
|
case getPointUseStatus
|
||||||
|
}
|
||||||
|
|
||||||
|
extension PointStatusApi: TargetType {
|
||||||
|
var baseURL: URL {
|
||||||
|
return URL(string: BASE_URL)!
|
||||||
|
}
|
||||||
|
|
||||||
|
var path: String {
|
||||||
|
switch self {
|
||||||
|
case .getPointStatus:
|
||||||
|
return "/point/status"
|
||||||
|
|
||||||
|
case .getPointRewardStatus:
|
||||||
|
return "/point/status/reward"
|
||||||
|
|
||||||
|
case .getPointUseStatus:
|
||||||
|
return "/point/status/use"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var method: Moya.Method {
|
||||||
|
return .get
|
||||||
|
}
|
||||||
|
|
||||||
|
var task: Moya.Task {
|
||||||
|
switch self {
|
||||||
|
case .getPointStatus:
|
||||||
|
return .requestPlain
|
||||||
|
|
||||||
|
case .getPointRewardStatus, .getPointUseStatus:
|
||||||
|
return .requestParameters(
|
||||||
|
parameters: ["timezone": TimeZone.current.identifier],
|
||||||
|
encoding: URLEncoding.queryString
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var headers: [String : String]? {
|
||||||
|
return ["Authorization": "Bearer \(UserDefaults.string(forKey: UserDefaultsKey.token))"]
|
||||||
|
}
|
||||||
|
}
|
||||||
27
SodaLive/Sources/MyPage/Point/PointStatusRepository.swift
Normal file
27
SodaLive/Sources/MyPage/Point/PointStatusRepository.swift
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
//
|
||||||
|
// PointStatusRepository.swift
|
||||||
|
// SodaLive
|
||||||
|
//
|
||||||
|
// Created by klaus on 5/20/25.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import CombineMoya
|
||||||
|
import Combine
|
||||||
|
import Moya
|
||||||
|
|
||||||
|
class PointStatusRepository {
|
||||||
|
private let api = MoyaProvider<PointStatusApi>()
|
||||||
|
|
||||||
|
func getPointStatus() -> AnyPublisher<Response, MoyaError> {
|
||||||
|
return api.requestPublisher(.getPointStatus)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getPointRewardStatus() -> AnyPublisher<Response, MoyaError> {
|
||||||
|
return api.requestPublisher(.getPointRewardStatus)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getPointUseStatus() -> AnyPublisher<Response, MoyaError> {
|
||||||
|
return api.requestPublisher(.getPointUseStatus)
|
||||||
|
}
|
||||||
|
}
|
||||||
135
SodaLive/Sources/MyPage/Point/PointStatusView.swift
Normal file
135
SodaLive/Sources/MyPage/Point/PointStatusView.swift
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
//
|
||||||
|
// PointStatusView.swift
|
||||||
|
// SodaLive
|
||||||
|
//
|
||||||
|
// Created by klaus on 5/20/25.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct PointStatusView: View {
|
||||||
|
|
||||||
|
let refresh: () -> Void
|
||||||
|
|
||||||
|
@StateObject var viewModel = PointStatusViewModel()
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
BaseView(isLoading: $viewModel.isLoading) {
|
||||||
|
GeometryReader { proxy in
|
||||||
|
VStack(spacing: 0) {
|
||||||
|
DetailNavigationBar(title: "포인트 내역") {
|
||||||
|
AppState.shared.setAppStep(step: .main)
|
||||||
|
}
|
||||||
|
|
||||||
|
ScrollView(.vertical, showsIndicators: false) {
|
||||||
|
VStack(spacing: 26.7) {
|
||||||
|
HStack(spacing: 6.7) {
|
||||||
|
Image("ic_point")
|
||||||
|
.resizable()
|
||||||
|
.frame(width: 26.7, height: 26.7)
|
||||||
|
|
||||||
|
Text("\(viewModel.totalCan)")
|
||||||
|
.font(.custom(Font.bold.rawValue, size: 18.3))
|
||||||
|
.foregroundColor(.grayee)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(.vertical, 13.3)
|
||||||
|
.frame(maxWidth: .infinity)
|
||||||
|
.padding(.horizontal, 13.3)
|
||||||
|
.background(Color.gray22)
|
||||||
|
.cornerRadius(16.7)
|
||||||
|
.padding(.top, 13.3)
|
||||||
|
|
||||||
|
HStack(spacing: 0) {
|
||||||
|
VStack(spacing: 0) {
|
||||||
|
Spacer()
|
||||||
|
Text("받은내역")
|
||||||
|
.font(.custom(Font.medium.rawValue, size: 13.3))
|
||||||
|
.foregroundColor(viewModel.currentTab == .reward ? .grayee : .gray77)
|
||||||
|
Spacer()
|
||||||
|
Rectangle()
|
||||||
|
.frame(height: 1)
|
||||||
|
.foregroundColor(
|
||||||
|
.button
|
||||||
|
.opacity(viewModel.currentTab == .reward ? 1 : 0)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.frame(maxWidth: .infinity)
|
||||||
|
.frame(height: 50)
|
||||||
|
.onTapGesture {
|
||||||
|
if viewModel.currentTab != .reward {
|
||||||
|
viewModel.currentTab = .reward
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
VStack(spacing: 0) {
|
||||||
|
Spacer()
|
||||||
|
Text("사용내역")
|
||||||
|
.font(.custom(Font.medium.rawValue, size: 13.3))
|
||||||
|
.foregroundColor(viewModel.currentTab == .use ? .grayee : .gray77)
|
||||||
|
Spacer()
|
||||||
|
Rectangle()
|
||||||
|
.frame(height: 1)
|
||||||
|
.foregroundColor(
|
||||||
|
.button
|
||||||
|
.opacity(viewModel.currentTab == .use ? 1 : 0)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.frame(maxWidth: .infinity)
|
||||||
|
.frame(height: 50)
|
||||||
|
.onTapGesture {
|
||||||
|
if viewModel.currentTab != .use {
|
||||||
|
viewModel.currentTab = .use
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(.top, 13.3)
|
||||||
|
|
||||||
|
switch viewModel.currentTab {
|
||||||
|
case .reward:
|
||||||
|
PointRewardStatusView()
|
||||||
|
|
||||||
|
case .use:
|
||||||
|
PointUseStatusView()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
|
||||||
|
if proxy.safeAreaInsets.bottom > 0 {
|
||||||
|
Rectangle()
|
||||||
|
.foregroundColor(.black)
|
||||||
|
.frame(width: proxy.size.width, height: 15.3)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.edgesIgnoringSafeArea(.bottom)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.popup(isPresented: $viewModel.isShowPopup, type: .toast, position: .top, autohideIn: 2) {
|
||||||
|
GeometryReader { geo in
|
||||||
|
HStack {
|
||||||
|
Spacer()
|
||||||
|
Text(viewModel.errorMessage)
|
||||||
|
.padding(.vertical, 13.3)
|
||||||
|
.padding(.horizontal, 6.7)
|
||||||
|
.frame(width: geo.size.width - 66.7, alignment: .center)
|
||||||
|
.font(.custom(Font.medium.rawValue, size: 12))
|
||||||
|
.background(Color.button)
|
||||||
|
.foregroundColor(Color.white)
|
||||||
|
.multilineTextAlignment(.leading)
|
||||||
|
.fixedSize(horizontal: false, vertical: true)
|
||||||
|
.cornerRadius(20)
|
||||||
|
.padding(.top, 66.7)
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.onAppear {
|
||||||
|
viewModel.getPointStatus()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#Preview {
|
||||||
|
PointStatusView {}
|
||||||
|
}
|
||||||
143
SodaLive/Sources/MyPage/Point/PointStatusViewModel.swift
Normal file
143
SodaLive/Sources/MyPage/Point/PointStatusViewModel.swift
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
//
|
||||||
|
// PointStatusViewModel.swift
|
||||||
|
// SodaLive
|
||||||
|
//
|
||||||
|
// Created by klaus on 5/20/25.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import Combine
|
||||||
|
|
||||||
|
final class PointStatusViewModel: ObservableObject {
|
||||||
|
private let repository = PointStatusRepository()
|
||||||
|
private var subscription = Set<AnyCancellable>()
|
||||||
|
|
||||||
|
@Published var currentTab: CurrentTab = .reward
|
||||||
|
|
||||||
|
@Published var errorMessage = ""
|
||||||
|
@Published var isShowPopup = false
|
||||||
|
@Published var isLoading = false
|
||||||
|
|
||||||
|
@Published var totalCan: Int = 0
|
||||||
|
|
||||||
|
@Published var useStatusItems: [GetPointUseStatusResponse] = []
|
||||||
|
@Published var rewardStatusItems: [GetPointRewardStatusResponse] = []
|
||||||
|
|
||||||
|
func getPointStatus() {
|
||||||
|
isLoading = true
|
||||||
|
|
||||||
|
repository.getPointStatus()
|
||||||
|
.sink { result in
|
||||||
|
switch result {
|
||||||
|
case .finished:
|
||||||
|
DEBUG_LOG("finish")
|
||||||
|
case .failure(let error):
|
||||||
|
ERROR_LOG(error.localizedDescription)
|
||||||
|
}
|
||||||
|
} receiveValue: { [unowned self] response in
|
||||||
|
self.isLoading = false
|
||||||
|
let responseData = response.data
|
||||||
|
|
||||||
|
do {
|
||||||
|
let jsonDecoder = JSONDecoder()
|
||||||
|
let decoded = try jsonDecoder.decode(ApiResponse<GetPointStatusResponse>.self, from: responseData)
|
||||||
|
|
||||||
|
if let data = decoded.data, decoded.success {
|
||||||
|
self.totalCan = data.point
|
||||||
|
} else {
|
||||||
|
if let message = decoded.message {
|
||||||
|
self.errorMessage = message
|
||||||
|
} else {
|
||||||
|
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||||
|
}
|
||||||
|
|
||||||
|
self.isShowPopup = true
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||||
|
self.isShowPopup = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.store(in: &subscription)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getPointRewardStatus() {
|
||||||
|
isLoading = true
|
||||||
|
|
||||||
|
repository.getPointRewardStatus()
|
||||||
|
.sink { result in
|
||||||
|
switch result {
|
||||||
|
case .finished:
|
||||||
|
DEBUG_LOG("finish")
|
||||||
|
case .failure(let error):
|
||||||
|
ERROR_LOG(error.localizedDescription)
|
||||||
|
}
|
||||||
|
} receiveValue: { [unowned self] response in
|
||||||
|
self.isLoading = false
|
||||||
|
let responseData = response.data
|
||||||
|
|
||||||
|
do {
|
||||||
|
let jsonDecoder = JSONDecoder()
|
||||||
|
let decoded = try jsonDecoder.decode(ApiResponse<[GetPointRewardStatusResponse]>.self, from: responseData)
|
||||||
|
|
||||||
|
if let data = decoded.data, decoded.success {
|
||||||
|
self.rewardStatusItems.append(contentsOf: data)
|
||||||
|
} else {
|
||||||
|
if let message = decoded.message {
|
||||||
|
self.errorMessage = message
|
||||||
|
} else {
|
||||||
|
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||||
|
}
|
||||||
|
|
||||||
|
self.isShowPopup = true
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||||
|
self.isShowPopup = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.store(in: &subscription)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getPointUseStatus() {
|
||||||
|
isLoading = true
|
||||||
|
|
||||||
|
repository.getPointUseStatus()
|
||||||
|
.sink { result in
|
||||||
|
switch result {
|
||||||
|
case .finished:
|
||||||
|
DEBUG_LOG("finish")
|
||||||
|
case .failure(let error):
|
||||||
|
ERROR_LOG(error.localizedDescription)
|
||||||
|
}
|
||||||
|
} receiveValue: { [unowned self] response in
|
||||||
|
self.isLoading = false
|
||||||
|
let responseData = response.data
|
||||||
|
|
||||||
|
do {
|
||||||
|
let jsonDecoder = JSONDecoder()
|
||||||
|
let decoded = try jsonDecoder.decode(ApiResponse<[GetPointUseStatusResponse]>.self, from: responseData)
|
||||||
|
|
||||||
|
if let data = decoded.data, decoded.success {
|
||||||
|
self.useStatusItems.append(contentsOf: data)
|
||||||
|
} else {
|
||||||
|
if let message = decoded.message {
|
||||||
|
self.errorMessage = message
|
||||||
|
} else {
|
||||||
|
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||||
|
}
|
||||||
|
|
||||||
|
self.isShowPopup = true
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||||
|
self.isShowPopup = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.store(in: &subscription)
|
||||||
|
}
|
||||||
|
|
||||||
|
enum CurrentTab: String {
|
||||||
|
case reward, use
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
//
|
||||||
|
// GetPointRewardStatusResponse.swift
|
||||||
|
// SodaLive
|
||||||
|
//
|
||||||
|
// Created by klaus on 5/20/25.
|
||||||
|
//
|
||||||
|
|
||||||
|
struct GetPointRewardStatusResponse: Decodable, Hashable {
|
||||||
|
let rewardPoint: String
|
||||||
|
let date: String
|
||||||
|
let method: String
|
||||||
|
}
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
//
|
||||||
|
// PointRewardStatusView.swift
|
||||||
|
// SodaLive
|
||||||
|
//
|
||||||
|
// Created by klaus on 5/20/25.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct PointRewardStatusView: View {
|
||||||
|
|
||||||
|
@StateObject var viewModel = PointStatusViewModel()
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
ZStack {
|
||||||
|
VStack(spacing: 13.3) {
|
||||||
|
ForEach(viewModel.rewardStatusItems, id: \.self) { item in
|
||||||
|
PointRewardStatusItemView(item: item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(.top, 13.3)
|
||||||
|
|
||||||
|
if viewModel.isLoading {
|
||||||
|
LoadingView()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.onAppear {
|
||||||
|
viewModel.getPointRewardStatus()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct PointRewardStatusItemView: View {
|
||||||
|
|
||||||
|
let item: GetPointRewardStatusResponse
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
HStack(spacing: 0) {
|
||||||
|
VStack(alignment: .leading, spacing: 6.7) {
|
||||||
|
Text(item.rewardPoint)
|
||||||
|
.font(.custom(Font.medium.rawValue, size: 13.3))
|
||||||
|
.foregroundColor(.grayee)
|
||||||
|
|
||||||
|
Text(item.date)
|
||||||
|
.font(.custom(Font.medium.rawValue, size: 12))
|
||||||
|
.foregroundColor(.gray77)
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
|
||||||
|
Text(item.method)
|
||||||
|
.font(.custom(Font.medium.rawValue, size: 13.3))
|
||||||
|
.foregroundColor(.grayee)
|
||||||
|
}
|
||||||
|
.padding(.horizontal, 13.3)
|
||||||
|
.padding(.vertical, 16)
|
||||||
|
.background(Color.gray11)
|
||||||
|
.cornerRadius(16.7)
|
||||||
|
.padding(.horizontal, 13.3)
|
||||||
|
.frame(maxWidth: .infinity)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#Preview {
|
||||||
|
PointRewardStatusView()
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
//
|
||||||
|
// GetPointUseStatusResponse.swift
|
||||||
|
// SodaLive
|
||||||
|
//
|
||||||
|
// Created by klaus on 5/20/25.
|
||||||
|
//
|
||||||
|
|
||||||
|
struct GetPointUseStatusResponse: Decodable, Hashable {
|
||||||
|
let title: String
|
||||||
|
let date: String
|
||||||
|
let point: Int
|
||||||
|
}
|
||||||
71
SodaLive/Sources/MyPage/Point/Use/PointUseStatusView.swift
Normal file
71
SodaLive/Sources/MyPage/Point/Use/PointUseStatusView.swift
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
//
|
||||||
|
// PointUseStatusView.swift
|
||||||
|
// SodaLive
|
||||||
|
//
|
||||||
|
// Created by klaus on 5/20/25.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct PointUseStatusView: View {
|
||||||
|
|
||||||
|
@StateObject var viewModel = PointStatusViewModel()
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
ZStack {
|
||||||
|
VStack(spacing: 13.3) {
|
||||||
|
ForEach(viewModel.useStatusItems, id: \.self) { item in
|
||||||
|
PointUseStatusItemView(item: item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(.top, 13.3)
|
||||||
|
|
||||||
|
if viewModel.isLoading {
|
||||||
|
LoadingView()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.onAppear {
|
||||||
|
viewModel.getPointUseStatus()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct PointUseStatusItemView: View {
|
||||||
|
|
||||||
|
let item: GetPointUseStatusResponse
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
HStack(spacing: 0) {
|
||||||
|
VStack(alignment: .leading, spacing: 6.7) {
|
||||||
|
Text(item.title)
|
||||||
|
.font(.custom(Font.medium.rawValue, size: 13.3))
|
||||||
|
.foregroundColor(.grayee)
|
||||||
|
|
||||||
|
Text(item.date)
|
||||||
|
.font(.custom(Font.medium.rawValue, size: 12))
|
||||||
|
.foregroundColor(.gray77)
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
|
||||||
|
Text("\(item.point)")
|
||||||
|
.font(.custom(Font.medium.rawValue, size: 13.3))
|
||||||
|
.foregroundColor(.grayee)
|
||||||
|
|
||||||
|
Image("ic_point")
|
||||||
|
.resizable()
|
||||||
|
.frame(width: 26.7, height: 26.7)
|
||||||
|
.padding(.leading, 6.7)
|
||||||
|
}
|
||||||
|
.padding(.horizontal, 13.3)
|
||||||
|
.padding(.vertical, 16)
|
||||||
|
.background(Color.gray11)
|
||||||
|
.cornerRadius(16.7)
|
||||||
|
.padding(.horizontal, 13.3)
|
||||||
|
.frame(maxWidth: .infinity)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#Preview {
|
||||||
|
PointUseStatusView()
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user