Skip to content

Commit 1b0b22a

Browse files
Merge pull request #697 from ThePalaceProject/release/2.0.2
Release/2.0.2
2 parents a892748 + 66c4ee2 commit 1b0b22a

35 files changed

+856
-296
lines changed

Palace.xcodeproj/project.pbxproj

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4731,7 +4731,7 @@
47314731
CODE_SIGN_IDENTITY = "Apple Distribution";
47324732
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
47334733
CODE_SIGN_STYLE = Manual;
4734-
CURRENT_PROJECT_VERSION = 389;
4734+
CURRENT_PROJECT_VERSION = 398;
47354735
DEVELOPMENT_TEAM = 88CBA74T8K;
47364736
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 88CBA74T8K;
47374737
ENABLE_BITCODE = NO;
@@ -4753,7 +4753,7 @@
47534753
"$(inherited)",
47544754
"@executable_path/Frameworks",
47554755
);
4756-
MARKETING_VERSION = 2.0.0;
4756+
MARKETING_VERSION = 2.0.2;
47574757
PRODUCT_BUNDLE_IDENTIFIER = org.thepalaceproject.palace;
47584758
PRODUCT_MODULE_NAME = Palace;
47594759
PRODUCT_NAME = "Palace-noDRM";
@@ -4790,7 +4790,7 @@
47904790
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES_ERROR;
47914791
CODE_SIGN_ENTITLEMENTS = Palace/SimplyE.entitlements;
47924792
CODE_SIGN_IDENTITY = "iPhone Distribution";
4793-
CURRENT_PROJECT_VERSION = 389;
4793+
CURRENT_PROJECT_VERSION = 398;
47944794
DEVELOPMENT_TEAM = 88CBA74T8K;
47954795
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 88CBA74T8K;
47964796
ENABLE_BITCODE = NO;
@@ -4812,7 +4812,7 @@
48124812
"$(inherited)",
48134813
"@executable_path/Frameworks",
48144814
);
4815-
MARKETING_VERSION = 2.0.0;
4815+
MARKETING_VERSION = 2.0.2;
48164816
PRODUCT_BUNDLE_IDENTIFIER = org.thepalaceproject.palace;
48174817
PRODUCT_MODULE_NAME = Palace;
48184818
PRODUCT_NAME = "Palace-noDRM";
@@ -4974,7 +4974,7 @@
49744974
CODE_SIGN_ENTITLEMENTS = Palace/PalaceDebug.entitlements;
49754975
CODE_SIGN_IDENTITY = "Apple Development";
49764976
CODE_SIGN_STYLE = Manual;
4977-
CURRENT_PROJECT_VERSION = 389;
4977+
CURRENT_PROJECT_VERSION = 398;
49784978
DEVELOPMENT_TEAM = "";
49794979
ENABLE_BITCODE = NO;
49804980
FRAMEWORK_SEARCH_PATHS = (
@@ -5000,7 +5000,7 @@
50005000
"$(inherited)",
50015001
"@executable_path/Frameworks",
50025002
);
5003-
MARKETING_VERSION = 2.0.0;
5003+
MARKETING_VERSION = 2.0.2;
50045004
PRODUCT_BUNDLE_IDENTIFIER = org.thepalaceproject.palace;
50055005
PROVISIONING_PROFILE_SPECIFIER = "";
50065006
RUN_CLANG_STATIC_ANALYZER = YES;
@@ -5035,7 +5035,7 @@
50355035
CODE_SIGN_IDENTITY = "Apple Development";
50365036
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
50375037
CODE_SIGN_STYLE = Manual;
5038-
CURRENT_PROJECT_VERSION = 389;
5038+
CURRENT_PROJECT_VERSION = 398;
50395039
DEVELOPMENT_TEAM = "";
50405040
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 88CBA74T8K;
50415041
ENABLE_BITCODE = NO;
@@ -5062,7 +5062,7 @@
50625062
"$(inherited)",
50635063
"@executable_path/Frameworks",
50645064
);
5065-
MARKETING_VERSION = 2.0.0;
5065+
MARKETING_VERSION = 2.0.2;
50665066
PRODUCT_BUNDLE_IDENTIFIER = org.thepalaceproject.palace;
50675067
PROVISIONING_PROFILE_SPECIFIER = "";
50685068
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "App Store";

