커뮤니티 유료 게시글 조회, 구매 기능 추가

This commit is contained in:
Yu Sung 2024-05-24 16:19:43 +09:00
parent 0a96509b35
commit 3ae5ea776c
13 changed files with 337 additions and 63 deletions

View File

@ -0,0 +1,21 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "ic_lock_bb.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@ -0,0 +1,92 @@
//
// CommunityPostPurchaseDialog.swift
// SodaLive
//
// Created by klaus on 5/24/24.
//
import SwiftUI
struct CommunityPostPurchaseDialog: View {
@Binding var isShowing: Bool
let can: Int
let confirmAction: () -> Void
var body: some View {
GeometryReader { geo in
ZStack {
Color.black
.opacity(0.5)
.frame(width: geo.size.width, height: geo.size.height)
VStack(spacing: 0) {
Text("게시글 보기")
.font(.custom(Font.bold.rawValue, size: 18.3))
.foregroundColor(Color.graybb)
.padding(.top, 40)
Text("게시글을\n확인하시겠습니까?")
.font(.custom(Font.medium.rawValue, size: 15))
.foregroundColor(Color.graybb)
.multilineTextAlignment(.center)
.padding(.top, 12)
.padding(.horizontal, 13.3)
HStack(spacing: 13.3) {
Text("취소")
.font(.custom(Font.bold.rawValue, size: 15.3))
.foregroundColor(Color.button)
.padding(.vertical, 16)
.frame(width: (geo.size.width - 66.7) / 3)
.background(Color.bg)
.cornerRadius(8)
.overlay(
RoundedRectangle(cornerRadius: 8)
.stroke(Color.button, lineWidth: 1)
)
.onTapGesture {
isShowing = false
}
HStack(spacing: 0) {
Text("\(can)")
.font(.custom(Font.bold.rawValue, size: 15.3))
.foregroundColor(Color.white)
Image("ic_can")
.resizable()
.frame(width: 20, height: 20)
Text("으로 보기")
.font(.custom(Font.bold.rawValue, size: 15.3))
.foregroundColor(Color.white)
}
.padding(.vertical, 16)
.frame(width: (geo.size.width - 66.7) * 2 / 3)
.background(Color.button)
.cornerRadius(8)
.onTapGesture {
confirmAction()
isShowing = false
}
}
.padding(.top, 26.7)
.padding(.bottom, 16.7)
}
.frame(width: geo.size.width - 26.7, alignment: .center)
.background(Color.gray22)
.cornerRadius(10)
}
}
}
}
#Preview {
CommunityPostPurchaseDialog(
isShowing: .constant(true),
can: 10,
confirmAction: {}
)
}

View File

@ -0,0 +1,39 @@
//
// CreatorCommunityAllItemLockView.swift
// SodaLive
//
// Created by klaus on 5/24/24.
//
import SwiftUI
struct CreatorCommunityAllItemLockView: View {
let price: Int
let onClickPurchaseContent: () -> Void
var body: some View {
VStack(spacing: 26.7) {
Image("ic_lock_bb")
Text("\(price)캔으로 게시글 보기")
.font(.custom(Font.bold.rawValue, size: 12))
.foregroundColor(Color.button)
.padding(.horizontal, 21)
.padding(.vertical, 11)
.overlay(
RoundedRectangle(cornerRadius: 26.7)
.stroke(Color.button, lineWidth: 1)
)
.onTapGesture { onClickPurchaseContent() }
}
.frame(width: screenSize().width - 42, height: screenSize().width - 42)
.background(Color.gray33)
.cornerRadius(5.3)
}
}
#Preview {
CreatorCommunityAllItemLockView(price: 100) {
}
}

View File

