Skip to content

Commit

Permalink
Merge pull request #340 from GSM-MSG/339-portfolio-link-dialog
Browse files Browse the repository at this point in the history
🔀 :: [#339] 포트폴리오 공유 다이얼로그 추가
  • Loading branch information
uuuunseo authored Jun 6, 2024
2 parents 14b7b5c + 97f391a commit 3f324cd
Show file tree
Hide file tree
Showing 7 changed files with 281 additions and 6 deletions.
80 changes: 80 additions & 0 deletions Projects/Core/DesignSystem/Sources/Dialog/SMSDialog.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,22 @@ public extension View {
)
)
}

func smsDialog<DialogView: View>(
title: String,
isShowing: Binding<Bool>,
dialogActions: [SMSAlertButtonType],
@ViewBuilder dialogView: @escaping () -> DialogView = { EmptyView() }
) -> some View {
modifier(
SMSDialogModifier(
title: title,
isShowing: isShowing,
dialogActions: dialogActions,
dialogView: dialogView
)
)
}
}

struct SMSAlertModifier: ViewModifier {
Expand Down Expand Up @@ -82,3 +98,67 @@ struct SMSAlertModifier: ViewModifier {
.cornerRadius(16)
}
}

struct SMSDialogModifier<DialogView: View>: ViewModifier {
var title: String
@Binding var isShowing: Bool
var dialogActions: [SMSAlertButtonType]
var dialogView: () -> DialogView

public init(
title: String,
isShowing: Binding<Bool>,
dialogActions: [SMSAlertButtonType],
@ViewBuilder dialogView: @escaping () -> DialogView = { EmptyView() }
) {
self.title = title
_isShowing = isShowing
self.dialogActions = dialogActions
self.dialogView = dialogView
}

func body(content: Content) -> some View {
ZStack {
content

ConditionView(isShowing) {
Color.sms(.system(.black))
.opacity(0.25)
.ignoresSafeArea()

smsDialog()
.padding(40)
.transition(
.asymmetric(
insertion: AnyTransition.move(edge: .bottom),
removal: .move(edge: .bottom).combined(with: .opacity)
)
)
}
}
}

@ViewBuilder
func smsDialog() -> some View {
VStack(alignment: .leading, spacing: 24) {
Text(title)
.smsFont(.title2, color: .system(.black))

dialogView()
.padding(.bottom, 13)

HStack(spacing: 8) {
ForEach(dialogActions, id: \.id) { button in
CTAButton(
text: button.text,
style: button.style,
action: button.action
)
}
}
}
.padding(24)
.background(Color.sms(.system(.white)))
.cornerRadius(16)
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import Foundation
import UIKit
import UserDomainInterface
import StudentDomainInterface
import EventLimiter
Expand Down Expand Up @@ -40,6 +41,30 @@ final class StudentDetailIntent: StudentDetailIntentProtocol {
}
}
}

func effectiveDateDialogIsRequired() {
model?.updateIsPresentedEffectiveDateDialog(isPresented: true)
}

func effectiveDateDialogDismissed() {
model?.updateIsPresentedEffectiveDateDialog(isPresented: false)
}

func effectiveDateSelect(effectiveDate: EffectiveDateType) {
model?.insertEffectiveDateType(effectiveDateType: effectiveDate)
}

func pasteLinkDialogIsRequired() {
model?.updateIsPresentedPasteLinkDialog(isPresented: true)
}

func pasteLinkDialogDismissed() {
model?.updateIsPresentedPasteLinkDialog(isPresented: false)
}

func pastePortfolioLink(portfolioLink: String) {
UIPasteboard.general.string = portfolioLink
}
}

