mirror of
https://github.com/oxen-io/session-ios.git
synced 2023-12-13 21:30:14 +01:00
80fbc093d9
// FREEBIE
243 lines
11 KiB
Objective-C
243 lines
11 KiB
Objective-C
//
|
|
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
|
|
//
|
|
|
|
#import "OWSMessagesBubblesSizeCalculator.h"
|
|
#import "OWSCall.h"
|
|
#import "OWSDisplayedMessageCollectionViewCell.h"
|
|
#import "TSGenericAttachmentAdapter.h"
|
|
#import "TSMessageAdapter.h"
|
|
#import "UIFont+OWS.h"
|
|
#import "tgmath.h" // generic math allows fmax to handle CGFLoat correctly on 32 & 64bit.
|
|
#import <JSQMessagesViewController/JSQMessagesCollectionViewFlowLayout.h>
|
|
|
|
NS_ASSUME_NONNULL_BEGIN
|
|
|
|
/**
|
|
* We use some private method to size our info messages.
|
|
*/
|
|
@interface OWSMessagesBubblesSizeCalculator (JSQPrivateMethods)
|
|
|
|
@property (strong, nonatomic, readonly) NSCache *cache;
|
|
@property (assign, nonatomic, readonly) NSUInteger minimumBubbleWidth;
|
|
@property (assign, nonatomic, readonly) BOOL usesFixedWidthBubbles;
|
|
@property (assign, nonatomic, readonly) NSInteger additionalInset;
|
|
@property (assign, nonatomic) CGFloat layoutWidthForFixedWidthBubbles;
|
|
|
|
- (CGSize)jsq_avatarSizeForMessageData:(id<JSQMessageData>)messageData
|
|
withLayout:(JSQMessagesCollectionViewFlowLayout *)layout;
|
|
- (CGFloat)textBubbleWidthForLayout:(JSQMessagesCollectionViewFlowLayout *)layout;
|
|
@end
|
|
|
|
@implementation OWSMessagesBubblesSizeCalculator
|
|
|
|
/**
|
|
* Computes and returns the size of the `messageBubbleImageView` property
|
|
* of a `JSQMessagesCollectionViewCell` for the specified messageData at indexPath.
|
|
*
|
|
* @param messageData A message data object.
|
|
* @param indexPath The index path at which messageData is located.
|
|
* @param layout The layout object asking for this information.
|
|
*
|
|
* @return A sizes that specifies the required dimensions to display the entire message contents.
|
|
* Note, this is *not* the entire cell, but only its message bubble.
|
|
*/
|
|
- (CGSize)messageBubbleSizeForMessageData:(id<JSQMessageData>)messageData
|
|
atIndexPath:(NSIndexPath *)indexPath
|
|
withLayout:(JSQMessagesCollectionViewFlowLayout *)layout
|
|
{
|
|
if ([messageData isKindOfClass:[TSMessageAdapter class]]) {
|
|
TSMessageAdapter *message = (TSMessageAdapter *)messageData;
|
|
if (message.messageType == TSInfoMessageAdapter || message.messageType == TSErrorMessageAdapter) {
|
|
return [self messageBubbleSizeForInfoMessageData:messageData atIndexPath:indexPath withLayout:layout];
|
|
}
|
|
}
|
|
|
|
if ([messageData isKindOfClass:[OWSCall class]]) {
|
|
return [self messageBubbleSizeForCallData:messageData atIndexPath:indexPath withLayout:layout];
|
|
}
|
|
|
|
// BEGIN HACK iOS10EmojiBug see: https://github.com/WhisperSystems/Signal-iOS/issues/1368
|
|
if ([self shouldApplyiOS10EmojiFixToString:messageData.text font:layout.messageBubbleFont]) {
|
|
return [self withiOS10EmojiFixSuperMessageBubbleSizeForMessageData:messageData
|
|
atIndexPath:indexPath
|
|
withLayout:layout];
|
|
} else {
|
|
// END HACK iOS10EmojiBug see: https://github.com/WhisperSystems/Signal-iOS/issues/1368
|
|
|
|
return [super messageBubbleSizeForMessageData:messageData atIndexPath:indexPath withLayout:layout];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Emoji sizing bug only affects iOS10. Unfortunately the "fix" for emoji font breaks some other fonts, so it's
|
|
* important
|
|
* to only apply it when emoji is actually present.
|
|
*/
|
|
- (BOOL)shouldApplyiOS10EmojiFixToString:(NSString *)string font:(UIFont *)font
|
|
{
|
|
if (!string) {
|
|
return NO;
|
|
}
|
|
|
|
BOOL isIOS10OrGreater =
|
|
[[NSProcessInfo processInfo] isOperatingSystemAtLeastVersion:(NSOperatingSystemVersion){.majorVersion = 10 }];
|
|
if (!isIOS10OrGreater) {
|
|
return NO;
|
|
}
|
|
|
|
__block BOOL foundEmoji = NO;
|
|
NSDictionary *attributes = @{ NSFontAttributeName : font };
|
|
|
|
NSMutableAttributedString *attributedString =
|
|
[[NSMutableAttributedString alloc] initWithString:string attributes:attributes];
|
|
[attributedString fixAttributesInRange:NSMakeRange(0, string.length)];
|
|
[attributedString enumerateAttribute:NSFontAttributeName
|
|
inRange:NSMakeRange(0, string.length)
|
|
options:0
|
|
usingBlock:^(id _Nullable value, NSRange range, BOOL *_Nonnull stop) {
|
|
UIFont *rangeFont = (UIFont *)value;
|
|
if ([rangeFont.fontName isEqualToString:@".AppleColorEmojiUI"]) {
|
|
DDLogVerbose(@"Detected Emoji at location: %lu, for length: %lu",
|
|
(unsigned long)range.location,
|
|
(unsigned long)range.length);
|
|
foundEmoji = YES;
|
|
*stop = YES;
|
|
}
|
|
}];
|
|
|
|
return foundEmoji;
|
|
}
|
|
|
|
/**
|
|
* HACK iOS10EmojiBug see: https://github.com/WhisperSystems/Signal-iOS/issues/1368
|
|
* As of iOS10.0 the UIEmoji font doesn't present proper line heights. In some cases this causes the last line in a
|
|
* message to get cropped off.
|
|
*/
|
|
- (CGSize)withiOS10EmojiFixSuperMessageBubbleSizeForMessageData:(id<JSQMessageData>)messageData
|
|
atIndexPath:(NSIndexPath *)indexPath
|
|
withLayout:(JSQMessagesCollectionViewFlowLayout *)layout
|
|
{
|
|
UIFont *emojiFont = [UIFont fontWithName:@".AppleColorEmojiUI" size:layout.messageBubbleFont.pointSize];
|
|
CGSize superSize = [super messageBubbleSizeForMessageData:messageData atIndexPath:indexPath withLayout:layout];
|
|
int lines = (int)floor(superSize.height / emojiFont.lineHeight);
|
|
|
|
// Add an extra pixel per line to fit the emoji.
|
|
// This is a crappy solution. Long messages with only one line of emoji will have an extra pixel per line.
|
|
return CGSizeMake(superSize.width, superSize.height + (CGFloat)1.5 * lines);
|
|
}
|
|
|
|
|
|
- (CGSize)messageBubbleSizeForInfoMessageData:(id<JSQMessageData>)messageData
|
|
atIndexPath:(NSIndexPath *)indexPath
|
|
withLayout:(JSQMessagesCollectionViewFlowLayout *)layout
|
|
{
|
|
NSValue *cachedSize = [self.cache objectForKey:@([messageData messageHash])];
|
|
if (cachedSize != nil) {
|
|
return [cachedSize CGSizeValue];
|
|
}
|
|
|
|
CGSize finalSize = CGSizeZero;
|
|
|
|
if ([messageData isMediaMessage]) {
|
|
finalSize = [[messageData media] mediaViewDisplaySize];
|
|
} else {
|
|
///////////////////
|
|
// BEGIN InfoMessage sizing HACK
|
|
// Braindead, and painstakingly produced.
|
|
// If you want to change, check for clipping / excess space on 1, 2, and 3 line messages with short and long
|
|
// words very near the edge.
|
|
|
|
// CGSize avatarSize = [self jsq_avatarSizeForMessageData:messageData withLayout:layout];
|
|
// // from the cell xibs, there is a 2 point space between avatar and bubble
|
|
// CGFloat spacingBetweenAvatarAndBubble = 2.0f;
|
|
// CGFloat horizontalContainerInsets = layout.messageBubbleTextViewTextContainerInsets.left + layout.messageBubbleTextViewTextContainerInsets.right;
|
|
// CGFloat horizontalFrameInsets = layout.messageBubbleTextViewFrameInsets.left + layout.messageBubbleTextViewFrameInsets.right;
|
|
// CGFloat horizontalInsetsTotal = horizontalContainerInsets + horizontalFrameInsets + spacingBetweenAvatarAndBubble;
|
|
// CGFloat maximumTextWidth = [self textBubbleWidthForLayout:layout] - avatarSize.width - layout.messageBubbleLeftRightMargin - horizontalInsetsTotal;
|
|
|
|
// The full layout width, less the textView margins from xib.
|
|
// CGFloat horizontalInsetsTotal = 12.0; cropped 3rd line
|
|
CGFloat horizontalInsetsTotal = 50.0;
|
|
CGFloat maximumTextWidth = [self textBubbleWidthForLayout:layout] - horizontalInsetsTotal;
|
|
|
|
CGRect stringRect = [[messageData text]
|
|
boundingRectWithSize:CGSizeMake(maximumTextWidth, CGFLOAT_MAX)
|
|
options:(NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading)
|
|
attributes:@{
|
|
NSFontAttributeName : [UIFont ows_dynamicTypeBodyFont]
|
|
} // Hack to use a slightly larger than actual font, because I'm seeing messages with higher line count get clipped.
|
|
context:nil];
|
|
// END InfoMessage sizing HACK
|
|
////////////////////
|
|
|
|
CGSize stringSize = CGRectIntegral(stringRect).size;
|
|
|
|
CGFloat verticalContainerInsets = layout.messageBubbleTextViewTextContainerInsets.top
|
|
+ layout.messageBubbleTextViewTextContainerInsets.bottom;
|
|
|
|
CGFloat verticalFrameInsets
|
|
= layout.messageBubbleTextViewFrameInsets.top + layout.messageBubbleTextViewFrameInsets.bottom;
|
|
///////////////////
|
|
// BEGIN InfoMessage sizing HACK
|
|
|
|
CGFloat topIconPortrusion = 28;
|
|
|
|
verticalFrameInsets += topIconPortrusion;
|
|
|
|
// END InfoMessage sizing HACK
|
|
///////////////////
|
|
|
|
// add extra 2 points of space (`self.additionalInset`), because `boundingRectWithSize:` is slightly off
|
|
// not sure why. magix. (shrug) if you know, submit a PR
|
|
CGFloat verticalInsets = verticalContainerInsets + verticalFrameInsets + self.additionalInset;
|
|
|
|
// same as above, an extra 2 points of magix
|
|
CGFloat finalWidth
|
|
= MAX(stringSize.width + horizontalInsetsTotal, self.minimumBubbleWidth) + self.additionalInset;
|
|
|
|
finalSize = CGSizeMake(finalWidth, stringSize.height + verticalInsets);
|
|
}
|
|
|
|
[self.cache setObject:[NSValue valueWithCGSize:finalSize] forKey:@([messageData messageHash])];
|
|
|
|
return finalSize;
|
|
}
|
|
|
|
- (CGSize)messageBubbleSizeForCallData:(id<JSQMessageData>)messageData
|
|
atIndexPath:(NSIndexPath *)indexPath
|
|
withLayout:(JSQMessagesCollectionViewFlowLayout *)layout
|
|
{
|
|
NSValue *cachedSize = [self.cache objectForKey:@([messageData messageHash])];
|
|
if (cachedSize != nil) {
|
|
return [cachedSize CGSizeValue];
|
|
}
|
|
|
|
CGFloat horizontalInsetsTotal = 0.0;
|
|
CGFloat maximumTextWidth = [self textBubbleWidthForLayout:layout] - horizontalInsetsTotal;
|
|
|
|
CGRect stringRect = [[messageData text]
|
|
boundingRectWithSize:CGSizeMake(maximumTextWidth, CGFLOAT_MAX)
|
|
options:(NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading)
|
|
attributes:@{
|
|
NSFontAttributeName : [UIFont ows_dynamicTypeBodyFont]
|
|
} // Hack to use a slightly larger than actual font, because I'm seeing messages with higher line
|
|
// count get clipped.
|
|
context:nil];
|
|
|
|
CGSize stringSize = CGRectIntegral(stringRect).size;
|
|
|
|
CGFloat verticalInsets = 0;
|
|
CGFloat finalWidth = maximumTextWidth + horizontalInsetsTotal;
|
|
|
|
CGSize finalSize = CGSizeMake(finalWidth, stringSize.height + verticalInsets);
|
|
|
|
[self.cache setObject:[NSValue valueWithCGSize:finalSize] forKey:@([messageData messageHash])];
|
|
|
|
return finalSize;
|
|
}
|
|
|
|
@end
|
|
|
|
NS_ASSUME_NONNULL_END
|