-
Notifications
You must be signed in to change notification settings - Fork 8
Add Font Provider Before Backend #632
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
base: main
Are you sure you want to change the base?
Changes from 3 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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 }) | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @anli5005 This isn't what you mean but WrappedUnitView cannot display before we have a loaded model |
||
|
|
||
| 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 | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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!) | ||
|
Comment on lines
+19
to
+25
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not a fan of trusting that neither is
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's the "trust me bro" guarantee, but yes I (or someone on iOS) can look into. The thought here is that WrappedUnitView shouldn't even be showing for a given model if that model doesn't have a lottie animation loaded, same thing with the font provider. But there does exist a mechanism to guarantee, so you're correct in that regard. |
||
| .playbackMode(vm.activeUnit == unit ? vm.activeUnitPlaybackMode : .paused(at:.currentFrame)) | ||
| .currentProgress(vm.activeUnit == unit ? normalizedProgress : 0) | ||
| .rotation3DEffect( | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
await MainActor.runOr just mark the entire
Taskas@MainActor