private extension UserRoleType {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,10 @@ import Foundation

protocol StudentDetailIntentProtocol {
func onAppear()
func effectiveDateDialogIsRequired()
func effectiveDateDialogDismissed()
func effectiveDateSelect(effectiveDate: EffectiveDateType)
func pasteLinkDialogIsRequired()
func pasteLinkDialogDismissed()
func pastePortfolioLink(portfolioLink: String)
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ final class StudentDetailModel: ObservableObject, StudentDetailStateProtocol {
}
@Published var _studentDetailEntity: StudentDetailEntity?
@Published var isLoading: Bool = false
@Published var isPresentedEffectiveDateDialog: Bool = false
@Published var effectiveDateType: EffectiveDateType = .five
@Published var isPresentedPasteLinkDialog: Bool = false
@Published var portfolioLink: String = ""
}
// swiftlint: enable identifier_name

Expand All @@ -40,4 +44,16 @@ extension StudentDetailModel: StudentDetailActionProtocol {
func updateIsLoading(isLoading: Bool) {
self.isLoading = isLoading
}

func updateIsPresentedEffectiveDateDialog(isPresented: Bool) {
self.isPresentedEffectiveDateDialog = isPresented
}

func insertEffectiveDateType(effectiveDateType: EffectiveDateType) {
self.effectiveDateType = effectiveDateType
}

func updateIsPresentedPasteLinkDialog(isPresented: Bool) {
self.isPresentedPasteLinkDialog = isPresented
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,30 @@ import Foundation
import UserDomainInterface
import StudentDomainInterface

enum EffectiveDateType: Int, CaseIterable {
case five = 5
case ten = 10
case fifteen = 15
case twenty = 20
case twentyFive = 25
case thirty = 30
}

protocol StudentDetailStateProtocol {
var userRole: UserRoleType { get }
var studentDetailEntity: StudentDetailEntity? { get }
var isLoading: Bool { get }
var isPresentedEffectiveDateDialog: Bool { get }
var effectiveDateType: EffectiveDateType { get }
var isPresentedPasteLinkDialog: Bool { get }
var portfolioLink: String { get }
}

protocol StudentDetailActionProtocol: AnyObject {
func updateUserRole(role: UserRoleType)
func updateStudentDetailEntity(entity: StudentDetailEntity)
func updateIsLoading(isLoading: Bool)
func updateIsPresentedEffectiveDateDialog(isPresented: Bool)
func insertEffectiveDateType(effectiveDateType: EffectiveDateType)
func updateIsPresentedPasteLinkDialog(isPresented: Bool)
}
Original file line number Diff line number Diff line change
Expand Up @@ -62,12 +62,19 @@ struct StudentDetailView: View {
}

if let detailInfoByTeacher = studentDetail?.detailInfoByTeacher {
CTAButton(text: "포트폴리오") {
guard
let portfolioURLString = detailInfoByTeacher.portfolioURL,
let portfolioURL = URL(string: portfolioURLString)
else { return }
openURL(portfolioURL)
HStack(spacing: 8) {
CTAButton(text: "포트폴리오") {
guard
let portfolioURLString = studentDetail?.detailInfoByTeacher?.portfolioURL,
let portfolioURL = URL(string: portfolioURLString)
else { return }
openURL(portfolioURL)
}

CTAButton(text: "공유", style: .outline) {
intent.effectiveDateDialogIsRequired()
}
.frame(maxWidth: 104)
}
.padding(.horizontal, 20)
.padding(.bottom, safeAreaInsets.bottom + 16)
Expand All @@ -88,6 +95,47 @@ struct StudentDetailView: View {
dismiss()
}
}
.smsDialog(
title: "만료기간 선택",
isShowing: Binding(
get: { state.isPresentedEffectiveDateDialog },
set: { _ in intent.effectiveDateDialogDismissed() }
),
dialogActions: [
.init(
text: "취소",
style: .outline,
action: { intent.effectiveDateDialogDismissed() }
),
.init(
text: "링크생성",
style: .default,
action: {
#warning("링크 생성 이벤트 추가")
intent.pasteLinkDialogIsRequired()
intent.effectiveDateDialogDismissed()
}
)
]
) {
effectiveDateView()
}
.smsDialog(
title: "만료기간 선택",
isShowing: Binding(
get: { state.isPresentedPasteLinkDialog },
set: { _ in intent.pasteLinkDialogDismissed() }
),
dialogActions: [
.init(
text: "확인",
style: .default,
action: { intent.pasteLinkDialogDismissed() }
)
]
) {
copyLinkView()
}
.statusBarHidden(true)
.animation(.easeIn, value: state.studentDetailEntity)
.frame(maxWidth: .infinity, maxHeight: .infinity)
Expand Down Expand Up @@ -308,4 +356,42 @@ struct StudentDetailView: View {
.frame(maxWidth: .infinity, alignment: .leading)
}
}

@ViewBuilder
func effectiveDateView() -> some View {
EffectiveDateRadioGroupView(
data: EffectiveDateType.allCases,
id: \.self,
isSelected: {
state.effectiveDateType == $0
},
selectAction: {
intent.effectiveDateSelect(effectiveDate: $0)
}
) { effectiveDate in
SMSText("\(effectiveDate.rawValue)", font: .body1)
}
}

@ViewBuilder
func copyLinkView() -> some View {
HStack(spacing: 8) {
SMSText(state.portfolioLink, font: .body1)

Spacer()

Button {
intent.pastePortfolioLink(portfolioLink: state.portfolioLink)
} label: {
Text("복사")
.padding(.horizontal, 20)
.padding(.vertical, 5)
.smsFont(.body1, color: .primary(.p2))
.overlay {
RoundedRectangle(cornerRadius: 56)
.strokeBorder(Color.sms(.primary(.p2)), lineWidth: 1)
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import SwiftUI
import DesignSystem

struct EffectiveDateRadioGroupView<T, ID, Content>: View where ID: Hashable, Content: View {
let columns = [GridItem(.flexible()), GridItem(.flexible())]
let data: [T]
let id: KeyPath<T, ID>
let isSelected: (T) -> Bool
let selectAction: (T) -> Void
let content: (T) -> Content

init(
data: [T],
id: KeyPath<T, ID>,
isSelected: @escaping (T) -> Bool,
selectAction: @escaping (T) -> Void,
@ViewBuilder content: @escaping (T) -> Content
) {
self.data = data
self.id = id
self.isSelected = isSelected
self.selectAction = selectAction
self.content = content
}

var body: some View {
VStack(alignment: .leading) {
LazyVGrid(columns: columns) {
ForEach(data, id: id) { index in
HStack(spacing: 8) {
SMSRadioButton(
isSelected: Binding(
get: { isSelected(index) },
set: { _ in selectAction(index) }
)
)

content(index)

Spacer()
}
}
}
}
}
}

0 comments on commit 3f324cd

Please sign in to comment.