mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Merge commit '9d5604d81d7c7cd25b07b8f82f50f353609b8e54'
This commit is contained in:
commit
f5a552ce96
Binary file not shown.
@ -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.";
|
||||
|
@ -465,7 +465,7 @@ public protocol ChatController: ViewController {
|
||||
func displayPromoAnnouncement(text: String)
|
||||
}
|
||||
|
||||
public protocol ChatMessagePrevewItemNode: class {
|
||||
public protocol ChatMessagePreviewItemNode: class {
|
||||
var forwardInfoReferenceNode: ASDisplayNode? { get }
|
||||
}
|
||||
|
||||
|
@ -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()
|
||||
|
@ -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:
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -82,6 +82,13 @@
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setHeading:(NSNumber *)heading
|
||||
{
|
||||
[self willChangeValueForKey:@"heading"];
|
||||
_heading = heading;
|
||||
[self didChangeValueForKey:@"heading"];
|
||||
}
|
||||
|
||||
- (void)setHasSession:(bool)hasSession
|
||||
{
|
||||
if (hasSession != _hasSession)
|
||||
|
@ -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);
|
||||
|
@ -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)];
|
||||
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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))
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -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)
|
||||
|
12
submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Select.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Select.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "ic_select.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
BIN
submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Select.imageset/ic_select.pdf
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Select.imageset/ic_select.pdf
vendored
Normal file
Binary file not shown.
12
submodules/TelegramUI/Images.xcassets/Chat/Context Menu/SelectAll.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Chat/Context Menu/SelectAll.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "ic_selectall.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
BIN
submodules/TelegramUI/Images.xcassets/Chat/Context Menu/SelectAll.imageset/ic_selectall.pdf
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Chat/Context Menu/SelectAll.imageset/ic_selectall.pdf
vendored
Normal file
Binary file not shown.
@ -1,9 +1,9 @@
|
||||
{
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
},
|
||||
"properties" : {
|
||||
"provides-namespace" : true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
12
submodules/TelegramUI/Images.xcassets/Location/NotificationIcon.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Location/NotificationIcon.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "ic_radiusnotify.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
BIN
submodules/TelegramUI/Images.xcassets/Location/NotificationIcon.imageset/ic_radiusnotify.pdf
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Location/NotificationIcon.imageset/ic_radiusnotify.pdf
vendored
Normal file
Binary file not shown.
Binary file not shown.
@ -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)"
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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() {
|
||||
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -944,7 +944,7 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView {
|
||||
return self.interactiveVideoNode.playMediaWithSound()
|
||||
}
|
||||
|
||||
override func getMessageContextSourceNode() -> ContextExtractedContentContainingNode? {
|
||||
override func getMessageContextSourceNode(stableId: UInt32?) -> ContextExtractedContentContainingNode? {
|
||||
return self.contextSourceNode
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -716,7 +716,7 @@ public class ChatMessageItemView: ListViewItemNode {
|
||||
return nil
|
||||
}
|
||||
|
||||
func getMessageContextSourceNode() -> ContextExtractedContentContainingNode? {
|
||||
func getMessageContextSourceNode(stableId: UInt32?) -> ContextExtractedContentContainingNode? {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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))
|
||||
|
Loading…
x
Reference in New Issue
Block a user