Palace.xcodeproj/xcshareddata/xcschemes/Palace.xcscheme

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -143,12 +143,12 @@
143143
</EnvironmentVariables>
144144
<AdditionalOptions>
145145
<AdditionalOption
146-
key = "MallocStackLogging"
146+
key = "PrefersMallocStackLoggingLite"
147147
value = ""
148148
isEnabled = "YES">
149149
</AdditionalOption>
150150
<AdditionalOption
151-
key = "PrefersMallocStackLoggingLite"
151+
key = "MallocStackLogging"
152152
value = ""
153153
isEnabled = "YES">
154154
</AdditionalOption>

Palace/Accounts/Library/AccountsManager.swift

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -257,14 +257,6 @@ let currentAccountIdentifierKey = "TPPCurrentAccountIdentifier"
257257
}
258258
}
259259

260-
for acct in newAccounts {
261-
group.enter()
262-
DispatchQueue.global(qos: .background).async {
263-
acct.loadLogo()
264-
group.leave()
265-
}
266-
}
267-
268260
group.notify(queue: .main) {
269261
var mainFeed = URL(string: self.currentAccount?.catalogUrl ?? "")
270262
if let cur = self.currentAccount, cur.details?.needsAgeCheck ?? false {

Palace/AppInfrastructure/NavigationCoordinator.swift

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,6 @@ final class NavigationCoordinator: ObservableObject {
190190

191191
struct CatalogLaneFilterState {
192192
let appliedSelections: Set<String>
193-
let currentSort: String // Store as string to avoid enum duplication
194193
let facetGroups: [CatalogFilterGroup]
195194
}
196195

Palace/AppInfrastructure/TPPAppDelegate.swift

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -223,14 +223,16 @@ extension TPPAppDelegate {
223223
if needsAccount {
224224
var nav: UINavigationController!
225225
let accountList = TPPAccountList { account in
226-
// Match CatalogView's Add Library flow: persist, switch account, update feed URL, notify, dismiss
227226
if !TPPSettings.shared.settingsAccountIdsList.contains(account.uuid) {
228227
TPPSettings.shared.settingsAccountIdsList.append(account.uuid)
229228
}
230-
AccountsManager.shared.currentAccount = account
231229
if let urlString = account.catalogUrl, let url = URL(string: urlString) {
232230
TPPSettings.shared.accountMainFeedURL = url
233231
}
232+
AccountsManager.shared.currentAccount = account
233+
234+
account.loadAuthenticationDocument { _ in }
235+
234236
NotificationCenter.default.post(name: .TPPCurrentAccountDidChange, object: nil)
235237
nav?.dismiss(animated: true)
236238
}
@@ -306,9 +308,6 @@ final class MemoryPressureMonitor {
306308
URLCache.shared.removeAllCachedResponses()
307309
TPPNetworkExecutor.shared.clearCache()
308310

309-
ImageCache.shared.clear()
310-
GeneralCache<String, Data>.clearAllCaches()
311-
312311
MyBooksDownloadCenter.shared.pauseAllDownloads()
313312

314313
self.reclaimDiskSpaceIfNeeded(minimumFreeMegabytes: 256)

Palace/Book/Models/TPPBook.swift

Lines changed: 59 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -557,10 +557,9 @@ extension TPPBook {
557557

558558
TPPBookCoverRegistryBridge.shared.thumbnailImageForBook(self) { [weak self] image in
559559
guard let self = self else { return }
560-
let final = image ?? UIImage(systemName: "book")
561560

562-
self.thumbnailImage = final
563-
if let img = final {
561+
self.thumbnailImage = image
562+
if let img = image {
564563
self.imageCache.set(img, for: self.identifier)
565564
self.imageCache.set(img, for: thumbnailKey)
566565
if self.coverImage == nil {
@@ -595,38 +594,69 @@ extension TPPBook {
595594

596595
// MARK: - Dominant Color (async, off main thread)
597596
private extension TPPBook {
597+
private static let colorProcessingQueue = DispatchQueue(label: "org.thepalaceproject.dominantcolor", qos: .utility)
598+
private static let sharedCIContext: CIContext = {
599+
guard let colorSpace = CGColorSpace(name: CGColorSpace.sRGB) else {
600+
return CIContext()
601+
}
602+
return CIContext(options: [
603+
.workingColorSpace: colorSpace,
604+
.outputColorSpace: colorSpace,
605+
.useSoftwareRenderer: false
606+
])
607+
}()
608+
598609
func updateDominantColor(using image: UIImage) {
599610
let inputImage = image
600-
DispatchQueue.global(qos: .userInitiated).async { [weak self] in
611+
Self.colorProcessingQueue.async { [weak self] in
601612
guard let self = self else { return }
602613

603-
let ciImage = CIImage(image: inputImage)
604-
let filter = CIFilter.areaAverage()
605-
filter.inputImage = ciImage
606-
filter.extent = ciImage?.extent ?? .zero
607-
608-
guard let outputImage = filter.outputImage else { return }
609-
610-
var bitmap = [UInt8](repeating: 0, count: 4)
611-
let context = CIContext(options: [CIContextOption.useSoftwareRenderer: false])
612-
context.render(
613-
outputImage,
614-
toBitmap: &bitmap,
615-
rowBytes: 4,
616-
bounds: CGRect(x: 0, y: 0, width: 1, height: 1),
617-
format: .RGBA8,
618-
colorSpace: nil
619-
)
614+
autoreleasepool {
615+
guard let ciImage = CIImage(image: inputImage) else {
616+
Log.debug(#file, "Failed to create CIImage from UIImage for book: \(self.identifier)")
617+
return
618+
}
620619

621-
let color = UIColor(
622-
red: CGFloat(bitmap[0]) / 255.0,
623-
green: CGFloat(bitmap[1]) / 255.0,
624-
blue: CGFloat(bitmap[2]) / 255.0,
625-
alpha: CGFloat(bitmap[3]) / 255.0
626-
)
620+
guard !ciImage.extent.isEmpty else {
621+
Log.debug(#file, "CIImage has empty extent for book: \(self.identifier)")
622+
return
623+
}
627624

628-
DispatchQueue.main.async {
629-
self.dominantUIColor = color
625+
let filter = CIFilter.areaAverage()
626+
filter.inputImage = ciImage
627+
filter.extent = ciImage.extent
628+
629+
guard let outputImage = filter.outputImage else {
630+
Log.debug(#file, "Failed to generate output image from filter for book: \(self.identifier)")
631+
return
632+
}
633+
634+
guard let colorSpace = CGColorSpace(name: CGColorSpace.sRGB) else {
635+
Log.debug(#file, "Failed to create sRGB color space for book: \(self.identifier)")
636+
return
637+
}
638+
639+
var bitmap = [UInt8](repeating: 0, count: 4)
640+
641+
Self.sharedCIContext.render(
642+
outputImage,
643+
toBitmap: &bitmap,
644+
rowBytes: 4,
645+
bounds: CGRect(x: 0, y: 0, width: 1, height: 1),
646+
format: .RGBA8,
647+
colorSpace: colorSpace
648+
)
649+
650+
let color = UIColor(
651+
red: CGFloat(bitmap[0]) / 255.0,
652+
green: CGFloat(bitmap[1]) / 255.0,
653+
blue: CGFloat(bitmap[2]) / 255.0,
654+
alpha: CGFloat(bitmap[3]) / 255.0
655+
)
656+
657+
DispatchQueue.main.async {
658+
self.dominantUIColor = color
659+
}
630660
}
631661
}
632662
}

Palace/Book/Models/TPPBookCoverRegistry.swift

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,19 @@ actor TPPBookCoverRegistry {
1313
}
1414

1515
func coverImage(for book: TPPBook) async -> UIImage? {
16-
guard let url = book.imageURL else { return await thumbnailImage(for: book) }
17-
return await fetchImage(from: url, for: book, isCover: true)
16+
if let url = book.imageURL, let image = await fetchImage(from: url, for: book, isCover: true) {
17+
return image
18+
}
19+
20+
return await thumbnailImage(for: book)
1821
}
1922

2023
func thumbnailImage(for book: TPPBook) async -> UIImage? {
21-
guard let url = book.imageThumbnailURL else {
22-
return await placeholder(for: book)
24+
if let url = book.imageThumbnailURL, let image = await fetchImage(from: url, for: book, isCover: false) {
25+
return image
2326
}
2427

25-
return await fetchImage(from: url, for: book, isCover: false)
28+
return await placeholder(for: book)
2629
}
2730

2831
private func fetchImage(from url: URL, for book: TPPBook, isCover: Bool) async -> UIImage? {
@@ -36,16 +39,19 @@ actor TPPBookCoverRegistry {
3639
}
3740

3841
let task = Task<UIImage?, Never> { [weak self] in
39-
guard let self else { return UIImage() }
42+
guard let self else { return nil }
4043

4144
do {
4245
let (data, _) = try await URLSession.shared.data(from: url)
43-
guard let image = UIImage(data: data) else { return nil }
46+
guard let image = UIImage(data: data) else {
47+
Log.error(#file, "Failed to decode image data from URL: \(url)")
48+
return nil
49+
}
4450

4551
self.imageCache.set(image, for: key as String, expiresIn: nil)
4652
return image
4753
} catch {
48-
Log.error(#file, "Failed to fetch image: \(error.localizedDescription)")
54+
Log.error(#file, "Failed to fetch image from \(url): \(error.localizedDescription)")
4955
return nil
5056
}
5157
}

Palace/Book/UI/AudiobookSampleToolbar.swift

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ struct AudiobookSampleToolbar: View {
1414
@ObservedObject var player: AudiobookSamplePlayer
1515

1616
private var book: TPPBook
17-
private let imageLoader = AsyncImage(image: UIImage(systemName: "book.closed.fill") ?? UIImage())
17+
private let imageLoader: AsyncImage
1818
private let toolbarHeight: CGFloat = 70
1919
private let toolbarPadding: CGFloat = 5
2020
private let imageViewHeight: CGFloat = 70
@@ -25,10 +25,31 @@ struct AudiobookSampleToolbar: View {
2525
self.book = book
2626
guard let sample = book.sample as? AudiobookSample else { return nil }
2727
player = AudiobookSamplePlayer(sample: sample)
28+
29+
let placeholderImage = Self.generatePlaceholder(for: book)
30+
imageLoader = AsyncImage(image: placeholderImage)
31+
2832
if let imageURL = book.imageThumbnailURL ?? book.imageURL {
2933
imageLoader.loadImage(url: imageURL)
3034
}
3135
}
36+
37+
private static func generatePlaceholder(for book: TPPBook) -> UIImage {
38+
let size = CGSize(width: 80, height: 120)
39+
let format = UIGraphicsImageRendererFormat()
40+
format.scale = UIScreen.main.scale
41+
return UIGraphicsImageRenderer(size: size, format: format)
42+
.image { ctx in
43+
if let view = NYPLTenPrintCoverView(
44+
frame: CGRect(origin: .zero, size: size),
45+
withTitle: book.title,
46+
withAuthor: book.authors ?? "Unknown Author",
47+
withScale: 0.4
48+
) {
49+
view.layer.render(in: ctx.cgContext)
50+
}
51+
}
52+
}
3253

3354
var body: some View {
3455
HStack {

0 commit comments

Comments
 (0)