feat(mypage): 최신 공지 배열과 제목 롤링을 적용한다

This commit is contained in:
Yu Sung
2026-10-28 17:15:38 +09:00
parent 8c08861b34
commit 42bdfb8779
9 changed files with 462 additions and 43 deletions
+80 -26
View File
@@ -56,9 +56,9 @@ struct MyPageView: View {
isShowButton: !token.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
)
if let notice = viewModel.latestNotice {
if !viewModel.latestNotices.isEmpty {
// Update Banner
UpdateBannerView(item: notice)
UpdateBannerView(items: viewModel.latestNotices)
}
ScrollView(.vertical, showsIndicators: false) {
@@ -170,7 +170,7 @@ struct MyPageView: View {
viewModel.getMypage()
recentContentViewModel.fetchRecentContents()
}
viewModel.getLatestNotice()
viewModel.getLatestNotices()
}
}
}
@@ -217,34 +217,88 @@ struct MyPageHeaderView: View {
// MARK: - Update Banner View
struct UpdateBannerView: View {
let item: NoticeItem
private static let foregroundColor = Color(hex: "B0BEC5")
private static let rollInterval: TimeInterval = 5
private static let transitionDuration: TimeInterval = 0.35
let items: [NoticeItem]
@Environment(\.scenePhase) private var scenePhase
@Environment(\.accessibilityReduceMotion) private var reduceMotion
@State private var roller = MyPageNoticeRoller()
@State private var rollTimer: Timer?
@State private var isVisible = false
var body: some View {
HStack {
Text("\(item.title)")
.appFont(size: 16)
.foregroundColor(Color(hex: "B0BEC5"))
Spacer()
HStack(spacing: 2) {
Text(I18n.MyPage.Main.detail)
.appFont(size: 16)
.foregroundColor(Color(hex: "B0BEC5"))
Image(systemName: "chevron.right")
.foregroundColor(Color(hex: "B0BEC5"))
.frame(width: 24, height: 24)
ZStack {
if let item = roller.current {
Button {
AppState.shared.setAppStep(step: .noticeDetail(notice: item))
} label: {
HStack(spacing: SodaSpacing.s8) {
ZStack {
Text(item.title)
.appFont(.body3)
.lineLimit(1)
.truncationMode(.tail)
.frame(maxWidth: .infinity, alignment: .leading)
.id(roller.index)
.transition(reduceMotion ? .identity : .asymmetric(
insertion: .move(edge: .bottom),
removal: .move(edge: .top)
))
}
.clipped()
Image(systemName: "chevron.right")
.frame(width: SodaSpacing.s24, height: SodaSpacing.s24)
.accessibilityHidden(true)
}
.foregroundColor(Self.foregroundColor)
.padding(.horizontal, SodaSpacing.s24)
.padding(.vertical, SodaSpacing.s6)
.contentShape(Rectangle())
}
.buttonStyle(.plain)
}
}
.padding(.horizontal, 24)
.padding(.vertical, 6)
.clipped()
.background(Color.black)
.contentShape(Rectangle())
.onTapGesture {
AppState.shared.setAppStep(step: .noticeDetail(notice: item))
.onAppear {
isVisible = true
roller.update(items)
restartRollTimer(scenePhase: scenePhase, reduceMotion: reduceMotion)
}
.onDisappear {
isVisible = false
stopRollTimer()
}
.onChange(of: items) { items in
roller.update(items)
restartRollTimer(scenePhase: scenePhase, reduceMotion: reduceMotion)
}
.onChange(of: scenePhase) { scenePhase in
restartRollTimer(scenePhase: scenePhase, reduceMotion: reduceMotion)
}
.onChange(of: reduceMotion) { reduceMotion in
restartRollTimer(scenePhase: scenePhase, reduceMotion: reduceMotion)
}
}
private func restartRollTimer(scenePhase: ScenePhase, reduceMotion: Bool) {
stopRollTimer()
guard roller.shouldRoll(isVisible: isVisible, isActive: scenePhase == .active) else { return }
rollTimer = Timer.scheduledTimer(withTimeInterval: Self.rollInterval, repeats: true) { _ in
withAnimation(reduceMotion ? nil : .easeOut(duration: Self.transitionDuration)) {
roller.advance()
}
}
}
private func stopRollTimer() {
rollTimer?.invalidate()
rollTimer = nil
}
}