mirror of
https://github.com/mattermost/mattermost-mobile.git
synced 2026-11-05 18:38:54 +00:00
* 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
189 lines
6.8 KiB
Swift
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)
|
|
}
|
|
}
|
|
}
|
|
}
|