mattermost-mobile-upstream/libraries/@mattermost/secure-pdf-viewer/ios/SecurePdfViewerComponent/SecurePdfViewerComponentView+PDFHandling.swift
Elias Nahum b6fc85385e
Migrate from RNN to Expo router (#9402)
* Expo-Router partial migration

* Full migration to ExpoRouter

* i18n

* add tests

* Merge branch 'main' into expo-router

* align package-lock.json

* i18n

* feedback review

* feedback review

* android 16kb patch

* Refactored emoji picker keyboard using a state machine

* set android launch_screen in drawables instead of layout

* no conditional hooks

* remove duplicate call in test

* address feedback

* fix tests and address more feedback

* small corrections

* small corrections

* small corrections

* fix draft test

* fix draft test 2

* remove navigateToScreen in draff test

* fix nit in draft test

* remove doc file

* Feedback review

* fix snapshots

* improve test coverage

* fix i18n

* Upgrade t o RN 0.83, other dependencies and new architecture (#9737)

* library hardware-keyboard android new arch support fix

* library rnshare android new arch support fix

* library rnutils android new arch support fix

* library secure-pdf-viewer add new arch support

* Enable new arch on iOS and Android

* update emm and network client with new-arch

* update intune to use new-arch

* performance improvements

* fix tests

* fixes post input on new-arch

* fix android emoji picker

* remove doc

* run tests in parallel

* upgrade to React Native 0.83.9, Expo 55, React 19, and New Architecture

Bumps core dependencies to their latest versions:
- React Native 0.77.3 → 0.83.9 (New Architecture enabled)
- React 18.3.1 → 19.2.5
- Expo SDK 52 → 55
- TypeScript 5.8.3 → 6.0.3
- Node.js 22.16.0 → 24.15.0
- react-native-reanimated 3.17.3 → 4.3.0
- Various other dependency upgrades

Migrates iOS AppDelegate from Objective-C to Swift, updates Android
build tooling and Gradle wrapper, refreshes all patches and font
assets, and adapts app code, tests, and CI workflows throughout for
API changes introduced by these upgrades.

* remove unecessary dismissKeyboard in notifications

* feedback review

* update intune to use new-arch

* update @mattermost deps

* missing i18n string

* additional fixes

* update fastlane
2026-05-13 17:44:55 +08:00

189 lines
6.8 KiB
Swift

import PDFKit
extension SecurePdfViewerComponentView {
@objc public func resetPDFState() {
// Clear PDF state immediately
pdfView.document = nil
pdfDocument = nil
normalizedSource = ""
customThumbnailView.pdfView = nil
// Reset and hide page indicator
pageIndicator?.updatePage(current: 0, total: 0)
pageIndicator?.setAlpha(0)
pageIndicator?.isHidden = true
pageIndicatorLeadingConstraint.constant = 16
hideTimer?.invalidate()
hideTimer = nil
// Reset thumbnail view to closed state
customThumbnailView.isHidden = true
if thumbnailLayoutMode == .panel {
thumbnailLeadingConstraint.constant = -thumbnailWidthConstraint.constant
pdfViewLeadingConstraint.constant = 0
} else if thumbnailLayoutMode == .drawer {
thumbnailLeadingConstraint.constant = -thumbnailWidthConstraint.constant
}
// Force layout update
layoutIfNeeded()
}
func loadPDF() {
let filePath = normalizedSource
let fileKey = HashUtils.hashOfFilePathOrId(filePath)
guard !attemptStore.hasExceededLimit(for: fileKey) else {
onPasswordFailureLimitReached?(["maxAttempts": attemptStore.maxAllowedAttempts])
return
}
guard isValidCachedFile(path: filePath) else {
onLoadError?(["message": "Invalid file path"])
return
}
let url = URL(fileURLWithPath: filePath)
if let error = PDFDiagnostics.diagnosePotentialLoadFailure(at: url) {
onLoadError?(["message": error.description])
return
}
if let document = PDFDocument(url: url) {
pdfDocument = document
if document.isEncrypted {
// Clear the view to prevent showing old document while waiting for password
pdfView.document = nil
pageIndicator?.setAlpha(0)
let passwordAttempts = attemptStore.getRemainingAttempts(for: fileKey)
onPasswordRequired!(["maxAttempts": attemptStore.maxAllowedAttempts, "remainingAttempts": passwordAttempts])
} else {
pdfView.document = document
customThumbnailView.pdfView = pdfView
updatePageIndicator()
if thumbnailLayoutMode == .panel {
customThumbnailView.isHidden = false
layoutIfNeeded()
}
onLoad?([:])
}
} else {
let error = PDFDiagnostics.diagnoseLoadFailure(at: url)
onLoadError?(["message": error.description])
}
}
func attemptUnlockIfNeeded() {
guard let password = password as String?, let document = pdfDocument else {
return
}
let fileKey = HashUtils.hashOfFilePathOrId(normalizedSource)
if document.unlock(withPassword: password) {
attemptStore.resetAttempts(for: fileKey)
pdfView.document = document
customThumbnailView.pdfView = pdfView
updatePageIndicator()
onLoad?([:])
} else {
let remaining = attemptStore.registerFailedAttempt(for: fileKey)
if remaining <= 0 {
onPasswordFailureLimitReached?(["maxAttempts": attemptStore.maxAllowedAttempts])
} else {
onPasswordFailed?(["remainingAttempts": remaining])
}
}
}
@objc func pageChanged() {
updatePageIndicator()
}
@objc func toggleThumbnails() {
switch thumbnailLayoutMode {
case .panel:
let isCurrentlyVisible = thumbnailLeadingConstraint.constant == 0
if isCurrentlyVisible {
thumbnailLeadingConstraint.constant = -thumbnailWidthConstraint.constant
pdfViewLeadingConstraint.constant = 0
UIView.animate(withDuration: 0.3) {
self.customThumbnailView.isHidden = true
self.layoutIfNeeded()
}
} else {
customThumbnailView.isHidden = false
thumbnailLeadingConstraint.constant = 0
pdfViewLeadingConstraint.constant = thumbnailWidthConstraint.constant
UIView.animate(withDuration: 0.3) {
self.layoutIfNeeded()
}
}
case .drawer:
let isDrawerOpen = thumbnailLeadingConstraint.constant == 0
thumbnailLeadingConstraint.constant = isDrawerOpen ? -thumbnailWidthConstraint.constant : 0
pageIndicatorLeadingConstraint.constant = isDrawerOpen ? 16 : thumbnailWidthConstraint.constant + 16
customThumbnailView.isHidden = isDrawerOpen
UIView.animate(withDuration: 0.3) {
self.layoutIfNeeded()
}
case .none: break // this is to make the switch exhaustive although this one here is not reachable
}
}
func updateScrollContentHeight(_ height: CGFloat) {
// Remove old height constraint if it exists
if let existingConstraint = thumbnailHeightConstraint {
existingConstraint.isActive = false
thumbnailHeightConstraint = nil
}
// Create and activate new height constraint
thumbnailHeightConstraint = customThumbnailView.heightAnchor.constraint(equalToConstant: height)
thumbnailHeightConstraint?.isActive = true
}
func updatePageIndicator() {
guard let doc = pdfView.document, let page = pdfView.currentPage else { return }
let pageIndex = doc.index(for: page) + 1
let totalPages = doc.pageCount
pageIndicator?.updatePage(current: pageIndex, total: totalPages)
pageIndicator?.isHidden = false
pageIndicator?.setAlpha(1)
hidePageIndicator()
}
func updatePageIndicatorPosition() {
if thumbnailLayoutMode == .panel {
// Keep PageIndicator in a fixed position
pageIndicatorLeadingConstraint.constant = 16
} else if thumbnailLayoutMode == .drawer {
// Align PageIndicator with the drawer
let isDrawerOpen = thumbnailLeadingConstraint.constant == 0
pageIndicatorLeadingConstraint.constant = isDrawerOpen ? thumbnailWidthConstraint.constant + 16 : 16
}
UIView.animate(withDuration: 0.3) {
self.layoutIfNeeded()
}
}
func hidePageIndicator() {
hideTimer?.invalidate()
hideTimer = Timer.scheduledTimer(withTimeInterval: 3, repeats: false) { [weak self] _ in
UIView.animate(withDuration: 0.3) {
self?.pageIndicator?.setAlpha(0)
}
}
}
}