Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 4 additions & 0 deletions TiebaPure.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
objects = {

/* Begin PBXBuildFile section */
B1C2D3E4F5061728394A5B6C /* SavedThreadMedia.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1B2C3D4E5F60718293A4B5C /* SavedThreadMedia.swift */; };
03BFC1CB27519F2A9851619C /* ContentDraft.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A1C1C285AED2131947564A /* ContentDraft.swift */; };
043AF9BB1E2EF30D61B1DC63 /* SwiftDataOrderedCollectionPersistence.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67285F50B29BA765B74B5271 /* SwiftDataOrderedCollectionPersistence.swift */; };
06132F36C724DF5F6A7409D5 /* ThreadDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1626D4B0DD88E88576EBC2EB /* ThreadDetailView.swift */; };
Expand Down Expand Up @@ -258,6 +259,7 @@
/* End PBXCopyFilesBuildPhase section */

/* Begin PBXFileReference section */
A1B2C3D4E5F60718293A4B5C /* SavedThreadMedia.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SavedThreadMedia.swift; sourceTree = "<group>"; };
00339148CB9511CB5F6A624F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
0060EA54C03E209E46FB2C40 /* ForumThreadsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ForumThreadsView.swift; sourceTree = "<group>"; };
00A0FAAA54B2D05276E46330 /* SessionExpirationMonitorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionExpirationMonitorTests.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -637,6 +639,7 @@
638DEF254549D0D4DE625487 /* ReadingPreferences.swift */,
FDE02417BF13C2E0A1D7824E /* RecentForum.swift */,
0DB03A9A81B77E7ECBD00B30 /* SavedThread.swift */,
A1B2C3D4E5F60718293A4B5C /* SavedThreadMedia.swift */,
24D4BAC59CD64A3B8F236258 /* SearchHistory.swift */,
16DDB351392A69BC42810B9E /* SearchResult.swift */,
6BBA032F531024778CFC5C0A /* SecureFilePersistence.swift */,
Expand Down Expand Up @@ -1219,6 +1222,7 @@
A4FCD6601BAA005BF0F264D0 /* RootView.swift in Sources */,
C02D20CEC3FEDED464983D96 /* SafariActionPayload.swift in Sources */,
1E84B72ACDF13678A6BE6CB8 /* SavedThread.swift in Sources */,
B1C2D3E4F5061728394A5B6C /* SavedThreadMedia.swift in Sources */,
985218E469AF18B5378E8549 /* SavedThreadsView.swift in Sources */,
CAA7C941E2D8AAA65B342CBA /* SearchHistory.swift in Sources */,
8028A9708BDE36C7BB815CEF /* SearchResult.swift in Sources */,
Expand Down
16 changes: 3 additions & 13 deletions TiebaPure/App/RootView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -431,24 +431,18 @@ private struct TabSelectionObserver: UIViewControllerRepresentable {
visited: inout Set<ObjectIdentifier>
) -> UITabBarController? {
guard let controller else { return nil }

let identifier = ObjectIdentifier(controller)
guard visited.insert(identifier).inserted else { return nil }

guard visited.insert(ObjectIdentifier(controller)).inserted else { return nil }
if let tabBarController = controller as? UITabBarController {
return tabBarController
}

if let found = findTabBarController(from: controller.presentedViewController, visited: &visited) {
return found
}

for child in controller.children {
if let found = findTabBarController(from: child, visited: &visited) {
return found
}
}

return nil
}
}
Expand All @@ -467,9 +461,7 @@ private struct TabSelectionObserver: UIViewControllerRepresentable {
return
}
detach()
if tabBarController.delegate !== self {
previousDelegate = tabBarController.delegate
}
previousDelegate = tabBarController.delegate
observedController = tabBarController
tabBarController.delegate = self
}
Expand All @@ -495,9 +487,7 @@ private struct TabSelectionObserver: UIViewControllerRepresentable {
if tabBarController.selectedViewController === viewController,
tabBarController.viewControllers?.first === viewController {
let callback = onReselectHome
DispatchQueue.main.async {
callback()
}
DispatchQueue.main.async(execute: callback)
}
return true
}
Expand Down
6 changes: 6 additions & 0 deletions TiebaPure/App/TiebaPureApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ struct TiebaPureApp: App {
@StateObject private var environment = AppEnvironment.live()
@StateObject private var appearanceStore = AppAppearanceStore.live()
@StateObject private var readingPreferencesStore = ReadingPreferencesStore.live()
@StateObject private var readerFontStore = ReaderFontStore.shared

var body: some Scene {
WindowGroup {
Expand All @@ -30,8 +31,13 @@ struct TiebaPureApp: App {
.environmentObject(environment.forumSignCoordinator)
.environmentObject(appearanceStore)
.environmentObject(readingPreferencesStore)
.environmentObject(readerFontStore)
.environment(\.readingPreferences, readingPreferencesStore.preferences)
.preferredColorScheme(appearanceStore.selection.preferredColorScheme)
.task(id: readerFontStore.isReady) {
guard readerFontStore.isReady else { return }
readingPreferencesStore.reconcileAvailableImportedFonts(readerFontStore.entries)
}
}
}
}
39 changes: 34 additions & 5 deletions TiebaPure/Core/UI/AvatarView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,15 @@ enum TiebaImageSourcePolicy {
static func urls(primary: URL?, fallback: URL? = nil) -> [URL] {
var result: [URL] = []
for candidate in [primary, fallback].compactMap({ $0 }) {
guard let safeURL = TiebaURL.image(candidate.absoluteString),
result.contains(safeURL) == false else {
let safeURL: URL
if SavedThreadMediaAuthorization.shared.allows(candidate) {
safeURL = candidate
} else if let validated = TiebaURL.image(candidate.absoluteString) {
safeURL = validated
} else {
continue
}
guard result.contains(safeURL) == false else { continue }
result.append(safeURL)
}
return result
Expand Down Expand Up @@ -271,8 +276,13 @@ actor TiebaImagePipeline {
) async throws -> UIImage {
// Download the validated URL, not the caller's: validation may have
// upgraded a legacy http source to https.
guard let safeURL = TiebaURL.image(url.absoluteString),
redirectScope.allows(safeURL) || Self.isSyntheticFixtureURL(safeURL) else {
let safeURL: URL
if SavedThreadMediaAuthorization.shared.allows(url) {
safeURL = url
} else if let validated = TiebaURL.image(url.absoluteString),
redirectScope.allows(validated) || Self.isSyntheticFixtureURL(validated) {
safeURL = validated
} else {
throw TiebaImagePipelineError.invalidURL
}
guard TiebaImageSourcePolicy.isSyntheticFailureURL(url) == false else {
Expand Down Expand Up @@ -386,7 +396,7 @@ actor TiebaImagePipeline {
} catch {
result = .failure(error)
}
await self.completeSharedRequest(
self.completeSharedRequest(
request,
operationID: operationID,
result: result
Expand Down Expand Up @@ -445,6 +455,25 @@ actor TiebaImagePipeline {
session: URLSession,
onProgress: (@Sendable (BoundedURLSessionProgress) async -> Void)?
) async throws -> UIImage {
if request.url.isFileURL {
guard SavedThreadMediaAuthorization.shared.allows(request.url) else {
throw TiebaImagePipelineError.invalidURL
}
let data = try Data(contentsOf: request.url, options: [.mappedIfSafe, .uncached])
guard data.isEmpty == false,
data.count <= maximumImageBytes,
let image = decodedImage(
from: data,
targetPixelSize: request.targetPixelSize
) else {
throw TiebaImagePipelineError.invalidImageData
}
await onProgress?(BoundedURLSessionProgress(
receivedBytes: data.count,
expectedBytes: data.count
))
return image
}
var attempt = 0
while true {
do {
Expand Down
9 changes: 7 additions & 2 deletions TiebaPure/Core/UI/InteractiveNavigationPop.swift
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,12 @@ enum NativeEdgePopGestureActivationPolicy {

enum NavigationPopGestureControlHostingPolicy {
static func requiresController(systemMajorVersion: Int, isEnabled: Bool) -> Bool {
isEnabled == false || systemMajorVersion < 26
_ = systemMajorVersion
// A normal NavigationStack must keep complete ownership of its native
// recognizers. Hosting a controller merely to force-enable the edge
// recognizer raced SwiftUI's push transition on iOS 16-18, especially
// when two navigation stacks were visible in an iPad split view.
return isEnabled == false
}
}

Expand Down Expand Up @@ -171,7 +176,7 @@ private struct NativeNavigationPopGestureControl: UIViewControllerRepresentable
private func applyRequestedState() {
guard requestedEnabled == false else {
restorePopGesturesIfNeeded()
enableNativeEdgePopIfNeeded()
updateDiagnostics(using: navigationController)
return
}
guard let navigationController else { return }
Expand Down
11 changes: 10 additions & 1 deletion TiebaPure/Domain/Models/ContentBlock.swift
Original file line number Diff line number Diff line change
Expand Up @@ -107,8 +107,15 @@ enum ContentBlock: Equatable, Codable, Sendable {
struct VoiceContent: Equatable, Codable, Sendable {
let md5: String
let durationMilliseconds: Int
var localURL: URL?
var offlineOnly: Bool?

init?(md5: String, durationMilliseconds: Int) {
init?(
md5: String,
durationMilliseconds: Int,
localURL: URL? = nil,
offlineOnly: Bool? = nil
) {
let normalizedMD5 = md5
.trimmingCharacters(in: .whitespacesAndNewlines)
.lowercased()
Expand All @@ -119,6 +126,8 @@ struct VoiceContent: Equatable, Codable, Sendable {

self.md5 = normalizedMD5
self.durationMilliseconds = max(durationMilliseconds, 0)
self.localURL = localURL
self.offlineOnly = offlineOnly
}

private static func isLowercaseHexDigit(_ byte: UInt8) -> Bool {
Expand Down
Loading
Loading