Mail and Message invite flow

* Spruce up compose contact-picker
- Fix random sorting for contacts missing first or last name
- Add Avatar to contact picker
- de-dupe contacts

Better copy for INVALID_MESSAGE error.

// FREEBIE
This commit is contained in:
Michael Kirk 2016-11-18 17:11:56 -05:00
parent bed5250397
commit 06ca3c9290
63 changed files with 610 additions and 154 deletions

View File

@ -26,6 +26,7 @@
453D28B71D32BA5F00D523F0 /* OWSDisplayedMessage.m in Sources */ = {isa = PBXBuildFile; fileRef = 453D28B61D32BA5F00D523F0 /* OWSDisplayedMessage.m */; };
453D28BA1D332DB100D523F0 /* OWSMessagesBubblesSizeCalculator.m in Sources */ = {isa = PBXBuildFile; fileRef = 453D28B91D332DB100D523F0 /* OWSMessagesBubblesSizeCalculator.m */; };
453D28BB1D332DB100D523F0 /* OWSMessagesBubblesSizeCalculator.m in Sources */ = {isa = PBXBuildFile; fileRef = 453D28B91D332DB100D523F0 /* OWSMessagesBubblesSizeCalculator.m */; };
45514DE21DDFA183003EFF90 /* InviteFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45514DE11DDFA183003EFF90 /* InviteFlow.swift */; };
45666EC61D99483D008FE134 /* OWSAvatarBuilder.m in Sources */ = {isa = PBXBuildFile; fileRef = 45666EC51D99483D008FE134 /* OWSAvatarBuilder.m */; };
45666EC91D994C0D008FE134 /* OWSGroupAvatarBuilder.m in Sources */ = {isa = PBXBuildFile; fileRef = 45666EC81D994C0D008FE134 /* OWSGroupAvatarBuilder.m */; };
45666F561D9B2827008FE134 /* OWSScrubbingLogFormatter.m in Sources */ = {isa = PBXBuildFile; fileRef = 45666F551D9B2827008FE134 /* OWSScrubbingLogFormatter.m */; };
@ -565,6 +566,7 @@
453D28B81D332DB100D523F0 /* OWSMessagesBubblesSizeCalculator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSMessagesBubblesSizeCalculator.h; sourceTree = "<group>"; };
453D28B91D332DB100D523F0 /* OWSMessagesBubblesSizeCalculator.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSMessagesBubblesSizeCalculator.m; sourceTree = "<group>"; };
454B35071D08EED80026D658 /* mk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = mk; path = translations/mk.lproj/Localizable.strings; sourceTree = "<group>"; };
45514DE11DDFA183003EFF90 /* InviteFlow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InviteFlow.swift; sourceTree = "<group>"; };
45666EC41D99483D008FE134 /* OWSAvatarBuilder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSAvatarBuilder.h; sourceTree = "<group>"; };
45666EC51D99483D008FE134 /* OWSAvatarBuilder.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSAvatarBuilder.m; sourceTree = "<group>"; };
45666EC71D994C0D008FE134 /* OWSGroupAvatarBuilder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSGroupAvatarBuilder.h; sourceTree = "<group>"; };
@ -1824,6 +1826,7 @@
76EB050C18170B33006006FC /* InCallViewController.m */,
458E382F1D6682450094BD24 /* OWSQRCodeScanningViewController.h */,
458E38301D6682450094BD24 /* OWSQRCodeScanningViewController.m */,
45514DE11DDFA183003EFF90 /* InviteFlow.swift */,
);
name = "View Controllers";
path = "view controllers";
@ -2834,6 +2837,7 @@
FCB11D931A12A4AA002F93FB /* FullImageViewController.m in Sources */,
B60C16651988999D00E97A6C /* VersionMigrations.m in Sources */,
B97940271832BD2400BD66CB /* UIUtil.m in Sources */,
45514DE21DDFA183003EFF90 /* InviteFlow.swift in Sources */,
4CE0E3771B954546007210CF /* TSAnimatedAdapter.m in Sources */,
76EB05BE18170B33006006FC /* ConfirmPacket.m in Sources */,
4531C9C41DD8E6D800F08304 /* JSQMessagesCollectionViewCell+OWS.m in Sources */,

View File

@ -0,0 +1,23 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "logo_with_background.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "logo_with_background@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "logo_with_background@3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

@ -38,7 +38,7 @@
</dict>
</array>
<key>CFBundleVersion</key>
<string>2.6.6.0</string>
<string>2.6.6.1</string>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>LOGS_EMAIL</key>

View File

@ -10,6 +10,10 @@ NS_ASSUME_NONNULL_BEGIN
@interface OWSContactAvatarBuilder : OWSAvatarBuilder
- (instancetype)initWithContactId:(NSString *)contactId
name:(NSString *)name
contactsManager:(OWSContactsManager *)contactsManager;
- (instancetype)initWithThread:(TSContactThread *)thread contactsManager:(OWSContactsManager *)contactsManager;
@end

View File

