Merge tag '2.28.0.15'

pull/1/head
Michael Kirk 6 years ago
commit 78ad597e44

@ -427,6 +427,7 @@
45FBC5C81DF8575700E9B410 /* CallKitCallManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45FBC59A1DF8575700E9B410 /* CallKitCallManager.swift */; };
45FBC5D11DF8592E00E9B410 /* SignalCall.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45FBC5D01DF8592E00E9B410 /* SignalCall.swift */; };
4AC4EA13C8A444455DAB351F /* Pods_SignalMessaging.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 264242150E87D10A357DB07B /* Pods_SignalMessaging.framework */; };
4C090A1B210FD9C7001FD7F9 /* HapticFeedback.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C090A1A210FD9C7001FD7F9 /* HapticFeedback.swift */; };
4C11AA5020FD59C700351FBD /* MessageStatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C11AA4F20FD59C700351FBD /* MessageStatusView.swift */; };
4C13C9F620E57BA30089A98B /* ColorPickerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C13C9F520E57BA30089A98B /* ColorPickerViewController.swift */; };
4C20B2B720CA0034001BAC90 /* ThreadViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4542DF51208B82E9007B4E76 /* ThreadViewModel.swift */; };
@ -1110,6 +1111,7 @@
45FBC59A1DF8575700E9B410 /* CallKitCallManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CallKitCallManager.swift; sourceTree = "<group>"; };
45FBC5D01DF8592E00E9B410 /* SignalCall.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SignalCall.swift; sourceTree = "<group>"; };
45FDA43420A4D22700396358 /* OWSNavigationBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OWSNavigationBar.swift; sourceTree = "<group>"; };
4C090A1A210FD9C7001FD7F9 /* HapticFeedback.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = HapticFeedback.swift; path = UserInterface/HapticFeedback.swift; sourceTree = "<group>"; };
4C11AA4F20FD59C700351FBD /* MessageStatusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageStatusView.swift; sourceTree = "<group>"; };
4C13C9F520E57BA30089A98B /* ColorPickerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorPickerViewController.swift; sourceTree = "<group>"; };
4C20B2B820CA10DE001BAC90 /* ConversationSearchViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationSearchViewController.swift; sourceTree = "<group>"; };
@ -1932,6 +1934,7 @@
isa = PBXGroup;
children = (
450DF2071E0DD29E003D14BE /* Notifications */,
4C090A1A210FD9C7001FD7F9 /* HapticFeedback.swift */,
34FD936E1E3BD43A00109093 /* OWSAnyTouchGestureRecognizer.h */,
34FD936F1E3BD43A00109093 /* OWSAnyTouchGestureRecognizer.m */,
34B3F8331E8DF1700035BE1A /* ViewControllers */,
@ -3321,6 +3324,7 @@
34A55F3720485465002CC6DE /* OWS2FARegistrationViewController.m in Sources */,
340FC8AD204DAC8D007AEB0F /* OWSLinkedDevicesTableViewController.m in Sources */,
340FC8AA204DAC8D007AEB0F /* NotificationSettingsViewController.m in Sources */,
4C090A1B210FD9C7001FD7F9 /* HapticFeedback.swift in Sources */,
3496744F2076ACD000080B5F /* LongTextViewController.swift in Sources */,
34FD93701E3BD43A00109093 /* OWSAnyTouchGestureRecognizer.m in Sources */,
34B3F8931E8DF1710035BE1A /* SignalsNavigationController.m in Sources */,

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

@ -0,0 +1,51 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
import Foundation
protocol HapticAdapter {
func selectionChanged()
}
class LegacyHapticAdapter: NSObject, HapticAdapter {
// MARK: HapticAdapter
func selectionChanged() {
// do nothing
}
}
@available(iOS 10, *)
class FeedbackGeneratorHapticAdapter: NSObject, HapticAdapter {
let selectionFeedbackGenerator: UISelectionFeedbackGenerator
override init() {
selectionFeedbackGenerator = UISelectionFeedbackGenerator()
selectionFeedbackGenerator.prepare()
}
// MARK: HapticAdapter
func selectionChanged() {
selectionFeedbackGenerator.selectionChanged()
selectionFeedbackGenerator.prepare()
}
}
class HapticFeedback: HapticAdapter {
let adapter: HapticAdapter
init() {
if #available(iOS 10, *) {
adapter = FeedbackGeneratorHapticAdapter()
} else {
adapter = LegacyHapticAdapter()
}
}
func selectionChanged() {
adapter.selectionChanged()
}
}

@ -508,12 +508,18 @@ NS_ASSUME_NONNULL_BEGIN
[self.bubbleView addPartnerView:shadowView];
[self.bubbleView addPartnerView:clipView];
// Prevent the layer from animating changes.
[CATransaction begin];
[CATransaction setDisableActions:YES];
OWSAssert(buttonsView.backgroundColor);
shadowView.fillColor = buttonsView.backgroundColor;
shadowView.layer.shadowColor = Theme.boldColor.CGColor;
shadowView.layer.shadowOpacity = 0.12f;
shadowView.layer.shadowOffset = CGSizeZero;
shadowView.layer.shadowRadius = 1.f;
[CATransaction commit];
}
- (BOOL)contactShareHasSpacerTop

