feat(gift): 선물 발송 표시를 보정한다
This commit is contained in:
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "ic_send_gift.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 815 B |
@@ -197,7 +197,7 @@ enum AppStep {
|
||||
|
||||
case seriesMain
|
||||
|
||||
case giftSend(recipientMemberId: Int, recipientCreatorNickname: String)
|
||||
case giftSend(recipientMemberId: Int, recipientCreatorNickname: String, recipientCreatorProfileImageUrl: String)
|
||||
|
||||
case giftBoxList
|
||||
|
||||
|
||||
@@ -374,8 +374,8 @@ struct AppStepLayerView: View {
|
||||
case .seriesMain:
|
||||
SeriesMainView()
|
||||
|
||||
case .giftSend(let recipientMemberId, let recipientCreatorNickname):
|
||||
GiftSendView(recipientMemberId: recipientMemberId, recipientCreatorNickname: recipientCreatorNickname)
|
||||
case .giftSend(let recipientMemberId, let recipientCreatorNickname, let recipientCreatorProfileImageUrl):
|
||||
GiftSendView(recipientMemberId: recipientMemberId, recipientCreatorNickname: recipientCreatorNickname, recipientCreatorProfileImageUrl: recipientCreatorProfileImageUrl)
|
||||
|
||||
case .giftBoxList:
|
||||
GiftBoxListView()
|
||||
|
||||
@@ -57,6 +57,7 @@ struct CreatorChannelHeaderSection: View {
|
||||
CreatorChannelTalkActionSection(
|
||||
recipientMemberId: creator.creatorId,
|
||||
recipientCreatorNickname: creator.nickname,
|
||||
recipientCreatorProfileImageUrl: creator.profileImageUrl,
|
||||
isAiChatAvailable: creator.isAiChatAvailable,
|
||||
isDmAvailable: creator.isDmAvailable,
|
||||
isOwnCreatorChannel: isOwnCreatorChannel,
|
||||
|
||||
+13
-4
@@ -3,6 +3,7 @@ import SwiftUI
|
||||
struct CreatorChannelTalkActionSection: View {
|
||||
let recipientMemberId: Int
|
||||
let recipientCreatorNickname: String
|
||||
let recipientCreatorProfileImageUrl: String
|
||||
let isAiChatAvailable: Bool
|
||||
let isDmAvailable: Bool
|
||||
let isOwnCreatorChannel: Bool
|
||||
@@ -66,16 +67,24 @@ struct CreatorChannelTalkActionSection: View {
|
||||
Button(action: {
|
||||
AppState.shared.setAppStep(step: .giftSend(
|
||||
recipientMemberId: recipientMemberId,
|
||||
recipientCreatorNickname: recipientCreatorNickname
|
||||
recipientCreatorNickname: recipientCreatorNickname,
|
||||
recipientCreatorProfileImageUrl: recipientCreatorProfileImageUrl
|
||||
))
|
||||
}) {
|
||||
HStack(spacing: SodaSpacing.s6) {
|
||||
Image("ic_send_gift")
|
||||
.resizable()
|
||||
.frame(width: 20, height: 20)
|
||||
.accessibilityHidden(true)
|
||||
|
||||
Text(I18n.CreatorChannelHome.sendGift)
|
||||
.appFont(size: 16, weight: .medium)
|
||||
.foregroundColor(.white)
|
||||
.foregroundColor(.black)
|
||||
}
|
||||
.padding(SodaSpacing.s12)
|
||||
.overlay(
|
||||
.background(
|
||||
Capsule()
|
||||
.stroke(Color.white.opacity(0.3), lineWidth: 1)
|
||||
.fill(Color.white)
|
||||
)
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
|
||||
@@ -3,6 +3,7 @@ import SwiftUI
|
||||
struct GiftAgreementRow: View {
|
||||
let title: String
|
||||
@Binding var isAgreed: Bool
|
||||
var policyURL: URL? = GiftConstants.termsURL
|
||||
|
||||
var body: some View {
|
||||
HStack(alignment: .top, spacing: SodaSpacing.s4) {
|
||||
@@ -25,7 +26,7 @@ struct GiftAgreementRow: View {
|
||||
.buttonStyle(.plain)
|
||||
.accessibilityValue(isAgreed ? "동의함" : "동의 안 함")
|
||||
|
||||
if let url = GiftConstants.termsURL {
|
||||
if let url = policyURL {
|
||||
Link(destination: url) {
|
||||
Text("(필수)")
|
||||
.underline()
|
||||
|
||||
@@ -2,6 +2,7 @@ import Foundation
|
||||
|
||||
enum GiftConstants {
|
||||
static let termsURL = URL(string: "https://app.notion.com/p/3eb27b359673802b937ae741ab7b4ad3")
|
||||
static let damagePolicyURL = URL(string: "https://app.notion.com/p/3f227b3596738018ba5dea2e5205293b")
|
||||
static let addressSearchAvailable = false
|
||||
static let inquiryStep: AppStep = .serviceCenter
|
||||
static let feedbackStep: AppStep = .serviceCenter
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import SwiftUI
|
||||
|
||||
struct GiftSendView: View {
|
||||
private let recipientCreatorProfileImageUrl: String
|
||||
@StateObject private var viewModel: GiftSendViewModel
|
||||
@State private var hasAppeared = false
|
||||
|
||||
init(recipientMemberId: Int, recipientCreatorNickname: String) {
|
||||
init(recipientMemberId: Int, recipientCreatorNickname: String, recipientCreatorProfileImageUrl: String) {
|
||||
self.recipientCreatorProfileImageUrl = recipientCreatorProfileImageUrl
|
||||
_viewModel = StateObject(wrappedValue: GiftSendViewModel(
|
||||
recipientMemberId: recipientMemberId,
|
||||
recipientCreatorNickname: recipientCreatorNickname,
|
||||
@@ -12,7 +14,7 @@ struct GiftSendView: View {
|
||||
AppState.shared.replaceCurrentAppStep(
|
||||
step: .giftDetail(applicationNo: applicationNo),
|
||||
ifCurrentStep: { step in
|
||||
guard case .giftSend(let memberId, let nickname) = step else { return false }
|
||||
guard case .giftSend(let memberId, let nickname, _) = step else { return false }
|
||||
return memberId == recipientMemberId && nickname == recipientCreatorNickname
|
||||
}
|
||||
)
|
||||
@@ -77,7 +79,8 @@ struct GiftSendView: View {
|
||||
VStack(alignment: .leading, spacing: SodaSpacing.s14) {
|
||||
sectionTitle("받는 크리에이터")
|
||||
HStack(spacing: SodaSpacing.s12) {
|
||||
Circle().fill(Color.gray800).frame(width: 38, height: 38)
|
||||
DownsampledKFImage(url: URL(string: recipientCreatorProfileImageUrl), size: CGSize(width: 38, height: 38))
|
||||
.clipShape(Circle())
|
||||
.accessibilityHidden(true)
|
||||
Text(viewModel.recipientCreatorNickname)
|
||||
.appFont(size: 16, weight: .bold)
|
||||
@@ -155,7 +158,8 @@ struct GiftSendView: View {
|
||||
}
|
||||
.disabled(viewModel.categories.isEmpty)
|
||||
if viewModel.selectedCategory?.requiresDamageWaiver == true {
|
||||
GiftAgreementRow(title: "파손 및 분실 면책 사항에 동의합니다.", isAgreed: $viewModel.damageWaiverAgreed)
|
||||
GiftAgreementRow(title: "파손 및 분실 면책 사항에 동의합니다.", isAgreed: $viewModel.damageWaiverAgreed,
|
||||
policyURL: GiftConstants.damagePolicyURL)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
ruby - "$root" "${1:-all}" <<'RUBY'
|
||||
root, mode = ARGV
|
||||
abort 'Usage: check-gift-phase11.sh [profile|policy|button]' unless %w[all profile policy button].include?(mode)
|
||||
source_root = File.join(root, 'SodaLive/Sources')
|
||||
read = lambda do |path|
|
||||
File.read(File.join(source_root, path))
|
||||
.gsub(%r{"(?:\\.|[^"\\])*"|//[^\n]*|/\*.*?\*/}m) { |token| token.start_with?('"') ? token : '' }
|
||||
.gsub(/\s+/, ' ').gsub(/\s*([(),:])\s*/, '\1').strip
|
||||
end
|
||||
check = lambda do |condition, message|
|
||||
abort "RED gift-phase11 #{mode}: #{message}" unless condition
|
||||
end
|
||||
send_view = read.call('V2/Gift/Send/GiftSendView.swift')
|
||||
|
||||
if %w[all profile].include?(mode)
|
||||
mode = 'profile'
|
||||
step = read.call('App/AppStep.swift')
|
||||
header = read.call('V2/CreatorChannel/Components/CreatorChannelHeaderSection.swift')
|
||||
action = read.call('V2/CreatorChannel/Home/Components/CreatorChannelTalkActionSection.swift')
|
||||
content = read.call('ContentView.swift')
|
||||
check.call(step.include?('case giftSend(recipientMemberId:Int,recipientCreatorNickname:String,recipientCreatorProfileImageUrl:String)'), 'AppStep.giftSend must carry recipientCreatorProfileImageUrl')
|
||||
check.call(header.match?(/CreatorChannelTalkActionSection\(recipientMemberId:creator.creatorId,recipientCreatorNickname:creator.nickname,recipientCreatorProfileImageUrl:creator.profileImageUrl,/), 'header must forward creator.profileImageUrl')
|
||||
check.call(action.include?('let recipientCreatorProfileImageUrl:String') && action.include?('.giftSend(recipientMemberId:recipientMemberId,recipientCreatorNickname:recipientCreatorNickname,recipientCreatorProfileImageUrl:recipientCreatorProfileImageUrl)'), 'action must forward profile URL to AppStep')
|
||||
check.call(content.include?('case .giftSend(let recipientMemberId,let recipientCreatorNickname,let recipientCreatorProfileImageUrl):') && content.include?('GiftSendView(recipientMemberId:recipientMemberId,recipientCreatorNickname:recipientCreatorNickname,recipientCreatorProfileImageUrl:recipientCreatorProfileImageUrl)'), 'ContentView must forward profile URL to GiftSendView')
|
||||
check.call(send_view.include?('let recipientCreatorProfileImageUrl:String') && send_view.include?('init(recipientMemberId:Int,recipientCreatorNickname:String,recipientCreatorProfileImageUrl:String)') && send_view.include?('self.recipientCreatorProfileImageUrl = recipientCreatorProfileImageUrl'), 'GiftSendView must retain display-only profile URL')
|
||||
check.call(send_view.include?('DownsampledKFImage(url:URL(string:recipientCreatorProfileImageUrl),size:CGSize(width:38,height:38)).clipShape(Circle()).accessibilityHidden(true)'), 'recipient must show circular accessible-hidden 38pt profile image')
|
||||
check.call(send_view.include?('guard case .giftSend(let memberId,let nickname,_)= step else { return false } return memberId == recipientMemberId && nickname == recipientCreatorNickname'), 'replacement guard must compare memberId/nickname and ignore only image')
|
||||
model_init = send_view.split('GiftSendViewModel(', 2).last.split('onCreated:', 2).first
|
||||
check.call(!model_init.include?('recipientCreatorProfileImageUrl'), 'profile URL must not enter ViewModel initializer')
|
||||
Dir.glob(File.join(source_root, '**/*{ViewModel,Api,Repository,Request}*.swift')).each do |path|
|
||||
check.call(!File.read(path).include?('recipientCreatorProfileImageUrl'), 'profile URL must remain outside ViewModel/API/request layers')
|
||||
end
|
||||
puts 'PASS gift-phase11 profile: creator image routing, 38pt display, guarded replacement, display-only boundary'
|
||||
end
|
||||
|
||||
if %w[all policy].include?(ARGV[1])
|
||||
mode = 'policy'
|
||||
constants = read.call('V2/Gift/Constants/GiftConstants.swift')
|
||||
agreement = read.call('V2/Gift/Components/GiftAgreementRow.swift')
|
||||
check.call(constants.include?('static let damagePolicyURL = URL(string:"https://app.notion.com/p/3f227b3596738018ba5dea2e5205293b")'), 'damagePolicyURL must use the approved damage-waiver URL')
|
||||
check.call(constants.include?('static let termsURL = URL(string:"https://app.notion.com/p/3eb27b359673802b937ae741ab7b4ad3")'), 'existing terms URL must remain unchanged')
|
||||
check.call(agreement.include?('var policyURL:URL? = GiftConstants.termsURL') && agreement.include?('if let url = policyURL { Link(destination:url)'), 'agreement must default to termsURL and link its policyURL')
|
||||
rows = Dir.glob(File.join(source_root, 'V2/Gift/**/*.swift')).flat_map do |path|
|
||||
read.call(path.delete_prefix(source_root + '/')).scan(/GiftAgreementRow\(([^)]*)\)/).flatten
|
||||
end
|
||||
damage_rows, other_rows = rows.partition { |row| row.include?('isAgreed:$viewModel.damageWaiverAgreed') }
|
||||
check.call(damage_rows.length == 1 && damage_rows.first.end_with?('policyURL:GiftConstants.damagePolicyURL'), 'only damage waiver must select damagePolicyURL')
|
||||
check.call(other_rows.length == 4 && other_rows.none? { |row| row.include?('policyURL:') }, 'existing sender/recipient agreements must keep default terms URL')
|
||||
puts 'PASS gift-phase11 policy: isolated damage-waiver URL, unchanged default agreement links'
|
||||
end
|
||||
|
||||
if %w[all button].include?(ARGV[1])
|
||||
mode = 'button'
|
||||
action = read.call('V2/CreatorChannel/Home/Components/CreatorChannelTalkActionSection.swift')
|
||||
predicate = action[/private var shouldShowGiftButton:Bool \{[^}]*\}/]
|
||||
check.call(predicate == 'private var shouldShowGiftButton:Bool { let normalizedCountryCode = UserDefaults .string(forKey:.countryCode).trimmingCharacters(in:.whitespacesAndNewlines).uppercased()let isKoreanCountry = normalizedCountryCode.isEmpty || normalizedCountryCode == "KR" return isOwnCreatorChannel == false && isDmAvailable && isKoreanCountry && LanguageHeaderProvider.current == "ko" }', 'gift button visibility predicate must remain unchanged')
|
||||
button = action[/if shouldShowGiftButton \{ (.*?) Spacer\(\)/, 1]
|
||||
check.call(button && button.start_with?('Button(action:{ AppState.shared.setAppStep(step:.giftSend(recipientMemberId:recipientMemberId,recipientCreatorNickname:recipientCreatorNickname,recipientCreatorProfileImageUrl:recipientCreatorProfileImageUrl))}){ '), 'gift button action must preserve all three recipient values')
|
||||
label = button.split('}){ ', 2).last
|
||||
check.call(label.start_with?('HStack(spacing:SodaSpacing.s6){ Image("ic_send_gift").resizable().frame(width:20,height:20).accessibilityHidden(true)Text(I18n.CreatorChannelHome.sendGift)'), 'gift button must place the accessible-hidden 20pt icon before text in an s6 HStack')
|
||||
check.call(label == 'HStack(spacing:SodaSpacing.s6){ Image("ic_send_gift").resizable().frame(width:20,height:20).accessibilityHidden(true)Text(I18n.CreatorChannelHome.sendGift).appFont(size:16,weight:.medium).foregroundColor(.black)} .padding(SodaSpacing.s12).background(Capsule().fill(Color.white))} .buttonStyle(.plain)}', 'gift button must preserve font/padding and use black text on a white Capsule')
|
||||
puts 'PASS gift-phase11 button: icon-before-text, 20pt hidden icon, black text, white Capsule, unchanged visibility/action'
|
||||
end
|
||||
RUBY
|
||||
Reference in New Issue
Block a user