Merge commit '9d5604d81d7c7cd25b07b8f82f50f353609b8e54'

This commit is contained in:
Ali 2020-10-14 18:07:07 +04:00
commit f5a552ce96
45 changed files with 5101 additions and 4328 deletions

View File

@ -5827,3 +5827,13 @@ Any member of this group will be able to see messages in the channel.";
"Conversation.PinOlderMessageAlertText" = "Do you want to pin an older message while leaving a more recent one pinned?";
"Conversation.PinMessageAlertPin" = "Pin";
"Conversation.ContextMenuSelect" = "Select";
"Conversation.ContextMenuSelectAll_0" = "Select All %@ Items";
"Conversation.ContextMenuSelectAll_1" = "Select All %@ Items";
"Conversation.ContextMenuSelectAll_2" = "Select All %@ Items";
"Conversation.ContextMenuSelectAll_3_10" = "Select All %@ Items";
"Conversation.ContextMenuSelectAll_many" = "Select All %@ Items";
"Conversation.ContextMenuSelectAll_any" = "Select All %@ Items";
"Conversation.Dice.u1F3B0" = "Send a slot machine emoji to try your luck.";

View File

@ -465,7 +465,7 @@ public protocol ChatController: ViewController {
func displayPromoAnnouncement(text: String)
}
public protocol ChatMessagePrevewItemNode: class {
public protocol ChatMessagePreviewItemNode: class {
var forwardInfoReferenceNode: ASDisplayNode? { get }
}

View File

@ -666,7 +666,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
})))
items.append(.separator)
items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.Conversation_ContextMenuMore, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/More"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in
items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.Conversation_ContextMenuSelect, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Select"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in
c.dismiss(completion: {
if let strongSelf = self {
strongSelf.dismissInput()
@ -720,8 +720,8 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
})))
items.append(.separator)
items.append(.action(ContextMenuActionItem(text: strings.Conversation_ContextMenuMore, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/More"), color: theme.actionSheet.primaryTextColor)
items.append(.action(ContextMenuActionItem(text: strings.Conversation_ContextMenuSelect, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Select"), color: theme.actionSheet.primaryTextColor)
}, action: { _, f in
if let strongSelf = self {
strongSelf.dismissInput()

View File

@ -61,13 +61,13 @@ public final class ContextControllerSourceNode: ASDisplayNode {
let currentScale = 1.0 * (1.0 - progress) + minScale * progress
let originalCenterOffsetX: CGFloat = targetNode.bounds.width / 2.0 - targetContentRect.midX
let scaledCaneterOffsetX: CGFloat = originalCenterOffsetX * currentScale
let scaledCenterOffsetX: CGFloat = originalCenterOffsetX * currentScale
let originalCenterOffsetY: CGFloat = targetNode.bounds.height / 2.0 - targetContentRect.midY
let scaledCaneterOffsetY: CGFloat = originalCenterOffsetY * currentScale
let scaledCenterOffsetY: CGFloat = originalCenterOffsetY * currentScale
let scaleMidX: CGFloat = scaledCaneterOffsetX - originalCenterOffsetX
let scaleMidY: CGFloat = scaledCaneterOffsetY - originalCenterOffsetY
let scaleMidX: CGFloat = scaledCenterOffsetX - originalCenterOffsetX
let scaleMidY: CGFloat = scaledCenterOffsetY - originalCenterOffsetY
switch update {
case .update:

View File

@ -425,7 +425,7 @@ typedef enum
if (gestureRecognizer == _panGestureRecognizer)
{
NSString *viewClassName = NSStringFromClass(otherGestureRecognizer.view.class);
if ([viewClassName rangeOfString:@"WKScroll"].location != NSNotFound || [viewClassName rangeOfString:@"UIWebViewScroll"].location != NSNotFound)
if ([viewClassName rangeOfString:@"WKScroll"].location != NSNotFound)
{
return true;
}

View File

@ -25,6 +25,7 @@
@property (nonatomic, assign) bool isOwn;
@property (nonatomic, assign) bool hasSession;
@property (nonatomic, assign) bool isExpired;
@property (nonatomic, strong) NSNumber *heading;
- (instancetype)initWithLocation:(TGLocationMediaAttachment *)location;
- (instancetype)initWithLocation:(TGLocationMediaAttachment *)location color:(UIColor *)color;

View File

@ -82,6 +82,13 @@
}
}
- (void)setHeading:(NSNumber *)heading
{
[self willChangeValueForKey:@"heading"];
_heading = heading;
[self didChangeValueForKey:@"heading"];
}
- (void)setHasSession:(bool)hasSession
{
if (hasSession != _hasSession)

View File

@ -25,6 +25,8 @@ NSString *const TGLocationPinAnnotationKind = @"TGLocationPinAnnotation";
TGImageView *_iconView;
UIImageView *_dotView;
TGLetteredAvatarView *_avatarView;
UIImageView *_smallArrowView;
UIImageView *_arrowView;
bool _liveLocation;
SMetaDisposable *_userDisposable;
@ -56,7 +58,10 @@ NSString *const TGLocationPinAnnotationKind = @"TGLocationPinAnnotation";
[_backgroundView addSubview:_iconView];
static dispatch_once_t onceToken;
static UIImage *dotImage;
static UIImage *smallDotImage;
static UIImage *largeDotImage;
static UIImage *smallHeadingArrowImage;
static UIImage *largeHeadingArrowImage;
dispatch_once(&onceToken, ^
{
UIGraphicsBeginImageContextWithOptions(CGSizeMake(6.0f, 6.0f), false, 0.0f);
@ -64,17 +69,91 @@ NSString *const TGLocationPinAnnotationKind = @"TGLocationPinAnnotation";
CGContextSetFillColorWithColor(context, UIColorRGB(0x008df2).CGColor);
CGContextFillEllipseInRect(context, CGRectMake(0.0f, 0.0f, 6.0f, 6.0f));
dotImage = UIGraphicsGetImageFromCurrentImageContext();
smallDotImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
UIGraphicsBeginImageContextWithOptions(CGSizeMake(16.0f, 16.0f), false, 0.0f);
context = UIGraphicsGetCurrentContext();
CGContextSaveGState(context);
CGContextSetShadowWithColor(context, CGSizeMake(0, 1), 1.0f, [UIColor colorWithWhite:0.0f alpha:0.22f].CGColor);
CGContextSetFillColorWithColor(context, [UIColor whiteColor].CGColor);
CGContextFillEllipseInRect(context, CGRectMake(2, 2, 12.0, 12.0));
CGContextRestoreGState(context);
CGContextSetFillColorWithColor(context, UIColorRGB(0x008df2).CGColor);
CGContextFillEllipseInRect(context, CGRectMake(4.0f, 4.0f, 8.0f, 8.0f));
largeDotImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
UIGraphicsBeginImageContextWithOptions(CGSizeMake(16.0f, 16.0f), false, 0.0f);
context = UIGraphicsGetCurrentContext();
CGContextClearRect(context, CGRectMake(0, 0, 18.0f, 18.0f));
CGContextSetFillColorWithColor(context, UIColorRGB(0x3393fe).CGColor);
CGContextMoveToPoint(context, 9, 0);
CGContextAddLineToPoint(context, 13, 7);
CGContextAddLineToPoint(context, 5, 7);
CGContextClosePath(context);
CGContextFillPath(context);
CGContextSetBlendMode(context, kCGBlendModeClear);
CGContextFillEllipseInRect(context, CGRectMake(3.0, 3.0, 12.0, 12.0));
smallHeadingArrowImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
UIGraphicsBeginImageContextWithOptions(CGSizeMake(33.0f, 33.0f), false, 0.0f);
context = UIGraphicsGetCurrentContext();
CGContextClearRect(context, CGRectMake(0, 0, 16.0f, 16.0f));
CGContextSetFillColorWithColor(context, UIColorRGB(0x3393fe).CGColor);
CGContextMoveToPoint(context, 16.5, 0);
CGContextAddLineToPoint(context, 21.5, 6);
CGContextAddLineToPoint(context, 11.5, 6);
CGContextClosePath(context);
CGContextFillPath(context);
CGContextSetBlendMode(context, kCGBlendModeClear);
CGContextFillEllipseInRect(context, CGRectMake(3.0, 3.0, 27.0, 27.0));
largeHeadingArrowImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
});
_smallView = [[UIImageView alloc] initWithImage:TGComponentsImageNamed(@"LocationSmallCircle")];
_smallView.hidden = true;
[self addSubview:_smallView];
UIImage *dotImage = smallDotImage;
if ([annotation isKindOfClass:[TGLocationAnnotation class]])
{
TGLocationAnnotation *locationAnnotation = (TGLocationAnnotation *)annotation;
if (locationAnnotation.location.period > 0) {
dotImage = largeDotImage;
}
}
_dotView = [[UIImageView alloc] initWithImage:dotImage];
[self addSubview:_dotView];
_smallArrowView = [[UIImageView alloc] initWithImage:smallHeadingArrowImage];
_smallArrowView.hidden = true;
[self addSubview:_smallArrowView];
_arrowView = [[UIImageView alloc] initWithImage:largeHeadingArrowImage];
_arrowView.hidden = true;
[self addSubview:_arrowView];
[self setAnnotation:annotation];
}
return self;
@ -99,6 +178,7 @@ NSString *const TGLocationPinAnnotationKind = @"TGLocationPinAnnotation";
return;
_observingExpiration = true;
[self addObserver:self forKeyPath:@"annotation.isExpired" options:NSKeyValueObservingOptionNew context:NULL];
[self addObserver:self forKeyPath:@"annotation.heading" options:NSKeyValueObservingOptionNew context:NULL];
}
- (void)unsubscribeFromExpiration
@ -107,6 +187,7 @@ NSString *const TGLocationPinAnnotationKind = @"TGLocationPinAnnotation";
return;
_observingExpiration = false;
[self removeObserver:self forKeyPath:@"annotation.isExpired"];
[self removeObserver:self forKeyPath:@"annotation.heading"];
}
- (void)setSelected:(BOOL)selected animated:(BOOL)animated
@ -214,6 +295,8 @@ NSString *const TGLocationPinAnnotationKind = @"TGLocationPinAnnotation";
_smallView.alpha = 1.0f;
[self layoutSubviews];
}
[self updateHeading];
}
- (void)setPallete:(TGLocationPallete *)pallete
@ -223,13 +306,38 @@ NSString *const TGLocationPinAnnotationKind = @"TGLocationPinAnnotation";
_pallete = pallete;
UIGraphicsBeginImageContextWithOptions(CGSizeMake(6.0f, 6.0f), false, 0.0f);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(context, pallete.locationColor.CGColor);
CGContextFillEllipseInRect(context, CGRectMake(0.0f, 0.0f, 6.0f, 6.0f));
UIImage *dotImage;
if ([self.annotation isKindOfClass:[TGLocationAnnotation class]])
{
TGLocationAnnotation *locationAnnotation = (TGLocationAnnotation *)self.annotation;
if (locationAnnotation.location.period > 0) {
UIGraphicsBeginImageContextWithOptions(CGSizeMake(16.0f, 16.0f), false, 0.0f);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSaveGState(context);
CGContextSetShadowWithColor(context, CGSizeMake(0, 1), 1.0f, [UIColor colorWithWhite:0.0f alpha:0.22f].CGColor);
CGContextSetFillColorWithColor(context, [UIColor whiteColor].CGColor);
CGContextFillEllipseInRect(context, CGRectMake(2, 2, 12.0, 12.0));
CGContextRestoreGState(context);
CGContextSetFillColorWithColor(context, pallete.locationColor.CGColor);
CGContextFillEllipseInRect(context, CGRectMake(4.0f, 4.0f, 8.0f, 8.0f));
dotImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
}
}
UIImage *dotImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
if (dotImage == nil) {
UIGraphicsBeginImageContextWithOptions(CGSizeMake(6.0f, 6.0f), false, 0.0f);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(context, pallete.locationColor.CGColor);
CGContextFillEllipseInRect(context, CGRectMake(0.0f, 0.0f, 6.0f, 6.0f));
dotImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
}
_dotView.image = dotImage;
@ -324,6 +432,26 @@ NSString *const TGLocationPinAnnotationKind = @"TGLocationPinAnnotation";
}
}
- (void)updateHeading
{
if ([self.annotation isKindOfClass:[TGLocationAnnotation class]]) {
NSNumber *heading = ((TGLocationAnnotation *)self.annotation).heading;
if (heading != nil) {
_arrowView.hidden = self.isSelected;
_smallArrowView.hidden = !self.isSelected;
_arrowView.transform = CGAffineTransformMakeRotation(heading.floatValue);
_smallArrowView.transform = CGAffineTransformMakeRotation(heading.floatValue);
} else {
_arrowView.hidden = true;
_smallArrowView.hidden = true;
}
} else {
_arrowView.hidden = true;
_smallArrowView.hidden = true;
}
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
{
if ([keyPath isEqualToString:@"annotation.isExpired"])
@ -339,6 +467,10 @@ NSString *const TGLocationPinAnnotationKind = @"TGLocationPinAnnotation";
_avatarView.alpha = 1.0f;
}
}
else if ([keyPath isEqual:@"annotation.heading"])
{
[self updateHeading];
}
else
{
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
@ -410,6 +542,8 @@ NSString *const TGLocationPinAnnotationKind = @"TGLocationPinAnnotation";
_dotView.center = CGPointZero;
_smallView.center = CGPointZero;
_arrowView.center = CGPointZero;
_smallArrowView.center = CGPointZero;
_shadowView.center = CGPointMake(TGScreenPixel, -36.0f);
_backgroundView.center = CGPointMake(_shadowView.frame.size.width / 2.0f, _shadowView.frame.size.height / 2.0f);
_iconView.center = CGPointMake(_shadowView.frame.size.width / 2.0f, _shadowView.frame.size.height / 2.0f - 5.0f);

View File

@ -370,6 +370,26 @@
}]];
}
double DegreesToRadians(double degrees) {return degrees * M_PI / 180.0;};
double RadiansToDegrees(double radians) {return radians * 180.0/M_PI;};
- (double)bearingFromLocation:(CLLocationCoordinate2D)fromLocation toLocation:(CLLocationCoordinate2D)toLocation
{
double lat1 = DegreesToRadians(fromLocation.latitude);
double lon1 = DegreesToRadians(fromLocation.longitude);
double lat2 = DegreesToRadians(toLocation.latitude);
double lon2 = DegreesToRadians(toLocation.longitude);
double dLon = lon2 - lon1;
double y = sin(dLon) * cos(lat2);
double x = cos(lat1) * sin(lat2) - sin(lat1) * cos(lat2) * cos(dLon);
double radiansBearing = atan2(y, x);
return radiansBearing;
}
- (void)updateAnnotations
{
NSMutableDictionary *liveLocations = [[NSMutableDictionary alloc] init];
@ -388,7 +408,20 @@
if (liveLocations[@(annotation.messageId)] != nil)
{
annotation.coordinate = [(TGLiveLocation *)liveLocations[@(annotation.messageId)] location].coordinate;
[UIView animateWithDuration:0.3 animations:^{
CLLocationCoordinate2D previousCoordinate = annotation.coordinate;
annotation.coordinate = [(TGLiveLocation *)liveLocations[@(annotation.messageId)] location].coordinate;
CLLocation *previous = [[CLLocation alloc] initWithLatitude:previousCoordinate.latitude longitude:previousCoordinate.longitude];
CLLocation *new = [[CLLocation alloc] initWithLatitude:annotation.coordinate.latitude longitude:annotation.coordinate.longitude];
// if ([new distanceFromLocation:previous] > 20) {
CGFloat hdg = [self bearingFromLocation:previousCoordinate toLocation:annotation.coordinate];
if (hdg != 0.0) {
annotation.heading = @(hdg);
}
// }
}];
annotation.isExpired = [(TGLiveLocation *)liveLocations[@(annotation.messageId)] isExpired];
[liveLocations removeObjectForKey:@(annotation.messageId)];

View File

@ -6,7 +6,7 @@ import TelegramPresentationData
import AppBundle
private let panelInset: CGFloat = 4.0
private let panelSize = CGSize(width: 46.0, height: 90.0)
private let panelButtonSize = CGSize(width: 46.0, height: 46.0)
private func generateBackgroundImage(theme: PresentationTheme) -> UIImage? {
let cornerRadius: CGFloat = 9.0
@ -38,24 +38,29 @@ final class LocationMapHeaderNode: ASDisplayNode {
private let toggleMapModeSelection: () -> Void
private let goToUserLocation: () -> Void
private let showPlacesInThisArea: () -> Void
private let setupProximityNotification: () -> Void
private var displayingPlacesButton = false
private var proximityNotification: Bool?
let mapNode: LocationMapNode
private let optionsBackgroundNode: ASImageNode
private let optionsSeparatorNode: ASDisplayNode
private let optionsSecondSeparatorNode: ASDisplayNode
private let infoButtonNode: HighlightableButtonNode
private let locationButtonNode: HighlightableButtonNode
private let notificationButtonNode: HighlightableButtonNode
private let placesBackgroundNode: ASImageNode
private let placesButtonNode: HighlightableButtonNode
private let shadowNode: ASImageNode
private var validLayout: (ContainerViewLayout, CGFloat, CGFloat, CGFloat, CGSize)?
init(presentationData: PresentationData, toggleMapModeSelection: @escaping () -> Void, goToUserLocation: @escaping () -> Void, showPlacesInThisArea: @escaping () -> Void = {}) {
init(presentationData: PresentationData, toggleMapModeSelection: @escaping () -> Void, goToUserLocation: @escaping () -> Void, setupProximityNotification: @escaping () -> Void = {}, showPlacesInThisArea: @escaping () -> Void = {}) {
self.presentationData = presentationData
self.toggleMapModeSelection = toggleMapModeSelection
self.goToUserLocation = goToUserLocation
self.setupProximityNotification = setupProximityNotification
self.showPlacesInThisArea = showPlacesInThisArea
self.mapNode = LocationMapNode()
@ -70,6 +75,9 @@ final class LocationMapHeaderNode: ASDisplayNode {
self.optionsSeparatorNode = ASDisplayNode()
self.optionsSeparatorNode.backgroundColor = presentationData.theme.rootController.navigationBar.separatorColor
self.optionsSecondSeparatorNode = ASDisplayNode()
self.optionsSecondSeparatorNode.backgroundColor = presentationData.theme.rootController.navigationBar.separatorColor
self.infoButtonNode = HighlightableButtonNode()
self.infoButtonNode.setImage(generateTintedImage(image: UIImage(bundleImageName: "Location/InfoIcon"), color: presentationData.theme.rootController.navigationBar.buttonColor), for: .normal)
self.infoButtonNode.setImage(generateTintedImage(image: UIImage(bundleImageName: "Location/InfoActiveIcon"), color: presentationData.theme.rootController.navigationBar.buttonColor), for: .selected)
@ -78,6 +86,9 @@ final class LocationMapHeaderNode: ASDisplayNode {
self.locationButtonNode = HighlightableButtonNode()
self.locationButtonNode.setImage(generateTintedImage(image: UIImage(bundleImageName: "Location/TrackIcon"), color: presentationData.theme.rootController.navigationBar.buttonColor), for: .normal)
self.notificationButtonNode = HighlightableButtonNode()
self.notificationButtonNode.setImage(generateTintedImage(image: UIImage(bundleImageName: "Location/NotificationIcon"), color: presentationData.theme.rootController.navigationBar.buttonColor), for: .normal)
self.placesBackgroundNode = ASImageNode()
self.placesBackgroundNode.contentMode = .scaleToFill
self.placesBackgroundNode.displaysAsynchronously = false
@ -101,23 +112,27 @@ final class LocationMapHeaderNode: ASDisplayNode {
self.addSubnode(self.mapNode)
self.addSubnode(self.optionsBackgroundNode)
self.optionsBackgroundNode.addSubnode(self.optionsSeparatorNode)
self.optionsBackgroundNode.addSubnode(self.optionsSecondSeparatorNode)
self.optionsBackgroundNode.addSubnode(self.infoButtonNode)
self.optionsBackgroundNode.addSubnode(self.locationButtonNode)
self.optionsBackgroundNode.addSubnode(self.notificationButtonNode)
self.addSubnode(self.placesBackgroundNode)
self.placesBackgroundNode.addSubnode(self.placesButtonNode)
self.addSubnode(self.shadowNode)
self.infoButtonNode.addTarget(self, action: #selector(self.infoPressed), forControlEvents: .touchUpInside)
self.locationButtonNode.addTarget(self, action: #selector(self.locationPressed), forControlEvents: .touchUpInside)
self.notificationButtonNode.addTarget(self, action: #selector(self.notificationPressed), forControlEvents: .touchUpInside)
self.placesButtonNode.addTarget(self, action: #selector(self.placesPressed), forControlEvents: .touchUpInside)
}
func updateState(mapMode: LocationMapMode, displayingMapModeOptions: Bool, displayingPlacesButton: Bool, animated: Bool) {
func updateState(mapMode: LocationMapMode, displayingMapModeOptions: Bool, displayingPlacesButton: Bool, proximityNotification: Bool?, animated: Bool) {
self.mapNode.mapMode = mapMode
self.infoButtonNode.isSelected = displayingMapModeOptions
let updateLayout = self.displayingPlacesButton != displayingPlacesButton
let updateLayout = self.displayingPlacesButton != displayingPlacesButton || self.proximityNotification != proximityNotification
self.displayingPlacesButton = displayingPlacesButton
self.proximityNotification = proximityNotification
if updateLayout, let (layout, navigationBarHeight, topPadding, offset, size) = self.validLayout {
let transition: ContainedViewLayoutTransition = animated ? .animated(duration: 0.3, curve: .spring) : .immediate
@ -130,10 +145,14 @@ final class LocationMapHeaderNode: ASDisplayNode {
self.optionsBackgroundNode.image = generateBackgroundImage(theme: presentationData.theme)
self.optionsSeparatorNode.backgroundColor = presentationData.theme.rootController.navigationBar.separatorColor
self.optionsSecondSeparatorNode.backgroundColor = presentationData.theme.rootController.navigationBar.separatorColor
self.infoButtonNode.setImage(generateTintedImage(image: UIImage(bundleImageName: "Location/InfoIcon"), color: presentationData.theme.rootController.navigationBar.buttonColor), for: .normal)
self.infoButtonNode.setImage(generateTintedImage(image: UIImage(bundleImageName: "Location/InfoActiveIcon"), color: presentationData.theme.rootController.navigationBar.buttonColor), for: .selected)
self.infoButtonNode.setImage(generateTintedImage(image: UIImage(bundleImageName: "Location/InfoActiveIcon"), color: presentationData.theme.rootController.navigationBar.buttonColor), for: [.selected, .highlighted])
self.locationButtonNode.setImage(generateTintedImage(image: UIImage(bundleImageName: "Location/TrackIcon"), color: presentationData.theme.rootController.navigationBar.buttonColor), for: .normal)
self.notificationButtonNode.setImage(generateTintedImage(image: UIImage(bundleImageName: "Location/InfoIcon"), color: presentationData.theme.rootController.navigationBar.buttonColor), for: .normal)
self.notificationButtonNode.setImage(generateTintedImage(image: UIImage(bundleImageName: "Location/InfoActiveIcon"), color: presentationData.theme.rootController.navigationBar.buttonColor), for: .selected)
self.notificationButtonNode.setImage(generateTintedImage(image: UIImage(bundleImageName: "Location/InfoActiveIcon"), color: presentationData.theme.rootController.navigationBar.buttonColor), for: [.selected, .highlighted])
self.placesBackgroundNode.image = generateBackgroundImage(theme: presentationData.theme)
self.shadowNode.image = generateShadowImage(theme: presentationData.theme, highlighted: false)
}
@ -155,10 +174,20 @@ final class LocationMapHeaderNode: ASDisplayNode {
transition.updateFrame(node: self.shadowNode, frame: CGRect(x: 0.0, y: size.height - 14.0, width: size.width, height: 14.0))
transition.updateFrame(node: self.optionsBackgroundNode, frame: CGRect(x: size.width - inset - panelSize.width - panelInset * 2.0, y: navigationBarHeight + topPadding + inset, width: panelSize.width + panelInset * 2.0, height: panelSize.height + panelInset * 2.0))
transition.updateFrame(node: self.infoButtonNode, frame: CGRect(x: panelInset, y: panelInset, width: panelSize.width, height: panelSize.height / 2.0))
transition.updateFrame(node: self.locationButtonNode, frame: CGRect(x: panelInset, y: panelInset + panelSize.height / 2.0, width: panelSize.width, height: panelSize.height / 2.0))
transition.updateFrame(node: self.optionsSeparatorNode, frame: CGRect(x: panelInset, y: panelInset + panelSize.height / 2.0, width: panelSize.width, height: UIScreenPixel))
transition.updateFrame(node: self.infoButtonNode, frame: CGRect(x: panelInset, y: panelInset, width: panelButtonSize.width, height: panelButtonSize.height))
transition.updateFrame(node: self.locationButtonNode, frame: CGRect(x: panelInset, y: panelInset + panelButtonSize.height, width: panelButtonSize.width, height: panelButtonSize.height))
transition.updateFrame(node: self.notificationButtonNode, frame: CGRect(x: panelInset, y: panelInset + panelButtonSize.height * 2.0, width: panelButtonSize.width, height: panelButtonSize.height))
transition.updateFrame(node: self.optionsSeparatorNode, frame: CGRect(x: panelInset, y: panelInset + panelButtonSize.height, width: panelButtonSize.width, height: UIScreenPixel))
transition.updateFrame(node: self.optionsSecondSeparatorNode, frame: CGRect(x: panelInset, y: panelInset + panelButtonSize.height * 2.0, width: panelButtonSize.width, height: UIScreenPixel))
var panelHeight: CGFloat = panelButtonSize.height * 2.0
if self.proximityNotification != nil {
panelHeight += panelButtonSize.height
}
transition.updateAlpha(node: self.notificationButtonNode, alpha: self.proximityNotification != nil ? 1.0 : 0.0)
transition.updateAlpha(node: self.optionsSecondSeparatorNode, alpha: self.proximityNotification != nil ? 1.0 : 0.0)
transition.updateFrame(node: self.optionsBackgroundNode, frame: CGRect(x: size.width - inset - panelButtonSize.width - panelInset * 2.0, y: navigationBarHeight + topPadding + inset, width: panelButtonSize.width + panelInset * 2.0, height: panelHeight + panelInset * 2.0))
let alphaTransition = ContainedViewLayoutTransition.animated(duration: 0.2, curve: .easeInOut)
let optionsAlpha: CGFloat = size.height > 160.0 + navigationBarHeight ? 1.0 : 0.0
@ -177,6 +206,10 @@ final class LocationMapHeaderNode: ASDisplayNode {
self.goToUserLocation()
}
@objc private func notificationPressed() {
self.setupProximityNotification()
}
@objc private func placesPressed() {
self.showPlacesInThisArea()
}

View File

@ -568,7 +568,7 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM
strongSelf.headerNode.mapNode.setMapCenter(coordinate: venue.coordinate, hidePicker: true, animated: true)
}
strongSelf.headerNode.updateState(mapMode: state.mapMode, displayingMapModeOptions: state.displayingMapModeOptions, displayingPlacesButton: displayingPlacesButton, animated: true)
strongSelf.headerNode.updateState(mapMode: state.mapMode, displayingMapModeOptions: state.displayingMapModeOptions, displayingPlacesButton: displayingPlacesButton, proximityNotification: nil, animated: true)
let annotations: [LocationPinAnnotation]
if let venues = displayedVenues {

View File

@ -35,14 +35,16 @@ class LocationViewInteraction {
let goToCoordinate: (CLLocationCoordinate2D) -> Void
let requestDirections: () -> Void
let share: () -> Void
let setupProximityNotification: () -> Void
init(toggleMapModeSelection: @escaping () -> Void, updateMapMode: @escaping (LocationMapMode) -> Void, goToUserLocation: @escaping () -> Void, goToCoordinate: @escaping (CLLocationCoordinate2D) -> Void, requestDirections: @escaping () -> Void, share: @escaping () -> Void) {
init(toggleMapModeSelection: @escaping () -> Void, updateMapMode: @escaping (LocationMapMode) -> Void, goToUserLocation: @escaping () -> Void, goToCoordinate: @escaping (CLLocationCoordinate2D) -> Void, requestDirections: @escaping () -> Void, share: @escaping () -> Void, setupProximityNotification: @escaping () -> Void) {
self.toggleMapModeSelection = toggleMapModeSelection
self.updateMapMode = updateMapMode
self.goToUserLocation = goToUserLocation
self.goToCoordinate = goToCoordinate
self.requestDirections = requestDirections
self.share = share
self.setupProximityNotification = setupProximityNotification
}
}
@ -140,6 +142,11 @@ public final class LocationViewController: ViewController {
strongSelf.present(ShareController(context: context, subject: .mapMedia(mapMedia), externalShare: true), in: .window(.root), with: nil)
})
strongSelf.present(OpenInActionSheetController(context: context, item: .location(location: mapMedia, withDirections: false), additionalAction: shareAction, openUrl: params.openUrl), in: .window(.root), with: nil)
}, setupProximityNotification: { [weak self] in
guard let strongSelf = self else {
return
}
})
self.scrollToTop = { [weak self] in

View File

@ -200,7 +200,7 @@ final class LocationViewControllerNode: ViewControllerTracingNode {
self.listNode.verticalScrollIndicatorColor = UIColor(white: 0.0, alpha: 0.3)
self.listNode.verticalScrollIndicatorFollowsOverscroll = true
self.headerNode = LocationMapHeaderNode(presentationData: presentationData, toggleMapModeSelection: interaction.toggleMapModeSelection, goToUserLocation: interaction.goToUserLocation)
self.headerNode = LocationMapHeaderNode(presentationData: presentationData, toggleMapModeSelection: interaction.toggleMapModeSelection, goToUserLocation: interaction.goToUserLocation, setupProximityNotification: interaction.setupProximityNotification)
self.headerNode.mapNode.isRotateEnabled = false
self.optionsNode = LocationOptionsNode(presentationData: presentationData, updateMapMode: interaction.updateMapMode)
@ -258,7 +258,7 @@ final class LocationViewControllerNode: ViewControllerTracingNode {
let transition = preparedTransition(from: previousEntries ?? [], to: entries, account: context.account, presentationData: presentationData, interaction: strongSelf.interaction)
strongSelf.enqueueTransition(transition)
strongSelf.headerNode.updateState(mapMode: state.mapMode, displayingMapModeOptions: state.displayingMapModeOptions, displayingPlacesButton: false, animated: false)
strongSelf.headerNode.updateState(mapMode: state.mapMode, displayingMapModeOptions: state.displayingMapModeOptions, displayingPlacesButton: false, proximityNotification: false, animated: false)
switch state.selectedLocation {
case .initial:

View File

@ -2602,7 +2602,7 @@ private func drawAlbumArtPlaceholder(into c: CGContext, arguments: TransformImag
}
}
public func playerAlbumArt(postbox: Postbox, fileReference: FileMediaReference?, albumArt: SharedMediaPlaybackAlbumArt?, thumbnail: Bool, overlayColor: UIColor? = nil, emptyColor: UIColor? = nil) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> {
public func playerAlbumArt(postbox: Postbox, fileReference: FileMediaReference?, albumArt: SharedMediaPlaybackAlbumArt?, thumbnail: Bool, overlayColor: UIColor? = nil, emptyColor: UIColor? = nil, drawPlaceholderWhenEmpty: Bool = true) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> {
var fileArtworkData: Signal<Data?, NoError> = .single(nil)
if let fileReference = fileReference {
let size = thumbnail ? CGSize(width: 48.0, height: 48.0) : CGSize(width: 320.0, height: 320.0)
@ -2685,10 +2685,12 @@ public func playerAlbumArt(postbox: Postbox, fileReference: FileMediaReference?,
c.setFillColor(emptyColor.cgColor)
c.fill(rect)
}
} else {
} else if drawPlaceholderWhenEmpty {
context.withFlippedContext { c in
drawAlbumArtPlaceholder(into: c, arguments: arguments, thumbnail: thumbnail)
}
} else {
return nil
}
}

View File

@ -2,6 +2,7 @@ import Foundation
import UIKit
import AsyncDisplayKit
import Display
import SwiftSignalKit
public enum SemanticStatusNodeState: Equatable {
public struct ProgressAppearance: Equatable {
@ -14,10 +15,19 @@ public enum SemanticStatusNodeState: Equatable {
}
}
public struct CheckAppearance: Equatable {
public var lineWidth: CGFloat
public init(lineWidth: CGFloat) {
self.lineWidth = lineWidth
}
}
case none
case download
case play
case pause
case check(appearance: CheckAppearance?)
case progress(value: CGFloat?, cancelEnabled: Bool, appearance: ProgressAppearance?)
case customIcon(UIImage)
}
@ -390,7 +400,7 @@ private final class SemanticStatusNodeProgressContext: SemanticStatusNodeStateCo
let previousValue = value
self.value = value
let timestamp = CACurrentMediaTime()
if let value = value, let previousValue = previousValue {
if let _ = value, let previousValue = previousValue {
if let transition = self.transition {
self.transition = SemanticStatusNodeProgressTransition(beginTime: timestamp, initialValue: transition.valueAt(timestamp: timestamp, actualValue: previousValue))
} else {
@ -403,6 +413,116 @@ private final class SemanticStatusNodeProgressContext: SemanticStatusNodeStateCo
}
}
private final class SemanticStatusNodeCheckContext: SemanticStatusNodeStateContext {
final class DrawingState: NSObject, SemanticStatusNodeStateDrawingState {
let transitionFraction: CGFloat
let value: CGFloat
let appearance: SemanticStatusNodeState.CheckAppearance?
init(transitionFraction: CGFloat, value: CGFloat, appearance: SemanticStatusNodeState.CheckAppearance?) {
self.transitionFraction = transitionFraction
self.value = value
self.appearance = appearance
super.init()
}
func draw(context: CGContext, size: CGSize, foregroundColor: UIColor) {
let diameter = size.width
let factor = diameter / 50.0
context.saveGState()
if foregroundColor.alpha.isZero {
context.setBlendMode(.destinationOut)
context.setFillColor(UIColor(white: 0.0, alpha: self.transitionFraction).cgColor)
context.setStrokeColor(UIColor(white: 0.0, alpha: self.transitionFraction).cgColor)
} else {
context.setBlendMode(.normal)
context.setFillColor(foregroundColor.withAlphaComponent(foregroundColor.alpha * self.transitionFraction).cgColor)
context.setStrokeColor(foregroundColor.withAlphaComponent(foregroundColor.alpha * self.transitionFraction).cgColor)
}
let center = CGPoint(x: diameter / 2.0, y: diameter / 2.0)
let lineWidth: CGFloat
if let appearance = self.appearance {
lineWidth = appearance.lineWidth
} else {
lineWidth = max(1.6, 2.25 * factor)
}
context.setLineWidth(max(1.7, lineWidth * factor))
context.setLineCap(.round)
context.setLineJoin(.round)
context.setMiterLimit(10.0)
let progress = self.value
let firstSegment: CGFloat = max(0.0, min(1.0, progress * 3.0))
var s = CGPoint(x: center.x - 10.0 * factor, y: center.y + 1.0 * factor)
var p1 = CGPoint(x: 7.0 * factor, y: 7.0 * factor)
var p2 = CGPoint(x: 13.0 * factor, y: -15.0 * factor)
if diameter < 36.0 {
s = CGPoint(x: center.x - 7.0 * factor, y: center.y + 1.0 * factor)
p1 = CGPoint(x: 4.5 * factor, y: 4.5 * factor)
p2 = CGPoint(x: 10.0 * factor, y: -11.0 * factor)
}
if !firstSegment.isZero {
if firstSegment < 1.0 {
context.move(to: CGPoint(x: s.x + p1.x * firstSegment, y: s.y + p1.y * firstSegment))
context.addLine(to: s)
} else {
let secondSegment = (progress - 0.33) * 1.5
context.move(to: CGPoint(x: s.x + p1.x + p2.x * secondSegment, y: s.y + p1.y + p2.y * secondSegment))
context.addLine(to: CGPoint(x: s.x + p1.x, y: s.y + p1.y))
context.addLine(to: s)
}
}
context.strokePath()
}
}
var value: CGFloat
let appearance: SemanticStatusNodeState.CheckAppearance?
var transition: SemanticStatusNodeProgressTransition?
var isAnimating: Bool {
return true
}
init(value: CGFloat, appearance: SemanticStatusNodeState.CheckAppearance?) {
self.value = value
self.appearance = appearance
self.animate()
}
func drawingState(transitionFraction: CGFloat) -> SemanticStatusNodeStateDrawingState {
let timestamp = CACurrentMediaTime()
let resolvedValue: CGFloat
if let transition = self.transition {
resolvedValue = transition.valueAt(timestamp: timestamp, actualValue: value)
} else {
resolvedValue = value
}
return DrawingState(transitionFraction: transitionFraction, value: resolvedValue, appearance: self.appearance)
}
func animate() {
guard self.value < 1.0 else {
return
}
let timestamp = CACurrentMediaTime()
self.value = 1.0
self.transition = SemanticStatusNodeProgressTransition(beginTime: timestamp, initialValue: 0.0)
}
}
private extension SemanticStatusNodeState {
func context(current: SemanticStatusNodeStateContext?) -> SemanticStatusNodeStateContext {
switch self {
@ -427,6 +547,12 @@ private extension SemanticStatusNodeState {
} else {
return SemanticStatusNodeIconContext(icon: icon)
}
case let .check(appearance):
if let current = current as? SemanticStatusNodeCheckContext {
return current
} else {
return SemanticStatusNodeCheckContext(value: 0.0, appearance: appearance)
}
case let .progress(value, cancelEnabled, appearance):
if let current = current as? SemanticStatusNodeProgressContext, current.displayCancel == cancelEnabled {
current.updateValue(value: value)
@ -454,14 +580,18 @@ private final class SemanticStatusNodeDrawingState: NSObject {
let hollow: Bool
let transitionState: SemanticStatusNodeTransitionDrawingState?
let drawingState: SemanticStatusNodeStateDrawingState
let backgroundImage: UIImage?
let overlayForeground: UIColor?
let cutout: SemanticStatusNode.Cutout?
init(background: UIColor, foreground: UIColor, hollow: Bool, transitionState: SemanticStatusNodeTransitionDrawingState?, drawingState: SemanticStatusNodeStateDrawingState, cutout: SemanticStatusNode.Cutout?) {
init(background: UIColor, foreground: UIColor, hollow: Bool, transitionState: SemanticStatusNodeTransitionDrawingState?, drawingState: SemanticStatusNodeStateDrawingState, backgroundImage: UIImage?, overlayForeground: UIColor?, cutout: SemanticStatusNode.Cutout?) {
self.background = background
self.foreground = foreground
self.hollow = hollow
self.transitionState = transitionState
self.drawingState = drawingState
self.backgroundImage = backgroundImage
self.overlayForeground = overlayForeground
self.cutout = cutout
super.init()
@ -503,6 +633,14 @@ public final class SemanticStatusNode: ASControlNode {
}
}
public var overlayForegroundNodeColor: UIColor? {
didSet {
if !(self.overlayForegroundNodeColor?.isEqual(oldValue) ?? true) {
self.setNeedsDisplay()
}
}
}
private let hollow: Bool
private var animator: ConstantDisplayLinkAnimator?
@ -512,9 +650,13 @@ public final class SemanticStatusNode: ASControlNode {
private var transtionContext: SemanticStatusNodeTransitionContext?
private var stateContext: SemanticStatusNodeStateContext
public init(backgroundNodeColor: UIColor, foregroundNodeColor: UIColor, hollow: Bool = false) {
private var disposable: Disposable?
private var backgroundNodeImage: UIImage?
public init(backgroundNodeColor: UIColor, foregroundNodeColor: UIColor, image: Signal<(TransformImageArguments) -> DrawingContext?, NoError>? = nil, overlayForegroundNodeColor: UIColor? = nil, hollow: Bool = false) {
self.backgroundNodeColor = backgroundNodeColor
self.foregroundNodeColor = foregroundNodeColor
self.overlayForegroundNodeColor = overlayForegroundNodeColor
self.hollow = hollow
self.state = .none
self.stateContext = self.state.context(current: nil)
@ -523,6 +665,22 @@ public final class SemanticStatusNode: ASControlNode {
self.isOpaque = false
self.displaysAsynchronously = true
if let image = image {
self.disposable = (image
|> deliverOnMainQueue).start(next: { [weak self] transform in
guard let strongSelf = self else {
return
}
let context = transform(TransformImageArguments(corners: ImageCorners(radius: strongSelf.bounds.width / 2.0), imageSize: strongSelf.bounds.size, boundingSize: strongSelf.bounds.size, intrinsicInsets: UIEdgeInsets()))
self?.backgroundNodeImage = context?.generateImage()
self?.setNeedsDisplay()
})
}
}
deinit {
self.disposable?.dispose()
}
private func updateAnimations() {
@ -595,7 +753,7 @@ public final class SemanticStatusNode: ASControlNode {
transitionState = SemanticStatusNodeTransitionDrawingState(transition: t, drawingState: transitionContext.previousStateContext.drawingState(transitionFraction: 1.0 - t))
}
return SemanticStatusNodeDrawingState(background: self.backgroundNodeColor, foreground: self.foregroundNodeColor, hollow: self.hollow, transitionState: transitionState, drawingState: self.stateContext.drawingState(transitionFraction: transitionFraction), cutout: nil)
return SemanticStatusNodeDrawingState(background: self.backgroundNodeColor, foreground: self.foregroundNodeColor, hollow: self.hollow, transitionState: transitionState, drawingState: self.stateContext.drawingState(transitionFraction: transitionFraction), backgroundImage: self.backgroundNodeImage, overlayForeground: self.overlayForegroundNodeColor, cutout: nil)
}
@objc override public class func draw(_ bounds: CGRect, withParameters parameters: Any?, isCancelled: () -> Bool, isRasterizing: Bool) {
@ -610,14 +768,29 @@ public final class SemanticStatusNode: ASControlNode {
guard let parameters = parameters as? SemanticStatusNodeDrawingState else {
return
}
context.setFillColor(parameters.background.cgColor)
context.fillEllipse(in: CGRect(origin: CGPoint(), size: bounds.size))
if let transitionState = parameters.transitionState {
transitionState.drawingState.draw(context: context, size: bounds.size, foregroundColor: parameters.foreground)
var foregroundColor = parameters.foreground
if let backgroundImage = parameters.backgroundImage?.cgImage {
context.saveGState()
context.translateBy(x: 0.0, y: bounds.height)
context.scaleBy(x: 1.0, y: -1.0)
context.draw(backgroundImage, in: bounds)
context.restoreGState()
if let overlayForegroundColor = parameters.overlayForeground {
foregroundColor = overlayForegroundColor
} else {
foregroundColor = .white
}
} else {
context.setFillColor(parameters.background.cgColor)
context.fillEllipse(in: CGRect(origin: CGPoint(), size: bounds.size))
}
parameters.drawingState.draw(context: context, size: bounds.size, foregroundColor: parameters.foreground)
if let transitionState = parameters.transitionState {
transitionState.drawingState.draw(context: context, size: bounds.size, foregroundColor: foregroundColor)
}
parameters.drawingState.draw(context: context, size: bounds.size, foregroundColor: foregroundColor)
if parameters.hollow {
context.setBlendMode(.clear)
context.fillEllipse(in: bounds.insetBy(dx: 8.0, dy: 8.0))

View File

@ -287,7 +287,7 @@ class ForwardPrivacyChatPreviewItemNode: ListViewItemNode {
let contentSize = CGSize(width: textSize.width + 12.0, height: textSize.height + 34.0)
var sourceRect: CGRect
if let messageNode = strongSelf.messageNode as? ChatMessagePrevewItemNode, let forwardInfoNode = messageNode.forwardInfoReferenceNode {
if let messageNode = strongSelf.messageNode as? ChatMessagePreviewItemNode, let forwardInfoNode = messageNode.forwardInfoReferenceNode {
sourceRect = forwardInfoNode.convert(forwardInfoNode.bounds, to: strongSelf)
if let authorNameCenter = authorNameCenter {
sourceRect.origin = CGPoint(x: sourceRect.minX + authorNameCenter, y: sourceRect.minY)

View File

@ -147,7 +147,7 @@ public func requestMessageActionCallback(account: Account, messageId: MessageId,
return .none
}
switch result {
case let .botCallbackAnswer(flags, message, url, cacheTime):
case let .botCallbackAnswer(flags, message, url, _):
if let message = message {
if (flags & (1 << 1)) != 0 {
return .alert(message)

View File

@ -10,6 +10,7 @@ public enum MessageBubbleImageNeighbors {
case bottom
case both
case side
case extracted
}
public func messageSingleBubbleLikeImage(fillColor: UIColor, strokeColor: UIColor) -> UIImage {
@ -117,6 +118,12 @@ public func messageBubbleImage(maxCornerRadius: CGFloat, minCornerRadius: CGFloa
bottomLeftRadius = side ? minCornerRadius : maxCornerRadius
bottomRightRadius = minCornerRadius
drawTail = false
case .extracted:
topLeftRadius = maxCornerRadius
topRightRadius = maxCornerRadius
bottomLeftRadius = maxCornerRadius
bottomRightRadius = maxCornerRadius
drawTail = false
}
let fixedMainDiameter: CGFloat = 33.0

View File

@ -78,7 +78,9 @@ public func chatBubbleActionButtonImage(fillColor: UIColor, strokeColor: UIColor
public final class PrincipalThemeEssentialGraphics {
public let chatMessageBackgroundIncomingMaskImage: UIImage
public let chatMessageBackgroundIncomingImage: UIImage
public let chatMessageBackgroundIncomingExtractedImage: UIImage
public let chatMessageBackgroundIncomingOutlineImage: UIImage
public let chatMessageBackgroundIncomingExtractedOutlineImage: UIImage
public let chatMessageBackgroundIncomingShadowImage: UIImage
public let chatMessageBackgroundIncomingHighlightedImage: UIImage
public let chatMessageBackgroundIncomingMergedTopMaskImage: UIImage
@ -109,7 +111,9 @@ public final class PrincipalThemeEssentialGraphics {
public let chatMessageBackgroundOutgoingMaskImage: UIImage
public let chatMessageBackgroundOutgoingImage: UIImage
public let chatMessageBackgroundOutgoingExtractedImage: UIImage
public let chatMessageBackgroundOutgoingOutlineImage: UIImage
public let chatMessageBackgroundOutgoingExtractedOutlineImage: UIImage
public let chatMessageBackgroundOutgoingShadowImage: UIImage
public let chatMessageBackgroundOutgoingHighlightedImage: UIImage
public let chatMessageBackgroundOutgoingMergedTopMaskImage: UIImage
@ -238,11 +242,15 @@ public final class PrincipalThemeEssentialGraphics {
if preview {
self.chatMessageBackgroundIncomingMaskImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: UIColor.black, strokeColor: UIColor.clear, neighbors: .none, theme: theme, wallpaper: .color(0xffffff), knockout: true, mask: true, extendedEdges: true)
self.chatMessageBackgroundIncomingImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill, strokeColor: incoming.stroke, neighbors: .none, theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true)
self.chatMessageBackgroundIncomingExtractedImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill, strokeColor: incoming.stroke, neighbors: .extracted, theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true)
self.chatMessageBackgroundIncomingOutlineImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill, strokeColor: incoming.stroke, neighbors: .none, theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true, onlyOutline: true)
self.chatMessageBackgroundIncomingExtractedOutlineImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill, strokeColor: incoming.stroke, neighbors: .extracted, theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true, onlyOutline: true)
self.chatMessageBackgroundIncomingShadowImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill, strokeColor: incoming.stroke, neighbors: .none, theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true, onlyShadow: true)
self.chatMessageBackgroundOutgoingMaskImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: .black, strokeColor: .clear, neighbors: .none, theme: theme, wallpaper: .color(0xffffff), knockout: true, mask: true, extendedEdges: true)
self.chatMessageBackgroundOutgoingImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill, strokeColor: outgoing.stroke, neighbors: .none, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true)
self.chatMessageBackgroundOutgoingExtractedImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill, strokeColor: outgoing.stroke, neighbors: .extracted, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true)
self.chatMessageBackgroundOutgoingOutlineImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill, strokeColor: outgoing.stroke, neighbors: .none, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true, onlyOutline: true)
self.chatMessageBackgroundOutgoingExtractedOutlineImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill, strokeColor: outgoing.stroke, neighbors: .extracted, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true, onlyOutline: true)
self.chatMessageBackgroundOutgoingShadowImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill, strokeColor: outgoing.stroke, neighbors: .none, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true, onlyShadow: true)
self.checkBubbleFullImage = generateCheckImage(partial: false, color: theme.message.outgoingCheckColor, width: 11.0)!
self.checkBubblePartialImage = generateCheckImage(partial: true, color: theme.message.outgoingCheckColor, width: 11.0)!
@ -330,7 +338,9 @@ public final class PrincipalThemeEssentialGraphics {
} else {
self.chatMessageBackgroundIncomingMaskImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: .black, strokeColor: .clear, neighbors: .none, theme: theme, wallpaper: .color(0xffffff), knockout: true, mask: true, extendedEdges: true)
self.chatMessageBackgroundIncomingImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill, strokeColor: incoming.stroke, neighbors: .none, theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true)
self.chatMessageBackgroundIncomingExtractedImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill, strokeColor: incoming.stroke, neighbors: .extracted, theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true)
self.chatMessageBackgroundIncomingOutlineImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill, strokeColor: incoming.stroke, neighbors: .none, theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true, onlyOutline: true)
self.chatMessageBackgroundIncomingExtractedOutlineImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill, strokeColor: incoming.stroke, neighbors: .extracted, theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true, onlyOutline: true)
self.chatMessageBackgroundIncomingShadowImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill, strokeColor: incoming.stroke, neighbors: .none, theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true, onlyShadow: true)
self.chatMessageBackgroundIncomingHighlightedImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.highlightedFill, strokeColor: incoming.stroke, neighbors: .none, theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true)
self.chatMessageBackgroundIncomingMergedTopMaskImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: .black, strokeColor: .clear, neighbors: .top(side: false), theme: theme, wallpaper: .color(0xffffff), knockout: true, mask: true, extendedEdges: true)
@ -356,7 +366,9 @@ public final class PrincipalThemeEssentialGraphics {
self.chatMessageBackgroundOutgoingMaskImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: .black, strokeColor: .clear, neighbors: .none, theme: theme, wallpaper: .color(0xffffff), knockout: true, mask: true, extendedEdges: true)
self.chatMessageBackgroundOutgoingImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill, strokeColor: outgoing.stroke, neighbors: .none, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true)
self.chatMessageBackgroundOutgoingExtractedImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill, strokeColor: outgoing.stroke, neighbors: .extracted, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true)
self.chatMessageBackgroundOutgoingOutlineImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill, strokeColor: outgoing.stroke, neighbors: .none, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true, onlyOutline: true)
self.chatMessageBackgroundOutgoingExtractedOutlineImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill, strokeColor: outgoing.stroke, neighbors: .extracted, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true, onlyOutline: true)
self.chatMessageBackgroundOutgoingShadowImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill, strokeColor: outgoing.stroke, neighbors: .none, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true, onlyShadow: true)
self.chatMessageBackgroundOutgoingHighlightedImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.highlightedFill, strokeColor: outgoing.stroke, neighbors: .none, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true)
self.chatMessageBackgroundOutgoingMergedTopMaskImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: .black, strokeColor: .clear, neighbors: .top(side: false), theme: theme, wallpaper: .color(0xffffff), knockout: true, mask: true, extendedEdges: true)

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "ic_select.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "ic_selectall.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -1,9 +1,9 @@
{
"info" : {
"version" : 1,
"author" : "xcode"
"author" : "xcode",
"version" : 1
},
"properties" : {
"provides-namespace" : true
}
}
}

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "ic_radiusnotify.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -669,7 +669,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
let _ = ApplicationSpecificNotice.incrementChatTextSelectionTips(accountManager: strongSelf.context.sharedContext.accountManager).start()
}
let controller = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .extracted(ChatMessageContextExtractedContentSource(chatNode: strongSelf.chatDisplayNode, message: message)), items: .single(actions), reactionItems: reactionItems, recognizer: recognizer, gesture: gesture, displayTextSelectionTip: displayTextSelectionTip)
let controller = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .extracted(ChatMessageContextExtractedContentSource(chatNode: strongSelf.chatDisplayNode, message: message, selectAll: selectAll)), items: .single(actions), reactionItems: reactionItems, recognizer: recognizer, gesture: gesture, displayTextSelectionTip: displayTextSelectionTip)
strongSelf.currentContextController = controller
controller.reactionSelected = { [weak controller] value in
guard let strongSelf = self, let message = updatedMessages.first else {
@ -882,7 +882,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
strongSelf.effectiveNavigationController?.pushViewController(GameController(context: strongSelf.context, url: url, message: message))
} else {
strongSelf.openUrl(url, concealed: false)
}
}
}
}
@ -1686,7 +1686,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
}
})))
let controller = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .extracted(ChatMessageContextExtractedContentSource(chatNode: strongSelf.chatDisplayNode, message: message)), items: .single(actions), reactionItems: [], recognizer: nil)
let controller = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .extracted(ChatMessageContextExtractedContentSource(chatNode: strongSelf.chatDisplayNode, message: message, selectAll: true)), items: .single(actions), reactionItems: [], recognizer: nil)
strongSelf.currentContextController = controller
strongSelf.forEachController({ controller in
if let controller = controller as? TooltipScreen {
@ -1763,7 +1763,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
f(.dismissWithoutContent)
})))
let controller = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .extracted(ChatMessageContextExtractedContentSource(chatNode: strongSelf.chatDisplayNode, message: topMessage)), items: .single(actions), reactionItems: [], recognizer: nil)
let controller = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .extracted(ChatMessageContextExtractedContentSource(chatNode: strongSelf.chatDisplayNode, message: topMessage, selectAll: true)), items: .single(actions), reactionItems: [], recognizer: nil)
strongSelf.currentContextController = controller
strongSelf.forEachController({ controller in
if let controller = controller as? TooltipScreen {
@ -2221,10 +2221,14 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
}
}
}, openMessageStats: { [weak self] id in
let _ = (context.account.postbox.transaction { transaction -> CachedPeerData? in
return transaction.getPeerCachedData(peerId: id.peerId)
} |> deliverOnMainQueue).start(next: { [weak self] cachedPeerData in
guard let strongSelf = self, let cachedPeerData = cachedPeerData else {
let _ = (context.account.postbox.transaction { transaction -> (MessageId, CachedPeerData?)? in
if let message = transaction.getMessage(id), let sourceMessageId = message.forwardInfo?.sourceMessageId {
return (sourceMessageId, transaction.getPeerCachedData(peerId: sourceMessageId.peerId))
} else {
return (id, transaction.getPeerCachedData(peerId: id.peerId))
}
} |> deliverOnMainQueue).start(next: { [weak self] messageIdAndCachedPeerData in
guard let strongSelf = self, let (id, cachedPeerDataValue) = messageIdAndCachedPeerData, let cachedPeerData = cachedPeerDataValue else {
return
}
strongSelf.push(messageStatsController(context: context, messageId: id, cachedPeerData: cachedPeerData))
@ -6573,7 +6577,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
}
let peerId = peer.id
let cacheUsageStats = (collectCacheUsageStats(account: strongSelf.context.account, peerId: peer.id)
let _ = (collectCacheUsageStats(account: strongSelf.context.account, peerId: peer.id)
|> deliverOnMainQueue).start(next: { [weak self, weak controller] result in
controller?.dismiss()
@ -7085,17 +7089,17 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
let replyMessageId = strongSelf.presentationInterfaceState.interfaceState.replyMessageId
var groupingKey: Int64?
var allItemsAreAudio = true
var allItemsAreSame = true
for item in results {
if let item = item {
let pathExtension = (item.fileName as NSString).pathExtension.lowercased()
if !["mp3", "m4a"].contains(pathExtension) {
allItemsAreAudio = false
allItemsAreSame = false
}
}
}
if allItemsAreAudio {
allItemsAreSame = true
if allItemsAreSame {
groupingKey = arc4random64()
}
@ -7768,6 +7772,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
value = self.presentationData.strings.Conversation_Dice_u1F3C0
case "":
value = self.presentationData.strings.Conversation_Dice_u26BD
case "🎰":
value = self.presentationData.strings.Conversation_Dice_u1F3B0
default:
let emojiHex = emoji.unicodeScalars.map({ String(format:"%02x", $0.value) }).joined().uppercased()
let key = "Conversation.Dice.u\(emojiHex)"

View File

@ -814,7 +814,7 @@ func contextMenuForChatPresentationIntefaceState(chatPresentationInterfaceState:
actions.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.Conversation_ContextMenuReport, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Report"), color: theme.actionSheet.primaryTextColor)
}, action: { controller, f in
interfaceInteraction.reportMessages(selectAll ? messages : [message], controller)
interfaceInteraction.reportMessages(messages, controller)
})))
} else if message.id.peerId.isReplies {
actions.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.Conversation_ContextMenuBlock, textColor: .destructive, icon: { theme in
@ -875,13 +875,25 @@ func contextMenuForChatPresentationIntefaceState(chatPresentationInterfaceState:
if !actions.isEmpty {
actions.append(.separator)
}
actions.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.Conversation_ContextMenuMore, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/More"), color: theme.actionSheet.primaryTextColor)
}, action: { _, f in
interfaceInteraction.beginMessageSelection(selectAll ? messages.map { $0.id } : [message.id], { transition in
f(.custom(transition))
})
})))
if !selectAll || messages.count == 1 {
actions.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.Conversation_ContextMenuSelect, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Select"), color: theme.actionSheet.primaryTextColor)
}, action: { _, f in
interfaceInteraction.beginMessageSelection(selectAll ? messages.map { $0.id } : [message.id], { transition in
f(.custom(transition))
})
})))
}
if messages.count > 1 {
actions.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.Conversation_ContextMenuSelectAll(Int32(messages.count)), icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/SelectAll"), color: theme.actionSheet.primaryTextColor)
}, action: { _, f in
interfaceInteraction.beginMessageSelection(messages.map { $0.id }, { transition in
f(.custom(transition))
})
})))
}
}
return actions

