From c9d0fda98e48c7adacb3d4f8be74fa2356e5ed92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=9F=E3=81=A4=E3=81=9D=E3=82=99=E3=81=86?= Date: Fri, 14 May 2021 02:41:20 +0800 Subject: [PATCH] NewDawn feature --- EhPanda/App/Extensions.swift | 13 +++ EhPanda/App/Tools/Parser.swift | 26 +++-- EhPanda/App/en.lproj/Localizable.strings | 6 + EhPanda/App/ja.lproj/Localizable.strings | 9 ++ EhPanda/App/zh-Hans.lproj/Localizable.strings | 9 ++ EhPanda/DataFlow/AppState.swift | 31 +++-- EhPanda/DataFlow/Store.swift | 8 +- EhPanda/Models/Misc.swift | 47 ++++++++ EhPanda/Models/Setting.swift | 1 + EhPanda/Models/User.swift | 2 + EhPanda/View/Home/HomeView.swift | 32 ++++-- EhPanda/View/Setting/AccountSettingView.swift | 4 + EhPanda/View/Tools/NewDawnView.swift | 106 +++++++++++++----- 13 files changed, 227 insertions(+), 67 deletions(-) diff --git a/EhPanda/App/Extensions.swift b/EhPanda/App/Extensions.swift index 4a7556be..2979c5e3 100644 --- a/EhPanda/App/Extensions.swift +++ b/EhPanda/App/Extensions.swift @@ -169,3 +169,16 @@ extension Bundle { return nil } } + +extension Int { + var withComma: String? { + let decimalFormatter = NumberFormatter() + decimalFormatter.numberStyle = .decimal + decimalFormatter.locale = Locale.current + + let string = decimalFormatter.string( + from: self as NSNumber + ) + return string + } +} diff --git a/EhPanda/App/Tools/Parser.swift b/EhPanda/App/Tools/Parser.swift index 3222712a..8f368aea 100644 --- a/EhPanda/App/Tools/Parser.swift +++ b/EhPanda/App/Tools/Parser.swift @@ -595,12 +595,18 @@ struct Parser { extension Parser { // MARK: Greeting static func parseGreeting(_ doc: HTMLDocument) throws -> Greeting { - func trimString(_ string: String) -> String { - string - .replacingOccurrences(of: ",", with: "") - .replacingOccurrences(of: "!", with: "") - .replacingOccurrences(of: "and", with: "") - .trimmingCharacters(in: .whitespacesAndNewlines) + func trimString(_ string: String) -> String? { + if string.contains("EXP") { + return "EXP" + } else if string.contains("Credits") { + return "Credits" + } else if string.contains("GP") { + return "GP" + } else if string.contains("Hath") { + return "Hath" + } else { + return nil + } } func trimInt(_ value: String) -> Int? { @@ -633,13 +639,17 @@ extension Parser { let removeText = String(text.prefix(upTo: range.upperBound)) if value != gainedValues.first { - gainedTypes.append(trimString(removeText)) + if let text = trimString(removeText) { + gainedTypes.append(text) + } } text = text.replacingOccurrences(of: removeText, with: "") if value == gainedValues.last { - gainedTypes.append(trimString(text)) + if let text = trimString(text) { + gainedTypes.append(text) + } } } diff --git a/EhPanda/App/en.lproj/Localizable.strings b/EhPanda/App/en.lproj/Localizable.strings index 06ea1fb9..0431b5dd 100644 --- a/EhPanda/App/en.lproj/Localizable.strings +++ b/EhPanda/App/en.lproj/Localizable.strings @@ -14,3 +14,9 @@ // MARK: User "favoriteNameByDev" = "Favorite"; "all_appendedByDev" = "All"; + +// MARK: NewDawnView +"GAINCONTENT_START" = "You gain "; +"GAINCONTENT_SEPARATOR" = ", "; +"GAINCONTENT_AND" = " and "; +"GAINCONTENT_END" = "!"; diff --git a/EhPanda/App/ja.lproj/Localizable.strings b/EhPanda/App/ja.lproj/Localizable.strings index a2745a45..422d774a 100644 --- a/EhPanda/App/ja.lproj/Localizable.strings +++ b/EhPanda/App/ja.lproj/Localizable.strings @@ -147,6 +147,15 @@ "Disable uploader filter" = "アップローダフィルターを無効化"; "Disable tags filter" = "タグフィルターを無効化"; +// MARK: NewDawnView +"Show new dawn greeting" = "夜明けの挨拶を表示"; +"It is the dawn of a new day!" = "新しい一日の夜明けです!"; +"Reflecting on your journey so far, you find that you are a little wiser." = "今までの歩みを振り返り、少し賢くなった気がする。"; +"GAINCONTENT_START" = ""; +"GAINCONTENT_SEPARATOR" = "、"; +"GAINCONTENT_AND" = "と"; +"GAINCONTENT_END" = "を手に入れた!"; + // MARK: HomeListType "Search" = "検索"; "Frontpage" = "ホーム"; diff --git a/EhPanda/App/zh-Hans.lproj/Localizable.strings b/EhPanda/App/zh-Hans.lproj/Localizable.strings index 12df52a9..d4586e1a 100644 --- a/EhPanda/App/zh-Hans.lproj/Localizable.strings +++ b/EhPanda/App/zh-Hans.lproj/Localizable.strings @@ -147,6 +147,15 @@ "Disable uploader filter" = "禁用上传者筛选"; "Disable tags filter" = "禁用标签筛选"; +// MARK: NewDawnView +"Show new dawn greeting" = "显示黎明问候"; +"It is the dawn of a new day!" = "又是新一天的黎明!"; +"Reflecting on your journey so far, you find that you are a little wiser." = "回顾至今的历程,发觉自己更睿智了一些。"; +"GAINCONTENT_START" = "你获得了"; +"GAINCONTENT_SEPARATOR" = "、"; +"GAINCONTENT_AND" = "和"; +"GAINCONTENT_END" = "!"; + // MARK: HomeListType "Search" = "搜索"; "Frontpage" = "主页"; diff --git a/EhPanda/DataFlow/AppState.swift b/EhPanda/DataFlow/AppState.swift index 18f3a07f..747cf50c 100644 --- a/EhPanda/DataFlow/AppState.swift +++ b/EhPanda/DataFlow/AppState.swift @@ -44,6 +44,7 @@ extension AppState { struct Settings { var userInfoLoading = false var favoriteNamesLoading = false + var greetingLoading = false @FileStorage(directory: .cachesDirectory, fileName: "user.json") var user: User? @@ -66,6 +67,20 @@ extension AppState { self.user?.currentCredits = currentCredits } } + + mutating func insertGreeting(greeting: Greeting) { + guard let currDate = greeting.updateTime + else { return } + + if let prevGreeting = user?.greeting, + let prevDate = prevGreeting.updateTime, + prevDate < currDate + { + user?.greeting = greeting + } else if user?.greeting == nil { + user?.greeting = greeting + } + } } } @@ -73,8 +88,6 @@ extension AppState { // MARK: HomeInfo struct HomeInfo { var searchKeyword = "" - var greeting: Greeting? - var greetingLoading = false var searchItems: [Manga]? var searchLoading = false @@ -136,20 +149,6 @@ extension AppState { return tmp } - mutating func insertGreeting(greeting: Greeting) { - guard let currDate = self.greeting?.updateTime - else { return } - - if let prevGreeting = self.greeting, - let prevDate = prevGreeting.updateTime, - prevDate < currDate - { - self.greeting = greeting - } else if self.greeting == nil { - self.greeting = greeting - } - } - mutating func insertSearchItems(mangas: [Manga]) { mangas.forEach { manga in if searchItems?.contains(manga) == false { diff --git a/EhPanda/DataFlow/Store.swift b/EhPanda/DataFlow/Store.swift index 79c5d827..333d0c26 100644 --- a/EhPanda/DataFlow/Store.swift +++ b/EhPanda/DataFlow/Store.swift @@ -105,16 +105,16 @@ final class Store: ObservableObject { // MARK: Fetch Data case .fetchGreeting: if !didLogin && isTokenMatched { break } - if appState.homeInfo.greetingLoading { break } - appState.homeInfo.greetingLoading = true + if appState.settings.greetingLoading { break } + appState.settings.greetingLoading = true appCommand = FetchGreetingCommand() case .fetchGreetingDone(let result): - appState.homeInfo.greetingLoading = false + appState.settings.greetingLoading = false switch result { case .success(let greeting): - appState.homeInfo.insertGreeting(greeting: greeting) + appState.settings.insertGreeting(greeting: greeting) case .failure(let error): print(error) } diff --git a/EhPanda/Models/Misc.swift b/EhPanda/Models/Misc.swift index 2d2b95c8..18d275ce 100644 --- a/EhPanda/Models/Misc.swift +++ b/EhPanda/Models/Misc.swift @@ -43,6 +43,53 @@ struct Greeting: Identifiable, Codable, Equatable { var gainedHath: Int? var updateTime: Date? + var strings: [String] { + var strings = [String]() + + if let exp = gainedEXP?.withComma { + strings.append(exp + " EXP") + } + if let credits = gainedCredits?.withComma { + strings.append(credits + " Credits") + } + if let galleryPoint = gainedGP?.withComma { + strings.append(galleryPoint + " GP") + } + if let hath = gainedHath?.withComma { + strings.append(hath + " Hath") + } + + return strings + } + + var gainContent: String? { + guard !strings.isEmpty else { return nil } + + var base = "GAINCONTENT_START".localized() + + if strings.count == 1 { + base += strings[0] + } else { + let stringsToJoin = strings.count > 2 + ? strings.dropLast() : strings + + base += stringsToJoin + .joined( + separator: + "GAINCONTENT_SEPARATOR" + .localized() + ) + if strings.count > 2 { + base += "GAINCONTENT_AND".localized() + base += strings[strings.count - 1] + } + } + + base += "GAINCONTENT_END".localized() + + return base + } + var gainedNothing: Bool { [ gainedEXP, diff --git a/EhPanda/Models/Setting.swift b/EhPanda/Models/Setting.swift index ef3c670f..a5fd0fb2 100644 --- a/EhPanda/Models/Setting.swift +++ b/EhPanda/Models/Setting.swift @@ -16,6 +16,7 @@ struct Setting: Codable { setGalleryType(with: galleryType) } } + var showNewDawnGreeting = false // General var detectGalleryFromPasteboard = false diff --git a/EhPanda/Models/User.swift b/EhPanda/Models/User.swift index 1776b998..f679c0fb 100644 --- a/EhPanda/Models/User.swift +++ b/EhPanda/Models/User.swift @@ -24,6 +24,8 @@ struct User: Codable { var currentGP: String? var currentCredits: String? + var greeting: Greeting? + var apiuid: String { getCookieValue( url: Defaults.URL.host.safeURL(), diff --git a/EhPanda/View/Home/HomeView.swift b/EhPanda/View/Home/HomeView.swift index 7f09ff88..9733c6cc 100644 --- a/EhPanda/View/Home/HomeView.swift +++ b/EhPanda/View/Home/HomeView.swift @@ -43,7 +43,7 @@ struct HomeView: View, StoreAccessor { perform: onFavoritesIndexChange ) .onChange( - of: homeInfo.greeting, + of: user?.greeting, perform: onReceiveGreeting ) .onAppear(perform: onListAppear) @@ -55,7 +55,6 @@ struct HomeView: View, StoreAccessor { } } .navigationViewStyle(StackNavigationViewStyle()) - .fullScreenCover(item: $greeting, content: NewDawnView.init) .sheet(item: environmentBinding.homeViewSheetState) { item in switch item { case .setting: @@ -72,6 +71,11 @@ struct HomeView: View, StoreAccessor { .preferredColorScheme(colorScheme) .blur(radius: environment.blurRadius) .allowsHitTesting(environment.isAppUnlocked) + case .newDawn: + NewDawnView(greeting: greeting) + .preferredColorScheme(colorScheme) + .blur(radius: environment.blurRadius) + .allowsHitTesting(environment.isAppUnlocked) } } .onAppear(perform: onAppear) @@ -236,8 +240,7 @@ private extension HomeView { func onAppear() { detectPasteboard() -// greeting = Greeting() -// fetchGreetingIfNeeded() + fetchGreetingIfNeeded() } func onListAppear() { if settings.user == nil { @@ -253,7 +256,10 @@ private extension HomeView { fetchFrontpageItemsIfNeeded() } func onBecomeActive() { - detectPasteboard() + if vcsCount == 1 { + detectPasteboard() + fetchGreetingIfNeeded() + } } func onHomeListTypeChange(_ type: HomeListType) { switch type { @@ -278,6 +284,7 @@ private extension HomeView { !greeting.gainedNothing { self.greeting = greeting + toggleNewDawn() } } func onFavoritesIndexChange(_ : Int) { @@ -421,12 +428,14 @@ private extension HomeView { return false } - if let greeting = homeInfo.greeting { - if verifyDate(with: greeting.updateTime) { + if setting?.showNewDawnGreeting == true { + if let greeting = user?.greeting { + if verifyDate(with: greeting.updateTime) { + fetchGreeting() + } + } else { fetchGreeting() } - } else { - fetchGreeting() } } func fetchFrontpageItemsIfNeeded() { @@ -449,6 +458,10 @@ private extension HomeView { fetchFavoritesItems() } } + + func toggleNewDawn() { + store.dispatch(.toggleHomeViewSheetState(state: .newDawn)) + } } // MARK: GenericList @@ -672,4 +685,5 @@ enum HomeViewSheetState: Identifiable { case setting case filter + case newDawn } diff --git a/EhPanda/View/Setting/AccountSettingView.swift b/EhPanda/View/Setting/AccountSettingView.swift index a070964e..393ab043 100644 --- a/EhPanda/View/Setting/AccountSettingView.swift +++ b/EhPanda/View/Setting/AccountSettingView.swift @@ -46,6 +46,10 @@ struct AccountSettingView: View { .withArrow() Button("Manage tags subscription", action: onMyTagsTap) .withArrow() + Toggle( + "Show new dawn greeting", + isOn: settingBinding.showNewDawnGreeting + ) } .foregroundColor(.primary) } diff --git a/EhPanda/View/Tools/NewDawnView.swift b/EhPanda/View/Tools/NewDawnView.swift index ae05f683..92d30e5e 100644 --- a/EhPanda/View/Tools/NewDawnView.swift +++ b/EhPanda/View/Tools/NewDawnView.swift @@ -7,7 +7,10 @@ import SwiftUI +private let sunWidth = screenW * (isPad ? 0.5 : 0.6) + struct NewDawnView: View { + @Environment(\.colorScheme) private var colorScheme @State private var rotationAngle: Double = 0 @State private var greeting: Greeting? @State private var timer = Timer @@ -20,15 +23,16 @@ struct NewDawnView: View { private let offset = screenW * 0.2 - init(greeting: Greeting) { - self.greeting = greeting + init(greeting: Greeting?) { + _greeting = State(initialValue: greeting) } + // MARK: NewDawnView var body: some View { ZStack { LinearGradient( gradient: Gradient( - colors: [Color(.systemTeal), Color(.systemIndigo)] + colors: gradientColors ), startPoint: .top, endPoint: .bottom @@ -37,9 +41,14 @@ struct NewDawnView: View { VStack { HStack { Spacer() - SunView() - .rotationEffect(Angle(degrees: rotationAngle)) - .offset(x: offset, y: -offset) + ZStack { + SunView() + if colorScheme == .light { + SunBeamView() + .rotationEffect(Angle(degrees: rotationAngle)) + } + } + .offset(x: offset, y: -offset) } Spacer() } @@ -51,16 +60,19 @@ struct NewDawnView: View { font: .largeTitle ) TextView( - text: "Reflecting on your journey so far, you find that you are a little wiser.", + text: "Reflecting on your journey so far, " + + "you find that you are a little wiser.", font: .title2 ) } - TextView( - text: "You gain 30 EXP, 10,393 Credits, 10,000 GP and 11 Hath!", - font: .title3, - fontWeight: .bold, - lineLimit: 3 - ) + if let content = greeting?.gainContent { + TextView( + text: content, + font: .title3, + fontWeight: .bold, + lineLimit: 3 + ) + } } .padding() } @@ -69,6 +81,17 @@ struct NewDawnView: View { } private extension NewDawnView { + var gradientColors: [Color] { + let teal = Color(.systemTeal) + let indigo = Color(.systemIndigo) + + if colorScheme == .light { + return [teal, indigo] + } else { + return [Color(.systemGray5), Color(.systemGray2)] + } + } + func onReceiveTimer(_: Date) { withAnimation { rotationAngle += 1 @@ -76,6 +99,7 @@ private extension NewDawnView { } } +// MARK: TextView private struct TextView: View { @Environment(\.colorScheme) private var colorScheme private let text: String @@ -101,19 +125,33 @@ private struct TextView: View { var body: some View { HStack { - Text(text) + Text(text.localized()) .fontWeight(fontWeight) .font(font) .lineLimit(lineLimit) - .foregroundColor(reversePrimary) + .foregroundColor(.white) Spacer() } } } +// MARK: SunView private struct SunView: View { - private let width = screenW * 0.75 - private var offset: CGFloat { width / 2 + 70 } + private let width = sunWidth + + var body: some View { + ZStack { + Circle() + .foregroundColor(.yellow) + .frame(width: width, height: width) + } + } +} + +// MARK: SunBeamView +private struct SunBeamView: View { + private let width = sunWidth + private var offset: CGFloat { width / 1.2 } private var evenOffset: CGFloat { offset / sqrt(2) } private var sizes: [CGSize] { [ @@ -132,35 +170,43 @@ private struct SunView: View { ] var body: some View { - ZStack { - Circle() - .foregroundColor(.yellow) - .frame(width: width, height: width) - ForEach(0..<8, id: \.self) { index in - SunBeamView() - .rotationEffect(Angle(degrees: degrees[index])) - .offset(sizes[index]) - } + ForEach(0..<8, id: \.self) { index in + SunBeamItem() + .rotationEffect(Angle(degrees: degrees[index])) + .offset(sizes[index]) } } } -private struct SunBeamView: View { - private let width = screenW * 0.05 +// MARK: SunBeamItem +private struct SunBeamItem: View { + private let width = sunWidth / 10 private var height: CGFloat { width * 5 } + private var cornerRadius: CGFloat { + width / 3 + } var body: some View { Rectangle() .foregroundColor(.yellow) .frame(width: width, height: height) - .cornerRadius(5) + .cornerRadius(cornerRadius) } } struct NewDawnView_Previews: PreviewProvider { static var previews: some View { - NewDawnView(greeting: Greeting()) + var greeting = Greeting() + greeting.gainedEXP = 10 + greeting.gainedCredits = 10000 + greeting.gainedGP = 10000 + greeting.gainedHath = 10 + + return Text("") + .sheet(isPresented: .constant(true), content: { + NewDawnView(greeting: greeting) + }) } }