Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
fa315de
PP-3077 Hide tabbar when hiding navbar
mauricecarrier7 Oct 15, 2025
880bba4
PP-3022 Make hold available when in first position
mauricecarrier7 Oct 16, 2025
9cf83ef
Update project.pbxproj
mauricecarrier7 Oct 16, 2025
3718b25
Update project.pbxproj
mauricecarrier7 Oct 16, 2025
09a4579
Merge pull request #685 from ThePalaceProject/task/PP-3022-holds-posi…
mauricecarrier7 Oct 16, 2025
1be1aa1
resolves updateDominateColor crash
mauricecarrier7 Oct 18, 2025
8b69236
Merge pull request #686 from ThePalaceProject/fix/resolve-tppbook-crash
mauricecarrier7 Oct 18, 2025
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
943f54d
Merge branch 'hotfix' into develop
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
a14cbaf
Merge pull request #692 from ThePalaceProject/fix/hotfixes
mauricecarrier7 Oct 21, 2025
3e57131
PP-3140: Properly order pagination
mauricecarrier7 Oct 22, 2025
90690f0
Fix NSCache crash by adding memory limits and proper cost tracking
mauricecarrier7 Oct 22, 2025
fdc05ea
Fix string buffer overflow crashes in PDF extraction and logging
mauricecarrier7 Oct 22, 2025
f39e3a3
Revert xcodeproj signing changes (were for debugging only)
mauricecarrier7 Oct 22, 2025
c13a2df
Create CACHE_SAFETY_VERIFICATION.md
mauricecarrier7 Oct 22, 2025
16cb593
Update project.pbxproj
mauricecarrier7 Oct 22, 2025
4ae9ab6
Merge pull request #693 from ThePalaceProject/fix/hotfixes
mauricecarrier7 Oct 22, 2025
323bced
Update project.pbxproj
mauricecarrier7 Oct 22, 2025
0576f90
Merge pull request #694 from ThePalaceProject/fix/hotfixes
mauricecarrier7 Oct 22, 2025
b86b46a
PP-3142 - properly manage nav/tab bar dismissal
mauricecarrier7 Oct 23, 2025
35ee22a
Update project.pbxproj
mauricecarrier7 Oct 23, 2025
c37c8b9
Merge pull request #695 from ThePalaceProject/fix/hotfixes
mauricecarrier7 Oct 23, 2025
97f1a5b
Hide nav bar on epub
mauricecarrier7 Oct 24, 2025
8da5686
Merge pull request #696 from ThePalaceProject/fix/hotfixes
mauricecarrier7 Oct 24, 2025
66c4ee2
Delete CACHE_SAFETY_VERIFICATION.md
mauricecarrier7 Oct 27, 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
16 changes: 8 additions & 8 deletions Palace.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -4731,7 +4731,7 @@
CODE_SIGN_IDENTITY = "Apple Distribution";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 389;
CURRENT_PROJECT_VERSION = 398;
DEVELOPMENT_TEAM = 88CBA74T8K;
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 88CBA74T8K;
ENABLE_BITCODE = NO;
Expand All @@ -4753,7 +4753,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 2.0.0;
MARKETING_VERSION = 2.0.2;
PRODUCT_BUNDLE_IDENTIFIER = org.thepalaceproject.palace;
PRODUCT_MODULE_NAME = Palace;
PRODUCT_NAME = "Palace-noDRM";
Expand Down Expand Up @@ -4790,7 +4790,7 @@
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES_ERROR;
CODE_SIGN_ENTITLEMENTS = Palace/SimplyE.entitlements;
CODE_SIGN_IDENTITY = "iPhone Distribution";
CURRENT_PROJECT_VERSION = 389;
CURRENT_PROJECT_VERSION = 398;
DEVELOPMENT_TEAM = 88CBA74T8K;
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 88CBA74T8K;
ENABLE_BITCODE = NO;
Expand All @@ -4812,7 +4812,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 2.0.0;
MARKETING_VERSION = 2.0.2;
PRODUCT_BUNDLE_IDENTIFIER = org.thepalaceproject.palace;
PRODUCT_MODULE_NAME = Palace;
PRODUCT_NAME = "Palace-noDRM";
Expand Down Expand Up @@ -4974,7 +4974,7 @@
CODE_SIGN_ENTITLEMENTS = Palace/PalaceDebug.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 389;
CURRENT_PROJECT_VERSION = 398;
DEVELOPMENT_TEAM = "";
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
Expand All @@ -5000,7 +5000,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 2.0.0;
MARKETING_VERSION = 2.0.2;
PRODUCT_BUNDLE_IDENTIFIER = org.thepalaceproject.palace;
PROVISIONING_PROFILE_SPECIFIER = "";
RUN_CLANG_STATIC_ANALYZER = YES;
Expand Down Expand Up @@ -5035,7 +5035,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 389;
CURRENT_PROJECT_VERSION = 398;
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 88CBA74T8K;
ENABLE_BITCODE = NO;
Expand All @@ -5062,7 +5062,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 2.0.0;
MARKETING_VERSION = 2.0.2;
PRODUCT_BUNDLE_IDENTIFIER = org.thepalaceproject.palace;
PROVISIONING_PROFILE_SPECIFIER = "";
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "App Store";
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
1 change: 0 additions & 1 deletion Palace/AppInfrastructure/NavigationCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,6 @@ final class NavigationCoordinator: ObservableObject {

struct CatalogLaneFilterState {
let appliedSelections: Set<String>
let currentSort: String // Store as string to avoid enum duplication
let facetGroups: [CatalogFilterGroup]
}

Expand Down
9 changes: 4 additions & 5 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 Expand Up @@ -306,9 +308,6 @@ final class MemoryPressureMonitor {
URLCache.shared.removeAllCachedResponses()
TPPNetworkExecutor.shared.clearCache()

ImageCache.shared.clear()
GeneralCache<String, Data>.clearAllCaches()

MyBooksDownloadCenter.shared.pauseAllDownloads()

self.reclaimDiskSpaceIfNeeded(minimumFreeMegabytes: 256)
Expand Down
88 changes: 59 additions & 29 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,38 +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 }

let ciImage = CIImage(image: inputImage)
let filter = CIFilter.areaAverage()
filter.inputImage = ciImage
filter.extent = ciImage?.extent ?? .zero

guard let outputImage = filter.outputImage else { return }

var bitmap = [UInt8](repeating: 0, count: 4)
let context = CIContext(options: [CIContextOption.useSoftwareRenderer: false])
context.render(
outputImage,
toBitmap: &bitmap,
rowBytes: 4,
bounds: CGRect(x: 0, y: 0, width: 1, height: 1),
format: .RGBA8,
colorSpace: nil
)
autoreleasepool {
guard let ciImage = CIImage(image: inputImage) else {
Log.debug(#file, "Failed to create CIImage from UIImage 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 !ciImage.extent.isEmpty else {
Log.debug(#file, "CIImage has empty extent for book: \(self.identifier)")
return
}

DispatchQueue.main.async {
self.dominantUIColor = color
let filter = CIFilter.areaAverage()
filter.inputImage = ciImage
filter.extent = ciImage.extent

guard let outputImage = filter.outputImage else {
Log.debug(#file, "Failed to generate output image from filter 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
}

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
Loading