View File

@ -327,10 +327,10 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
}
if let telegramDice = self.telegramDice {
// if telegramDice.emoji == "🎲" {
// let animationNode = SlotMachineAnimationNode(context: item.context)
// self.animationNode = animationNode
// } else {
if telegramDice.emoji == "🎰" {
let animationNode = SlotMachineAnimationNode(context: item.context)
self.animationNode = animationNode
} else {
let animationNode = ManagedDiceAnimationNode(context: item.context, emoji: telegramDice.emoji.strippedEmoji)
if !item.message.effectivelyIncoming(item.context.account.peerId) {
animationNode.success = { [weak self] in
@ -340,7 +340,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
}
}
self.animationNode = animationNode
// }
}
} else {
let animationNode: AnimatedStickerNode
if let (node, parentNode, listNode, greetingCompletion) = item.controllerInteraction.greetingStickerNode(), let greetingStickerNode = node as? AnimatedStickerNode {
@ -1478,7 +1478,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
}
override func getMessageContextSourceNode() -> ContextExtractedContentContainingNode? {
override func getMessageContextSourceNode(stableId: UInt32?) -> ContextExtractedContentContainingNode? {
return self.contextSourceNode
}

View File

@ -5,7 +5,7 @@ import Display
import TelegramPresentationData
enum ChatMessageBackgroundMergeType: Equatable {
case None, Side, Top(side: Bool), Bottom, Both
case None, Side, Top(side: Bool), Bottom, Both, Extracted
init(top: Bool, bottom: Bool, side: Bool) {
if top && bottom {
@ -131,6 +131,8 @@ class ChatMessageBackground: ASDisplayNode {
image = highlighted ? graphics.chatMessageBackgroundIncomingMergedBothHighlightedImage : graphics.chatMessageBackgroundIncomingMergedBothImage
case .Side:
image = highlighted ? graphics.chatMessageBackgroundIncomingMergedSideHighlightedImage : graphics.chatMessageBackgroundIncomingMergedSideImage
case .Extracted:
image = graphics.chatMessageBackgroundIncomingExtractedImage
}
}
case let .outgoing(mergeType):
@ -152,6 +154,8 @@ class ChatMessageBackground: ASDisplayNode {
image = highlighted ? graphics.chatMessageBackgroundOutgoingMergedBothHighlightedImage : graphics.chatMessageBackgroundOutgoingMergedBothImage
case .Side:
image = highlighted ? graphics.chatMessageBackgroundOutgoingMergedSideHighlightedImage : graphics.chatMessageBackgroundOutgoingMergedSideImage
case .Extracted:
image = graphics.chatMessageBackgroundOutgoingExtractedImage
}
}
}
@ -178,6 +182,8 @@ class ChatMessageBackground: ASDisplayNode {
outlineImage = graphics.chatMessageBackgroundIncomingMergedBothOutlineImage
case .Side:
outlineImage = graphics.chatMessageBackgroundIncomingMergedSideOutlineImage
case .Extracted:
outlineImage = graphics.chatMessageBackgroundIncomingExtractedOutlineImage
}
case let .outgoing(mergeType):
switch mergeType {
@ -195,6 +201,8 @@ class ChatMessageBackground: ASDisplayNode {
outlineImage = graphics.chatMessageBackgroundOutgoingMergedBothOutlineImage
case .Side:
outlineImage = graphics.chatMessageBackgroundOutgoingMergedSideOutlineImage
case .Extracted:
outlineImage = graphics.chatMessageBackgroundOutgoingExtractedOutlineImage
}
}
} else {
@ -269,6 +277,8 @@ final class ChatMessageShadowNode: ASDisplayNode {
shadowImage = graphics.chatMessageBackgroundIncomingMergedBothShadowImage
case .Side:
shadowImage = graphics.chatMessageBackgroundIncomingMergedSideShadowImage
case .Extracted:
shadowImage = nil
}
case let .outgoing(mergeType):
switch mergeType {
@ -286,6 +296,8 @@ final class ChatMessageShadowNode: ASDisplayNode {
shadowImage = graphics.chatMessageBackgroundOutgoingMergedBothShadowImage
case .Side:
shadowImage = graphics.chatMessageBackgroundOutgoingMergedSideShadowImage
case .Extracted:
shadowImage = nil
}
}
} else {

View File

@ -6,6 +6,53 @@ import TelegramPresentationData
private let maskInset: CGFloat = 1.0
func bubbleMaskForType(_ type: ChatMessageBackgroundType, graphics: PrincipalThemeEssentialGraphics) -> UIImage? {
let image: UIImage?
switch type {
case .none:
image = nil
case let .incoming(mergeType):
switch mergeType {
case .None:
image = graphics.chatMessageBackgroundIncomingMaskImage
case let .Top(side):
if side {
image = graphics.chatMessageBackgroundIncomingMergedTopSideMaskImage
} else {
image = graphics.chatMessageBackgroundIncomingMergedTopMaskImage
}
case .Bottom:
image = graphics.chatMessageBackgroundIncomingMergedBottomMaskImage
case .Both:
image = graphics.chatMessageBackgroundIncomingMergedBothMaskImage
case .Side:
image = graphics.chatMessageBackgroundIncomingMergedSideMaskImage
case .Extracted:
image = nil
}
case let .outgoing(mergeType):
switch mergeType {
case .None:
image = graphics.chatMessageBackgroundOutgoingMaskImage
case let .Top(side):
if side {
image = graphics.chatMessageBackgroundOutgoingMergedTopSideMaskImage
} else {
image = graphics.chatMessageBackgroundOutgoingMergedTopMaskImage
}
case .Bottom:
image = graphics.chatMessageBackgroundOutgoingMergedBottomMaskImage
case .Both:
image = graphics.chatMessageBackgroundOutgoingMergedBothMaskImage
case .Side:
image = graphics.chatMessageBackgroundOutgoingMergedSideMaskImage
case .Extracted:
image = nil
}
}
return image
}
final class ChatMessageBubbleBackdrop: ASDisplayNode {
private let backgroundContent: ASDisplayNode
@ -41,49 +88,6 @@ final class ChatMessageBubbleBackdrop: ASDisplayNode {
self.addSubnode(self.backgroundContent)
}
private func maskForType(_ type: ChatMessageBackgroundType, graphics: PrincipalThemeEssentialGraphics) -> UIImage? {
let image: UIImage?
switch type {
case .none:
image = nil
case let .incoming(mergeType):
switch mergeType {
case .None:
image = graphics.chatMessageBackgroundIncomingMaskImage
case let .Top(side):
if side {
image = graphics.chatMessageBackgroundIncomingMergedTopSideMaskImage
} else {
image = graphics.chatMessageBackgroundIncomingMergedTopMaskImage
}
case .Bottom:
image = graphics.chatMessageBackgroundIncomingMergedBottomMaskImage
case .Both:
image = graphics.chatMessageBackgroundIncomingMergedBothMaskImage
case .Side:
image = graphics.chatMessageBackgroundIncomingMergedSideMaskImage
}
case let .outgoing(mergeType):
switch mergeType {
case .None:
image = graphics.chatMessageBackgroundOutgoingMaskImage
case let .Top(side):
if side {
image = graphics.chatMessageBackgroundOutgoingMergedTopSideMaskImage
} else {
image = graphics.chatMessageBackgroundOutgoingMergedTopMaskImage
}
case .Bottom:
image = graphics.chatMessageBackgroundOutgoingMergedBottomMaskImage
case .Both:
image = graphics.chatMessageBackgroundOutgoingMergedBothMaskImage
case .Side:
image = graphics.chatMessageBackgroundOutgoingMergedSideMaskImage
}
}
return image
}
func setMaskMode(_ maskMode: Bool, mediaBox: MediaBox) {
if let currentType = self.currentType, let theme = self.theme, let essentialGraphics = self.essentialGraphics {
self.setType(type: currentType, theme: theme, mediaBox: mediaBox, essentialGraphics: essentialGraphics, maskMode: maskMode)
@ -127,7 +131,7 @@ final class ChatMessageBubbleBackdrop: ASDisplayNode {
}
if let maskView = self.maskView {
maskView.image = self.maskForType(type, graphics: essentialGraphics)
maskView.image = bubbleMaskForType(type, graphics: essentialGraphics)
}
}
}

View File

@ -35,7 +35,7 @@ private struct BubbleItemAttributes {
var neighborSpacing: ChatMessageBubbleRelativePosition.NeighbourSpacing
}
private func contentNodeMessagesAndClassesForItem(_ item: ChatMessageItem) -> [(Message, AnyClass, ChatMessageEntryAttributes, BubbleItemAttributes)] {
private func contentNodeMessagesAndClassesForItem(_ item: ChatMessageItem) -> ([(Message, AnyClass, ChatMessageEntryAttributes, BubbleItemAttributes)], Bool) {
var result: [(Message, AnyClass, ChatMessageEntryAttributes, BubbleItemAttributes)] = []
var skipText = false
var messageWithCaptionToAdd: (Message, ChatMessageEntryAttributes)?
@ -43,6 +43,7 @@ private func contentNodeMessagesAndClassesForItem(_ item: ChatMessageItem) -> [(
var isAction = false
var previousItemIsMusic = false
var hasFiles = false
outer: for (message, itemAttributes) in item.content {
for attribute in message.attributes {
@ -52,8 +53,9 @@ private func contentNodeMessagesAndClassesForItem(_ item: ChatMessageItem) -> [(
}
}
var isMusic = false
var isFile = false
inner: for media in message.media {
var isMusic = false
if let _ = media as? TelegramMediaImage {
result.append((message, ChatMessageMediaBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .media, neighborSpacing: .default)))
} else if let file = media as? TelegramMediaFile {
@ -66,6 +68,8 @@ private func contentNodeMessagesAndClassesForItem(_ item: ChatMessageItem) -> [(
neighborSpacing = .condensed
}
isMusic = file.isMusic
isFile = true
hasFiles = true
result.append((message, ChatMessageFileBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .freeform, neighborSpacing: neighborSpacing)))
}
} else if let action = media as? TelegramMediaAction {
@ -90,7 +94,7 @@ private func contentNodeMessagesAndClassesForItem(_ item: ChatMessageItem) -> [(
} else if let _ = media as? TelegramMediaExpiredContent {
result.removeAll()
result.append((message, ChatMessageActionBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .freeform, neighborSpacing: .default)))
return result
return (result, false)
} else if let _ = media as? TelegramMediaPoll {
result.append((message, ChatMessagePollBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .freeform, neighborSpacing: .default)))
} else if let _ = media as? TelegramMediaUnsupported {
@ -106,11 +110,11 @@ private func contentNodeMessagesAndClassesForItem(_ item: ChatMessageItem) -> [(
if !messageText.isEmpty || isUnsupportedMedia {
if !skipText {
if case .group = item.content {
if case .group = item.content, !isFile {
messageWithCaptionToAdd = (message, itemAttributes)
skipText = true
} else {
result.append((message, ChatMessageTextBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .freeform, neighborSpacing: .default)))
result.append((message, ChatMessageTextBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .freeform, neighborSpacing: isFile ? .condensed : .default)))
}
} else {
if case .group = item.content {
@ -184,7 +188,12 @@ private func contentNodeMessagesAndClassesForItem(_ item: ChatMessageItem) -> [(
}
}
return result
var needSeparateContainers = false
if case .group = item.content, hasFiles {
needSeparateContainers = true
}
return (result, needSeparateContainers)
}
private let chatMessagePeerIdColors: [UIColor] = [
@ -202,9 +211,86 @@ private enum ContentNodeOperation {
case insert(index: Int, node: ChatMessageBubbleContentNode)
}
class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode {
private let contextSourceNode: ContextExtractedContentContainingNode
private let containerNode: ContextControllerSourceNode
class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode {
class ContentContainer {
let contentMessageStableId: UInt32
let sourceNode: ContextExtractedContentContainingNode
let containerNode: ContextControllerSourceNode
var backgroundNode: ChatMessageBackground?
var selectionBackgroundNode: ASDisplayNode?
var currentParams: (size: CGSize, contentOrigin: CGPoint, presentationData: ChatPresentationData, graphics: PrincipalThemeEssentialGraphics, backgroundType: ChatMessageBackgroundType, Bool?)?
init(contentMessageStableId: UInt32) {
self.contentMessageStableId = contentMessageStableId
self.sourceNode = ContextExtractedContentContainingNode()
self.containerNode = ContextControllerSourceNode()
}
func willUpdateIsExtractedToContextPreview(isExtractedToContextPreview: Bool, transition: ContainedViewLayoutTransition) {
if isExtractedToContextPreview {
if let _ = self.backgroundNode {
} else if let currentParams = self.currentParams {
let backgroundNode = ChatMessageBackground()
backgroundNode.alpha = 0.0
var type: ChatMessageBackgroundType
if case .incoming = currentParams.backgroundType {
type = .incoming(.Extracted)
} else {
type = .outgoing(.Extracted)
}
backgroundNode.setType(type: type, highlighted: false, graphics: currentParams.graphics, maskMode: false, hasWallpaper: currentParams.presentationData.theme.wallpaper.hasWallpaper, transition: .immediate)
self.sourceNode.contentNode.insertSubnode(backgroundNode, at: 0)
self.backgroundNode = backgroundNode
transition.updateAlpha(node: backgroundNode, alpha: 1.0)
}
if let currentParams = self.currentParams {
self.backgroundNode?.updateLayout(size: currentParams.size, transition: .immediate)
self.backgroundNode?.frame = CGRect(origin: currentParams.contentOrigin, size: currentParams.size)
}
} else if let backgroundNode = self.backgroundNode {
self.backgroundNode = nil
transition.updateAlpha(node: backgroundNode, alpha: 0.0, completion: { [weak backgroundNode] _ in
backgroundNode?.removeFromSupernode()
})
}
}
func update(size: CGSize, contentOrigin: CGPoint, presentationData: ChatPresentationData, graphics: PrincipalThemeEssentialGraphics, backgroundType: ChatMessageBackgroundType, messageSelection: Bool?) {
self.currentParams = (size, contentOrigin, presentationData, graphics, backgroundType, messageSelection)
let bounds = CGRect(origin: CGPoint(), size: size)
var incoming: Bool = false
if case .incoming = backgroundType {
incoming = true
}
let messageTheme = incoming ? presentationData.theme.theme.chat.message.incoming : presentationData.theme.theme.chat.message.outgoing
if let messageSelection = messageSelection, messageSelection {
if let _ = self.selectionBackgroundNode {
} else {
let selectionBackgroundNode = ASDisplayNode()
self.sourceNode.contentNode.insertSubnode(selectionBackgroundNode, at: 0)
self.selectionBackgroundNode = selectionBackgroundNode
}
self.selectionBackgroundNode?.backgroundColor = messageTheme.accentTextColor.withAlphaComponent(0.08)
self.selectionBackgroundNode?.frame = bounds.offsetBy(dx: contentOrigin.x, dy: contentOrigin.y)
} else if let selectionBackgroundNode = self.selectionBackgroundNode {
self.selectionBackgroundNode = nil
selectionBackgroundNode.removeFromSupernode()
}
}
}
private let mainContextSourceNode: ContextExtractedContentContainingNode
private let mainContainerNode: ContextControllerSourceNode
private let backgroundWallpaperNode: ChatMessageBubbleBackdrop
private let backgroundNode: ChatMessageBackground
private let shadowNode: ChatMessageShadowNode
@ -228,6 +314,9 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode
}
private var replyInfoNode: ChatMessageReplyInfoNode?
private var contentContainersMaskView: UIImageView?
private var contentContainersWrapperNode: ASDisplayNode
private var contentContainers: [ContentContainer] = []
private(set) var contentNodes: [ChatMessageBubbleContentNode] = []
private var mosaicStatusNode: ChatMessageDateAndStatusNode?
private var actionButtonsNode: ChatMessageActionButtonsNode?
@ -262,9 +351,10 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode
}
required init() {
self.contextSourceNode = ContextExtractedContentContainingNode()
self.containerNode = ContextControllerSourceNode()
self.mainContextSourceNode = ContextExtractedContentContainingNode()
self.mainContainerNode = ContextControllerSourceNode()
self.backgroundWallpaperNode = ChatMessageBubbleBackdrop()
self.contentContainersWrapperNode = ASDisplayNode()
self.backgroundNode = ChatMessageBackground()
self.shadowNode = ChatMessageShadowNode()
@ -272,13 +362,16 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode
super.init(layerBacked: false)
self.containerNode.shouldBegin = { [weak self] location in
self.mainContainerNode.shouldBegin = { [weak self] location in
guard let strongSelf = self else {
return false
}
if !strongSelf.backgroundNode.frame.contains(location) {
return false
}
if strongSelf.selectionNode != nil {
return false
}
if let action = strongSelf.gestureRecognized(gesture: .tap, location: location, recognizer: nil) {
if case .action = action {
return false
@ -288,14 +381,14 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode
switch action {
case .action, .optionalAction:
return false
case .openContextMenu:
return true
case let .openContextMenu(_, selectAll, _):
return selectAll || strongSelf.contentContainers.isEmpty
}
}
return true
}
self.containerNode.activated = { [weak self] gesture, location in
self.mainContainerNode.activated = { [weak self] gesture, location in
guard let strongSelf = self, let item = strongSelf.item else {
return
}
@ -310,12 +403,13 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode
}
}
self.containerNode.addSubnode(self.contextSourceNode)
self.containerNode.targetNodeForActivationProgress = self.contextSourceNode.contentNode
self.addSubnode(self.containerNode)
self.mainContainerNode.addSubnode(self.mainContextSourceNode)
self.mainContainerNode.targetNodeForActivationProgress = self.mainContextSourceNode.contentNode
self.addSubnode(self.mainContainerNode)
self.contextSourceNode.contentNode.addSubnode(self.backgroundWallpaperNode)
self.contextSourceNode.contentNode.addSubnode(self.backgroundNode)
self.mainContextSourceNode.contentNode.addSubnode(self.backgroundWallpaperNode)
self.mainContextSourceNode.contentNode.addSubnode(self.backgroundNode)
self.mainContextSourceNode.contentNode.addSubnode(self.contentContainersWrapperNode)
self.addSubnode(self.messageAccessibilityArea)
self.messageAccessibilityArea.activate = { [weak self] in
@ -332,15 +426,15 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode
self?.accessibilityElementDidBecomeFocused()
}
self.contextSourceNode.willUpdateIsExtractedToContextPreview = { [weak self] isExtractedToContextPreview, _ in
guard let strongSelf = self, let item = strongSelf.item else {
self.mainContextSourceNode.willUpdateIsExtractedToContextPreview = { [weak self] isExtractedToContextPreview, _ in
guard let strongSelf = self, let _ = strongSelf.item else {
return
}
for contentNode in strongSelf.contentNodes {
contentNode.willUpdateIsExtractedToContextPreview(isExtractedToContextPreview)
}
}
self.contextSourceNode.isExtractedToContextPreviewUpdated = { [weak self] isExtractedToContextPreview in
self.mainContextSourceNode.isExtractedToContextPreviewUpdated = { [weak self] isExtractedToContextPreview in
guard let strongSelf = self, let item = strongSelf.item else {
return
}
@ -355,20 +449,20 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode
}
}
self.contextSourceNode.updateAbsoluteRect = { [weak self] rect, size in
guard let strongSelf = self, strongSelf.contextSourceNode.isExtractedToContextPreview else {
self.mainContextSourceNode.updateAbsoluteRect = { [weak self] rect, size in
guard let strongSelf = self, strongSelf.mainContextSourceNode.isExtractedToContextPreview else {
return
}
strongSelf.updateAbsoluteRectInternal(rect, within: size)
}
self.contextSourceNode.applyAbsoluteOffset = { [weak self] value, animationCurve, duration in
guard let strongSelf = self, strongSelf.contextSourceNode.isExtractedToContextPreview else {
self.mainContextSourceNode.applyAbsoluteOffset = { [weak self] value, animationCurve, duration in
guard let strongSelf = self, strongSelf.mainContextSourceNode.isExtractedToContextPreview else {
return
}
strongSelf.applyAbsoluteOffsetInternal(value: value, animationCurve: animationCurve, duration: duration)
}
self.contextSourceNode.applyAbsoluteOffsetSpring = { [weak self] value, duration, damping in
guard let strongSelf = self, strongSelf.contextSourceNode.isExtractedToContextPreview else {
self.mainContextSourceNode.applyAbsoluteOffsetSpring = { [weak self] value, duration, damping in
guard let strongSelf = self, strongSelf.mainContextSourceNode.isExtractedToContextPreview else {
return
}
strongSelf.applyAbsoluteOffsetSpringInternal(value: value, duration: duration, damping: damping)
@ -510,8 +604,9 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode
if let strongSelf = self {
for contentNode in strongSelf.contentNodes {
var translatedPoint: CGPoint?
if let point = point, contentNode.frame.insetBy(dx: -4.0, dy: -4.0).contains(point) {
translatedPoint = CGPoint(x: point.x - contentNode.frame.minX, y: point.y - contentNode.frame.minY)
let convertedNodeFrame = contentNode.convert(contentNode.bounds, to: strongSelf)
if let point = point, convertedNodeFrame.insetBy(dx: -4.0, dy: -4.0).contains(point) {
translatedPoint = strongSelf.convert(point, to: contentNode)
}
contentNode.updateTouchesAtPoint(translatedPoint)
}
@ -1003,7 +1098,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode
var contentPropertiesAndPrepareLayouts: [(Message, Bool, ChatMessageEntryAttributes, BubbleItemAttributes, (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool) -> Void))))] = []
var addedContentNodes: [(Message, ChatMessageBubbleContentNode)]?
let contentNodeMessagesAndClasses = contentNodeMessagesAndClassesForItem(item)
let (contentNodeMessagesAndClasses, needSeparateContainers) = contentNodeMessagesAndClassesForItem(item)
for (contentNodeMessage, contentNodeClass, attributes, bubbleAttributes) in contentNodeMessagesAndClasses {
var found = false
for (currentMessage, currentClass, supportsMosaic, currentLayout) in currentContentClassesPropertiesAndLayouts {
@ -1076,7 +1171,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode
inlineBotNameString = nil
}
var contentPropertiesAndLayouts: [(CGSize?, ChatMessageBubbleContentProperties, ChatMessageBubblePreparePosition, BubbleItemAttributes, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool) -> Void)))] = []
var contentPropertiesAndLayouts: [(CGSize?, ChatMessageBubbleContentProperties, ChatMessageBubblePreparePosition, BubbleItemAttributes, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool) -> Void)), UInt32?, Bool?)] = []
let topNodeMergeStatus: ChatMessageBubbleMergeStatus = mergedTop.merged ? (incoming ? .Left : .Right) : .None(incoming ? .Incoming : .Outgoing)
let bottomNodeMergeStatus: ChatMessageBubbleMergeStatus = mergedBottom.merged ? (incoming ? .Left : .Right) : .None(incoming ? .Incoming : .Outgoing)
@ -1178,7 +1273,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode
let (properties, unboundSize, maxNodeWidth, nodeLayout) = prepareLayout(contentItem, layoutConstants, prepareContentPosition, itemSelection, CGSize(width: maximumContentWidth, height: CGFloat.greatestFiniteMagnitude))
maximumNodeWidth = min(maximumNodeWidth, maxNodeWidth)
contentPropertiesAndLayouts.append((unboundSize, properties, prepareContentPosition, bubbleAttributes, nodeLayout))
contentPropertiesAndLayouts.append((unboundSize, properties, prepareContentPosition, bubbleAttributes, nodeLayout, needSeparateContainers ? message.stableId : nil, itemSelection))
switch properties.hidesBackground {
case .never:
@ -1503,7 +1598,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode
}
}
var contentNodePropertiesAndFinalize: [(ChatMessageBubbleContentProperties, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool) -> Void))] = []
var contentNodePropertiesAndFinalize: [(ChatMessageBubbleContentProperties, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool) -> Void), UInt32?, Bool?)] = []
var maxContentWidth: CGFloat = headerSize.width
@ -1515,7 +1610,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode
}
for i in 0 ..< contentPropertiesAndLayouts.count {
let (_, contentNodeProperties, preparePosition, isAttachment, contentNodeLayout) = contentPropertiesAndLayouts[i]
let (_, contentNodeProperties, preparePosition, _, contentNodeLayout, contentGroupId, itemSelection) = contentPropertiesAndLayouts[i]
if let mosaicRange = mosaicRange, mosaicRange.contains(i), let (framesAndPositions, size) = calculatedGroupFramesAndSize {
let mosaicIndex = i - mosaicRange.lowerBound
@ -1619,7 +1714,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode
let (_, contentNodeFinalize) = contentNodeLayout(framesAndPositions[mosaicIndex].0.size, .mosaic(position: ChatMessageBubbleContentMosaicPosition(topLeft: topLeft, topRight: topRight, bottomLeft: bottomLeft, bottomRight: bottomRight), wide: position.isWide))
contentNodePropertiesAndFinalize.append((contentNodeProperties, contentNodeFinalize))
contentNodePropertiesAndFinalize.append((contentNodeProperties, contentNodeFinalize, contentGroupId, itemSelection))
maxContentWidth = max(maxContentWidth, size.width)
} else {
@ -1663,16 +1758,22 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode
#endif
maxContentWidth = max(maxContentWidth, contentNodeWidth)
contentNodePropertiesAndFinalize.append((contentNodeProperties, contentNodeFinalize))
contentNodePropertiesAndFinalize.append((contentNodeProperties, contentNodeFinalize, contentGroupId, itemSelection))
}
}
var contentSize = CGSize(width: maxContentWidth, height: 0.0)
var contentNodeFramesPropertiesAndApply: [(CGRect, ChatMessageBubbleContentProperties, (ListViewItemUpdateAnimation, Bool) -> Void)] = []
var contentContainerNodeFrames: [(UInt32, CGRect, Bool?)] = []
var currentContainerGroupId: UInt32?
var currentItemSelection: Bool?
var contentNodesHeight: CGFloat = 0.0
var totalContentNodesHeight: CGFloat = 0.0
var mosaicStatusOrigin: CGPoint?
for i in 0 ..< contentNodePropertiesAndFinalize.count {
let (properties, finalize) = contentNodePropertiesAndFinalize[i]
let (properties, finalize, contentGroupId, itemSelection) = contentNodePropertiesAndFinalize[i]
if let mosaicRange = mosaicRange, mosaicRange.contains(i), let (framesAndPositions, size) = calculatedGroupFramesAndSize {
let mosaicIndex = i - mosaicRange.lowerBound
@ -1689,21 +1790,37 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode
if mosaicIndex == mosaicRange.upperBound - 1 {
contentNodesHeight += size.height
totalContentNodesHeight += size.height
mosaicStatusOrigin = contentNodeFrame.bottomRight
}
} else {
if i == 0 && !headerSize.height.isZero {
contentNodesHeight += properties.headerSpacing
totalContentNodesHeight += contentNodesHeight
}
if currentContainerGroupId != contentGroupId {
if let containerGroupId = currentContainerGroupId {
contentContainerNodeFrames.append((containerGroupId, CGRect(x: 0.0, y: totalContentNodesHeight - contentNodesHeight, width: maxContentWidth, height: contentNodesHeight), currentItemSelection))
}
contentNodesHeight = 0.0
currentContainerGroupId = contentGroupId
currentItemSelection = itemSelection
}
let (size, apply) = finalize(maxContentWidth)
contentNodeFramesPropertiesAndApply.append((CGRect(origin: CGPoint(x: 0.0, y: contentNodesHeight), size: size), properties, apply))
contentNodesHeight += size.height
totalContentNodesHeight += size.height
}
}
contentSize.height += contentNodesHeight
contentSize.height += totalContentNodesHeight
if let containerGroupId = currentContainerGroupId {
contentContainerNodeFrames.append((containerGroupId, CGRect(x: 0.0, y: totalContentNodesHeight - contentNodesHeight, width: maxContentWidth, height: contentNodesHeight), currentItemSelection))
}
var actionButtonsSizeAndApply: (CGSize, (Bool) -> ChatMessageActionButtonsNode)?
if let actionButtonsFinalize = actionButtonsFinalize {
@ -1829,6 +1946,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode
addedContentNodes: addedContentNodes,
contentNodeMessagesAndClasses: contentNodeMessagesAndClasses,
contentNodeFramesPropertiesAndApply: contentNodeFramesPropertiesAndApply,
contentContainerNodeFrames: contentContainerNodeFrames,
mosaicStatusOrigin: mosaicStatusOrigin,
mosaicStatusSizeAndApply: mosaicStatusSizeAndApply,
updatedShareButtonNode: updatedShareButtonNode,
@ -1868,6 +1986,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode
addedContentNodes: [(Message, ChatMessageBubbleContentNode)]?,
contentNodeMessagesAndClasses: [(Message, AnyClass, ChatMessageEntryAttributes, BubbleItemAttributes)],
contentNodeFramesPropertiesAndApply: [(CGRect, ChatMessageBubbleContentProperties, (ListViewItemUpdateAnimation, Bool) -> Void)],
contentContainerNodeFrames: [(UInt32, CGRect, Bool?)],
mosaicStatusOrigin: CGPoint?,
mosaicStatusSizeAndApply: (CGSize, (Bool) -> ChatMessageDateAndStatusNode)?,
updatedShareButtonNode: HighlightableButtonNode?,
@ -1876,10 +1995,11 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode
guard let strongSelf = selfReference.value else {
return
}
let previousContextFrame = strongSelf.containerNode.frame
strongSelf.containerNode.frame = CGRect(origin: CGPoint(), size: layout.contentSize)
strongSelf.contextSourceNode.frame = CGRect(origin: CGPoint(), size: layout.contentSize)
strongSelf.contextSourceNode.contentNode.frame = CGRect(origin: CGPoint(), size: layout.contentSize)
let previousContextFrame = strongSelf.mainContainerNode.frame
strongSelf.mainContainerNode.frame = CGRect(origin: CGPoint(), size: layout.contentSize)
strongSelf.mainContextSourceNode.frame = CGRect(origin: CGPoint(), size: layout.contentSize)
strongSelf.mainContextSourceNode.contentNode.frame = CGRect(origin: CGPoint(), size: layout.contentSize)
strongSelf.contentContainersWrapperNode.frame = CGRect(origin: CGPoint(), size: layout.contentSize)
strongSelf.appliedItem = item
strongSelf.appliedForwardInfo = (forwardSource, forwardAuthorSignature)
@ -1922,7 +2042,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode
isAppearing = true
deliveryFailedNode = ChatMessageDeliveryFailedNode(tapped: { [weak strongSelf] in
if let item = strongSelf?.item {
item.controllerInteraction.requestRedeliveryOfFailedMessages(item.content.firstMessage.id)
item.controllerInteraction.requestRedeliveryOfFailedMessages(item.content.firstMessage.id)
}
})
strongSelf.deliveryFailedNode = deliveryFailedNode
@ -1950,7 +2070,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode
if !nameNode.isNodeLoaded {
nameNode.isUserInteractionEnabled = false
}
strongSelf.contextSourceNode.contentNode.addSubnode(nameNode)
strongSelf.mainContextSourceNode.contentNode.addSubnode(nameNode)
}
nameNode.frame = CGRect(origin: CGPoint(x: contentOrigin.x + layoutConstants.text.bubbleInsets.left, y: layoutConstants.bubble.contentInsets.top + nameNodeOriginY), size: nameNodeSizeApply.0)
nameNode.displaysAsynchronously = !item.presentationData.isPreview
@ -1962,7 +2082,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode
} else {
credibilityIconNode = ASImageNode()
strongSelf.credibilityIconNode = credibilityIconNode
strongSelf.contextSourceNode.contentNode.addSubnode(credibilityIconNode)
strongSelf.mainContextSourceNode.contentNode.addSubnode(credibilityIconNode)
}
credibilityIconNode.frame = CGRect(origin: CGPoint(x: nameNode.frame.maxX + 4.0, y: nameNode.frame.minY), size: credibilityIconImage.size)
credibilityIconNode.image = credibilityIconImage
@ -1978,7 +2098,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode
if !adminBadgeNode.isNodeLoaded {
adminBadgeNode.isUserInteractionEnabled = false
}
strongSelf.contextSourceNode.contentNode.addSubnode(adminBadgeNode)
strongSelf.mainContextSourceNode.contentNode.addSubnode(adminBadgeNode)
adminBadgeNode.frame = adminBadgeFrame
} else {
let previousAdminBadgeFrame = adminBadgeNode.frame
@ -2000,7 +2120,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode
strongSelf.forwardInfoNode = forwardInfoNode
var animateFrame = true
if forwardInfoNode.supernode == nil {
strongSelf.contextSourceNode.contentNode.addSubnode(forwardInfoNode)
strongSelf.mainContextSourceNode.contentNode.addSubnode(forwardInfoNode)
animateFrame = false
forwardInfoNode.openPsa = { [weak strongSelf] type, sourceNode in
guard let strongSelf = strongSelf, let item = strongSelf.item else {
@ -2025,7 +2145,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode
strongSelf.replyInfoNode = replyInfoNode
var animateFrame = true
if replyInfoNode.supernode == nil {
strongSelf.contextSourceNode.contentNode.addSubnode(replyInfoNode)
strongSelf.mainContextSourceNode.contentNode.addSubnode(replyInfoNode)
animateFrame = false
}
let previousReplyInfoNodeFrame = replyInfoNode.frame
@ -2040,6 +2160,148 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode
strongSelf.replyInfoNode = nil
}
var incomingOffset: CGFloat = 0.0
switch backgroundType {
case .incoming:
incomingOffset = 5.0
default:
break
}
var hasSelection = false
for (stableId, relativeFrame, itemSelection) in contentContainerNodeFrames {
if let itemSelection = itemSelection, itemSelection {
hasSelection = true
}
var contentContainer: ContentContainer? = strongSelf.contentContainers.first(where: { $0.contentMessageStableId == stableId })
let previousContextFrame = contentContainer?.containerNode.frame
let previousContextContentFrame = contentContainer?.sourceNode.contentRect
if contentContainer == nil {
let container = ContentContainer(contentMessageStableId: stableId)
let contextSourceNode = container.sourceNode
let containerNode = container.containerNode
container.containerNode.shouldBegin = { [weak strongSelf, weak containerNode] location in
guard let strongSelf = strongSelf, let strongContainerNode = containerNode else {
return false
}
let location = location.offsetBy(dx: 0.0, dy: strongContainerNode.frame.minY)
if !strongSelf.backgroundNode.frame.contains(location) {
return false
}
if strongSelf.selectionNode != nil {
return false
}
if let action = strongSelf.gestureRecognized(gesture: .tap, location: location, recognizer: nil) {
if case .action = action {
return false
}
}
if let action = strongSelf.gestureRecognized(gesture: .longTap, location: location, recognizer: nil) {
switch action {
case .action, .optionalAction:
return false
case let .openContextMenu(_, selectAll, _):
return !selectAll
}
}
return true
}
containerNode.activated = { [weak strongSelf, weak containerNode] gesture, location in
guard let strongSelf = strongSelf, let strongContainerNode = containerNode else {
return
}
let location = location.offsetBy(dx: 0.0, dy: strongContainerNode.frame.minY)
strongSelf.mainContainerNode.activated?(gesture, location)
}
containerNode.addSubnode(contextSourceNode)
containerNode.targetNodeForActivationProgress = contextSourceNode.contentNode
strongSelf.contentContainersWrapperNode.addSubnode(containerNode)
contextSourceNode.willUpdateIsExtractedToContextPreview = { [weak strongSelf, weak container, weak contextSourceNode] isExtractedToContextPreview, transition in
guard let strongSelf = strongSelf, let strongContextSourceNode = contextSourceNode else {
return
}
container?.willUpdateIsExtractedToContextPreview(isExtractedToContextPreview: isExtractedToContextPreview, transition: transition)
for contentNode in strongSelf.contentNodes {
if contentNode.supernode === strongContextSourceNode.contentNode {
contentNode.willUpdateIsExtractedToContextPreview(isExtractedToContextPreview)
}
}
}
contextSourceNode.isExtractedToContextPreviewUpdated = { [weak strongSelf, weak contextSourceNode] isExtractedToContextPreview in
guard let strongSelf = strongSelf, let strongContextSourceNode = contextSourceNode else {
return
}
// if !isExtractedToContextPreview, let (rect, size) = strongSelf.absoluteRect {
// strongSelf.updateAbsoluteRect(rect, within: size)
// }
for contentNode in strongSelf.contentNodes {
if contentNode.supernode === strongContextSourceNode.contentNode {
contentNode.updateIsExtractedToContextPreview(isExtractedToContextPreview)
}
}
}
contextSourceNode.updateAbsoluteRect = { [weak strongSelf] rect, size in
guard let strongSelf = strongSelf, strongSelf.mainContextSourceNode.isExtractedToContextPreview else {
return
}
// strongSelf.updateAbsoluteRectInternal(rect, within: size)
}
contextSourceNode.applyAbsoluteOffset = { [weak strongSelf] value, animationCurve, duration in
guard let strongSelf = strongSelf, strongSelf.mainContextSourceNode.isExtractedToContextPreview else {
return
}
// strongSelf.applyAbsoluteOffsetInternal(value: value, animationCurve: animationCurve, duration: duration)
}
contextSourceNode.applyAbsoluteOffsetSpring = { [weak strongSelf] value, duration, damping in
guard let strongSelf = strongSelf, strongSelf.mainContextSourceNode.isExtractedToContextPreview else {
return
}
// strongSelf.applyAbsoluteOffsetSpringInternal(value: value, duration: duration, damping: damping)
}
strongSelf.contentContainers.append(container)
contentContainer = container
}
contentContainer?.sourceNode.frame = CGRect(origin: CGPoint(), size: relativeFrame.size)
contentContainer?.sourceNode.contentNode.frame = CGRect(origin: CGPoint(), size: relativeFrame.size)
contentContainer?.containerNode.frame = relativeFrame
contentContainer?.sourceNode.contentRect = CGRect(origin: CGPoint(x: backgroundFrame.minX + incomingOffset, y: 0.0), size: relativeFrame.size)
contentContainer?.containerNode.targetNodeForActivationProgressContentRect = relativeFrame.offsetBy(dx: backgroundFrame.minX + incomingOffset, dy: 0.0)
if previousContextFrame?.size != contentContainer?.containerNode.bounds.size || previousContextContentFrame != contentContainer?.sourceNode.contentRect {
contentContainer?.sourceNode.layoutUpdated?(relativeFrame.size)
}
contentContainer?.update(size: relativeFrame.size, contentOrigin: contentOrigin, presentationData: item.presentationData, graphics: graphics, backgroundType: backgroundType, messageSelection: itemSelection)
}
if hasSelection {
var currentMaskView: UIImageView?
if let maskView = strongSelf.contentContainersWrapperNode.view.mask as? UIImageView {
currentMaskView = maskView
} else {
currentMaskView = UIImageView()
strongSelf.contentContainersWrapperNode.view.mask = currentMaskView
}
currentMaskView?.frame = CGRect(origin: contentOrigin, size: backgroundFrame.size).insetBy(dx: -1.0, dy: -1.0)
currentMaskView?.image = bubbleMaskForType(backgroundType, graphics: graphics)
} else {
strongSelf.contentContainersWrapperNode.view.mask = nil
}
if removedContentNodeIndices?.count ?? 0 != 0 || addedContentNodes?.count ?? 0 != 0 {
var updatedContentNodes = strongSelf.contentNodes
@ -2060,15 +2322,18 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode
}
if let addedContentNodes = addedContentNodes {
for (_, contentNode) in addedContentNodes {
for (contentNodeMessage, contentNode) in addedContentNodes {
updatedContentNodes.append(contentNode)
strongSelf.contextSourceNode.contentNode.addSubnode(contentNode)
let contextSourceNode: ContextExtractedContentContainingNode = strongSelf.contentContainers.first(where: { $0.contentMessageStableId == contentNodeMessage.stableId })?.sourceNode ?? strongSelf.mainContextSourceNode
contextSourceNode.contentNode.addSubnode(contentNode)
contentNode.visibility = strongSelf.visibility
contentNode.updateIsTextSelectionActive = { [weak strongSelf] value in
strongSelf?.contextSourceNode.updateDistractionFreeMode?(value)
contentNode.updateIsTextSelectionActive = { [weak contextSourceNode] value in
contextSourceNode?.updateDistractionFreeMode?(value)
}
contentNode.updateIsExtractedToContextPreview(strongSelf.contextSourceNode.isExtractedToContextPreview)
contentNode.updateIsExtractedToContextPreview(contextSourceNode.isExtractedToContextPreview)
}
}
@ -2138,7 +2403,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode
if mosaicStatusNode !== strongSelf.mosaicStatusNode {
strongSelf.mosaicStatusNode?.removeFromSupernode()
strongSelf.mosaicStatusNode = mosaicStatusNode
strongSelf.contextSourceNode.contentNode.addSubnode(mosaicStatusNode)
strongSelf.mainContextSourceNode.contentNode.addSubnode(mosaicStatusNode)
}
let absoluteOrigin = mosaicStatusOrigin.offsetBy(dx: contentOrigin.x, dy: contentOrigin.y)
mosaicStatusNode.frame = CGRect(origin: CGPoint(x: absoluteOrigin.x - layoutConstants.image.statusInsets.right - size.width, y: absoluteOrigin.y - layoutConstants.image.statusInsets.bottom - size.height), size: size)
@ -2164,7 +2429,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode
strongSelf.shareButtonNode = nil
}
if case .System = animation, !strongSelf.contextSourceNode.isExtractedToContextPreview {
if case .System = animation, !strongSelf.mainContextSourceNode.isExtractedToContextPreview {
if !strongSelf.backgroundNode.frame.equalTo(backgroundFrame) {
strongSelf.backgroundFrameTransition = (strongSelf.backgroundNode.frame, backgroundFrame)
strongSelf.enableTransitionClippingNode()
@ -2184,7 +2449,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode
}
strongSelf.disableTransitionClippingNode()
if case .System = animation, strongSelf.contextSourceNode.isExtractedToContextPreview {
if case .System = animation, strongSelf.mainContextSourceNode.isExtractedToContextPreview {
transition.updateFrame(node: strongSelf.backgroundNode, frame: backgroundFrame)
strongSelf.backgroundNode.updateLayout(size: backgroundFrame.size, transition: transition)
strongSelf.backgroundWallpaperNode.updateFrame(backgroundFrame, transition: transition)
@ -2238,20 +2503,12 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode
strongSelf.actionButtonsNode = nil
}
var incomingOffset: CGFloat = 0.0
switch backgroundType {
case .incoming:
incomingOffset = 5.0
default:
break
}
let previousContextContentFrame = strongSelf.mainContextSourceNode.contentRect
strongSelf.mainContextSourceNode.contentRect = backgroundFrame.offsetBy(dx: incomingOffset, dy: 0.0)
strongSelf.mainContainerNode.targetNodeForActivationProgressContentRect = strongSelf.mainContextSourceNode.contentRect
let previousContextContentFrame = strongSelf.contextSourceNode.contentRect
strongSelf.contextSourceNode.contentRect = backgroundFrame.offsetBy(dx: incomingOffset, dy: 0.0)
strongSelf.containerNode.targetNodeForActivationProgressContentRect = strongSelf.contextSourceNode.contentRect
if previousContextFrame.size != strongSelf.contextSourceNode.bounds.size || previousContextContentFrame != strongSelf.contextSourceNode.contentRect {
strongSelf.contextSourceNode.layoutUpdated?(strongSelf.contextSourceNode.bounds.size)
if previousContextFrame.size != strongSelf.mainContextSourceNode.bounds.size || previousContextContentFrame != strongSelf.mainContextSourceNode.contentRect {
strongSelf.mainContextSourceNode.layoutUpdated?(strongSelf.mainContextSourceNode.bounds.size)
}
strongSelf.updateSearchTextHighlightState()
@ -2336,14 +2593,6 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode
}
}
private func addContentNode(node: ChatMessageBubbleContentNode) {
if let transitionClippingNode = self.transitionClippingNode {
transitionClippingNode.addSubnode(node)
} else {
self.contextSourceNode.contentNode.addSubnode(node)
}
}
private func enableTransitionClippingNode() {
if self.transitionClippingNode == nil {
let node = ASDisplayNode()
@ -2361,7 +2610,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode
for contentNode in self.contentNodes {
node.addSubnode(contentNode)
}
self.contextSourceNode.contentNode.addSubnode(node)
self.mainContextSourceNode.contentNode.addSubnode(node)
self.transitionClippingNode = node
}
}
@ -2369,13 +2618,13 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode
private func disableTransitionClippingNode() {
if let transitionClippingNode = self.transitionClippingNode {
if let forwardInfoNode = self.forwardInfoNode {
self.contextSourceNode.contentNode.addSubnode(forwardInfoNode)
self.mainContextSourceNode.contentNode.addSubnode(forwardInfoNode)
}
if let replyInfoNode = self.replyInfoNode {
self.contextSourceNode.contentNode.addSubnode(replyInfoNode)
self.mainContextSourceNode.contentNode.addSubnode(replyInfoNode)
}
for contentNode in self.contentNodes {
self.contextSourceNode.contentNode.addSubnode(contentNode)
self.mainContextSourceNode.contentNode.addSubnode(contentNode)
}
transitionClippingNode.removeFromSupernode()
self.transitionClippingNode = nil
@ -2408,9 +2657,9 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode
default:
break
}
self.contextSourceNode.contentRect = backgroundFrame.offsetBy(dx: incomingOffset, dy: 0.0)
self.containerNode.targetNodeForActivationProgressContentRect = self.contextSourceNode.contentRect
if !self.contextSourceNode.isExtractedToContextPreview {
self.mainContextSourceNode.contentRect = backgroundFrame.offsetBy(dx: incomingOffset, dy: 0.0)
self.mainContainerNode.targetNodeForActivationProgressContentRect = self.mainContextSourceNode.contentRect
if !self.mainContextSourceNode.isExtractedToContextPreview {
if let (rect, size) = self.absoluteRect {
self.updateAbsoluteRect(rect, within: size)
}
@ -2446,7 +2695,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode
if let (gesture, location) = recognizer.lastRecognizedGestureAndLocation {
if let action = self.gestureRecognized(gesture: gesture, location: location, recognizer: nil) {
if case .doubleTap = gesture {
self.containerNode.cancelGesture()
self.mainContainerNode.cancelGesture()
}
switch action {
case let .action(f):
@ -2602,7 +2851,9 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode
}
}
loop: for contentNode in self.contentNodes {
let tapAction = contentNode.tapActionAtPoint(CGPoint(x: location.x - contentNode.frame.minX, y: location.y - contentNode.frame.minY), gesture: gesture, isEstimating: false)
let convertedLocation = self.convert(location, to: contentNode)
let tapAction = contentNode.tapActionAtPoint(convertedLocation, gesture: gesture, isEstimating: false)
switch tapAction {
case .none:
if let item = self.item, self.backgroundNode.frame.contains(CGPoint(x: self.frame.width - location.x, y: location.y)), let tapMessage = self.item?.controllerInteraction.tapMessage {
@ -2709,13 +2960,18 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode
var tapMessage: Message? = item.content.firstMessage
var selectAll = true
loop: for contentNode in self.contentNodes {
if !contentNode.frame.contains(location) {
let convertedLocation = self.convert(location, to: contentNode)
let convertedNodeFrame = contentNode.convert(contentNode.bounds, to: self)
if !convertedNodeFrame.contains(location) {
continue loop
} else if contentNode is ChatMessageMediaBubbleContentNode {
selectAll = false
} else if contentNode is ChatMessageFileBubbleContentNode {
selectAll = false
}
tapMessage = contentNode.item?.message
let tapAction = contentNode.tapActionAtPoint(CGPoint(x: location.x - contentNode.frame.minX, y: location.y - contentNode.frame.minY), gesture: gesture, isEstimating: false)
let tapAction = contentNode.tapActionAtPoint(convertedLocation, gesture: gesture, isEstimating: false)
switch tapAction {
case .none, .ignore:
break
@ -2807,7 +3063,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode
return nil
}
if self.contextSourceNode.isExtractedToContextPreview {
if self.mainContextSourceNode.isExtractedToContextPreview {
if let result = super.hitTest(point, with: event) as? TextSelectionNodeView {
return result
}
@ -3090,7 +3346,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode
let graphics = PresentationResourcesChat.principalGraphics(mediaBox: item.context.account.postbox.mediaBox, knockoutWallpaper: item.context.sharedContext.immediateExperimentalUISettings.knockoutWallpaper, theme: item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper, bubbleCorners: item.presentationData.chatBubbleCorners)
let hasWallpaper = item.presentationData.theme.wallpaper.hasWallpaper
self.backgroundNode.setType(type: backgroundType, highlighted: highlighted, graphics: graphics, maskMode: self.contextSourceNode.isExtractedToContextPreview, hasWallpaper: hasWallpaper, transition: animated ? .animated(duration: 0.3, curve: .easeInOut) : .immediate)
self.backgroundNode.setType(type: backgroundType, highlighted: highlighted, graphics: graphics, maskMode: self.mainContextSourceNode.isExtractedToContextPreview, hasWallpaper: hasWallpaper, transition: animated ? .animated(duration: 0.3, curve: .easeInOut) : .immediate)
}
}
}
@ -3196,7 +3452,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode
override func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) {
self.absoluteRect = (rect, containerSize)
if !self.contextSourceNode.isExtractedToContextPreview {
if !self.mainContextSourceNode.isExtractedToContextPreview {
var rect = rect
rect.origin.y = containerSize.height - rect.maxY + self.insets.top
self.updateAbsoluteRectInternal(rect, within: containerSize)
@ -3209,7 +3465,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode
}
override func applyAbsoluteOffset(value: CGFloat, animationCurve: ContainedViewLayoutTransitionCurve, duration: Double) {
if !self.contextSourceNode.isExtractedToContextPreview {
if !self.mainContextSourceNode.isExtractedToContextPreview {
self.applyAbsoluteOffsetInternal(value: -value, animationCurve: animationCurve, duration: duration)
}
}
@ -3222,12 +3478,12 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode
self.backgroundWallpaperNode.offsetSpring(value: value, duration: duration, damping: damping)
}
override func getMessageContextSourceNode() -> ContextExtractedContentContainingNode? {
return self.contextSourceNode
override func getMessageContextSourceNode(stableId: UInt32?) -> ContextExtractedContentContainingNode? {
return self.contentContainers.first(where: { $0.contentMessageStableId == stableId })?.sourceNode ?? self.mainContextSourceNode
}
override func addAccessoryItemNode(_ accessoryItemNode: ListViewAccessoryItemNode) {
self.contextSourceNode.contentNode.addSubnode(accessoryItemNode)
self.mainContextSourceNode.contentNode.addSubnode(accessoryItemNode)
}
override func targetReactionNode(value: String) -> (ASDisplayNode, ASDisplayNode)? {
@ -3242,7 +3498,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode
private var backgroundMaskMode: Bool {
let hasWallpaper = self.item?.presentationData.theme.wallpaper.hasWallpaper ?? false
let isPreview = self.item?.presentationData.isPreview ?? false
return self.contextSourceNode.isExtractedToContextPreview || hasWallpaper || isPreview
return self.mainContextSourceNode.isExtractedToContextPreview || hasWallpaper || isPreview
}
func animateQuizInvalidOptionSelected() {

View File

@ -10,10 +10,12 @@ final class ChatMessageContextExtractedContentSource: ContextExtractedContentSou
private weak var chatNode: ChatControllerNode?
private let message: Message
private let selectAll: Bool
init(chatNode: ChatControllerNode, message: Message) {
init(chatNode: ChatControllerNode, message: Message, selectAll: Bool) {
self.chatNode = chatNode
self.message = message
self.selectAll = selectAll
}
func takeView() -> ContextControllerTakeViewInfo? {
@ -29,7 +31,7 @@ final class ChatMessageContextExtractedContentSource: ContextExtractedContentSou
guard let item = itemNode.item else {
return
}
if item.content.contains(where: { $0.0.stableId == self.message.stableId }), let contentNode = itemNode.getMessageContextSourceNode() {
if item.content.contains(where: { $0.0.stableId == self.message.stableId }), let contentNode = itemNode.getMessageContextSourceNode(stableId: self.selectAll ? nil : self.message.stableId) {
result = ContextControllerTakeViewInfo(contentContainingNode: contentNode, contentAreaInScreenSpace: chatNode.convert(chatNode.frameForVisibleArea(), to: nil))
}
}

View File

@ -103,7 +103,11 @@ class ChatMessageFileBubbleContentNode: ChatMessageBubbleContentNode {
if case let .linear(_, bottom) = position {
if case .Neighbour(_, _, .condensed) = bottom {
bottomInset -= 24.0
if selectedFile?.isMusic ?? false {
bottomInset -= 14.0
} else {
bottomInset -= 10.0
}
}
}

View File

@ -944,7 +944,7 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView {
return self.interactiveVideoNode.playMediaWithSound()
}
override func getMessageContextSourceNode() -> ContextExtractedContentContainingNode? {
override func getMessageContextSourceNode(stableId: UInt32?) -> ContextExtractedContentContainingNode? {
return self.contextSourceNode
}

View File

@ -15,6 +15,7 @@ import RadialStatusNode
import SemanticStatusNode
import FileMediaResourceStatus
import CheckNode
import MusicAlbumArtResources
private struct FetchControls {
let fetch: () -> Void
@ -22,7 +23,6 @@ private struct FetchControls {
}
final class ChatMessageInteractiveFileNode: ASDisplayNode {
private var selectionBackgroundNode: ASDisplayNode?
private var selectionNode: FileMessageSelectionNode?
private var cutoutNode: ASDisplayNode?
@ -488,7 +488,7 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
controlAreaWidth = 86.0
} else {
progressFrame = CGRect(
origin: CGPoint(x: 3.0, y: -3.0),
origin: CGPoint(x: 3.0, y: isVoice ? -3.0 : 0.0),
size: CGSize(width: progressDiameter, height: progressDiameter)
)
controlAreaWidth = progressFrame.maxX + 8.0
@ -519,7 +519,7 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
fittedLayoutSize = CGSize(width: minLayoutWidth, height: 38.0)
} else {
let unionSize = titleFrame.union(descriptionFrame).union(progressFrame).size
fittedLayoutSize = CGSize(width: unionSize.width, height: unionSize.height + 6.0)
fittedLayoutSize = CGSize(width: unionSize.width, height: unionSize.height)
}
var statusFrame: CGRect?
@ -540,7 +540,6 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
if isAudio && !isVoice {
streamingCacheStatusFrame = CGRect(origin: CGPoint(x: progressFrame.maxX - streamingProgressDiameter + 2.0, y: progressFrame.maxY - streamingProgressDiameter + 2.0), size: CGSize(width: streamingProgressDiameter, height: streamingProgressDiameter))
fittedLayoutSize.width = max(fittedLayoutSize.width, boundingWidth + 2.0)
} else {
streamingCacheStatusFrame = CGRect()
}
@ -701,7 +700,7 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
selectionNode.frame = selectionFrame
selectionNode.updateSelected(selection, animated: isAnimated)
} else {
let selectionNode = FileMessageSelectionNode(theme: presentationData.theme.theme, incoming: incoming, toggle: { [weak self] value in
let selectionNode = FileMessageSelectionNode(theme: presentationData.theme.theme, incoming: incoming, isMusic: file.isMusic, toggle: { [weak self] value in
self?.toggleSelection(value)
})
strongSelf.selectionNode = selectionNode
@ -712,19 +711,6 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
selectionNode.animateIn()
}
}
let selectionBackgroundFrame = CGRect(origin: CGPoint(x: -8.0, y: -9.0), size: CGSize(width: fittedLayoutSize.width + 16.0, height: fittedLayoutSize.height + 6.0))
if let selectionBackgroundNode = strongSelf.selectionBackgroundNode {
selectionBackgroundNode.frame = selectionBackgroundFrame
selectionBackgroundNode.isHidden = !selection
} else {
let selectionBackgroundNode = ASDisplayNode()
selectionBackgroundNode.backgroundColor = messageTheme.accentControlColor.withAlphaComponent(0.08)
selectionBackgroundNode.frame = selectionBackgroundFrame
selectionBackgroundNode.isHidden = !selection
strongSelf.selectionBackgroundNode = selectionBackgroundNode
strongSelf.insertSubnode(selectionBackgroundNode, at: 0)
}
} else {
if let streamingStatusNode = strongSelf.streamingStatusNode {
transition.updateAlpha(node: streamingStatusNode, alpha: 1.0)
@ -740,10 +726,6 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
selectionNode.removeFromSupernode()
}
}
if let selectionBackgroundNode = strongSelf.selectionBackgroundNode {
strongSelf.selectionBackgroundNode = nil
selectionBackgroundNode.removeFromSupernode()
}
}
strongSelf.updateStatus(animated: isAnimated)
@ -834,20 +816,6 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
}
}
if isAudio && !isVoice && !isSending {
switch resourceStatus.fetchStatus {
case let .Fetching(_, progress):
let adjustedProgress = max(progress, 0.027)
streamingState = .progress(value: CGFloat(adjustedProgress), cancelEnabled: true, appearance: .init(inset: 1.0, lineWidth: 2.0))
case .Local:
streamingState = .none
case .Remote:
streamingState = .download
}
} else {
streamingState = .none
}
switch resourceStatus.mediaStatus {
case var .fetchStatus(fetchStatus):
if self.message?.forwardInfo != nil {
@ -857,7 +825,16 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
switch fetchStatus {
case let .Fetching(_, progress):
let adjustedProgress = max(progress, 0.027)
state = .progress(value: CGFloat(adjustedProgress), cancelEnabled: true, appearance: nil)
var wasCheck = false
if let statusNode = self.statusNode, case .check = statusNode.state {
wasCheck = true
}
if adjustedProgress.isEqual(to: 1.0), (message.flags.contains(.Unsent) || wasCheck) {
state = .check(appearance: nil)
} else {
state = .progress(value: CGFloat(adjustedProgress), cancelEnabled: true, appearance: nil)
}
case .Local:
if isAudio {
state = .play
@ -883,6 +860,20 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
}
}
if isAudio && !isVoice && !isSending && state != .pause {
switch resourceStatus.fetchStatus {
case let .Fetching(_, progress):
let adjustedProgress = max(progress, 0.027)
streamingState = .progress(value: CGFloat(adjustedProgress), cancelEnabled: true, appearance: .init(inset: 1.0, lineWidth: 2.0))
case .Local:
streamingState = .none
case .Remote:
streamingState = .download
}
} else {
streamingState = .none
}
let backgroundNodeColor: UIColor
let foregroundNodeColor: UIColor
if self.iconNode != nil {
@ -894,7 +885,22 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
}
if state != .none && self.statusNode == nil {
let statusNode = SemanticStatusNode(backgroundNodeColor: backgroundNodeColor, foregroundNodeColor: foregroundNodeColor)
var image: Signal<(TransformImageArguments) -> DrawingContext?, NoError>? = nil
if file.isMusic {
var title: String?
var performer: String?
for attribute in file.attributes {
if case let .Audio(_, _, titleValue, performerValue, _) = attribute {
title = titleValue
performer = performerValue
break
}
}
image = playerAlbumArt(postbox: context.account.postbox, fileReference: .message(message: MessageReference(message), media: file), albumArt: .init(thumbnailResource: ExternalMusicAlbumArtResource(title: title ?? "", performer: performer ?? "", isThumbnail: true), fullSizeResource: ExternalMusicAlbumArtResource(title: title ?? "", performer: performer ?? "", isThumbnail: false)), thumbnail: true, overlayColor: UIColor(white: 0.0, alpha: 0.3), drawPlaceholderWhenEmpty: false)
}
let statusNode = SemanticStatusNode(backgroundNodeColor: backgroundNodeColor, foregroundNodeColor: foregroundNodeColor, image: image, overlayForegroundNodeColor: presentationData.theme.theme.chat.message.mediaOverlayControlColors.foregroundColor)
self.statusNode = statusNode
statusNode.frame = progressFrame
self.addSubnode(statusNode)
@ -1114,10 +1120,12 @@ final class FileMessageSelectionNode: ASDisplayNode {
private var selected = false
private let checkNode: CheckNode
private let isMusic: Bool
public init(theme: PresentationTheme, incoming: Bool, toggle: @escaping (Bool) -> Void) {
public init(theme: PresentationTheme, incoming: Bool, isMusic: Bool, toggle: @escaping (Bool) -> Void) {
self.isMusic = isMusic
self.toggle = toggle
self.checkNode = CheckNode(strokeColor: incoming ? theme.chat.message.incoming.mediaPlaceholderColor : theme.chat.message.outgoing.mediaPlaceholderColor, fillColor: theme.list.itemCheckColors.fillColor, foregroundColor: theme.list.itemCheckColors.foregroundColor, style: .compact)
self.checkNode = CheckNode(strokeColor: incoming ? theme.chat.message.incoming.mediaPlaceholderColor : theme.chat.message.outgoing.mediaPlaceholderColor, fillColor: theme.list.itemCheckColors.fillColor, foregroundColor: theme.list.itemCheckColors.foregroundColor, style: isMusic ? .compact : .overlay)
self.checkNode.isUserInteractionEnabled = false
super.init()
@ -1160,7 +1168,13 @@ final class FileMessageSelectionNode: ASDisplayNode {
super.layout()
let checkSize = CGSize(width: 30.0, height: 30.0)
self.checkNode.frame = CGRect(origin: CGPoint(x: 23.0, y: 17.0), size: checkSize)
let checkOrigin: CGPoint
if self.isMusic {
checkOrigin = CGPoint(x: 23.0, y: 20.0)
} else {
checkOrigin = CGPoint(x: 39.0, y: -5.0)
}
self.checkNode.frame = CGRect(origin: checkOrigin, size: checkSize)
}
}

View File

@ -716,7 +716,7 @@ public class ChatMessageItemView: ListViewItemNode {
return nil
}
func getMessageContextSourceNode() -> ContextExtractedContentContainingNode? {
func getMessageContextSourceNode(stableId: UInt32?) -> ContextExtractedContentContainingNode? {
return nil
}

View File

@ -1051,7 +1051,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
}
override func getMessageContextSourceNode() -> ContextExtractedContentContainingNode? {
override func getMessageContextSourceNode(stableId: UInt32?) -> ContextExtractedContentContainingNode? {
return self.contextSourceNode
}

View File

@ -66,25 +66,30 @@ func openChatMessageImpl(_ params: OpenChatMessageParams) -> Bool {
case let .map(mapMedia):
params.dismissInput()
// let controllerParams = LocationViewParams(sendLiveLocation: { location in
// let outMessage: EnqueueMessage = .message(text: "", attributes: [], mediaReference: .standalone(media: location), replyToMessageId: nil, localGroupingKey: nil)
// params.enqueueMessage(outMessage)
// }, stopLiveLocation: {
// params.context.liveLocationManager?.cancelLiveLocation(peerId: params.message.id.peerId)
// }, openUrl: params.openUrl, openPeer: { peer in
// params.openPeer(peer, .info)
// })
// let controller = LocationViewController(context: params.context, mapMedia: mapMedia, params: controllerParams)
let controller = legacyLocationController(message: params.message, mapMedia: mapMedia, context: params.context, openPeer: { peer in
params.openPeer(peer, .info)
}, sendLiveLocation: { coordinate, period in
let outMessage: EnqueueMessage = .message(text: "", attributes: [], mediaReference: .standalone(media: TelegramMediaMap(latitude: coordinate.latitude, longitude: coordinate.longitude, geoPlace: nil, venue: nil, liveBroadcastingTimeout: period)), replyToMessageId: nil, localGroupingKey: nil)
params.enqueueMessage(outMessage)
}, stopLiveLocation: {
params.context.liveLocationManager?.cancelLiveLocation(peerId: params.message.id.peerId)
}, openUrl: params.openUrl)
controller.navigationPresentation = .modal
params.navigationController?.pushViewController(controller)
if mapMedia.liveBroadcastingTimeout == nil {
let controllerParams = LocationViewParams(sendLiveLocation: { location in
let outMessage: EnqueueMessage = .message(text: "", attributes: [], mediaReference: .standalone(media: location), replyToMessageId: nil, localGroupingKey: nil)
params.enqueueMessage(outMessage)
}, stopLiveLocation: {
params.context.liveLocationManager?.cancelLiveLocation(peerId: params.message.id.peerId)
}, openUrl: params.openUrl, openPeer: { peer in
params.openPeer(peer, .info)
})
let controller = LocationViewController(context: params.context, mapMedia: mapMedia, params: controllerParams)
controller.navigationPresentation = .modal
params.navigationController?.pushViewController(controller)
} else {
let controller = legacyLocationController(message: params.message, mapMedia: mapMedia, context: params.context, openPeer: { peer in
params.openPeer(peer, .info)
}, sendLiveLocation: { coordinate, period in
let outMessage: EnqueueMessage = .message(text: "", attributes: [], mediaReference: .standalone(media: TelegramMediaMap(latitude: coordinate.latitude, longitude: coordinate.longitude, geoPlace: nil, venue: nil, liveBroadcastingTimeout: period)), replyToMessageId: nil, localGroupingKey: nil)
params.enqueueMessage(outMessage)
}, stopLiveLocation: {
params.context.liveLocationManager?.cancelLiveLocation(peerId: params.message.id.peerId)
}, openUrl: params.openUrl)
controller.navigationPresentation = .modal
params.navigationController?.pushViewController(controller)
}
return true
case let .stickerPack(reference):
let controller = StickerPackScreen(context: params.context, mainStickerPack: reference, stickerPacks: [reference], parentNavigationController: params.navigationController, sendSticker: params.sendSticker, actionPerformed: { info, items, action in

View File

@ -22,8 +22,6 @@ private func commitOwnershipTransferController(context: AccountContext, present:
var dismissImpl: (() -> Void)?
var proceedImpl: (() -> Void)?
var pushControllerImpl: ((ViewController) -> Void)?
let disposable = MetaDisposable()
let contentNode = ChannelOwnershipTransferAlertContentNode(theme: AlertControllerTheme(presentationData: presentationData), ptheme: presentationData.theme, strings: presentationData.strings, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {
@ -56,6 +54,7 @@ private func commitOwnershipTransferController(context: AccountContext, present:
contentNode.updateIsChecking(true)
disposable.set((commit(contentNode.password) |> deliverOnMainQueue).start(next: { result in
completion(result)
dismissImpl?()
}, error: { [weak contentNode] error in
var errorTextAndActions: (String, [TextAlertAction])?
@ -78,10 +77,6 @@ private func commitOwnershipTransferController(context: AccountContext, present:
}))
}
pushControllerImpl = { [weak controller] c in
controller?.push(c)
}
return controller
}

View File

@ -1668,7 +1668,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
if strongSelf.searchDisplayController == nil {
items.append(.separator)
items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.Conversation_ContextMenuMore, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/More"), color: theme.contextMenu.primaryColor) }, action: { c, _ in
items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.Conversation_ContextMenuSelect, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Select"), color: theme.contextMenu.primaryColor) }, action: { c, _ in
c.dismiss(completion: {
if let strongSelf = self {
strongSelf.chatInterfaceInteraction.toggleMessagesSelection([message.id], true)
@ -1776,8 +1776,8 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
}
items.append(.separator)
items.append(.action(ContextMenuActionItem(text: strings.Conversation_ContextMenuMore, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/More"), color: theme.actionSheet.primaryTextColor)
items.append(.action(ContextMenuActionItem(text: strings.Conversation_ContextMenuSelect, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Select"), color: theme.actionSheet.primaryTextColor)
}, action: { _, f in
guard let strongSelf = self else {
return

View File

@ -9,16 +9,81 @@ import AccountContext
import StickerResources
import ManagedAnimationNode
enum ReelValue {
case rolling
case bar
case berries
case lemon
case seven
case sevenWin
private struct SlotMachineValue {
enum ReelValue {
case rolling
case bar
case berries
case lemon
case seven
case sevenWin
var isResult: Bool {
if case .rolling = self {
return false
} else {
return true
}
}
}
let left: ReelValue
let center: ReelValue
let right: ReelValue
init(rawValue: Int32?) {
if let rawValue = rawValue, rawValue > 0 {
let rawValue = rawValue - 1
let leftRawValue = rawValue & 3
let centerRawValue = rawValue >> 2 & 3
let rightRawValue = rawValue >> 4
func reelValue(for rawValue: Int32) -> ReelValue {
switch rawValue {
case 0:
return .bar
case 1:
return .berries
case 2:
return .lemon
case 3:
return .seven
default:
return .rolling
}
}
var leftReelValue = reelValue(for: leftRawValue)
var centerReelValue = reelValue(for: centerRawValue)
var rightReelValue = reelValue(for: rightRawValue)
if leftReelValue == .seven && centerReelValue == .seven && rightReelValue == .seven {
leftReelValue = .sevenWin
centerReelValue = .sevenWin
rightReelValue = .sevenWin
}
self.left = leftReelValue
self.center = centerReelValue
self.right = rightReelValue
} else {
self.left = .rolling
self.center = .rolling
self.right = .rolling
}
}
var isThreeOfSame: Bool {
return self.left == self.center && self.center == self.right && self.left.isResult
}
var is777: Bool {
return self.left == .sevenWin && self.center == .sevenWin && self.right == .sevenWin
}
}
private func leftReelAnimationItem(value: ReelValue, immediate: Bool = false) -> ManagedAnimationItem {
private func leftReelAnimationItem(value: SlotMachineValue.ReelValue, immediate: Bool = false) -> ManagedAnimationItem {
let frames: ManagedAnimationFrameRange? = immediate ? .still(.end) : nil
switch value {
case .rolling:
@ -36,7 +101,7 @@ private func leftReelAnimationItem(value: ReelValue, immediate: Bool = false) ->
}
}
private func centerReelAnimationItem(value: ReelValue, immediate: Bool = false) -> ManagedAnimationItem {
private func centerReelAnimationItem(value: SlotMachineValue.ReelValue, immediate: Bool = false) -> ManagedAnimationItem {
let frames: ManagedAnimationFrameRange? = immediate ? .still(.end) : nil
switch value {
case .rolling:
@ -54,7 +119,7 @@ private func centerReelAnimationItem(value: ReelValue, immediate: Bool = false)
}
}
private func rightReelAnimationItem(value: ReelValue, immediate: Bool = false) -> ManagedAnimationItem {
private func rightReelAnimationItem(value: SlotMachineValue.ReelValue, immediate: Bool = false) -> ManagedAnimationItem {
let frames: ManagedAnimationFrameRange? = immediate ? .still(.end) : nil
switch value {
case .rolling:
@ -126,49 +191,17 @@ final class SlotMachineAnimationNode: ASDisplayNode, GenericAnimatedStickerNode
case .rolling:
switch diceState {
case let .value(value, _):
let l: ReelValue
let c: ReelValue
let r: ReelValue
switch value {
case 1:
l = .seven
c = .berries
r = .bar
case 2:
l = .berries
c = .berries
r = .bar
case 3:
l = .seven
c = .berries
r = .seven
case 4:
l = .bar
c = .lemon
r = .seven
case 5:
l = .berries
c = .berries
r = .berries
case 6:
l = .sevenWin
c = .sevenWin
r = .sevenWin
default:
l = .sevenWin
c = .sevenWin
r = .sevenWin
}
if value == 6 {
let slotValue = SlotMachineValue(rawValue: value)
if slotValue.isThreeOfSame {
Queue.mainQueue().after(1.5) {
self.backNode.trackTo(item: ManagedAnimationItem(source: .local("Slot_Back_Win"), loop: false))
}
} else {
self.backNode.trackTo(item: ManagedAnimationItem(source: .local("Slot_Back_Idle"), loop: false))
self.backNode.trackTo(item: ManagedAnimationItem(source: .local("Slot_Back_Win"), frames: .still(.start), loop: false))
}
self.leftReelNode.trackTo(item: leftReelAnimationItem(value: l))
self.centerReelNode.trackTo(item: centerReelAnimationItem(value: c))
self.rightReelNode.trackTo(item: rightReelAnimationItem(value: r))
self.leftReelNode.trackTo(item: leftReelAnimationItem(value: slotValue.left))
self.centerReelNode.trackTo(item: centerReelAnimationItem(value: slotValue.center))
self.rightReelNode.trackTo(item: rightReelAnimationItem(value: slotValue.right))
self.frontNode.trackTo(item: ManagedAnimationItem(source: .local("Slot_Front_Pull"), frames: .still(.end), loop: false))
case .rolling:
break
@ -176,7 +209,7 @@ final class SlotMachineAnimationNode: ASDisplayNode, GenericAnimatedStickerNode
case .value:
switch diceState {
case .rolling:
self.backNode.trackTo(item: ManagedAnimationItem(source: .local("Slot_Back_Idle"), loop: false))
self.backNode.trackTo(item: ManagedAnimationItem(source: .local("Slot_Back_Win"), frames: .still(.start), loop: false))
self.leftReelNode.trackTo(item: leftReelAnimationItem(value: .rolling))
self.centerReelNode.trackTo(item: centerReelAnimationItem(value: .rolling))
self.rightReelNode.trackTo(item: rightReelAnimationItem(value: .rolling))
@ -188,49 +221,17 @@ final class SlotMachineAnimationNode: ASDisplayNode, GenericAnimatedStickerNode
} else {
switch diceState {
case let .value(value, immediate):
self.backNode.trackTo(item: ManagedAnimationItem(source: .local("Slot_Back_Idle"), loop: false))
self.backNode.trackTo(item: ManagedAnimationItem(source: .local("Slot_Back_Win"), frames: .still(.start), loop: false))
let l: ReelValue
let c: ReelValue
let r: ReelValue
switch value {
case 1:
l = .seven
c = .berries
r = .bar
case 2:
l = .berries
c = .berries
r = .bar
case 3:
l = .seven
c = .berries
r = .seven
case 4:
l = .bar
c = .lemon
r = .seven
case 5:
l = .berries
c = .berries
r = .berries
case 6:
l = .sevenWin
c = .sevenWin
r = .sevenWin
default:
l = .sevenWin
c = .sevenWin
r = .sevenWin
}
self.leftReelNode.trackTo(item: leftReelAnimationItem(value: l, immediate: immediate))
self.centerReelNode.trackTo(item: centerReelAnimationItem(value: c, immediate: immediate))
self.rightReelNode.trackTo(item: rightReelAnimationItem(value: r, immediate: immediate))
let slotValue = SlotMachineValue(rawValue: value)
self.leftReelNode.trackTo(item: leftReelAnimationItem(value: slotValue.left, immediate: immediate))
self.centerReelNode.trackTo(item: centerReelAnimationItem(value: slotValue.center, immediate: immediate))
self.rightReelNode.trackTo(item: rightReelAnimationItem(value: slotValue.right, immediate: immediate))
let frames: ManagedAnimationFrameRange? = immediate ? .still(.end) : nil
self.frontNode.trackTo(item: ManagedAnimationItem(source: .local("Slot_Front_Pull"), frames: frames, loop: false))
case .rolling:
self.backNode.trackTo(item: ManagedAnimationItem(source: .local("Slot_Back_Idle"), loop: false))
self.backNode.trackTo(item: ManagedAnimationItem(source: .local("Slot_Back_Win"), frames: .still(.start), loop: false))
self.leftReelNode.trackTo(item: leftReelAnimationItem(value: .rolling))
self.centerReelNode.trackTo(item: centerReelAnimationItem(value: .rolling))
self.rightReelNode.trackTo(item: rightReelAnimationItem(value: .rolling))