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
2 changes: 1 addition & 1 deletion Sources/LCP/LCPError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import Foundation
import ReadiumShared

public enum LCPError: Error {
public enum LCPError: Error, Sendable {
/// The license could not be retrieved because the passphrase is unknown.
case missingPassphrase

Expand Down
33 changes: 22 additions & 11 deletions Sources/Navigator/Decorator/DecorableNavigator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ public protocol DecorableNavigator {
public typealias DecorationGroup = String

/// Holds the metadata about a decoration activation interaction.
public struct OnDecorationActivatedEvent {
public struct OnDecorationActivatedEvent: Sendable {
/// Activated decoration.
public let decoration: Decoration
/// Name of the group the decoration belongs to.
Expand All @@ -60,7 +60,7 @@ public struct OnDecorationActivatedEvent {
/// a discrete `locator` in the publication.
///
/// For example, decorations can be used to draw highlights, images or buttons.
public struct Decoration: Hashable, JSONObjectEncodable {
public struct Decoration: Hashable, JSONObjectEncodable, Sendable {
/// An identifier for this decoration. It must be unique in the group the decoration is applied to.
public var id: Id

Expand All @@ -70,14 +70,16 @@ public struct Decoration: Hashable, JSONObjectEncodable {
/// Declares the look and feel of the decoration.
public var style: Style

/// Additional context data specific to a reading app. Readium does not use it.
public var userInfo: [AnyHashable: AnyHashable]
private let _userInfo: [String: AnySendableHashable]
public var userInfo: [String: AnyHashable] {
_userInfo.mapValues(\.asAnyHashable)
}

public init(id: Id, locator: Locator, style: Style, userInfo: [AnyHashable: AnyHashable] = [:]) {
public init(id: Id, locator: Locator, style: Style, userInfo: [String: any Sendable & Hashable] = [:]) {
self.id = id
self.style = style
self.locator = locator
self.userInfo = userInfo
_userInfo = userInfo.mapValues { AnySendableHashable($0) }
}

/// Unique identifier for a decoration.
Expand All @@ -87,7 +89,7 @@ public struct Decoration: Hashable, JSONObjectEncodable {
///
/// It is media type agnostic, meaning that each Navigator will translate the style into a set of rendering
/// instructions which makes sense for the resource type.
public struct Style: Hashable {
public struct Style: Hashable, Sendable {
/// Unique ID for a style.
public struct Id: RawRepresentable, ExpressibleByStringLiteral, Hashable, JSONValueEncodable, Sendable {
public let rawValue: String
Expand Down Expand Up @@ -117,7 +119,7 @@ public struct Decoration: Hashable, JSONObjectEncodable {
.init(id: .underline, config: HighlightConfig(tint: tint, isActive: isActive))
}

public struct HighlightConfig: Hashable {
public struct HighlightConfig: Hashable, Sendable {
public var tint: UIColor?
public var isActive: Bool
public init(tint: UIColor? = nil, isActive: Bool = false) {
Expand All @@ -127,11 +129,20 @@ public struct Decoration: Hashable, JSONObjectEncodable {
}

public let id: Id
public let config: AnyHashable?

public init(id: Id, config: AnyHashable? = nil) {
private let _config: AnySendableHashable?
public var config: AnyHashable? {
_config.map { AnyHashable($0.base) }
}

Comment thread
stevenzeck marked this conversation as resolved.
public init(id: Id) {
self.id = id
_config = nil
}

public init<T: Hashable & Sendable>(id: Id, config: T) {
self.id = id
self.config = config
_config = AnySendableHashable(config)
}
Comment thread
mickael-menu marked this conversation as resolved.
}

Expand Down
4 changes: 2 additions & 2 deletions Sources/Navigator/EPUB/EPUBSpreadView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -787,9 +787,9 @@ private extension PointerEvent {

let optionalPointer: Pointer? = switch pointerType {
case "mouse":
.mouse(MousePointer(id: pointerId, buttons: MouseButtons(json: json)))
.mouse(MousePointer(id: .int(pointerId), buttons: MouseButtons(json: json)))
case "touch":
.touch(TouchPointer(id: pointerId))
.touch(TouchPointer(id: .int(pointerId)))
default:
nil
}
Expand Down
6 changes: 3 additions & 3 deletions Sources/Navigator/EPUB/HTMLDecorationTemplate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -95,9 +95,9 @@ public struct HTMLDecorationTemplate: JSONObjectEncodable {
return HTMLDecorationTemplate(
layout: .boxes,
element: { decoration in
let config = decoration.style.config as! Decoration.Style.HighlightConfig
let tint = config.tint ?? defaultTint
let isActive = config.isActive
let config = decoration.style.config as? Decoration.Style.HighlightConfig
let tint = config?.tint ?? defaultTint
let isActive = config?.isActive ?? false
var css = ""
if asHighlight || isActive {
css += "background-color: \(tint.cssValue(alpha: alpha)) !important;"
Expand Down
6 changes: 3 additions & 3 deletions Sources/Navigator/Input/InputObservable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,10 @@ import Foundation

/// A token which can be used to remove an `InputObserver` from an
/// ``InputObservable``.
public struct InputObservableToken: Hashable, Identifiable {
public let id: AnyHashable
public struct InputObservableToken: Hashable, Identifiable, Sendable {
public let id: UUID

public init(id: AnyHashable = UUID()) {
public init(id: UUID = UUID()) {
self.id = id
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ open class InputObservableViewController: UIViewController, InputObservable {

extension Pointer {
init(touch: UITouch, event: UIEvent?) {
let id = AnyHashable(ObjectIdentifier(touch))
let id = PointerId.object(ObjectIdentifier(touch))

self = switch touch.type {
case .direct, .indirect:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ final class InputObservingGestureRecognizerAdapter: UIGestureRecognizer {
/// Stores the ``PointerEvent`` that were notified to the `observer`, to
/// cancel them if the gesture recognizer is resetted before the touches
/// are cancelled or ended.
private var pendingPointers: [AnyHashable: PointerEvent] = [:]
private var pendingPointers: [PointerId: PointerEvent] = [:]

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
super.touchesBegan(touches, with: event)
Expand Down
4 changes: 2 additions & 2 deletions Sources/Navigator/Input/Pointer/ActivatePointerObserver.swift
Original file line number Diff line number Diff line change
Expand Up @@ -85,9 +85,9 @@ public extension InputObserving where Self == ActivatePointerObserver {

private enum State {
case idle
case recognizing(id: AnyHashable, lastLocation: CGPoint)
case recognizing(id: PointerId, lastLocation: CGPoint)
case recognized
case failed(activePointers: Set<AnyHashable>)
case failed(activePointers: Set<PointerId>)
}

private var state: State = .idle {
Expand Down
6 changes: 3 additions & 3 deletions Sources/Navigator/Input/Pointer/DragPointerObserver.swift
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,9 @@ public extension InputObserving where Self == DragPointerObserver {

private enum State {
case idle
case pending(id: AnyHashable, startLocation: CGPoint)
case dragging(id: AnyHashable, lastEvent: PointerEvent)
case failed(activePointers: Set<AnyHashable>)
case pending(id: PointerId, startLocation: CGPoint)
case dragging(id: PointerId, lastEvent: PointerEvent)
case failed(activePointers: Set<PointerId>)
}

private enum Action {
Expand Down
36 changes: 26 additions & 10 deletions Sources/Navigator/Input/Pointer/PointerEvent.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import Foundation
import ReadiumShared

/// Represents a pointer event (e.g. touch, mouse) emitted by a navigator.
public struct PointerEvent: Equatable {
public struct PointerEvent: Equatable, Sendable {
Comment thread
stevenzeck marked this conversation as resolved.
/// Pointer causing this event.
public var pointer: Pointer

Expand All @@ -26,7 +26,7 @@ public struct PointerEvent: Equatable {

/// A content element targeted by a pointer event, paired with its
/// on-screen frame.
@_spi(ExperimentalTargetElement) public struct TargetElement: Equatable {
@_spi(ExperimentalTargetElement) public struct TargetElement: Equatable, Sendable {
/// Frame of the element relative to the navigator's view.
public var frame: CGRect

Expand Down Expand Up @@ -70,13 +70,29 @@ public struct PointerEvent: Equatable {
}
}

public enum PointerId: Hashable, Sendable, CustomStringConvertible {
case object(ObjectIdentifier)
case string(String)
case int(Int)
case uuid(UUID)

public var description: String {
switch self {
case let .object(id): return String(describing: id)
case let .string(id): return id
case let .int(id): return String(id)
case let .uuid(id): return id.uuidString
}
}
}

/// Represents a pointer device, such as a mouse or a physical touch.
public enum Pointer: Equatable, CustomStringConvertible {
public enum Pointer: Equatable, CustomStringConvertible, Sendable {
case touch(TouchPointer)
case mouse(MousePointer)

/// Unique identifier for this pointer.
public var id: AnyHashable {
public var id: PointerId {
switch self {
case let .touch(pointer): pointer.id
case let .mouse(pointer): pointer.id
Expand Down Expand Up @@ -108,24 +124,24 @@ public enum PointerType: Equatable, CaseIterable, Sendable {
}

/// Represents a physical touch pointer.
public struct TouchPointer: Identifiable, Equatable {
public struct TouchPointer: Identifiable, Equatable, Sendable {
/// Unique identifier for this pointer.
public let id: AnyHashable
public let id: PointerId

public init(id: AnyHashable) {
public init(id: PointerId) {
self.id = id
}
}

/// Represents a mouse pointer.
public struct MousePointer: Identifiable, Equatable {
public struct MousePointer: Identifiable, Equatable, Sendable {
/// Unique identifier for this pointer.
public let id: AnyHashable
public let id: PointerId

/// Indicates which buttons are pressed on the mouse.
public let buttons: MouseButtons

public init(id: AnyHashable, buttons: MouseButtons) {
public init(id: PointerId, buttons: MouseButtons) {
self.id = id
self.buttons = buttons
}
Expand Down
4 changes: 2 additions & 2 deletions Sources/Navigator/PDF/PDFNavigatorViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -319,7 +319,7 @@ open class PDFNavigatorViewController:

@objc private func didTap(_ gesture: UITapGestureRecognizer) {
let location = gesture.location(in: view)
let pointer = Pointer.touch(TouchPointer(id: ObjectIdentifier(gesture)))
let pointer = Pointer.touch(TouchPointer(id: .object(ObjectIdentifier(gesture))))
let modifiers = KeyModifiers(flags: gesture.modifierFlags)
Task {
_ = await inputObservers.didReceive(PointerEvent(pointer: pointer, phase: .down, location: location, modifiers: modifiers))
Expand All @@ -331,7 +331,7 @@ open class PDFNavigatorViewController:

@objc private func didClick(_ gesture: UITapGestureRecognizer) {
let location = gesture.location(in: view)
let pointer = Pointer.mouse(MousePointer(id: ObjectIdentifier(gesture), buttons: .main))
let pointer = Pointer.mouse(MousePointer(id: .object(ObjectIdentifier(gesture)), buttons: .main))
let modifiers = KeyModifiers(flags: gesture.modifierFlags)
Task {
_ = await inputObservers.didReceive(PointerEvent(pointer: pointer, phase: .down, location: location, modifiers: modifiers))
Expand Down
4 changes: 2 additions & 2 deletions Sources/OPDS/OPDS1Parser.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@ import Foundation
import ReadiumFuzi
import ReadiumShared

public enum OPDS1ParserError: Error {
public enum OPDS1ParserError: Error, Sendable {
/// The title is missing from the feed.
case missingTitle
/// Root is not found
case rootNotFound
}

public enum OPDSParserOpenSearchHelperError: Error {
public enum OPDSParserOpenSearchHelperError: Error, Sendable {
/// Search link not found in feed
case searchLinkNotFound
/// OpenSearch document is invalid
Expand Down
2 changes: 1 addition & 1 deletion Sources/OPDS/OPDS2Parser.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import Foundation
import ReadiumShared

public enum OPDS2ParserError: Error {
public enum OPDS2ParserError: Error, Sendable {
case invalidJSON
case metadataNotFound
case invalidLink
Expand Down
Loading
Loading