Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions today-s-sound/Core/AppState/AppThemeManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ final class AppThemeManager: ObservableObject {
{
self.theme = theme
} else {
// 기본값은 일반 모드
theme = .normal
// 기본값은 고대비 모드
theme = .highContrast
}
}

Expand Down
18 changes: 9 additions & 9 deletions today-s-sound/Core/Auth/UserCredentialsProvider.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@ import Foundation
protocol UserCredentialsProvider {
/// 사용자 ID를 반환
func getUserId() -> String?

/// 디바이스 시크릿을 반환
func getDeviceSecret() -> String?

/// 사용자 ID와 디바이스 시크릿을 튜플로 반환
/// 둘 다 존재할 때만 반환, 없으면 nil
func getCredentials() -> (userId: String, deviceSecret: String)?
Expand All @@ -26,11 +26,11 @@ final class KeychainCredentialsProvider: UserCredentialsProvider {
func getUserId() -> String? {
Keychain.getString(for: KeychainKey.userId)
}

func getDeviceSecret() -> String? {
Keychain.getString(for: KeychainKey.deviceSecret)
}

func getCredentials() -> (userId: String, deviceSecret: String)? {
guard let userId = Keychain.getString(for: KeychainKey.userId),
let deviceSecret = Keychain.getString(for: KeychainKey.deviceSecret)
Expand All @@ -46,22 +46,22 @@ final class KeychainCredentialsProvider: UserCredentialsProvider {
final class MockCredentialsProvider: UserCredentialsProvider {
var userId: String?
var deviceSecret: String?

init(userId: String? = "test-user-id", deviceSecret: String? = "test-device-secret") {
self.userId = userId
self.deviceSecret = deviceSecret
}

func getUserId() -> String? {
userId
}

func getDeviceSecret() -> String? {
deviceSecret
}

func getCredentials() -> (userId: String, deviceSecret: String)? {
guard let userId = userId, let deviceSecret = deviceSecret else {
guard let userId, let deviceSecret else {
return nil
}
return (userId, deviceSecret)
Expand Down
14 changes: 7 additions & 7 deletions today-s-sound/Core/Error/ErrorHandler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,23 +8,23 @@
import Foundation

/// 네트워크 에러를 사용자 친화적인 메시지로 변환하는 유틸리티
struct ErrorHandler {
enum ErrorHandler {
/// NetworkError를 사용자에게 표시할 메시지로 변환
static func userFacingMessage(from error: NetworkError) -> String {
switch error {
case let .serverError(statusCode):
return "서버 오류 (상태: \(statusCode))"
"서버 오류 (상태: \(statusCode))"
case .decodingFailed:
return "응답 처리 실패"
"응답 처리 실패"
case let .requestFailed(requestError):
return "요청 실패: \(requestError.localizedDescription)"
"요청 실패: \(requestError.localizedDescription)"
case .invalidURL:
return "잘못된 URL"
"잘못된 URL"
case .unknown:
return "알 수 없는 오류"
"알 수 없는 오류"
}
}

/// 에러를 로깅하고 사용자 메시지 반환
static func handleError(_ error: NetworkError, context: String = "") -> String {
let message = userFacingMessage(from: error)
Expand Down
4 changes: 2 additions & 2 deletions today-s-sound/Core/Network/Config.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import Foundation
enum Config {
static var baseURL: String {
#if DEBUG
// return "https://www.today-sound.com"
return "http://localhost:8080" // 개발 서버
return "https://www.today-sound.com"
// return "http://localhost:8080" // 개발 서버
#else
return "https://api.todays-sound.com" // 운영 서버
#endif
Expand Down
10 changes: 5 additions & 5 deletions today-s-sound/Core/Pagination/PaginationState.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,27 +13,27 @@ final class PaginationState {
private(set) var currentPage: Int = 0
private(set) var hasMoreData: Bool = true
let pageSize: Int

init(pageSize: Int = 10) {
self.pageSize = pageSize
}

/// 다음 페이지로 이동
func moveToNextPage() {
currentPage += 1
}

/// 받은 데이터 개수를 기반으로 더 이상 데이터가 있는지 확인
func updateHasMoreData(receivedCount: Int) {
hasMoreData = receivedCount >= pageSize
}

/// 페이지네이션 상태 초기화 (새로고침 시 사용)
func reset() {
currentPage = 0
hasMoreData = true
}

/// 첫 페이지인지 확인
var isFirstPage: Bool {
currentPage == 0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ struct ScreenMainTitle: View {

var body: some View {
Text(text)
.font(.KoddiBold48)
.font(.KoddiBold56)
.foregroundColor(Color.text(theme))
.frame(maxWidth: .infinity, alignment: .center)
.multilineTextAlignment(.center)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -399,5 +399,6 @@ struct FlowLayout: Layout {
struct AddSubscriptionView_Previews: PreviewProvider {
static var previews: some View {
AddSubscriptionView()
.environmentObject(AppThemeManager())
}
}
5 changes: 3 additions & 2 deletions today-s-sound/Presentation/Features/Main/Home/HomeView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ struct HomeView: View {
Text("오늘의 소리")
.font(.KoddiBold56)
.foregroundStyle(Color.text(appTheme.theme))
.padding(.top, 60)
.padding(.bottom, 30)
.padding(.top, 120)
.padding(.bottom, 60)
.accessibilityElement() // 이 텍스트를 독립 요소로
.accessibilityLabel("오늘의 소리") // 👉 "오늘의 소리"라고 읽기
.accessibilityAddTraits(.isHeader) // 머리말(헤더)로 인식
Expand Down Expand Up @@ -112,4 +112,5 @@ struct HomeView: View {

#Preview {
HomeView()
.environmentObject(AppThemeManager())
}
96 changes: 62 additions & 34 deletions today-s-sound/Presentation/Features/Main/MainView.swift
Original file line number Diff line number Diff line change
@@ -1,63 +1,91 @@
import SwiftUI

private struct TabBarForegroundUpdater: UIViewControllerRepresentable {
let theme: AppTheme

func makeUIViewController(context: Context) -> UIViewController {
let vc = UIViewController()
vc.view.backgroundColor = .clear
return vc
}

func updateUIViewController(_ uiViewController: UIViewController, context: Context) {
guard let tabBar = uiViewController.tabBarController?.tabBar else { return }

let selectedColor = UIColor(Color.primaryGreen)

let unselectedColor: UIColor = (theme == .highContrast)
? UIColor(white: 1.0, alpha: 0.85)
: UIColor(Color.primaryGrey)

tabBar.tintColor = selectedColor
tabBar.unselectedItemTintColor = unselectedColor

tabBar.items?.forEach { item in
item.setTitleTextAttributes([.foregroundColor: unselectedColor], for: .normal)
item.setTitleTextAttributes([.foregroundColor: selectedColor], for: .selected)

item.image = item.image?.withRenderingMode(.alwaysTemplate)
item.selectedImage = item.selectedImage?.withRenderingMode(.alwaysTemplate)
}
}
}

struct MainView: View {
private enum Tab: Hashable {
case home
case feed
case notifications
case settings
case home, feed, notifications, settings
}

@State private var selectedTab: Tab = .home
@EnvironmentObject private var appTheme: AppThemeManager

var body: some View {
let theme = appTheme.theme

TabView(selection: $selectedTab) {
HomeView()
.tabItem {
VStack {
Image(systemName: "play.house.fill")
.accessibilityHidden(true) // 아이콘은 숨기고
Text("홈") // 이름만 읽히게
}
}
.tabItem { Label("홈", systemImage: "play.house.fill") }
.tag(Tab.home)

FeedView()
.tabItem {
VStack {
Image(systemName: "text.bubble.fill")
.accessibilityHidden(true)
Text("피드")
}
}
.tabItem { Label("피드", systemImage: "text.bubble.fill") }
.tag(Tab.feed)

NotificationListView()
.tabItem {
VStack {
Image(systemName: "bell.fill")
.accessibilityHidden(true)
Text("알림")
}
}
.tabItem { Label("알림", systemImage: "bell.fill") }
.tag(Tab.notifications)

SettingsView()
.tabItem {
VStack {
Image(systemName: "gearshape.fill")
.accessibilityHidden(true)
Text("관리")
}
}
.tabItem { Label("관리", systemImage: "gearshape.fill") }
.tag(Tab.settings)
}
.tint(.primaryGreen)
.tint(Color.primaryGreen)
.background(TabBarForegroundUpdater(theme: theme).frame(width: 0, height: 0))
}
}

struct MainView_Previews: PreviewProvider {
private static var normalThemeManager: AppThemeManager {
let m = AppThemeManager()
m.theme = .normal
return m
}

private static var highContrastThemeManager: AppThemeManager {
let m = AppThemeManager()
m.theme = .highContrast
return m
}

static var previews: some View {
MainView()
Group {
MainView()
.environmentObject(normalThemeManager)
.previewDisplayName("Theme: Normal")

MainView()
.environmentObject(highContrastThemeManager)
.previewDisplayName("Theme: HighContrast")
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -67,19 +67,20 @@ struct NotificationListView: View {
// 알림 없음
else if viewModel.alarms.isEmpty {
Spacer()
VStack(spacing: 16) {
VStack(spacing: 0) {
Text("새로운 알림이 없습니다")
.font(.KoddiBold20)
.foregroundColor(Color.secondaryText(appTheme.theme))
.accessibilityLabel("새로운 알림이 없습니다")
}
.padding(.top, 28)
Spacer()
}
// 알림 목록
else {
List {
ForEach(viewModel.alarms) { alarm in
AlertCardView(alarm: alarm, theme: appTheme.theme)
AlertCardView(alarm: alarm, theme: appTheme.theme)
.listRowInsets(EdgeInsets(top: 0, leading: 20, bottom: 12, trailing: 20))
.listRowSeparator(.hidden)
.listRowBackground(Color.clear)
Expand Down Expand Up @@ -119,23 +120,19 @@ struct NotificationListView: View {
Group {
// 데이터 있는 상태 - 라이트 모드
NotificationListView(viewModel: .previewData)
.environment(\.colorScheme, .light)
.previewDisplayName("알림 목록 - Light")
.environmentObject(AppThemeManager()).previewDisplayName("알림 목록 - Light")

// 데이터 있는 상태 - 다크 모드
NotificationListView(viewModel: .previewData)
.environment(\.colorScheme, .dark)
.previewDisplayName("알림 목록 - Dark")
.environmentObject(AppThemeManager()).previewDisplayName("알림 목록 - Dark")

// 빈 상태
NotificationListView(viewModel: .previewEmpty)
.environment(\.colorScheme, .light)
.previewDisplayName("알림 없음")
.environmentObject(AppThemeManager()).previewDisplayName("알림 없음")

// 에러 상태
NotificationListView(viewModel: .previewError)
.environment(\.colorScheme, .light)
.previewDisplayName("에러 상태")
.environmentObject(AppThemeManager()).previewDisplayName("에러 상태")
}
}
}
Expand Down
Loading
Loading