From 7d09305f05cadc38922995c7db33f7a7fc0deda7 Mon Sep 17 00:00:00 2001 From: chaemin2001 Date: Mon, 16 Sep 2024 16:08:05 +0900 Subject: [PATCH 1/6] Add Theme Market Web View --- SNUTT-2022/SNUTT.xcodeproj/project.pbxproj | 4 ++++ SNUTT-2022/SNUTT/Info.plist | 2 ++ .../Repositories/NetworkConfiguration.swift | 3 ++- .../Components/WebViews/ThemeMarketView.swift | 21 +++++++++++++++++++ .../Components/WebViews/WebViewType.swift | 5 +++++ .../Views/Scenes/Settings/SettingScene.swift | 3 +++ 6 files changed, 37 insertions(+), 1 deletion(-) create mode 100644 SNUTT-2022/SNUTT/Views/Components/WebViews/ThemeMarketView.swift diff --git a/SNUTT-2022/SNUTT.xcodeproj/project.pbxproj b/SNUTT-2022/SNUTT.xcodeproj/project.pbxproj index a5bff4267..eeedbb9ef 100644 --- a/SNUTT-2022/SNUTT.xcodeproj/project.pbxproj +++ b/SNUTT-2022/SNUTT.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 73194F0F2C98086C00883EA5 /* ThemeMarketView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 73194F0E2C98086C00883EA5 /* ThemeMarketView.swift */; }; 731D9FFD297BC5060027BA25 /* Bookmark.swift in Sources */ = {isa = PBXBuildFile; fileRef = 731D9FFC297BC5060027BA25 /* Bookmark.swift */; }; 731DA001297BC54B0027BA25 /* BookmarkDto.swift in Sources */ = {isa = PBXBuildFile; fileRef = 731DA000297BC54B0027BA25 /* BookmarkDto.swift */; }; 731DA003297BC5740027BA25 /* BookmarkRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 731DA002297BC5740027BA25 /* BookmarkRouter.swift */; }; @@ -340,6 +341,7 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 73194F0E2C98086C00883EA5 /* ThemeMarketView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeMarketView.swift; sourceTree = ""; }; 731D9FFC297BC5060027BA25 /* Bookmark.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bookmark.swift; sourceTree = ""; }; 731DA000297BC54B0027BA25 /* BookmarkDto.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarkDto.swift; sourceTree = ""; }; 731DA002297BC5740027BA25 /* BookmarkRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarkRouter.swift; sourceTree = ""; }; @@ -699,6 +701,7 @@ B8F40EAE28980D990021A2A9 /* DeveloperInfoView.swift */, B8F40EB028980DE00021A2A9 /* TermsOfServiceView.swift */, B8F40EB228980DFF0021A2A9 /* PrivacyPolicyView.swift */, + 73194F0E2C98086C00883EA5 /* ThemeMarketView.swift */, ); path = WebViews; sourceTree = ""; @@ -1583,6 +1586,7 @@ B88D16F328ABC5DD00E2D652 /* String+Ext.swift in Sources */, BEDE34D62879A7B800525014 /* DIContainer.swift in Sources */, BE9413D228C2458A00171060 /* DateFormatter+Parse.swift in Sources */, + 73194F0F2C98086C00883EA5 /* ThemeMarketView.swift in Sources */, BE9413C328C21D1000171060 /* NotificationRepository.swift in Sources */, B87B315E28D5A70F005C170B /* SearchState.swift in Sources */, BE060BD728DF43AC00A2F1B9 /* AuthService.swift in Sources */, diff --git a/SNUTT-2022/SNUTT/Info.plist b/SNUTT-2022/SNUTT/Info.plist index cff22e741..bfb83c68e 100644 --- a/SNUTT-2022/SNUTT/Info.plist +++ b/SNUTT-2022/SNUTT/Info.plist @@ -61,6 +61,8 @@ SNUEV_WEB_URL https://${SNUEV_WEB_URL} + THEME_WEB_URL + https://${THEME_WEB_URL} NSPhotoLibraryUsageDescription Save Screenshots to Photo Library diff --git a/SNUTT-2022/SNUTT/Repositories/NetworkConfiguration.swift b/SNUTT-2022/SNUTT/Repositories/NetworkConfiguration.swift index d215e0f99..6df0a4ac1 100644 --- a/SNUTT-2022/SNUTT/Repositories/NetworkConfiguration.swift +++ b/SNUTT-2022/SNUTT/Repositories/NetworkConfiguration.swift @@ -12,7 +12,8 @@ struct NetworkConfiguration { static let serverBaseURL: String = Bundle.main.infoDictionary?["API_SERVER_URL"] as! String static let serverV1BaseURL: String = serverBaseURL + "/v1" static let snuevBaseURL: String = Bundle.main.infoDictionary?["SNUEV_WEB_URL"] as! String - + static let themeBaseURL: String = Bundle.main.infoDictionary?["THEME_WEB_URL"] as! String + static func getCookie(name: String, value: String) -> HTTPCookie? { return HTTPCookie(properties: [ .domain: NetworkConfiguration.snuevBaseURL.replacingOccurrences(of: "https://", with: ""), diff --git a/SNUTT-2022/SNUTT/Views/Components/WebViews/ThemeMarketView.swift b/SNUTT-2022/SNUTT/Views/Components/WebViews/ThemeMarketView.swift new file mode 100644 index 000000000..2473dabbc --- /dev/null +++ b/SNUTT-2022/SNUTT/Views/Components/WebViews/ThemeMarketView.swift @@ -0,0 +1,21 @@ +// +// ThemeMarketView.swift +// SNUTT +// +// Created by 이채민 on 9/16/24. +// + +import SwiftUI + +struct ThemeMarketView: View { + var body: some View { + SingleWebView(url: WebViewType.themeMarket.url) + .navigationTitle("테마 마켓") + } +} + +struct ThemeMarketScene_Previews: PreviewProvider { + static var previews: some View { + ThemeMarketView() + } +} diff --git a/SNUTT-2022/SNUTT/Views/Components/WebViews/WebViewType.swift b/SNUTT-2022/SNUTT/Views/Components/WebViews/WebViewType.swift index da9b63d56..72686cb1d 100644 --- a/SNUTT-2022/SNUTT/Views/Components/WebViews/WebViewType.swift +++ b/SNUTT-2022/SNUTT/Views/Components/WebViews/WebViewType.swift @@ -18,11 +18,14 @@ enum WebViewType { case privacyPolicy case review case reviewDetail(id: Int) + case themeMarket var baseURL: String { switch self { case .review, .reviewDetail: return NetworkConfiguration.snuevBaseURL + case .themeMarket: + return NetworkConfiguration.themeBaseURL default: return NetworkConfiguration.serverBaseURL } @@ -40,6 +43,8 @@ enum WebViewType { return "" case let .reviewDetail(id): return "/detail/?id=\(id)&on_back=close" + case .themeMarket: + return "/download" } } diff --git a/SNUTT-2022/SNUTT/Views/Scenes/Settings/SettingScene.swift b/SNUTT-2022/SNUTT/Views/Scenes/Settings/SettingScene.swift index 924cd77ea..81c42d46a 100644 --- a/SNUTT-2022/SNUTT/Views/Scenes/Settings/SettingScene.swift +++ b/SNUTT-2022/SNUTT/Views/Scenes/Settings/SettingScene.swift @@ -48,6 +48,9 @@ struct SettingScene: View { SettingsLinkItem(title: "빈자리 알림", isActive: $viewModel.routingState.pushToVacancy) { VacancyScene(viewModel: .init(container: viewModel.container)) } + SettingsLinkItem(title: "테마 마켓") { + ThemeMarketView() + } } header: { Text("서비스") } From f4fc2e5fee18c083d7e5b4bacbf614c9e01c1d6f Mon Sep 17 00:00:00 2001 From: chaemin2001 Date: Wed, 29 Jan 2025 21:51:29 +0900 Subject: [PATCH 2/6] Add "Downloaded" Theme Section --- .../SNUTT/AppState/States/ThemeState.swift | 2 +- SNUTT-2022/SNUTT/Models/Theme.swift | 10 ++++ .../SNUTT/Repositories/Dto/ThemeDto.swift | 18 +++++++ SNUTT-2022/SNUTT/Services/ThemeService.swift | 15 ++++++ .../SNUTT/ViewModels/MenuSheetViewModel.swift | 2 +- .../ViewModels/ThemeSettingViewModel.swift | 24 ++++++++- .../Views/Components/ThemeBottomSheet.swift | 49 +++++++++++++------ .../Scenes/Settings/ThemeSettingScene.swift | 18 ++++++- .../SNUTT/Views/Scenes/ThemeDetailScene.swift | 28 +++++++++-- 9 files changed, 141 insertions(+), 25 deletions(-) diff --git a/SNUTT-2022/SNUTT/AppState/States/ThemeState.swift b/SNUTT-2022/SNUTT/AppState/States/ThemeState.swift index c0194d5a0..bff260465 100644 --- a/SNUTT-2022/SNUTT/AppState/States/ThemeState.swift +++ b/SNUTT-2022/SNUTT/AppState/States/ThemeState.swift @@ -6,7 +6,6 @@ // import Foundation -import SwiftUI class ThemeState { @Published var themeList: [Theme] = [] @@ -16,4 +15,5 @@ class ThemeState { @Published var isNewThemeSheetOpen = false @Published var isBasicThemeSheetOpen = false @Published var isCustomThemeSheetOpen = false + @Published var isDownloadedThemeSheetOpen = false } diff --git a/SNUTT-2022/SNUTT/Models/Theme.swift b/SNUTT-2022/SNUTT/Models/Theme.swift index 83dcfdbf5..58a1a40b4 100644 --- a/SNUTT-2022/SNUTT/Models/Theme.swift +++ b/SNUTT-2022/SNUTT/Models/Theme.swift @@ -20,6 +20,14 @@ struct Theme: Equatable, Identifiable { /// 사용자 생성 커스텀 테마 var isCustom: Bool + + var status: ThemeStatus? +} + +enum ThemeStatus: String, Codable { + case basic = "BASIC" + case downloaded = "DOWNLOADED" + case published = "PUBLISHED" } extension Theme { @@ -29,6 +37,7 @@ extension Theme { name = dto.name colors = dto.colors?.map { .init(fg: Color(hex: $0.fg), bg: Color(hex: $0.bg)) } ?? [] isCustom = dto.isCustom + status = ThemeStatus(rawValue: dto.status) } init(rawValue: Int) { @@ -37,5 +46,6 @@ extension Theme { name = theme?.name ?? "" colors = theme?.getLectureColorList() ?? [] isCustom = false + status = .basic } } diff --git a/SNUTT-2022/SNUTT/Repositories/Dto/ThemeDto.swift b/SNUTT-2022/SNUTT/Repositories/Dto/ThemeDto.swift index 247b30c8a..d47ee6155 100644 --- a/SNUTT-2022/SNUTT/Repositories/Dto/ThemeDto.swift +++ b/SNUTT-2022/SNUTT/Repositories/Dto/ThemeDto.swift @@ -14,13 +14,28 @@ struct ThemeDto: Codable { let colors: [ThemeColorDto]? let isDefault: Bool let isCustom: Bool + let origin: ThemeOriginDto? + let status: String + let publishInfo: ThemePublishInfoDto? } + struct ThemeColorDto: Codable { let bg: String let fg: String } +struct ThemeOriginDto: Codable { + let originId: String + let authorId: String +} + +struct ThemePublishInfoDto: Codable { + let publishName: String + let authorName: String + let downloads: Int +} + extension ThemeColorDto { init(from model: LectureColor) { bg = model.bg.toHex() @@ -36,5 +51,8 @@ extension ThemeDto { colors = model.colors.map { ThemeColorDto(from: $0) } isDefault = false isCustom = model.isCustom + origin = nil + status = model.status?.rawValue ?? "" + publishInfo = nil } } diff --git a/SNUTT-2022/SNUTT/Services/ThemeService.swift b/SNUTT-2022/SNUTT/Services/ThemeService.swift index 10c087c00..04d74e80e 100644 --- a/SNUTT-2022/SNUTT/Services/ThemeService.swift +++ b/SNUTT-2022/SNUTT/Services/ThemeService.swift @@ -18,6 +18,8 @@ protocol ThemeServiceProtocol: Sendable { func closeBasicThemeSheet() func openCustomThemeSheet(for theme: Theme) func closeCustomThemeSheet() + func openDownloadedThemeSheet(for theme: Theme) + func closeDownloadedThemeSheet() func getThemeList() async throws func addTheme(theme: Theme, apply: Bool) async throws @@ -75,6 +77,17 @@ struct ThemeService: ThemeServiceProtocol { appState.theme.isCustomThemeSheetOpen = false appState.theme.bottomSheetTarget = nil } + + func openDownloadedThemeSheet(for _: Theme) { + appState.theme.isDownloadedThemeSheetOpen = true + appState.theme.isBottomSheetOpen = false + } + + func closeDownloadedThemeSheet() { + appState.timetable.current?.selectedTheme = nil + appState.theme.isDownloadedThemeSheetOpen = false + appState.theme.bottomSheetTarget = nil + } func getThemeList() async throws { let dtos = try await themeRepository.getThemeList() @@ -137,6 +150,8 @@ struct FakeThemeService: ThemeServiceProtocol { func closeBasicThemeSheet() {} func openCustomThemeSheet(for _: Theme) {} func closeCustomThemeSheet() {} + func openDownloadedThemeSheet(for _: Theme) {} + func closeDownloadedThemeSheet() {} func getThemeList() async throws {} func addTheme(theme _: Theme, apply _: Bool) async throws {} diff --git a/SNUTT-2022/SNUTT/ViewModels/MenuSheetViewModel.swift b/SNUTT-2022/SNUTT/ViewModels/MenuSheetViewModel.swift index ca6ffffd8..b3387d8db 100644 --- a/SNUTT-2022/SNUTT/ViewModels/MenuSheetViewModel.swift +++ b/SNUTT-2022/SNUTT/ViewModels/MenuSheetViewModel.swift @@ -144,7 +144,7 @@ class MenuSheetViewModel: BaseViewModel, ObservableObject { } var newTheme: Theme { - let theme: Theme = .init(from: .init(id: UUID().uuidString, theme: 0, name: "새 테마", colors: [ThemeColorDto(bg: STColor.cyan.toHex(), fg: Color.white.toHex())], isDefault: false, isCustom: true)) + let theme: Theme = .init(id: UUID().uuidString, name: "새 테마", colors: [LectureColor(fg: Color.white, bg: STColor.cyan)], isCustom: true) return theme } diff --git a/SNUTT-2022/SNUTT/ViewModels/ThemeSettingViewModel.swift b/SNUTT-2022/SNUTT/ViewModels/ThemeSettingViewModel.swift index 2c49180f6..4e956dbf3 100644 --- a/SNUTT-2022/SNUTT/ViewModels/ThemeSettingViewModel.swift +++ b/SNUTT-2022/SNUTT/ViewModels/ThemeSettingViewModel.swift @@ -12,7 +12,11 @@ class ThemeSettingViewModel: BaseViewModel, ObservableObject { @Published private var themes: [Theme] = [] var customThemes: [Theme] { - themes.filter { $0.isCustom } + themes.filter { $0.status != .downloaded && $0.isCustom } + } + + var downloadedThemes: [Theme] { + themes.filter { $0.status == .downloaded } } var basicThemes: [Theme] { @@ -54,6 +58,15 @@ class ThemeSettingViewModel: BaseViewModel, ObservableObject { services.themeService.closeCustomThemeSheet() } } + + @Published private var _isDownloadedThemeSheetOpen: Bool = false + var isDownloadedThemeSheetOpen: Bool { + get { _isDownloadedThemeSheetOpen } + set { + _isDownloadedThemeSheetOpen = false + services.themeService.closeDownloadedThemeSheet() + } + } override init(container: DIContainer) { super.init(container: container) @@ -62,6 +75,7 @@ class ThemeSettingViewModel: BaseViewModel, ObservableObject { appState.theme.$isNewThemeSheetOpen.assign(to: &$_isNewThemeSheetOpen) appState.theme.$isBasicThemeSheetOpen.assign(to: &$_isBasicThemeSheetOpen) appState.theme.$isCustomThemeSheetOpen.assign(to: &$_isCustomThemeSheetOpen) + appState.theme.$isDownloadedThemeSheetOpen.assign(to: &$_isDownloadedThemeSheetOpen) } var themeState: ThemeState { @@ -73,7 +87,7 @@ class ThemeSettingViewModel: BaseViewModel, ObservableObject { } var newTheme: Theme { - let theme: Theme = .init(from: .init(id: UUID().uuidString, theme: 0, name: "새 테마", colors: [ThemeColorDto(bg: STColor.cyan.toHex(), fg: Color.white.toHex())], isDefault: false, isCustom: true)) + let theme: Theme = .init(id: UUID().uuidString, name: "새 테마", colors: [LectureColor(fg: Color.white, bg: STColor.cyan)], isCustom: true) return theme } @@ -95,6 +109,12 @@ class ThemeSettingViewModel: BaseViewModel, ObservableObject { services.timetableService.selectTimetableTheme(theme: theme) services.themeService.openCustomThemeSheet(for: theme) } + + func openDownloadedThemeSheet() { + guard let theme = targetTheme else { return } + services.timetableService.selectTimetableTheme(theme: theme) + services.themeService.openDownloadedThemeSheet(for: theme) + } func copyTheme() async { guard let themeId = targetTheme?.id else { return } diff --git a/SNUTT-2022/SNUTT/Views/Components/ThemeBottomSheet.swift b/SNUTT-2022/SNUTT/Views/Components/ThemeBottomSheet.swift index 9e6deb681..59166e943 100644 --- a/SNUTT-2022/SNUTT/Views/Components/ThemeBottomSheet.swift +++ b/SNUTT-2022/SNUTT/Views/Components/ThemeBottomSheet.swift @@ -9,8 +9,10 @@ import SwiftUI struct ThemeBottomSheet: View { @Binding var isOpen: Bool - + + let targetTheme: Theme? let openCustomThemeSheet: @MainActor () async -> Void + let openDownloadedThemeSheet: @MainActor () async -> Void let copyTheme: @MainActor () async -> Void let deleteTheme: @MainActor () async -> Void @@ -23,28 +25,40 @@ struct ThemeBottomSheet: View { disableDragGesture: true) { VStack(spacing: 0) { - ThemeBottomSheetButton(menu: .edit) { - Task { - await openCustomThemeSheet() + if (targetTheme?.status == .downloaded) { + ThemeBottomSheetButton(menu: .detail) { + Task { + await openDownloadedThemeSheet() + } } - } - ThemeBottomSheetButton(menu: .copy) { - Task { - await copyTheme() + ThemeBottomSheetButton(menu: .delete) { + isDeleteAlertPresented = true + } + } else { + ThemeBottomSheetButton(menu: .edit) { + Task { + await openCustomThemeSheet() + } } - } - ThemeBottomSheetButton(menu: .delete) { - isDeleteAlertPresented = true - } - .alert("테마를 삭제하시겠습니까?\n이 테마가 지정된 시간표의 강의 색상은 유지됩니다.", isPresented: $isDeleteAlertPresented) { - Button("취소", role: .cancel, action: {}) - Button("삭제", role: .destructive) { + ThemeBottomSheetButton(menu: .copy) { Task { - await deleteTheme() + await copyTheme() } } + + ThemeBottomSheetButton(menu: .delete) { + isDeleteAlertPresented = true + } + } + } + .alert("테마를 삭제하시겠습니까?\n이 테마가 지정된 시간표의 강의 색상은 유지됩니다.", isPresented: $isDeleteAlertPresented) { + Button("취소", role: .cancel, action: {}) + Button("삭제", role: .destructive) { + Task { + await deleteTheme() + } } } } @@ -79,12 +93,14 @@ struct ThemeBottomSheetButton: View { extension ThemeBottomSheetButton { enum ThemeMenu { + case detail case edit case copy case delete var imageName: String { switch self { + case .detail: return "sheet.palette" case .edit: return "sheet.palette" case .copy: return "menu.duplicate" case .delete: return "sheet.trash" @@ -93,6 +109,7 @@ extension ThemeBottomSheetButton { var text: String { switch self { + case .detail: return "상세 보기" case .edit: return "상세 수정" case .copy: return "테마 복제" case .delete: return "테마 삭제" diff --git a/SNUTT-2022/SNUTT/Views/Scenes/Settings/ThemeSettingScene.swift b/SNUTT-2022/SNUTT/Views/Scenes/Settings/ThemeSettingScene.swift index 516b9e3c7..865d3155c 100644 --- a/SNUTT-2022/SNUTT/Views/Scenes/Settings/ThemeSettingScene.swift +++ b/SNUTT-2022/SNUTT/Views/Scenes/Settings/ThemeSettingScene.swift @@ -20,6 +20,13 @@ struct ThemeSettingScene: View { viewModel.openBottomSheet(for: theme) } } + if !viewModel.downloadedThemes.isEmpty { + Section(header: Text("담은 테마")) { + ThemeScrollView(themes: viewModel.downloadedThemes) { theme in + viewModel.openBottomSheet(for: theme) + } + } + } Section(header: Text("제공 테마"), footer: infoView()) { ThemeScrollView(themes: viewModel.basicThemes) { theme in viewModel.openBasicThemeSheet(for: theme) @@ -27,7 +34,8 @@ struct ThemeSettingScene: View { } } ThemeBottomSheet(isOpen: $viewModel.isBottomSheetOpen, - openCustomThemeSheet: viewModel.openCustomThemeSheet, + targetTheme: viewModel.targetTheme, + openCustomThemeSheet: viewModel.openCustomThemeSheet, openDownloadedThemeSheet: viewModel.openDownloadedThemeSheet, copyTheme: viewModel.copyTheme, deleteTheme: viewModel.deleteTheme) } @@ -57,6 +65,14 @@ struct ThemeSettingScene: View { } .accentColor(Color(UIColor.label)) }) + .sheet(isPresented: $viewModel.isDownloadedThemeSheetOpen, content: { + ZStack { + NavigationView { + ThemeDetailScene(viewModel: .init(container: viewModel.container), theme: viewModel.targetTheme ?? viewModel.newTheme, themeType: .downloaded) + } + } + .accentColor(Color(UIColor.label)) + }) } } diff --git a/SNUTT-2022/SNUTT/Views/Scenes/ThemeDetailScene.swift b/SNUTT-2022/SNUTT/Views/Scenes/ThemeDetailScene.swift index dc86c771c..d8a000806 100644 --- a/SNUTT-2022/SNUTT/Views/Scenes/ThemeDetailScene.swift +++ b/SNUTT-2022/SNUTT/Views/Scenes/ThemeDetailScene.swift @@ -27,6 +27,7 @@ struct ThemeDetailScene: View { case basic case custom case new + case downloaded } @Environment(\.dismiss) var dismiss @@ -37,7 +38,7 @@ struct ThemeDetailScene: View { HStack { DetailLabel(text: "테마명") switch themeType { - case .basic: + case .basic, .downloaded: Text("\(theme.name)") .font(.system(size: 16, weight: .regular)) .foregroundColor(Color(uiColor: .label.withAlphaComponent(0.6))) @@ -88,7 +89,6 @@ struct ThemeDetailScene: View { VStack { Spacer(minLength: 5) ForEach(theme.colors.indices, id: \.self) { index in - VStack { HStack { DetailLabel(text: "색상 \(index + 1)") @@ -166,6 +166,26 @@ struct ThemeDetailScene: View { } .padding(.top, 8) } + + case .downloaded: + VStack(spacing: 0) { + ForEach(theme.colors.indices, id: \.self) { index in + HStack(alignment: .center) { + DetailLabel(text: "색상 \(index + 1)") + LectureColorPreview(lectureColor: theme.colors[index]) + .frame(height: 25) + Spacer() + } + .padding(.horizontal, 20) + .padding(.vertical, 10) + + Divider() + .frame(height: 1) + } + } + .background(STColor.groupForeground) + .border(Color.black.opacity(0.1), width: 0.5) + .padding(.vertical, 10) } HStack { @@ -213,7 +233,7 @@ struct ThemeDetailScene: View { } ToolbarItem(placement: .navigationBarTrailing) { switch themeType { - case .basic: + case .basic, .downloaded: Button { dismiss() } label: { @@ -271,7 +291,7 @@ struct ThemeDetailScene: View { #if DEBUG struct ThemeDetailScene_Previews: PreviewProvider { static var previews: some View { - ThemeDetailScene(viewModel: .init(container: .preview), theme: .init(from: .init(id: UUID().uuidString, theme: 0, name: "새 테마", colors: [ThemeColorDto(bg: STColor.cyan.toHex(), fg: Color.white.toHex())], isDefault: false, isCustom: true)), themeType: .new) + ThemeDetailScene(viewModel: .init(container: .preview), theme: .init(id: UUID().uuidString, name: "새 테마", colors: [LectureColor(fg: Color.white, bg: STColor.cyan)], isCustom: true), themeType: .new) } } #endif From 44076420f7f247194b71fad2c427847e92b8fe3c Mon Sep 17 00:00:00 2001 From: chaemin2001 Date: Fri, 31 Jan 2025 00:12:35 +0900 Subject: [PATCH 3/6] Add Theme Market Webview --- SNUTT-2022/SNUTT.xcodeproj/project.pbxproj | 6 +- .../SNUTT/AppState/States/ThemeState.swift | 88 ++++++++++++++ .../SNUTT/Services/GlobalUIService.swift | 2 + .../Components/WebViews/ThemeMarketView.swift | 55 +++++++-- .../Views/Scenes/Settings/SettingScene.swift | 2 +- .../SNUTT/Views/Scenes/ThemeMarketScene.swift | 112 ++++++++++++++++++ 6 files changed, 255 insertions(+), 10 deletions(-) create mode 100644 SNUTT-2022/SNUTT/Views/Scenes/ThemeMarketScene.swift diff --git a/SNUTT-2022/SNUTT.xcodeproj/project.pbxproj b/SNUTT-2022/SNUTT.xcodeproj/project.pbxproj index 47d9ecb99..588644918 100644 --- a/SNUTT-2022/SNUTT.xcodeproj/project.pbxproj +++ b/SNUTT-2022/SNUTT.xcodeproj/project.pbxproj @@ -15,6 +15,7 @@ 731DA001297BC54B0027BA25 /* BookmarkDto.swift in Sources */ = {isa = PBXBuildFile; fileRef = 731DA000297BC54B0027BA25 /* BookmarkDto.swift */; }; 731DA003297BC5740027BA25 /* BookmarkRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 731DA002297BC5740027BA25 /* BookmarkRouter.swift */; }; 731DA005297BC8990027BA25 /* BookmarkScene.swift in Sources */ = {isa = PBXBuildFile; fileRef = 731DA004297BC8990027BA25 /* BookmarkScene.swift */; }; + 7329BF252D4BAD4700ABAF23 /* ThemeMarketScene.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7329BF242D4BAD3F00ABAF23 /* ThemeMarketScene.swift */; }; 734A831F2C2FD41200D6CB95 /* KakaoLogin.swift in Sources */ = {isa = PBXBuildFile; fileRef = 734A831E2C2FD41200D6CB95 /* KakaoLogin.swift */; }; 736AF84C2C2F275E00ED9C1A /* GoogleLogin.swift in Sources */ = {isa = PBXBuildFile; fileRef = 736AF84B2C2F275E00ED9C1A /* GoogleLogin.swift */; }; 736AF84F2C2F279900ED9C1A /* GoogleSignIn in Frameworks */ = {isa = PBXBuildFile; productRef = 736AF84E2C2F279900ED9C1A /* GoogleSignIn */; }; @@ -33,7 +34,6 @@ 738407052B577E9000007E62 /* ThemeDetailScene.swift in Sources */ = {isa = PBXBuildFile; fileRef = 738407042B577E9000007E62 /* ThemeDetailScene.swift */; }; 738407082B579F9200007E62 /* Theme.swift in Sources */ = {isa = PBXBuildFile; fileRef = 738406ED2B57107C00007E62 /* Theme.swift */; }; 738407092B57B10000007E62 /* ThemeDto.swift in Sources */ = {isa = PBXBuildFile; fileRef = 738406F02B5710C200007E62 /* ThemeDto.swift */; }; - 738407102B6060F800007E62 /* ThemeState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 738406F82B57154400007E62 /* ThemeState.swift */; }; 73AB84D22C35128B0075DE83 /* IntegrateAccountScene.swift in Sources */ = {isa = PBXBuildFile; fileRef = 73AB84D12C35128B0075DE83 /* IntegrateAccountScene.swift */; }; 73AB84D42C3514080075DE83 /* IntegrateAccountViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 73AB84D32C3514080075DE83 /* IntegrateAccountViewModel.swift */; }; B800A38B2B76132C008E8D84 /* SearchTimeMaskDto.swift in Sources */ = {isa = PBXBuildFile; fileRef = B800A38A2B76132C008E8D84 /* SearchTimeMaskDto.swift */; }; @@ -366,6 +366,7 @@ 731DA000297BC54B0027BA25 /* BookmarkDto.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarkDto.swift; sourceTree = ""; }; 731DA002297BC5740027BA25 /* BookmarkRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarkRouter.swift; sourceTree = ""; }; 731DA004297BC8990027BA25 /* BookmarkScene.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarkScene.swift; sourceTree = ""; }; + 7329BF242D4BAD3F00ABAF23 /* ThemeMarketScene.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeMarketScene.swift; sourceTree = ""; }; 734A831E2C2FD41200D6CB95 /* KakaoLogin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KakaoLogin.swift; sourceTree = ""; }; 736AF84B2C2F275E00ED9C1A /* GoogleLogin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GoogleLogin.swift; sourceTree = ""; }; 738406ED2B57107C00007E62 /* Theme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Theme.swift; sourceTree = ""; }; @@ -1133,6 +1134,7 @@ B87DF6FA291A4303008BB95B /* PopupScene.swift */, 731DA004297BC8990027BA25 /* BookmarkScene.swift */, 738407042B577E9000007E62 /* ThemeDetailScene.swift */, + 7329BF242D4BAD3F00ABAF23 /* ThemeMarketScene.swift */, ); path = Scenes; sourceTree = ""; @@ -1449,7 +1451,6 @@ CEEAB49329C5EA41005497B0 /* Collection+Getter.swift in Sources */, BE419B94288B8FA600FA9590 /* TimetableGridLayer.swift in Sources */, 738407082B579F9200007E62 /* Theme.swift in Sources */, - 738407102B6060F800007E62 /* ThemeState.swift in Sources */, BE98A080288B046900C2CE95 /* TimetableWidgetEntryView.swift in Sources */, BE804C062894157500782F16 /* SNUTTWidgetProvider.swift in Sources */, BE419B85288B8B8F00FA9590 /* Lecture.swift in Sources */, @@ -1477,6 +1478,7 @@ 738406F72B57134300007E62 /* ThemeService.swift in Sources */, BE9413C128C219AD00171060 /* STNotification.swift in Sources */, B87B317328D81447005C170B /* UserDto.swift in Sources */, + 7329BF252D4BAD4700ABAF23 /* ThemeMarketScene.swift in Sources */, BE060BD928DF4C1E00A2F1B9 /* OnboardScene.swift in Sources */, 73AB84D22C35128B0075DE83 /* IntegrateAccountScene.swift in Sources */, BE682BFF2888056C009EBCB7 /* Quarter.swift in Sources */, diff --git a/SNUTT-2022/SNUTT/AppState/States/ThemeState.swift b/SNUTT-2022/SNUTT/AppState/States/ThemeState.swift index bff260465..353b5ac08 100644 --- a/SNUTT-2022/SNUTT/AppState/States/ThemeState.swift +++ b/SNUTT-2022/SNUTT/AppState/States/ThemeState.swift @@ -5,7 +5,10 @@ // Created by 이채민 on 2024/01/17. // +import Combine import Foundation +import SwiftUI +import WebKit class ThemeState { @Published var themeList: [Theme] = [] @@ -16,4 +19,89 @@ class ThemeState { @Published var isBasicThemeSheetOpen = false @Published var isCustomThemeSheetOpen = false @Published var isDownloadedThemeSheetOpen = false + + let preloaded = ThemeMarketViewPreloadManager() +} + +class ThemeMarketViewPreloadManager { + var url: URL? + var webView: WKWebView? + var eventSignal: PassthroughSubject? + var coordinator: Coordinator? + private var bag = Set() + + func preload(url: URL, accessToken: String) { + eventSignal = eventSignal ?? .init() + webView = WKWebView(cookies: NetworkConfiguration.getCookiesFrom(accessToken: accessToken)) + coordinator = coordinator ?? Coordinator(eventSignal: eventSignal!) + webView?.scrollView.bounces = false + webView?.backgroundColor = UIColor(STColor.systemBackground) + webView?.isOpaque = false + webView?.configuration.userContentController.add(coordinator!, name: "snutt") + reload(url: url) + bindEventSignal() + } + + func reload(url: URL? = nil) { + guard let url = url ?? self.url else { return } + webView?.load(URLRequest(url: url)) + webView?.allowsBackForwardNavigationGestures = url == WebViewType.themeMarket.url + self.url = url + } + + func setColorScheme(_ colorScheme: ColorScheme) { + webView?.setCookie(name: "theme", value: colorScheme.description) + webView?.evaluateJavaScript("changeTheme('\(colorScheme.description)')") + } + + private func bindEventSignal() { + eventSignal? + .sink { [weak self] event in + switch event { + case let .reload(url): + self?.reload(url: url) + default: + return + } + } + .store(in: &bag) + + /// The `colorScheme` value can be quite unstable, especially during the SwiftUI lifecycle. + /// To address this, we debounce it for 0.1 seconds. + eventSignal? + .debounce(for: .seconds(0.1), scheduler: DispatchQueue.main) + .sink { [weak self] event in + switch event { + case let .colorSchemeChange(to: colorScheme): + self?.setColorScheme(colorScheme) + default: + return + } + } + .store(in: &bag) + } +} + +extension ThemeMarketViewPreloadManager { + class Coordinator: NSObject, WKScriptMessageHandler { + let eventSignal: PassthroughSubject + + init(eventSignal: PassthroughSubject) { + self.eventSignal = eventSignal + } + + func userContentController(_: WKUserContentController, didReceive message: WKScriptMessage) { + if message.name == "snutt" { + eventSignal.send(.close) + } + } + } +} + +enum ThemeMarketViewEventType { + case reload(url: URL) + case colorSchemeChange(to: ColorScheme) + case close + case error + case success } diff --git a/SNUTT-2022/SNUTT/Services/GlobalUIService.swift b/SNUTT-2022/SNUTT/Services/GlobalUIService.swift index 2cc02a3fc..298bddac7 100644 --- a/SNUTT-2022/SNUTT/Services/GlobalUIService.swift +++ b/SNUTT-2022/SNUTT/Services/GlobalUIService.swift @@ -147,10 +147,12 @@ struct GlobalUIService: GlobalUIServiceProtocol, UserAuthHandler, ConfigsProvida guard let accessToken = appState.user.accessToken else { return } appState.review.preloadedMain.preload(url: WebViewType.review.url, accessToken: accessToken) appState.review.preloadedDetail.preload(url: WebViewType.review.url, accessToken: accessToken) + appState.theme.preloaded.preload(url: WebViewType.themeMarket.url, accessToken: accessToken) } func sendMainWebViewReloadSignal() { appState.review.preloadedMain.eventSignal?.send(.reload(url: WebViewType.review.url)) + appState.theme.preloaded.eventSignal?.send(.reload(url: WebViewType.themeMarket.url)) } func sendDetailWebViewReloadSignal(url: URL) { diff --git a/SNUTT-2022/SNUTT/Views/Components/WebViews/ThemeMarketView.swift b/SNUTT-2022/SNUTT/Views/Components/WebViews/ThemeMarketView.swift index 2473dabbc..121e1112e 100644 --- a/SNUTT-2022/SNUTT/Views/Components/WebViews/ThemeMarketView.swift +++ b/SNUTT-2022/SNUTT/Views/Components/WebViews/ThemeMarketView.swift @@ -5,17 +5,58 @@ // Created by 이채민 on 9/16/24. // +import Combine import SwiftUI +import WebKit -struct ThemeMarketView: View { - var body: some View { - SingleWebView(url: WebViewType.themeMarket.url) - .navigationTitle("테마 마켓") +struct ThemeMarketView: UIViewRepresentable { + var preloadWebView: ThemeMarketViewPreloadManager + + var webView: WKWebView { + preloadWebView.webView! + } + + var eventSignal: PassthroughSubject { + preloadWebView.eventSignal! + } + + init(preloadedWebView: ThemeMarketViewPreloadManager) { + preloadWebView = preloadedWebView + } + + func makeUIView(context: Context) -> WKWebView { + webView.navigationDelegate = context.coordinator + return webView + } + + func updateUIView(_: WKWebView, context _: Context) { + /// Don't refresh webview here. This method is called quite often. + } + + func makeCoordinator() -> ThemeMarketView.Coordinator { + Coordinator(self) + } + + class Coordinator: NSObject { + let parent: ThemeMarketView + + init(_ parent: ThemeMarketView) { + self.parent = parent + super.init() + } } } -struct ThemeMarketScene_Previews: PreviewProvider { - static var previews: some View { - ThemeMarketView() +extension ThemeMarketView.Coordinator: WKNavigationDelegate { + func webView(_: WKWebView, didFailProvisionalNavigation _: WKNavigation!, withError _: Error) { + parent.eventSignal.send(.error) + } + + func webView(_: WKWebView, didFail _: WKNavigation!, withError _: Error) { + parent.eventSignal.send(.error) + } + + func webView(_: WKWebView, didFinish _: WKNavigation!) { + parent.eventSignal.send(.success) } } diff --git a/SNUTT-2022/SNUTT/Views/Scenes/Settings/SettingScene.swift b/SNUTT-2022/SNUTT/Views/Scenes/Settings/SettingScene.swift index 81c42d46a..172b1c2b2 100644 --- a/SNUTT-2022/SNUTT/Views/Scenes/Settings/SettingScene.swift +++ b/SNUTT-2022/SNUTT/Views/Scenes/Settings/SettingScene.swift @@ -49,7 +49,7 @@ struct SettingScene: View { VacancyScene(viewModel: .init(container: viewModel.container)) } SettingsLinkItem(title: "테마 마켓") { - ThemeMarketView() + ThemeMarketScene(viewModel: .init(container: viewModel.container)) } } header: { Text("서비스") diff --git a/SNUTT-2022/SNUTT/Views/Scenes/ThemeMarketScene.swift b/SNUTT-2022/SNUTT/Views/Scenes/ThemeMarketScene.swift new file mode 100644 index 000000000..d9439f32e --- /dev/null +++ b/SNUTT-2022/SNUTT/Views/Scenes/ThemeMarketScene.swift @@ -0,0 +1,112 @@ +// +// ThemeMarketScene.swift +// SNUTT +// +// Created by 이채민 on 1/30/25. +// + +import Combine +import SwiftUI + +struct ThemeMarketScene: View { + @ObservedObject var viewModel: ViewModel + + @Environment(\.colorScheme) var colorScheme + @Environment(\.dismiss) var dismiss + + init(viewModel: ViewModel) { + self.viewModel = viewModel + eventSignal?.send(.colorSchemeChange(to: viewModel.preferredColorScheme)) + } + + private var eventSignal: PassthroughSubject? { + viewModel.getPreloadedWebView().eventSignal + } + + private var themeMarketUrl: URL = WebViewType.themeMarket.url + + var body: some View { + ZStack { + ThemeMarketView(preloadedWebView: viewModel.getPreloadedWebView()) + .navigationBarHidden(true) + .background(STColor.systemBackground) + + if viewModel.connectionState == .error { + WebErrorView(refresh: { + eventSignal?.send(.reload(url: themeMarketUrl)) + }) + .navigationTitle("테마 마켓") + .navigationBarTitleDisplayMode(.inline) + .frame(maxWidth: .infinity, maxHeight: .infinity) + .background(STColor.systemBackground) + } + } + .onAppear { + eventSignal?.send(.colorSchemeChange(to: viewModel.preferredColorScheme)) + } + /// Respond to changes of preferred color scheme in `SettingScene`. + .onChange(of: viewModel.preferredColorScheme) { newValue in + eventSignal?.send(.colorSchemeChange(to: newValue)) + } + /// Respond to changes of system color scheme. + .onChange(of: colorScheme) { newValue in + eventSignal?.send(.colorSchemeChange(to: newValue)) + } + .onReceive(eventSignal ?? .init()) { signal in + switch signal { + case .success: + viewModel.connectionState = .success + case .error: + viewModel.connectionState = .error + case .close: + dismiss() + default: + return + } + } + + let _ = debugChanges() + } +} + +extension ThemeMarketScene { + class ViewModel: BaseViewModel, ObservableObject { + @Published var accessToken: String = "" + @Published var connectionState: WebViewConnectionState = .success + @Published var preferredColorScheme = UITraitCollection.current.userInterfaceStyle.toColorScheme() + + private var bag = Set() + + override init(container: DIContainer) { + super.init(container: container) + appState.user.$accessToken.sink { newValue in + if let token = newValue, !token.isEmpty { + self.connectionState = .success + self.accessToken = token + } else { + self.connectionState = .error + self.accessToken = "" + } + }.store(in: &bag) + + appState.system.$preferredColorScheme.sink { newValue in + if let newValue { + self.preferredColorScheme = newValue + } else { + self.preferredColorScheme = UITraitCollection.current.userInterfaceStyle.toColorScheme() + } + }.store(in: &bag) + } + + func getPreloadedWebView() -> ThemeMarketViewPreloadManager { + let webviewManager = appState.theme.preloaded + + // make sure the webview has all required cookies + if let accessToken = appState.user.accessToken { + webviewManager.webView?.setCookies(cookies: NetworkConfiguration.getCookiesFrom(accessToken: accessToken)) + } + + return webviewManager + } + } +} From 0527bebd9c14e93c4310b9654660bb225394d87b Mon Sep 17 00:00:00 2001 From: chaemin2001 Date: Fri, 31 Jan 2025 17:04:21 +0900 Subject: [PATCH 4/6] Fix Cookie Setting --- .../SNUTT/AppState/States/ReviewState.swift | 4 +- .../SNUTT/AppState/States/ThemeState.swift | 4 +- .../Repositories/NetworkConfiguration.swift | 45 +++++++++++++------ .../SNUTT/Services/GlobalUIService.swift | 1 - .../Components/WebViews/WebViewProtocol.swift | 9 +++- .../SNUTT/Views/Scenes/ReviewScene.swift | 2 +- .../SNUTT/Views/Scenes/ThemeMarketScene.swift | 2 +- 7 files changed, 45 insertions(+), 22 deletions(-) diff --git a/SNUTT-2022/SNUTT/AppState/States/ReviewState.swift b/SNUTT-2022/SNUTT/AppState/States/ReviewState.swift index 72589017b..3a0211d47 100644 --- a/SNUTT-2022/SNUTT/AppState/States/ReviewState.swift +++ b/SNUTT-2022/SNUTT/AppState/States/ReviewState.swift @@ -23,7 +23,7 @@ class WebViewPreloadManager { func preload(url: URL, accessToken: String) { eventSignal = eventSignal ?? .init() - webView = WKWebView(cookies: NetworkConfiguration.getCookiesFrom(accessToken: accessToken)) + webView = WKWebView(cookies: NetworkConfiguration.getCookiesFrom(accessToken: accessToken, type: "review")) coordinator = coordinator ?? Coordinator(eventSignal: eventSignal!) webView?.scrollView.bounces = false webView?.backgroundColor = UIColor(STColor.systemBackground) @@ -41,7 +41,7 @@ class WebViewPreloadManager { } func setColorScheme(_ colorScheme: ColorScheme) { - webView?.setCookie(name: "theme", value: colorScheme.description) + webView?.setSnuevCookie(name: "theme", value: colorScheme.description) webView?.evaluateJavaScript("changeTheme('\(colorScheme.description)')") } diff --git a/SNUTT-2022/SNUTT/AppState/States/ThemeState.swift b/SNUTT-2022/SNUTT/AppState/States/ThemeState.swift index 353b5ac08..bc2b483d1 100644 --- a/SNUTT-2022/SNUTT/AppState/States/ThemeState.swift +++ b/SNUTT-2022/SNUTT/AppState/States/ThemeState.swift @@ -32,7 +32,7 @@ class ThemeMarketViewPreloadManager { func preload(url: URL, accessToken: String) { eventSignal = eventSignal ?? .init() - webView = WKWebView(cookies: NetworkConfiguration.getCookiesFrom(accessToken: accessToken)) + webView = WKWebView(cookies: NetworkConfiguration.getCookiesFrom(accessToken: accessToken, type: "theme")) coordinator = coordinator ?? Coordinator(eventSignal: eventSignal!) webView?.scrollView.bounces = false webView?.backgroundColor = UIColor(STColor.systemBackground) @@ -50,7 +50,7 @@ class ThemeMarketViewPreloadManager { } func setColorScheme(_ colorScheme: ColorScheme) { - webView?.setCookie(name: "theme", value: colorScheme.description) + webView?.setThemeCookie(name: "theme", value: colorScheme.description) webView?.evaluateJavaScript("changeTheme('\(colorScheme.description)')") } diff --git a/SNUTT-2022/SNUTT/Repositories/NetworkConfiguration.swift b/SNUTT-2022/SNUTT/Repositories/NetworkConfiguration.swift index 6df0a4ac1..6a0166d7b 100644 --- a/SNUTT-2022/SNUTT/Repositories/NetworkConfiguration.swift +++ b/SNUTT-2022/SNUTT/Repositories/NetworkConfiguration.swift @@ -14,26 +14,45 @@ struct NetworkConfiguration { static let snuevBaseURL: String = Bundle.main.infoDictionary?["SNUEV_WEB_URL"] as! String static let themeBaseURL: String = Bundle.main.infoDictionary?["THEME_WEB_URL"] as! String - static func getCookie(name: String, value: String) -> HTTPCookie? { + private static func createCookie(domain: String, name: String, value: String) -> HTTPCookie? { return HTTPCookie(properties: [ - .domain: NetworkConfiguration.snuevBaseURL.replacingOccurrences(of: "https://", with: ""), + .domain: domain.replacingOccurrences(of: "https://", with: ""), .path: "/", .name: name, .value: value, - .expires: Date(timeIntervalSince1970: Date().timeIntervalSince1970 + pow(10, 9) * 2), + .expires: Date(timeIntervalSinceNow: pow(10, 9) * 2), ]) } - - static func getCookiesFrom(accessToken: String) -> [HTTPCookie] { - guard let tokenCookie = getCookie(name: "x-access-token", value: accessToken), - let apiKeyCookie = getCookie(name: AppMetadata.apiKey.key, value: AppMetadata.apiKey.value), - let osTypeCookie = getCookie(name: AppMetadata.osType.key, value: AppMetadata.osType.value), - let osVersionCookie = getCookie(name: AppMetadata.osVersion.key, value: AppMetadata.osVersion.value), - let appVersionCookie = getCookie(name: AppMetadata.appVersion.key, value: AppMetadata.appVersion.value), - let appTypeCookie = getCookie(name: AppMetadata.appType.key, value: AppMetadata.appType.value), - let buildNumberCookie = getCookie(name: AppMetadata.buildNumber.key, value: AppMetadata.buildNumber.value) + + static func getSnuevCookie(name: String, value: String) -> HTTPCookie? { + return createCookie(domain: snuevBaseURL, name: name, value: value) + } + + static func getThemeCookie(name: String, value: String) -> HTTPCookie? { + return createCookie(domain: themeBaseURL, name: name, value: value) + } + + static func getCookiesFrom(accessToken: String, type: String) -> [HTTPCookie] { + let cookieProvider: (String, String) -> HTTPCookie? = { + switch type { + case "review": + return getSnuevCookie + case "theme": + return getThemeCookie + default: + return { _, _ in nil } + } + }() + + guard let tokenCookie = cookieProvider("x-access-token", accessToken), + let apiKeyCookie = cookieProvider(AppMetadata.apiKey.key, AppMetadata.apiKey.value), + let osTypeCookie = cookieProvider(AppMetadata.osType.key, AppMetadata.osType.value), + let osVersionCookie = cookieProvider(AppMetadata.osVersion.key, AppMetadata.osVersion.value), + let appVersionCookie = cookieProvider(AppMetadata.appVersion.key, AppMetadata.appVersion.value), + let appTypeCookie = cookieProvider(AppMetadata.appType.key, AppMetadata.appType.value), + let buildNumberCookie = cookieProvider(AppMetadata.buildNumber.key, AppMetadata.buildNumber.value) else { return [] } - + return [apiKeyCookie, tokenCookie, osTypeCookie, osVersionCookie, appVersionCookie, appTypeCookie, buildNumberCookie] } } diff --git a/SNUTT-2022/SNUTT/Services/GlobalUIService.swift b/SNUTT-2022/SNUTT/Services/GlobalUIService.swift index 298bddac7..3fa0154b1 100644 --- a/SNUTT-2022/SNUTT/Services/GlobalUIService.swift +++ b/SNUTT-2022/SNUTT/Services/GlobalUIService.swift @@ -152,7 +152,6 @@ struct GlobalUIService: GlobalUIServiceProtocol, UserAuthHandler, ConfigsProvida func sendMainWebViewReloadSignal() { appState.review.preloadedMain.eventSignal?.send(.reload(url: WebViewType.review.url)) - appState.theme.preloaded.eventSignal?.send(.reload(url: WebViewType.themeMarket.url)) } func sendDetailWebViewReloadSignal(url: URL) { diff --git a/SNUTT-2022/SNUTT/Views/Components/WebViews/WebViewProtocol.swift b/SNUTT-2022/SNUTT/Views/Components/WebViews/WebViewProtocol.swift index f36681633..112e22ce1 100644 --- a/SNUTT-2022/SNUTT/Views/Components/WebViews/WebViewProtocol.swift +++ b/SNUTT-2022/SNUTT/Views/Components/WebViews/WebViewProtocol.swift @@ -37,8 +37,13 @@ extension WKWebView { configuration.websiteDataStore.httpCookieStore } - func setCookie(name: String, value: String) { - guard let cookie = NetworkConfiguration.getCookie(name: name, value: value) else { return } + func setSnuevCookie(name: String, value: String) { + guard let cookie = NetworkConfiguration.getSnuevCookie(name: name, value: value) else { return } + cookieStore.setCookie(cookie) + } + + func setThemeCookie(name: String, value: String) { + guard let cookie = NetworkConfiguration.getThemeCookie(name: name, value: value) else { return } cookieStore.setCookie(cookie) } diff --git a/SNUTT-2022/SNUTT/Views/Scenes/ReviewScene.swift b/SNUTT-2022/SNUTT/Views/Scenes/ReviewScene.swift index e3e2ad48d..01c999c7d 100644 --- a/SNUTT-2022/SNUTT/Views/Scenes/ReviewScene.swift +++ b/SNUTT-2022/SNUTT/Views/Scenes/ReviewScene.swift @@ -115,7 +115,7 @@ extension ReviewScene { // make sure the webview has all required cookies if let accessToken = appState.user.accessToken { - webviewManager.webView?.setCookies(cookies: NetworkConfiguration.getCookiesFrom(accessToken: accessToken)) + webviewManager.webView?.setCookies(cookies: NetworkConfiguration.getCookiesFrom(accessToken: accessToken, type: "review")) } return webviewManager diff --git a/SNUTT-2022/SNUTT/Views/Scenes/ThemeMarketScene.swift b/SNUTT-2022/SNUTT/Views/Scenes/ThemeMarketScene.swift index d9439f32e..b27469f0b 100644 --- a/SNUTT-2022/SNUTT/Views/Scenes/ThemeMarketScene.swift +++ b/SNUTT-2022/SNUTT/Views/Scenes/ThemeMarketScene.swift @@ -103,7 +103,7 @@ extension ThemeMarketScene { // make sure the webview has all required cookies if let accessToken = appState.user.accessToken { - webviewManager.webView?.setCookies(cookies: NetworkConfiguration.getCookiesFrom(accessToken: accessToken)) + webviewManager.webView?.setCookies(cookies: NetworkConfiguration.getCookiesFrom(accessToken: accessToken, type: "theme")) } return webviewManager From e13f0f76b9c23c2972f84d604a14fe8b421293b4 Mon Sep 17 00:00:00 2001 From: chaemin2001 Date: Fri, 31 Jan 2025 17:09:12 +0900 Subject: [PATCH 5/6] Add Navigation Bar --- SNUTT-2022/SNUTT/Views/Scenes/ThemeMarketScene.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/SNUTT-2022/SNUTT/Views/Scenes/ThemeMarketScene.swift b/SNUTT-2022/SNUTT/Views/Scenes/ThemeMarketScene.swift index b27469f0b..05a1517a9 100644 --- a/SNUTT-2022/SNUTT/Views/Scenes/ThemeMarketScene.swift +++ b/SNUTT-2022/SNUTT/Views/Scenes/ThemeMarketScene.swift @@ -28,7 +28,8 @@ struct ThemeMarketScene: View { var body: some View { ZStack { ThemeMarketView(preloadedWebView: viewModel.getPreloadedWebView()) - .navigationBarHidden(true) + .navigationTitle("테마 마켓") + .navigationBarTitleDisplayMode(.inline) .background(STColor.systemBackground) if viewModel.connectionState == .error { From 4cf2801c31930e89adb5d329dc7e51ea7c8ac553 Mon Sep 17 00:00:00 2001 From: chaemin2001 Date: Fri, 31 Jan 2025 13:16:23 +0000 Subject: [PATCH 6/6] Apply SwiftFormat changes --- SNUTT-2022/SNUTT/AppState/States/ThemeState.swift | 2 +- SNUTT-2022/SNUTT/Models/Theme.swift | 2 +- SNUTT-2022/SNUTT/Repositories/Dto/ThemeDto.swift | 1 - .../SNUTT/Repositories/NetworkConfiguration.swift | 12 ++++++------ SNUTT-2022/SNUTT/Services/ThemeService.swift | 2 +- .../SNUTT/ViewModels/ThemeSettingViewModel.swift | 6 +++--- .../SNUTT/Views/Components/ThemeBottomSheet.swift | 4 ++-- .../Views/Components/WebViews/WebViewProtocol.swift | 4 ++-- SNUTT-2022/SNUTT/Views/Scenes/ThemeDetailScene.swift | 2 +- SNUTT-2022/SNUTT/Views/Scenes/ThemeMarketScene.swift | 2 +- .../Sources/UI/LectureSearchResultScene.swift | 2 +- .../UI/TimetableMenu/TimetableMenuContentView.swift | 2 +- .../TimetableMenu/TimetableMenuSheetModifier.swift | 2 +- 13 files changed, 21 insertions(+), 22 deletions(-) diff --git a/SNUTT-2022/SNUTT/AppState/States/ThemeState.swift b/SNUTT-2022/SNUTT/AppState/States/ThemeState.swift index bc2b483d1..9f54de26f 100644 --- a/SNUTT-2022/SNUTT/AppState/States/ThemeState.swift +++ b/SNUTT-2022/SNUTT/AppState/States/ThemeState.swift @@ -19,7 +19,7 @@ class ThemeState { @Published var isBasicThemeSheetOpen = false @Published var isCustomThemeSheetOpen = false @Published var isDownloadedThemeSheetOpen = false - + let preloaded = ThemeMarketViewPreloadManager() } diff --git a/SNUTT-2022/SNUTT/Models/Theme.swift b/SNUTT-2022/SNUTT/Models/Theme.swift index 58a1a40b4..d3bfb5800 100644 --- a/SNUTT-2022/SNUTT/Models/Theme.swift +++ b/SNUTT-2022/SNUTT/Models/Theme.swift @@ -20,7 +20,7 @@ struct Theme: Equatable, Identifiable { /// 사용자 생성 커스텀 테마 var isCustom: Bool - + var status: ThemeStatus? } diff --git a/SNUTT-2022/SNUTT/Repositories/Dto/ThemeDto.swift b/SNUTT-2022/SNUTT/Repositories/Dto/ThemeDto.swift index d47ee6155..28f4b9886 100644 --- a/SNUTT-2022/SNUTT/Repositories/Dto/ThemeDto.swift +++ b/SNUTT-2022/SNUTT/Repositories/Dto/ThemeDto.swift @@ -19,7 +19,6 @@ struct ThemeDto: Codable { let publishInfo: ThemePublishInfoDto? } - struct ThemeColorDto: Codable { let bg: String let fg: String diff --git a/SNUTT-2022/SNUTT/Repositories/NetworkConfiguration.swift b/SNUTT-2022/SNUTT/Repositories/NetworkConfiguration.swift index 6a0166d7b..c23c2b081 100644 --- a/SNUTT-2022/SNUTT/Repositories/NetworkConfiguration.swift +++ b/SNUTT-2022/SNUTT/Repositories/NetworkConfiguration.swift @@ -13,7 +13,7 @@ struct NetworkConfiguration { static let serverV1BaseURL: String = serverBaseURL + "/v1" static let snuevBaseURL: String = Bundle.main.infoDictionary?["SNUEV_WEB_URL"] as! String static let themeBaseURL: String = Bundle.main.infoDictionary?["THEME_WEB_URL"] as! String - + private static func createCookie(domain: String, name: String, value: String) -> HTTPCookie? { return HTTPCookie(properties: [ .domain: domain.replacingOccurrences(of: "https://", with: ""), @@ -23,15 +23,15 @@ struct NetworkConfiguration { .expires: Date(timeIntervalSinceNow: pow(10, 9) * 2), ]) } - + static func getSnuevCookie(name: String, value: String) -> HTTPCookie? { return createCookie(domain: snuevBaseURL, name: name, value: value) } - + static func getThemeCookie(name: String, value: String) -> HTTPCookie? { return createCookie(domain: themeBaseURL, name: name, value: value) } - + static func getCookiesFrom(accessToken: String, type: String) -> [HTTPCookie] { let cookieProvider: (String, String) -> HTTPCookie? = { switch type { @@ -43,7 +43,7 @@ struct NetworkConfiguration { return { _, _ in nil } } }() - + guard let tokenCookie = cookieProvider("x-access-token", accessToken), let apiKeyCookie = cookieProvider(AppMetadata.apiKey.key, AppMetadata.apiKey.value), let osTypeCookie = cookieProvider(AppMetadata.osType.key, AppMetadata.osType.value), @@ -52,7 +52,7 @@ struct NetworkConfiguration { let appTypeCookie = cookieProvider(AppMetadata.appType.key, AppMetadata.appType.value), let buildNumberCookie = cookieProvider(AppMetadata.buildNumber.key, AppMetadata.buildNumber.value) else { return [] } - + return [apiKeyCookie, tokenCookie, osTypeCookie, osVersionCookie, appVersionCookie, appTypeCookie, buildNumberCookie] } } diff --git a/SNUTT-2022/SNUTT/Services/ThemeService.swift b/SNUTT-2022/SNUTT/Services/ThemeService.swift index 04d74e80e..c9e642972 100644 --- a/SNUTT-2022/SNUTT/Services/ThemeService.swift +++ b/SNUTT-2022/SNUTT/Services/ThemeService.swift @@ -77,7 +77,7 @@ struct ThemeService: ThemeServiceProtocol { appState.theme.isCustomThemeSheetOpen = false appState.theme.bottomSheetTarget = nil } - + func openDownloadedThemeSheet(for _: Theme) { appState.theme.isDownloadedThemeSheetOpen = true appState.theme.isBottomSheetOpen = false diff --git a/SNUTT-2022/SNUTT/ViewModels/ThemeSettingViewModel.swift b/SNUTT-2022/SNUTT/ViewModels/ThemeSettingViewModel.swift index 4e956dbf3..4f021540b 100644 --- a/SNUTT-2022/SNUTT/ViewModels/ThemeSettingViewModel.swift +++ b/SNUTT-2022/SNUTT/ViewModels/ThemeSettingViewModel.swift @@ -14,7 +14,7 @@ class ThemeSettingViewModel: BaseViewModel, ObservableObject { var customThemes: [Theme] { themes.filter { $0.status != .downloaded && $0.isCustom } } - + var downloadedThemes: [Theme] { themes.filter { $0.status == .downloaded } } @@ -58,7 +58,7 @@ class ThemeSettingViewModel: BaseViewModel, ObservableObject { services.themeService.closeCustomThemeSheet() } } - + @Published private var _isDownloadedThemeSheetOpen: Bool = false var isDownloadedThemeSheetOpen: Bool { get { _isDownloadedThemeSheetOpen } @@ -109,7 +109,7 @@ class ThemeSettingViewModel: BaseViewModel, ObservableObject { services.timetableService.selectTimetableTheme(theme: theme) services.themeService.openCustomThemeSheet(for: theme) } - + func openDownloadedThemeSheet() { guard let theme = targetTheme else { return } services.timetableService.selectTimetableTheme(theme: theme) diff --git a/SNUTT-2022/SNUTT/Views/Components/ThemeBottomSheet.swift b/SNUTT-2022/SNUTT/Views/Components/ThemeBottomSheet.swift index 59166e943..8b24685c9 100644 --- a/SNUTT-2022/SNUTT/Views/Components/ThemeBottomSheet.swift +++ b/SNUTT-2022/SNUTT/Views/Components/ThemeBottomSheet.swift @@ -9,7 +9,7 @@ import SwiftUI struct ThemeBottomSheet: View { @Binding var isOpen: Bool - + let targetTheme: Theme? let openCustomThemeSheet: @MainActor () async -> Void let openDownloadedThemeSheet: @MainActor () async -> Void @@ -25,7 +25,7 @@ struct ThemeBottomSheet: View { disableDragGesture: true) { VStack(spacing: 0) { - if (targetTheme?.status == .downloaded) { + if targetTheme?.status == .downloaded { ThemeBottomSheetButton(menu: .detail) { Task { await openDownloadedThemeSheet() diff --git a/SNUTT-2022/SNUTT/Views/Components/WebViews/WebViewProtocol.swift b/SNUTT-2022/SNUTT/Views/Components/WebViews/WebViewProtocol.swift index 112e22ce1..31417eaa0 100644 --- a/SNUTT-2022/SNUTT/Views/Components/WebViews/WebViewProtocol.swift +++ b/SNUTT-2022/SNUTT/Views/Components/WebViews/WebViewProtocol.swift @@ -41,14 +41,14 @@ extension WKWebView { guard let cookie = NetworkConfiguration.getSnuevCookie(name: name, value: value) else { return } cookieStore.setCookie(cookie) } - + func setThemeCookie(name: String, value: String) { guard let cookie = NetworkConfiguration.getThemeCookie(name: name, value: value) else { return } cookieStore.setCookie(cookie) } func setCookies(cookies: [HTTPCookie]) { - cookies.forEach { cookie in + for cookie in cookies { cookieStore.setCookie(cookie) } } diff --git a/SNUTT-2022/SNUTT/Views/Scenes/ThemeDetailScene.swift b/SNUTT-2022/SNUTT/Views/Scenes/ThemeDetailScene.swift index d8a000806..1c177a718 100644 --- a/SNUTT-2022/SNUTT/Views/Scenes/ThemeDetailScene.swift +++ b/SNUTT-2022/SNUTT/Views/Scenes/ThemeDetailScene.swift @@ -166,7 +166,7 @@ struct ThemeDetailScene: View { } .padding(.top, 8) } - + case .downloaded: VStack(spacing: 0) { ForEach(theme.colors.indices, id: \.self) { index in diff --git a/SNUTT-2022/SNUTT/Views/Scenes/ThemeMarketScene.swift b/SNUTT-2022/SNUTT/Views/Scenes/ThemeMarketScene.swift index 05a1517a9..430f17bfd 100644 --- a/SNUTT-2022/SNUTT/Views/Scenes/ThemeMarketScene.swift +++ b/SNUTT-2022/SNUTT/Views/Scenes/ThemeMarketScene.swift @@ -24,7 +24,7 @@ struct ThemeMarketScene: View { } private var themeMarketUrl: URL = WebViewType.themeMarket.url - + var body: some View { ZStack { ThemeMarketView(preloadedWebView: viewModel.getPreloadedWebView()) diff --git a/SNUTT/Modules/Feature/Timetable/Sources/UI/LectureSearchResultScene.swift b/SNUTT/Modules/Feature/Timetable/Sources/UI/LectureSearchResultScene.swift index 1389474b4..9fafa2927 100644 --- a/SNUTT/Modules/Feature/Timetable/Sources/UI/LectureSearchResultScene.swift +++ b/SNUTT/Modules/Feature/Timetable/Sources/UI/LectureSearchResultScene.swift @@ -26,7 +26,7 @@ struct LectureSearchResultScene: View { #Preview { let viewModel = LectureSearchViewModel() - let _ = Task { + _ = Task { await viewModel.fetchInitialSearchResult() } LectureSearchResultScene(viewModel: viewModel) diff --git a/SNUTT/Modules/Feature/Timetable/Sources/UI/TimetableMenu/TimetableMenuContentView.swift b/SNUTT/Modules/Feature/Timetable/Sources/UI/TimetableMenu/TimetableMenuContentView.swift index 2c3fefe0e..da13e2bd8 100644 --- a/SNUTT/Modules/Feature/Timetable/Sources/UI/TimetableMenu/TimetableMenuContentView.swift +++ b/SNUTT/Modules/Feature/Timetable/Sources/UI/TimetableMenu/TimetableMenuContentView.swift @@ -112,7 +112,7 @@ struct TimetableMenuContentView: View { #Preview { let viewModel = TimetableViewModel() - let _ = Task { + _ = Task { try await Task.sleep(for: .seconds(1)) try await viewModel.loadTimetable() try await viewModel.loadTimetableList() diff --git a/SNUTT/Modules/Feature/Timetable/Sources/UI/TimetableMenu/TimetableMenuSheetModifier.swift b/SNUTT/Modules/Feature/Timetable/Sources/UI/TimetableMenu/TimetableMenuSheetModifier.swift index 56e9ec8d9..34e97ef21 100644 --- a/SNUTT/Modules/Feature/Timetable/Sources/UI/TimetableMenu/TimetableMenuSheetModifier.swift +++ b/SNUTT/Modules/Feature/Timetable/Sources/UI/TimetableMenu/TimetableMenuSheetModifier.swift @@ -30,7 +30,7 @@ extension View { @Previewable @State var isPresented = false @Previewable @State var context: SheetPresentationContext? let viewModel = TimetableViewModel() - let _ = Task { + _ = Task { try await viewModel.loadTimetableList() }