diff --git a/Telegram/NotificationService/Sources/NotificationService.swift b/Telegram/NotificationService/Sources/NotificationService.swift index 3e50ecf6e1..2ba4482b8c 100644 --- a/Telegram/NotificationService/Sources/NotificationService.swift +++ b/Telegram/NotificationService/Sources/NotificationService.swift @@ -613,7 +613,7 @@ private final class NotificationServiceHandler { let appVersion = (Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String) ?? "unknown" - self.accountManager = AccountManager(basePath: rootPath + "/accounts-metadata", isTemporary: true, isReadOnly: false, useCaches: false) + self.accountManager = AccountManager(basePath: rootPath + "/accounts-metadata", isTemporary: true, isReadOnly: false, useCaches: false, removeDatabaseOnError: false) let deviceSpecificEncryptionParameters = BuildConfig.deviceSpecificEncryptionParameters(rootPath, baseAppBundleId: baseAppBundleId) self.encryptionParameters = ValueBoxEncryptionParameters(forceEncryptionIfNoSet: false, key: ValueBoxEncryptionParameters.Key(data: deviceSpecificEncryptionParameters.key)!, salt: ValueBoxEncryptionParameters.Salt(data: deviceSpecificEncryptionParameters.salt)!) diff --git a/Telegram/SiriIntents/IntentHandler.swift b/Telegram/SiriIntents/IntentHandler.swift index 075cd25ffd..b9e62875fb 100644 --- a/Telegram/SiriIntents/IntentHandler.swift +++ b/Telegram/SiriIntents/IntentHandler.swift @@ -121,7 +121,7 @@ class DefaultIntentHandler: INExtension, INSendMessageIntentHandling, INSearchFo let appVersion = (Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String) ?? "unknown" initializeAccountManagement() - let accountManager = AccountManager(basePath: rootPath + "/accounts-metadata", isTemporary: true, isReadOnly: false, useCaches: false) + let accountManager = AccountManager(basePath: rootPath + "/accounts-metadata", isTemporary: true, isReadOnly: false, useCaches: false, removeDatabaseOnError: false) self.accountManager = accountManager let deviceSpecificEncryptionParameters = BuildConfig.deviceSpecificEncryptionParameters(rootPath, baseAppBundleId: baseAppBundleId) diff --git a/buildbox/deploy-appcenter.sh b/buildbox/deploy-appcenter.sh index 141a43bf48..0c5195fe3f 100644 --- a/buildbox/deploy-appcenter.sh +++ b/buildbox/deploy-appcenter.sh @@ -9,5 +9,11 @@ DSYM_PATH="build/artifacts/Telegram.DSYMs.zip" APPCENTER="/usr/local/bin/appcenter" $APPCENTER login --token "$API_TOKEN" -$APPCENTER distribute release --app "$API_USER_NAME/$API_APP_NAME" -f "$IPA_PATH" -g Internal + +NEXT_WAIT_TIME=0 +until [ $NEXT_WAIT_TIME -eq 5 ] || $APPCENTER distribute release --app "$API_USER_NAME/$API_APP_NAME" -f "$IPA_PATH" -g Internal; do + sleep $(( NEXT_WAIT_TIME++ )) +done +[ $NEXT_WAIT_TIME -lt 10 ] + $APPCENTER crashes upload-symbols --app "$API_USER_NAME/$API_APP_NAME" --symbol "$DSYM_PATH" diff --git a/submodules/Components/ReactionButtonListComponent/Sources/ReactionButtonListComponent.swift b/submodules/Components/ReactionButtonListComponent/Sources/ReactionButtonListComponent.swift index 437824c587..a98a6f642c 100644 --- a/submodules/Components/ReactionButtonListComponent/Sources/ReactionButtonListComponent.swift +++ b/submodules/Components/ReactionButtonListComponent/Sources/ReactionButtonListComponent.swift @@ -153,7 +153,7 @@ public final class ReactionButtonAsyncNode: ContextControllerSourceNode { } var counterComponents: [String] = [] - for character in "\(spec.component.count)" { + for character in countString(Int64(spec.component.count)) { counterComponents.append(String(character)) } @@ -164,12 +164,12 @@ public final class ReactionButtonAsyncNode: ContextControllerSourceNode { var previousDisplayCounter: String? if let currentLayout = currentLayout { if currentLayout.spec.component.avatarPeers.isEmpty { - previousDisplayCounter = "\(spec.component.count)" + previousDisplayCounter = countString(Int64(spec.component.count)) } } var currentDisplayCounter: String? if spec.component.avatarPeers.isEmpty { - currentDisplayCounter = "\(spec.component.count)" + currentDisplayCounter = countString(Int64(spec.component.count)) } let backgroundImage: UIImage @@ -669,7 +669,7 @@ public final class ReactionButtonComponent: Component { self.iconView.frame = CGRect(origin: CGPoint(x: sideInsets, y: floorToScreenPixels((height - imageSize.height) / 2.0)), size: imageSize) - let text = "\(component.count)" + let text = countString(Int64(component.count)) var measureText = "" for _ in 0 ..< text.count { measureText.append("0") diff --git a/submodules/Components/ReactionListContextMenuContent/Sources/ReactionListContextMenuContent.swift b/submodules/Components/ReactionListContextMenuContent/Sources/ReactionListContextMenuContent.swift index ce1b2c0593..0ae1d4ae53 100644 --- a/submodules/Components/ReactionListContextMenuContent/Sources/ReactionListContextMenuContent.swift +++ b/submodules/Components/ReactionListContextMenuContent/Sources/ReactionListContextMenuContent.swift @@ -741,7 +741,7 @@ public final class ReactionListContextMenuContent: ContextControllerItemsContent reactions.insert((nil, totalCount), at: 0) } - if reactions.count > 2 { + if reactions.count > 2 && totalCount > 10 { self.tabListNode = ReactionTabListNode(context: context, availableReactions: availableReactions, reactions: reactions, message: message) } diff --git a/submodules/ContextUI/Sources/ContextController.swift b/submodules/ContextUI/Sources/ContextController.swift index 47769179ca..0ac7f7abd5 100644 --- a/submodules/ContextUI/Sources/ContextController.swift +++ b/submodules/ContextUI/Sources/ContextController.swift @@ -1229,12 +1229,21 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi } func addRelativeContentOffset(_ offset: CGPoint, transition: ContainedViewLayoutTransition) { + if let presentationNode = self.presentationNode { + presentationNode.addRelativeContentOffset(offset, transition: transition) + } if self.reactionContextNodeIsAnimatingOut, let reactionContextNode = self.reactionContextNode { reactionContextNode.bounds = reactionContextNode.bounds.offsetBy(dx: 0.0, dy: offset.y) transition.animateOffsetAdditive(node: reactionContextNode, offset: -offset.y) } } + func cancelReactionAnimation() { + if let presentationNode = self.presentationNode { + presentationNode.cancelReactionAnimation() + } + } + func animateOutToReaction(value: String, targetView: UIView, hideNode: Bool, completion: @escaping () -> Void) { if let presentationNode = self.presentationNode { presentationNode.animateOutToReaction(value: value, targetView: targetView, hideNode: hideNode, completion: completion) @@ -2372,6 +2381,10 @@ public final class ContextController: ViewController, StandalonePresentableContr } } + public func cancelReactionAnimation() { + self.controllerNode.cancelReactionAnimation() + } + public func addRelativeContentOffset(_ offset: CGPoint, transition: ContainedViewLayoutTransition) { self.controllerNode.addRelativeContentOffset(offset, transition: transition) } diff --git a/submodules/ContextUI/Sources/ContextControllerActionsStackNode.swift b/submodules/ContextUI/Sources/ContextControllerActionsStackNode.swift index 48ba044a90..c3ae419655 100644 --- a/submodules/ContextUI/Sources/ContextControllerActionsStackNode.swift +++ b/submodules/ContextUI/Sources/ContextControllerActionsStackNode.swift @@ -93,9 +93,7 @@ private final class ContextControllerActionsListActionItemNode: HighlightTrackin if highlighted { strongSelf.highlightBackgroundNode.alpha = 1.0 } else { - let previousAlpha = strongSelf.highlightBackgroundNode.alpha strongSelf.highlightBackgroundNode.alpha = 0.0 - strongSelf.highlightBackgroundNode.layer.animateAlpha(from: previousAlpha, to: 0.0, duration: 0.2) } } @@ -246,14 +244,14 @@ private final class ContextControllerActionsListActionItemNode: HighlightTrackin let titleFrame = CGRect(origin: CGPoint(x: sideInset, y: verticalInset), size: titleSize) let subtitleFrame = CGRect(origin: CGPoint(x: sideInset, y: titleFrame.maxY + titleSubtitleSpacing), size: subtitleSize) - transition.updateFrame(node: self.highlightBackgroundNode, frame: CGRect(origin: CGPoint(), size: size)) + transition.updateFrame(node: self.highlightBackgroundNode, frame: CGRect(origin: CGPoint(), size: size), beginWithCurrentState: true) transition.updateFrameAdditive(node: self.titleLabelNode, frame: titleFrame) transition.updateFrameAdditive(node: self.subtitleNode, frame: subtitleFrame) if let iconSize = iconSize { let iconWidth = max(standardIconWidth, iconSize.width) let iconFrame = CGRect(origin: CGPoint(x: size.width - iconSideInset - iconWidth + floor((iconWidth - iconSize.width) / 2.0), y: floor((size.height - iconSize.height) / 2.0)), size: iconSize) - transition.updateFrame(node: self.iconNode, frame: iconFrame) + transition.updateFrame(node: self.iconNode, frame: iconFrame, beginWithCurrentState: true) } }) } @@ -344,7 +342,7 @@ private final class ContextControllerActionsListCustomItemNode: ASDisplayNode, C let itemLayoutAndApply = itemNode.updateLayout(constrainedWidth: constrainedSize.width, constrainedHeight: constrainedSize.height) return (minSize: itemLayoutAndApply.0, apply: { size, transition in - transition.updateFrame(node: itemNode, frame: CGRect(origin: CGPoint(), size: size)) + transition.updateFrame(node: itemNode, frame: CGRect(origin: CGPoint(), size: size), beginWithCurrentState: true) itemLayoutAndApply.1(size, transition) }) } @@ -493,10 +491,10 @@ final class ContextControllerActionsListStackItem: ContextControllerActionsStack let itemSize = CGSize(width: combinedSize.width, height: itemNodeLayout.minSize.height) let itemFrame = CGRect(origin: nextItemOrigin, size: itemSize) - itemTransition.updateFrame(node: item.node, frame: itemFrame) + itemTransition.updateFrame(node: item.node, frame: itemFrame, beginWithCurrentState: true) if let separatorNode = item.separatorNode { - itemTransition.updateFrame(node: separatorNode, frame: CGRect(origin: CGPoint(x: itemFrame.minX, y: itemFrame.maxY), size: CGSize(width: itemFrame.width, height: UIScreenPixel))) + itemTransition.updateFrame(node: separatorNode, frame: CGRect(origin: CGPoint(x: itemFrame.minX, y: itemFrame.maxY), size: CGSize(width: itemFrame.width, height: UIScreenPixel)), beginWithCurrentState: true) if i != self.itemNodes.count - 1 { switch self.items[i + 1] { case .separator: @@ -543,10 +541,9 @@ final class ContextControllerActionsListStackItem: ContextControllerActionsStack func highlightGestureFinished(performAction: Bool) { if let highlightedItemNode = self.highlightedItemNode { self.highlightedItemNode = nil + highlightedItemNode.node.updateIsHighlighted(isHighlighted: false) if performAction { highlightedItemNode.node.performAction() - } else { - highlightedItemNode.node.updateIsHighlighted(isHighlighted: false) } } } @@ -614,7 +611,7 @@ final class ContextControllerActionsCustomStackItem: ContextControllerActionsSta bottomInset: 0.0, transition: transition ) - transition.updateFrame(node: self.contentNode, frame: CGRect(origin: CGPoint(), size: contentLayout.cleanSize)) + transition.updateFrame(node: self.contentNode, frame: CGRect(origin: CGPoint(), size: contentLayout.cleanSize), beginWithCurrentState: true) return (contentLayout.cleanSize, contentLayout.apparentHeight) } @@ -779,9 +776,9 @@ final class ContextControllerActionsStackNode: ASDisplayNode { let scale: CGFloat = (size.width - scaleOffset) / size.width let yOffset: CGFloat = size.height * (1.0 - scale) let transitionOffset = (1.0 - transitionFraction) * size.width / 2.0 - transition.updatePosition(node: self.node, position: CGPoint(x: size.width / 2.0 + scaleOffset / 2.0 + transitionOffset, y: size.height / 2.0 - yOffset / 2.0)) - transition.updateBounds(node: self.node, bounds: CGRect(origin: CGPoint(), size: size)) - transition.updateTransformScale(node: self.node, scale: scale) + transition.updatePosition(node: self.node, position: CGPoint(x: size.width / 2.0 + scaleOffset / 2.0 + transitionOffset, y: size.height / 2.0 - yOffset / 2.0), beginWithCurrentState: true) + transition.updateBounds(node: self.node, bounds: CGRect(origin: CGPoint(), size: size), beginWithCurrentState: true) + transition.updateTransformScale(node: self.node, scale: scale, beginWithCurrentState: true) return (size, apparentHeight) } @@ -789,8 +786,8 @@ final class ContextControllerActionsStackNode: ASDisplayNode { func updateDimNode(presentationData: PresentationData, size: CGSize, transitionFraction: CGFloat, transition: ContainedViewLayoutTransition) { self.dimNode.backgroundColor = presentationData.theme.contextMenu.sectionSeparatorColor - transition.updateFrame(node: self.dimNode, frame: CGRect(origin: CGPoint(), size: size)) - transition.updateAlpha(node: self.dimNode, alpha: 1.0 - transitionFraction) + transition.updateFrame(node: self.dimNode, frame: CGRect(origin: CGPoint(), size: size), beginWithCurrentState: true) + transition.updateAlpha(node: self.dimNode, alpha: 1.0 - transitionFraction, beginWithCurrentState: true) } func highlightGestureMoved(location: CGPoint) { @@ -810,6 +807,8 @@ final class ContextControllerActionsStackNode: ASDisplayNode { private var itemContainers: [ItemContainer] = [] private var dismissingItemContainers: [(container: ItemContainer, isPopped: Bool)] = [] + private var selectionPanGesture: UIPanGestureRecognizer? + var topReactionItems: (context: AccountContext, reactionItems: [ReactionContextItem])? { return self.itemContainers.last?.reactionItems } @@ -850,6 +849,25 @@ final class ContextControllerActionsStackNode: ASDisplayNode { } strongSelf.pop() } + + let selectionPanGesture = UIPanGestureRecognizer(target: self, action: #selector(self.panGesture(_:))) + self.selectionPanGesture = selectionPanGesture + self.view.addGestureRecognizer(selectionPanGesture) + selectionPanGesture.isEnabled = false + } + + @objc private func panGesture(_ recognizer: UIPanGestureRecognizer) { + switch recognizer.state { + case .changed: + let location = recognizer.location(in: self.view) + self.highlightGestureMoved(location: location) + case .ended: + self.highlightGestureFinished(performAction: true) + case .cancelled: + self.highlightGestureFinished(performAction: false) + default: + break + } } func replace(item: ContextControllerActionsStackItem, animated: Bool) { @@ -1000,7 +1018,7 @@ final class ContextControllerActionsStackNode: ASDisplayNode { } let navigationContainerFrame = CGRect(origin: CGPoint(), size: CGSize(width: topItemWidth, height: max(14 * 2.0, topItemApparentHeight))) - transition.updateFrame(node: self.navigationContainer, frame: navigationContainerFrame) + transition.updateFrame(node: self.navigationContainer, frame: navigationContainerFrame, beginWithCurrentState: true) self.navigationContainer.update(presentationData: presentationData, size: navigationContainerFrame.size, transition: transition) for i in 0 ..< self.itemContainers.count { @@ -1016,7 +1034,7 @@ final class ContextControllerActionsStackNode: ASDisplayNode { } let itemFrame = CGRect(origin: CGPoint(x: xOffset, y: 0.0), size: CGSize(width: itemLayouts[i].size.width, height: navigationContainerFrame.height)) - itemLayouts[i].itemTransition.updateFrame(node: self.itemContainers[i], frame: itemFrame) + itemLayouts[i].itemTransition.updateFrame(node: self.itemContainers[i], frame: itemFrame, beginWithCurrentState: true) if itemLayouts[i].animateAppearingContainer { transition.animatePositionAdditive(node: self.itemContainers[i], offset: CGPoint(x: itemFrame.width, y: 0.0)) } @@ -1051,4 +1069,10 @@ final class ContextControllerActionsStackNode: ASDisplayNode { topItemContainer.highlightGestureFinished(performAction: performAction) } } + + func updatePanSelection(isEnabled: Bool) { + if let selectionPanGesture = self.selectionPanGesture { + selectionPanGesture.isEnabled = isEnabled + } + } } diff --git a/submodules/ContextUI/Sources/ContextControllerExtractedPresentationNode.swift b/submodules/ContextUI/Sources/ContextControllerExtractedPresentationNode.swift index 00f2486bd3..54686eafdf 100644 --- a/submodules/ContextUI/Sources/ContextControllerExtractedPresentationNode.swift +++ b/submodules/ContextUI/Sources/ContextControllerExtractedPresentationNode.swift @@ -23,12 +23,17 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo super.init() self.addSubnode(self.offsetContainerNode) - self.offsetContainerNode.addSubnode(self.containingNode.contentNode) } func update(presentationData: PresentationData, size: CGSize, transition: ContainedViewLayoutTransition) { } + func takeContainingNode() { + if self.containingNode.contentNode.supernode !== self.offsetContainerNode { + self.offsetContainerNode.addSubnode(self.containingNode.contentNode) + } + } + override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { if !self.bounds.contains(point) { return nil @@ -217,12 +222,12 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo transition: .immediate ) - transition.updateFrame(node: self.backgroundNode, frame: CGRect(origin: CGPoint(), size: layout.size)) + transition.updateFrame(node: self.backgroundNode, frame: CGRect(origin: CGPoint(), size: layout.size), beginWithCurrentState: true) self.backgroundNode.update(size: layout.size, transition: transition) - transition.updateFrame(node: self.clippingNode, frame: CGRect(origin: CGPoint(), size: layout.size)) + transition.updateFrame(node: self.clippingNode, frame: CGRect(origin: CGPoint(), size: layout.size), beginWithCurrentState: true) if self.scrollNode.frame != CGRect(origin: CGPoint(), size: layout.size) { - transition.updateFrame(node: self.scrollNode, frame: CGRect(origin: CGPoint(), size: layout.size)) + transition.updateFrame(node: self.scrollNode, frame: CGRect(origin: CGPoint(), size: layout.size), beginWithCurrentState: true) } if let current = self.contentNode { @@ -328,8 +333,8 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo if reactionContextNode.frame.isEmpty { reactionContextNodeTransition = .immediate } - reactionContextNodeTransition.updateFrame(node: reactionContextNode, frame: CGRect(origin: CGPoint(), size: layout.size)) - reactionContextNode.updateLayout(size: layout.size, insets: UIEdgeInsets(top: topInset, left: 0.0, bottom: 0.0, right: 0.0), anchorRect: contentRect, transition: reactionContextNodeTransition) + reactionContextNodeTransition.updateFrame(node: reactionContextNode, frame: CGRect(origin: CGPoint(), size: layout.size), beginWithCurrentState: true) + reactionContextNode.updateLayout(size: layout.size, insets: UIEdgeInsets(top: topInset, left: 0.0, bottom: 0.0, right: 0.0), anchorRect: contentRect.offsetBy(dx: contentParentGlobalFrame.minX, dy: 0.0), transition: reactionContextNodeTransition) } if let removedReactionContextNode = removedReactionContextNode { removedReactionContextNode.animateOut(to: contentRect, animatingOutToReaction: false) @@ -338,7 +343,7 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo }) } - transition.updateFrame(node: self.contentRectDebugNode, frame: contentRect) + transition.updateFrame(node: self.contentRectDebugNode, frame: contentRect, beginWithCurrentState: true) var actionsFrame = CGRect(origin: CGPoint(x: actionsSideInset, y: contentRect.maxY + contentActionsSpacing), size: actionsSize) if self.source.keepInPlace { @@ -365,9 +370,9 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo actionsFrame.origin.x = actionsEdgeInset } } - transition.updateFrame(node: self.actionsStackNode, frame: actionsFrame) + transition.updateFrame(node: self.actionsStackNode, frame: actionsFrame, beginWithCurrentState: true) - contentTransition.updateFrame(node: contentNode, frame: CGRect(origin: CGPoint(x: contentParentGlobalFrame.minX + contentRect.minX - contentNode.containingNode.contentRect.minX, y: contentRect.minY - contentNode.containingNode.contentRect.minY), size: contentNode.containingNode.bounds.size)) + contentTransition.updateFrame(node: contentNode, frame: CGRect(origin: CGPoint(x: contentParentGlobalFrame.minX + contentRect.minX - contentNode.containingNode.contentRect.minX, y: contentRect.minY - contentNode.containingNode.contentRect.minY), size: contentNode.containingNode.bounds.size), beginWithCurrentState: true) let contentHeight: CGFloat if self.actionsStackNode.topPositionLock != nil { @@ -391,6 +396,8 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo } } + self.actionsStackNode.updatePanSelection(isEnabled: contentSize.height <= layout.size.height) + defaultScrollY = contentSize.height - layout.size.height if defaultScrollY < 0.0 { defaultScrollY = 0.0 @@ -401,6 +408,8 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo switch stateTransition { case .animateIn: + contentNode.takeContainingNode() + let duration: Double = 0.42 let springDamping: CGFloat = 104.0 @@ -409,7 +418,7 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo self.backgroundNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) if let animateClippingFromContentAreaInScreenSpace = contentNode.animateClippingFromContentAreaInScreenSpace { - self.clippingNode.layer.animateFrame(from: animateClippingFromContentAreaInScreenSpace, to: CGRect(origin: CGPoint(), size: layout.size), duration: 0.2) + self.clippingNode.layer.animateFrame(from: CGRect(origin: CGPoint(x: 0.0, y: animateClippingFromContentAreaInScreenSpace.minY), size: CGSize(width: layout.size.width, height: animateClippingFromContentAreaInScreenSpace.height)), to: CGRect(origin: CGPoint(), size: layout.size), duration: 0.2) self.clippingNode.layer.animateBoundsOriginYAdditive(from: animateClippingFromContentAreaInScreenSpace.minY, to: 0.0, duration: 0.2) } @@ -519,7 +528,7 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo let putBackInfo = self.source.putBack() if let putBackInfo = putBackInfo { - self.clippingNode.layer.animateFrame(from: CGRect(origin: CGPoint(), size: layout.size), to: putBackInfo.contentAreaInScreenSpace, duration: duration, removeOnCompletion: false) + self.clippingNode.layer.animateFrame(from: CGRect(origin: CGPoint(), size: layout.size), to: CGRect(origin: CGPoint(x: 0.0, y: putBackInfo.contentAreaInScreenSpace.minY), size: CGSize(width: layout.size.width, height: putBackInfo.contentAreaInScreenSpace.height)), duration: duration, removeOnCompletion: false) self.clippingNode.layer.animateBoundsOriginYAdditive(from: 0.0, to: putBackInfo.contentAreaInScreenSpace.minY, duration: duration, removeOnCompletion: false) } @@ -644,4 +653,15 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo intermediateCompletion() }) } + + func cancelReactionAnimation() { + self.reactionContextNode?.cancelReactionAnimation() + } + + func addRelativeContentOffset(_ offset: CGPoint, transition: ContainedViewLayoutTransition) { + if self.reactionContextNodeIsAnimatingOut, let reactionContextNode = self.reactionContextNode { + reactionContextNode.bounds = reactionContextNode.bounds.offsetBy(dx: 0.0, dy: offset.y) + transition.animateOffsetAdditive(node: reactionContextNode, offset: -offset.y) + } + } } diff --git a/submodules/ContextUI/Sources/ContextControllerPresentationNode.swift b/submodules/ContextUI/Sources/ContextControllerPresentationNode.swift index a45bdcfaac..cd27640916 100644 --- a/submodules/ContextUI/Sources/ContextControllerPresentationNode.swift +++ b/submodules/ContextUI/Sources/ContextControllerPresentationNode.swift @@ -25,7 +25,10 @@ protocol ContextControllerPresentationNode: ASDisplayNode { ) func animateOutToReaction(value: String, targetView: UIView, hideNode: Bool, completion: @escaping () -> Void) + func cancelReactionAnimation() func highlightGestureMoved(location: CGPoint) func highlightGestureFinished(performAction: Bool) + + func addRelativeContentOffset(_ offset: CGPoint, transition: ContainedViewLayoutTransition) } diff --git a/submodules/Display/Source/ContainedViewLayoutTransition.swift b/submodules/Display/Source/ContainedViewLayoutTransition.swift index 2a4b4e5d7c..74eec36301 100644 --- a/submodules/Display/Source/ContainedViewLayoutTransition.swift +++ b/submodules/Display/Source/ContainedViewLayoutTransition.swift @@ -105,7 +105,7 @@ public extension CGRect { } public extension ContainedViewLayoutTransition { - func updateFrame(node: ASDisplayNode, frame: CGRect, force: Bool = false, beginWithCurrentState: Bool = true, delay: Double = 0.0, completion: ((Bool) -> Void)? = nil) { + func updateFrame(node: ASDisplayNode, frame: CGRect, force: Bool = false, beginWithCurrentState: Bool = false, delay: Double = 0.0, completion: ((Bool) -> Void)? = nil) { if frame.origin.x.isNaN { return } @@ -157,7 +157,7 @@ public extension ContainedViewLayoutTransition { } } - func updateFrameAsPositionAndBounds(node: ASDisplayNode, frame: CGRect, force: Bool = false, beginWithCurrentState: Bool = true, completion: ((Bool) -> Void)? = nil) { + func updateFrameAsPositionAndBounds(node: ASDisplayNode, frame: CGRect, force: Bool = false, beginWithCurrentState: Bool = false, completion: ((Bool) -> Void)? = nil) { if node.frame.equalTo(frame) && !force { completion?(true) } else { @@ -190,7 +190,7 @@ public extension ContainedViewLayoutTransition { } } - func updateFrameAsPositionAndBounds(layer: CALayer, frame: CGRect, force: Bool = false, beginWithCurrentState: Bool = true, completion: ((Bool) -> Void)? = nil) { + func updateFrameAsPositionAndBounds(layer: CALayer, frame: CGRect, force: Bool = false, beginWithCurrentState: Bool = false, completion: ((Bool) -> Void)? = nil) { if layer.frame.equalTo(frame) && !force { completion?(true) } else { @@ -261,7 +261,7 @@ public extension ContainedViewLayoutTransition { } } - func updateBounds(node: ASDisplayNode, bounds: CGRect, force: Bool = false, completion: ((Bool) -> Void)? = nil) { + func updateBounds(node: ASDisplayNode, bounds: CGRect, force: Bool = false, beginWithCurrentState: Bool = false, completion: ((Bool) -> Void)? = nil) { if node.bounds.equalTo(bounds) && !force { completion?(true) } else { @@ -272,7 +272,12 @@ public extension ContainedViewLayoutTransition { completion(true) } case let .animated(duration, curve): - let previousBounds = node.bounds + let previousBounds: CGRect + if beginWithCurrentState, let presentation = node.layer.presentation() { + previousBounds = presentation.bounds + } else { + previousBounds = node.bounds + } node.bounds = bounds node.layer.animateBounds(from: previousBounds, to: bounds, duration: duration, timingFunction: curve.timingFunction, mediaTimingFunction: curve.mediaTimingFunction, force: force, completion: { result in if let completion = completion { @@ -305,7 +310,7 @@ public extension ContainedViewLayoutTransition { } } - func updatePosition(node: ASDisplayNode, position: CGPoint, beginWithCurrentState: Bool = true, completion: ((Bool) -> Void)? = nil) { + func updatePosition(node: ASDisplayNode, position: CGPoint, beginWithCurrentState: Bool = false, completion: ((Bool) -> Void)? = nil) { if node.position.equalTo(position) { completion?(true) } else { diff --git a/submodules/Postbox/Sources/Postbox.swift b/submodules/Postbox/Sources/Postbox.swift index 28d04fa502..452cf92738 100644 --- a/submodules/Postbox/Sources/Postbox.swift +++ b/submodules/Postbox/Sources/Postbox.swift @@ -1170,7 +1170,7 @@ func debugRestoreState(basePath:String, name: String) { private let sharedQueue = Queue(name: "org.telegram.postbox.Postbox") -public func openPostbox(basePath: String, seedConfiguration: SeedConfiguration, encryptionParameters: ValueBoxEncryptionParameters, timestampForAbsoluteTimeBasedOperations: Int32, isTemporary: Bool, isReadOnly: Bool, useCopy: Bool, useCaches: Bool) -> Signal { +public func openPostbox(basePath: String, seedConfiguration: SeedConfiguration, encryptionParameters: ValueBoxEncryptionParameters, timestampForAbsoluteTimeBasedOperations: Int32, isTemporary: Bool, isReadOnly: Bool, useCopy: Bool, useCaches: Bool, removeDatabaseOnError: Bool) -> Signal { let queue = sharedQueue return Signal { subscriber in queue.async { @@ -1214,7 +1214,7 @@ public func openPostbox(basePath: String, seedConfiguration: SeedConfiguration, postboxLog("openPostbox, initialize SqliteValueBox") - guard var valueBox = SqliteValueBox(basePath: dbBasePath, queue: queue, isTemporary: isTemporary, isReadOnly: isReadOnly, useCaches: useCaches, encryptionParameters: encryptionParameters, upgradeProgress: { progress in + guard var valueBox = SqliteValueBox(basePath: dbBasePath, queue: queue, isTemporary: isTemporary, isReadOnly: isReadOnly, useCaches: useCaches, removeDatabaseOnError: removeDatabaseOnError, encryptionParameters: encryptionParameters, upgradeProgress: { progress in postboxLog("openPostbox, SqliteValueBox upgrading progress \(progress)") subscriber.putNext(.upgrading(progress)) }) else { @@ -1242,7 +1242,7 @@ public func openPostbox(basePath: String, seedConfiguration: SeedConfiguration, postboxLog("Version \(userVersion) is newer than supported") assertionFailure("Version \(userVersion) is newer than supported") valueBox.drop() - guard let updatedValueBox = SqliteValueBox(basePath: dbBasePath, queue: queue, isTemporary: isTemporary, isReadOnly: isReadOnly, useCaches: useCaches, encryptionParameters: encryptionParameters, upgradeProgress: { progress in + guard let updatedValueBox = SqliteValueBox(basePath: dbBasePath, queue: queue, isTemporary: isTemporary, isReadOnly: isReadOnly, useCaches: useCaches, removeDatabaseOnError: removeDatabaseOnError, encryptionParameters: encryptionParameters, upgradeProgress: { progress in subscriber.putNext(.upgrading(progress)) }) else { subscriber.putNext(.error) @@ -1266,7 +1266,7 @@ public func openPostbox(basePath: String, seedConfiguration: SeedConfiguration, valueBox.internalClose() let _ = try? FileManager.default.removeItem(atPath: dbBasePath) let _ = try? FileManager.default.moveItem(atPath: updatedPath, toPath: dbBasePath) - guard let updatedValueBox = SqliteValueBox(basePath: dbBasePath, queue: queue, isTemporary: isTemporary, isReadOnly: isReadOnly, useCaches: useCaches, encryptionParameters: encryptionParameters, upgradeProgress: { progress in + guard let updatedValueBox = SqliteValueBox(basePath: dbBasePath, queue: queue, isTemporary: isTemporary, isReadOnly: isReadOnly, useCaches: useCaches, removeDatabaseOnError: removeDatabaseOnError, encryptionParameters: encryptionParameters, upgradeProgress: { progress in subscriber.putNext(.upgrading(progress)) }) else { subscriber.putNext(.error) @@ -1280,7 +1280,7 @@ public func openPostbox(basePath: String, seedConfiguration: SeedConfiguration, assertionFailure("Couldn't find any upgrade for \(userVersion)") postboxLog("Couldn't find any upgrade for \(userVersion)") valueBox.drop() - guard let updatedValueBox = SqliteValueBox(basePath: dbBasePath, queue: queue, isTemporary: isTemporary, isReadOnly: isReadOnly, useCaches: useCaches, encryptionParameters: encryptionParameters, upgradeProgress: { progress in + guard let updatedValueBox = SqliteValueBox(basePath: dbBasePath, queue: queue, isTemporary: isTemporary, isReadOnly: isReadOnly, useCaches: useCaches, removeDatabaseOnError: removeDatabaseOnError, encryptionParameters: encryptionParameters, upgradeProgress: { progress in subscriber.putNext(.upgrading(progress)) }) else { subscriber.putNext(.error) diff --git a/submodules/Postbox/Sources/SqliteValueBox.swift b/submodules/Postbox/Sources/SqliteValueBox.swift index 133996cc46..088f89623d 100644 --- a/submodules/Postbox/Sources/SqliteValueBox.swift +++ b/submodules/Postbox/Sources/SqliteValueBox.swift @@ -46,7 +46,7 @@ struct SqlitePreparedStatement { sqlite3_clear_bindings(statement) } - func step(handle: OpaquePointer?, _ initial: Bool = false, path: String?) -> Bool { + func step(handle: OpaquePointer?, _ initial: Bool = false, pathToRemoveOnError: String?) -> Bool { let res = sqlite3_step(statement) if res != SQLITE_ROW && res != SQLITE_DONE { if let error = sqlite3_errmsg(handle), let str = NSString(utf8String: error) { @@ -56,7 +56,7 @@ struct SqlitePreparedStatement { } if res == SQLITE_CORRUPT { - if let path = path { + if let path = pathToRemoveOnError { postboxLog("Corrupted DB at step, dropping") try? FileManager.default.removeItem(atPath: path) preconditionFailure() @@ -70,7 +70,7 @@ struct SqlitePreparedStatement { var code: Int32 } - func tryStep(handle: OpaquePointer?, _ initial: Bool = false, path: String?) -> Result { + func tryStep(handle: OpaquePointer?, _ initial: Bool = false, pathToRemoveOnError: String?) -> Result { let res = sqlite3_step(statement) if res != SQLITE_ROW && res != SQLITE_DONE { if let error = sqlite3_errmsg(handle), let str = NSString(utf8String: error) { @@ -80,7 +80,7 @@ struct SqlitePreparedStatement { } if res == SQLITE_CORRUPT { - if let path = path { + if let path = pathToRemoveOnError { postboxLog("Corrupted DB at step, dropping") try? FileManager.default.removeItem(atPath: path) preconditionFailure() @@ -167,6 +167,7 @@ public final class SqliteValueBox: ValueBox { private let inMemory: Bool private let encryptionParameters: ValueBoxEncryptionParameters? private let databasePath: String + private let removeDatabaseOnError: Bool private var database: Database! private var tables: [Int32: SqliteValueBoxTable] = [:] private var fullTextTables: [Int32: ValueBoxFullTextTable] = [:] @@ -202,11 +203,12 @@ public final class SqliteValueBox: ValueBox { private let queue: Queue - public init?(basePath: String, queue: Queue, isTemporary: Bool, isReadOnly: Bool, useCaches: Bool, encryptionParameters: ValueBoxEncryptionParameters?, upgradeProgress: (Float) -> Void, inMemory: Bool = false) { + public init?(basePath: String, queue: Queue, isTemporary: Bool, isReadOnly: Bool, useCaches: Bool, removeDatabaseOnError: Bool, encryptionParameters: ValueBoxEncryptionParameters?, upgradeProgress: (Float) -> Void, inMemory: Bool = false) { self.basePath = basePath self.isTemporary = isTemporary self.isReadOnly = isReadOnly self.useCaches = useCaches + self.removeDatabaseOnError = removeDatabaseOnError self.inMemory = inMemory self.encryptionParameters = encryptionParameters self.databasePath = basePath + "/db_sqlite" @@ -294,7 +296,9 @@ public final class SqliteValueBox: ValueBox { preconditionFailure("Don't have write access to database folder") } - let _ = try? FileManager.default.removeItem(atPath: path) + if self.removeDatabaseOnError { + let _ = try? FileManager.default.removeItem(atPath: path) + } preconditionFailure("Couldn't open database") } @@ -328,7 +332,7 @@ public final class SqliteValueBox: ValueBox { if self.isEncrypted(database) { postboxLog("Encryption key is invalid") - if isTemporary || isReadOnly { + if isTemporary || isReadOnly || !self.removeDatabaseOnError { return nil } @@ -347,7 +351,7 @@ public final class SqliteValueBox: ValueBox { } } else { postboxLog("Encryption key is required") - if isReadOnly { + if isReadOnly || !self.removeDatabaseOnError { return nil } @@ -548,13 +552,16 @@ public final class SqliteValueBox: ValueBox { postboxLog("isEncrypted prepare...") let allIsOk = Atomic(value: false) + let removeDatabaseOnError = self.removeDatabaseOnError let databasePath = self.databasePath DispatchQueue.global().asyncAfter(deadline: .now() + 5.0, execute: { if allIsOk.with({ $0 }) == false { postboxLog("Timeout reached, discarding database") - try? FileManager.default.removeItem(atPath: databasePath) + if removeDatabaseOnError { + try? FileManager.default.removeItem(atPath: databasePath) + } - exit(0) + preconditionFailure() } }) let status = sqlite3_prepare_v2(database.handle, "SELECT * FROM sqlite_master LIMIT 1", -1, &statement, nil) @@ -569,7 +576,7 @@ public final class SqliteValueBox: ValueBox { return true } let preparedStatement = SqlitePreparedStatement(statement: statement) - switch preparedStatement.tryStep(handle: database.handle, path: self.databasePath) { + switch preparedStatement.tryStep(handle: database.handle, pathToRemoveOnError: self.removeDatabaseOnError ? self.databasePath : nil) { case .success: break case let .failure(error): @@ -588,7 +595,7 @@ public final class SqliteValueBox: ValueBox { let status = sqlite3_prepare_v2(database.handle, "PRAGMA user_version", -1, &statement, nil) precondition(status == SQLITE_OK) let preparedStatement = SqlitePreparedStatement(statement: statement) - let _ = preparedStatement.step(handle: database.handle, path: self.databasePath) + let _ = preparedStatement.step(handle: database.handle, pathToRemoveOnError: self.removeDatabaseOnError ? self.databasePath : nil) let value = preparedStatement.int64At(0) preparedStatement.destroy() return value @@ -601,7 +608,7 @@ public final class SqliteValueBox: ValueBox { precondition(status == SQLITE_OK) let preparedStatement = SqlitePreparedStatement(statement: statement) var result: String? - if preparedStatement.step(handle: database.handle, path: self.databasePath) { + if preparedStatement.step(handle: database.handle, pathToRemoveOnError: self.removeDatabaseOnError ? self.databasePath : nil) { result = preparedStatement.stringAt(0) } preparedStatement.destroy() @@ -616,7 +623,7 @@ public final class SqliteValueBox: ValueBox { let preparedStatement = SqlitePreparedStatement(statement: statement) var tables: [SqliteValueBoxTable] = [] - while preparedStatement.step(handle: database.handle, true, path: self.databasePath) { + while preparedStatement.step(handle: database.handle, true, pathToRemoveOnError: self.removeDatabaseOnError ? self.databasePath : nil) { guard let name = preparedStatement.stringAt(0) else { assertionFailure() continue @@ -663,7 +670,7 @@ public final class SqliteValueBox: ValueBox { let preparedStatement = SqlitePreparedStatement(statement: statement) var tables: [ValueBoxFullTextTable] = [] - while preparedStatement.step(handle: database.handle, true, path: self.databasePath) { + while preparedStatement.step(handle: database.handle, true, pathToRemoveOnError: self.removeDatabaseOnError ? self.databasePath : nil) { let value = preparedStatement.int64At(0) tables.append(ValueBoxFullTextTable(id: Int32(value))) } @@ -1475,7 +1482,7 @@ public final class SqliteValueBox: ValueBox { var buffer: ReadBuffer? - while statement.step(handle: self.database.handle, path: self.databasePath) { + while statement.step(handle: self.database.handle, pathToRemoveOnError: self.removeDatabaseOnError ? self.databasePath : nil) { buffer = statement.valueAt(0) break } @@ -1495,7 +1502,7 @@ public final class SqliteValueBox: ValueBox { if let _ = self.tables[table.id] { let statement = self.getRowIdStatement(table, key: key) - if statement.step(handle: self.database.handle, path: self.databasePath) { + if statement.step(handle: self.database.handle, pathToRemoveOnError: self.removeDatabaseOnError ? self.databasePath : nil) { let rowId = statement.int64At(0) var blobHandle: OpaquePointer? sqlite3_blob_open(database.handle, "main", "t\(table.id)", "value", rowId, 0, &blobHandle) @@ -1515,7 +1522,7 @@ public final class SqliteValueBox: ValueBox { if let _ = self.tables[table.id] { let statement = self.getRowIdStatement(table, key: key) - if statement.step(handle: self.database.handle, path: self.databasePath) { + if statement.step(handle: self.database.handle, pathToRemoveOnError: self.removeDatabaseOnError ? self.databasePath : nil) { let rowId = statement.int64At(0) var blobHandle: OpaquePointer? sqlite3_blob_open(database.handle, "main", "t\(table.id)", "value", rowId, 1, &blobHandle) @@ -1566,7 +1573,7 @@ public final class SqliteValueBox: ValueBox { } } - while statement.step(handle: self.database.handle, path: self.databasePath) { + while statement.step(handle: self.database.handle, pathToRemoveOnError: self.removeDatabaseOnError ? self.databasePath : nil) { let key = statement.keyAt(0) let value = statement.valueAt(1) @@ -1591,7 +1598,7 @@ public final class SqliteValueBox: ValueBox { } } - while statement.step(handle: self.database.handle, path: self.databasePath) { + while statement.step(handle: self.database.handle, pathToRemoveOnError: self.removeDatabaseOnError ? self.databasePath : nil) { let key = statement.int64KeyAt(0) let value = statement.valueAt(1) @@ -1702,7 +1709,7 @@ public final class SqliteValueBox: ValueBox { } } - while statement.step(handle: self.database.handle, path: self.databasePath) { + while statement.step(handle: self.database.handle, pathToRemoveOnError: self.removeDatabaseOnError ? self.databasePath : nil) { let key = statement.keyAt(0) if !keys(key) { @@ -1726,7 +1733,7 @@ public final class SqliteValueBox: ValueBox { } } - while statement.step(handle: self.database.handle, path: self.databasePath) { + while statement.step(handle: self.database.handle, pathToRemoveOnError: self.removeDatabaseOnError ? self.databasePath : nil) { let key = statement.int64KeyAt(0) if !keys(key) { @@ -1748,7 +1755,7 @@ public final class SqliteValueBox: ValueBox { if let _ = self.tables[table.id] { let statement: SqlitePreparedStatement = self.scanStatement(table) - while statement.step(handle: self.database.handle, path: self.databasePath) { + while statement.step(handle: self.database.handle, pathToRemoveOnError: self.removeDatabaseOnError ? self.databasePath : nil) { let key = statement.keyAt(0) let value = statement.valueAt(1) @@ -1767,7 +1774,7 @@ public final class SqliteValueBox: ValueBox { if let _ = self.tables[table.id] { let statement: SqlitePreparedStatement = self.scanKeysStatement(table) - while statement.step(handle: self.database.handle, path: self.databasePath) { + while statement.step(handle: self.database.handle, pathToRemoveOnError: self.removeDatabaseOnError ? self.databasePath : nil) { let key = statement.keyAt(0) if !keys(key) { @@ -1785,7 +1792,7 @@ public final class SqliteValueBox: ValueBox { if let _ = self.tables[table.id] { let statement: SqlitePreparedStatement = self.scanStatement(table) - while statement.step(handle: self.database.handle, path: self.databasePath) { + while statement.step(handle: self.database.handle, pathToRemoveOnError: self.removeDatabaseOnError ? self.databasePath : nil) { let key = statement.int64KeyValueAt(0) let value = statement.valueAt(1) @@ -1804,7 +1811,7 @@ public final class SqliteValueBox: ValueBox { if let _ = self.tables[table.id] { let statement: SqlitePreparedStatement = self.scanKeysStatement(table) - while statement.step(handle: self.database.handle, path: self.databasePath) { + while statement.step(handle: self.database.handle, pathToRemoveOnError: self.removeDatabaseOnError ? self.databasePath : nil) { let key = statement.int64KeyValueAt(0) if !keys(key) { @@ -1822,18 +1829,18 @@ public final class SqliteValueBox: ValueBox { if sqliteTable.hasPrimaryKey { let statement = self.insertOrReplaceStatement(sqliteTable, key: key, value: value) - while statement.step(handle: self.database.handle, path: self.databasePath) { + while statement.step(handle: self.database.handle, pathToRemoveOnError: self.removeDatabaseOnError ? self.databasePath : nil) { } statement.reset() } else { if self.exists(table, key: key) { let statement = self.updateStatement(table, key: key, value: value) - while statement.step(handle: self.database.handle, path: self.databasePath) { + while statement.step(handle: self.database.handle, pathToRemoveOnError: self.removeDatabaseOnError ? self.databasePath : nil) { } statement.reset() } else { let statement = self.insertOrReplaceStatement(sqliteTable, key: key, value: value) - while statement.step(handle: self.database.handle, path: self.databasePath) { + while statement.step(handle: self.database.handle, pathToRemoveOnError: self.removeDatabaseOnError ? self.databasePath : nil) { } statement.reset() } @@ -1850,7 +1857,7 @@ public final class SqliteValueBox: ValueBox { } let statement = self.deleteStatement(table, key: key) - while statement.step(handle: self.database.handle, path: self.databasePath) { + while statement.step(handle: self.database.handle, pathToRemoveOnError: self.removeDatabaseOnError ? self.databasePath : nil) { } statement.reset() } @@ -1860,7 +1867,7 @@ public final class SqliteValueBox: ValueBox { precondition(self.queue.isCurrent()) if let _ = self.tables[table.id] { let statement = self.rangeDeleteStatement(table, start: min(start, end), end: max(start, end)) - while statement.step(handle: self.database.handle, path: self.databasePath) { + while statement.step(handle: self.database.handle, pathToRemoveOnError: self.removeDatabaseOnError ? self.databasePath : nil) { } statement.reset() } @@ -1870,7 +1877,7 @@ public final class SqliteValueBox: ValueBox { precondition(self.queue.isCurrent()) if let _ = self.tables[table.id] { let statement = self.moveStatement(table, from: previousKey, to: updatedKey) - while statement.step(handle: self.database.handle, path: self.databasePath) { + while statement.step(handle: self.database.handle, pathToRemoveOnError: self.removeDatabaseOnError ? self.databasePath : nil) { } statement.reset() } @@ -1880,7 +1887,7 @@ public final class SqliteValueBox: ValueBox { precondition(self.queue.isCurrent()) if let _ = self.tables[fromTable.id] { let statement = self.copyStatement(fromTable: fromTable, fromKey: fromKey, toTable: toTable, toKey: toKey) - while statement.step(handle: self.database.handle, path: self.databasePath) { + while statement.step(handle: self.database.handle, pathToRemoveOnError: self.removeDatabaseOnError ? self.databasePath : nil) { } statement.reset() } @@ -1916,7 +1923,7 @@ public final class SqliteValueBox: ValueBox { } if let statement = statement { - while statement.step(handle: self.database.handle, path: self.databasePath) { + while statement.step(handle: self.database.handle, pathToRemoveOnError: self.removeDatabaseOnError ? self.databasePath : nil) { let resultCollectionId = statement.stringAt(0) let resultItemId = statement.stringAt(1) @@ -1942,7 +1949,7 @@ public final class SqliteValueBox: ValueBox { } let statement = self.fullTextInsertStatement(table, collectionId: collectionIdData, itemId: itemIdData, contents: contentsData, tags: tagsData) - while statement.step(handle: self.database.handle, path: self.databasePath) { + while statement.step(handle: self.database.handle, pathToRemoveOnError: self.removeDatabaseOnError ? self.databasePath : nil) { } statement.reset() } @@ -1960,7 +1967,7 @@ public final class SqliteValueBox: ValueBox { } let statement = self.fullTextDeleteStatement(table, itemId: itemIdData) - while statement.step(handle: self.database.handle, path: self.databasePath) { + while statement.step(handle: self.database.handle, pathToRemoveOnError: self.removeDatabaseOnError ? self.databasePath : nil) { } statement.reset() } @@ -1987,7 +1994,7 @@ public final class SqliteValueBox: ValueBox { } var result = 0 - while statement.step(handle: database.handle, true, path: self.databasePath) { + while statement.step(handle: database.handle, true, pathToRemoveOnError: self.removeDatabaseOnError ? self.databasePath : nil) { let value = statement.int32At(0) result = Int(value) } @@ -2005,7 +2012,7 @@ public final class SqliteValueBox: ValueBox { let statement = SqlitePreparedStatement(statement: statementImpl) var result = 0 - while statement.step(handle: database.handle, true, path: self.databasePath) { + while statement.step(handle: database.handle, true, pathToRemoveOnError: self.removeDatabaseOnError ? self.databasePath : nil) { let value = statement.int32At(0) result = Int(value) } diff --git a/submodules/ReactionSelectionNode/Sources/ReactionContextNode.swift b/submodules/ReactionSelectionNode/Sources/ReactionContextNode.swift index 1776369416..1dadfc99ca 100644 --- a/submodules/ReactionSelectionNode/Sources/ReactionContextNode.swift +++ b/submodules/ReactionSelectionNode/Sources/ReactionContextNode.swift @@ -7,11 +7,6 @@ import TelegramPresentationData import AccountContext import TelegramAnimatedStickerNode -public enum ReactionGestureItem { - case like - case unlike -} - public final class ReactionContextItem { public struct Reaction: Equatable { public var rawValue: String @@ -118,6 +113,10 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate { public var reactionSelected: ((ReactionContextItem) -> Void)? private var hapticFeedback: HapticFeedback? + private var standaloneReactionAnimation: StandaloneReactionAnimation? + + private weak var animationTargetView: UIView? + private var animationHideNode: Bool = false public init(context: AccountContext, theme: PresentationTheme, items: [ReactionContextItem]) { self.theme = theme @@ -489,10 +488,7 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate { itemNode.layer.animatePosition(from: itemNode.layer.position, to: targetPosition, duration: duration, removeOnCompletion: false) targetSnapshotView.layer.animateAlpha(from: 0.0, to: 1.0, duration: duration * 0.8) targetSnapshotView.layer.animatePosition(from: sourceFrame.center, to: targetPosition, duration: duration, removeOnCompletion: false) - targetSnapshotView.layer.animateScale(from: itemNode.bounds.width / targetSnapshotView.bounds.width, to: 1.0, duration: duration, removeOnCompletion: false, completion: { [weak self, weak targetSnapshotView] _ in - if let _ = self { - //strongSelf.hapticFeedback.tap() - } + targetSnapshotView.layer.animateScale(from: itemNode.bounds.width / targetSnapshotView.bounds.width, to: 1.0, duration: duration, removeOnCompletion: false, completion: { [weak targetSnapshotView] _ in completedTarget = true intermediateCompletion() @@ -500,11 +496,9 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate { if hideNode { targetView.isHidden = false - /*targetView.layer.animateSpring(from: 0.5 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: duration, initialVelocity: 0.0, damping: 90.0, completion: { _ in*/ - targetSnapshotView?.isHidden = true - targetScaleCompleted = true - intermediateCompletion() - //}) + targetSnapshotView?.isHidden = true + targetScaleCompleted = true + intermediateCompletion() } else { targetScaleCompleted = true intermediateCompletion() @@ -528,6 +522,18 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate { if itemNode.item.reaction.rawValue != value { continue } + + self.animationTargetView = targetView + self.animationHideNode = hideNode + + /*let standaloneReactionAnimation = StandaloneReactionAnimation() + self.standaloneReactionAnimation = standaloneReactionAnimation + standaloneReactionAnimation.frame = self.bounds + self.addSubnode(standaloneReactionAnimation) + standaloneReactionAnimation.animateReactionSelection(context: itemNode.context, theme: self.theme, reaction: itemNode.item, targetView: targetView, currentItemNode: itemNode, hideNode: hideNode, completion: completion) + + return*/ + if hideNode { targetView.isHidden = true } @@ -662,6 +668,14 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate { } } + public func cancelReactionAnimation() { + self.standaloneReactionAnimation?.cancel() + + if let animationTargetView = self.animationTargetView, self.animationHideNode { + animationTargetView.isHidden = false + } + } + public func setHighlightedReaction(_ value: ReactionContextItem.Reaction?) { self.highlightedReaction = value if let (size, insets, anchorRect) = self.validLayout { @@ -678,29 +692,47 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate { } public final class StandaloneReactionAnimation: ASDisplayNode { - private let itemNode: ReactionNode + private var itemNode: ReactionNode? = nil private let hapticFeedback = HapticFeedback() + private var isCancelled: Bool = false - public init(context: AccountContext, theme: PresentationTheme, reaction: ReactionContextItem) { - self.itemNode = ReactionNode(context: context, theme: theme, item: reaction) - + private weak var targetView: UIView? + private var hideNode: Bool = false + + override public init() { super.init() self.isUserInteractionEnabled = false - - self.addSubnode(self.itemNode) } - public func animateReactionSelection(targetView: UIView, hideNode: Bool, completion: @escaping () -> Void) { + public func animateReactionSelection(context: AccountContext, theme: PresentationTheme, reaction: ReactionContextItem, targetView: UIView, hideNode: Bool, completion: @escaping () -> Void) { + self.animateReactionSelection(context: context, theme: theme, reaction: reaction, targetView: targetView, currentItemNode: nil, hideNode: hideNode, completion: completion) + } + + func animateReactionSelection(context: AccountContext, theme: PresentationTheme, reaction: ReactionContextItem, targetView: UIView, currentItemNode: ReactionNode?, hideNode: Bool, completion: @escaping () -> Void) { guard let sourceSnapshotView = targetView.snapshotContentTree() else { completion() return } + + self.targetView = targetView + self.hideNode = hideNode + + let itemNode: ReactionNode + if let currentItemNode = currentItemNode { + itemNode = currentItemNode + } else { + itemNode = ReactionNode(context: context, theme: theme, item: reaction) + } + self.itemNode = itemNode + + self.addSubnode(itemNode) + if hideNode { targetView.isHidden = true } - self.itemNode.isExtracted = true + itemNode.isExtracted = true let sourceItemSize: CGFloat = 40.0 let selfTargetRect = self.view.convert(targetView.bounds, from: targetView) @@ -732,7 +764,7 @@ public final class StandaloneReactionAnimation: ASDisplayNode { let animationFrame = expandedFrame.insetBy(dx: -expandedFrame.width * 0.5, dy: -expandedFrame.height * 0.5) .offsetBy(dx: incomingMessage ? (expandedFrame.width - 50.0) : (-expandedFrame.width + 50.0), dy: 0.0) - additionalAnimationNode.setup(source: AnimatedStickerResourceSource(account: self.itemNode.context.account, resource: self.itemNode.item.applicationAnimation.resource), width: Int(animationFrame.width * 2.0), height: Int(animationFrame.height * 2.0), playbackMode: .once, mode: .direct(cachePathPrefix: self.itemNode.context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(self.itemNode.item.applicationAnimation.resource.id))) + additionalAnimationNode.setup(source: AnimatedStickerResourceSource(account: itemNode.context.account, resource: itemNode.item.applicationAnimation.resource), width: Int(animationFrame.width * 2.0), height: Int(animationFrame.height * 2.0), playbackMode: .once, mode: .direct(cachePathPrefix: itemNode.context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(itemNode.item.applicationAnimation.resource.id))) additionalAnimationNode.frame = animationFrame if incomingMessage { additionalAnimationNode.transform = CATransform3DMakeScale(-1.0, 1.0, 1.0) @@ -750,9 +782,28 @@ public final class StandaloneReactionAnimation: ASDisplayNode { } } + var didBeginDismissAnimation = false + let beginDismissAnimation: () -> Void = { [weak self] in + if !didBeginDismissAnimation { + didBeginDismissAnimation = true + + guard let strongSelf = self else { + mainAnimationCompleted = true + intermediateCompletion() + return + } + strongSelf.animateFromItemNodeToReaction(itemNode: itemNode, targetView: targetView, hideNode: hideNode, completion: { + mainAnimationCompleted = true + intermediateCompletion() + }) + } + } + additionalAnimationNode.completed = { _ in additionalAnimationCompleted = true intermediateCompletion() + + beginDismissAnimation() } DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.1 * UIView.animationDurationFactor(), execute: { @@ -760,11 +811,9 @@ public final class StandaloneReactionAnimation: ASDisplayNode { }) DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 2.0, execute: { - self.animateFromItemNodeToReaction(itemNode: self.itemNode, targetView: targetView, hideNode: hideNode, completion: { - mainAnimationCompleted = true - intermediateCompletion() - }) + beginDismissAnimation() }) + } private func animateFromItemNodeToReaction(itemNode: ReactionNode, targetView: UIView, hideNode: Bool, completion: @escaping () -> Void) { @@ -795,9 +844,6 @@ public final class StandaloneReactionAnimation: ASDisplayNode { targetSnapshotView.layer.animateAlpha(from: 0.0, to: 1.0, duration: duration * 0.8) targetSnapshotView.layer.animatePosition(from: sourceFrame.center, to: targetPosition, duration: duration, removeOnCompletion: false) targetSnapshotView.layer.animateScale(from: itemNode.bounds.width / targetSnapshotView.bounds.width, to: 1.0, duration: duration, removeOnCompletion: false, completion: { [weak targetSnapshotView] _ in - /*if let strongSelf = self { - strongSelf.hapticFeedback.tap() - }*/ completedTarget = true intermediateCompletion() @@ -805,11 +851,9 @@ public final class StandaloneReactionAnimation: ASDisplayNode { if hideNode { targetView.isHidden = false - /*targetView.layer.animateSpring(from: 0.5 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: duration, initialVelocity: 0.0, damping: 90.0, completion: { _ in*/ - targetSnapshotView?.isHidden = true - targetScaleCompleted = true - intermediateCompletion() - //}) + targetSnapshotView?.isHidden = true + targetScaleCompleted = true + intermediateCompletion() } else { targetScaleCompleted = true intermediateCompletion() @@ -823,6 +867,14 @@ public final class StandaloneReactionAnimation: ASDisplayNode { self.bounds = self.bounds.offsetBy(dx: 0.0, dy: offset.y) transition.animateOffsetAdditive(node: self, offset: -offset.y) } + + public func cancel() { + self.isCancelled = true + + if let targetView = self.targetView, self.hideNode { + targetView.isHidden = false + } + } } public final class StandaloneDismissReactionAnimation: ASDisplayNode { diff --git a/submodules/SettingsUI/Sources/Reactions/ReactionChatPreviewItem.swift b/submodules/SettingsUI/Sources/Reactions/ReactionChatPreviewItem.swift index ed105b9b1f..906d2f538b 100644 --- a/submodules/SettingsUI/Sources/Reactions/ReactionChatPreviewItem.swift +++ b/submodules/SettingsUI/Sources/Reactions/ReactionChatPreviewItem.swift @@ -132,6 +132,7 @@ class ReactionChatPreviewItemNode: ListViewItemNode { for reaction in availableReactions.reactions { if reaction.value == updatedReaction { if let standaloneReactionAnimation = self.standaloneReactionAnimation { + standaloneReactionAnimation.cancel() standaloneReactionAnimation.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak standaloneReactionAnimation] _ in standaloneReactionAnimation?.removeFromSupernode() }) @@ -139,19 +140,24 @@ class ReactionChatPreviewItemNode: ListViewItemNode { } if let supernode = self.supernode { - let standaloneReactionAnimation = StandaloneReactionAnimation(context: item.context, theme: item.theme, reaction: ReactionContextItem( - reaction: ReactionContextItem.Reaction(rawValue: reaction.value), - stillAnimation: reaction.selectAnimation, - listAnimation: reaction.activateAnimation, - applicationAnimation: reaction.effectAnimation - )) + let standaloneReactionAnimation = StandaloneReactionAnimation() self.standaloneReactionAnimation = standaloneReactionAnimation supernode.addSubnode(standaloneReactionAnimation) standaloneReactionAnimation.frame = supernode.bounds - standaloneReactionAnimation.animateReactionSelection(targetView: targetView, hideNode: true, completion: { [weak standaloneReactionAnimation] in + standaloneReactionAnimation.animateReactionSelection( + context: item.context, theme: item.theme, reaction: ReactionContextItem( + reaction: ReactionContextItem.Reaction(rawValue: reaction.value), + stillAnimation: reaction.selectAnimation, + listAnimation: reaction.activateAnimation, + applicationAnimation: reaction.effectAnimation + ), + targetView: targetView, + hideNode: true, + completion: { [weak standaloneReactionAnimation] in standaloneReactionAnimation?.removeFromSupernode() - }) + } + ) } break diff --git a/submodules/TelegramCore/Sources/Account/Account.swift b/submodules/TelegramCore/Sources/Account/Account.swift index 8012a5d5ae..f0b6e79b90 100644 --- a/submodules/TelegramCore/Sources/Account/Account.swift +++ b/submodules/TelegramCore/Sources/Account/Account.swift @@ -173,7 +173,8 @@ public func accountWithId(accountManager: AccountManager { } } - fileprivate init?(queue: Queue, basePath: String, isTemporary: Bool, isReadOnly: Bool, useCaches: Bool, temporarySessionId: Int64) { + fileprivate init?(queue: Queue, basePath: String, isTemporary: Bool, isReadOnly: Bool, useCaches: Bool, removeDatabaseOnError: Bool, temporarySessionId: Int64) { let startTime = CFAbsoluteTimeGetCurrent() self.queue = queue @@ -82,11 +82,11 @@ final class AccountManagerImpl { self.loginTokensPath = "\(basePath)/login-tokens" self.temporarySessionId = temporarySessionId let _ = try? FileManager.default.createDirectory(atPath: basePath, withIntermediateDirectories: true, attributes: nil) - guard let guardValueBox = SqliteValueBox(basePath: basePath + "/guard_db", queue: queue, isTemporary: isTemporary, isReadOnly: false, useCaches: useCaches, encryptionParameters: nil, upgradeProgress: { _ in }) else { + guard let guardValueBox = SqliteValueBox(basePath: basePath + "/guard_db", queue: queue, isTemporary: isTemporary, isReadOnly: false, useCaches: useCaches, removeDatabaseOnError: removeDatabaseOnError, encryptionParameters: nil, upgradeProgress: { _ in }) else { return nil } self.guardValueBox = guardValueBox - guard let valueBox = SqliteValueBox(basePath: basePath + "/db", queue: queue, isTemporary: isTemporary, isReadOnly: isReadOnly, useCaches: useCaches, encryptionParameters: nil, upgradeProgress: { _ in }) else { + guard let valueBox = SqliteValueBox(basePath: basePath + "/db", queue: queue, isTemporary: isTemporary, isReadOnly: isReadOnly, useCaches: useCaches, removeDatabaseOnError: removeDatabaseOnError, encryptionParameters: nil, upgradeProgress: { _ in }) else { return nil } self.valueBox = valueBox @@ -510,7 +510,7 @@ public final class AccountManager { return AccountManagerImpl.getCurrentRecords(basePath: basePath) } - public init(basePath: String, isTemporary: Bool, isReadOnly: Bool, useCaches: Bool) { + public init(basePath: String, isTemporary: Bool, isReadOnly: Bool, useCaches: Bool, removeDatabaseOnError: Bool) { self.queue = sharedQueue self.basePath = basePath var temporarySessionId: Int64 = 0 @@ -518,7 +518,7 @@ public final class AccountManager { self.temporarySessionId = temporarySessionId let queue = self.queue self.impl = QueueLocalObject(queue: queue, generate: { - if let value = AccountManagerImpl(queue: queue, basePath: basePath, isTemporary: isTemporary, isReadOnly: isReadOnly, useCaches: useCaches, temporarySessionId: temporarySessionId) { + if let value = AccountManagerImpl(queue: queue, basePath: basePath, isTemporary: isTemporary, isReadOnly: isReadOnly, useCaches: useCaches, removeDatabaseOnError: removeDatabaseOnError, temporarySessionId: temporarySessionId) { return value } else { preconditionFailure() diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_StandaloneAccountTransaction.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_StandaloneAccountTransaction.swift index ea3f266d0e..18ace49fe7 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_StandaloneAccountTransaction.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_StandaloneAccountTransaction.swift @@ -97,9 +97,9 @@ public enum AccountTransactionError { case couldNotOpen } -public func accountTransaction(rootPath: String, id: AccountRecordId, encryptionParameters: ValueBoxEncryptionParameters, isReadOnly: Bool, useCopy: Bool = false, useCaches: Bool = true, transaction: @escaping (Postbox, Transaction) -> T) -> Signal { +public func accountTransaction(rootPath: String, id: AccountRecordId, encryptionParameters: ValueBoxEncryptionParameters, isReadOnly: Bool, useCopy: Bool = false, useCaches: Bool = true, removeDatabaseOnError: Bool = true, transaction: @escaping (Postbox, Transaction) -> T) -> Signal { let path = "\(rootPath)/\(accountRecordIdPathName(id))" - let postbox = openPostbox(basePath: path + "/postbox", seedConfiguration: telegramPostboxSeedConfiguration, encryptionParameters: encryptionParameters, timestampForAbsoluteTimeBasedOperations: Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970), isTemporary: true, isReadOnly: isReadOnly, useCopy: useCopy, useCaches: useCaches) + let postbox = openPostbox(basePath: path + "/postbox", seedConfiguration: telegramPostboxSeedConfiguration, encryptionParameters: encryptionParameters, timestampForAbsoluteTimeBasedOperations: Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970), isTemporary: true, isReadOnly: isReadOnly, useCopy: useCopy, useCaches: useCaches, removeDatabaseOnError: removeDatabaseOnError) return postbox |> castError(AccountTransactionError.self) |> mapToSignal { value -> Signal in diff --git a/submodules/TelegramPresentationData/Sources/DefaultPresentationStrings.swift b/submodules/TelegramPresentationData/Sources/DefaultPresentationStrings.swift index 22191fc8e1..53da3d2101 100644 --- a/submodules/TelegramPresentationData/Sources/DefaultPresentationStrings.swift +++ b/submodules/TelegramPresentationData/Sources/DefaultPresentationStrings.swift @@ -95,3 +95,31 @@ public func dataSizeString(_ size: Int64, forceDecimal: Bool = false, formatting return formatting.byte("\(size)").string } } + +public func countString(_ count: Int64, forceDecimal: Bool = false) -> String { + let decimalSeparator = "." + if count >= 1000 * 1000 * 1000 { + let remainder = Int64((Double(count % (1000 * 1000 * 1000)) / (1000 * 1000 * 100.0)).rounded(.down)) + if remainder != 0 || forceDecimal { + return "\(count / (1000 * 1000 * 1000))\(decimalSeparator)\(remainder)T" + } else { + return "\(count / (1000 * 1000 * 1000))T" + } + } else if count >= 1000 * 1000 { + let remainder = Int64((Double(count % (1000 * 1000)) / (1000.0 * 100.0)).rounded(.down)) + if remainder != 0 || forceDecimal { + return "\(count / (1000 * 1000))\(decimalSeparator)\(remainder)M" + } else { + return "\(count / (1000 * 1000))M" + } + } else if count >= 1000 { + let remainder = (count % (1000)) / (102) + if remainder != 0 || forceDecimal { + return "\(count / 1000)\(decimalSeparator)\(remainder)K" + } else { + return "\(count / 1000)K" + } + } else { + return "\(count)" + } +} diff --git a/submodules/TelegramUI/Sources/AppDelegate.swift b/submodules/TelegramUI/Sources/AppDelegate.swift index 58ce4b2f49..a43ff5cb3d 100644 --- a/submodules/TelegramUI/Sources/AppDelegate.swift +++ b/submodules/TelegramUI/Sources/AppDelegate.swift @@ -705,7 +705,7 @@ private func extractAccountManagerState(records: AccountRecordsView(basePath: rootPath + "/accounts-metadata", isTemporary: false, isReadOnly: false, useCaches: true) + let accountManager = AccountManager(basePath: rootPath + "/accounts-metadata", isTemporary: false, isReadOnly: false, useCaches: true, removeDatabaseOnError: true) self.accountManager = accountManager telegramUIDeclareEncodables() diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index d65e57448a..98ca92e802 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -429,12 +429,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G private weak var currentPinchController: PinchController? private weak var currentPinchSourceItemNode: ListViewItemNode? - private weak var currentReactionContextController: ContextController? - private weak var currentReactionContextItemNode: ListViewItemNode? - - private weak var currentStandaloneReactionAnimation: StandaloneReactionAnimation? - private weak var currentStandaloneReactionItemNode: ListViewItemNode? - private var screenCaptureManager: ScreenCaptureDetectionManager? private let chatAdditionalDataDisposable = MetaDisposable() @@ -594,6 +588,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G guard let strongSelf = self else { return true } + + strongSelf.chatDisplayNode.messageTransitionNode.dismissMessageReactionContexts() + if strongSelf.presentVoiceMessageDiscardAlert(action: action, performAction: false) { return false } @@ -1025,6 +1022,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } } + strongSelf.chatDisplayNode.messageTransitionNode.dismissMessageReactionContexts() + let controller = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .extracted(ChatMessageContextExtractedContentSource(chatNode: strongSelf.chatDisplayNode, postbox: strongSelf.context.account.postbox, message: message, selectAll: selectAll)), items: .single(actions), recognizer: recognizer, gesture: gesture) strongSelf.currentContextController = controller @@ -1059,8 +1058,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G return } if let itemNode = itemNode, let targetView = itemNode.targetReactionView(value: updatedReaction) { - strongSelf.currentReactionContextController = controller - strongSelf.currentReactionContextItemNode = itemNode + strongSelf.chatDisplayNode.messageTransitionNode.addMessageContextController(messageId: item.message.id, contextController: controller) controller.dismissWithReaction(value: updatedReaction, targetView: targetView, hideNode: true, completion: { [weak itemNode, weak targetView] in guard let strongSelf = self, let itemNode = itemNode, let targetView = targetView else { @@ -1116,6 +1114,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G }) }))) + strongSelf.chatDisplayNode.messageTransitionNode.dismissMessageReactionContexts() + let controller = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .extracted(ChatMessageReactionContextExtractedContentSource(chatNode: strongSelf.chatDisplayNode, postbox: strongSelf.context.account.postbox, message: message, contentNode: sourceNode)), items: .single(items), recognizer: nil, gesture: gesture) dismissController = { [weak controller] completion in @@ -1238,44 +1238,54 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G if let itemNode = itemNode, let item = itemNode.item, let availableReactions = item.associatedData.availableReactions, let targetView = itemNode.targetReactionView(value: updatedReaction) { for reaction in availableReactions.reactions { if reaction.value == updatedReaction { - let standaloneReactionAnimation = StandaloneReactionAnimation(context: strongSelf.context, theme: strongSelf.presentationData.theme, reaction: ReactionContextItem( - reaction: ReactionContextItem.Reaction(rawValue: reaction.value), - stillAnimation: reaction.selectAnimation, - listAnimation: reaction.activateAnimation, - applicationAnimation: reaction.effectAnimation - )) + let standaloneReactionAnimation = StandaloneReactionAnimation() - strongSelf.currentStandaloneReactionAnimation = standaloneReactionAnimation - strongSelf.currentStandaloneReactionItemNode = itemNode + strongSelf.chatDisplayNode.messageTransitionNode.addMessageStandaloneReactionAnimation(messageId: item.message.id, standaloneReactionAnimation: standaloneReactionAnimation) strongSelf.chatDisplayNode.addSubnode(standaloneReactionAnimation) standaloneReactionAnimation.frame = strongSelf.chatDisplayNode.bounds - standaloneReactionAnimation.animateReactionSelection(targetView: targetView, hideNode: true, completion: { [weak standaloneReactionAnimation] in - standaloneReactionAnimation?.removeFromSupernode() - }) + standaloneReactionAnimation.animateReactionSelection( + context: strongSelf.context, + theme: strongSelf.presentationData.theme, + reaction: ReactionContextItem( + reaction: ReactionContextItem.Reaction(rawValue: reaction.value), + stillAnimation: reaction.selectAnimation, + listAnimation: reaction.activateAnimation, + applicationAnimation: reaction.effectAnimation + ), + targetView: targetView, + hideNode: true, + completion: { [weak standaloneReactionAnimation] in + standaloneReactionAnimation?.removeFromSupernode() + } + ) break } } } }) - } else if let removedReaction = removedReaction, let targetView = itemNode.targetReactionView(value: removedReaction), shouldDisplayInlineDateReactions(message: message) { - var hideRemovedReaction: Bool = false - if let reactions = mergedMessageReactions(attributes: message.attributes) { - for reaction in reactions.reactions { - if reaction.value == removedReaction { - hideRemovedReaction = reaction.count == 1 - break + } else { + strongSelf.chatDisplayNode.messageTransitionNode.dismissMessageReactionContexts(itemNode: itemNode) + + if let removedReaction = removedReaction, let targetView = itemNode.targetReactionView(value: removedReaction), shouldDisplayInlineDateReactions(message: message) { + var hideRemovedReaction: Bool = false + if let reactions = mergedMessageReactions(attributes: message.attributes) { + for reaction in reactions.reactions { + if reaction.value == removedReaction { + hideRemovedReaction = reaction.count == 1 + break + } } } + + let standaloneDismissAnimation = StandaloneDismissReactionAnimation() + standaloneDismissAnimation.frame = strongSelf.chatDisplayNode.bounds + strongSelf.chatDisplayNode.addSubnode(standaloneDismissAnimation) + standaloneDismissAnimation.animateReactionDismiss(sourceView: targetView, hideNode: hideRemovedReaction, completion: { [weak standaloneDismissAnimation] in + standaloneDismissAnimation?.removeFromSupernode() + }) } - - let standaloneDismissAnimation = StandaloneDismissReactionAnimation() - standaloneDismissAnimation.frame = strongSelf.chatDisplayNode.bounds - strongSelf.chatDisplayNode.addSubnode(standaloneDismissAnimation) - standaloneDismissAnimation.animateReactionDismiss(sourceView: targetView, hideNode: hideRemovedReaction, completion: { [weak standaloneDismissAnimation] in - standaloneDismissAnimation?.removeFromSupernode() - }) } let _ = updateMessageReactionsInteractively(account: strongSelf.context.account, messageId: message.id, reaction: updatedReaction).start() @@ -2471,6 +2481,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } }))) + strongSelf.chatDisplayNode.messageTransitionNode.dismissMessageReactionContexts() + let controller = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .extracted(ChatMessageContextExtractedContentSource(chatNode: strongSelf.chatDisplayNode, postbox: strongSelf.context.account.postbox, message: message, selectAll: true)), items: .single(ContextController.Items(content: .list(actions))), recognizer: nil) strongSelf.currentContextController = controller strongSelf.forEachController({ controller in @@ -2548,6 +2560,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G f(.dismissWithoutContent) }))) + strongSelf.chatDisplayNode.messageTransitionNode.dismissMessageReactionContexts() + let controller = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .extracted(ChatMessageContextExtractedContentSource(chatNode: strongSelf.chatDisplayNode, postbox: strongSelf.context.account.postbox, message: topMessage, selectAll: true)), items: .single(ContextController.Items(content: .list(actions))), recognizer: nil) strongSelf.currentContextController = controller strongSelf.forEachController({ controller in @@ -2981,6 +2995,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G return items } + strongSelf.chatDisplayNode.messageTransitionNode.dismissMessageReactionContexts() + let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .controller(ContextControllerContentSourceImpl(controller: galleryController, sourceNode: node, passthroughTouches: false)), items: items |> map { ContextController.Items(content: .list($0)) }, gesture: gesture) strongSelf.presentInGlobalOverlay(contextController) }) @@ -3287,6 +3303,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G return items } + strongSelf.chatDisplayNode.messageTransitionNode.dismissMessageReactionContexts() + let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .controller(ContextControllerContentSourceImpl(controller: galleryController, sourceNode: node, passthroughTouches: false)), items: items |> map { ContextController.Items(content: .list($0)) }, gesture: gesture) strongSelf.presentInGlobalOverlay(contextController) } @@ -4888,26 +4906,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } } - if let reactionItemNode = strongSelf.currentReactionContextItemNode, let currentReactionContextController = strongSelf.currentReactionContextController { - if let itemNode = itemNode { - if itemNode === reactionItemNode { - currentReactionContextController.addRelativeContentOffset(CGPoint(x: 0.0, y: -offset), transition: transition) - } - } else { - currentReactionContextController.addRelativeContentOffset(CGPoint(x: 0.0, y: -offset), transition: transition) - } - } - - if let standaloneReactionItemNode = strongSelf.currentStandaloneReactionItemNode, let currentStandaloneReactionAnimation = strongSelf.currentStandaloneReactionAnimation { - if let itemNode = itemNode { - if itemNode === standaloneReactionItemNode { - currentStandaloneReactionAnimation.addRelativeContentOffset(CGPoint(x: 0.0, y: -offset), transition: transition) - } - } else { - currentStandaloneReactionAnimation.addRelativeContentOffset(CGPoint(x: 0.0, y: -offset), transition: transition) - } - } - strongSelf.chatDisplayNode.messageTransitionNode.addExternalOffset(offset: offset, transition: transition, itemNode: itemNode) } @@ -6271,6 +6269,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G return items } + + strongSelf.chatDisplayNode.messageTransitionNode.dismissMessageReactionContexts() let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .controller(ContextControllerContentSourceImpl(controller: chatController, sourceNode: sourceNode, passthroughTouches: true)), items: items |> map { ContextController.Items(content: .list($0)) }) contextController.dismissedForCancel = { [weak chatController] in @@ -7750,6 +7750,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G let chatController = strongSelf.context.sharedContext.makeChatController(context: strongSelf.context, chatLocation: .peer(peerId), subject: .pinnedMessages(id: pinnedMessage.message.id), botStart: nil, mode: .standard(previewing: true)) chatController.canReadHistory.set(false) + + strongSelf.chatDisplayNode.messageTransitionNode.dismissMessageReactionContexts() + let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .controller(ContextControllerContentSourceImpl(controller: chatController, sourceNode: node, passthroughTouches: true)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture) strongSelf.presentInGlobalOverlay(contextController) }, joinGroupCall: { [weak self] activeCall in @@ -7812,6 +7815,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G items.append(.custom(ChatSendAsPeerTitleContextItem(text: strongSelf.presentationInterfaceState.strings.Conversation_SendMesageAs.uppercased()), false)) items.append(.custom(ChatSendAsPeerListContextItem(context: strongSelf.context, chatPeerId: peerId, peers: peers, selectedPeerId: myPeerId), false)) + strongSelf.chatDisplayNode.messageTransitionNode.dismissMessageReactionContexts() + let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .reference(ChatControllerContextReferenceContentSource(controller: strongSelf, sourceNode: node, insets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: bottomInset, right: 0.0))), items: .single(ContextController.Items(content: .list(items))), gesture: gesture) contextController.dismissed = { [weak self] in if let strongSelf = self { @@ -11998,6 +12003,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G let chatController = strongSelf.context.sharedContext.makeChatController(context: strongSelf.context, chatLocation: .peer(peerId), subject: .message(id: .timestamp(timestamp), highlight: false, timecode: nil), botStart: nil, mode: .standard(previewing: true)) chatController.canReadHistory.set(false) + + strongSelf.chatDisplayNode.messageTransitionNode.dismissMessageReactionContexts() + let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .controller(ContextControllerContentSourceImpl(controller: chatController, sourceNode: sourceNode, sourceRect: sourceRect, passthroughTouches: true)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture) strongSelf.presentInGlobalOverlay(contextController) } diff --git a/submodules/TelegramUI/Sources/ChatMessageAnimatedStickerItemNode.swift b/submodules/TelegramUI/Sources/ChatMessageAnimatedStickerItemNode.swift index f7beb9ea94..ad2c8bfc1a 100644 --- a/submodules/TelegramUI/Sources/ChatMessageAnimatedStickerItemNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageAnimatedStickerItemNode.swift @@ -1144,7 +1144,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { layoutSize.height += actionButtonsSizeAndApply.0.height } if let reactionButtonsSizeAndApply = reactionButtonsSizeAndApply { - layoutSize.height += reactionButtonsSizeAndApply.0.height + layoutSize.height += 4.0 + reactionButtonsSizeAndApply.0.height } return (ListViewItemNodeLayout(contentSize: layoutSize, insets: layoutInsets), { [weak self] animation, _, _ in @@ -1411,7 +1411,10 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { if let reactionButtonsSizeAndApply = reactionButtonsSizeAndApply { let reactionButtonsNode = reactionButtonsSizeAndApply.1(animation) - let reactionButtonsFrame = CGRect(origin: CGPoint(x: imageFrame.minX, y: imageFrame.maxY), size: reactionButtonsSizeAndApply.0) + var reactionButtonsFrame = CGRect(origin: CGPoint(x: imageFrame.minX, y: imageFrame.maxY), size: reactionButtonsSizeAndApply.0) + if let actionButtonsSizeAndApply = actionButtonsSizeAndApply { + reactionButtonsFrame.origin.y += 4.0 + actionButtonsSizeAndApply.0.height + } if reactionButtonsNode !== strongSelf.reactionButtonsNode { strongSelf.reactionButtonsNode = reactionButtonsNode reactionButtonsNode.reactionSelected = { value in diff --git a/submodules/TelegramUI/Sources/ChatMessageInstantVideoItemNode.swift b/submodules/TelegramUI/Sources/ChatMessageInstantVideoItemNode.swift index ba8455156d..0857dcdb14 100644 --- a/submodules/TelegramUI/Sources/ChatMessageInstantVideoItemNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageInstantVideoItemNode.swift @@ -558,7 +558,7 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureRecognizerD if !reactions.reactions.isEmpty { let totalInset = params.leftInset + layoutConstants.bubble.edgeInset * 2.0 + avatarInset + layoutConstants.bubble.contentInsets.left + params.rightInset + layoutConstants.bubble.contentInsets.right - let maxReactionsWidth = params.width - totalInset + let maxReactionsWidth = params.width - totalInset - 8.0 let (minWidth, buttonsLayout) = reactionButtonsLayout(ChatMessageReactionButtonsNode.Arguments( context: item.context, presentationData: item.presentationData, @@ -787,7 +787,10 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureRecognizerD if let reactionButtonsSizeAndApply = reactionButtonsSizeAndApply { let reactionButtonsNode = reactionButtonsSizeAndApply.1(animation) - let reactionButtonsFrame = CGRect(origin: CGPoint(x: videoFrame.minX, y: videoFrame.maxY + 6.0), size: reactionButtonsSizeAndApply.0) + var reactionButtonsFrame = CGRect(origin: CGPoint(x: videoFrame.minX, y: videoFrame.maxY + 6.0), size: reactionButtonsSizeAndApply.0) + if let actionButtonsSizeAndApply = actionButtonsSizeAndApply { + reactionButtonsFrame.origin.y += 4.0 + actionButtonsSizeAndApply.0.height + } if reactionButtonsNode !== strongSelf.reactionButtonsNode { strongSelf.reactionButtonsNode = reactionButtonsNode reactionButtonsNode.reactionSelected = { value in @@ -805,6 +808,16 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureRecognizerD item.controllerInteraction.openMessageReactionContextMenu(item.message, sourceNode, gesture, value) } reactionButtonsNode.frame = reactionButtonsFrame + if let (rect, containerSize) = strongSelf.absoluteRect { + var rect = rect + rect.origin.y = containerSize.height - rect.maxY + strongSelf.insets.top + + var reactionButtonsNodeFrame = reactionButtonsFrame + reactionButtonsNodeFrame.origin.x += rect.minX + reactionButtonsNodeFrame.origin.y += rect.minY + + reactionButtonsNode.update(rect: rect, within: containerSize, transition: .immediate) + } strongSelf.addSubnode(reactionButtonsNode) if animation.isAnimated { reactionButtonsNode.animateIn(animation: animation) @@ -1338,6 +1351,28 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureRecognizerD item.controllerInteraction.openMessageContextMenu(item.message, false, self, self.interactiveVideoNode.frame, nil) } + private var absoluteRect: (CGRect, CGSize)? + override func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) { + self.absoluteRect = (rect, containerSize) + + var rect = rect + rect.origin.y = containerSize.height - rect.maxY + self.insets.top + + if let reactionButtonsNode = self.reactionButtonsNode { + var reactionButtonsNodeFrame = reactionButtonsNode.frame + reactionButtonsNodeFrame.origin.x += rect.minX + reactionButtonsNodeFrame.origin.y += rect.minY + + reactionButtonsNode.update(rect: rect, within: containerSize, transition: .immediate) + } + } + + override func applyAbsoluteOffset(value: CGPoint, animationCurve: ContainedViewLayoutTransitionCurve, duration: Double) { + if let reactionButtonsNode = self.reactionButtonsNode { + reactionButtonsNode.offset(value: value, animationCurve: animationCurve, duration: duration) + } + } + override func targetReactionView(value: String) -> UIView? { if let result = self.reactionButtonsNode?.reactionTargetView(value: value) { return result diff --git a/submodules/TelegramUI/Sources/ChatMessageStickerItemNode.swift b/submodules/TelegramUI/Sources/ChatMessageStickerItemNode.swift index 619222d1f8..c16ce72c85 100644 --- a/submodules/TelegramUI/Sources/ChatMessageStickerItemNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageStickerItemNode.swift @@ -689,7 +689,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView { layoutSize.height += dateAndStatusSize.height } if let reactionButtonsSizeAndApply = reactionButtonsSizeAndApply { - layoutSize.height += reactionButtonsSizeAndApply.0.height + layoutSize.height += 4.0 + reactionButtonsSizeAndApply.0.height } if let actionButtonsSizeAndApply = actionButtonsSizeAndApply { layoutSize.height += actionButtonsSizeAndApply.0.height @@ -986,7 +986,10 @@ class ChatMessageStickerItemNode: ChatMessageItemView { if let reactionButtonsSizeAndApply = reactionButtonsSizeAndApply { let reactionButtonsNode = reactionButtonsSizeAndApply.1(animation) - let reactionButtonsFrame = CGRect(origin: CGPoint(x: imageFrame.minX, y: imageFrame.maxY - 10.0), size: reactionButtonsSizeAndApply.0) + var reactionButtonsFrame = CGRect(origin: CGPoint(x: imageFrame.minX, y: imageFrame.maxY - 10.0), size: reactionButtonsSizeAndApply.0) + if let actionButtonsSizeAndApply = actionButtonsSizeAndApply { + reactionButtonsFrame.origin.y += 4.0 + actionButtonsSizeAndApply.0.height + } if reactionButtonsNode !== strongSelf.reactionButtonsNode { strongSelf.reactionButtonsNode = reactionButtonsNode reactionButtonsNode.reactionSelected = { value in diff --git a/submodules/TelegramUI/Sources/ChatMessageTransitionNode.swift b/submodules/TelegramUI/Sources/ChatMessageTransitionNode.swift index cf4d579b2c..62e47ed088 100644 --- a/submodules/TelegramUI/Sources/ChatMessageTransitionNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageTransitionNode.swift @@ -5,6 +5,10 @@ import Display import ContextUI import AnimatedStickerNode import SwiftSignalKit +import ContextUI +import Postbox +import TelegramCore +import ReactionSelectionNode private final class OverlayTransitionContainerNode: ViewControllerTracingNode { override init() { @@ -595,6 +599,53 @@ public final class ChatMessageTransitionNode: ASDisplayNode { } } } + + private final class MessageReactionContext { + private(set) weak var itemNode: ListViewItemNode? + private(set) weak var contextController: ContextController? + private(set) weak var standaloneReactionAnimation: StandaloneReactionAnimation? + + var isEmpty: Bool { + return self.contextController == nil && self.standaloneReactionAnimation == nil + } + + init(itemNode: ListViewItemNode, contextController: ContextController?, standaloneReactionAnimation: StandaloneReactionAnimation?) { + self.itemNode = itemNode + self.contextController = contextController + self.standaloneReactionAnimation = standaloneReactionAnimation + } + + func addExternalOffset(offset: CGFloat, transition: ContainedViewLayoutTransition, itemNode: ListViewItemNode?) { + guard let currentItemNode = self.itemNode else { + return + } + if itemNode == nil || itemNode === currentItemNode { + if let contextController = self.contextController { + contextController.addRelativeContentOffset(CGPoint(x: 0.0, y: -offset), transition: transition) + } + if let standaloneReactionAnimation = self.standaloneReactionAnimation { + standaloneReactionAnimation.addRelativeContentOffset(CGPoint(x: 0.0, y: -offset), transition: transition) + } + } + } + + func addContentOffset(offset: CGFloat, itemNode: ListViewItemNode?) { + } + + func dismiss() { + if let contextController = self.contextController { + contextController.cancelReactionAnimation() + contextController.view.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { _ in + }) + } + if let standaloneReactionAnimation = self.standaloneReactionAnimation { + standaloneReactionAnimation.cancel() + standaloneReactionAnimation.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak standaloneReactionAnimation] _ in + standaloneReactionAnimation?.removeFromSupernode() + }) + } + } + } private let listNode: ChatHistoryListNode private let getContentAreaInScreenSpace: () -> CGRect @@ -604,6 +655,7 @@ public final class ChatMessageTransitionNode: ASDisplayNode { private var animatingItemNodes: [AnimatingItemNode] = [] private var decorationItemNodes: [DecorationItemNode] = [] + private var messageReactionContexts: [MessageReactionContext] = [] var hasScheduledTransitions: Bool { return self.currentPendingItem != nil @@ -714,6 +766,61 @@ public final class ChatMessageTransitionNode: ASDisplayNode { override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { return nil } + + private func removeEmptyMessageReactionContexts() { + for i in (0 ..< self.messageReactionContexts.count).reversed() { + if self.messageReactionContexts[i].isEmpty { + self.messageReactionContexts.remove(at: i) + } + } + } + + func dismissMessageReactionContexts(itemNode: ListViewItemNode? = nil) { + for i in (0 ..< self.messageReactionContexts.count).reversed() { + let messageReactionContext = self.messageReactionContexts[i] + if itemNode == nil || messageReactionContext.itemNode === itemNode { + self.messageReactionContexts.remove(at: i) + messageReactionContext.dismiss() + } + } + } + + func addMessageContextController(messageId: MessageId, contextController: ContextController) { + self.addMessageReactionContextContext(messageId: messageId, contextController: contextController, standaloneReactionAnimation: nil) + } + + func addMessageStandaloneReactionAnimation(messageId: MessageId, standaloneReactionAnimation: StandaloneReactionAnimation) { + self.addMessageReactionContextContext(messageId: messageId, contextController: nil, standaloneReactionAnimation: standaloneReactionAnimation) + } + + private func addMessageReactionContextContext(messageId: MessageId, contextController: ContextController?, standaloneReactionAnimation: StandaloneReactionAnimation?) { + self.removeEmptyMessageReactionContexts() + + var messageItemNode: ListViewItemNode? + self.listNode.forEachItemNode { itemNode in + if let itemNode = itemNode as? ChatMessageItemView { + if let item = itemNode.item { + for (message, _) in item.content { + if message.id == messageId { + messageItemNode = itemNode + break + } + } + } + } + } + + if let messageItemNode = messageItemNode { + for i in 0 ..< self.messageReactionContexts.count { + if self.messageReactionContexts[i].itemNode === messageItemNode { + self.messageReactionContexts[i].dismiss() + self.messageReactionContexts.remove(at: i) + break + } + } + self.messageReactionContexts.append(MessageReactionContext(itemNode: messageItemNode, contextController: contextController, standaloneReactionAnimation: standaloneReactionAnimation)) + } + } func addExternalOffset(offset: CGFloat, transition: ContainedViewLayoutTransition, itemNode: ListViewItemNode?) { for animatingItemNode in self.animatingItemNodes { @@ -724,6 +831,9 @@ public final class ChatMessageTransitionNode: ASDisplayNode { decorationItemNode.addExternalOffset(offset: offset, transition: transition) } } + for messageReactionContext in self.messageReactionContexts { + messageReactionContext.addExternalOffset(offset: offset, transition: transition, itemNode: itemNode) + } } func addContentOffset(offset: CGFloat, itemNode: ListViewItemNode?) { @@ -735,6 +845,9 @@ public final class ChatMessageTransitionNode: ASDisplayNode { decorationItemNode.addContentOffset(offset: offset) } } + for messageReactionContext in self.messageReactionContexts { + messageReactionContext.addContentOffset(offset: offset, itemNode: itemNode) + } } func isAnimatingMessage(stableId: UInt32) -> Bool { diff --git a/submodules/TelegramUI/Sources/NotificationContentContext.swift b/submodules/TelegramUI/Sources/NotificationContentContext.swift index 0925c65132..7d6151ecf1 100644 --- a/submodules/TelegramUI/Sources/NotificationContentContext.swift +++ b/submodules/TelegramUI/Sources/NotificationContentContext.swift @@ -94,7 +94,7 @@ public final class NotificationViewControllerImpl { if sharedAccountContext == nil { initializeAccountManagement() - let accountManager = AccountManager(basePath: rootPath + "/accounts-metadata", isTemporary: true, isReadOnly: false, useCaches: false) + let accountManager = AccountManager(basePath: rootPath + "/accounts-metadata", isTemporary: true, isReadOnly: false, useCaches: false, removeDatabaseOnError: false) var initialPresentationDataAndSettings: InitialPresentationDataAndSettings? let semaphore = DispatchSemaphore(value: 0) diff --git a/submodules/TelegramUI/Sources/ShareExtensionContext.swift b/submodules/TelegramUI/Sources/ShareExtensionContext.swift index af225ff735..9d4fefba03 100644 --- a/submodules/TelegramUI/Sources/ShareExtensionContext.swift +++ b/submodules/TelegramUI/Sources/ShareExtensionContext.swift @@ -205,7 +205,7 @@ public class ShareRootControllerImpl { let internalContext: InternalContext - let accountManager = AccountManager(basePath: rootPath + "/accounts-metadata", isTemporary: true, isReadOnly: false, useCaches: false) + let accountManager = AccountManager(basePath: rootPath + "/accounts-metadata", isTemporary: true, isReadOnly: false, useCaches: false, removeDatabaseOnError: false) if let globalInternalContext = globalInternalContext { internalContext = globalInternalContext