Merge branches 'master' and 'master' of gitlab.com:peter-iakovlev/telegram-ios

This commit is contained in:
Ilya Laktyushin
2021-12-21 18:36:54 +04:00
26 changed files with 527 additions and 199 deletions
@@ -613,7 +613,7 @@ private final class NotificationServiceHandler {
let appVersion = (Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String) ?? "unknown"
self.accountManager = AccountManager<TelegramAccountManagerTypes>(basePath: rootPath + "/accounts-metadata", isTemporary: true, isReadOnly: false, useCaches: false)
self.accountManager = AccountManager<TelegramAccountManagerTypes>(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)!)
+1 -1
View File
@@ -121,7 +121,7 @@ class DefaultIntentHandler: INExtension, INSendMessageIntentHandling, INSearchFo
let appVersion = (Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String) ?? "unknown"
initializeAccountManagement()
let accountManager = AccountManager<TelegramAccountManagerTypes>(basePath: rootPath + "/accounts-metadata", isTemporary: true, isReadOnly: false, useCaches: false)
let accountManager = AccountManager<TelegramAccountManagerTypes>(basePath: rootPath + "/accounts-metadata", isTemporary: true, isReadOnly: false, useCaches: false, removeDatabaseOnError: false)
self.accountManager = accountManager
let deviceSpecificEncryptionParameters = BuildConfig.deviceSpecificEncryptionParameters(rootPath, baseAppBundleId: baseAppBundleId)
+7 -1
View File
@@ -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"
@@ -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")
@@ -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)
}
@@ -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)
}
@@ -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
}
}
}
@@ -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)
}
}
}
@@ -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)
}
@@ -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 {
+5 -5
View File
@@ -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<PostboxResult, NoError> {
public func openPostbox(basePath: String, seedConfiguration: SeedConfiguration, encryptionParameters: ValueBoxEncryptionParameters, timestampForAbsoluteTimeBasedOperations: Int32, isTemporary: Bool, isReadOnly: Bool, useCopy: Bool, useCaches: Bool, removeDatabaseOnError: Bool) -> Signal<PostboxResult, NoError> {
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)
+45 -38
View File
@@ -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<Void, SqlError> {
func tryStep(handle: OpaquePointer?, _ initial: Bool = false, pathToRemoveOnError: String?) -> Result<Void, SqlError> {
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<Bool>(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)
}
@@ -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 {
@@ -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
@@ -173,7 +173,8 @@ public func accountWithId(accountManager: AccountManager<TelegramAccountManagerT
isTemporary: false,
isReadOnly: false,
useCopy: false,
useCaches: !supplementary
useCaches: !supplementary,
removeDatabaseOnError: !supplementary
)
return postbox
@@ -1295,7 +1296,8 @@ public func standaloneStateManager(
isTemporary: false,
isReadOnly: false,
useCopy: false,
useCaches: false
useCaches: false,
removeDatabaseOnError: false
)
return postbox
@@ -73,7 +73,7 @@ final class AccountManagerImpl<Types: AccountManagerTypes> {
}
}
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<Types: AccountManagerTypes> {
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<Types: AccountManagerTypes> {
return AccountManagerImpl<Types>.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<Types: AccountManagerTypes> {
self.temporarySessionId = temporarySessionId
let queue = self.queue
self.impl = QueueLocalObject(queue: queue, generate: {
if let value = AccountManagerImpl<Types>(queue: queue, basePath: basePath, isTemporary: isTemporary, isReadOnly: isReadOnly, useCaches: useCaches, temporarySessionId: temporarySessionId) {
if let value = AccountManagerImpl<Types>(queue: queue, basePath: basePath, isTemporary: isTemporary, isReadOnly: isReadOnly, useCaches: useCaches, removeDatabaseOnError: removeDatabaseOnError, temporarySessionId: temporarySessionId) {
return value
} else {
preconditionFailure()
@@ -97,9 +97,9 @@ public enum AccountTransactionError {
case couldNotOpen
}
public func accountTransaction<T>(rootPath: String, id: AccountRecordId, encryptionParameters: ValueBoxEncryptionParameters, isReadOnly: Bool, useCopy: Bool = false, useCaches: Bool = true, transaction: @escaping (Postbox, Transaction) -> T) -> Signal<T, AccountTransactionError> {
public func accountTransaction<T>(rootPath: String, id: AccountRecordId, encryptionParameters: ValueBoxEncryptionParameters, isReadOnly: Bool, useCopy: Bool = false, useCaches: Bool = true, removeDatabaseOnError: Bool = true, transaction: @escaping (Postbox, Transaction) -> T) -> Signal<T, AccountTransactionError> {
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<T, AccountTransactionError> in
@@ -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)"
}
}
@@ -705,7 +705,7 @@ private func extractAccountManagerState(records: AccountRecordsView<TelegramAcco
UINavigationController.attemptRotationToDeviceOrientation()
})
let accountManager = AccountManager<TelegramAccountManagerTypes>(basePath: rootPath + "/accounts-metadata", isTemporary: false, isReadOnly: false, useCaches: true)
let accountManager = AccountManager<TelegramAccountManagerTypes>(basePath: rootPath + "/accounts-metadata", isTemporary: false, isReadOnly: false, useCaches: true, removeDatabaseOnError: true)
self.accountManager = accountManager
telegramUIDeclareEncodables()
@@ -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)
}
@@ -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
@@ -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
@@ -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
@@ -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 {
@@ -94,7 +94,7 @@ public final class NotificationViewControllerImpl {
if sharedAccountContext == nil {
initializeAccountManagement()
let accountManager = AccountManager<TelegramAccountManagerTypes>(basePath: rootPath + "/accounts-metadata", isTemporary: true, isReadOnly: false, useCaches: false)
let accountManager = AccountManager<TelegramAccountManagerTypes>(basePath: rootPath + "/accounts-metadata", isTemporary: true, isReadOnly: false, useCaches: false, removeDatabaseOnError: false)
var initialPresentationDataAndSettings: InitialPresentationDataAndSettings?
let semaphore = DispatchSemaphore(value: 0)
@@ -205,7 +205,7 @@ public class ShareRootControllerImpl {
let internalContext: InternalContext
let accountManager = AccountManager<TelegramAccountManagerTypes>(basePath: rootPath + "/accounts-metadata", isTemporary: true, isReadOnly: false, useCaches: false)
let accountManager = AccountManager<TelegramAccountManagerTypes>(basePath: rootPath + "/accounts-metadata", isTemporary: true, isReadOnly: false, useCaches: false, removeDatabaseOnError: false)
if let globalInternalContext = globalInternalContext {
internalContext = globalInternalContext