@ -3461,9 +3461,9 @@ typedef enum : NSUInteger {
}
[self updateLastVisibleTimestamp];
if (scrollToBottom) {
[self scrollToBottomAnimated:shouldAnimateScrollToBottom && shouldAnimateUpdates];
if (scrollToBottom && shouldAnimateUpdates) {
[self scrollToBottomAnimated:shouldAnimateScrollToBottom];
}
};
if (shouldAnimateUpdates) {
@ -3471,6 +3471,9 @@ typedef enum : NSUInteger {
} else {
[UIView performWithoutAnimation:^{
[self.collectionView performBatchUpdates:batchUpdates completion:batchUpdatesCompletion];
if (scrollToBottom) {
[self scrollToBottomAnimated:NO];
}
}];
}
self.lastReloadDate = [NSDate new];
@ -3482,55 +3485,40 @@ typedef enum : NSUInteger {
OWSAssert(rowChanges);
// If user sends a new outgoing message, don't animate the change.
BOOL isOnlyInsertingNewOutgoingMessages = YES;
BOOL isOnlyUpdatingLastOutgoingMessage = YES;
NSNumber *_Nullable lastUpdateRow = nil;
NSNumber *_Nullable lastNonUpdateRow = nil;
BOOL isOnlyModifyingLastMessage = YES;
for (YapDatabaseViewRowChange *rowChange in rowChanges) {
switch (rowChange.type) {
case YapDatabaseViewChangeDelete:
isOnlyInsertingNewOutgoingMessages = NO;
isOnlyUpdatingLastOutgoingMessage = NO;
if (!lastNonUpdateRow || lastNonUpdateRow.integerValue < rowChange.indexPath.row) {
lastNonUpdateRow = @(rowChange.indexPath.row);
}
isOnlyModifyingLastMessage = NO;
break;
case YapDatabaseViewChangeInsert: {
isOnlyUpdatingLastOutgoingMessage = NO;
ConversationViewItem *_Nullable viewItem = [self viewItemForIndex:(NSInteger)rowChange.finalIndex];
if ([viewItem.interaction isKindOfClass:[TSOutgoingMessage class]]
if (([viewItem.interaction isKindOfClass:[TSIncomingMessage class]] ||
[viewItem.interaction isKindOfClass:[TSOutgoingMessage class]])
&& rowChange.finalIndex >= oldViewItemCount) {
continue;
}
if (!lastNonUpdateRow || lastNonUpdateRow.unsignedIntegerValue < rowChange.finalIndex) {
lastNonUpdateRow = @(rowChange.finalIndex);
}
isOnlyModifyingLastMessage = NO;
}
case YapDatabaseViewChangeMove:
isOnlyInsertingNewOutgoingMessages = NO;
isOnlyUpdatingLastOutgoingMessage = NO;
if (!lastNonUpdateRow || lastNonUpdateRow.integerValue < rowChange.indexPath.row) {
lastNonUpdateRow = @(rowChange.indexPath.row);
}
if (!lastNonUpdateRow || lastNonUpdateRow.unsignedIntegerValue < rowChange.finalIndex) {
lastNonUpdateRow = @(rowChange.finalIndex);
}
isOnlyModifyingLastMessage = NO;
break;
case YapDatabaseViewChangeUpdate: {
isOnlyInsertingNewOutgoingMessages = NO;
ConversationViewItem *_Nullable viewItem = [self viewItemForIndex:(NSInteger)rowChange.finalIndex];
if (![viewItem.interaction isKindOfClass:[TSOutgoingMessage class]]
|| rowChange.indexPath.row != (NSInteger)(oldViewItemCount - 1)) {
isOnlyUpdatingLastOutgoingMessage = NO;
if (rowChange.changes == YapDatabaseViewChangedDependency) {
continue;
}
if (!lastUpdateRow || lastUpdateRow.integerValue < rowChange.indexPath.row) {
lastUpdateRow = @(rowChange.indexPath.row);
ConversationViewItem *_Nullable viewItem = [self viewItemForIndex:(NSInteger)rowChange.finalIndex];
if (([viewItem.interaction isKindOfClass:[TSIncomingMessage class]] ||
[viewItem.interaction isKindOfClass:[TSOutgoingMessage class]])
&& rowChange.finalIndex >= oldViewItemCount) {
continue;
}
isOnlyModifyingLastMessage = NO;
break;
}
}
}
BOOL shouldAnimateRowUpdates = !(isOnlyInsertingNewOutgoingMessages || isOnlyUpdatingLastOutgoingMessage);
BOOL shouldAnimateRowUpdates = !isOnlyModifyingLastMessage;
return shouldAnimateRowUpdates;
}

@ -40,6 +40,23 @@ typedef NS_ENUM(NSInteger, HomeViewMode) {
HomeViewMode_Inbox,
};
// The bulk of the content in this view is driven by a YapDB view/mapping.
// However, we also want to optionally include ReminderView's at the top
// and an "Archived Conversations" button at the bottom. Rather than introduce
// index-offsets into the Mapping calculation, we introduce two pseudo groups
// to add a top and bottom section to the content, and create cells for those
// sections without consulting the YapMapping.
// This is a bit of a hack, but it consolidates the hacks into the Reminder/Archive section
// and allows us to leaves the bulk of the content logic on the happy path.
NSString *const kReminderViewPseudoGroup = @"kReminderViewPseudoGroup";
NSString *const kArchiveButtonPseudoGroup = @"kArchiveButtonPseudoGroup";
typedef NS_ENUM(NSInteger, HomeViewControllerSection) {
HomeViewControllerSectionReminders,
HomeViewControllerSectionConversations,
HomeViewControllerSectionArchiveButton,
};
NSString *const kArchivedConversationsReuseIdentifier = @"kArchivedConversationsReuseIdentifier";
@interface HomeViewController () <UITableViewDelegate,
@ -76,6 +93,8 @@ NSString *const kArchivedConversationsReuseIdentifier = @"kArchivedConversations
// Views
@property (nonatomic, readonly) UIStackView *reminderStackView;
@property (nonatomic, readonly) UITableViewCell *reminderViewCell;
@property (nonatomic, readonly) UIView *deregisteredView;
@property (nonatomic, readonly) UIView *outageView;
@property (nonatomic, readonly) UIView *archiveReminderView;
@ -85,6 +104,7 @@ NSString *const kArchivedConversationsReuseIdentifier = @"kArchivedConversations
@property (nonatomic) BOOL hasArchivedThreadsRow;
@property (nonatomic) BOOL hasThemeChanged;
@property (nonatomic) BOOL hasVisibleReminders;
@end
@ -252,18 +272,13 @@ NSString *const kArchivedConversationsReuseIdentifier = @"kArchivedConversations
}
UIStackView *reminderStackView = [UIStackView new];
_reminderStackView = reminderStackView;
reminderStackView.axis = UILayoutConstraintAxisVertical;
reminderStackView.spacing = 0;
[self.view addSubview:reminderStackView];
[reminderStackView autoPinWidthToSuperview];
[reminderStackView autoPinToTopLayoutGuideOfViewController:self withInset:0];
// Fixes ambiguous height of an empty stack view pinned above a scroll view on iOS10.
// Without this users would sometimes see the empty stackview take up most of their screen.
[NSLayoutConstraint autoSetPriority:UILayoutPriorityDefaultLow
forConstraints:^{
[reminderStackView autoSetDimension:ALDimensionHeight toSize:0];
}];
_reminderViewCell = [UITableViewCell new];
self.reminderViewCell.selectionStyle = UITableViewCellSelectionStyleNone;
[self.reminderViewCell.contentView addSubview:reminderStackView];
[reminderStackView autoPinEdgesToSuperviewEdges];
__weak HomeViewController *weakSelf = self;
ReminderView *deregisteredView =
@ -307,11 +322,8 @@ NSString *const kArchivedConversationsReuseIdentifier = @"kArchivedConversations
[self.tableView registerClass:[HomeViewCell class] forCellReuseIdentifier:HomeViewCell.cellReuseIdentifier];
[self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:kArchivedConversationsReuseIdentifier];
[self.view addSubview:self.tableView];
[self.tableView autoPinWidthToSuperview];
[self.tableView autoPinEdgeToSuperviewEdge:ALEdgeBottom];
[self.tableView autoPinEdgesToSuperviewEdges];
// TODO - have content scroll behind navbar will require changing this.
[self.tableView autoPinEdge:ALEdgeTop toEdge:ALEdgeBottom ofView:reminderStackView];
self.tableView.rowHeight = UITableViewAutomaticDimension;
self.tableView.estimatedRowHeight = 60;
@ -345,6 +357,9 @@ NSString *const kArchivedConversationsReuseIdentifier = @"kArchivedConversations
self.missingContactsPermissionView.hidden = !self.contactsManager.isSystemContactsDenied;
self.deregisteredView.hidden = !TSAccountManager.sharedInstance.isDeregistered;
self.outageView.hidden = !OutageDetection.sharedManager.hasOutage;
self.hasVisibleReminders = !self.archiveReminderView.isHidden || !self.missingContactsPermissionView.isHidden
|| !self.deregisteredView.isHidden || !self.outageView.isHidden;
}
- (void)viewDidLoad
@ -400,9 +415,12 @@ NSString *const kArchivedConversationsReuseIdentifier = @"kArchivedConversations
self.searchResultsController = searchResultsController;
[self addChildViewController:searchResultsController];
[self.view addSubview:searchResultsController.view];
[searchResultsController.view autoPinWidthToSuperview];
[searchResultsController.view autoPinEdge:ALEdgeTop toEdge:ALEdgeBottom ofView:searchBar];
[searchResultsController.view autoPinEdge:ALEdgeBottom toEdge:ALEdgeBottom ofView:self.tableView];
[searchResultsController.view autoPinEdgesToSuperviewEdgesWithInsets:UIEdgeInsetsZero excludingEdge:ALEdgeTop];
if (@available(iOS 11, *)) {
[searchResultsController.view autoPinTopToSuperviewMarginWithInset:56];
} else {
[searchResultsController.view autoPinToTopLayoutGuideOfViewController:self withInset:40];
}
searchResultsController.view.hidden = YES;
[self updateBarButtonItems];
@ -486,23 +504,23 @@ NSString *const kArchivedConversationsReuseIdentifier = @"kArchivedConversations
{
NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint:location];
if ([self isIndexPathForArchivedConversations:indexPath]) {
if (!indexPath) {
return nil;
}
if (indexPath) {
[previewingContext setSourceRect:[self.tableView rectForRowAtIndexPath:indexPath]];
ConversationViewController *vc = [ConversationViewController new];
TSThread *thread = [self threadForIndexPath:indexPath];
self.lastThread = thread;
[vc configureForThread:thread action:ConversationViewActionNone focusMessageId:nil];
[vc peekSetup];
return vc;
} else {
if (indexPath.section != HomeViewControllerSectionConversations) {
return nil;
}
[previewingContext setSourceRect:[self.tableView rectForRowAtIndexPath:indexPath]];
ConversationViewController *vc = [ConversationViewController new];
TSThread *thread = [self threadForIndexPath:indexPath];
self.lastThread = thread;
[vc configureForThread:thread action:ConversationViewActionNone focusMessageId:nil];
[vc peekSetup];
return vc;
}
- (void)previewingContext:(id<UIViewControllerPreviewing>)previewingContext
@ -558,7 +576,7 @@ NSString *const kArchivedConversationsReuseIdentifier = @"kArchivedConversations
BOOL isShowingSearchResults = !self.searchResultsController.view.hidden;
if (isShowingSearchResults) {
OWSAssert(self.searchBar.text.ows_stripped.length > 0);
self.tableView.contentOffset = CGPointZero;
[self scrollSearchBarToTopAnimated:NO];
} else if (self.lastThread) {
OWSAssert(self.searchBar.text.ows_stripped.length == 0);
@ -754,31 +772,30 @@ NSString *const kArchivedConversationsReuseIdentifier = @"kArchivedConversations
return (NSInteger)[self.threadMappings numberOfSections];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)aSection
{
NSInteger result = (NSInteger)[self.threadMappings numberOfItemsInSection:(NSUInteger)section];
if (self.hasArchivedThreadsRow) {
// Add the "archived conversations" row.
result++;
HomeViewControllerSection section = (HomeViewControllerSection)aSection;
switch (section) {
case HomeViewControllerSectionReminders: {
return self.hasVisibleReminders ? 1 : 0;
}
case HomeViewControllerSectionConversations: {
NSInteger result = (NSInteger)[self.threadMappings numberOfItemsInSection:(NSUInteger)section];
return result;
}
case HomeViewControllerSectionArchiveButton: {
return self.hasArchivedThreadsRow ? 1 : 0;
}
}
return result;
}
- (BOOL)isIndexPathForArchivedConversations:(NSIndexPath *)indexPath
{
if (self.homeViewMode != HomeViewMode_Inbox) {
return NO;
}
if (indexPath.section != 0) {
return NO;
}
NSInteger cellCount = (NSInteger)[self.threadMappings numberOfItemsInSection:(NSUInteger)0];
return indexPath.row == cellCount;
OWSFail(@"%@ failure: unexpected section: %lu", self.logTag, (unsigned long)section);
return 0;
}
- (ThreadViewModel *)threadViewModelForIndexPath:(NSIndexPath *)indexPath
{
TSThread *threadRecord = [self threadForIndexPath:indexPath];
OWSAssert(threadRecord);
ThreadViewModel *_Nullable cachedThreadViewModel = [self.threadViewModelCache objectForKey:threadRecord.uniqueId];
if (cachedThreadViewModel) {
@ -795,11 +812,21 @@ NSString *const kArchivedConversationsReuseIdentifier = @"kArchivedConversations
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
if ([self isIndexPathForArchivedConversations:indexPath]) {
return [self cellForArchivedConversationsRow:tableView];
} else {
return [self tableView:tableView cellForConversationAtIndexPath:indexPath];
HomeViewControllerSection section = (HomeViewControllerSection)indexPath.section;
switch (section) {
case HomeViewControllerSectionReminders: {
return self.reminderViewCell;
}
case HomeViewControllerSectionConversations: {
return [self tableView:tableView cellForConversationAtIndexPath:indexPath];
}
case HomeViewControllerSectionArchiveButton: {
return [self cellForArchivedConversationsRow:tableView];
}
}
OWSFail(@"%@ failure: unexpected section: %lu", self.logTag, (unsigned long)section);
return [UITableViewCell new];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForConversationAtIndexPath:(NSIndexPath *)indexPath
@ -894,56 +921,70 @@ NSString *const kArchivedConversationsReuseIdentifier = @"kArchivedConversations
- (nullable NSArray *)tableView:(UITableView *)tableView editActionsForRowAtIndexPath:(NSIndexPath *)indexPath
{
if ([self isIndexPathForArchivedConversations:indexPath]) {
return @[];
}
UITableViewRowAction *deleteAction =
[UITableViewRowAction rowActionWithStyle:UITableViewRowActionStyleDefault
title:NSLocalizedString(@"TXT_DELETE_TITLE", nil)
handler:^(UITableViewRowAction *action, NSIndexPath *swipedIndexPath) {
[self tableViewCellTappedDelete:swipedIndexPath];
}];
UITableViewRowAction *archiveAction;
if (self.homeViewMode == HomeViewMode_Inbox) {
archiveAction = [UITableViewRowAction
rowActionWithStyle:UITableViewRowActionStyleNormal
title:NSLocalizedString(@"ARCHIVE_ACTION",
@"Pressing this button moves a thread from the inbox to the archive")
handler:^(UITableViewRowAction *_Nonnull action, NSIndexPath *_Nonnull tappedIndexPath) {
[self archiveIndexPath:tappedIndexPath];
[Environment.preferences setHasArchivedAMessage:YES];
}];
HomeViewControllerSection section = (HomeViewControllerSection)indexPath.section;
switch (section) {
case HomeViewControllerSectionReminders: {
return @[];
}
case HomeViewControllerSectionConversations: {
UITableViewRowAction *deleteAction =
[UITableViewRowAction rowActionWithStyle:UITableViewRowActionStyleDefault
title:NSLocalizedString(@"TXT_DELETE_TITLE", nil)
handler:^(UITableViewRowAction *action, NSIndexPath *swipedIndexPath) {
[self tableViewCellTappedDelete:swipedIndexPath];
}];
UITableViewRowAction *archiveAction;
if (self.homeViewMode == HomeViewMode_Inbox) {
archiveAction = [UITableViewRowAction
rowActionWithStyle:UITableViewRowActionStyleNormal
title:NSLocalizedString(@"ARCHIVE_ACTION",
@"Pressing this button moves a thread from the inbox to the archive")
handler:^(UITableViewRowAction *_Nonnull action, NSIndexPath *_Nonnull tappedIndexPath) {
[self archiveIndexPath:tappedIndexPath];
[Environment.preferences setHasArchivedAMessage:YES];
}];
} else {
archiveAction = [UITableViewRowAction
rowActionWithStyle:UITableViewRowActionStyleNormal
title:NSLocalizedString(@"UNARCHIVE_ACTION",
@"Pressing this button moves an archived thread from the archive back to "
@"the inbox")
handler:^(UITableViewRowAction *_Nonnull action, NSIndexPath *_Nonnull tappedIndexPath) {
[self archiveIndexPath:tappedIndexPath];
}];
}
} else {
archiveAction = [UITableViewRowAction
rowActionWithStyle:UITableViewRowActionStyleNormal
title:NSLocalizedString(@"UNARCHIVE_ACTION",
@"Pressing this button moves an archived thread from the archive back to the inbox")
handler:^(UITableViewRowAction *_Nonnull action, NSIndexPath *_Nonnull tappedIndexPath) {
[self archiveIndexPath:tappedIndexPath];
}];
return @[ deleteAction, archiveAction ];
}
case HomeViewControllerSectionArchiveButton: {
return @[];
}
}
return @[ deleteAction, archiveAction ];
}
- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath
{
if ([self isIndexPathForArchivedConversations:indexPath]) {
return NO;
HomeViewControllerSection section = (HomeViewControllerSection)indexPath.section;
switch (section) {
case HomeViewControllerSectionReminders: {
return NO;
}
case HomeViewControllerSectionConversations: {
return YES;
}
case HomeViewControllerSectionArchiveButton: {
return NO;
}
}
return YES;
}
#pragma mark - UISearchBarDelegate
- (void)searchBarTextDidBeginEditing:(UISearchBar *)searchBar
{
[self.tableView setContentOffset:CGPointZero animated:NO];
[self scrollSearchBarToTopAnimated:NO];
[self updateSearchResultsVisibility];
@ -996,13 +1037,19 @@ NSString *const kArchivedConversationsReuseIdentifier = @"kArchivedConversations
self.searchResultsController.view.hidden = !isSearching;
if (isSearching) {
[self.tableView setContentOffset:CGPointZero animated:NO];
[self scrollSearchBarToTopAnimated:NO];
self.tableView.scrollEnabled = NO;
} else {
self.tableView.scrollEnabled = YES;
}
}
- (void)scrollSearchBarToTopAnimated:(BOOL)isAnimated
{
CGFloat topInset = self.topLayoutGuide.length;
[self.tableView setContentOffset:CGPointMake(0, -topInset) animated:isAnimated];
}
#pragma mark - UIScrollViewDelegate
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
@ -1023,6 +1070,11 @@ NSString *const kArchivedConversationsReuseIdentifier = @"kArchivedConversations
- (void)tableViewCellTappedDelete:(NSIndexPath *)indexPath
{
if (indexPath.section != HomeViewControllerSectionConversations) {
OWSFail(@"%@ failure: unexpected section: %lu", self.logTag, (unsigned long)indexPath.section);
return;
}
TSThread *thread = [self threadForIndexPath:indexPath];
if ([thread isKindOfClass:[TSGroupThread class]]) {
@ -1074,6 +1126,11 @@ NSString *const kArchivedConversationsReuseIdentifier = @"kArchivedConversations
- (void)archiveIndexPath:(NSIndexPath *)indexPath
{
if (indexPath.section != HomeViewControllerSectionConversations) {
OWSFail(@"%@ failure: unexpected section: %lu", self.logTag, (unsigned long)indexPath.section);
return;
}
TSThread *thread = [self threadForIndexPath:indexPath];
[self.editingDbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
@ -1094,15 +1151,22 @@ NSString *const kArchivedConversationsReuseIdentifier = @"kArchivedConversations
DDLogInfo(@"%@ %s %ld %ld", self.logTag, __PRETTY_FUNCTION__, (long)indexPath.row, (long)indexPath.section);
[self.searchBar resignFirstResponder];
if ([self isIndexPathForArchivedConversations:indexPath]) {
[self showArchivedConversations];
return;
HomeViewControllerSection section = (HomeViewControllerSection)indexPath.section;
switch (section) {
case HomeViewControllerSectionReminders: {
break;
}
case HomeViewControllerSectionConversations: {
TSThread *thread = [self threadForIndexPath:indexPath];
[self presentThread:thread action:ConversationViewActionNone];
[tableView deselectRowAtIndexPath:indexPath animated:YES];
break;
}
case HomeViewControllerSectionArchiveButton: {
[self showArchivedConversations];
break;
}
}
TSThread *thread = [self threadForIndexPath:indexPath];
[self presentThread:thread action:ConversationViewActionNone];
[tableView deselectRowAtIndexPath:indexPath animated:YES];
}
- (void)presentThread:(TSThread *)thread action:(ConversationViewAction)action
@ -1237,8 +1301,9 @@ NSString *const kArchivedConversationsReuseIdentifier = @"kArchivedConversations
{
OWSAssertIsOnMainThread();
self.threadMappings = [[YapDatabaseViewMappings alloc] initWithGroups:@[ self.currentGrouping ]
view:TSThreadDatabaseViewExtensionName];
self.threadMappings = [[YapDatabaseViewMappings alloc]
initWithGroups:@[ kReminderViewPseudoGroup, self.currentGrouping, kArchiveButtonPseudoGroup ]
view:TSThreadDatabaseViewExtensionName];
[self.threadMappings setIsReversed:YES forGroup:self.currentGrouping];
[self resetMappings];

@ -71,10 +71,6 @@ class MenuActionsViewController: UIViewController, MenuActionSheetDelegate {
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(didTapBackground))
self.view.addGestureRecognizer(tapGesture)
let swipeGesture = UISwipeGestureRecognizer(target: self, action: #selector(didSwipeBackground))
swipeGesture.direction = .down
self.view.addGestureRecognizer(swipeGesture)
}
override func viewDidAppear(_ animated: Bool) {
@ -249,11 +245,6 @@ class MenuActionsViewController: UIViewController, MenuActionSheetDelegate {
animateDismiss(action: nil)
}
@objc
func didSwipeBackground(gesture: UISwipeGestureRecognizer) {
animateDismiss(action: nil)
}
// MARK: MenuActionSheetDelegate
func actionSheet(_ actionSheet: MenuActionSheetView, didSelectAction action: MenuAction) {
@ -269,6 +260,10 @@ class MenuActionSheetView: UIView, MenuActionViewDelegate {
private let actionStackView: UIStackView
private var actions: [MenuAction]
private var actionViews: [MenuActionView]
private var hapticFeedback: HapticFeedback
private var hasEverHighlightedAction = false
weak var delegate: MenuActionSheetDelegate?
override var bounds: CGRect {
@ -288,29 +283,58 @@ class MenuActionSheetView: UIView, MenuActionViewDelegate {
actionStackView.spacing = CGHairlineWidth()
actions = []
actionViews = []
hapticFeedback = HapticFeedback()
super.init(frame: frame)
backgroundColor = UIColor.ows_light10
addSubview(actionStackView)
actionStackView.ows_autoPinToSuperviewEdges()
actionStackView.autoPinEdgesToSuperviewEdges()
self.clipsToBounds = true
// Prevent panning from percolating to the superview, which would
// cause us to dismiss
let panGestureSink = UIPanGestureRecognizer(target: nil, action: nil)
self.addGestureRecognizer(panGestureSink)
let touchGesture = UILongPressGestureRecognizer(target: self, action: #selector(didTouch(gesture:)))
touchGesture.minimumPressDuration = 0.0
touchGesture.allowableMovement = CGFloat.greatestFiniteMagnitude
self.addGestureRecognizer(touchGesture)
}
required init?(coder aDecoder: NSCoder) {
fatalError("not implemented")
}
@objc
public func didTouch(gesture: UIGestureRecognizer) {
switch gesture.state {
case .possible:
break
case .began:
let location = gesture.location(in: self)
highlightActionView(location: location, fromView: self)
case .changed:
let location = gesture.location(in: self)
highlightActionView(location: location, fromView: self)
case .ended:
Logger.debug("\(logTag) in \(#function) ended")
let location = gesture.location(in: self)
selectActionView(location: location, fromView: self)
case .cancelled:
Logger.debug("\(logTag) in \(#function) canceled")
unhighlightAllActionViews()
case .failed:
Logger.debug("\(logTag) in \(#function) failed")
unhighlightAllActionViews()
}
}
public func addAction(_ action: MenuAction) {
actions.append(action)
let actionView = MenuActionView(action: action)
actionView.delegate = self
actions.append(action)
actionViews.append(actionView)
self.actionStackView.addArrangedSubview(actionView)
}
@ -329,6 +353,47 @@ class MenuActionSheetView: UIView, MenuActionViewDelegate {
mask.path = path.cgPath
self.layer.mask = mask
}
private func unhighlightAllActionViews() {
for actionView in actionViews {
actionView.isHighlighted = false
}
}
private func actionView(touchedBy touchPoint: CGPoint, fromView: UIView) -> MenuActionView? {
for actionView in actionViews {
let convertedPoint = actionView.convert(touchPoint, from: fromView)
if actionView.point(inside: convertedPoint, with: nil) {
return actionView
}
}
return nil
}
private func highlightActionView(location: CGPoint, fromView: UIView) {
guard let touchedView = actionView(touchedBy: location, fromView: fromView) else {
unhighlightAllActionViews()
return
}
if hasEverHighlightedAction, !touchedView.isHighlighted {
self.hapticFeedback.selectionChanged()
}
touchedView.isHighlighted = true
hasEverHighlightedAction = true
self.actionViews.filter { $0 != touchedView }.forEach { $0.isHighlighted = false }
}
private func selectActionView(location: CGPoint, fromView: UIView) {
guard let selectedView: MenuActionView = actionView(touchedBy: location, fromView: fromView) else {
unhighlightAllActionViews()
return
}
selectedView.isHighlighted = true
self.actionViews.filter { $0 != selectedView }.forEach { $0.isHighlighted = false }
delegate?.actionSheet(self, didSelectAction: selectedView.action)
}
}
protocol MenuActionViewDelegate: class {
@ -337,7 +402,7 @@ protocol MenuActionViewDelegate: class {
class MenuActionView: UIButton {
public weak var delegate: MenuActionViewDelegate?
private let action: MenuAction
public let action: MenuAction
required init(action: MenuAction) {
self.action = action
@ -378,10 +443,10 @@ class MenuActionView: UIButton {
contentRow.isUserInteractionEnabled = false
self.addSubview(contentRow)
contentRow.ows_autoPinToSuperviewMargins()
contentRow.autoPinEdgesToSuperviewMargins()
contentRow.autoSetDimension(.height, toSize: 56, relation: .greaterThanOrEqual)
self.addTarget(self, action: #selector(didPress(sender:)), for: .touchUpInside)
self.isUserInteractionEnabled = false
}
override var isHighlighted: Bool {

@ -43,7 +43,9 @@ NS_ASSUME_NONNULL_BEGIN
// RTCMTLVideoView requires the MTKView class, available only in iOS9+
// So check that it exists before proceeding.
if ([MTKView class]) {
_videoRenderer = [[RTCMTLVideoView alloc] initWithFrame:CGRectZero];
RTCMTLVideoView *rtcMetalView = [[RTCMTLVideoView alloc] initWithFrame:CGRectZero];
rtcMetalView.videoContentMode = UIViewContentModeScaleAspectFill;
_videoRenderer = rtcMetalView;
[self addSubview:_videoRenderer];
[_videoRenderer autoPinEdgesToSuperviewEdges];
// HACK: Although RTCMTLVideo view is positioned to the top edge of the screen

@ -2121,7 +2121,7 @@
"SHARE_EXTENSION_VIEW_TITLE" = "Share to Signal";
/* Action sheet item */
"SHOW_SAFETY_NUMBER_ACTION" = "Show Safety Number";
"SHOW_SAFETY_NUMBER_ACTION" = "Vis sikkerhedsnummer";
/* notification action */
"SHOW_THREAD_BUTTON_TITLE" = "Vis Samtale";

@ -402,7 +402,7 @@
"CONFIRM_LEAVE_GROUP_TITLE" = "Wirklich verlassen?";
/* Button text */
"CONFIRM_LINK_NEW_DEVICE_ACTION" = "Neues Gerät verknüpfen";
"CONFIRM_LINK_NEW_DEVICE_ACTION" = "Neues Gerät koppeln";
/* Action sheet body presented when a user's SN has recently changed. Embeds {{contact's name or phone number}} */
"CONFIRM_SENDING_TO_CHANGED_IDENTITY_BODY_FORMAT" = "%@hat Signal vielleicht erneut installiert oder das Gerät gewechselt. Verifiziert eure gemeinsame Sicherheitsnummer zur Sicherstellung der Privatsphäre.";
@ -676,7 +676,7 @@
"DEVICE_LAST_ACTIVE_AT_LABEL" = "Zuletzt aktiv: %@";
/* {{Short Date}} when device was linked. */
"DEVICE_LINKED_AT_LABEL" = "Verknüpft: %@";
"DEVICE_LINKED_AT_LABEL" = "Gekoppelt: %@";
/* Alert title that can occur when viewing device manager. */
"DEVICE_LIST_UPDATE_FAILED_TITLE" = "Aktualisieren der Geräteliste gescheitert.";
@ -1090,34 +1090,34 @@
"LEAVE_GROUP_ACTION" = "Gruppe verlassen";
/* report an invalid linking code */
"LINK_DEVICE_INVALID_CODE_BODY" = "Dieser QR-Code ist ungültig. Stelle bitte sicher, dass du den QR-Code einscannst, der auf dem zu verknüpfenden Gerät angezeigt wird.";
"LINK_DEVICE_INVALID_CODE_BODY" = "Dieser QR-Code ist ungültig. Stelle bitte sicher, dass du den QR-Code einscannst, der auf dem zu koppelnden Gerät angezeigt wird.";
/* report an invalid linking code */
"LINK_DEVICE_INVALID_CODE_TITLE" = "Verknüpfen des Geräts gescheitert";
"LINK_DEVICE_INVALID_CODE_TITLE" = "Koppeln des Geräts gescheitert";
/* confirm the users intent to link a new device */
"LINK_DEVICE_PERMISSION_ALERT_BODY" = "Dieses Gerät wird in der Lage sein, deine Gruppen und Kontakte zu sehen, alle deine Nachrichten zu lesen und Nachrichten in deinem Namen zu versenden.";
/* confirm the users intent to link a new device */
"LINK_DEVICE_PERMISSION_ALERT_TITLE" = "Gerät verknüpfen?";
"LINK_DEVICE_PERMISSION_ALERT_TITLE" = "Gerät koppeln?";
/* attempt another linking */
"LINK_DEVICE_RESTART" = "Erneut versuchen";
/* QR Scanning screen instructions, placed alongside a camera view for scanning QR Codes */
"LINK_DEVICE_SCANNING_INSTRUCTIONS" = "Scanne zum Verknüpfen den auf dem Gerät angezeigten QR-Code ein.";
"LINK_DEVICE_SCANNING_INSTRUCTIONS" = "Scanne zum Koppeln den auf dem Gerät angezeigten QR-Code ein.";
/* Subheading for 'Link New Device' navigation */
"LINK_NEW_DEVICE_SUBTITLE" = "QR-Code einscannen";
/* Navigation title when scanning QR code to add new device. */
"LINK_NEW_DEVICE_TITLE" = "Neues Gerät verknüpfen";
"LINK_NEW_DEVICE_TITLE" = "Neues Gerät koppeln";
/* Menu item and navbar title for the device manager */
"LINKED_DEVICES_TITLE" = "Verknüpfte Geräte";
"LINKED_DEVICES_TITLE" = "Gekoppelte Geräte";
/* Alert Title */
"LINKING_DEVICE_FAILED_TITLE" = "Verknüpfen des Geräts gescheitert";
"LINKING_DEVICE_FAILED_TITLE" = "Gerätekopplung gescheitert";
/* table cell label in conversation settings */
"LIST_GROUP_MEMBERS_ACTION" = "Gruppenmitglieder";
@ -2208,16 +2208,16 @@
"UNKNOWN_VALUE" = "Unbekannt";
/* button title for unlinking a device */
"UNLINK_ACTION" = "Entfernen";
"UNLINK_ACTION" = "Entkoppeln";
/* Alert message to confirm unlinking a device */
"UNLINK_CONFIRMATION_ALERT_BODY" = "Nach Entfernen dieses Geräts wird es keine weiteren Nachrichten senden oder empfangen können.";
"UNLINK_CONFIRMATION_ALERT_BODY" = "Nach Entkoppeln dieses Geräts wird es keine weiteren Nachrichten senden oder empfangen können.";
/* Alert title for confirming device deletion */
"UNLINK_CONFIRMATION_ALERT_TITLE" = "»%@« entfernen?";
"UNLINK_CONFIRMATION_ALERT_TITLE" = "»%@« entkoppeln?";
/* Alert title when unlinking device fails */
"UNLINKING_FAILED_ALERT_TITLE" = "Die Verknüpfung für dein Gerät konnte nicht entfernt werden.";
"UNLINKING_FAILED_ALERT_TITLE" = "Signal konnte dein Gerät nicht entkoppeln.";
/* Label text in device manager for a device with no name */
"UNNAMED_DEVICE" = "Unbenanntes Gerät";

@ -1431,7 +1431,7 @@
"OPEN_SETTINGS_BUTTON" = "Ajustes";
/* Info Message when {{other user}} disables or doesn't support disappearing messages */
"OTHER_DISABLED_DISAPPEARING_MESSAGES_CONFIGURATION" = "%@ ha desactivado la caducidad de mensajes.";
"OTHER_DISABLED_DISAPPEARING_MESSAGES_CONFIGURATION" = "%@ ha desactivado los mensajes con caducidad.";
/* Info Message when {{other user}} updates message expiration to {{time amount}}, see the *_TIME_AMOUNT strings for context. */
"OTHER_UPDATED_DISAPPEARING_MESSAGES_CONFIGURATION" = "%@ ha fijado la caducidad de mensajes a %@.";
@ -2343,7 +2343,7 @@
"WAITING_TO_COMPLETE_DEVICE_LINK_TEXT" = "Completa la configuración en la aplicación de escritorio.";
/* Info Message when you disable disappearing messages */
"YOU_DISABLED_DISAPPEARING_MESSAGES_CONFIGURATION" = "Has desactivado la caducidad de mensajes.";
"YOU_DISABLED_DISAPPEARING_MESSAGES_CONFIGURATION" = "Has desactivado los mensajes con caducidad.";
/* Info message embedding a {{time amount}}, see the *_TIME_AMOUNT strings for context. */
"YOU_UPDATED_DISAPPEARING_MESSAGES_CONFIGURATION" = "Has fijado la caducidad de mensajes a %@.";

@ -609,10 +609,10 @@
"DATABASE_VIEW_OVERLAY_TITLE" = "Mengoptimalkan Database";
/* Format string for a relative time, expressed as a certain number of hours in the past. Embeds {{The number of hours}}. */
"DATE_HOURS_AGO_FORMAT" = "%@ jam lalu";
"DATE_HOURS_AGO_FORMAT" = "%@ Jam Lalu";
/* Format string for a relative time, expressed as a certain number of minutes in the past. Embeds {{The number of minutes}}. */
"DATE_MINUTES_AGO_FORMAT" = "%@ Menit lalu";
"DATE_MINUTES_AGO_FORMAT" = "%@ Menit Lalu";
/* The present; the current time. */
"DATE_NOW" = "Sekarang";
@ -868,7 +868,7 @@
"ERROR_MESSAGE_INVALID_KEY_EXCEPTION" = "Kunci penerima tidak cocok";
/* No comment provided by engineer. */
"ERROR_MESSAGE_INVALID_MESSAGE" = "Pesan diterima tidak tersinkronisasi";
"ERROR_MESSAGE_INVALID_MESSAGE" = "Pesan yang diterima tidak sinkron.";
/* No comment provided by engineer. */
"ERROR_MESSAGE_INVALID_VERSION" = "Menerima pesan yang tidak cocok dengan versi ini";
@ -886,7 +886,7 @@
"ERROR_MESSAGE_UNKNOWN_ERROR" = "Sebuah kegagagalan tak dikenal muncul.";
/* No comment provided by engineer. */
"ERROR_MESSAGE_WRONG_TRUSTED_IDENTITY_KEY" = "Nomor aman diubah";
"ERROR_MESSAGE_WRONG_TRUSTED_IDENTITY_KEY" = "Nomor keamanan diubah";
/* Format string for 'unregistered user' error. Embeds {{the unregistered user's name or signal id}}. */
"ERROR_UNREGISTERED_USER_FORMAT" = "Pengguna tidak terdaftar: %@";
@ -1494,7 +1494,7 @@
"PLAY_BUTTON_ACCESSABILITY_LABEL" = "Mainkan Media";
/* Label indicating that the user is not verified. Embeds {{the user's name or phone number}}. */
"PRIVACY_IDENTITY_IS_NOT_VERIFIED_FORMAT" = "Anda tidak menandai %@telah terverifikasi.";
"PRIVACY_IDENTITY_IS_NOT_VERIFIED_FORMAT" = "Anda belum menandai %@ telah terverifikasi.";
/* Badge indicating that the user is verified. */
"PRIVACY_IDENTITY_IS_VERIFIED_BADGE" = "Terverifikasi";
@ -2121,7 +2121,7 @@
"SHARE_EXTENSION_VIEW_TITLE" = "Bagikan ke Signal";
/* Action sheet item */
"SHOW_SAFETY_NUMBER_ACTION" = "Tunjukkan nomor pengamanan";
"SHOW_SAFETY_NUMBER_ACTION" = "Tunjukkan nomor keamanan";
/* notification action */
"SHOW_THREAD_BUTTON_TITLE" = "Tunjukkan pembicaraan";

@ -12,7 +12,7 @@
/* Label for 'send message' button in contact view.
Label for button that lets you send a message to a contact. */
"ACTION_SEND_MESSAGE" = "Manda messaggio";
"ACTION_SEND_MESSAGE" = "Invia messaggio";
/* Label for 'share contact' button. */
"ACTION_SHARE_CONTACT" = "Condivi contatto";
@ -168,7 +168,7 @@
"ATTACHMENT_PICKER_DOCUMENTS_PICKED_DIRECTORY_FAILED_ALERT_TITLE" = "Documento non supportato";
/* Short text label for a voice message attachment, used for thread preview and on the lock screen */
"ATTACHMENT_TYPE_VOICE_MESSAGE" = "Messaggio Vocale";
"ATTACHMENT_TYPE_VOICE_MESSAGE" = "Messaggio vocale";
/* action sheet button title to enable built in speaker during a call */
"AUDIO_ROUTE_BUILT_IN_SPEAKER" = "Vivavoce";
@ -742,7 +742,7 @@
"EDIT_TXT" = "Modifica";
/* body of email sent to contacts when inviting to install Signal. Embeds {{link to install Signal}} and {{link to the Signal home page}} */
"EMAIL_INVITE_BODY" = "Ehi,\n\nUltimamente sto usando Signal per mantenere private le conversazioni sul mio iPhone. Vorrei che anche te la installassi, così potremo essere sicuri che saremo gli unici a poter leggere i nostri messaggi e ad ascoltare le nostre chiamate.\n\nSignal è disponibile per gli iPhone e per Android. Scaricalo qui: %@\n\nSignal funziona come la tua solita app di messaggistica. Possiamo mandarci immagini, video, fare chiamate e creare chat di gruppo. Ma la ciliegina sulla torta è che nessuno potrà vedere né sentire nulla di tutto ciò, nemmeno chi sta dietro a Signal!\n\nPuoi scoprire di più su Open Whisper Systems, i creatori di Signal, qui: %@";
"EMAIL_INVITE_BODY" = "Ehi,\n\nUltimamente sto usando Signal per mantenere private le conversazioni sul mio iPhone. Vorrei che anche tu la installassi, così potremo essere sicuri di essere gli unici a poter leggere i nostri messaggi e ad ascoltare le nostre chiamate.\n\nSignal è disponibile per iPhone e Android. Scaricalo qui: %@\n\nSignal funziona come la tua solita app di messaggistica. Possiamo mandarci immagini, video, fare chiamate e creare chat di gruppo. Ma la ciliegina sulla torta è che nessuno potrà vedere né sentire nulla di tutto ciò, nemmeno chi produce Signal!\n\nPuoi scoprire di più su Open Whisper Systems, i creatori di Signal, qui: %@";
/* subject of email sent to contacts when inviting to install Signal */
"EMAIL_INVITE_SUBJECT" = "Passiamo a Signal";
@ -754,7 +754,7 @@
"EMPTY_ARCHIVE_FIRST_TITLE" = "Inizia la tua prima conversazione su Signal!";
/* No comment provided by engineer. */
"EMPTY_ARCHIVE_TEXT" = "Puoi archiviare conversazioni inattive dalla lista Chat per consultarle successivamente.";
"EMPTY_ARCHIVE_TEXT" = "Puoi archiviare conversazioni inattive dalla lista chat per consultarle successivamente.";
/* No comment provided by engineer. */
"EMPTY_ARCHIVE_TITLE" = "Pulisci le tue conversazioni";
@ -781,10 +781,10 @@
"ENABLE_2FA_VIEW_CONFIRM_PIN_INSTRUCTIONS" = "Conferma il tuo PIN";
/* Error indicating that attempt to disable 'two-factor auth' failed. */
"ENABLE_2FA_VIEW_COULD_NOT_DISABLE_2FA" = "Impossibile disattivare il Blocco Registrazione.";
"ENABLE_2FA_VIEW_COULD_NOT_DISABLE_2FA" = "Impossibile disattivare il blocco registrazione.";
/* Error indicating that attempt to enable 'two-factor auth' failed. */
"ENABLE_2FA_VIEW_COULD_NOT_ENABLE_2FA" = "Impossibile abilitare il Blocco Registrazione.";
"ENABLE_2FA_VIEW_COULD_NOT_ENABLE_2FA" = "Impossibile abilitare il blocco registrazione.";
/* Label for the 'enable two-factor auth' item in the settings view */
"ENABLE_2FA_VIEW_DISABLE_2FA" = "Disabilita";
@ -799,13 +799,13 @@
"ENABLE_2FA_VIEW_PIN_DOES_NOT_MATCH" = "Il PIN non corrisponde. ";
/* Indicates that user should select a 'two factor auth pin'. */
"ENABLE_2FA_VIEW_SELECT_PIN_INSTRUCTIONS" = "Inserisci un PIN per il Blocco Registrazione. Questo PIN ti verrà richiesto la prossima volta che verrà effettuata una registrazione con questo numero di telefono.";
"ENABLE_2FA_VIEW_SELECT_PIN_INSTRUCTIONS" = "Inserisci un PIN per il blocco registrazione. Questo PIN ti verrà richiesto la prossima volta che verrà effettuata una registrazione con questo numero di telefono.";
/* Indicates that user has 'two factor auth pin' disabled. */
"ENABLE_2FA_VIEW_STATUS_DISABLED_INSTRUCTIONS" = "Per aumentare la sicurezza, attiva il PIN di Blocco Registrazione che diventerà necessario per registrare nuovamente questo numero di telefono con Signal.";
"ENABLE_2FA_VIEW_STATUS_DISABLED_INSTRUCTIONS" = "Per aumentare la sicurezza, attiva il PIN di blocco registrazione che diventerà necessario per registrare nuovamente questo numero di telefono con Signal.";
/* Indicates that user has 'two factor auth pin' enabled. */
"ENABLE_2FA_VIEW_STATUS_ENABLED_INSTRUCTIONS" = "È attivo il Blocco Registrazione. Dovrai inserire il tuo PIN quando registrerai nuovamente il tuo numero di telefono con Signal.";
"ENABLE_2FA_VIEW_STATUS_ENABLED_INSTRUCTIONS" = "È attivo il blocco registrazione. Dovrai inserire il tuo PIN quando registrerai nuovamente il tuo numero di telefono con Signal.";
/* Title for the 'enable two factor auth PIN' views. */
"ENABLE_2FA_VIEW_TITLE" = "Blocco registrazione";
@ -931,7 +931,7 @@
"GIF_PICKER_FAILURE_ALERT_TITLE" = "Impossibile recuperare la GIF selezionata";
/* Alert message shown when user tries to search for GIFs without entering any search terms. */
"GIF_PICKER_VIEW_MISSING_QUERY" = "Inserite la vostra la ricerca.";
"GIF_PICKER_VIEW_MISSING_QUERY" = "Inserisci la tua ricerca.";
/* Title for the 'GIF picker' dialog. */
"GIF_PICKER_VIEW_TITLE" = "Ricerca GIF";
@ -1353,7 +1353,7 @@
"NETWORK_STATUS_HEADER" = "Stato della rete";
/* No comment provided by engineer. */
"NETWORK_STATUS_OFFLINE" = "Offline";
"NETWORK_STATUS_OFFLINE" = "Sconnesso";
/* A label the cell that lets you add a new member to a group. */
"NEW_CONVERSATION_FIND_BY_PHONE_NUMBER" = "Cerca per numero";
@ -2202,7 +2202,7 @@
"UNKNOWN_CONTACT_BLOCK_OFFER" = "Utente non presente nei contatti. Vuoi bloccare questo utente?";
/* Displayed if for some reason we can't determine a contacts phone number *or* name */
"UNKNOWN_CONTACT_NAME" = "Contatto Sconosciuto";
"UNKNOWN_CONTACT_NAME" = "Contatto sconosciuto";
/* Indicates an unknown or unrecognizable value. */
"UNKNOWN_VALUE" = "Sconosciuto";
@ -2214,13 +2214,13 @@
"UNLINK_CONFIRMATION_ALERT_BODY" = "Dissociando questo dispositivo, non sarà più possibile inviare o ricevere messaggi.";
/* Alert title for confirming device deletion */
"UNLINK_CONFIRMATION_ALERT_TITLE" = "Dissocia \"%@\" ?";
"UNLINK_CONFIRMATION_ALERT_TITLE" = "Dissociare \"%@\"?";
/* Alert title when unlinking device fails */
"UNLINKING_FAILED_ALERT_TITLE" = "Signal non è riuscito a dissociare il tuo dispositivo.";
/* Label text in device manager for a device with no name */
"UNNAMED_DEVICE" = "Dispositivo Senza Nome";
"UNNAMED_DEVICE" = "Dispositivo senza nome";
/* No comment provided by engineer. */
"UNREGISTER_SIGNAL_FAIL" = "Cancellazione da Signal fallita.";
@ -2274,7 +2274,7 @@
"UPGRADE_EXPERIENCE_INTRODUCING_READ_RECEIPTS_TITLE" = "Introduzione delle ricevute di lettura";
/* Description of video calling to upgrading (existing) users */
"UPGRADE_EXPERIENCE_VIDEO_DESCRIPTION" = "Signal ora supporta le chiamate video sicure. Inizia la chiamata come di consueto, tocca il pulsante della telecamere e fai ciao.";
"UPGRADE_EXPERIENCE_VIDEO_DESCRIPTION" = "Signal ora supporta le videochiamate sicure. Inizia la chiamata come di consueto, tocca il pulsante della fotocamera e fai ciao.";
/* Header for upgrade experience */
"UPGRADE_EXPERIENCE_VIDEO_TITLE" = "Videochiamate sicure!";

@ -742,7 +742,7 @@
"EDIT_TXT" = "แก้ไข";
/* body of email sent to contacts when inviting to install Signal. Embeds {{link to install Signal}} and {{link to the Signal home page}} */
"EMAIL_INVITE_BODY" = "สวัสดี\n\nเมื่อไม่นานนี้ ฉันได้ใช้ Signal เพื่อรักษาการสนทนาบนไอโฟนของฉันให้เป็นส่วนตัว ฉันอยากให้คุณติดตั้งเหมือนกัน เพื่อให้เรามั่นใจได้ว่ามีแค่คุณและฉันที่สามารถอ่านข้อความของเราหรือได้ยินเสียงคุยของเรา\n\nSignal ใช้งานได้บนไอโฟนและแอนดรอยด์ โหลดเลยที่นี่: %@\n\nSignal ทำงานเหมือนแอปส่งข้อความที่คุณมีอยู่ เราสามารถส่งรูปและวิดีโอ โทรออก และเริ่มแชตกลุ่ม สิ่งที่ดีที่สุดคือไม่มีใครคนอื่นเห็นสิ่งเหล่านี้ แม้แต่ผู้คนที่สร้าง Signal ก็จะไม่เห็น!\n\nคุณสามารถอ่านเพิ่มเติมเกี่ยวกับ Open Whisper Systems ผู้สร้าง Signal ได้ที่นี่: %@";
"EMAIL_INVITE_BODY" = "สวัสดี\n\nเมื่อไม่นานนี้ ฉันได้ใช้ Signal เพื่อรักษาการสนทนาบน iPhone ของฉันให้เป็นส่วนตัว ฉันอยากให้คุณติดตั้งเหมือนกัน เพื่อให้เรามั่นใจได้ว่ามีแค่คุณและฉันที่สามารถอ่านข้อความของเราหรือได้ยินเสียงคุยของเรา\n\nSignal ใช้งานได้บน iPhone และ Android โหลดเลยที่นี่: %@\n\nSignal ทำงานเหมือนแอปส่งข้อความที่คุณมีอยู่ เราสามารถส่งรูปและวิดีโอ โทรออก และเริ่มแชตกลุ่ม สิ่งที่ดีที่สุดคือไม่มีใครคนอื่นเห็นสิ่งเหล่านี้ แม้แต่ผู้คนที่สร้าง Signal ก็จะไม่เห็น!\n\nคุณสามารถอ่านเพิ่มเติมเกี่ยวกับ Open Whisper Systems ผู้สร้าง Signal ได้ที่นี่: %@";
/* subject of email sent to contacts when inviting to install Signal */
"EMAIL_INVITE_SUBJECT" = "เปลี่ยนมาใช้ Signal กันเถอะ";
@ -1464,7 +1464,7 @@
"PHONE_NUMBER_TYPE_HOME_FAX" = "แฟกซ์ที่บ้าน";
/* Label for 'iPhone' phone numbers. */
"PHONE_NUMBER_TYPE_IPHONE" = "ไอโฟน";
"PHONE_NUMBER_TYPE_IPHONE" = "iPhone";
/* Label for 'Main' phone numbers. */
"PHONE_NUMBER_TYPE_MAIN" = "หลัก";
@ -1761,13 +1761,13 @@
"RETRY_BUTTON_TEXT" = "ลองใหม่";
/* button title to confirm adding a recipient to a group when their safety number has recently changed */
"SAFETY_NUMBER_CHANGED_CONFIRM_ADD_TO_GROUP_ACTION" = "เพิ่มไปที่กลุ่มเสมอ";
"SAFETY_NUMBER_CHANGED_CONFIRM_ADD_TO_GROUP_ACTION" = "ยืนยันที่จะเพิ่มไปที่กลุ่ม";
/* alert button text to confirm placing an outgoing call after the recipients Safety Number has changed. */
"SAFETY_NUMBER_CHANGED_CONFIRM_CALL_ACTION" = "โทรเสมอ";
"SAFETY_NUMBER_CHANGED_CONFIRM_CALL_ACTION" = "ยืนยันที่จะโทร";
/* button title to confirm sending to a recipient whose safety number recently changed */
"SAFETY_NUMBER_CHANGED_CONFIRM_SEND_ACTION" = "ส่งเสมอ";
"SAFETY_NUMBER_CHANGED_CONFIRM_SEND_ACTION" = "ยืนยันที่จะส่ง";
/* Snippet to share {{safety number}} with a friend. sent e.g. via SMS */
"SAFETY_NUMBER_SHARE_FORMAT" = "รหัสความปลอดภัย Signal ของเรา:\n%@";

@ -249,7 +249,7 @@
"BLOCK_LIST_VIEW_BLOCKED_ALERT_MESSAGE_FORMAT" = "%@ engellendi";
/* The title of the 'user blocked' alert. */
"BLOCK_LIST_VIEW_BLOCKED_ALERT_TITLE" = "Kullanıcı Engellenmiş";
"BLOCK_LIST_VIEW_BLOCKED_ALERT_TITLE" = "Kullanıcı Engellendi";
/* The message of the 'You can't block yourself' alert. */
"BLOCK_LIST_VIEW_CANT_BLOCK_SELF_ALERT_MESSAGE" = "Kendinizi engelleyemezsiniz.";
@ -324,7 +324,7 @@
"CALL_VIEW_HANGUP_LABEL" = "Aramayı sonlandır";
/* Accessibility label for muting the microphone */
"CALL_VIEW_MUTE_LABEL" = "Sessiz";
"CALL_VIEW_MUTE_LABEL" = "Sessize al";
/* Reminder to the user of the benefits of enabling CallKit and disabling CallKit privacy. */
"CALL_VIEW_SETTINGS_NAG_DESCRIPTION_ALL" = "Eğer ayarlarınızı değiştirirseniz gelen aramaları kilit ekranınızdan yanıtlayabilir ve gelen aramalardaki ismi ve numarayı görebilirsiniz.\n\nDetaylar için gizlilik ayarlarına göz atın.";
@ -703,7 +703,7 @@
"DOMAIN_FRONTING_COUNTRY_VIEW_SECTION_HEADER" = "Sansür Atlatma Lokasyonu";
/* Alert body for when the user has just tried to edit a contacts after declining to give Signal contacts permissions */
"EDIT_CONTACT_WITHOUT_CONTACTS_PERMISSION_ALERT_BODY" = "Ayarlar uygulamasından erişim verebilirsiniz";
"EDIT_CONTACT_WITHOUT_CONTACTS_PERMISSION_ALERT_BODY" = "Ayarlar uygulamasından erişim verebilirsiniz.";
/* Alert title for when the user has just tried to edit a contacts after declining to give Signal contacts permissions */
"EDIT_CONTACT_WITHOUT_CONTACTS_PERMISSION_ALERT_TITLE" = "Signal Kişi Bilgilerini Düzenlemek İçin Kişi Erişimine İhtiyacı Var";
@ -802,10 +802,10 @@
"ENABLE_2FA_VIEW_SELECT_PIN_INSTRUCTIONS" = "Bir Kayıt Kilidi PIN'i giriniz. Bu telefon numarasıyla Signal'e tekrar kaydolduğunuzda bu PIN kodunu girmeniz istenecektir.";
/* Indicates that user has 'two factor auth pin' disabled. */
"ENABLE_2FA_VIEW_STATUS_DISABLED_INSTRUCTIONS" = "Arttırılmış güvenlik için, bu telefon numarasıyla Signal'e tekrar kaydolduğunuzda sorulmak üzere bir kayıt kilidi PIN'i aktifleştirin.";
"ENABLE_2FA_VIEW_STATUS_DISABLED_INSTRUCTIONS" = "Daha fazla güvenlik için, bu telefon numarası Signal'e tekrar kaydedilirken sorulacak olan Kayıt Kilidi PIN'ini etkinleştirin.";
/* Indicates that user has 'two factor auth pin' enabled. */
"ENABLE_2FA_VIEW_STATUS_ENABLED_INSTRUCTIONS" = "Kayıt Kilidi aktifleştirildi. Bu numarayla Signal'e tekrar kaydolduğunuzda PIN kodunuzu girmeniz gerekecek.";
"ENABLE_2FA_VIEW_STATUS_ENABLED_INSTRUCTIONS" = "Kayıt Kilidi etkinleştirildi. Numaranızla Signal'e tekrar kaydolurken PIN kodunuzu girmeniz gerekecektir.";
/* Title for the 'enable two factor auth PIN' views. */
"ENABLE_2FA_VIEW_TITLE" = "Kayıt Kilidi";
@ -832,7 +832,7 @@
"ERROR_DESCRIPTION_MESSAGE_SEND_FAILED_DUE_TO_FAILED_ATTACHMENT_WRITE" = "Eklenti yazımı başarısız olduğundan dolayı gönderilemedi.";
/* Generic error used whenever Signal can't contact the server */
"ERROR_DESCRIPTION_NO_INTERNET" = "Sinyal internete bağlanamadı. Lütfen başka bir WiFi ağından deneyin veya mobil veri kullanın.";
"ERROR_DESCRIPTION_NO_INTERNET" = "Signal İnternet'e bağlanamadı. Lütfen farklı bir WiFi ağından veya mobil veriden tekrar deneyin.";
/* Error indicating that an outgoing message had no valid recipients. */
"ERROR_DESCRIPTION_NO_VALID_RECIPIENTS" = "Mesaj gönderimi geçerli alıcı olmadığından başarısız oldu.";
@ -979,7 +979,7 @@
"GROUP_MEMBERS_SECTION_TITLE_MEMBERS" = "Üyeler";
/* Title for the 'no longer verified' section of the 'group members' view. */
"GROUP_MEMBERS_SECTION_TITLE_NO_LONGER_VERIFIED" = "Artık Doğrulanmış değil";
"GROUP_MEMBERS_SECTION_TITLE_NO_LONGER_VERIFIED" = "Artık Doğrulanmış Değil";
/* Button label for the 'send message to group member' button */
"GROUP_MEMBERS_SEND_MESSAGE" = "Mesaj Gönder";
@ -1075,7 +1075,7 @@
"INVITE_FRIENDS_PICKER_TITLE" = "Arkadaş Davet Et";
/* Alert warning that sending an invite to multiple users will create a group message whose recipients will be able to see each other. */
"INVITE_WARNING_MULTIPLE_INVITES_BY_TEXT" = "Aynı anda birden çok kullanıcıyı davet etmek, alıcıların birbirlerini görebilecekleri bir grup mesajı gönderir.";
"INVITE_WARNING_MULTIPLE_INVITES_BY_TEXT" = "Aynı anda birden fazla kullanıcıyı davet etmek, alıcıların birbirlerini görebilecekleri bir grup mesajı gönderir.";
/* Slider label embeds {{TIME_AMOUNT}}, e.g. '2 hours'. See *_TIME_AMOUNT strings for examples. */
"KEEP_MESSAGES_DURATION" = "Mesajlar %@ geçtikten sonra kayboluyor.";
@ -1090,7 +1090,7 @@
"LEAVE_GROUP_ACTION" = "Grupdan ayrıl";
/* report an invalid linking code */
"LINK_DEVICE_INVALID_CODE_BODY" = "Bu QR kodu geçerli değil, bağlamak istediğiniz cihazda görüntülenen QR kodunu taradığınızdan emin olun.";
"LINK_DEVICE_INVALID_CODE_BODY" = "Bu karekod geçerli değil, bağlamak istediğiniz cihazda görüntülenen karekodu taradığınızdan emin olun.";
/* report an invalid linking code */
"LINK_DEVICE_INVALID_CODE_TITLE" = "Cihaz Bağlanması Başarısız Oldu";
@ -1521,7 +1521,7 @@
"PRIVACY_VERIFICATION_FAILED_NO_SAFETY_NUMBERS_IN_CLIPBOARD" = "Signal, panonuzda herhangi bir güvenlik numarası bulamadı. Doğru şekilde kopyaladınız mı?";
/* Alert body when verifying with {{contact name}} */
"PRIVACY_VERIFICATION_FAILED_THEY_HAVE_WRONG_KEY_FOR_ME" = "Signal kullanıcılarının her çifti ayrı bir güvenlik numarası paylaşıyor. %@ *sizin* emniyet numaranızı gösterdiğini tekrar kontrol edin.";
"PRIVACY_VERIFICATION_FAILED_THEY_HAVE_WRONG_KEY_FOR_ME" = "Signal kullanıcılarının her çifti ayrı bir güvenlik numarası paylaşır. %@ *size* özel olan güvenlik numarasını görüntülüyor olduğunu kontrol edin.";
/* alert body */
"PRIVACY_VERIFICATION_FAILED_WITH_OLD_LOCAL_VERSION" = "Signal'in eski bir sürümünü kullanıyorsunuz. Doğrulamadan önce güncellemeniz gerekiyor.";
@ -2049,7 +2049,7 @@
"SETTINGS_SCREEN_SECURITY_DETAIL" = "Uygulama geçişleri esnasında Signal önizlemelerinin görülmez.";
/* Settings table section footer. */
"SETTINGS_SECTION_CALL_KIT_DESCRIPTION" = "IOS Çağrı Entegrasyonu, Signal çağrılarını kilit ekranınızda ve sistemin arama geçmişinde gösterir. İsteğe bağlı olarak kişinin adını ve numarasını da gösterebilirsiniz. ICloud etkinleştirilirse, bu arama geçmişi Apple ile paylaşılacaktır.";
"SETTINGS_SECTION_CALL_KIT_DESCRIPTION" = "IOS Çağrı Entegrasyonu, Signal çağrılarını kilit ekranınızda ve sistemin arama geçmişinde gösterir. İsteğe bağlı olarak kişinin adını ve numarasını da gösterebilirsiniz. ICloud etkinleştirilmiş ise, bu arama geçmişi Apple ile paylaşılacaktır.";
/* Label for the notifications section of conversation settings view. */
"SETTINGS_SECTION_NOTIFICATIONS" = "Bildirimler";
@ -2238,7 +2238,7 @@
"UPDATE_GROUP_CANT_REMOVE_MEMBERS_ALERT_TITLE" = "İzin Verilmiyor";
/* Description of CallKit to upgrading (existing) users */
"UPGRADE_EXPERIENCE_CALLKIT_DESCRIPTION" = "IOS çağrı entegrasyonu ile kilit ekranınızdan gelen aramaları cevaplamak kolaydır. Varsayılan olarak arayanı anonimleştiririz, bu yüzden de özeldir.";
"UPGRADE_EXPERIENCE_CALLKIT_DESCRIPTION" = "IOS çağrı entegrasyonu ile kilit ekranınızdan gelen aramaları cevaplamak kolaydır. Varsayılan olarak arayan bilgilerini saklarız.";
/* button label shown once when when user upgrades app, in context of call kit */
"UPGRADE_EXPERIENCE_CALLKIT_PRIVACY_SETTINGS_BUTTON" = "Daha fazlasını gizlilik ayarlarınızdan öğrenin.";
@ -2247,7 +2247,7 @@
"UPGRADE_EXPERIENCE_CALLKIT_TITLE" = "Cevaplamak için Sadece Kaydırın";
/* Description for notification audio customization */
"UPGRADE_EXPERIENCE_INTRODUCING_NOTIFICATION_AUDIO_DESCRIPTION" = "Artık varsayılan ve konuşma başına bildirim seslerini seçebilirsiniz ve aramalar her sistem kişisi için seçtiğiniz zil sesine uygun olacaktır.";
"UPGRADE_EXPERIENCE_INTRODUCING_NOTIFICATION_AUDIO_DESCRIPTION" = "Artık varsayılan ve sohbet başına bildirim seslerini seçebilirsiniz ve aramalar her sistem kişisi için seçtiğiniz zil sesine uygun olacaktır.";
/* button label shown one time, after upgrade */
"UPGRADE_EXPERIENCE_INTRODUCING_NOTIFICATION_AUDIO_SETTINGS_BUTTON" = "Bildirim Ayarlarını Gözden Geçirin";

@ -133,35 +133,17 @@ NS_ASSUME_NONNULL_BEGIN
if (@available(iOS 11.0, *)) {
if (OWSWindowManager.sharedManager.hasCall) {
if (UIDevice.currentDevice.isIPhoneX) {
// iPhoneX computes status bar height differently.
// IOS_DEVICE_CONSTANT
self.additionalSafeAreaInsets = UIEdgeInsetsMake(navbar.navbarWithoutStatusHeight + 20, 0, 0, 0);
} else {
self.additionalSafeAreaInsets
= UIEdgeInsetsMake(navbar.navbarWithoutStatusHeight + CurrentAppContext().statusBarHeight, 0, 0, 0);
}
self.additionalSafeAreaInsets = UIEdgeInsetsMake(20, 0, 0, 0);
} else {
self.additionalSafeAreaInsets = UIEdgeInsetsZero;
}
// in iOS11 we have to ensure the navbar frame *in* layoutSubviews.
[navbar layoutSubviews];
} else {
// Pre iOS11 we size the navbar, and position it vertically once.
// in iOS9/10 we only need to size the navbar once
[navbar sizeToFit];
if (OWSWindowManager.sharedManager.hasCall) {
CGRect oldFrame = navbar.frame;
CGRect newFrame = oldFrame;
newFrame.size.height = navbar.callBannerHeight;
navbar.frame = newFrame;
} else {
CGRect oldFrame = navbar.frame;
CGRect newFrame
= CGRectMake(oldFrame.origin.x, navbar.statusBarHeight, oldFrame.size.width, oldFrame.size.height);
navbar.frame = newFrame;
}
[navbar layoutIfNeeded];
// Since the navbar's frame was updated, we need to be sure our child VC's
// container view is updated.

@ -94,11 +94,18 @@ public class OWSNavigationBar: UINavigationBar {
if #available(iOS 11, *) {
return super.sizeThatFits(size)
} else {
// pre iOS11, sizeThatFits is repeatedly called to determine how much space to reserve for that navbar.
} else if #available(iOS 10, *) {
// iOS10
// sizeThatFits is repeatedly called to determine how much space to reserve for that navbar.
// That is, increasing this causes the child view controller to be pushed down.
// (as of iOS11, this is not used and instead we use additionalSafeAreaInsets)
return CGSize(width: fullWidth, height: navbarWithoutStatusHeight + statusBarHeight)
} else {
// iOS9
// sizeThatFits is repeatedly called to determine how much space to reserve for that navbar.
// That is, increasing this causes the child view controller to be pushed down.
// (as of iOS11, this is not used and instead we use additionalSafeAreaInsets)
return CGSize(width: fullWidth, height: navbarWithoutStatusHeight + callBannerHeight + 20)
}
}
@ -108,15 +115,16 @@ public class OWSNavigationBar: UINavigationBar {
return
}
guard #available(iOS 11, *) else {
super.layoutSubviews()
return
}
self.frame = CGRect(x: 0, y: callBannerHeight, width: fullWidth, height: navbarWithoutStatusHeight)
self.bounds = CGRect(x: 0, y: 0, width: fullWidth, height: navbarWithoutStatusHeight)
super.layoutSubviews()
guard #available(iOS 11, *) else {
return
}
// This is only necessary on iOS11, which has some private views within that lay outside of the navbar.
// They aren't actually visible behind the call status bar, but they looks strange during present/dismiss
// animations for modal VC's

@ -19,7 +19,7 @@
<key>CFBundleShortVersionString</key>
<string>2.28.0</string>
<key>CFBundleVersion</key>
<string>2.28.0.13</string>
<string>2.28.0.15</string>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>NSAppTransportSecurity</key>

Loading…
Cancel
Save