Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

테마 마켓 #331

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
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
10 changes: 8 additions & 2 deletions SNUTT-2022/SNUTT.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@
objects = {

/* Begin PBXBuildFile section */
73194F0F2C98086C00883EA5 /* ThemeMarketView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 73194F0E2C98086C00883EA5 /* ThemeMarketView.swift */; };
731C244E2C4442590015877B /* KakaoSDK in Frameworks */ = {isa = PBXBuildFile; productRef = 731C244D2C4442590015877B /* KakaoSDK */; };
731C24502C4442590015877B /* KakaoSDKAuth in Frameworks */ = {isa = PBXBuildFile; productRef = 731C244F2C4442590015877B /* KakaoSDKAuth */; };
731C24522C4442590015877B /* KakaoSDKCommon in Frameworks */ = {isa = PBXBuildFile; productRef = 731C24512C4442590015877B /* KakaoSDKCommon */; };
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 */; };
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 */; };
Expand All @@ -32,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 */; };
Expand Down Expand Up @@ -360,10 +361,12 @@
/* End PBXCopyFilesBuildPhase section */

/* Begin PBXFileReference section */
73194F0E2C98086C00883EA5 /* ThemeMarketView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeMarketView.swift; sourceTree = "<group>"; };
731D9FFC297BC5060027BA25 /* Bookmark.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bookmark.swift; sourceTree = "<group>"; };
731DA000297BC54B0027BA25 /* BookmarkDto.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarkDto.swift; sourceTree = "<group>"; };
731DA002297BC5740027BA25 /* BookmarkRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarkRouter.swift; sourceTree = "<group>"; };
731DA004297BC8990027BA25 /* BookmarkScene.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarkScene.swift; sourceTree = "<group>"; };
7329BF242D4BAD3F00ABAF23 /* ThemeMarketScene.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeMarketScene.swift; sourceTree = "<group>"; };
734A831E2C2FD41200D6CB95 /* KakaoLogin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KakaoLogin.swift; sourceTree = "<group>"; };
736AF84B2C2F275E00ED9C1A /* GoogleLogin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GoogleLogin.swift; sourceTree = "<group>"; };
738406ED2B57107C00007E62 /* Theme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Theme.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -741,6 +744,7 @@
B8F40EAE28980D990021A2A9 /* DeveloperInfoView.swift */,
B8F40EB028980DE00021A2A9 /* TermsOfServiceView.swift */,
B8F40EB228980DFF0021A2A9 /* PrivacyPolicyView.swift */,
73194F0E2C98086C00883EA5 /* ThemeMarketView.swift */,
);
path = WebViews;
sourceTree = "<group>";
Expand Down Expand Up @@ -1130,6 +1134,7 @@
B87DF6FA291A4303008BB95B /* PopupScene.swift */,
731DA004297BC8990027BA25 /* BookmarkScene.swift */,
738407042B577E9000007E62 /* ThemeDetailScene.swift */,
7329BF242D4BAD3F00ABAF23 /* ThemeMarketScene.swift */,
);
path = Scenes;
sourceTree = "<group>";
Expand Down Expand Up @@ -1446,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 */,
Expand Down Expand Up @@ -1474,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 */,
Expand Down Expand Up @@ -1637,6 +1642,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 */,
Expand Down
4 changes: 2 additions & 2 deletions SNUTT-2022/SNUTT/AppState/States/ReviewState.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)')")
}

Expand Down
88 changes: 88 additions & 0 deletions SNUTT-2022/SNUTT/AppState/States/ThemeState.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@
// Created by 이채민 on 2024/01/17.
//

import Combine
import Foundation
import SwiftUI
import WebKit

class ThemeState {
@Published var themeList: [Theme] = []
Expand All @@ -16,4 +18,90 @@ class ThemeState {
@Published var isNewThemeSheetOpen = false
@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<ThemeMarketViewEventType, Never>?
var coordinator: Coordinator?
private var bag = Set<AnyCancellable>()

func preload(url: URL, accessToken: String) {
eventSignal = eventSignal ?? .init()
webView = WKWebView(cookies: NetworkConfiguration.getCookiesFrom(accessToken: accessToken, type: "theme"))
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?.setThemeCookie(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<ThemeMarketViewEventType, Never>

init(eventSignal: PassthroughSubject<ThemeMarketViewEventType, Never>) {
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
}
4 changes: 4 additions & 0 deletions SNUTT-2022/SNUTT/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,10 @@
</dict>
<key>SNUEV_WEB_URL</key>
<string>https://${SNUEV_WEB_URL}</string>
<key>THEME_WEB_URL</key>
<string>https://${THEME_WEB_URL}</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>Save Screenshots to Photo Library</string>
<key>UIViewControllerBasedStatusBarAppearance</key>
<false/>
</dict>
Expand Down
10 changes: 10 additions & 0 deletions SNUTT-2022/SNUTT/Models/Theme.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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) {
Expand All @@ -37,5 +46,6 @@ extension Theme {
name = theme?.name ?? ""
colors = theme?.getLectureColorList() ?? []
isCustom = false
status = .basic
}
}
17 changes: 17 additions & 0 deletions SNUTT-2022/SNUTT/Repositories/Dto/ThemeDto.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,27 @@ 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()
Expand All @@ -36,5 +50,8 @@ extension ThemeDto {
colors = model.colors.map { ThemeColorDto(from: $0) }
isDefault = false
isCustom = model.isCustom
origin = nil
status = model.status?.rawValue ?? ""
publishInfo = nil
}
}
42 changes: 31 additions & 11 deletions SNUTT-2022/SNUTT/Repositories/NetworkConfiguration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,25 +12,45 @@ 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? {
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]
Expand Down
1 change: 1 addition & 0 deletions SNUTT-2022/SNUTT/Services/GlobalUIService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ 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() {
Expand Down
15 changes: 15 additions & 0 deletions SNUTT-2022/SNUTT/Services/ThemeService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -76,6 +78,17 @@ struct ThemeService: ThemeServiceProtocol {
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()
let themeList = dtos.map { Theme(from: $0) }
Expand Down Expand Up @@ -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 {}
Expand Down
2 changes: 1 addition & 1 deletion SNUTT-2022/SNUTT/ViewModels/MenuSheetViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down
Loading