Call notifications are deletable

// FREEBIE
This commit is contained in:
Michael Kirk 2016-10-08 11:48:50 -04:00
parent 405990a7d5
commit 7106eee4a3
10 changed files with 200 additions and 111 deletions

View file

@ -4,6 +4,10 @@
#import "OWSMessageData.h"
#import "TSMessageAdapter.h"
NS_ASSUME_NONNULL_BEGIN
@class TSCall;
typedef enum : NSUInteger {
kCallOutgoing = 1,
kCallIncoming = 2,
@ -15,6 +19,18 @@ typedef enum : NSUInteger {
@interface OWSCall : NSObject <OWSMessageData, NSCoding, NSCopying>
#pragma mark - Initialization
- (instancetype)initWithCallRecord:(TSCall *)callRecord;
- (instancetype)initWithCallerId:(NSString *)callerId
callerDisplayName:(NSString *)callerDisplayName
date:(nullable NSDate *)date
status:(CallStatus)status
displayString:(NSString *)detailString NS_DESIGNATED_INITIALIZER;
- (instancetype)init NS_UNAVAILABLE;
/*
* Returns the string Id of the user who initiated the call
*/
@ -41,14 +57,8 @@ typedef enum : NSUInteger {
*/
@property (nonatomic, copy) NSString *detailString;
#pragma mark - Initialization
- (instancetype)initWithCallerId:(NSString *)callerId
callerDisplayName:(NSString *)callerDisplayName
date:(NSDate *)date
status:(CallStatus)status
displayString:(NSString *)detailString NS_DESIGNATED_INITIALIZER;
- (NSString *)dateText;
@end
NS_ASSUME_NONNULL_END

View file

@ -2,8 +2,13 @@
// Portions Copyright (c) 2016 Open Whisper Systems. All rights reserved.
#import "OWSCall.h"
#import "TSCall.h"
#import "TSContactThread.h"
#import <JSQMessagesViewController/JSQMessagesTimestampFormatter.h>
#import <JSQMessagesViewController/UIImage+JSQMessages.h>
#import <SignalServiceKit/TSCall.h>
NS_ASSUME_NONNULL_BEGIN
@interface OWSCall ()
@ -13,6 +18,7 @@
@property (nonatomic) BOOL shouldStartExpireTimer;
@property (nonatomic) uint64_t expiresAtSeconds;
@property (nonatomic) uint32_t expiresInSeconds;
@property (nonatomic) TSInteraction *interaction;
@end
@ -20,19 +26,68 @@
#pragma mark - Initialzation
- (id)init
- (instancetype)initWithCallRecord:(TSCall *)callRecord
{
NSAssert(NO,
@"%s is not a valid initializer for %@. Use %@ instead",
__PRETTY_FUNCTION__,
[self class],
NSStringFromSelector(@selector(initWithCallerId:callerDisplayName:date:status:displayString:)));
return [self initWithCallerId:nil callerDisplayName:nil date:nil status:0 displayString:nil];
TSThread *thread = callRecord.thread;
TSContactThread *contactThread;
if ([thread isKindOfClass:[TSContactThread class]]) {
contactThread = (TSContactThread *)thread;
} else {
DDLogError(@"%@ Unexpected thread type: %@", self.tag, thread);
}
CallStatus status = 0;
switch (callRecord.callType) {
case RPRecentCallTypeOutgoing:
status = kCallOutgoing;
break;
case RPRecentCallTypeMissed:
status = kCallMissed;
break;
case RPRecentCallTypeIncoming:
status = kCallIncoming;
break;
default:
status = kCallIncoming;
break;
}
NSString *name = contactThread.name;
NSString *detailString;
switch (status) {
case kCallMissed:
detailString = [NSString stringWithFormat:NSLocalizedString(@"MSGVIEW_MISSED_CALL", nil), name];
break;
case kCallIncoming:
detailString = [NSString stringWithFormat:NSLocalizedString(@"MSGVIEW_RECEIVED_CALL", nil), name];
break;
case kCallOutgoing:
detailString = [NSString stringWithFormat:NSLocalizedString(@"MSGVIEW_YOU_CALLED", nil), name];
break;
default:
detailString = @"";
break;
}
self = [self initWithCallerId:contactThread.contactIdentifier
callerDisplayName:name
date:callRecord.date
status:status
displayString:detailString];
if (!self) {
return self;
}
_interaction = callRecord;
return self;
}
- (instancetype)initWithCallerId:(NSString *)senderId
callerDisplayName:(NSString *)senderDisplayName
date:(NSDate *)date
date:(nullable NSDate *)date
status:(CallStatus)status
displayString:(NSString *)detailString
{
@ -105,11 +160,18 @@
- (BOOL)canPerformEditingAction:(SEL)action
{
return NO;
return action == @selector(delete:);
}
- (void)performEditingAction:(SEL)action
{
// Deletes are always handled by TSMessageAdapter
if (action == @selector(delete:)) {
DDLogDebug(@"%@ Deleting interaction with uniqueId: %@", self.tag, self.interaction.uniqueId);
[self.interaction remove];
return;
}
// Shouldn't get here, as only supported actions should be exposed via canPerformEditingAction
NSString *actionString = NSStringFromSelector(action);
DDLogError(@"%@ '%@' action unsupported", self.tag, actionString);
@ -124,7 +186,7 @@
#pragma mark - NSCoding
- (instancetype)initWithCoder:(NSCoder *)aDecoder
- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder
{
NSString *senderId = [aDecoder decodeObjectForKey:NSStringFromSelector(@selector(senderId))];
NSString *senderDisplayName = [aDecoder decodeObjectForKey:NSStringFromSelector(@selector(senderDisplayName))];
@ -149,7 +211,7 @@
#pragma mark - NSCopying
- (instancetype)copyWithZone:(NSZone *)zone
- (instancetype)copyWithZone:(nullable NSZone *)zone
{
return [[[self class] allocWithZone:zone] initWithCallerId:self.senderId
callerDisplayName:self.senderDisplayName
@ -181,3 +243,5 @@
}
@end
NS_ASSUME_NONNULL_END

View file

@ -1,6 +1,7 @@
// Copyright (c) 2016 Open Whisper Systems. All rights reserved.
#import "OWSMessagesBubblesSizeCalculator.h"
#import "OWSCall.h"
#import "OWSDisplayedMessageCollectionViewCell.h"
#import "TSMessageAdapter.h"
#import "UIFont+OWS.h"
@ -49,6 +50,10 @@ NS_ASSUME_NONNULL_BEGIN
}
}
if ([messageData isKindOfClass:[OWSCall class]]) {
return [self messageBubbleSizeForCallData:messageData atIndexPath:indexPath withLayout:layout];
}
CGSize size;
// BEGIN HACK iOS10EmojiBug see: https://github.com/WhisperSystems/Signal-iOS/issues/1368
BOOL isIOS10OrGreater =
@ -243,6 +248,39 @@ NS_ASSUME_NONNULL_BEGIN
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

View file

@ -19,6 +19,7 @@ typedef NS_ENUM(NSInteger, TSMessageAdapterType) {
@protocol OWSMessageData <JSQMessageData, OWSMessageEditing>
@property (nonatomic, readonly) TSMessageAdapterType messageType;
@property (nonatomic, readonly) TSInteraction *interaction;
@property (nonatomic, readonly) BOOL isExpiringMessage;
@property (nonatomic, readonly) BOOL shouldStartExpireTimer;
@property (nonatomic, readonly) uint64_t expiresAtSeconds;

View file

@ -1,6 +1,8 @@
// Copyright © 2016 Open Whisper Systems. All rights reserved.
// Created by Michael Kirk on 3/11/16.
@class TSInteraction;
NS_ASSUME_NONNULL_BEGIN
@protocol OWSMessageEditing <NSObject>

View file

@ -20,7 +20,7 @@ NS_ASSUME_NONNULL_BEGIN
+ (id<OWSMessageData>)messageViewDataWithInteraction:(TSInteraction *)interaction inThread:(TSThread *)thread;
@property TSInteraction *interaction;
@property (nonatomic) TSInteraction *interaction;
@end

View file

@ -169,10 +169,8 @@
}
}
} else if ([interaction isKindOfClass:[TSCall class]]) {
adapter.messageBody = @"Placeholder for TSCalls";
adapter.messageType = TSCallAdapter;
OWSCall *call = [self owsCallForTSCall:(TSCall *)interaction thread:(TSContactThread *)thread];
return call;
TSCall *callRecord = (TSCall *)interaction;
return [[OWSCall alloc] initWithCallRecord:callRecord];
} else if ([interaction isKindOfClass:[TSInfoMessage class]]) {
TSInfoMessage *infoMessage = (TSInfoMessage *)interaction;
adapter.infoMessageType = infoMessage.messageType;
@ -209,48 +207,6 @@
return adapter;
}
+ (OWSCall *)owsCallForTSCall:(TSCall *)call thread:(TSContactThread *)thread {
CallStatus status = 0;
NSString *name = thread.name;
NSString *detailString = @"";
switch (call.callType) {
case RPRecentCallTypeOutgoing:
status = kCallOutgoing;
break;
case RPRecentCallTypeMissed:
status = kCallMissed;
break;
case RPRecentCallTypeIncoming:
status = kCallIncoming;
break;
default:
status = kCallIncoming;
break;
}
switch (status) {
case kCallMissed:
detailString = [NSString stringWithFormat:NSLocalizedString(@"MSGVIEW_MISSED_CALL", nil), name];
break;
case kCallIncoming:
detailString = [NSString stringWithFormat:NSLocalizedString(@"MSGVIEW_RECEIVED_CALL", nil), name];
break;
case kCallOutgoing:
detailString = [NSString stringWithFormat:NSLocalizedString(@"MSGVIEW_YOU_CALLED", nil), name];
break;
default:
break;
}
OWSCall *owsCall = [[OWSCall alloc] initWithCallerId:thread.contactIdentifier
callerDisplayName:thread.name
date:call.date
status:status
displayString:detailString];
return owsCall;
}
- (NSString *)senderId {
if (_senderId) {
return _senderId;

View file

@ -829,12 +829,18 @@ typedef enum : NSUInteger {
[attributedText setAttributes:@{ NSFontAttributeName: regularFont }
range:range];
}
callCell.cellLabel.attributedText = attributedText;
callCell.cellLabel.numberOfLines = 0; // uses as many lines as it needs
callCell.cellLabel.textColor = [UIColor ows_materialBlueColor];
callCell.textView.text = nil;
callCell.textView.attributedText = attributedText;
callCell.textView.textAlignment = NSTextAlignmentCenter;
callCell.textView.textColor = [UIColor ows_materialBlueColor];
callCell.layer.shouldRasterize = YES;
callCell.layer.rasterizationScale = [UIScreen mainScreen].scale;
// Disable text selectability. Specifying this in prepareForReuse/awakeFromNib was not sufficient.
callCell.textView.userInteractionEnabled = NO;
callCell.textView.selectable = NO;
return callCell;
}
@ -865,6 +871,7 @@ typedef enum : NSUInteger {
// Disable text selectability. Specifying this in prepareForReuse/awakeFromNib was not sufficient.
infoCell.textView.userInteractionEnabled = NO;
infoCell.textView.selectable = NO;
infoCell.messageBubbleContainerView.layer.borderColor = [[UIColor ows_infoMessageBorderColor] CGColor];
infoCell.headerImageView.image = [UIImage imageNamed:@"warning_white"];
@ -876,9 +883,11 @@ typedef enum : NSUInteger {
{
OWSDisplayedMessageCollectionViewCell *errorCell = [self loadDisplayedMessageCollectionViewCellForIndexPath:indexPath];
errorCell.textView.text = [errorMessage text];
// Disable text selectability. Specifying this in prepareForReuse/awakeFromNib was not sufficient.
errorCell.textView.userInteractionEnabled = NO;
errorCell.textView.selectable = NO;
errorCell.messageBubbleContainerView.layer.borderColor = [[UIColor ows_errorMessageBorderColor] CGColor];
errorCell.headerImageView.image = [UIImage imageNamed:@"error_white"];

View file

@ -2,6 +2,8 @@
// Portions Copyright (c) 2014 Open Whisper Systems. All rights reserved.
#import "OWSCallCollectionViewCell.h"
#import "UIColor+OWS.h"
#import <JSQMessagesViewController/JSQMessagesCollectionViewLayoutAttributes.h>
#import <JSQMessagesViewController/UIView+JSQMessages.h>
@interface OWSCallCollectionViewCell ()
@ -31,23 +33,19 @@
- (void)awakeFromNib
{
[super awakeFromNib];
self.textView.font = [UIFont fontWithName:@"HelveticaNeue-Light" size:12.0f];
[self setTranslatesAutoresizingMaskIntoConstraints:NO];
self.backgroundColor = [UIColor whiteColor];
self.cellLabel.textAlignment = NSTextAlignmentCenter;
self.cellLabel.font = [UIFont fontWithName:@"HelveticaNeue-Light" size:14.0f];
self.cellLabel.textColor = [UIColor lightGrayColor];
}
#pragma mark - Collection view cell
- (void)prepareForReuse
{
[super prepareForReuse];
self.cellLabel.text = nil;
}
- (void)applyLayoutAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes
{
// override superclass with no-op which resets our attributed font on layout.
}
@end

View file

@ -1,8 +1,9 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="10117" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="11201" systemVersion="15G1004" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="11161"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
@ -14,46 +15,56 @@
<rect key="frame" x="0.0" y="0.0" width="320" height="20"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="YEr-eC-P6i" customClass="JSQMessagesLabel">
<rect key="frame" x="39" y="0.0" width="242" height="20"/>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="ErD-Uv-aKj">
<subviews>
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" misplaced="YES" translatesAutoresizingMaskIntoConstraints="NO" id="o2l-Ms-1mk">
<frame key="frameInset" minX="293" width="20" height="20"/>
<constraints>
<constraint firstAttribute="height" constant="20" id="78L-mQ-gEo"/>
<constraint firstAttribute="width" constant="20" id="olH-5o-XyR"/>
</constraints>
</imageView>
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" misplaced="YES" translatesAutoresizingMaskIntoConstraints="NO" id="H8m-r4-eEC">
<frame key="frameInset" minX="7" width="20" height="20"/>
<constraints>
<constraint firstAttribute="height" constant="20" id="Qay-jM-aBk"/>
<constraint firstAttribute="width" constant="20" id="RpE-jJ-cYX"/>
</constraints>
</imageView>
<textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" text="You called Joe" textAlignment="center" translatesAutoresizingMaskIntoConstraints="NO" id="R85-fN-3GC" customClass="JSQMessagesCellTextView">
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<constraints>
<constraint firstAttribute="height" constant="20" id="105-nv-5Fw"/>
</constraints>
<fontDescription key="fontDescription" style="UICTFontTextStyleCaption1"/>
<textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
</textView>
</subviews>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<constraints>
<constraint firstAttribute="height" constant="20" id="7nw-w2-91p"/>
<constraint firstItem="H8m-r4-eEC" firstAttribute="leading" secondItem="ErD-Uv-aKj" secondAttribute="leading" constant="6" id="8pe-Zh-bRn"/>
<constraint firstItem="R85-fN-3GC" firstAttribute="top" secondItem="ErD-Uv-aKj" secondAttribute="top" id="US4-6n-Kmr"/>
<constraint firstItem="o2l-Ms-1mk" firstAttribute="leading" secondItem="R85-fN-3GC" secondAttribute="trailing" id="WKY-HI-dAT"/>
<constraint firstAttribute="trailing" secondItem="o2l-Ms-1mk" secondAttribute="trailing" constant="6" id="ZFy-4i-25N"/>
<constraint firstItem="R85-fN-3GC" firstAttribute="centerX" secondItem="ErD-Uv-aKj" secondAttribute="centerX" id="app-de-yA3"/>
<constraint firstAttribute="bottom" secondItem="R85-fN-3GC" secondAttribute="bottom" id="dyS-Re-6e7"/>
<constraint firstItem="H8m-r4-eEC" firstAttribute="trailing" secondItem="R85-fN-3GC" secondAttribute="leading" id="m7j-ui-ZMI"/>
</constraints>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
<nil key="highlightedColor"/>
</label>
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="o2l-Ms-1mk">
<rect key="frame" x="281" y="0.0" width="20" height="20"/>
<constraints>
<constraint firstAttribute="height" constant="20" id="78L-mQ-gEo"/>
<constraint firstAttribute="width" constant="20" id="olH-5o-XyR"/>
</constraints>
</imageView>
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="H8m-r4-eEC">
<rect key="frame" x="19" y="0.0" width="20" height="20"/>
<constraints>
<constraint firstAttribute="height" constant="20" id="Qay-jM-aBk"/>
<constraint firstAttribute="width" constant="20" id="RpE-jJ-cYX"/>
</constraints>
</imageView>
</view>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
</view>
<constraints>
<constraint firstItem="YEr-eC-P6i" firstAttribute="centerY" secondItem="o2l-Ms-1mk" secondAttribute="centerY" id="8wg-Tg-9Nh"/>
<constraint firstAttribute="trailing" secondItem="YEr-eC-P6i" secondAttribute="trailing" constant="39" id="Hj7-z5-WMM"/>
<constraint firstItem="YEr-eC-P6i" firstAttribute="leading" secondItem="H8m-r4-eEC" secondAttribute="trailing" id="ZZJ-jL-10d"/>
<constraint firstItem="YEr-eC-P6i" firstAttribute="top" secondItem="Efo-Hk-7Hw" secondAttribute="top" id="rEI-cY-6lx"/>
<constraint firstItem="YEr-eC-P6i" firstAttribute="leading" secondItem="Efo-Hk-7Hw" secondAttribute="leading" constant="39" id="uNd-aK-1hE"/>
<constraint firstItem="YEr-eC-P6i" firstAttribute="centerY" secondItem="H8m-r4-eEC" secondAttribute="centerY" id="wg8-V9-pBf"/>
<constraint firstItem="o2l-Ms-1mk" firstAttribute="leading" secondItem="YEr-eC-P6i" secondAttribute="trailing" id="wmR-MD-QeR"/>
<constraint firstAttribute="bottom" secondItem="ErD-Uv-aKj" secondAttribute="bottom" id="5rh-PH-WZl"/>
<constraint firstItem="ErD-Uv-aKj" firstAttribute="top" secondItem="Efo-Hk-7Hw" secondAttribute="top" id="TGa-7F-Fiw"/>
<constraint firstItem="ErD-Uv-aKj" firstAttribute="leading" secondItem="Efo-Hk-7Hw" secondAttribute="leading" id="fnH-V7-ocW"/>
<constraint firstAttribute="trailing" secondItem="ErD-Uv-aKj" secondAttribute="trailing" id="nlh-Ve-1Sm"/>
</constraints>
<size key="customSize" width="320" height="20"/>
<connections>
<outlet property="cellLabel" destination="YEr-eC-P6i" id="jii-8O-zLL"/>
<outlet property="incomingCallImageView" destination="H8m-r4-eEC" id="hVW-Ng-BnU"/>
<outlet property="messageBubbleContainerView" destination="ErD-Uv-aKj" id="WSq-Wd-61K"/>
<outlet property="outgoingCallImageView" destination="o2l-Ms-1mk" id="Q5m-uX-80H"/>
<outlet property="textView" destination="R85-fN-3GC" id="Hry-eB-f6P"/>
</connections>
<point key="canvasLocation" x="219" y="435"/>
</collectionViewCell>