diff --git a/PennMobile/Home/HomeViewModel.swift b/PennMobile/Home/HomeViewModel.swift index 8e442271..1be701e9 100644 --- a/PennMobile/Home/HomeViewModel.swift +++ b/PennMobile/Home/HomeViewModel.swift @@ -7,6 +7,7 @@ // import SwiftUI +import PennMobileShared extension Optional { mutating func makeNilIfError() where Wrapped == Result { @@ -195,31 +196,19 @@ extension Optional { _ = await announcementsTask - async let wrappedTask = Task { - let url = URL(string: "https://pennmobile.org/api/wrapped/semester/2025S-public/")! - guard let req = try? await URLRequest(url: url, mode: .accessToken) else { - DispatchQueue.main.async { - self.data.wrapped = .success(WrappedModel(semester: "", pages: [])) + async let wrappedTask = Task { @MainActor in + let url = URL(string: "https://pennmobile.org/api/wrapped/semester/current/")! + do { + let req = try await URLRequest(url: url, mode: .accessToken) + let (data, response) = try await URLSession.shared.data(for: req) + guard let http = response as? HTTPURLResponse, http.statusCode == 200 else { + throw NetworkingError.serverError } - return + let wrapped = try JSONDecoder().decode(WrappedModel.self, from: data) + self.data.wrapped = .success(wrapped) + } catch { + self.data.wrapped = .failure(error) } - let task = URLSession.shared.dataTask(with: url) { data, response, _ in - guard let httpResponse = response as? HTTPURLResponse, let data, httpResponse.statusCode == 200 else { - DispatchQueue.main.async { - self.data.wrapped = .success(WrappedModel(semester: "", pages: [])) - } - return - } - DispatchQueue.main.async { - do { - let wrapped = try JSONDecoder().decode(WrappedModel.self, from: data) - self.data.wrapped = .success(wrapped) - } catch { - self.data.wrapped = .failure(error) - } - } - } - task.resume() } _ = await wrappedTask diff --git a/PennMobile/Wrapped/Fonts/Poppins/OFL.txt b/PennMobile/Wrapped/Fonts/Poppins/OFL.txt deleted file mode 100644 index 3e92931f..00000000 --- a/PennMobile/Wrapped/Fonts/Poppins/OFL.txt +++ /dev/null @@ -1,93 +0,0 @@ -Copyright 2020 The Poppins Project Authors (https://github.com/itfoundry/Poppins) - -This Font Software is licensed under the SIL Open Font License, Version 1.1. -This license is copied below, and is also available with a FAQ at: -https://openfontlicense.org - - ------------------------------------------------------------ -SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 ------------------------------------------------------------ - -PREAMBLE -The goals of the Open Font License (OFL) are to stimulate worldwide -development of collaborative font projects, to support the font creation -efforts of academic and linguistic communities, and to provide a free and -open framework in which fonts may be shared and improved in partnership -with others. - -The OFL allows the licensed fonts to be used, studied, modified and -redistributed freely as long as they are not sold by themselves. The -fonts, including any derivative works, can be bundled, embedded, -redistributed and/or sold with any software provided that any reserved -names are not used by derivative works. The fonts and derivatives, -however, cannot be released under any other type of license. The -requirement for fonts to remain under this license does not apply -to any document created using the fonts or their derivatives. - -DEFINITIONS -"Font Software" refers to the set of files released by the Copyright -Holder(s) under this license and clearly marked as such. This may -include source files, build scripts and documentation. - -"Reserved Font Name" refers to any names specified as such after the -copyright statement(s). - -"Original Version" refers to the collection of Font Software components as -distributed by the Copyright Holder(s). - -"Modified Version" refers to any derivative made by adding to, deleting, -or substituting -- in part or in whole -- any of the components of the -Original Version, by changing formats or by porting the Font Software to a -new environment. - -"Author" refers to any designer, engineer, programmer, technical -writer or other person who contributed to the Font Software. - -PERMISSION & CONDITIONS -Permission is hereby granted, free of charge, to any person obtaining -a copy of the Font Software, to use, study, copy, merge, embed, modify, -redistribute, and sell modified and unmodified copies of the Font -Software, subject to the following conditions: - -1) Neither the Font Software nor any of its individual components, -in Original or Modified Versions, may be sold by itself. - -2) Original or Modified Versions of the Font Software may be bundled, -redistributed and/or sold with any software, provided that each copy -contains the above copyright notice and this license. These can be -included either as stand-alone text files, human-readable headers or -in the appropriate machine-readable metadata fields within text or -binary files as long as those fields can be easily viewed by the user. - -3) No Modified Version of the Font Software may use the Reserved Font -Name(s) unless explicit written permission is granted by the corresponding -Copyright Holder. This restriction only applies to the primary font name as -presented to the users. - -4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font -Software shall not be used to promote, endorse or advertise any -Modified Version, except to acknowledge the contribution(s) of the -Copyright Holder(s) and the Author(s) or with their explicit written -permission. - -5) The Font Software, modified or unmodified, in part or in whole, -must be distributed entirely under this license, and must not be -distributed under any other license. The requirement for fonts to -remain under this license does not apply to any document created -using the Font Software. - -TERMINATION -This license becomes null and void if any of the above conditions are -not met. - -DISCLAIMER -THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT -OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE -COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL -DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM -OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/PennMobile/Wrapped/Fonts/Poppins/Poppins-Black.ttf b/PennMobile/Wrapped/Fonts/Poppins/Poppins-Black.ttf deleted file mode 100644 index 71c0f995..00000000 Binary files a/PennMobile/Wrapped/Fonts/Poppins/Poppins-Black.ttf and /dev/null differ diff --git a/PennMobile/Wrapped/Fonts/Poppins/Poppins-BlackItalic.ttf b/PennMobile/Wrapped/Fonts/Poppins/Poppins-BlackItalic.ttf deleted file mode 100644 index 7aeb58bd..00000000 Binary files a/PennMobile/Wrapped/Fonts/Poppins/Poppins-BlackItalic.ttf and /dev/null differ diff --git a/PennMobile/Wrapped/Fonts/Poppins/Poppins-Bold.ttf b/PennMobile/Wrapped/Fonts/Poppins/Poppins-Bold.ttf deleted file mode 100644 index 00559eeb..00000000 Binary files a/PennMobile/Wrapped/Fonts/Poppins/Poppins-Bold.ttf and /dev/null differ diff --git a/PennMobile/Wrapped/Fonts/Poppins/Poppins-BoldItalic.ttf b/PennMobile/Wrapped/Fonts/Poppins/Poppins-BoldItalic.ttf deleted file mode 100644 index e61e8e88..00000000 Binary files a/PennMobile/Wrapped/Fonts/Poppins/Poppins-BoldItalic.ttf and /dev/null differ diff --git a/PennMobile/Wrapped/Fonts/Poppins/Poppins-ExtraBold.ttf b/PennMobile/Wrapped/Fonts/Poppins/Poppins-ExtraBold.ttf deleted file mode 100644 index df709360..00000000 Binary files a/PennMobile/Wrapped/Fonts/Poppins/Poppins-ExtraBold.ttf and /dev/null differ diff --git a/PennMobile/Wrapped/Fonts/Poppins/Poppins-ExtraBoldItalic.ttf b/PennMobile/Wrapped/Fonts/Poppins/Poppins-ExtraBoldItalic.ttf deleted file mode 100644 index 14d2b375..00000000 Binary files a/PennMobile/Wrapped/Fonts/Poppins/Poppins-ExtraBoldItalic.ttf and /dev/null differ diff --git a/PennMobile/Wrapped/Fonts/Poppins/Poppins-ExtraLight.ttf b/PennMobile/Wrapped/Fonts/Poppins/Poppins-ExtraLight.ttf deleted file mode 100644 index e76ec69a..00000000 Binary files a/PennMobile/Wrapped/Fonts/Poppins/Poppins-ExtraLight.ttf and /dev/null differ diff --git a/PennMobile/Wrapped/Fonts/Poppins/Poppins-ExtraLightItalic.ttf b/PennMobile/Wrapped/Fonts/Poppins/Poppins-ExtraLightItalic.ttf deleted file mode 100644 index 89513d94..00000000 Binary files a/PennMobile/Wrapped/Fonts/Poppins/Poppins-ExtraLightItalic.ttf and /dev/null differ diff --git a/PennMobile/Wrapped/Fonts/Poppins/Poppins-Italic.ttf b/PennMobile/Wrapped/Fonts/Poppins/Poppins-Italic.ttf deleted file mode 100644 index 12b7b3c4..00000000 Binary files a/PennMobile/Wrapped/Fonts/Poppins/Poppins-Italic.ttf and /dev/null differ diff --git a/PennMobile/Wrapped/Fonts/Poppins/Poppins-Light.ttf b/PennMobile/Wrapped/Fonts/Poppins/Poppins-Light.ttf deleted file mode 100644 index bc36bcc2..00000000 Binary files a/PennMobile/Wrapped/Fonts/Poppins/Poppins-Light.ttf and /dev/null differ diff --git a/PennMobile/Wrapped/Fonts/Poppins/Poppins-LightItalic.ttf b/PennMobile/Wrapped/Fonts/Poppins/Poppins-LightItalic.ttf deleted file mode 100644 index 9e70be6a..00000000 Binary files a/PennMobile/Wrapped/Fonts/Poppins/Poppins-LightItalic.ttf and /dev/null differ diff --git a/PennMobile/Wrapped/Fonts/Poppins/Poppins-Medium.ttf b/PennMobile/Wrapped/Fonts/Poppins/Poppins-Medium.ttf deleted file mode 100644 index 6bcdcc27..00000000 Binary files a/PennMobile/Wrapped/Fonts/Poppins/Poppins-Medium.ttf and /dev/null differ diff --git a/PennMobile/Wrapped/Fonts/Poppins/Poppins-MediumItalic.ttf b/PennMobile/Wrapped/Fonts/Poppins/Poppins-MediumItalic.ttf deleted file mode 100644 index be67410f..00000000 Binary files a/PennMobile/Wrapped/Fonts/Poppins/Poppins-MediumItalic.ttf and /dev/null differ diff --git a/PennMobile/Wrapped/Fonts/Poppins/Poppins-Regular.ttf b/PennMobile/Wrapped/Fonts/Poppins/Poppins-Regular.ttf deleted file mode 100644 index 9f0c71b7..00000000 Binary files a/PennMobile/Wrapped/Fonts/Poppins/Poppins-Regular.ttf and /dev/null differ diff --git a/PennMobile/Wrapped/Fonts/Poppins/Poppins-SemiBold.ttf b/PennMobile/Wrapped/Fonts/Poppins/Poppins-SemiBold.ttf deleted file mode 100644 index 74c726e3..00000000 Binary files a/PennMobile/Wrapped/Fonts/Poppins/Poppins-SemiBold.ttf and /dev/null differ diff --git a/PennMobile/Wrapped/Fonts/Poppins/Poppins-SemiBoldItalic.ttf b/PennMobile/Wrapped/Fonts/Poppins/Poppins-SemiBoldItalic.ttf deleted file mode 100644 index 3e6c9422..00000000 Binary files a/PennMobile/Wrapped/Fonts/Poppins/Poppins-SemiBoldItalic.ttf and /dev/null differ diff --git a/PennMobile/Wrapped/Fonts/Poppins/Poppins-Thin.ttf b/PennMobile/Wrapped/Fonts/Poppins/Poppins-Thin.ttf deleted file mode 100644 index 03e73661..00000000 Binary files a/PennMobile/Wrapped/Fonts/Poppins/Poppins-Thin.ttf and /dev/null differ diff --git a/PennMobile/Wrapped/Fonts/Poppins/Poppins-ThinItalic.ttf b/PennMobile/Wrapped/Fonts/Poppins/Poppins-ThinItalic.ttf deleted file mode 100644 index e26db5dd..00000000 Binary files a/PennMobile/Wrapped/Fonts/Poppins/Poppins-ThinItalic.ttf and /dev/null differ diff --git a/PennMobile/Wrapped/WrappedModel.swift b/PennMobile/Wrapped/WrappedModel.swift index 75c71312..bd04fe5f 100644 --- a/PennMobile/Wrapped/WrappedModel.swift +++ b/PennMobile/Wrapped/WrappedModel.swift @@ -7,33 +7,50 @@ // import Foundation +import Lottie +import CoreText public struct WrappedModel: Decodable { let semester: String // Designed to be optional for forwards compatability // (making pages an optional field was a design discussion for disabling wrapped between semesters) var pages: [WrappedUnit] + let fonts: [String: URL] + + var fontProvider: WrappedFontProvider? public init(from decoder: Decoder) throws { let values = try decoder.container(keyedBy: CodingKeys.self) self.semester = try values.decode(String.self, forKey: .semester) self.pages = try values.decodeIfPresent([WrappedUnit].self, forKey: .pages) ?? [] + self.fonts = try values.decodeIfPresent([String: URL].self, forKey: .fonts) ?? [:] } - public init(semester: String, pages: [WrappedUnit]) { + public init(semester: String, pages: [WrappedUnit], fonts: [String: URL] = [:]) { self.pages = pages self.semester = semester + self.fonts = fonts } enum CodingKeys: String, CodingKey { - case pages, semester + case pages, semester, fonts } mutating func loadModel() async { - var newPages: [WrappedUnit] = await self.pages.asyncMap { page in - var newPage = page - await newPage.loadAnimation() - return newPage + let newPages = await withTaskGroup(of: WrappedUnit.self, returning: [WrappedUnit].self) { group in + for page in self.pages { + var newPage = page + group.addTask { + await newPage.loadAnimation() + return newPage + } + } + + var results: [WrappedUnit] = [] + for await result in group { + results.append(result) + } + return results } // Fail if duplicate ID (note: this silently fails) @@ -47,5 +64,49 @@ public struct WrappedModel: Decodable { } self.pages = newPages.filter({ $0.lottie != nil }).sorted(by: { $0.id < $1.id }) + + let fontFiles: [String: Data] = await withTaskGroup(of: (String, Data?).self, returning: [String: Data].self) { group in + for (name, downloadURL) in self.fonts { + group.addTask { + let request = URLRequest(url: downloadURL) + guard let (localURL, response) = try? await URLSession.shared.download(for: request) else { + return (name, nil) + } + return (name, try? Data(contentsOf: localURL)) + } + } + + var results: [String: Data] = [:] + for await result in group { + guard result.1 != nil else { continue } + results[result.0] = result.1 + } + return results + } + + self.fontProvider = WrappedFontProvider(from: fontFiles) + } +} + +class WrappedFontProvider: AnimationFontProvider, Equatable { + static func == (lhs: WrappedFontProvider, rhs: WrappedFontProvider) -> Bool { + lhs === rhs + } + + let fonts: [String: CGFont] + init(from files: [String: Data]) { + var fonts: [String: CGFont] = [:] + for (name, data) in files { + if let provider = CGDataProvider(data: data as CFData) { + fonts[name] = CGFont(provider) + } + } + self.fonts = fonts + } + + func fontFor(family: String, size: CGFloat) -> CTFont? { + guard let font = fonts[family] else { return nil } + let ctFont = CTFontCreateWithGraphicsFont(font, size, nil, nil) + return ctFont } } diff --git a/PennMobile/Wrapped/WrappedUnitView.swift b/PennMobile/Wrapped/WrappedUnitView.swift index a67833da..f0e82c40 100644 --- a/PennMobile/Wrapped/WrappedUnitView.swift +++ b/PennMobile/Wrapped/WrappedUnitView.swift @@ -16,14 +16,13 @@ struct WrappedUnitView: View { var body: some View { // https://github.com/pennlabs/penn-mobile-ios/pull/602#discussion_r2070443421 - // Note, if we get to this point, unit.lottie really should not be nil + // Note, if we get to this point, unit.lottie really should not be nil, neither should the fontProvider let progressFraction = (CGFloat(vm.activeUnitProgress) * unit.time!) / unit.lottie!.duration let normalizedProgress = progressFraction - floor(progressFraction) GeometryReader { proxy in LottieView(animation: unit.lottie!) .textProvider(DictionaryTextProvider(unit.values)) - // TODO: Define a custom font provider conforming class that fetches fonts dynamically from backend. - .fontProvider(DefaultFontProvider()) + .fontProvider(experienceVM.model.fontProvider!) .playbackMode(vm.activeUnit == unit ? vm.activeUnitPlaybackMode : .paused(at:.currentFrame)) .currentProgress(vm.activeUnit == unit ? normalizedProgress : 0) .rotation3DEffect(