Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
e174de0
Update project.pbxproj
mauricecarrier7 Oct 18, 2025
1ab0473
Revert "Update project.pbxproj"
mauricecarrier7 Oct 18, 2025
36755c8
Revert "Update project.pbxproj"
mauricecarrier7 Oct 18, 2025
03a89e6
Merge pull request #688 from ThePalaceProject/fix/resolve-tppbook-crash
mauricecarrier7 Oct 18, 2025
53d7822
Update project.pbxproj
mauricecarrier7 Oct 20, 2025
fcfd2d4
Merge pull request #689 from ThePalaceProject/build/create-hotfix-build
mauricecarrier7 Oct 20, 2025
6e3354b
PP-3130 Fallback on tenprint cover
mauricecarrier7 Oct 21, 2025
ab53892
PP-3133 Prevent crash when showing login screen from book detail view
mauricecarrier7 Oct 21, 2025
7f4e042
PP-3129 Add pagination to more view
mauricecarrier7 Oct 21, 2025
9a25376
PP-3131 Resolve crash when clearing cache and restoring images
mauricecarrier7 Oct 21, 2025
fbb49cf
PP-3117 Fix sort selection
mauricecarrier7 Oct 21, 2025
2132412
Update project.pbxproj
mauricecarrier7 Oct 21, 2025
b4ccd2a
Ensure Authentication flow is triggered when user isn't signed in and…
mauricecarrier7 Oct 21, 2025
7ab67a3
PP-3136 Update catalog on library switch, ensure login works
mauricecarrier7 Oct 21, 2025
4990674
Merge branch 'develop' into fix/hotfixes
mauricecarrier7 Oct 21, 2025
b53c69a
Update project.pbxproj
mauricecarrier7 Oct 21, 2025
fb7edb5
Update ios-audiobooktoolkit
mauricecarrier7 Oct 21, 2025
b8c40f7
Hide tabbar and navbar on first open of epubreader
mauricecarrier7 Oct 21, 2025
c9ab492
PP-3139 Paginate accounts list on first install
mauricecarrier7 Oct 21, 2025
04f6fc0
PP-3139: improve scrolling during pagination
mauricecarrier7 Oct 21, 2025
7743936
Resolve image caching crash on low ram devices
mauricecarrier7 Oct 21, 2025
5debf8a
Fix LCPStreamingPlayer playback issue
mauricecarrier7 Oct 21, 2025
578d44a
Update project.pbxproj
mauricecarrier7 Oct 21, 2025
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
20 changes: 4 additions & 16 deletions Palace.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -4731,10 +4731,7 @@
CODE_SIGN_IDENTITY = "Apple Distribution";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 390;
CURRENT_PROJECT_VERSION = 389;
CURRENT_PROJECT_VERSION = 390;
CURRENT_PROJECT_VERSION = 391;
CURRENT_PROJECT_VERSION = 394;
DEVELOPMENT_TEAM = 88CBA74T8K;
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 88CBA74T8K;
ENABLE_BITCODE = NO;
Expand Down Expand Up @@ -4793,10 +4790,7 @@
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES_ERROR;
CODE_SIGN_ENTITLEMENTS = Palace/SimplyE.entitlements;
CODE_SIGN_IDENTITY = "iPhone Distribution";
CURRENT_PROJECT_VERSION = 390;
CURRENT_PROJECT_VERSION = 389;
CURRENT_PROJECT_VERSION = 390;
CURRENT_PROJECT_VERSION = 391;
CURRENT_PROJECT_VERSION = 394;
DEVELOPMENT_TEAM = 88CBA74T8K;
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 88CBA74T8K;
ENABLE_BITCODE = NO;
Expand Down Expand Up @@ -4980,10 +4974,7 @@
CODE_SIGN_ENTITLEMENTS = Palace/PalaceDebug.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 390;
CURRENT_PROJECT_VERSION = 389;
CURRENT_PROJECT_VERSION = 390;
CURRENT_PROJECT_VERSION = 391;
CURRENT_PROJECT_VERSION = 394;
DEVELOPMENT_TEAM = "";
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
Expand Down Expand Up @@ -5044,10 +5035,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 390;
CURRENT_PROJECT_VERSION = 389;
CURRENT_PROJECT_VERSION = 390;
CURRENT_PROJECT_VERSION = 391;
CURRENT_PROJECT_VERSION = 394;
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 88CBA74T8K;
ENABLE_BITCODE = NO;
Expand Down
4 changes: 2 additions & 2 deletions Palace.xcodeproj/xcshareddata/xcschemes/Palace.xcscheme
Original file line number Diff line number Diff line change
Expand Up @@ -143,12 +143,12 @@
</EnvironmentVariables>
<AdditionalOptions>
<AdditionalOption
key = "MallocStackLogging"
key = "PrefersMallocStackLoggingLite"
value = ""
isEnabled = "YES">
</AdditionalOption>
<AdditionalOption
key = "PrefersMallocStackLoggingLite"
key = "MallocStackLogging"
value = ""
isEnabled = "YES">
</AdditionalOption>
Expand Down
8 changes: 0 additions & 8 deletions Palace/Accounts/Library/AccountsManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -257,14 +257,6 @@ let currentAccountIdentifierKey = "TPPCurrentAccountIdentifier"
}
}

