diff --git a/Signal.xcodeproj/project.pbxproj b/Signal.xcodeproj/project.pbxproj index f43fe4359..e795afba6 100644 --- a/Signal.xcodeproj/project.pbxproj +++ b/Signal.xcodeproj/project.pbxproj @@ -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 = ""; }; 45FBC5D01DF8592E00E9B410 /* SignalCall.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SignalCall.swift; sourceTree = ""; }; 45FDA43420A4D22700396358 /* OWSNavigationBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OWSNavigationBar.swift; sourceTree = ""; }; + 4C090A1A210FD9C7001FD7F9 /* HapticFeedback.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = HapticFeedback.swift; path = UserInterface/HapticFeedback.swift; sourceTree = ""; }; 4C11AA4F20FD59C700351FBD /* MessageStatusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageStatusView.swift; sourceTree = ""; }; 4C13C9F520E57BA30089A98B /* ColorPickerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorPickerViewController.swift; sourceTree = ""; }; 4C20B2B820CA10DE001BAC90 /* ConversationSearchViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationSearchViewController.swift; sourceTree = ""; }; @@ -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 */, diff --git a/Signal/Signal-Info.plist b/Signal/Signal-Info.plist index ef60dcb6f..cd17c4326 100644 --- a/Signal/Signal-Info.plist +++ b/Signal/Signal-Info.plist @@ -38,7 +38,7 @@ CFBundleVersion - 2.28.0.13 + 2.28.0.15 ITSAppUsesNonExemptEncryption LOGS_EMAIL diff --git a/Signal/src/UserInterface/HapticFeedback.swift b/Signal/src/UserInterface/HapticFeedback.swift new file mode 100644 index 000000000..beb47fd6a --- /dev/null +++ b/Signal/src/UserInterface/HapticFeedback.swift @@ -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() + } +} diff --git a/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageBubbleView.m b/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageBubbleView.m index 277eb50db..7d5292917 100644 --- a/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageBubbleView.m +++ b/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageBubbleView.m @@ -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 diff --git a/Signal/src/ViewControllers/ConversationView/ConversationViewController.m b/Signal/src/ViewControllers/ConversationView/ConversationViewController.m index 7ac9d2e89..a3b643086 100644 --- a/Signal/src/ViewControllers/ConversationView/ConversationViewController.m +++ b/Signal/src/ViewControllers/ConversationView/ConversationViewController.m @@ -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; } diff --git a/Signal/src/ViewControllers/HomeView/HomeViewController.m b/Signal/src/ViewControllers/HomeView/HomeViewController.m index 0c0c92c6c..9d3baf52f 100644 --- a/Signal/src/ViewControllers/HomeView/HomeViewController.m +++ b/Signal/src/ViewControllers/HomeView/HomeViewController.m @@ -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 () )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]; diff --git a/Signal/src/ViewControllers/MenuActionsViewController.swift b/Signal/src/ViewControllers/MenuActionsViewController.swift index d18f28aa1..cf9f22813 100644 --- a/Signal/src/ViewControllers/MenuActionsViewController.swift +++ b/Signal/src/ViewControllers/MenuActionsViewController.swift @@ -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 { diff --git a/Signal/src/views/RemoteVideoView.m b/Signal/src/views/RemoteVideoView.m index a52f935f1..416394017 100644 --- a/Signal/src/views/RemoteVideoView.m +++ b/Signal/src/views/RemoteVideoView.m @@ -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 diff --git a/Signal/translations/da.lproj/Localizable.strings b/Signal/translations/da.lproj/Localizable.strings index b518f8d40..1b839258d 100644 --- a/Signal/translations/da.lproj/Localizable.strings +++ b/Signal/translations/da.lproj/Localizable.strings @@ -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"; diff --git a/Signal/translations/de.lproj/Localizable.strings b/Signal/translations/de.lproj/Localizable.strings index 5812d64e8..6d26d8cb1 100644 --- a/Signal/translations/de.lproj/Localizable.strings +++ b/Signal/translations/de.lproj/Localizable.strings @@ -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"; diff --git a/Signal/translations/es.lproj/Localizable.strings b/Signal/translations/es.lproj/Localizable.strings index 429b96f82..c1be91fdc 100644 --- a/Signal/translations/es.lproj/Localizable.strings +++ b/Signal/translations/es.lproj/Localizable.strings @@ -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 %@."; diff --git a/Signal/translations/id.lproj/Localizable.strings b/Signal/translations/id.lproj/Localizable.strings index a8e9cae6c..b589da6d7 100644 --- a/Signal/translations/id.lproj/Localizable.strings +++ b/Signal/translations/id.lproj/Localizable.strings @@ -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"; diff --git a/Signal/translations/it_IT.lproj/Localizable.strings b/Signal/translations/it_IT.lproj/Localizable.strings index b1e4e7f9f..02e583fab 100644 --- a/Signal/translations/it_IT.lproj/Localizable.strings +++ b/Signal/translations/it_IT.lproj/Localizable.strings @@ -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!"; diff --git a/Signal/translations/th_TH.lproj/Localizable.strings b/Signal/translations/th_TH.lproj/Localizable.strings index 3dec917cd..12c53c625 100644 --- a/Signal/translations/th_TH.lproj/Localizable.strings +++ b/Signal/translations/th_TH.lproj/Localizable.strings @@ -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%@"; diff --git a/Signal/translations/tr_TR.lproj/Localizable.strings b/Signal/translations/tr_TR.lproj/Localizable.strings index 543f1b058..5f82040f0 100644 --- a/Signal/translations/tr_TR.lproj/Localizable.strings +++ b/Signal/translations/tr_TR.lproj/Localizable.strings @@ -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"; diff --git a/SignalMessaging/ViewControllers/OWSNavigationController.m b/SignalMessaging/ViewControllers/OWSNavigationController.m index 264e193dd..e6233463d 100644 --- a/SignalMessaging/ViewControllers/OWSNavigationController.m +++ b/SignalMessaging/ViewControllers/OWSNavigationController.m @@ -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. diff --git a/SignalMessaging/Views/OWSNavigationBar.swift b/SignalMessaging/Views/OWSNavigationBar.swift index bf519cc08..49ce69bc5 100644 --- a/SignalMessaging/Views/OWSNavigationBar.swift +++ b/SignalMessaging/Views/OWSNavigationBar.swift @@ -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 diff --git a/SignalShareExtension/Info.plist b/SignalShareExtension/Info.plist index c313a2949..4727a5757 100644 --- a/SignalShareExtension/Info.plist +++ b/SignalShareExtension/Info.plist @@ -19,7 +19,7 @@ CFBundleShortVersionString 2.28.0 CFBundleVersion - 2.28.0.13 + 2.28.0.15 ITSAppUsesNonExemptEncryption NSAppTransportSecurity