Various improvements

This commit is contained in:
Ilya Laktyushin 2022-04-24 06:30:30 +04:00
parent c929cce94f
commit b9f141aae4
10 changed files with 279 additions and 151 deletions

View File

@ -7,64 +7,145 @@ import SwiftSignalKit
import TelegramPresentationData
import AccountContext
private struct BotCheckoutPasswordAlertAction {
public let title: String
public let action: () -> Void
private final class BotCheckoutPassworInputFieldNode: ASDisplayNode, UITextFieldDelegate {
private var theme: PresentationTheme
private let backgroundNode: ASImageNode
private let textInputNode: TextFieldNode
private let placeholderNode: ASTextNode
public init(title: String, action: @escaping () -> Void) {
self.title = title
self.action = action
}
}
private final class BotCheckoutPasswordAlertActionNode: HighlightableButtonNode {
private let backgroundNode: ASDisplayNode
var updateHeight: (() -> Void)?
var complete: (() -> Void)?
var textChanged: ((String) -> Void)?
let action: BotCheckoutPasswordAlertAction
private let backgroundInsets = UIEdgeInsets(top: 8.0, left: 16.0, bottom: 15.0, right: 16.0)
private let inputInsets = UIEdgeInsets(top: 5.0, left: 12.0, bottom: 5.0, right: 12.0)
init(theme: PresentationTheme, action: BotCheckoutPasswordAlertAction) {
self.backgroundNode = ASDisplayNode()
self.backgroundNode.isLayerBacked = true
self.backgroundNode.backgroundColor = theme.actionSheet.opaqueItemHighlightedBackgroundColor
self.backgroundNode.alpha = 0.0
self.action = action
super.init()
self.setTitle(action.title, with: Font.regular(17.0), with: theme.actionSheet.controlAccentColor, for: [])
self.setTitle(action.title, with: Font.regular(17.0), with: theme.actionSheet.disabledActionTextColor, for: [.disabled])
self.highligthedChanged = { [weak self] value in
if let strongSelf = self {
if value {
if strongSelf.backgroundNode.supernode == nil {
strongSelf.insertSubnode(strongSelf.backgroundNode, at: 0)
}
strongSelf.backgroundNode.layer.removeAnimation(forKey: "opacity")
strongSelf.backgroundNode.alpha = 1.0
} else if !strongSelf.backgroundNode.alpha.isZero {
strongSelf.backgroundNode.alpha = 0.0
strongSelf.backgroundNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25)
}
}
var text: String {
get {
return self.textInputNode.textField.text ?? ""
}
set {
self.textInputNode.textField.text = newValue
self.placeholderNode.isHidden = !newValue.isEmpty
}
}
override func didLoad() {
super.didLoad()
self.addTarget(self, action: #selector(self.pressed), forControlEvents: .touchUpInside)
var placeholder: String = "" {
didSet {
self.placeholderNode.attributedText = NSAttributedString(string: self.placeholder, font: Font.regular(17.0), textColor: self.theme.actionSheet.inputPlaceholderColor)
}
}
@objc func pressed() {
self.action.action()
init(theme: PresentationTheme, placeholder: String) {
self.theme = theme
self.backgroundNode = ASImageNode()
self.backgroundNode.isLayerBacked = true
self.backgroundNode.displaysAsynchronously = false
self.backgroundNode.displayWithoutProcessing = true
self.backgroundNode.image = generateStretchableFilledCircleImage(diameter: 12.0, color: theme.actionSheet.inputHollowBackgroundColor, strokeColor: theme.actionSheet.inputBorderColor, strokeWidth: 1.0)
self.textInputNode = TextFieldNode()
self.textInputNode.textField.typingAttributes = [NSAttributedString.Key.font: Font.regular(17.0), NSAttributedString.Key.foregroundColor: theme.actionSheet.inputTextColor]
self.textInputNode.textField.clipsToBounds = true
self.textInputNode.hitTestSlop = UIEdgeInsets(top: -5.0, left: -5.0, bottom: -5.0, right: -5.0)
self.textInputNode.textField.keyboardAppearance = theme.rootController.keyboardColor.keyboardAppearance
self.textInputNode.textField.returnKeyType = .done
self.textInputNode.textField.isSecureTextEntry = true
self.textInputNode.textField.tintColor = theme.actionSheet.controlAccentColor
self.placeholderNode = ASTextNode()
self.placeholderNode.isUserInteractionEnabled = false
self.placeholderNode.displaysAsynchronously = false
self.placeholderNode.attributedText = NSAttributedString(string: placeholder, font: Font.regular(17.0), textColor: self.theme.actionSheet.inputPlaceholderColor)
super.init()
self.textInputNode.textField.delegate = self
self.textInputNode.textField.addTarget(self, action: #selector(self.textDidChange), for: .editingChanged)
self.addSubnode(self.backgroundNode)
self.addSubnode(self.textInputNode)
self.addSubnode(self.placeholderNode)
}
override func layout() {
super.layout()
func updateTheme(_ theme: PresentationTheme) {
self.theme = theme
self.backgroundNode.frame = self.bounds
self.backgroundNode.image = generateStretchableFilledCircleImage(diameter: 12.0, color: self.theme.actionSheet.inputHollowBackgroundColor, strokeColor: self.theme.actionSheet.inputBorderColor, strokeWidth: 1.0)
self.textInputNode.textField.keyboardAppearance = self.theme.rootController.keyboardColor.keyboardAppearance
self.placeholderNode.attributedText = NSAttributedString(string: self.placeholderNode.attributedText?.string ?? "", font: Font.regular(17.0), textColor: self.theme.actionSheet.inputPlaceholderColor)
self.textInputNode.textField.tintColor = self.theme.actionSheet.controlAccentColor
self.textInputNode.textField.typingAttributes = [NSAttributedString.Key.font: Font.regular(17.0), NSAttributedString.Key.foregroundColor: theme.actionSheet.inputTextColor]
}
func updateLayout(width: CGFloat, transition: ContainedViewLayoutTransition) -> CGFloat {
let backgroundInsets = self.backgroundInsets
let inputInsets = self.inputInsets
let textFieldHeight = self.calculateTextFieldMetrics(width: width)
let panelHeight = textFieldHeight + backgroundInsets.top + backgroundInsets.bottom
let backgroundFrame = CGRect(origin: CGPoint(x: backgroundInsets.left, y: backgroundInsets.top), size: CGSize(width: width - backgroundInsets.left - backgroundInsets.right, height: panelHeight - backgroundInsets.top - backgroundInsets.bottom))
transition.updateFrame(node: self.backgroundNode, frame: backgroundFrame)
let placeholderSize = self.placeholderNode.measure(backgroundFrame.size)
transition.updateFrame(node: self.placeholderNode, frame: CGRect(origin: CGPoint(x: backgroundFrame.minX + inputInsets.left, y: backgroundFrame.minY + floor((backgroundFrame.size.height - placeholderSize.height) / 2.0)), size: placeholderSize))
transition.updateFrame(node: self.textInputNode, frame: CGRect(origin: CGPoint(x: backgroundFrame.minX + inputInsets.left, y: backgroundFrame.minY), size: CGSize(width: backgroundFrame.size.width - inputInsets.left - inputInsets.right, height: backgroundFrame.size.height)))
return panelHeight
}
func activateInput() {
self.textInputNode.becomeFirstResponder()
}
func deactivateInput() {
self.textInputNode.resignFirstResponder()
}
func shake() {
self.layer.addShakeAnimation()
}
@objc func textDidChange() {
self.updateTextNodeText(animated: true)
self.textChanged?(self.textInputNode.textField.text ?? "")
self.placeholderNode.isHidden = !(self.textInputNode.textField.text ?? "").isEmpty
}
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
if text == "\n" {
self.complete?()
return false
}
return true
}
private func calculateTextFieldMetrics(width: CGFloat) -> CGFloat {
let backgroundInsets = self.backgroundInsets
let inputInsets = self.inputInsets
let unboundTextFieldHeight = max(33.0, ceil(self.textInputNode.measure(CGSize(width: width - backgroundInsets.left - backgroundInsets.right - inputInsets.left - inputInsets.right, height: CGFloat.greatestFiniteMagnitude)).height))
return min(61.0, max(33.0, unboundTextFieldHeight))
}
private func updateTextNodeText(animated: Bool) {
let backgroundInsets = self.backgroundInsets
let textFieldHeight = self.calculateTextFieldMetrics(width: self.bounds.size.width)
let panelHeight = textFieldHeight + backgroundInsets.top + backgroundInsets.bottom
if !self.bounds.size.height.isEqual(to: panelHeight) {
self.updateHeight?()
}
}
@objc func clearPressed() {
self.textInputNode.textField.text = nil
self.deactivateInput()
}
}
@ -78,14 +159,13 @@ private final class BotCheckoutPasswordAlertContentNode: AlertContentNode {
private let textNode: ASTextNode
private let actionNodesSeparator: ASDisplayNode
private let actionNodes: [BotCheckoutPasswordAlertActionNode]
private let actionNodes: [TextAlertContentActionNode]
private let actionVerticalSeparators: [ASDisplayNode]
private let cancelActionNode: BotCheckoutPasswordAlertActionNode
private let doneActionNode: BotCheckoutPasswordAlertActionNode
private let cancelActionNode: TextAlertContentActionNode
private let doneActionNode: TextAlertContentActionNode
private let textFieldNodeBackground: ASImageNode
private let textFieldNode: TextFieldNode
let inputFieldNode: BotCheckoutPassworInputFieldNode
private var validLayout: CGSize?
private var isVerifying = false
@ -99,6 +179,8 @@ private final class BotCheckoutPasswordAlertContentNode: AlertContentNode {
self.requiresBiometrics = requiresBiometrics
self.completion = completion
let alertTheme = AlertControllerTheme(presentationTheme: theme, fontSize: .regular)
let titleNode = ASTextNode()
titleNode.attributedText = NSAttributedString(string: strings.Checkout_PasswordEntry_Title, font: Font.semibold(17.0), textColor: theme.actionSheet.primaryTextColor, paragraphAlignment: .center)
titleNode.displaysAsynchronously = false
@ -112,16 +194,18 @@ private final class BotCheckoutPasswordAlertContentNode: AlertContentNode {
self.textNode.displaysAsynchronously = false
self.textNode.isUserInteractionEnabled = false
self.inputFieldNode = BotCheckoutPassworInputFieldNode(theme: theme, placeholder: passwordTip ?? "")
self.actionNodesSeparator = ASDisplayNode()
self.actionNodesSeparator.isLayerBacked = true
self.actionNodesSeparator.backgroundColor = theme.actionSheet.opaqueItemSeparatorColor
self.cancelActionNode = BotCheckoutPasswordAlertActionNode(theme: theme, action: BotCheckoutPasswordAlertAction(title: strings.Common_Cancel, action: {
self.cancelActionNode = TextAlertContentActionNode(theme: alertTheme, action: TextAlertAction(type: .genericAction, title: strings.Common_Cancel, action: {
cancel()
}))
var doneImpl: (() -> Void)?
self.doneActionNode = BotCheckoutPasswordAlertActionNode(theme: theme, action: BotCheckoutPasswordAlertAction(title: strings.Checkout_PasswordEntry_Pay, action: {
self.doneActionNode = TextAlertContentActionNode(theme: alertTheme, action: TextAlertAction(type: .defaultAction, title: strings.Checkout_PasswordEntry_Pay, action: {
doneImpl?()
}))
@ -138,26 +222,6 @@ private final class BotCheckoutPasswordAlertContentNode: AlertContentNode {
}
self.actionVerticalSeparators = actionVerticalSeparators
self.textFieldNodeBackground = ASImageNode()
self.textFieldNodeBackground.displaysAsynchronously = false
self.textFieldNodeBackground.displayWithoutProcessing = true
self.textFieldNodeBackground.image = generateImage(CGSize(width: 4.0, height: 4.0), rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
context.setStrokeColor(theme.actionSheet.primaryTextColor.cgColor)
context.setLineWidth(UIScreenPixel)
context.stroke(CGRect(origin: CGPoint(), size: size))
})?.stretchableImage(withLeftCapWidth: 2, topCapHeight: 2)
self.textFieldNode = TextFieldNode()
self.textFieldNode.textField.textColor = theme.actionSheet.primaryTextColor
self.textFieldNode.textField.font = Font.regular(12.0)
self.textFieldNode.textField.typingAttributes = [NSAttributedString.Key.font: Font.regular(12.0)]
self.textFieldNode.textField.keyboardAppearance = theme.rootController.keyboardColor.keyboardAppearance
self.textFieldNode.textField.isSecureTextEntry = true
self.textFieldNode.textField.tintColor = theme.list.itemAccentColor
self.textFieldNode.textField.placeholder = passwordTip
super.init()
self.addSubnode(self.titleNode)
@ -173,11 +237,14 @@ private final class BotCheckoutPasswordAlertContentNode: AlertContentNode {
self.addSubnode(separatorNode)
}
self.addSubnode(self.textFieldNodeBackground)
self.addSubnode(self.textFieldNode)
self.textFieldNode.textField.addTarget(self, action: #selector(self.textFieldChanged(_:)), for: .editingChanged)
self.addSubnode(self.inputFieldNode)
self.inputFieldNode.textChanged = { [weak self] _ in
if let strongSelf = self {
strongSelf.updateState()
}
}
self.updateState()
doneImpl = { [weak self] in
@ -213,13 +280,11 @@ private final class BotCheckoutPasswordAlertContentNode: AlertContentNode {
let textFrame = CGRect(origin: CGPoint(x: insets.left + floor((contentWidth - textSize.width) / 2.0), y: titleFrame.maxY + spacing), size: textSize)
transition.updateFrame(node: self.textNode, frame: textFrame)
let inputHeight: CGFloat = 38.0
let resultSize = CGSize(width: contentWidth + insets.left + insets.right, height: titleSize.height + spacing + textSize.height + actionsHeight + insets.top + insets.bottom + 46.0)
let resultSize = CGSize(width: contentWidth + insets.left + insets.right, height: titleSize.height + spacing + textSize.height + actionsHeight + insets.top + insets.bottom + inputHeight)
let textFieldBackgroundFrame = CGRect(origin: CGPoint(x: insets.left, y: resultSize.height - inputHeight + 12.0 - actionsHeight - insets.bottom), size: CGSize(width: resultSize.width - insets.left - insets.right, height: 25.0))
self.textFieldNodeBackground.frame = textFieldBackgroundFrame
self.textFieldNode.frame = textFieldBackgroundFrame.offsetBy(dx: 0.0, dy: 0.0).insetBy(dx: 4.0, dy: 0.0)
let inputFieldWidth = resultSize.width
let inputFieldHeight = self.inputFieldNode.updateLayout(width: inputFieldWidth, transition: transition)
transition.updateFrame(node: self.inputFieldNode, frame: CGRect(x: 0.0, y: resultSize.height - 36.0 - actionsHeight - insets.bottom, width: resultSize.width, height: inputFieldHeight))
self.actionNodesSeparator.frame = CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel))
@ -250,7 +315,7 @@ private final class BotCheckoutPasswordAlertContentNode: AlertContentNode {
}
if previousLayout == nil {
self.textFieldNode.textField.becomeFirstResponder()
self.inputFieldNode.activateInput()
}
return resultSize
@ -262,24 +327,15 @@ private final class BotCheckoutPasswordAlertContentNode: AlertContentNode {
private func updateState() {
var enabled = true
if self.isVerifying {
if self.isVerifying || self.inputFieldNode.text.isEmpty {
enabled = false
}
if let text = self.textFieldNode.textField.text {
if text.isEmpty {
enabled = false
}
} else {
enabled = false
}
self.doneActionNode.isEnabled = enabled
self.doneActionNode.actionEnabled = enabled
}
private func verify() {
guard let text = self.textFieldNode.textField.text, !text.isEmpty else {
let text = self.inputFieldNode.text
guard !text.isEmpty else {
return
}
@ -290,8 +346,7 @@ private final class BotCheckoutPasswordAlertContentNode: AlertContentNode {
}
}, error: { [weak self] _ in
if let strongSelf = self {
strongSelf.textFieldNodeBackground.layer.addShakeAnimation()
strongSelf.textFieldNode.layer.addShakeAnimation()
strongSelf.inputFieldNode.shake()
strongSelf.hapticFeedback.error()
strongSelf.isVerifying = false
strongSelf.updateState()

View File

@ -45,6 +45,7 @@
_explicit = explicit;
_sliderView = [[TGPhotoEditorSliderView alloc] initWithFrame:CGRectZero];
_sliderView.enablePanHandling = true;
if (editorItem.segmented)
_sliderView.positionsCount = (NSInteger)editorItem.maximumValue + 1;
_sliderView.minimumValue = editorItem.minimumValue;

View File

@ -68,6 +68,7 @@ const CGFloat TGPhotoEditorSliderViewInternalMargin = 7.0f;
_panGestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handlePan:)];
_panGestureRecognizer.enabled = false;
_panGestureRecognizer.delegate = self;
[self addGestureRecognizer:_panGestureRecognizer];
_tapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTap:)];
@ -531,6 +532,19 @@ const CGFloat TGPhotoEditorSliderViewInternalMargin = 7.0f;
}
}
- (BOOL)gestureRecognizerShouldBegin:(UIPanGestureRecognizer *)gestureRecognizer {
if (gestureRecognizer == _panGestureRecognizer) {
CGPoint velocity = [gestureRecognizer velocityInView:gestureRecognizer.view];
if (ABS(velocity.x) > ABS(velocity.y)) {
return true;
} else {
return false;
}
}
return true;
}
- (BOOL)beginTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)__unused event
{
if (!_enablePanHandling) {

View File

@ -266,6 +266,7 @@ const CGFloat TGPhotoEditorToolsLandscapePanelSize = TGPhotoEditorToolsPanelSize
_portraitCollectionView.toolsDataSource = self;
_portraitCollectionView.interactionBegan = _interactionBegan;
_portraitCollectionView.interactionEnded = _interactionEnded;
_portraitCollectionView.canCancelContentTouches = true;
[_portraitToolsWrapperView addSubview:_portraitCollectionView];
if (!TGIsPad())
@ -278,6 +279,7 @@ const CGFloat TGPhotoEditorToolsLandscapePanelSize = TGPhotoEditorToolsPanelSize
_landscapeCollectionView.toolsDataSource = self;
_landscapeCollectionView.interactionBegan = _interactionBegan;
_landscapeCollectionView.interactionEnded = _interactionEnded;
_landscapeCollectionView.canCancelContentTouches = true;
[_landscapeToolsWrapperView addSubview:_landscapeCollectionView];
}

View File

@ -93,6 +93,17 @@ private final class TabBarItemNode: ASDisplayNode {
var contentWidth: CGFloat?
var isSelected: Bool = false
let ringImageNode: ASImageNode
var ringColor: UIColor? {
didSet {
if let ringColor = self.ringColor {
self.ringImageNode.image = generateCircleImage(diameter: 29.0, lineWidth: 1.0, color: ringColor, backgroundColor: nil)
} else {
self.ringImageNode.image = nil
}
}
}
var swiped: ((TabBarItemSwipeDirection) -> Void)?
var pointerInteraction: PointerInteraction?
@ -101,6 +112,11 @@ private final class TabBarItemNode: ASDisplayNode {
self.extractedContainerNode = ContextExtractedContentContainingNode()
self.containerNode = ContextControllerSourceNode()
self.ringImageNode = ASImageNode()
self.ringImageNode.isUserInteractionEnabled = false
self.ringImageNode.displayWithoutProcessing = true
self.ringImageNode.displaysAsynchronously = false
self.imageNode = ASImageNode()
self.imageNode.isUserInteractionEnabled = false
self.imageNode.displayWithoutProcessing = true
@ -136,6 +152,7 @@ private final class TabBarItemNode: ASDisplayNode {
self.isAccessibilityElement = true
self.extractedContainerNode.contentNode.addSubnode(self.ringImageNode)
self.extractedContainerNode.contentNode.addSubnode(self.textImageNode)
self.extractedContainerNode.contentNode.addSubnode(self.imageNode)
self.extractedContainerNode.contentNode.addSubnode(self.animationContainerNode)
@ -150,6 +167,7 @@ private final class TabBarItemNode: ASDisplayNode {
guard let strongSelf = self else {
return
}
transition.updateAlpha(node: strongSelf.ringImageNode, alpha: isExtracted ? 0.0 : 1.0)
transition.updateAlpha(node: strongSelf.imageNode, alpha: isExtracted ? 0.0 : 1.0)
transition.updateAlpha(node: strongSelf.animationNode, alpha: isExtracted ? 0.0 : 1.0)
transition.updateAlpha(node: strongSelf.textImageNode, alpha: isExtracted ? 0.0 : 1.0)
@ -441,6 +459,12 @@ class TabBarNode: ASDisplayNode {
}, swipeAction: { [weak self] direction in
self?.swipeAction(i, direction)
})
if item.item.ringSelection {
node.ringColor = self.theme.tabBarSelectedIconColor
} else {
node.ringColor = nil
}
if let selectedIndex = self.selectedIndex, selectedIndex == i {
let (textImage, contentWidth) = tabBarItemImage(item.item.selectedImage, title: item.item.title ?? "", backgroundColor: .clear, tintColor: self.theme.tabBarSelectedTextColor, horizontal: self.horizontal, imageMode: false, centered: self.centered)
let (image, imageContentWidth): (UIImage, CGFloat)
@ -507,6 +531,12 @@ class TabBarNode: ASDisplayNode {
self.centered = self.theme.tabBarTextColor == .clear
if item.item.ringSelection {
node.ringColor = self.theme.tabBarSelectedIconColor
} else {
node.ringColor = nil
}
let previousImageSize = node.imageNode.image?.size ?? CGSize()
let previousTextImageSize = node.textImageNode.image?.size ?? CGSize()
if let selectedIndex = self.selectedIndex, selectedIndex == index {
@ -524,7 +554,11 @@ class TabBarNode: ASDisplayNode {
node.animationNode.setOverlayColor(self.theme.tabBarSelectedIconColor, replace: true, animated: false)
node.animationNode.updateLayout(size: CGSize(width: 51.0, height: 51.0))
} else {
(image, imageContentWidth) = tabBarItemImage(item.item.selectedImage, title: item.item.title ?? "", backgroundColor: .clear, tintColor: self.theme.tabBarSelectedIconColor, horizontal: self.horizontal, imageMode: true, centered: self.centered)
if item.item.ringSelection {
(image, imageContentWidth) = (item.item.selectedImage ?? UIImage(), item.item.selectedImage?.size.width ?? 0.0)
} else {
(image, imageContentWidth) = tabBarItemImage(item.item.selectedImage, title: item.item.title ?? "", backgroundColor: .clear, tintColor: self.theme.tabBarSelectedIconColor, horizontal: self.horizontal, imageMode: true, centered: self.centered)
}
node.animationNode.isHidden = true
node.animationNode.visibility = false
@ -539,9 +573,22 @@ class TabBarNode: ASDisplayNode {
node.contextImageNode.image = contextImage
node.contentWidth = max(contentWidth, imageContentWidth)
node.isSelected = true
ContainedViewLayoutTransition.animated(duration: 0.35, curve: .easeInOut).updateTransformScale(node: node.ringImageNode, scale: 1.0, delay: 0.1)
node.imageNode.layer.animateScale(from: 1.0, to: 0.87, duration: 0.1, removeOnCompletion: false, completion: { [weak node] _ in
node?.imageNode.layer.animateScale(from: 0.87, to: 1.0, duration: 0.35, removeOnCompletion: false, completion: { [weak node] _ in
node?.imageNode.layer.removeAllAnimations()
})
})
} else {
let (textImage, contentWidth) = tabBarItemImage(item.item.image, title: item.item.title ?? "", backgroundColor: .clear, tintColor: self.theme.tabBarTextColor, horizontal: self.horizontal, imageMode: false, centered: self.centered)
let (image, imageContentWidth) = tabBarItemImage(item.item.image, title: item.item.title ?? "", backgroundColor: .clear, tintColor: self.theme.tabBarIconColor, horizontal: self.horizontal, imageMode: true, centered: self.centered)
let (image, imageContentWidth): (UIImage, CGFloat)
if item.item.ringSelection {
(image, imageContentWidth) = (item.item.image ?? UIImage(), item.item.image?.size.width ?? 0.0)
} else {
(image, imageContentWidth) = tabBarItemImage(item.item.image, title: item.item.title ?? "", backgroundColor: .clear, tintColor: self.theme.tabBarIconColor, horizontal: self.horizontal, imageMode: true, centered: self.centered)
}
let (contextTextImage, _) = tabBarItemImage(item.item.image, title: item.item.title ?? "", backgroundColor: .clear, tintColor: self.theme.tabBarExtractedTextColor, horizontal: self.horizontal, imageMode: false, centered: self.centered)
let (contextImage, _) = tabBarItemImage(item.item.image, title: item.item.title ?? "", backgroundColor: .clear, tintColor: self.theme.tabBarExtractedIconColor, horizontal: self.horizontal, imageMode: true, centered: self.centered)
@ -556,6 +603,8 @@ class TabBarNode: ASDisplayNode {
node.contextImageNode.image = contextImage
node.contentWidth = max(contentWidth, imageContentWidth)
node.isSelected = false
ContainedViewLayoutTransition.animated(duration: 0.2, curve: .easeInOut).updateTransformScale(node: node.ringImageNode, scale: 0.5)
}
let updatedImageSize = node.imageNode.image?.size ?? CGSize()
@ -647,18 +696,33 @@ class TabBarNode: ASDisplayNode {
node.containerNode.frame = CGRect(origin: CGPoint(), size: nodeFrame.size)
node.hitTestSlop = UIEdgeInsets(top: -3.0, left: -horizontalHitTestInset, bottom: -3.0, right: -horizontalHitTestInset)
node.containerNode.hitTestSlop = UIEdgeInsets(top: -3.0, left: -horizontalHitTestInset, bottom: -3.0, right: -horizontalHitTestInset)
node.imageNode.frame = CGRect(origin: CGPoint(), size: nodeFrame.size)
if node.ringColor == nil {
node.imageNode.frame = CGRect(origin: CGPoint(), size: nodeFrame.size)
}
node.textImageNode.frame = CGRect(origin: CGPoint(), size: nodeFrame.size)
node.contextImageNode.frame = CGRect(origin: CGPoint(), size: nodeFrame.size)
node.contextTextImageNode.frame = CGRect(origin: CGPoint(), size: nodeFrame.size)
let scaleFactor: CGFloat = horizontal ? 0.8 : 1.0
node.animationContainerNode.subnodeTransform = CATransform3DMakeScale(scaleFactor, scaleFactor, 1.0)
let animationOffset: CGPoint = self.tabBarItems[i].item.animationOffset
let ringImageFrame: CGRect
let imageFrame: CGRect
if horizontal {
node.animationNode.frame = CGRect(origin: CGPoint(x: -10.0 - UIScreenPixel, y: -4.0 - UIScreenPixel), size: CGSize(width: 51.0, height: 51.0))
ringImageFrame = CGRect(origin: CGPoint(x: UIScreenPixel, y: 5.0 + UIScreenPixel), size: CGSize(width: 23.0, height: 23.0))
imageFrame = ringImageFrame.insetBy(dx: -1.0 + UIScreenPixel, dy: -1.0 + UIScreenPixel)
} else {
node.animationNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((nodeSize.width - 51.0) / 2.0), y: -10.0 - UIScreenPixel).offsetBy(dx: animationOffset.x, dy: animationOffset.y), size: CGSize(width: 51.0, height: 51.0))
ringImageFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((nodeSize.width - 29.0) / 2.0), y: 1.0), size: CGSize(width: 29.0, height: 29.0))
imageFrame = ringImageFrame.insetBy(dx: -1.0, dy: -1.0)
}
node.ringImageNode.bounds = CGRect(origin: CGPoint(), size: ringImageFrame.size)
node.ringImageNode.position = ringImageFrame.center
if node.ringColor != nil {
node.imageNode.bounds = CGRect(origin: CGPoint(), size: imageFrame.size)
node.imageNode.position = imageFrame.center
}
if container.badgeValue != container.appliedBadgeValue {

View File

@ -367,7 +367,12 @@ private final class PeerInfoScreenLabeledValueItemNode: PeerInfoScreenItemNode {
self.expandBackgroundNode.image = generateExpandBackground(size: expandBackgroundFrame.size, color: presentationData.theme.list.itemBlocksBackgroundColor)
transition.updateFrame(node: self.labelNode, frame: labelFrame)
transition.updateFrame(node: self.textNode, frame: textFrame)
var textTransition = transition
if self.textNode.frame.size != textFrame.size {
textTransition = .immediate
}
textTransition.updateFrame(node: self.textNode, frame: textFrame)
let height = labelSize.height + 3.0 + textSize.height + 22.0

View File

@ -2010,7 +2010,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
var requestAvatarExpansion: ((Bool, [AvatarGalleryEntry], AvatarGalleryEntry?, (ASDisplayNode, CGRect, () -> (UIView?, UIView?))?) -> Void)?
var requestOpenAvatarForEditing: ((Bool) -> Void)?
var cancelUpload: (() -> Void)?
var requestUpdateLayout: (() -> Void)?
var requestUpdateLayout: ((Bool) -> Void)?
var animateOverlaysFadeIn: (() -> Void)?
var displayAvatarContextMenu: ((ASDisplayNode, ContextGesture?) -> Void)?
@ -2100,7 +2100,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
super.init()
requestUpdateLayoutImpl = { [weak self] in
self?.requestUpdateLayout?()
self?.requestUpdateLayout?(false)
}

View File

@ -460,7 +460,7 @@ private final class PeerInfoInteraction {
let performMemberAction: (PeerInfoMember, PeerInfoMemberAction) -> Void
let openPeerInfoContextMenu: (PeerInfoContextSubject, ASDisplayNode) -> Void
let performBioLinkAction: (TextLinkItemActionType, TextLinkItem) -> Void
let requestLayout: () -> Void
let requestLayout: (Bool) -> Void
let openEncryptionKey: () -> Void
let openSettings: (PeerInfoSettingsSection) -> Void
let switchToAccount: (AccountRecordId) -> Void
@ -502,7 +502,7 @@ private final class PeerInfoInteraction {
performMemberAction: @escaping (PeerInfoMember, PeerInfoMemberAction) -> Void,
openPeerInfoContextMenu: @escaping (PeerInfoContextSubject, ASDisplayNode) -> Void,
performBioLinkAction: @escaping (TextLinkItemActionType, TextLinkItem) -> Void,
requestLayout: @escaping () -> Void,
requestLayout: @escaping (Bool) -> Void,
openEncryptionKey: @escaping () -> Void,
openSettings: @escaping (PeerInfoSettingsSection) -> Void,
switchToAccount: @escaping (AccountRecordId) -> Void,
@ -864,7 +864,7 @@ private func infoItems(data: PeerInfoScreenData?, context: AccountContext, prese
}, longTapAction: { sourceNode in
interaction.openPeerInfoContextMenu(.phone(formattedPhone), sourceNode)
}, requestLayout: {
interaction.requestLayout()
interaction.requestLayout(false)
}))
}
if let username = user.username {
@ -875,21 +875,21 @@ private func infoItems(data: PeerInfoScreenData?, context: AccountContext, prese
}, iconAction: {
interaction.openQrCode()
}, requestLayout: {
interaction.requestLayout()
interaction.requestLayout(false)
}))
}
if let cachedData = data.cachedData as? CachedUserData {
if user.isFake {
items[.peerInfo]!.append(PeerInfoScreenLabeledValueItem(id: 0, label: user.botInfo == nil ? presentationData.strings.Profile_About : presentationData.strings.Profile_BotInfo, text: user.botInfo != nil ? presentationData.strings.UserInfo_FakeBotWarning : presentationData.strings.UserInfo_FakeUserWarning, textColor: .primary, textBehavior: .multiLine(maxLines: 100, enabledEntities: user.botInfo != nil ? enabledPrivateBioEntities : []), action: nil, requestLayout: {
interaction.requestLayout()
interaction.requestLayout(false)
}))
} else if user.isScam {
items[.peerInfo]!.append(PeerInfoScreenLabeledValueItem(id: 0, label: user.botInfo == nil ? presentationData.strings.Profile_About : presentationData.strings.Profile_BotInfo, text: user.botInfo != nil ? presentationData.strings.UserInfo_ScamBotWarning : presentationData.strings.UserInfo_ScamUserWarning, textColor: .primary, textBehavior: .multiLine(maxLines: 100, enabledEntities: user.botInfo != nil ? enabledPrivateBioEntities : []), action: nil, requestLayout: {
interaction.requestLayout()
interaction.requestLayout(false)
}))
} else if let about = cachedData.about, !about.isEmpty {
items[.peerInfo]!.append(PeerInfoScreenLabeledValueItem(id: 0, label: user.botInfo == nil ? presentationData.strings.Profile_About : presentationData.strings.Profile_BotInfo, text: about, textColor: .primary, textBehavior: .multiLine(maxLines: 100, enabledEntities: enabledPrivateBioEntities), action: nil, longTapAction: bioContextAction, linkItemAction: bioLinkAction, requestLayout: {
interaction.requestLayout()
interaction.requestLayout(false)
}))
}
}
@ -982,7 +982,7 @@ private func infoItems(data: PeerInfoScreenData?, context: AccountContext, prese
}, iconAction: {
interaction.openQrCode()
}, requestLayout: {
interaction.requestLayout()
interaction.requestLayout(false)
}))
}
if let cachedData = data.cachedData as? CachedChannelData {
@ -1011,7 +1011,7 @@ private func infoItems(data: PeerInfoScreenData?, context: AccountContext, prese
enabledEntities = enabledPrivateBioEntities
}
items[.peerInfo]!.append(PeerInfoScreenLabeledValueItem(id: ItemAbout, label: presentationData.strings.Channel_Info_Description, text: aboutText, textColor: .primary, textBehavior: .multiLine(maxLines: 100, enabledEntities: enabledEntities), action: nil, longTapAction: bioContextAction, linkItemAction: bioLinkAction, requestLayout: {
interaction.requestLayout()
interaction.requestLayout(true)
}))
}
@ -1056,7 +1056,7 @@ private func infoItems(data: PeerInfoScreenData?, context: AccountContext, prese
if let aboutText = aboutText {
items[.peerInfo]!.append(PeerInfoScreenLabeledValueItem(id: 0, label: presentationData.strings.Channel_Info_Description, text: aboutText, textColor: .primary, textBehavior: .multiLine(maxLines: 100, enabledEntities: enabledPrivateBioEntities), action: nil, longTapAction: bioContextAction, linkItemAction: bioLinkAction, requestLayout: {
interaction.requestLayout()
interaction.requestLayout(true)
}))
}
}
@ -1776,8 +1776,8 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
performBioLinkAction: { [weak self] action, item in
self?.performBioLinkAction(action: action, item: item)
},
requestLayout: { [weak self] in
self?.requestLayout()
requestLayout: { [weak self] animated in
self?.requestLayout(animated: animated)
},
openEncryptionKey: { [weak self] in
self?.openEncryptionKey()
@ -2571,12 +2571,12 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
navigationBar.layer.animateAlpha(from: 0.0, to: navigationBar.alpha, duration: 0.25)
}
self.headerNode.requestUpdateLayout = { [weak self] in
self.headerNode.requestUpdateLayout = { [weak self] animated in
guard let strongSelf = self else {
return
}
if let (layout, navigationHeight) = strongSelf.validLayout {
strongSelf.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: .immediate, additive: false)
strongSelf.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: animated ? .animated(duration: 0.35, curve: .slide) : .immediate, additive: false)
}
}
@ -5630,8 +5630,8 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
self.context.sharedContext.handleTextLinkAction(context: self.context, peerId: peer.id, navigateDisposable: self.resolveUrlDisposable, controller: controller, action: action, itemLink: item)
}
private func requestLayout() {
self.headerNode.requestUpdateLayout?()
private func requestLayout(animated: Bool = false) {
self.headerNode.requestUpdateLayout?(animated)
}
private func openDeletePeer() {
@ -7763,18 +7763,8 @@ public final class PeerInfoScreenImpl: ViewController, PeerInfoScreen {
if let signal = peerAvatarImage(account: primary.0, peerReference: PeerReference(primary.1._asPeer()), authorOfMessage: nil, representation: primary.1.profileImageRepresentations.first, displayDimensions: size, inset: 3.0, emptyColor: nil, synchronousLoad: false) {
return signal
|> map { imageVersions -> (UIImage, UIImage)? in
let image = imageVersions?.0
if let image = image, let selectedImage = generateImage(size, rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
context.translateBy(x: size.width / 2.0, y: size.height / 2.0)
context.scaleBy(x: 1.0, y: -1.0)
context.translateBy(x: -size.width / 2.0, y: -size.height / 2.0)
context.draw(image.cgImage!, in: CGRect(x: 0.0, y: 0.0, width: size.width, height: size.height))
context.setLineWidth(1.0)
context.setStrokeColor(primary.2.rootController.tabBar.selectedIconColor.cgColor)
context.strokeEllipse(in: CGRect(x: 1.5, y: 1.5, width: 28.0, height: 28.0))
}) {
return (image.withRenderingMode(.alwaysOriginal), selectedImage.withRenderingMode(.alwaysOriginal))
if let image = imageVersions?.0 {
return (image.withRenderingMode(.alwaysOriginal), image.withRenderingMode(.alwaysOriginal))
} else {
return nil
}
@ -7792,22 +7782,8 @@ public final class PeerInfoScreenImpl: ViewController, PeerInfoScreen {
drawPeerAvatarLetters(context: context, size: CGSize(width: size.width - inset * 2.0, height: size.height - inset * 2.0), font: avatarFont, letters: displayLetters, peerId: primary.1.id)
})?.withRenderingMode(.alwaysOriginal)
let selectedImage = generateImage(size, rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
context.translateBy(x: size.width / 2.0, y: size.height / 2.0)
context.scaleBy(x: 1.0, y: -1.0)
context.translateBy(x: -size.width / 2.0, y: -size.height / 2.0)
if let cgImage = image?.cgImage {
context.draw(cgImage, in: CGRect(x: 0.0, y: 0.0, width: size.width, height: size.height))
}
context.setLineWidth(1.0)
context.setStrokeColor(primary.2.rootController.tabBar.selectedIconColor.cgColor)
context.strokeEllipse(in: CGRect(x: 1.5, y: 1.5, width: 28.0, height: 28.0))
})?.withRenderingMode(.alwaysOriginal)
if let image = image, let selectedImage = selectedImage {
subscriber.putNext((image, selectedImage))
if let image = image {
subscriber.putNext((image, image))
} else {
subscriber.putNext(nil)
}
@ -7884,6 +7860,7 @@ public final class PeerInfoScreenImpl: ViewController, PeerInfoScreen {
strongSelf.tabBarItem.image = image
strongSelf.tabBarItem.selectedImage = selectedImage
strongSelf.tabBarItem.animationName = isAvatar ? nil : "TabSettings"
strongSelf.tabBarItem.ringSelection = isAvatar
strongSelf.tabBarItem.badgeValue = badgeValue
}
})

View File

@ -50,5 +50,6 @@ NSInteger UITabBarItem_addSetBadgeListener(UITabBarItem * _Nonnull item, UITabBa
@property (nonatomic, strong) NSString * _Nullable animationName;
@property (nonatomic, assign) CGPoint animationOffset;
@property (nonatomic, assign) bool ringSelection;
@end

View File

@ -18,6 +18,7 @@ static const void *setBadgeListenerBagKey = &setBadgeListenerBagKey;
static const void *badgeKey = &badgeKey;
static const void *animationNameKey = &animationNameKey;
static const void *animationOffsetKey = &animationOffsetKey;
static const void *ringSelectionKey = &ringSelectionKey;
@implementation UINavigationItem (Proxy)
@ -419,4 +420,12 @@ NSInteger UITabBarItem_addSetBadgeListener(UITabBarItem *item, UITabBarItemSetBa
return ((NSValue *)[self associatedObjectForKey:animationOffsetKey]).CGPointValue;
}
- (void)setRingSelection:(bool)ringSelection {
[self setAssociatedObject:@(ringSelection) forKey:ringSelectionKey];
}
- (bool)ringSelection {
return ((NSNumber *)[self associatedObjectForKey:ringSelectionKey]).boolValue;
}
@end