for acct in newAccounts {
group.enter()
DispatchQueue.global(qos: .background).async {
acct.loadLogo()
group.leave()
}
}

group.notify(queue: .main) {
var mainFeed = URL(string: self.currentAccount?.catalogUrl ?? "")
if let cur = self.currentAccount, cur.details?.needsAgeCheck ?? false {
Expand Down
6 changes: 4 additions & 2 deletions Palace/AppInfrastructure/TPPAppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -223,14 +223,16 @@ extension TPPAppDelegate {
if needsAccount {
var nav: UINavigationController!
let accountList = TPPAccountList { account in
// Match CatalogView's Add Library flow: persist, switch account, update feed URL, notify, dismiss
if !TPPSettings.shared.settingsAccountIdsList.contains(account.uuid) {
TPPSettings.shared.settingsAccountIdsList.append(account.uuid)
}
AccountsManager.shared.currentAccount = account
if let urlString = account.catalogUrl, let url = URL(string: urlString) {
TPPSettings.shared.accountMainFeedURL = url
}
AccountsManager.shared.currentAccount = account

account.loadAuthenticationDocument { _ in }

NotificationCenter.default.post(name: .TPPCurrentAccountDidChange, object: nil)
nav?.dismiss(animated: true)
}
Expand Down
104 changes: 56 additions & 48 deletions Palace/Book/Models/TPPBook.swift
Original file line number Diff line number Diff line change
Expand Up @@ -557,10 +557,9 @@ extension TPPBook {

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

self.thumbnailImage = final
if let img = final {
self.thumbnailImage = image
if let img = image {
self.imageCache.set(img, for: self.identifier)
self.imageCache.set(img, for: thumbnailKey)
if self.coverImage == nil {
Expand Down Expand Up @@ -595,60 +594,69 @@ extension TPPBook {

// MARK: - Dominant Color (async, off main thread)
private extension TPPBook {
private static let colorProcessingQueue = DispatchQueue(label: "org.thepalaceproject.dominantcolor", qos: .utility)
private static let sharedCIContext: CIContext = {
guard let colorSpace = CGColorSpace(name: CGColorSpace.sRGB) else {
return CIContext()
}
return CIContext(options: [
.workingColorSpace: colorSpace,
.outputColorSpace: colorSpace,
.useSoftwareRenderer: false
])
}()

func updateDominantColor(using image: UIImage) {
let inputImage = image
DispatchQueue.global(qos: .userInitiated).async { [weak self] in
Self.colorProcessingQueue.async { [weak self] in
guard let self = self else { return }

guard let ciImage = CIImage(image: inputImage) else {
Log.debug(#file, "Failed to create CIImage from UIImage for book: \(self.identifier)")
return
}

guard !ciImage.extent.isEmpty else {
Log.debug(#file, "CIImage has empty extent for book: \(self.identifier)")
return
}

let filter = CIFilter.areaAverage()
filter.inputImage = ciImage
filter.extent = ciImage.extent
autoreleasepool {
guard let ciImage = CIImage(image: inputImage) else {
Log.debug(#file, "Failed to create CIImage from UIImage for book: \(self.identifier)")
return
}

guard let outputImage = filter.outputImage else {
Log.debug(#file, "Failed to generate output image from filter for book: \(self.identifier)")
return
}
guard !ciImage.extent.isEmpty else {
Log.debug(#file, "CIImage has empty extent for book: \(self.identifier)")
return
}

guard let colorSpace = CGColorSpace(name: CGColorSpace.sRGB) else {
Log.debug(#file, "Failed to create sRGB color space for book: \(self.identifier)")
return
}
let filter = CIFilter.areaAverage()
filter.inputImage = ciImage
filter.extent = ciImage.extent

var bitmap = [UInt8](repeating: 0, count: 4)
let context = CIContext(options: [
.workingColorSpace: colorSpace,
.outputColorSpace: colorSpace,
.useSoftwareRenderer: false
])

context.render(
outputImage,
toBitmap: &bitmap,
rowBytes: 4,
bounds: CGRect(x: 0, y: 0, width: 1, height: 1),
format: .RGBA8,
colorSpace: colorSpace
)
guard let outputImage = filter.outputImage else {
Log.debug(#file, "Failed to generate output image from filter for book: \(self.identifier)")
return
}

let color = UIColor(
red: CGFloat(bitmap[0]) / 255.0,
green: CGFloat(bitmap[1]) / 255.0,
blue: CGFloat(bitmap[2]) / 255.0,
alpha: CGFloat(bitmap[3]) / 255.0
)
guard let colorSpace = CGColorSpace(name: CGColorSpace.sRGB) else {
Log.debug(#file, "Failed to create sRGB color space for book: \(self.identifier)")
return
}

DispatchQueue.main.async {
self.dominantUIColor = color
var bitmap = [UInt8](repeating: 0, count: 4)

Self.sharedCIContext.render(
outputImage,
toBitmap: &bitmap,
rowBytes: 4,
bounds: CGRect(x: 0, y: 0, width: 1, height: 1),
format: .RGBA8,
colorSpace: colorSpace
)

let color = UIColor(
red: CGFloat(bitmap[0]) / 255.0,
green: CGFloat(bitmap[1]) / 255.0,
blue: CGFloat(bitmap[2]) / 255.0,
alpha: CGFloat(bitmap[3]) / 255.0
)

DispatchQueue.main.async {
self.dominantUIColor = color
}
}
}
}
Expand Down
22 changes: 14 additions & 8 deletions Palace/Book/Models/TPPBookCoverRegistry.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,19 @@ actor TPPBookCoverRegistry {
}

func coverImage(for book: TPPBook) async -> UIImage? {
guard let url = book.imageURL else { return await thumbnailImage(for: book) }
return await fetchImage(from: url, for: book, isCover: true)
if let url = book.imageURL, let image = await fetchImage(from: url, for: book, isCover: true) {
return image
}

return await thumbnailImage(for: book)
}

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

return await fetchImage(from: url, for: book, isCover: false)
return await placeholder(for: book)
}

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

let task = Task<UIImage?, Never> { [weak self] in
guard let self else { return UIImage() }
guard let self else { return nil }

do {
let (data, _) = try await URLSession.shared.data(from: url)
guard let image = UIImage(data: data) else { return nil }
guard let image = UIImage(data: data) else {
Log.error(#file, "Failed to decode image data from URL: \(url)")
return nil
}

self.imageCache.set(image, for: key as String, expiresIn: nil)
return image
} catch {
Log.error(#file, "Failed to fetch image: \(error.localizedDescription)")
Log.error(#file, "Failed to fetch image from \(url): \(error.localizedDescription)")
return nil
}
}
Expand Down
23 changes: 22 additions & 1 deletion Palace/Book/UI/AudiobookSampleToolbar.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ struct AudiobookSampleToolbar: View {
@ObservedObject var player: AudiobookSamplePlayer

private var book: TPPBook
private let imageLoader = AsyncImage(image: UIImage(systemName: "book.closed.fill") ?? UIImage())
private let imageLoader: AsyncImage
private let toolbarHeight: CGFloat = 70
private let toolbarPadding: CGFloat = 5
private let imageViewHeight: CGFloat = 70
Expand All @@ -25,10 +25,31 @@ struct AudiobookSampleToolbar: View {
self.book = book
guard let sample = book.sample as? AudiobookSample else { return nil }
player = AudiobookSamplePlayer(sample: sample)

let placeholderImage = Self.generatePlaceholder(for: book)
imageLoader = AsyncImage(image: placeholderImage)

if let imageURL = book.imageThumbnailURL ?? book.imageURL {
imageLoader.loadImage(url: imageURL)
}
}

private static func generatePlaceholder(for book: TPPBook) -> UIImage {
let size = CGSize(width: 80, height: 120)
let format = UIGraphicsImageRendererFormat()
format.scale = UIScreen.main.scale
return UIGraphicsImageRenderer(size: size, format: format)
.image { ctx in
if let view = NYPLTenPrintCoverView(
frame: CGRect(origin: .zero, size: size),
withTitle: book.title,
withAuthor: book.authors ?? "Unknown Author",
withScale: 0.4
) {
view.layer.render(in: ctx.cgContext)
}
}
}

var body: some View {
HStack {
Expand Down
Loading