@ -22,20 +22,28 @@ NS_ASSUME_NONNULL_BEGIN
@implementation OWSContactAvatarBuilder
- (instancetype)initWithThread:(TSContactThread *)thread contactsManager:(OWSContactsManager *)contactsManager
- (instancetype)initWithContactId:(NSString *)contactId
name:(NSString *)name
contactsManager:(OWSContactsManager *)contactsManager
{
self = [super init];
if (!self) {
return self;
}
_signalId = thread.contactIdentifier;
_contactName = thread.name;
_signalId = contactId;
_contactName = name;
_contactsManager = contactsManager;
return self;
}
- (instancetype)initWithThread:(TSContactThread *)thread contactsManager:(OWSContactsManager *)contactsManager
{
return [self initWithContactId:thread.contactIdentifier name:thread.name contactsManager:contactsManager];
}
- (nullable UIImage *)buildSavedImage
{
return [self.contactsManager imageForPhoneIdentifier:self.signalId];
@ -43,6 +51,11 @@ NS_ASSUME_NONNULL_BEGIN
- (UIImage *)buildDefaultImage
{
UIImage *cachedAvatar = [self.contactsManager.avatarCache objectForKey:self.signalId];
if (cachedAvatar) {
return cachedAvatar;
}
NSMutableString *initials = [NSMutableString string];
if (self.contactName.length > 0) {
@ -61,11 +74,14 @@ NS_ASSUME_NONNULL_BEGIN
UIColor *backgroundColor = [UIColor backgroundColorForContact:self.signalId];
return [[JSQMessagesAvatarImageFactory avatarImageWithUserInitials:initials
backgroundColor:backgroundColor
textColor:[UIColor whiteColor]
font:[UIFont ows_boldFontWithSize:36.0]
diameter:100] avatarImage];
UIImage *image = [[JSQMessagesAvatarImageFactory avatarImageWithUserInitials:initials
backgroundColor:backgroundColor
textColor:[UIColor whiteColor]
font:[UIFont ows_boldFontWithSize:36.0]
diameter:100] avatarImage];
[self.contactsManager.avatarCache setObject:image forKey:self.signalId];
return image;
}

View File

@ -7,6 +7,7 @@
#import "PropertyListPreferences.h"
#import "PushManager.h"
#import "RPAccountManager.h"
#import "UIUtil.h"
#import <SignalServiceKit/NSDate+millisecondTimeStamp.h>
#import <SignalServiceKit/OWSEndSessionMessage.h>
#import <SignalServiceKit/OWSError.h>

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="11542" systemVersion="16C41b" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" colorMatched="YES" initialViewController="tuk-0x-yCb">
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="11542" systemVersion="15G1108" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" colorMatched="YES" initialViewController="tuk-0x-yCb">
<device id="retina4_7" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
@ -1184,15 +1184,32 @@
</subviews>
</tableViewCellContentView>
</tableViewCell>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" textLabel="9FD-M0-WLe" style="IBUITableViewCellStyleDefault" id="a7a-qe-87x" userLabel="Invite Your Friends">
<rect key="frame" x="0.0" y="382" width="375" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="a7a-qe-87x" id="gcU-Ld-alv">
<rect key="frame" x="0.0" y="0.0" width="342" height="43"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Invite Your Friends" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="9FD-M0-WLe" userLabel="Invite Your Friends">
<rect key="frame" x="15" y="0.0" width="325" height="43"/>
<autoresizingMask key="autoresizingMask"/>
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
</subviews>
</tableViewCellContentView>
</tableViewCell>
</cells>
</tableViewSection>
<tableViewSection id="FdD-MV-PUQ">
<cells>
<tableViewCell autoresizesSubviews="NO" contentMode="scaleToFill" selectionStyle="default" indentationWidth="10" rowHeight="100" id="R82-FT-SEA" userLabel="Destroy Account Cell">
<rect key="frame" x="0.0" y="382" width="375" height="100"/>
<rect key="frame" x="0.0" y="426" width="375" height="100"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="R82-FT-SEA" id="Ok9-fE-WhB">
<rect key="frame" x="0.0" y="0.0" width="375" height="99"/>
<rect key="frame" x="0.0" y="0.0" width="375" height="99.5"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="4Mk-ly-6fq">
@ -1240,6 +1257,7 @@
<outlet property="aboutLabel" destination="qeN-f1-cIQ" id="kmc-iU-NK5"/>
<outlet property="advancedLabel" destination="dkL-Nz-E6H" id="HUw-SB-apQ"/>
<outlet property="destroyAccountButton" destination="4Mk-ly-6fq" id="6Xj-Rb-kfF"/>
<outlet property="inviteLabel" destination="9FD-M0-WLe" id="TfU-b4-OtN"/>
<outlet property="linkedDevicesLabel" destination="hrc-lZ-NeA" id="VkD-E5-2kW"/>
<outlet property="networkStatusHeader" destination="uNq-FV-lwt" id="vca-cC-nXG"/>
<outlet property="networkStatusLabel" destination="tg3-dQ-odw" id="l6J-8y-maW"/>
@ -1376,25 +1394,36 @@
<rect key="frame" x="0.0" y="22" width="375" height="48"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="Ld5-sX-pB8" id="EqP-87-4hZ">
<rect key="frame" x="0.0" y="0.0" width="375" height="47"/>
<rect key="frame" x="0.0" y="0.0" width="375" height="47.5"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="urb-Me-knG">
<rect key="frame" x="20" y="4" width="275" height="39.5"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="4zF-SU-q4z">
<rect key="frame" x="67.5" y="8" width="299.5" height="31.5"/>
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="11n-jp-FCg">
<rect key="frame" x="16" y="4" width="39.5" height="39.5"/>
<constraints>
<constraint firstAttribute="width" secondItem="11n-jp-FCg" secondAttribute="height" multiplier="1:1" id="CMp-Im-YMw"/>
</constraints>
</imageView>
</subviews>
<constraints>
<constraint firstAttribute="bottomMargin" secondItem="urb-Me-knG" secondAttribute="bottom" constant="-4" id="3fF-H3-Spz"/>
<constraint firstAttribute="trailingMargin" secondItem="urb-Me-knG" secondAttribute="trailing" constant="72" id="R9g-GE-nWU"/>
<constraint firstItem="urb-Me-knG" firstAttribute="leading" secondItem="EqP-87-4hZ" secondAttribute="leadingMargin" constant="12" id="Zjd-vI-V9V"/>
<constraint firstItem="urb-Me-knG" firstAttribute="top" secondItem="EqP-87-4hZ" secondAttribute="topMargin" constant="-4" id="iex-zH-iTD"/>
<constraint firstAttribute="trailingMargin" secondItem="4zF-SU-q4z" secondAttribute="trailing" id="0Af-5G-21G"/>
<constraint firstItem="11n-jp-FCg" firstAttribute="leading" secondItem="EqP-87-4hZ" secondAttribute="leadingMargin" constant="8" id="GYw-2Z-sxC"/>
<constraint firstAttribute="bottomMargin" secondItem="11n-jp-FCg" secondAttribute="bottom" constant="-4" id="XTB-Kg-dNU"/>
<constraint firstItem="4zF-SU-q4z" firstAttribute="leading" secondItem="11n-jp-FCg" secondAttribute="trailing" constant="12" id="ZiW-nb-biK"/>
<constraint firstItem="11n-jp-FCg" firstAttribute="top" secondItem="EqP-87-4hZ" secondAttribute="topMargin" constant="-4" id="pYu-uo-erC"/>
<constraint firstAttribute="bottomMargin" secondItem="4zF-SU-q4z" secondAttribute="bottom" id="qZb-xR-JoS"/>
<constraint firstItem="4zF-SU-q4z" firstAttribute="top" secondItem="EqP-87-4hZ" secondAttribute="topMargin" id="ruR-db-L1p"/>
</constraints>
</tableViewCellContentView>
<inset key="separatorInset" minX="15" minY="0.0" maxX="0.0" maxY="0.0"/>
<connections>
<outlet property="nameLabel" destination="urb-Me-knG" id="2h5-l1-QDQ"/>
<outlet property="avatarView" destination="11n-jp-FCg" id="OBx-gj-ljU"/>
<outlet property="nameLabel" destination="4zF-SU-q4z" id="ita-p1-Vod"/>
</connections>
</tableViewCell>
</prototypes>
@ -1405,8 +1434,7 @@
</tableView>
<navigationItem key="navigationItem" title="New Message" id="D6A-aV-gEq">
<barButtonItem key="backBarButtonItem" title=" " id="rvF-0w-DNd"/>
<barButtonItem key="leftBarButtonItem" image="btnCancel--white" id="bP2-Tc-byJ">
<inset key="imageInsets" minX="-10" minY="0.0" maxX="0.0" maxY="0.0"/>
<barButtonItem key="leftBarButtonItem" systemItem="stop" id="bP2-Tc-byJ">
<color key="tintColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<connections>
<action selector="closeAction:" destination="Qga-41-bw2" id="i8h-aS-Y3o"/>
@ -1420,8 +1448,29 @@
</connections>
</barButtonItem>
</navigationItem>
<connections>
<outlet property="inviteCell" destination="Pgm-Qb-oPu" id="Nf5-sS-IKL"/>
</connections>
</tableViewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="fUD-iU-Cax" userLabel="First Responder" sceneMemberID="firstResponder"/>
<tableViewCell contentMode="scaleToFill" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" reuseIdentifier="ContactTableInviteCell" textLabel="QGl-1D-W9D" rowHeight="48" style="IBUITableViewCellStyleDefault" id="Pgm-Qb-oPu" userLabel="Invite Cell">
<rect key="frame" x="0.0" y="0.0" width="375" height="48"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="Pgm-Qb-oPu" id="amG-75-RNT">
<rect key="frame" x="0.0" y="0.0" width="342" height="47"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Invite your friends!" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="QGl-1D-W9D">
<rect key="frame" x="15" y="0.0" width="325" height="47"/>
<autoresizingMask key="autoresizingMask"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" red="0.09412795243864841" green="0.43645224658557435" blue="0.71380208333333339" alpha="1" colorSpace="calibratedRGB"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<color key="tintColor" red="0.1135657504" green="0.4787300229" blue="0.89595204589999999" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</tableViewCellContentView>
</tableViewCell>
<searchDisplayController id="f1M-Dk-nMv"/>
</objects>
<point key="canvasLocation" x="-2357" y="-235"/>

View File

@ -18,6 +18,7 @@ typedef void (^ABReloadRequestCompletionBlock)(NSArray *contacts);
@interface OWSContactsManager : NSObject <ContactsManagerProtocol>
@property CNContactStore *contactStore;
@property NSCache<NSString *, UIImage *> *avatarCache;
- (ObservableValue *)getObservableContacts;

View File

@ -25,11 +25,15 @@ typedef BOOL (^ContactSearchBlock)(id, NSUInteger, BOOL *);
- (id)init {
self = [super init];
if (self) {
_life = [TOCCancelTokenSource new];
_observableContactsController = [ObservableValueController observableValueControllerWithInitialValue:nil];
_latestContactsById = @{};
if (!self) {
return self;
}
_life = [TOCCancelTokenSource new];
_observableContactsController = [ObservableValueController observableValueControllerWithInitialValue:nil];
_latestContactsById = @{};
_avatarCache = [NSCache new];
return self;
}
@ -67,11 +71,17 @@ void onAddressBookChanged(ABAddressBookRef notifyAddressBook, CFDictionaryRef in
void onAddressBookChanged(ABAddressBookRef notifyAddressBook, CFDictionaryRef info, void *context) {
OWSContactsManager *contactsManager = (__bridge OWSContactsManager *)context;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[contactsManager pullLatestAddressBook];
[contactsManager intersectContacts];
[contactsManager handleAddressBookChanged];
});
}
- (void)handleAddressBookChanged
{
[self pullLatestAddressBook];
[self intersectContacts];
[self.avatarCache removeAllObjects];
}
#pragma mark - Setup
- (void)setupAddressBook {
@ -81,8 +91,7 @@ void onAddressBookChanged(ABAddressBookRef notifyAddressBook, CFDictionaryRef in
ABAddressBookRef cfAddressBook = (__bridge ABAddressBookRef)addressBook;
ABAddressBookRegisterExternalChangeCallback(cfAddressBook, onAddressBookChanged, (__bridge void *)self);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self pullLatestAddressBook];
[self intersectContacts];
[self handleAddressBookChanged];
});
}];
});
@ -370,9 +379,13 @@ void onAddressBookChanged(ABAddressBookRef notifyAddressBook, CFDictionaryRef in
#pragma mark - Whisper User Management
- (NSArray *)getSignalUsersFromContactsArray:(NSArray *)contacts {
return [[contacts filter:^int(Contact *contact) {
return [contact isSignalContact];
}] sortedArrayUsingComparator:[[self class] contactComparator]];
NSMutableDictionary *signalContacts = [NSMutableDictionary new];
for (Contact *contact in contacts) {
if ([contact isSignalContact]) {
signalContacts[contact.textSecureIdentifiers.firstObject] = contact;
}
}
return [signalContacts.allValues sortedArrayUsingComparator:[[self class] contactComparator]];
}
+ (NSComparator)contactComparator {
@ -383,9 +396,33 @@ void onAddressBookChanged(ABAddressBookRef notifyAddressBook, CFDictionaryRef in
BOOL firstNameOrdering = ABPersonGetSortOrdering() == kABPersonCompositeNameFormatFirstNameFirst ? YES : NO;
if (firstNameOrdering) {
return [contact1.firstName caseInsensitiveCompare:contact2.firstName];
if (contact1.firstName) {
if (contact2.firstName) {
return [contact1.firstName caseInsensitiveCompare:contact2.firstName];
} else {
return [contact1.firstName caseInsensitiveCompare:contact2.lastName];
}
} else {
if (contact2.firstName) {
return [contact1.lastName caseInsensitiveCompare:contact2.firstName];
} else {
return [contact1.lastName caseInsensitiveCompare:contact2.lastName];
}
}
} else {
return [contact1.lastName caseInsensitiveCompare:contact2.lastName];
if (contact1.lastName) {
if (contact2.lastName) {
return [contact1.lastName caseInsensitiveCompare:contact2.lastName];
} else {
return [contact1.lastName caseInsensitiveCompare:contact2.firstName];
}
} else {
if (contact2.lastName) {
return [contact1.firstName caseInsensitiveCompare:contact2.lastName];
} else {
return [contact1.firstName caseInsensitiveCompare:contact2.firstName];
}
}
};
};
}

View File

@ -16,7 +16,7 @@ typedef void (^completionBlock)(void);
@interface UIUtil : NSObject
+ (void)applyRoundedBorderToImageView:(UIImageView *__strong *)imageView;
+ (void)applyRoundedBorderToImageView:(UIImageView *)imageView;
+ (void)removeRoundedBorderToImageView:(UIImageView *__strong *)imageView;
+ (completionBlock)modalCompletionBlock;

View File

@ -4,14 +4,14 @@
@implementation UIUtil
+ (void)applyRoundedBorderToImageView:(UIImageView *__strong *)imageView {
[[*imageView layer] setBorderWidth:CONTACT_PICTURE_VIEW_BORDER_WIDTH];
[[*imageView layer] setBorderColor:[[UIColor clearColor] CGColor]];
[[*imageView layer] setCornerRadius:CGRectGetWidth([*imageView frame]) / 2];
[[*imageView layer] setMasksToBounds:YES];
+ (void)applyRoundedBorderToImageView:(UIImageView *)imageView
{
imageView.layer.borderWidth = CONTACT_PICTURE_VIEW_BORDER_WIDTH;
imageView.layer.borderColor = [UIColor clearColor].CGColor;
imageView.layer.cornerRadius = CGRectGetWidth(imageView.frame) / 2;
imageView.layer.masksToBounds = YES;
}
+ (void)removeRoundedBorderToImageView:(UIImageView *__strong *)imageView {
[[*imageView layer] setBorderWidth:0];
[[*imageView layer] setCornerRadius:0];
@ -42,7 +42,7 @@
[[UIBarButtonItem appearanceWhenContainedIn:[UISearchBar class], nil] setTintColor:[UIColor ows_materialBlueColor]];
[[UISwitch appearance] setOnTintColor:[UIColor ows_materialBlueColor]];
[[UIToolbar appearance] setTintColor:[UIColor ows_materialBlueColor]];
[[UIBarButtonItem appearance] setTintColor:[UIColor whiteColor]];
@ -53,9 +53,6 @@
NSForegroundColorAttributeName : [UIColor whiteColor],
NSShadowAttributeName : shadow,
};
[[UISwitch appearance] setOnTintColor:[UIColor ows_materialBlueColor]];
[[UINavigationBar appearance] setTitleTextAttributes:navbarTitleTextAttributes];
}

View File

@ -22,6 +22,11 @@
@end
typedef NS_ENUM(NSUInteger, AboutTableViewControllerSection) {
AboutTableViewControllerSectionInformation,
AboutTableViewControllerSectionHelp
};
@implementation AboutTableViewController
- (instancetype)init {
@ -64,32 +69,19 @@
self.footerView.font = [UIFont ows_regularFontWithSize:15.0f];
self.footerView.numberOfLines = 2;
self.footerView.textAlignment = NSTextAlignmentCenter;
// Twitter Invite
self.twitterInviteCell = [[UITableViewCell alloc] init];
self.twitterInviteCell.textLabel.text = NSLocalizedString(@"SETTINGS_SHARE_INSTALL", @"");
UIImageView *twitterImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"twitter_logo"]];
[twitterImageView setFrame:CGRectMake(0, 0, 34, 34)];
twitterImageView.contentMode = UIViewContentModeScaleAspectFit;
self.twitterInviteCell.accessoryView = twitterImageView;
}
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 3;
return 2;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
switch (section) {
case 0:
case AboutTableViewControllerSectionInformation:
return 1;
case 1:
return 1;
case 2:
case AboutTableViewControllerSectionHelp:
return 1;
default:
return 0;
@ -98,13 +90,10 @@
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
switch (section) {
case 0:
case AboutTableViewControllerSectionInformation:
return NSLocalizedString(@"SETTINGS_INFORMATION_HEADER", @"");
case 1:
return NSLocalizedString(@"SETTINGS_INVITE_HEADER", @"");
case 2:
case AboutTableViewControllerSectionHelp:
return NSLocalizedString(@"SETTINGS_HELP_HEADER", @"");
default:
return nil;
}
@ -112,11 +101,9 @@
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
switch (indexPath.section) {
case 0:
case AboutTableViewControllerSectionInformation:
return self.versionCell;
case 1:
return self.twitterInviteCell;
case 2:
case AboutTableViewControllerSectionHelp:
return self.supportCell;
}
@ -127,10 +114,7 @@
[tableView deselectRowAtIndexPath:indexPath animated:YES];
switch (indexPath.section) {
case 1:
[self tappedInviteTwitter];
break;
case 2:
case AboutTableViewControllerSectionHelp:
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:@"http://support.whispersystems.org"]];
break;
@ -140,25 +124,11 @@
}
- (UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section {
return section == 2 ? self.footerView : nil;
return section == AboutTableViewControllerSectionHelp ? self.footerView : nil;
}
- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section {
return section == 2 ? 60.0f : 0;
}
- (void)tappedInviteTwitter {
if ([SLComposeViewController isAvailableForServiceType:SLServiceTypeTwitter]) {
SLComposeViewController *tweetSheet =
[SLComposeViewController composeViewControllerForServiceType:SLServiceTypeTwitter];
NSString *tweetString = [NSString stringWithFormat:NSLocalizedString(@"SETTINGS_INVITE_TWITTER_TEXT", @"")];
[tweetSheet setInitialText:tweetString];
[tweetSheet addURL:[NSURL URLWithString:@"https://whispersystems.org/signal/install/"]];
tweetSheet.completionHandler = ^(SLComposeViewControllerResult result) {
};
[self presentViewController:tweetSheet animated:YES completion:[UIUtil modalCompletionBlock]];
}
return section == AboutTableViewControllerSectionHelp ? 60.0f : 0;
}
@end

View File

@ -120,7 +120,7 @@
if (_potentiallyKnownContact) {
if (_potentiallyKnownContact.image) {
[UIUtil applyRoundedBorderToImageView:&_contactImageView];
[UIUtil applyRoundedBorderToImageView:_contactImageView];
}
_nameLabel.text = _potentiallyKnownContact.fullName;

View File

@ -70,7 +70,7 @@ NS_ASSUME_NONNULL_BEGIN
self.snippetLabel.text = snippetLabel;
self.timeLabel.attributedText = attributedDate;
self.contactPictureView.image = avatar;
[UIUtil applyRoundedBorderToImageView:&_contactPictureView];
[UIUtil applyRoundedBorderToImageView:_contactPictureView];
self.separatorInset = UIEdgeInsetsMake(0, _contactPictureView.frame.size.width * 1.5f, 0, 0);

View File

@ -0,0 +1,257 @@
// Created by Michael Kirk on 11/18/16.
// Copyright © 2016 Open Whisper Systems. All rights reserved.
import Foundation
import Social
import ContactsUI
import MessageUI
@objc(OWSInviteFlow)
class InviteFlow: NSObject, CNContactPickerDelegate, MFMessageComposeViewControllerDelegate, MFMailComposeViewControllerDelegate {
enum Channel {
case message, mail, twitter
}
let TAG = "[ShareActions]"
// redirects to either the ios appstore or google play store depending on the user agent.
// Appropriate to send to mobile browsers, but for email we want to expose both directs link
// since an e.g. mac user, might have an Android, or Windows user might have an iPhone.
let mobileInstallUrl = "https://signal.org/install/"
let iOSInstallUrl = "https://itunes.apple.com/us/app/signal-private-messenger/id874139669"
let androidInstallUrl = "https://play.google.com/store/apps/details?id=org.thoughtcrime.securesms"
let homepageUrl = "https://whispersystems.org"
let actionSheetController: UIAlertController
let presentingViewController: UIViewController
var channel: Channel?
required init(presentingViewController: UIViewController) {
self.presentingViewController = presentingViewController
actionSheetController = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
super.init()
actionSheetController.addAction(dismissAction())
if #available(iOS 9.0, *) {
if let messageAction = messageAction() {
actionSheetController.addAction(messageAction)
}
if let mailAction = mailAction() {
actionSheetController.addAction(mailAction)
}
}
if let tweetAction = tweetAction() {
actionSheetController.addAction(tweetAction)
}
}
// MARK: Twitter
func canTweet() -> Bool {
return SLComposeViewController.isAvailable(forServiceType: SLServiceTypeTwitter)
}
func tweetAction() -> UIAlertAction? {
guard canTweet() else {
Logger.info("\(TAG) Twitter not supported.")
return nil
}
guard let twitterViewController = SLComposeViewController(forServiceType: SLServiceTypeTwitter) else {
Logger.error("\(TAG) unable to build twitter controller.")
return nil
}
let tweetString = NSLocalizedString("SETTINGS_INVITE_TWITTER_TEXT", comment:"content of tweet when inviting via twitter")
twitterViewController.setInitialText(tweetString)
let tweetUrl = URL(string: mobileInstallUrl)
twitterViewController.add(tweetUrl)
twitterViewController.add(#imageLiteral(resourceName: "logo_with_background"))
let tweetTitle = NSLocalizedString("SHARE_ACTION_TWEET", comment:"action sheet item")
return UIAlertAction(title: tweetTitle, style: .default) { action in
Logger.debug("\(self.TAG) Chose tweet")
self.presentingViewController.present(twitterViewController, animated: true, completion: nil)
}
}
func dismissAction() -> UIAlertAction {
return UIAlertAction(title: NSLocalizedString("DISMISS_BUTTON_TEXT", comment:""), style: .cancel)
}
// MARK: ContactPickerDelegate
/*!
* @abstract Invoked when the picker is closed.
* @discussion The picker will be dismissed automatically after a contact or property is picked.
*/
@available(iOS 9.0, *)
func contactPickerDidCancel(_ picker: CNContactPickerViewController) {
Logger.debug("\(TAG) pickerDidCancel")
}
/*!
* @abstract Plural delegate methods.
* @discussion These delegate methods will be invoked when the user is done selecting multiple contacts or properties.
* Implementing one of these methods will configure the picker for multi-selection.
*/
@available(iOS 9.0, *)
func contactPicker(_ picker: CNContactPickerViewController, didSelect contacts: [CNContact]) {
Logger.debug("\(TAG) didSelectContacts:\(contacts)")
guard let inviteChannel = channel else {
Logger.error("\(TAG) unexpected nil channel after returning from contact picker.")
return
}
switch inviteChannel {
case .message:
sendSMSTo(contacts: contacts)
case .mail:
sendMailTo(contacts:contacts)
default:
Logger.error("\(TAG) unexpected channel after returning from contact picker: \(inviteChannel)")
}
}
@available(iOS 9.0, *)
func contactPicker(_ picker: CNContactPickerViewController, didSelectContactProperties contactProperties: [CNContactProperty]) {
Logger.debug("\(TAG) didSelectContactProperties:\(contactProperties)")
}
// MARK: SMS
@available(iOS 9.0, *)
func messageAction() -> UIAlertAction? {
guard MFMessageComposeViewController.canSendText() else {
Logger.info("\(TAG) Device cannot send text")
return nil
}
let messageTitle = NSLocalizedString("SHARE_ACTION_MESSAGE", comment: "action sheet item to open native messages app")
return UIAlertAction(title: messageTitle, style: .default) { action in
Logger.debug("\(self.TAG) Chose message.")
self.channel = .message
let picker = CNContactPickerViewController()
picker.predicateForSelectionOfContact = NSPredicate(value: false)
picker.predicateForEnablingContact = NSPredicate(format: "phoneNumbers.@count > 0")
picker.delegate = self
self.presentingViewController.present(picker, animated: true, completion: nil)
}
}
@available(iOS 9.0, *)
func sendSMSTo(contacts: [CNContact]) {
self.presentingViewController.dismiss(animated: true) {
if #available(iOS 10.0, *) {
// iOS10 message compose view doesn't respect some system appearence attributes.
// Specifically, the title is white, but the navbar is gray.
// So, we have to set system appearence before init'ing the message compose view controller in order
// to make its colors legible.
// Then we have to be sure to set it back in the ComposeViewControllerDelegate callback.
UIUtil.applyDefaultSystemAppearence()
}
let messageComposeViewController = MFMessageComposeViewController()
messageComposeViewController.messageComposeDelegate = self
messageComposeViewController.recipients = contacts.map { $0.phoneNumbers.first }.filter { $0 != nil }.map { $0!.value.stringValue }
let inviteText = NSLocalizedString("SMS_INVITE_BODY", comment:"body sent to contacts when inviting to Install Signal")
messageComposeViewController.body = inviteText.appending(" \(self.mobileInstallUrl)")
self.presentingViewController.navigationController?.present(messageComposeViewController, animated:true)
}
}
// MARK: MessageComposeViewControllerDelegate
func messageComposeViewController(_ controller: MFMessageComposeViewController, didFinishWith result: MessageComposeResult) {
// Revert system styling applied to make messaging app legible on iOS10.
UIUtil.applySignalAppearence()
self.presentingViewController.dismiss(animated: true, completion: nil)
switch result {
case .failed:
let warning = UIAlertController(title: nil, message: NSLocalizedString("SEND_INVITE_FAILURE", comment:"Alert body after invite failed"), preferredStyle: .alert)
warning.addAction(UIAlertAction(title: NSLocalizedString("DISMISS_BUTTON_TEXT", comment:""), style: .default, handler: nil))
self.presentingViewController.present(warning, animated: true, completion: nil)
case .sent:
Logger.debug("\(self.TAG) user successfully invited their friends via SMS.")
case .cancelled:
Logger.debug("\(self.TAG) user cancelled message invite")
}
}
// MARK: Mail
@available(iOS 9.0, *)
func mailAction() -> UIAlertAction? {
guard MFMailComposeViewController.canSendMail() else {
Logger.info("\(TAG) Device cannot send mail")
return nil
}
let mailActionTitle = NSLocalizedString("SHARE_ACTION_MAIL", comment: "action sheet item to open native mail app")
return UIAlertAction(title: mailActionTitle, style: .default) { action in
Logger.debug("\(self.TAG) Chose mail.")
self.channel = .mail
let picker = CNContactPickerViewController()
picker.predicateForSelectionOfContact = NSPredicate(value: false)
picker.predicateForEnablingContact = NSPredicate(format: "emailAddresses.@count > 0")
picker.delegate = self
self.presentingViewController.present(picker, animated: true)
}
}
@available(iOS 9.0, *)
func sendMailTo(contacts: [CNContact]) {
let mailComposeViewController = MFMailComposeViewController()
mailComposeViewController.mailComposeDelegate = self
let recipients: [String] = contacts.map { $0.emailAddresses.first }.filter { $0 != nil }.map { $0!.value as String }
mailComposeViewController.setBccRecipients(recipients)
let subject = NSLocalizedString("EMAIL_INVITE_SUBJECT", comment:"subject of email sent to contacts when inviting to install Signal")
let bodyFormat = NSLocalizedString("EMAIL_INVITE_BODY", comment:"body of email sent to contacts when inviting to install Signal. Embeds {{link to install Signal-iOS}}, {{link to install Signal-Android}}, and {{link to WhisperSystems home page}}")
let body = String.init(format: bodyFormat, iOSInstallUrl, androidInstallUrl, homepageUrl)
mailComposeViewController.setSubject(subject)
mailComposeViewController.setMessageBody(body, isHTML: false)
self.presentingViewController.dismiss(animated: true) {
self.presentingViewController.navigationController?.present(mailComposeViewController, animated:true) {
UIUtil.applySignalAppearence();
}
}
}
// MARK: MailComposeViewControllerDelegate
func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) {
self.presentingViewController.dismiss(animated: true, completion: nil)
switch result {
case .failed:
let warning = UIAlertController(title: nil, message: NSLocalizedString("SEND_INVITE_FAILURE", comment:"Alert body after invite failed"), preferredStyle: .alert)
warning.addAction(UIAlertAction(title: NSLocalizedString("DISMISS_BUTTON_TEXT", comment:""), style: .default, handler: nil))
self.presentingViewController.present(warning, animated: true, completion: nil)
case .sent:
Logger.debug("\(self.TAG) user successfully invited their friends via mail.")
case .saved:
Logger.debug("\(self.TAG) user saved mail invite.")
case .cancelled:
Logger.debug("\(self.TAG) user cancelled mail invite.")
}
}
}