@ -15,6 +15,7 @@ struct CreatorCommunityAllItemView: View {
let onClickComment: () -> Void
let onClickWriteComment: (String) -> Void
let onClickShowReportMenu: () -> Void
let onClickPurchaseContent: () -> Void
@State var isLike = false
@State var likeCount = 0
@ -25,13 +26,15 @@ struct CreatorCommunityAllItemView: View {
onClickLike: @escaping () -> Void,
onClickComment: @escaping () -> Void,
onClickWriteComment: @escaping (String) -> Void,
onClickShowReportMenu: @escaping () -> Void
onClickShowReportMenu: @escaping () -> Void,
onClickPurchaseContent: @escaping () -> Void
) {
self.item = item
self.onClickLike = onClickLike
self.onClickComment = onClickComment
self.onClickWriteComment = onClickWriteComment
self.onClickShowReportMenu = onClickShowReportMenu
self.onClickPurchaseContent = onClickPurchaseContent
self._isLike = State(initialValue: item.isLike)
self._likeCount = State(initialValue: item.likeCount)
@ -58,66 +61,74 @@ struct CreatorCommunityAllItemView: View {
Spacer()
Image("ic_seemore_vertical")
.padding(.trailing, 8.3)
.onTapGesture { onClickShowReportMenu() }
if item.price <= 0 || item.existOrdered {
Image("ic_seemore_vertical")
.padding(.trailing, 8.3)
.onTapGesture { onClickShowReportMenu() }
}
}
DetectableTextView(text: item.content, textSize: 13.3, font: Font.medium.rawValue)
.frame(
width: screenSize().width - 16,
height: textHeight
)
.onAppear {
self.textHeight = self.estimatedHeight(
for: item.content,
width: screenSize().width - 16
if item.price <= 0 || item.existOrdered {
DetectableTextView(text: item.content, textSize: 13.3, font: Font.medium.rawValue)
.frame(
width: screenSize().width - 16,
height: textHeight
)
}
.onChange(of: item.content) { newText in
self.textHeight = self.estimatedHeight(
for: newText,
width: screenSize().width - 16
)
}
if let imageUrl = item.imageUrl {
KFImage(URL(string: imageUrl))
.resizable()
.frame(maxWidth: .infinity)
.scaledToFit()
}
HStack(spacing: 8) {
IconAndTitleToggleButton(
isChecked: isLike,
title: "\(likeCount)",
normalIconName: "ic_audio_content_heart_normal",
checkedIconName: "ic_audio_content_heart_pressed"
) {
if isLike {
isLike = false
likeCount -= 1
} else {
isLike = true
likeCount += 1
.onAppear {
self.textHeight = self.estimatedHeight(
for: item.content,
width: screenSize().width - 16
)
}
onClickLike()
.onChange(of: item.content) { newText in
self.textHeight = self.estimatedHeight(
for: newText,
width: screenSize().width - 16
)
}
if let imageUrl = item.imageUrl {
KFImage(URL(string: imageUrl))
.resizable()
.frame(maxWidth: .infinity)
.scaledToFit()
}
}
.frame(maxWidth: .infinity, alignment: .leading)
if item.isCommentAvailable {
CreatorCommunityCommentView(
commentCount: item.commentCount,
commentItem: item.firstComment,
onClickWriteComment: onClickWriteComment
)
.onTapGesture {
if item.commentCount > 0 {
onClickComment()
HStack(spacing: 8) {
IconAndTitleToggleButton(
isChecked: isLike,
title: "\(likeCount)",
normalIconName: "ic_audio_content_heart_normal",
checkedIconName: "ic_audio_content_heart_pressed"
) {
if isLike {
isLike = false
likeCount -= 1
} else {
isLike = true
likeCount += 1
}
onClickLike()
}
}
.frame(maxWidth: .infinity, alignment: .leading)
if item.isCommentAvailable {
CreatorCommunityCommentView(
commentCount: item.commentCount,
commentItem: item.firstComment,
onClickWriteComment: onClickWriteComment
)
.onTapGesture {
if item.commentCount > 0 {
onClickComment()
}
}
}
} else {
CreatorCommunityAllItemLockView(
price: item.price,
onClickPurchaseContent: onClickPurchaseContent)
}
}
.padding(.horizontal, 8)
@ -144,10 +155,12 @@ struct CreatorCommunityAllItemView_Previews: PreviewProvider {
creatorProfileUrl: "https://test-cf.sodalive.net/profile/default-profile.png",
imageUrl: "https://test-cf.sodalive.net/profile/default-profile.png",
content: "너무 조하유 앞으로도 좋은 라이브 많이 들려주세요!너무 조하유 앞으로도 좋은 라이브 많이 들려주세요!너무 조하유 앞으로도 좋은 라이브 많이 들려주세요!너무 조하유 앞으로도 좋은 라이브 많이 들려주세요!너무 조하유 앞으로도 좋은 라이브 많이 들려주세요!너무 조하유 앞으로도 좋은 라이브 많이 들려주세요!너무 조하유 앞으로도 좋은 라이브 많이 들려주세요!너무 조하유 앞으로도 좋은 라이브 많이 들려주세요!너무 조하유 앞으로도 좋은 라이브 많이 들려주세요!너무 조하유 앞으로도 좋은 라이브 많이 들려주세요!너무 조하유 앞으로도 좋은 라이브 많이 들려주세요!너무 조하유 앞으로도 좋은 라이브 많이 들려주세요!",
price: 10,
date: "3일전",
isCommentAvailable: false,
isAdult: false,
isLike: true,
existOrdered: false,
likeCount: 10,
commentCount: 0,
firstComment: nil
@ -155,7 +168,8 @@ struct CreatorCommunityAllItemView_Previews: PreviewProvider {
onClickLike: {},
onClickComment: {},
onClickWriteComment: { _ in },
onClickShowReportMenu: {}
onClickShowReportMenu: {},
onClickPurchaseContent: {}
)
}
}

View File

@ -41,6 +41,12 @@ struct CreatorCommunityAllView: View {
onClickShowReportMenu: {
viewModel.postId = item.postId
viewModel.isShowReportMenu = true
},
onClickPurchaseContent: {
viewModel.postId = item.postId
viewModel.postPrice = item.price
viewModel.postIndex = index
viewModel.isShowPostPurchaseView = true
}
)
.onAppear {
@ -121,6 +127,15 @@ struct CreatorCommunityAllView: View {
}
)
}
if viewModel.isShowPostPurchaseView {
CommunityPostPurchaseDialog(
isShowing: $viewModel.isShowPostPurchaseView,
can: viewModel.postPrice
) {
viewModel.purchaseCommunityPost()
}
}
}
}
.popup(isPresented: $viewModel.isShowPopup, type: .toast, position: .top, autohideIn: 2) {

View File

@ -21,6 +21,8 @@ class CreatorCommunityAllViewModel: ObservableObject {
@Published private(set) var communityPostList = [GetCommunityPostListResponse]()
@Published var postId = 0
@Published var postPrice = 0
@Published var postIndex = -1
@Published var isShowCommentListView = false {
didSet {
@ -46,6 +48,16 @@ class CreatorCommunityAllViewModel: ObservableObject {
}
}
@Published var isShowPostPurchaseView = false {
didSet {
if !isShowPostPurchaseView {
postId = 0
postPrice = 0
postIndex = -1
}
}
}
var creatorId = 0
var page = 1
@ -254,4 +266,51 @@ class CreatorCommunityAllViewModel: ObservableObject {
self.isLoading = false
}
}
func purchaseCommunityPost() {
let postId = postId
let postIndex = postIndex
if !isLoading {
isLoading = true
repository
.purchaseCommunityPost(postId: postId)
.sink { result in
switch result {
case .finished:
DEBUG_LOG("finish")
case .failure(let error):
ERROR_LOG(error.localizedDescription)
}
} receiveValue: { [unowned self] response in
let responseData = response.data
do {
let jsonDecoder = JSONDecoder()
let decoded = try jsonDecoder.decode(ApiResponse<GetCommunityPostListResponse>.self, from: responseData)
if let data = decoded.data, decoded.success {
if postIndex >= 0 {
communityPostList[postIndex] = data
}
} else {
if let message = decoded.message {
self.errorMessage = message
} else {
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
}
self.isShowPopup = true
}
} catch {
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
self.isShowPopup = true
}
self.isLoading = false
}
.store(in: &subscription)
}
}
}

View File

@ -0,0 +1,14 @@
//
// PurchasePostRequest.swift
// SodaLive
//
// Created by klaus on 5/24/24.
//
import Foundation
struct PurchasePostRequest: Encodable {
let postId: Int
let container: String = "ios"
let timezone: String = TimeZone.current.identifier
}

View File

@ -19,6 +19,7 @@ enum CreatorCommunityApi {
case getCommentReplyList(commentId: Int, page: Int, size: Int)
case modifyComment(request: ModifyCommunityPostCommentRequest)
case getLatestPostListFromCreatorsYouFollow
case purchaseCommunityPost(postId: Int)
}
extension CreatorCommunityApi: TargetType {
@ -48,12 +49,15 @@ extension CreatorCommunityApi: TargetType {
case .getLatestPostListFromCreatorsYouFollow:
return "/creator-community/latest"
case .purchaseCommunityPost:
return "/creator-community/purchase"
}
}
var method: Moya.Method {
switch self {
case .createCommunityPost, .communityPostLike, .createCommunityPostComment:
case .createCommunityPost, .communityPostLike, .createCommunityPostComment, .purchaseCommunityPost:
return .post
case .getCommunityPostList, .getCommunityPostCommentList, .getCommentReplyList, .getCommunityPostDetail, .getLatestPostListFromCreatorsYouFollow:
@ -115,6 +119,9 @@ extension CreatorCommunityApi: TargetType {
case .getLatestPostListFromCreatorsYouFollow:
let parameters = ["timezone": TimeZone.current.identifier] as [String: Any]
return .requestParameters(parameters: parameters, encoding: URLEncoding.queryString)
case .purchaseCommunityPost(let postId):
return .requestJSONEncodable(PurchasePostRequest(postId: postId))
}
}

View File

@ -22,19 +22,19 @@ struct CreatorCommunityItemView: View {
Text(item.creatorNickname)
.font(.custom(Font.medium.rawValue, size: 13.3))
.foregroundColor(Color(hex: "eeeeee"))
.foregroundColor(Color.grayee)
Spacer()
Text(item.date)
.font(.custom(Font.light.rawValue, size: 13.3))
.foregroundColor(Color(hex: "777777"))
.foregroundColor(Color.gray77)
}
HStack(spacing: 0) {
Text(item.content)
.font(.custom(Font.medium.rawValue, size: 12))
.foregroundColor(Color(hex: "bbbbbb"))
.foregroundColor(Color.graybb)
.fixedSize(horizontal: false, vertical: true)
.lineLimit(3)
@ -45,9 +45,10 @@ struct CreatorCommunityItemView: View {
.resizable()
.frame(width: 53.3, height: 53.3)
.cornerRadius(4.7)
.blur(radius: item.existOrdered ? 0 : 15)
} else {
Rectangle()
.foregroundColor(Color(hex: "222222").opacity(0))
.foregroundColor(Color.gray22.opacity(0))
.frame(width: 53.3, height: 53.3)
}
}
@ -60,7 +61,7 @@ struct CreatorCommunityItemView: View {
Text("\(item.likeCount)")
.font(.custom(Font.medium.rawValue, size: 11))
.foregroundColor(Color(hex: "777777"))
.foregroundColor(Color.gray77)
}
HStack(spacing: 6) {
@ -70,13 +71,13 @@ struct CreatorCommunityItemView: View {
Text("\(item.commentCount)")
.font(.custom(Font.medium.rawValue, size: 11))
.foregroundColor(Color(hex: "777777"))
.foregroundColor(Color.gray77)
}
}
}
.frame(maxWidth: .infinity, alignment: .leading)
.padding(13.3)
.background(Color(hex: "222222"))
.background(Color.gray22)
.cornerRadius(11)
}
}
@ -91,10 +92,12 @@ struct CreatorCommunityItemView_Previews: PreviewProvider {
creatorProfileUrl: "https://test-cf.sodalive.net/profile/default-profile.png",
imageUrl: "https://test-cf.sodalive.net/profile/default-profile.png",
content: "안녕하세요",
price: 10,
date: "3일전",
isCommentAvailable: false,
isAdult: false,
isLike: false,
existOrdered: false,
likeCount: 10,
commentCount: 0,
firstComment: nil

View File

@ -52,4 +52,8 @@ class CreatorCommunityRepository {
func getLatestPostListFromCreatorsYouFollow() -> AnyPublisher<Response, MoyaError> {
return api.requestPublisher(.getLatestPostListFromCreatorsYouFollow)
}
func purchaseCommunityPost(postId: Int) -> AnyPublisher<Response, MoyaError> {
return api.requestPublisher(.purchaseCommunityPost(postId: postId))
}
}

View File

@ -12,10 +12,12 @@ struct GetCommunityPostListResponse: Decodable {
let creatorProfileUrl: String
let imageUrl: String?
let content: String
let price: Int
let date: String
let isCommentAvailable: Bool
let isAdult: Bool
let isLike: Bool
let existOrdered: Bool
let likeCount: Int
let commentCount: Int
let firstComment: GetCommunityPostCommentListItem?

View File

@ -38,10 +38,12 @@ struct SectionCommunityPostView_Previews: PreviewProvider {
creatorProfileUrl: "https://test-cf.sodalive.net/profile/default-profile.png",
imageUrl: "https://test-cf.sodalive.net/profile/default-profile.png",
content: "라이브 많이 들려주세요!너무 조하유 앞으로도 좋은 라이브 많이 들려주세요!너무 조하유 앞으로도 좋은 라이브 많이 들려주세요!",
price: 10,
date: "3일전",
isCommentAvailable: false,
isAdult: false,
isLike: true,
existOrdered: false,
likeCount: 10,
commentCount: 0,
firstComment: nil
@ -53,10 +55,12 @@ struct SectionCommunityPostView_Previews: PreviewProvider {
creatorProfileUrl: "https://test-cf.sodalive.net/profile/default-profile.png",
imageUrl: "https://test-cf.sodalive.net/profile/default-profile.png",
content: "너무 조하유 앞으로도 좋은 라이브 많이 들려주세요!너무 조하유 앞으로도 좋은 라이브 많이 들려주세요!너무 조하유 앞으로도 좋은 라이브 많이 들려주세요!너무 조하유 앞으로도 좋은 라이브 많이 들려주세요!너무 조하유 앞으로도 좋은 라이브 많이 들려주세요!너무 조하유 앞으로도 좋은 라이브 많이 들려주세요!너무 조하유 앞으로도 좋은 라이브 많이 들려주세요!너무 조하유 앞으로도 좋은 라이브 많이 들려주세요!너무 조하유 앞으로도 좋은 라이브 많이 들려주세요!너무 조하유 앞으로도 좋은 라이브 많이 들려주세요!너무 조하유 앞으로도 좋은 라이브 많이 들려주세요!너무 조하유 앞으로도 좋은 라이브 많이 들려주세요!",
price: 10,
date: "3일전",
isCommentAvailable: false,
isAdult: false,
isLike: true,
existOrdered: false,
likeCount: 20,
commentCount: 0,
firstComment: nil