View File

@ -14,6 +14,10 @@
#import "Contact.h"
#import "LocalizableText.h"
NS_ASSUME_NONNULL_BEGIN
@interface MessageComposeTableViewController : UITableViewController
@end
NS_ASSUME_NONNULL_END

View File

@ -12,15 +12,20 @@
#import "ContactTableViewCell.h"
#import "ContactsUpdater.h"
#import "OWSContactsSearcher.h"
#import "Environment.h"
#import "OWSContactsSearcher.h"
#import "Signal-Swift.h"
#import "UIColor+OWS.h"
#import "UIUtil.h"
NS_ASSUME_NONNULL_BEGIN
@interface MessageComposeTableViewController () <UISearchBarDelegate,
UISearchResultsUpdating,
MFMessageComposeViewControllerDelegate>
@property (nonatomic, strong) IBOutlet UITableViewCell *inviteCell;
@property (nonatomic) UIButton *sendTextButton;
@property (nonatomic, strong) UISearchController *searchController;
@property (nonatomic, strong) UIActivityIndicatorView *activityIndicator;
@ -30,21 +35,54 @@
@property (nonatomic) NSString *currentSearchTerm;
@property (copy) NSArray<Contact *> *contacts;
@property (copy) NSArray<Contact *> *searchResults;
@property (nonatomic, readonly) OWSContactsManager *contactsManager;
@end
NSInteger const MessageComposeTableViewControllerSectionInvite = 0;
NSInteger const MessageComposeTableViewControllerSectionContacts = 1;
NSString *const MessageComposeTableViewControllerCellInvite = @"ContactTableInviteCell";
NSString *const MessageComposeTableViewControllerCellContact = @"ContactTableViewCell";
@implementation MessageComposeTableViewController
- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (!self) {
return self;
}
_contactsManager = [Environment getCurrent].contactsManager;
return self;
}
- (instancetype)init
{
self = [super init];
if (!self) {
return self;
}
_contactsManager = [Environment getCurrent].contactsManager;
return self;
}
- (void)viewDidLoad {
[super viewDidLoad];
[self.navigationController.navigationBar setTranslucent:NO];
self.contacts = [[[Environment getCurrent] contactsManager] signalContacts];
self.contacts = self.contactsManager.signalContacts;
self.searchResults = self.contacts;
[self initializeSearch];
self.searchController.searchBar.hidden = NO;
self.searchController.searchBar.backgroundColor = [UIColor whiteColor];
self.inviteCell.textLabel.text = NSLocalizedString(
@"INVITE_FRIENDS_CONTACT_TABLE_BUTTON", @"Text for button at the top of the contact picker");
self.tableView.tableFooterView = [[UIView alloc] initWithFrame:CGRectZero];
[self createLoadingAndBackgroundViews];
@ -244,7 +282,7 @@
- (void)updateSearchResultsForSearchController:(UISearchController *)searchController {
NSString *searchString = [self.searchController.searchBar text];
[self filterContentForSearchText:searchString scope:nil];
[self filterContentForSearchText:searchString];
[self.tableView reloadData];
}
@ -263,7 +301,8 @@
#pragma mark - Filter
- (void)filterContentForSearchText:(NSString *)searchText scope:(NSString *)scope {
- (void)filterContentForSearchText:(NSString *)searchText
{
OWSContactsSearcher *contactsSearcher = [[OWSContactsSearcher alloc] initWithContacts: self.contacts];
self.searchResults = [contactsSearcher filterWithString:searchText];
@ -355,7 +394,7 @@
case MessageComposeResultFailed: {
UIAlertView *warningAlert =
[[UIAlertView alloc] initWithTitle:@""
message:NSLocalizedString(@"SEND_SMS_INVITE_FAILURE", @"")
message:NSLocalizedString(@"SEND_INVITE_FAILURE", @"")
delegate:nil
cancelButtonTitle:NSLocalizedString(@"OK", @"")
otherButtonTitles:nil];
@ -369,7 +408,7 @@
}];
UIAlertView *successAlert =
[[UIAlertView alloc] initWithTitle:@""
message:NSLocalizedString(@"SEND_SMS_INVITE_SUCCESS", @"")
message:NSLocalizedString(@"SEND_INVITE_SUCCESS", @"Alert body after invite succeeded")
delegate:nil
cancelButtonTitle:NSLocalizedString(@"OK", @"")
otherButtonTitles:nil];
@ -386,34 +425,33 @@
#pragma mark - Table View Data Source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
return 2;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
if (self.searchController.active) {
return (NSInteger)[self.searchResults count];
if (section == MessageComposeTableViewControllerSectionInvite) {
return 1;
} else {
return (NSInteger)[self.contacts count];
if (self.searchController.active) {
return (NSInteger)[self.searchResults count];
} else {
return (NSInteger)[self.contacts count];
}
}
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
if (indexPath.section == MessageComposeTableViewControllerSectionInvite) {
return self.inviteCell;
} else {
ContactTableViewCell *cell = (ContactTableViewCell *)[tableView
dequeueReusableCellWithIdentifier:MessageComposeTableViewControllerCellContact];
- (ContactTableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
ContactTableViewCell *cell =
(ContactTableViewCell *)[tableView dequeueReusableCellWithIdentifier:@"ContactTableViewCell"];
[cell configureWithContact:[self contactForIndexPath:indexPath] contactsManager:self.contactsManager];
if (cell == nil) {
cell = [[ContactTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:@"ContactTableViewCell"];
return cell;
}
cell.shouldShowContactButtons = NO;
[cell configureWithContact:[self contactForIndexPath:indexPath]];
tableView.tableFooterView = [[UIView alloc] initWithFrame:CGRectZero];
return cell;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
@ -423,17 +461,31 @@
#pragma mark - Table View delegate
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
NSString *identifier = [[[self contactForIndexPath:indexPath] textSecureIdentifiers] firstObject];
[self dismissViewControllerAnimated:YES
completion:^() {
[Environment messageIdentifier:identifier withCompose:YES];
if (indexPath.section == MessageComposeTableViewControllerSectionInvite) {
void (^showInvite)() = ^{
OWSInviteFlow *inviteFlow = [[OWSInviteFlow alloc] initWithPresentingViewController:self];
[self presentViewController:inviteFlow.actionSheetController
animated:YES
completion:^{
[self.tableView deselectRowAtIndexPath:indexPath animated:YES];
}];
}
};
- (void)tableView:(UITableView *)tableView didDeselectRowAtIndexPath:(NSIndexPath *)indexPath {
ContactTableViewCell *cell = (ContactTableViewCell *)[tableView cellForRowAtIndexPath:indexPath];
cell.accessoryType = UITableViewCellAccessoryNone;
if (self.presentedViewController) {
// If search controller is active, dismiss it first.
[self dismissViewControllerAnimated:YES completion:showInvite];
} else {
showInvite();
}
} else {
NSString *identifier = [[[self contactForIndexPath:indexPath] textSecureIdentifiers] firstObject];
[self dismissViewControllerAnimated:YES
completion:^() {
[Environment messageIdentifier:identifier withCompose:YES];
}];
}
}
- (Contact *)contactForIndexPath:(NSIndexPath *)indexPath {
@ -462,15 +514,14 @@
}
- (void)refreshContacts {
[[ContactsUpdater sharedUpdater]
updateSignalContactIntersectionWithABContacts:[Environment getCurrent].contactsManager.allContacts
[[ContactsUpdater sharedUpdater] updateSignalContactIntersectionWithABContacts:self.contactsManager.allContacts
success:^{
self.contacts = [[[Environment getCurrent] contactsManager] signalContacts];
dispatch_async(dispatch_get_main_queue(), ^{
[self updateSearchResultsForSearchController:self.searchController];
[self.tableView reloadData];
[self updateAfterRefreshTry];
});
self.contacts = self.contactsManager.signalContacts;
dispatch_async(dispatch_get_main_queue(), ^{
[self updateSearchResultsForSearchController:self.searchController];
[self.tableView reloadData];
[self updateAfterRefreshTry];
});
}
failure:^(NSError *error) {
dispatch_async(dispatch_get_main_queue(), ^{
@ -492,8 +543,8 @@
#pragma mark - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(nullable id)sender
{
self.searchController.active = NO;
}
@ -510,3 +561,5 @@
}
@end
NS_ASSUME_NONNULL_END

View File

@ -19,6 +19,7 @@
@property (strong, nonatomic) IBOutlet UILabel *linkedDevicesLabel;
@property (strong, nonatomic) IBOutlet UILabel *advancedLabel;
@property (strong, nonatomic) IBOutlet UILabel *aboutLabel;
@property (strong, nonatomic) IBOutlet UILabel *inviteLabel;
@property (strong, nonatomic) IBOutlet UIButton *destroyAccountButton;
- (IBAction)unregisterUser:(id)sender;

View File

@ -17,13 +17,13 @@
#import "TSSocketManager.h"
#import "OWSContactsManager.h"
#import "AboutTableViewController.h"
#import "AdvancedSettingsTableViewController.h"
#import "NotificationSettingsViewController.h"
#import "OWSContactsManager.h"
#import "PrivacySettingsTableViewController.h"
#import "PushManager.h"
#import "Signal-Swift.h"
#define kProfileCellHeight 87.0f
#define kStandardCellHeight 44.0f
@ -35,12 +35,13 @@
#define kNotificationRow 1
#define kAdvancedRow 3
#define kAboutRow 4
#define kInviteRow 5
#define kNetworkRow 0
#define kUnregisterRow 0
typedef enum {
kRegisteredRows = 1,
kGeneralRows = 5,
kGeneralRows = 6,
kNetworkStatusRows = 1,
kUnregisterRows = 1,
} kRowsForSection;
@ -81,6 +82,8 @@ typedef enum {
self.notificationsLabel.text = NSLocalizedString(@"SETTINGS_NOTIFICATIONS", nil);
self.linkedDevicesLabel.text
= NSLocalizedString(@"LINKED_DEVICES_TITLE", @"Menu item and navbar title for the device manager");
self.inviteLabel.text = NSLocalizedString(@"SETTINGS_INVITE_TITLE", @"Settings table view cell label");
[self.destroyAccountButton setTitle:NSLocalizedString(@"SETTINGS_DELETE_ACCOUNT_BUTTON", @"")
forState:UIControlStateNormal];
}
@ -153,7 +156,16 @@ typedef enum {
[self.navigationController pushViewController:vc animated:YES];
break;
}
case kInviteRow: {
OWSInviteFlow *inviteFlow = [[OWSInviteFlow alloc] initWithPresentingViewController:self];
[self presentViewController:inviteFlow.actionSheetController
animated:YES
completion:^{
[self.tableView deselectRowAtIndexPath:indexPath animated:YES];
}];
}
default:
DDLogError(@"%@ Unhandled row selected at index path: %@", self.tag, indexPath);
break;
}
@ -260,4 +272,16 @@ typedef enum {
self.networkStatusLabel.textColor = [UIColor ows_yellowColor];
}
#pragma mark - Logging
+ (NSString *)tag
{
return [NSString stringWithFormat:@"[%@]", self.class];
}
- (NSString *)tag
{
return self.class.tag;
}
@end

View File

@ -7,11 +7,14 @@
*
*/
NS_ASSUME_NONNULL_BEGIN
@class OWSContactsManager;
@interface ContactTableViewCell : UITableViewCell
@property (nonatomic, strong) IBOutlet UILabel *nameLabel;
@property BOOL shouldShowContactButtons;
- (void)configureWithContact:(Contact *)contact;
- (void)configureWithContact:(Contact *)contact contactsManager:(OWSContactsManager *)contactsManager;
@end
NS_ASSUME_NONNULL_END

View File

@ -1,12 +1,14 @@
#import "ContactTableViewCell.h"
#import "UIUtil.h"
#import "Environment.h"
#import "OWSContactAvatarBuilder.h"
#import "OWSContactsManager.h"
#import "PhoneManager.h"
#import "UIUtil.h"
@interface ContactTableViewCell ()
@property (strong, nonatomic) Contact *associatedContact;
@property (nonatomic) IBOutlet UILabel *nameLabel;
@property (nonatomic) IBOutlet UIImageView *avatarView;
@end
@ -21,9 +23,19 @@
return NSStringFromClass(self.class);
}
- (void)configureWithContact:(Contact *)contact {
self.associatedContact = contact;
- (void)configureWithContact:(Contact *)contact contactsManager:(OWSContactsManager *)contactsManager
{
self.nameLabel.attributedText = [self attributedStringForContact:contact];
self.avatarView.image =
[[[OWSContactAvatarBuilder alloc] initWithContactId:contact.textSecureIdentifiers.firstObject
name:contact.fullName
contactsManager:contactsManager] build];
}
- (void)layoutSubviews
{
[super layoutSubviews];
[UIUtil applyRoundedBorderToImageView:self.avatarView];
}
- (NSAttributedString *)attributedStringForContact:(Contact *)contact {
@ -34,11 +46,11 @@
UIFont *lastNameFont;
if (ABPersonGetSortOrdering() == kABPersonCompositeNameFormatFirstNameFirst) {
firstNameFont = [UIFont ows_mediumFontWithSize:_nameLabel.font.pointSize];
lastNameFont = [UIFont ows_regularFontWithSize:_nameLabel.font.pointSize];
firstNameFont = [UIFont ows_mediumFontWithSize:self.nameLabel.font.pointSize];
lastNameFont = [UIFont ows_regularFontWithSize:self.nameLabel.font.pointSize];
} else {
firstNameFont = [UIFont ows_regularFontWithSize:_nameLabel.font.pointSize];
lastNameFont = [UIFont ows_mediumFontWithSize:_nameLabel.font.pointSize];
firstNameFont = [UIFont ows_regularFontWithSize:self.nameLabel.font.pointSize];
lastNameFont = [UIFont ows_mediumFontWithSize:self.nameLabel.font.pointSize];
}
[fullNameAttributedString addAttribute:NSFontAttributeName
value:firstNameFont