mirror of
https://github.com/oxen-io/session-ios.git
synced 2023-12-13 21:30:14 +01:00
Merge tag '2.34.0.26'
This commit is contained in:
commit
8c7c9b27a2
55 changed files with 714 additions and 339 deletions
|
@ -337,7 +337,6 @@
|
|||
452037D11EE84975004E4CDF /* DebugUISessionState.m in Sources */ = {isa = PBXBuildFile; fileRef = 452037D01EE84975004E4CDF /* DebugUISessionState.m */; };
|
||||
4520D8D51D417D8E00123472 /* Photos.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4520D8D41D417D8E00123472 /* Photos.framework */; };
|
||||
4521C3C01F59F3BA00B4C582 /* TextFieldHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4521C3BF1F59F3BA00B4C582 /* TextFieldHelper.swift */; };
|
||||
452314A01F7E9E18003A428C /* DirectionalPanGestureRecognizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4523149F1F7E9E18003A428C /* DirectionalPanGestureRecognizer.swift */; };
|
||||
4523D016206EDC2B00A2AB51 /* LRUCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4523D015206EDC2B00A2AB51 /* LRUCache.swift */; };
|
||||
452B999020A34B6B006F2F9E /* AddContactShareToExistingContactViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 452B998F20A34B6B006F2F9E /* AddContactShareToExistingContactViewController.swift */; };
|
||||
452C468F1E427E200087B011 /* OutboundCallInitiator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 452C468E1E427E200087B011 /* OutboundCallInitiator.swift */; };
|
||||
|
@ -450,6 +449,8 @@
|
|||
4C20B2B920CA10DE001BAC90 /* ConversationSearchViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C20B2B820CA10DE001BAC90 /* ConversationSearchViewController.swift */; };
|
||||
4C23A5F2215C4ADE00534937 /* SheetViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C23A5F1215C4ADE00534937 /* SheetViewController.swift */; };
|
||||
4C2F454F214C00E1004871FF /* AvatarTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C2F454E214C00E1004871FF /* AvatarTableViewCell.swift */; };
|
||||
4C3E245C21F29FCE000AE092 /* Toast.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CA5F792211E1F06008C2708 /* Toast.swift */; };
|
||||
4C3E245D21F2B395000AE092 /* DirectionalPanGestureRecognizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4523149F1F7E9E18003A428C /* DirectionalPanGestureRecognizer.swift */; };
|
||||
4C3EF7FD2107DDEE0007EBF7 /* ParamParserTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C3EF7FC2107DDEE0007EBF7 /* ParamParserTest.swift */; };
|
||||
4C3EF802210918740007EBF7 /* SSKProtoEnvelopeTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C3EF801210918740007EBF7 /* SSKProtoEnvelopeTest.swift */; };
|
||||
4C4AEC4520EC343B0020E72B /* DismissableTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C4AEC4420EC343B0020E72B /* DismissableTextField.swift */; };
|
||||
|
@ -466,7 +467,6 @@
|
|||
4C9CA25D217E676900607C63 /* ZXingObjC.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4C9CA25C217E676900607C63 /* ZXingObjC.framework */; };
|
||||
4CA46F4C219CCC630038ABDE /* CaptionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CA46F4B219CCC630038ABDE /* CaptionView.swift */; };
|
||||
4CA46F4D219CFDAA0038ABDE /* GalleryRailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CA46F49219C78050038ABDE /* GalleryRailView.swift */; };
|
||||
4CA5F793211E1F06008C2708 /* Toast.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CA5F792211E1F06008C2708 /* Toast.swift */; };
|
||||
4CB5F26720F6E1E2004D1B42 /* MenuActionsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CFF4C0920F55BBA005DA313 /* MenuActionsViewController.swift */; };
|
||||
4CB5F26920F7D060004D1B42 /* MessageActions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CB5F26820F7D060004D1B42 /* MessageActions.swift */; };
|
||||
4CB93DC22180FF07004B9764 /* ProximityMonitoringManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CB93DC12180FF07004B9764 /* ProximityMonitoringManager.swift */; };
|
||||
|
@ -1720,6 +1720,7 @@
|
|||
346129CE1FD207F200532771 /* Views */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4523149F1F7E9E18003A428C /* DirectionalPanGestureRecognizer.swift */,
|
||||
34AC0A0C211B39EA00997B47 /* AvatarImageView.swift */,
|
||||
34AC0A07211B39E900997B47 /* CommonStrings.swift */,
|
||||
34AC0A0A211B39EA00997B47 /* ContactCellView.h */,
|
||||
|
@ -1747,6 +1748,7 @@
|
|||
34AC09FD211B39E700997B47 /* TappableView.swift */,
|
||||
34AC0A0D211B39EA00997B47 /* ThreadViewHelper.h */,
|
||||
34AC0A0B211B39EA00997B47 /* ThreadViewHelper.m */,
|
||||
4CA5F792211E1F06008C2708 /* Toast.swift */,
|
||||
34AC0A04211B39E800997B47 /* VideoPlayerView.swift */,
|
||||
);
|
||||
path = Views;
|
||||
|
@ -2334,7 +2336,6 @@
|
|||
4C2F454E214C00E1004871FF /* AvatarTableViewCell.swift */,
|
||||
4CA46F4B219CCC630038ABDE /* CaptionView.swift */,
|
||||
451764291DE939FD00EDB8B9 /* ContactCell.swift */,
|
||||
4523149F1F7E9E18003A428C /* DirectionalPanGestureRecognizer.swift */,
|
||||
4C4AEC4420EC343B0020E72B /* DismissableTextField.swift */,
|
||||
45A663C41F92EC760027B59E /* GroupTableViewCell.swift */,
|
||||
34129B8521EF8779005457A8 /* LinkPreviewView.swift */,
|
||||
|
@ -2353,7 +2354,6 @@
|
|||
45A6DAD51EBBF85500893231 /* ReminderView.swift */,
|
||||
450D19111F85236600970622 /* RemoteVideoView.h */,
|
||||
450D19121F85236600970622 /* RemoteVideoView.m */,
|
||||
4CA5F792211E1F06008C2708 /* Toast.swift */,
|
||||
34B6A902218B3F62007C4606 /* TypingIndicatorView.swift */,
|
||||
);
|
||||
name = Views;
|
||||
|
@ -3314,6 +3314,7 @@
|
|||
34074F61203D0CBE004596AE /* OWSSounds.m in Sources */,
|
||||
34BEDB1721C80BCA007B0EAE /* OWSAnyTouchGestureRecognizer.m in Sources */,
|
||||
34B6A909218B8824007C4606 /* OWS112TypingIndicatorsMigration.swift in Sources */,
|
||||
4C3E245D21F2B395000AE092 /* DirectionalPanGestureRecognizer.swift in Sources */,
|
||||
346129B51FD1F7E800532771 /* OWSProfileManager.m in Sources */,
|
||||
4CBBCA6321714B4500EEB37D /* OWS110SortIdMigration.swift in Sources */,
|
||||
342950832124C9750000B063 /* OWSTextView.m in Sources */,
|
||||
|
@ -3391,6 +3392,7 @@
|
|||
4C618199219DF03A009BD6B5 /* OWSButton.swift in Sources */,
|
||||
4598198F204E2F28009414F2 /* OWS108CallLoggingPreference.m in Sources */,
|
||||
34AC09F3211B39B100997B47 /* NewNonContactConversationViewController.m in Sources */,
|
||||
4C3E245C21F29FCE000AE092 /* Toast.swift in Sources */,
|
||||
34AC09FA211B39B100997B47 /* SharingThreadPickerViewController.m in Sources */,
|
||||
45F59A082028E4FB00E8D2B0 /* OWSAudioSession.swift in Sources */,
|
||||
34612A071FD7238600532771 /* OWSSyncManager.m in Sources */,
|
||||
|
@ -3512,7 +3514,6 @@
|
|||
4556FA681F54AA9500AF40DD /* DebugUIProfile.swift in Sources */,
|
||||
45A6DAD61EBBF85500893231 /* ReminderView.swift in Sources */,
|
||||
34D1F0881F8678AA0066283D /* ConversationViewLayout.m in Sources */,
|
||||
452314A01F7E9E18003A428C /* DirectionalPanGestureRecognizer.swift in Sources */,
|
||||
34330AA31E79686200DF2FB9 /* OWSProgressView.m in Sources */,
|
||||
344825C6211390C800DB4BD8 /* OWSOrphanDataCleaner.m in Sources */,
|
||||
45D2AC02204885170033C692 /* OWS2FAReminderViewController.swift in Sources */,
|
||||
|
@ -3569,7 +3570,6 @@
|
|||
34277A5E20751BDC006049F2 /* OWSQuotedMessageView.m in Sources */,
|
||||
458DE9D61DEE3FD00071BB03 /* PeerConnectionClient.swift in Sources */,
|
||||
45DDA6242090CEB500DE97F8 /* ConversationHeaderView.swift in Sources */,
|
||||
4CA5F793211E1F06008C2708 /* Toast.swift in Sources */,
|
||||
3488F9362191CC4000E524CC /* ConversationMediaView.swift in Sources */,
|
||||
45F32C242057297A00A300D5 /* MessageDetailViewController.swift in Sources */,
|
||||
3496955C219B605E00DCFE74 /* ImagePickerController.swift in Sources */,
|
||||
|
|
|
@ -47,7 +47,7 @@
|
|||
</dict>
|
||||
</array>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>2.34.0.25</string>
|
||||
<string>2.34.0.26</string>
|
||||
<key>ITSAppUsesNonExemptEncryption</key>
|
||||
<false/>
|
||||
<key>LOGS_EMAIL</key>
|
||||
|
|
|
@ -1173,6 +1173,9 @@ typedef enum : NSUInteger {
|
|||
{
|
||||
[super viewDidAppear:animated];
|
||||
|
||||
// recover status bar when returning from PhotoPicker, which is dark (uses light status bar)
|
||||
[self setNeedsStatusBarAppearanceUpdate];
|
||||
|
||||
[ProfileFetcherJob runWithThread:self.thread];
|
||||
[self markVisibleMessagesAsRead];
|
||||
[self startReadTimer];
|
||||
|
|
|
@ -9,7 +9,6 @@
|
|||
#import "OWSMessageHeaderView.h"
|
||||
#import "OWSSystemMessageCell.h"
|
||||
#import "Signal-Swift.h"
|
||||
#import <AssetsLibrary/AssetsLibrary.h>
|
||||
#import <SignalMessaging/OWSUnreadIndicator.h>
|
||||
#import <SignalServiceKit/NSData+Image.h>
|
||||
#import <SignalServiceKit/NSString+SSK.h>
|
||||
|
@ -1020,41 +1019,59 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType)
|
|||
break;
|
||||
}
|
||||
case OWSMessageCellType_MediaAlbum: {
|
||||
// TODO: Use PHPhotoLibrary.
|
||||
ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
|
||||
for (ConversationMediaAlbumItem *mediaAlbumItem in self.mediaAlbumItems) {
|
||||
if (!mediaAlbumItem.attachmentStream) {
|
||||
continue;
|
||||
}
|
||||
if (!mediaAlbumItem.attachmentStream.isValidVisualMedia) {
|
||||
continue;
|
||||
}
|
||||
if (mediaAlbumItem.attachmentStream.isImage || mediaAlbumItem.attachmentStream.isAnimated) {
|
||||
NSData *data = [NSData dataWithContentsOfURL:[mediaAlbumItem.attachmentStream originalMediaURL]];
|
||||
if (!data) {
|
||||
OWSFailDebug(@"Could not load image data");
|
||||
continue;
|
||||
}
|
||||
[library writeImageDataToSavedPhotosAlbum:data
|
||||
metadata:nil
|
||||
completionBlock:^(NSURL *assetURL, NSError *error) {
|
||||
if (error) {
|
||||
OWSLogWarn(@"Error saving image to photo album: %@", error);
|
||||
}
|
||||
}];
|
||||
}
|
||||
if (mediaAlbumItem.attachmentStream.isVideo) {
|
||||
if (UIVideoAtPathIsCompatibleWithSavedPhotosAlbum(
|
||||
mediaAlbumItem.attachmentStream.originalFilePath)) {
|
||||
UISaveVideoAtPathToSavedPhotosAlbum(
|
||||
mediaAlbumItem.attachmentStream.originalFilePath, self, nil, nil);
|
||||
}
|
||||
}
|
||||
}
|
||||
[self saveMediaAlbumItems];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)saveMediaAlbumItems
|
||||
{
|
||||
// We need to do these writes serially to avoid "write busy" errors
|
||||
// from too many concurrent asset saves.
|
||||
[self saveMediaAlbumItems:[self.mediaAlbumItems mutableCopy]];
|
||||
}
|
||||
|
||||
- (void)saveMediaAlbumItems:(NSMutableArray<ConversationMediaAlbumItem *> *)mediaAlbumItems
|
||||
{
|
||||
if (mediaAlbumItems.count < 1) {
|
||||
return;
|
||||
}
|
||||
ConversationMediaAlbumItem *mediaAlbumItem = mediaAlbumItems.firstObject;
|
||||
[mediaAlbumItems removeObjectAtIndex:0];
|
||||
|
||||
if (!mediaAlbumItem.attachmentStream || !mediaAlbumItem.attachmentStream.isValidVisualMedia) {
|
||||
// Skip this item.
|
||||
} else if (mediaAlbumItem.attachmentStream.isImage || mediaAlbumItem.attachmentStream.isAnimated) {
|
||||
[[PHPhotoLibrary sharedPhotoLibrary]
|
||||
performChanges:^{
|
||||
[PHAssetChangeRequest
|
||||
creationRequestForAssetFromImageAtFileURL:mediaAlbumItem.attachmentStream.originalMediaURL];
|
||||
}
|
||||
completionHandler:^(BOOL success, NSError *error) {
|
||||
if (error || !success) {
|
||||
OWSFailDebug(@"Image save failed: %@", error);
|
||||
}
|
||||
[self saveMediaAlbumItems:mediaAlbumItems];
|
||||
}];
|
||||
return;
|
||||
} else if (mediaAlbumItem.attachmentStream.isVideo) {
|
||||
[[PHPhotoLibrary sharedPhotoLibrary]
|
||||
performChanges:^{
|
||||
[PHAssetChangeRequest
|
||||
creationRequestForAssetFromVideoAtFileURL:mediaAlbumItem.attachmentStream.originalMediaURL];
|
||||
}
|
||||
completionHandler:^(BOOL success, NSError *error) {
|
||||
if (error || !success) {
|
||||
OWSFailDebug(@"Video save failed: %@", error);
|
||||
}
|
||||
[self saveMediaAlbumItems:mediaAlbumItems];
|
||||
}];
|
||||
return;
|
||||
}
|
||||
return [self saveMediaAlbumItems:mediaAlbumItems];
|
||||
}
|
||||
|
||||
- (void)deleteAction
|
||||
{
|
||||
[self.interaction remove];
|
||||
|
|
|
@ -23,9 +23,7 @@ class ImagePickerGridController: UICollectionViewController, PhotoLibraryDelegat
|
|||
private let photoMediaSize = PhotoMediaSize()
|
||||
|
||||
var collectionViewFlowLayout: UICollectionViewFlowLayout
|
||||
|
||||
private let titleLabel = UILabel()
|
||||
private let titleIconView = UIImageView()
|
||||
var titleView: TitleView!
|
||||
|
||||
// We use NSMutableOrderedSet so that we can honor selection order.
|
||||
private let selectedIds = NSMutableOrderedSet()
|
||||
|
@ -67,31 +65,110 @@ class ImagePickerGridController: UICollectionViewController, PhotoLibraryDelegat
|
|||
cancelButton.tintColor = .ows_gray05
|
||||
navigationItem.leftBarButtonItem = cancelButton
|
||||
|
||||
let titleView = TitleView()
|
||||
titleView.delegate = self
|
||||
titleView.text = photoCollection.localizedTitle()
|
||||
|
||||
if #available(iOS 11, *) {
|
||||
titleLabel.text = photoCollection.localizedTitle()
|
||||
titleLabel.textColor = .ows_gray05
|
||||
titleLabel.font = UIFont.ows_dynamicTypeBody.ows_mediumWeight()
|
||||
|
||||
titleIconView.tintColor = .ows_gray05
|
||||
titleIconView.image = UIImage(named: "navbar_disclosure_down")?.withRenderingMode(.alwaysTemplate)
|
||||
|
||||
let titleView = UIStackView(arrangedSubviews: [titleLabel, titleIconView])
|
||||
titleView.axis = .horizontal
|
||||
titleView.alignment = .center
|
||||
titleView.spacing = 5
|
||||
titleView.isUserInteractionEnabled = true
|
||||
titleView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(titleTapped)))
|
||||
navigationItem.titleView = titleView
|
||||
// do nothing
|
||||
} else {
|
||||
navigationItem.title = photoCollection.localizedTitle()
|
||||
// must assign titleView frame manually on older iOS
|
||||
titleView.frame = CGRect(origin: .zero, size: titleView.systemLayoutSizeFitting(UILayoutFittingCompressedSize))
|
||||
}
|
||||
|
||||
navigationItem.titleView = titleView
|
||||
self.titleView = titleView
|
||||
|
||||
let featureFlag_isMultiselectEnabled = true
|
||||
if featureFlag_isMultiselectEnabled {
|
||||
updateSelectButton()
|
||||
}
|
||||
|
||||
collectionView.backgroundColor = .ows_gray95
|
||||
|
||||
let selectionPanGesture = DirectionalPanGestureRecognizer(direction: [.horizontal], target: self, action: #selector(didPanSelection))
|
||||
selectionPanGesture.delegate = self
|
||||
self.selectionPanGesture = selectionPanGesture
|
||||
collectionView.addGestureRecognizer(selectionPanGesture)
|
||||
}
|
||||
|
||||
var selectionPanGesture: UIPanGestureRecognizer?
|
||||
enum BatchSelectionGestureMode {
|
||||
case select, deselect
|
||||
}
|
||||
var selectionPanGestureMode: BatchSelectionGestureMode = .select
|
||||
|
||||
@objc
|
||||
func didPanSelection(_ selectionPanGesture: UIPanGestureRecognizer) {
|
||||
guard isInBatchSelectMode else {
|
||||
return
|
||||
}
|
||||
|
||||
guard let collectionView = collectionView else {
|
||||
owsFailDebug("collectionView was unexpectedly nil")
|
||||
return
|
||||
}
|
||||
|
||||
switch selectionPanGesture.state {
|
||||
case .possible:
|
||||
break
|
||||
case .began:
|
||||
collectionView.isUserInteractionEnabled = false
|
||||
collectionView.isScrollEnabled = false
|
||||
|
||||
let location = selectionPanGesture.location(in: collectionView)
|
||||
guard let indexPath = collectionView.indexPathForItem(at: location) else {
|
||||
return
|
||||
}
|
||||
let asset = photoCollectionContents.asset(at: indexPath.item)
|
||||
if selectedIds.contains(asset.localIdentifier) {
|
||||
selectionPanGestureMode = .deselect
|
||||
} else {
|
||||
selectionPanGestureMode = .select
|
||||
}
|
||||
case .changed:
|
||||
let location = selectionPanGesture.location(in: collectionView)
|
||||
guard let indexPath = collectionView.indexPathForItem(at: location) else {
|
||||
return
|
||||
}
|
||||
tryToToggleBatchSelect(at: indexPath)
|
||||
case .cancelled, .ended, .failed:
|
||||
collectionView.isUserInteractionEnabled = true
|
||||
collectionView.isScrollEnabled = true
|
||||
}
|
||||
}
|
||||
|
||||
func tryToToggleBatchSelect(at indexPath: IndexPath) {
|
||||
guard isInBatchSelectMode else {
|
||||
owsFailDebug("isInBatchSelectMode was unexpectedly false")
|
||||
return
|
||||
}
|
||||
|
||||
guard let collectionView = collectionView else {
|
||||
owsFailDebug("collectionView was unexpectedly nil")
|
||||
return
|
||||
}
|
||||
|
||||
let asset = photoCollectionContents.asset(at: indexPath.item)
|
||||
switch selectionPanGestureMode {
|
||||
case .select:
|
||||
guard canSelectAdditionalItems else {
|
||||
showTooManySelectedToast()
|
||||
return
|
||||
}
|
||||
|
||||
selectedIds.add(asset.localIdentifier)
|
||||
collectionView.selectItem(at: indexPath, animated: true, scrollPosition: [])
|
||||
case .deselect:
|
||||
selectedIds.remove(asset.localIdentifier)
|
||||
collectionView.deselectItem(at: indexPath, animated: true)
|
||||
}
|
||||
|
||||
updateDoneButton()
|
||||
}
|
||||
|
||||
var canSelectAdditionalItems: Bool {
|
||||
return selectedIds.count <= SignalAttachment.maxAttachmentsAllowed
|
||||
}
|
||||
|
||||
override func viewWillLayoutSubviews() {
|
||||
|
@ -120,6 +197,21 @@ class ImagePickerGridController: UICollectionViewController, PhotoLibraryDelegat
|
|||
}
|
||||
}
|
||||
|
||||
override func viewSafeAreaInsetsDidChange() {
|
||||
if !hasEverAppeared {
|
||||
// To scroll precisely to the bottom of the content, we have to account for the space
|
||||
// taken up by the navbar and any notch.
|
||||
//
|
||||
// Before iOS11 the system accounts for this by assigning contentInset to the scrollView
|
||||
// which is available by the time `viewWillAppear` is called.
|
||||
//
|
||||
// On iOS11+, contentInsets are not assigned to the scrollView in `viewWillAppear`, but
|
||||
// this method, `viewSafeAreaInsetsDidChange` is called *between* `viewWillAppear` and
|
||||
// `viewDidAppear` and indicates `safeAreaInsets` have been assigned.
|
||||
scrollToBottom(animated: false)
|
||||
}
|
||||
}
|
||||
|
||||
override func viewDidAppear(_ animated: Bool) {
|
||||
super.viewDidAppear(animated)
|
||||
|
||||
|
@ -157,38 +249,12 @@ class ImagePickerGridController: UICollectionViewController, PhotoLibraryDelegat
|
|||
return
|
||||
}
|
||||
|
||||
var verticalOffset: CGFloat
|
||||
|
||||
let visibleHeight = collectionView.bounds.height - collectionView.contentInset.top
|
||||
let contentHeight = collectionView.contentSize.height
|
||||
if contentHeight <= visibleHeight {
|
||||
verticalOffset = -collectionView.contentInset.top
|
||||
} else {
|
||||
let topOfLastPage = contentHeight - collectionView.bounds.height
|
||||
verticalOffset = topOfLastPage
|
||||
let lastSection = collectionView.numberOfSections - 1
|
||||
let lastItem = collectionView.numberOfItems(inSection: lastSection) - 1
|
||||
if lastSection >= 0 && lastItem >= 0 {
|
||||
let lastIndex = IndexPath(item: lastItem, section: lastSection)
|
||||
collectionView.scrollToItem(at: lastIndex, at: .bottom, animated: animated)
|
||||
}
|
||||
|
||||
if #available(iOS 11, *) {
|
||||
if hasEverAppeared {
|
||||
verticalOffset += collectionView.safeAreaInsets.bottom
|
||||
} else {
|
||||
// On iOS10 and earlier, we can be precise, but as of iOS11 `collectionView.contentInset`
|
||||
// is based on `safeAreaInsets`, which isn't accurate until `viewDidAppear` at the earliest.
|
||||
//
|
||||
// from https://developer.apple.com/documentation/uikit/uiview/positioning_content_relative_to_the_safe_area
|
||||
// > Make your modifications in [viewDidAppear] because the safe area insets for a view are
|
||||
// > not accurate until the view is added to a view hierarchy.
|
||||
//
|
||||
// Overshooting like this works without visible animation glitch. on iOS11+
|
||||
// However, before iOS11, "overshooting" the contentOffset like this produces a broken
|
||||
// layout or hanging. Luckily for those versions, before the safeAreaInset feature
|
||||
// existed, we can accurately access collectionView.contentInset before `viewDidAppear`
|
||||
// and calculate a precise content offset.
|
||||
verticalOffset += 122
|
||||
}
|
||||
}
|
||||
|
||||
collectionView.setContentOffset(CGPoint(x: 0, y: verticalOffset), animated: animated)
|
||||
}
|
||||
|
||||
private func reloadDataAndRestoreSelection() {
|
||||
|
@ -400,6 +466,27 @@ class ImagePickerGridController: UICollectionViewController, PhotoLibraryDelegat
|
|||
}
|
||||
}
|
||||
|
||||
func showTooManySelectedToast() {
|
||||
Logger.info("")
|
||||
|
||||
guard let collectionView = collectionView else {
|
||||
owsFailDebug("collectionView was unexpectedly nil")
|
||||
return
|
||||
}
|
||||
|
||||
let toastFormat = NSLocalizedString("IMAGE_PICKER_CAN_SELECT_NO_MORE_TOAST_FORMAT",
|
||||
comment: "Momentarily shown to the user when attempting to select more images than is allowed. Embeds {{max number of items}} that can be shared.")
|
||||
|
||||
let toastText = String(format: toastFormat, NSNumber(value: SignalAttachment.maxAttachmentsAllowed))
|
||||
|
||||
let toastController = ToastController(text: toastText)
|
||||
|
||||
let kToastInset: CGFloat = 10
|
||||
let bottomInset = kToastInset + collectionView.contentInset.bottom + view.layoutMargins.bottom
|
||||
|
||||
toastController.presentToastView(fromBottomOfView: view, inset: bottomInset)
|
||||
}
|
||||
|
||||
// MARK: - PhotoLibraryDelegate
|
||||
|
||||
func photoLibraryDidChange(_ photoLibrary: PhotoLibrary) {
|
||||
|
@ -444,9 +531,7 @@ class ImagePickerGridController: UICollectionViewController, PhotoLibraryDelegat
|
|||
|
||||
self.updateSelectButton()
|
||||
|
||||
// *slightly* more than `pi` to ensure the chevron animates counter-clockwise
|
||||
let chevronRotationAngle = CGFloat.pi + 0.001
|
||||
self.titleIconView.transform = CGAffineTransform(rotationAngle: chevronRotationAngle)
|
||||
self.titleView.rotateIcon(.up)
|
||||
}.retainUntilComplete()
|
||||
}
|
||||
|
||||
|
@ -463,7 +548,7 @@ class ImagePickerGridController: UICollectionViewController, PhotoLibraryDelegat
|
|||
|
||||
self.updateSelectButton()
|
||||
|
||||
self.titleIconView.transform = .identity
|
||||
self.titleView.rotateIcon(.down)
|
||||
}.done { _ in
|
||||
collectionPickerController.view.removeFromSuperview()
|
||||
collectionPickerController.removeFromParentViewController()
|
||||
|
@ -484,30 +569,13 @@ class ImagePickerGridController: UICollectionViewController, PhotoLibraryDelegat
|
|||
photoCollection = collection
|
||||
photoCollectionContents = photoCollection.contents()
|
||||
|
||||
if #available(iOS 11, *) {
|
||||
titleLabel.text = photoCollection.localizedTitle()
|
||||
} else {
|
||||
navigationItem.title = photoCollection.localizedTitle()
|
||||
}
|
||||
titleView.text = photoCollection.localizedTitle()
|
||||
|
||||
collectionView?.reloadData()
|
||||
scrollToBottom(animated: false)
|
||||
hideCollectionPicker()
|
||||
}
|
||||
|
||||
// MARK: - Event Handlers
|
||||
|
||||
@objc func titleTapped(sender: UIGestureRecognizer) {
|
||||
guard sender.state == .recognized else {
|
||||
return
|
||||
}
|
||||
if isShowingCollectionPickerController {
|
||||
hideCollectionPicker()
|
||||
} else {
|
||||
showCollectionPicker()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - UICollectionView
|
||||
|
||||
override func collectionView(_ collectionView: UICollectionView, shouldSelectItemAt indexPath: IndexPath) -> Bool {
|
||||
|
@ -515,7 +583,12 @@ class ImagePickerGridController: UICollectionViewController, PhotoLibraryDelegat
|
|||
return true
|
||||
}
|
||||
|
||||
return indexPathsForSelectedItems.count < SignalAttachment.maxAttachmentsAllowed
|
||||
if (indexPathsForSelectedItems.count < SignalAttachment.maxAttachmentsAllowed) {
|
||||
return true
|
||||
} else {
|
||||
showTooManySelectedToast()
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
|
||||
|
@ -613,3 +686,107 @@ class ImagePickerGridController: UICollectionViewController, PhotoLibraryDelegat
|
|||
assetIdToCommentMap[assetId] = captionText
|
||||
}
|
||||
}
|
||||
|
||||
extension ImagePickerGridController: UIGestureRecognizerDelegate {
|
||||
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
|
||||
// Ensure we can still scroll the collectionView by allowing other gestures to
|
||||
// take precedence.
|
||||
guard otherGestureRecognizer == selectionPanGesture else {
|
||||
return true
|
||||
}
|
||||
|
||||
// Once we've startd the selectionPanGesture, don't allow scrolling
|
||||
if otherGestureRecognizer.state == .began || otherGestureRecognizer.state == .changed {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
protocol TitleViewDelegate: class {
|
||||
func titleViewWasTapped(_ titleView: TitleView)
|
||||
}
|
||||
|
||||
class TitleView: UIView {
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private let label = UILabel()
|
||||
private let iconView = UIImageView()
|
||||
private let stackView: UIStackView
|
||||
|
||||
// MARK: - Initializers
|
||||
|
||||
override init(frame: CGRect) {
|
||||
let stackView = UIStackView(arrangedSubviews: [label, iconView])
|
||||
stackView.axis = .horizontal
|
||||
stackView.alignment = .center
|
||||
stackView.spacing = 5
|
||||
stackView.isUserInteractionEnabled = true
|
||||
|
||||
self.stackView = stackView
|
||||
|
||||
super.init(frame: frame)
|
||||
|
||||
addSubview(stackView)
|
||||
stackView.autoPinEdgesToSuperviewEdges()
|
||||
|
||||
label.textColor = .ows_gray05
|
||||
label.font = UIFont.ows_dynamicTypeBody.ows_mediumWeight()
|
||||
|
||||
iconView.tintColor = .ows_gray05
|
||||
iconView.image = UIImage(named: "navbar_disclosure_down")?.withRenderingMode(.alwaysTemplate)
|
||||
|
||||
addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(titleTapped)))
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
// MARK: - Public
|
||||
|
||||
weak var delegate: TitleViewDelegate?
|
||||
|
||||
public var text: String? {
|
||||
get {
|
||||
return label.text
|
||||
}
|
||||
set {
|
||||
label.text = newValue
|
||||
}
|
||||
}
|
||||
|
||||
public enum TitleViewRotationDirection {
|
||||
case up, down
|
||||
}
|
||||
|
||||
public func rotateIcon(_ direction: TitleViewRotationDirection) {
|
||||
switch direction {
|
||||
case .up:
|
||||
// *slightly* more than `pi` to ensure the chevron animates counter-clockwise
|
||||
let chevronRotationAngle = CGFloat.pi + 0.001
|
||||
iconView.transform = CGAffineTransform(rotationAngle: chevronRotationAngle)
|
||||
case .down:
|
||||
iconView.transform = .identity
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Events
|
||||
|
||||
@objc
|
||||
func titleTapped(_ tapGesture: UITapGestureRecognizer) {
|
||||
self.delegate?.titleViewWasTapped(self)
|
||||
}
|
||||
}
|
||||
|
||||
extension ImagePickerGridController: TitleViewDelegate {
|
||||
func titleViewWasTapped(_ titleView: TitleView) {
|
||||
if isShowingCollectionPickerController {
|
||||
hideCollectionPicker()
|
||||
} else {
|
||||
showCollectionPicker()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,68 +0,0 @@
|
|||
//
|
||||
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit.UIGestureRecognizerSubclass
|
||||
|
||||
@objc
|
||||
enum PanDirection: Int {
|
||||
case left, right, up, down, any
|
||||
}
|
||||
|
||||
@objc
|
||||
class PanDirectionGestureRecognizer: UIPanGestureRecognizer {
|
||||
|
||||
let direction: PanDirection
|
||||
|
||||
@objc init(direction: PanDirection, target: AnyObject, action: Selector) {
|
||||
self.direction = direction
|
||||
|
||||
super.init(target: target, action: action)
|
||||
}
|
||||
|
||||
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent) {
|
||||
// Only start gesture if it's initially in the specified direction.
|
||||
if state == .possible {
|
||||
guard let touch = touches.first else {
|
||||
return
|
||||
}
|
||||
|
||||
let previousLocation = touch.previousLocation(in: view)
|
||||
let location = touch.location(in: view)
|
||||
let deltaY = previousLocation.y - location.y
|
||||
let deltaX = previousLocation.x - location.x
|
||||
|
||||
switch direction {
|
||||
case .up where deltaY > 0:
|
||||
return
|
||||
case .down where deltaY < 0:
|
||||
return
|
||||
case .left where deltaX > 0:
|
||||
return
|
||||
case .right where deltaX < 0:
|
||||
return
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Gesture was already started, or in the correct direction.
|
||||
super.touchesMoved(touches, with: event)
|
||||
|
||||
if state == .began {
|
||||
let vel = velocity(in: view)
|
||||
switch direction {
|
||||
case .left, .right:
|
||||
if fabs(vel.y) > fabs(vel.x) {
|
||||
state = .cancelled
|
||||
}
|
||||
case .up, .down:
|
||||
if fabs(vel.x) > fabs(vel.y) {
|
||||
state = .cancelled
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1086,6 +1086,9 @@
|
|||
/* Label for crop button in image editor. */
|
||||
"IMAGE_EDITOR_CROP_BUTTON" = "Crop";
|
||||
|
||||
/* Momentarily shown to the user when attempting to select more images than is allowed. Embeds {{max number of items}} that can be shared. */
|
||||
"IMAGE_PICKER_CAN_SELECT_NO_MORE_TOAST_FORMAT" = "You can't share more than %@ items.";
|
||||
|
||||
/* alert title */
|
||||
"IMAGE_PICKER_FAILED_TO_PROCESS_ATTACHMENTS" = "Failed to select attachment.";
|
||||
|
||||
|
|
|
@ -1086,6 +1086,9 @@
|
|||
/* Label for crop button in image editor. */
|
||||
"IMAGE_EDITOR_CROP_BUTTON" = "Crop";
|
||||
|
||||
/* Momentarily shown to the user when attempting to select more images than is allowed. Embeds {{max number of items}} that can be shared. */
|
||||
"IMAGE_PICKER_CAN_SELECT_NO_MORE_TOAST_FORMAT" = "You can't share more than %@ items.";
|
||||
|
||||
/* alert title */
|
||||
"IMAGE_PICKER_FAILED_TO_PROCESS_ATTACHMENTS" = "Failed to select attachment.";
|
||||
|
||||
|
|
|
@ -1086,6 +1086,9 @@
|
|||
/* Label for crop button in image editor. */
|
||||
"IMAGE_EDITOR_CROP_BUTTON" = "Crop";
|
||||
|
||||
/* Momentarily shown to the user when attempting to select more images than is allowed. Embeds {{max number of items}} that can be shared. */
|
||||
"IMAGE_PICKER_CAN_SELECT_NO_MORE_TOAST_FORMAT" = "You can't share more than %@ items.";
|
||||
|
||||
/* alert title */
|
||||
"IMAGE_PICKER_FAILED_TO_PROCESS_ATTACHMENTS" = "Failed to select attachment.";
|
||||
|
||||
|
|
|
@ -1086,6 +1086,9 @@
|
|||
/* Label for crop button in image editor. */
|
||||
"IMAGE_EDITOR_CROP_BUTTON" = "Crop";
|
||||
|
||||
/* Momentarily shown to the user when attempting to select more images than is allowed. Embeds {{max number of items}} that can be shared. */
|
||||
"IMAGE_PICKER_CAN_SELECT_NO_MORE_TOAST_FORMAT" = "You can't share more than %@ items.";
|
||||
|
||||
/* alert title */
|
||||
"IMAGE_PICKER_FAILED_TO_PROCESS_ATTACHMENTS" = "Failed to select attachment.";
|
||||
|
||||
|
|
|
@ -1072,7 +1072,7 @@
|
|||
"HOME_VIEW_CONVERSATION_SEARCHBAR_PLACEHOLDER" = "Cerca";
|
||||
|
||||
/* Format string when search returns no results. Embeds {{search term}} */
|
||||
"HOME_VIEW_SEARCH_NO_RESULTS_FORMAT" = "No results found for '%@'";
|
||||
"HOME_VIEW_SEARCH_NO_RESULTS_FORMAT" = "No s'ha trobat cap resultat per a «%@»";
|
||||
|
||||
/* Title for the home view's 'archive' mode. */
|
||||
"HOME_VIEW_TITLE_ARCHIVE" = "Arxiva";
|
||||
|
@ -1086,14 +1086,17 @@
|
|||
/* Label for crop button in image editor. */
|
||||
"IMAGE_EDITOR_CROP_BUTTON" = "Crop";
|
||||
|
||||
/* Momentarily shown to the user when attempting to select more images than is allowed. Embeds {{max number of items}} that can be shared. */
|
||||
"IMAGE_PICKER_CAN_SELECT_NO_MORE_TOAST_FORMAT" = "You can't share more than %@ items.";
|
||||
|
||||
/* alert title */
|
||||
"IMAGE_PICKER_FAILED_TO_PROCESS_ATTACHMENTS" = "Failed to select attachment.";
|
||||
"IMAGE_PICKER_FAILED_TO_PROCESS_ATTACHMENTS" = "Ha fallat seleccionar l'adjunt.";
|
||||
|
||||
/* Call setup status label */
|
||||
"IN_CALL_CONNECTING" = "S'està connectant...";
|
||||
|
||||
/* Call setup status label */
|
||||
"IN_CALL_RECONNECTING" = "Reconnecting…";
|
||||
"IN_CALL_RECONNECTING" = "Reconnectant...";
|
||||
|
||||
/* Call setup status label */
|
||||
"IN_CALL_RINGING" = "Està sonant...";
|
||||
|
@ -1105,10 +1108,10 @@
|
|||
"IN_CALL_TERMINATED" = "Ha finalitzat.";
|
||||
|
||||
/* Label reminding the user that they are in archive mode. */
|
||||
"INBOX_VIEW_ARCHIVE_MODE_REMINDER" = "These conversations are archived and will only appear in the Inbox if new messages are received.";
|
||||
"INBOX_VIEW_ARCHIVE_MODE_REMINDER" = "Aquestes converses estan arxivades i només apareixeran a la bústia d'entrada si es reben missatges nous.";
|
||||
|
||||
/* Multi-line label explaining how to show names instead of phone numbers in your inbox */
|
||||
"INBOX_VIEW_MISSING_CONTACTS_PERMISSION" = "You can enable contacts access in the iOS Settings app to see contact names in your Signal conversation list.";
|
||||
"INBOX_VIEW_MISSING_CONTACTS_PERMISSION" = "Podeu activar l'accés als contactes a la Configuració de l'iOS per veure els noms dels contactes a la llista de converses del Signal.";
|
||||
|
||||
/* notification body */
|
||||
"INCOMING_CALL" = "Telefonada rebuda";
|
||||
|
@ -1129,7 +1132,7 @@
|
|||
"INVALID_AUDIO_FILE_ALERT_ERROR_MESSAGE" = "Fitxer d'àudio no vàlid.";
|
||||
|
||||
/* Alert body when contacts disabled while trying to invite contacts to signal */
|
||||
"INVITE_FLOW_REQUIRES_CONTACT_ACCESS_BODY" = "You can enable contacts access in the iOS Settings app to invite your friends to join Signal.";
|
||||
"INVITE_FLOW_REQUIRES_CONTACT_ACCESS_BODY" = "Podeu activar l'accés als contactes a la Configuració de l'iOS per convidar les amistats al Signal.";
|
||||
|
||||
/* Alert title when contacts disabled while trying to invite contacts to signal */
|
||||
"INVITE_FLOW_REQUIRES_CONTACT_ACCESS_TITLE" = "Doneu accés als contactes";
|
||||
|
@ -1198,7 +1201,7 @@
|
|||
"LONG_TEXT_VIEW_TITLE" = "Missatge";
|
||||
|
||||
/* nav bar button item */
|
||||
"MEDIA_DETAIL_VIEW_ALL_MEDIA_BUTTON" = "All Media";
|
||||
"MEDIA_DETAIL_VIEW_ALL_MEDIA_BUTTON" = "Tot el contingut";
|
||||
|
||||
/* media picker option to take photo or video */
|
||||
"MEDIA_FROM_CAMERA_BUTTON" = "Càmera";
|
||||
|
@ -1210,13 +1213,13 @@
|
|||
"MEDIA_FROM_LIBRARY_BUTTON" = "Galeria";
|
||||
|
||||
/* Confirmation button text to delete selected media from the gallery, embeds {{number of messages}} */
|
||||
"MEDIA_GALLERY_DELETE_MULTIPLE_MESSAGES_FORMAT" = "Delete %d Messages";
|
||||
"MEDIA_GALLERY_DELETE_MULTIPLE_MESSAGES_FORMAT" = "Suprimeix %d missatges";
|
||||
|
||||
/* Confirmation button text to delete selected media message from the gallery */
|
||||
"MEDIA_GALLERY_DELETE_SINGLE_MESSAGE" = "Suprimeix el missatge";
|
||||
|
||||
/* embeds {{sender name}} and {{sent datetime}}, e.g. 'Sarah on 10/30/18, 3:29' */
|
||||
"MEDIA_GALLERY_LANDSCAPE_TITLE_FORMAT" = "%@ on %@";
|
||||
"MEDIA_GALLERY_LANDSCAPE_TITLE_FORMAT" = "%@ el %@";
|
||||
|
||||
/* Format for the 'more items' indicator for media galleries. Embeds {{the number of additional items}}. */
|
||||
"MEDIA_GALLERY_MORE_ITEMS_FORMAT" = "+%@";
|
||||
|
@ -1228,25 +1231,25 @@
|
|||
"MEDIA_GALLERY_THIS_MONTH_HEADER" = "Aquest mes";
|
||||
|
||||
/* Action sheet button title */
|
||||
"MESSAGE_ACTION_COPY_MEDIA" = "Copy Media";
|
||||
"MESSAGE_ACTION_COPY_MEDIA" = "Copia el contingut";
|
||||
|
||||
/* Action sheet button title */
|
||||
"MESSAGE_ACTION_COPY_TEXT" = "Copy Message Text";
|
||||
"MESSAGE_ACTION_COPY_TEXT" = "Copia el text del missatge";
|
||||
|
||||
/* Action sheet button title */
|
||||
"MESSAGE_ACTION_DELETE_MESSAGE" = "Delete This Message";
|
||||
"MESSAGE_ACTION_DELETE_MESSAGE" = "Suprimeix aquest missatge";
|
||||
|
||||
/* Action sheet button subtitle */
|
||||
"MESSAGE_ACTION_DELETE_MESSAGE_SUBTITLE" = "It will only be deleted on this device.";
|
||||
"MESSAGE_ACTION_DELETE_MESSAGE_SUBTITLE" = "Només se suprimirà d'aquest dispositiu.";
|
||||
|
||||
/* Action sheet button title */
|
||||
"MESSAGE_ACTION_DETAILS" = "More Info";
|
||||
"MESSAGE_ACTION_DETAILS" = "Més informació";
|
||||
|
||||
/* Action sheet button title */
|
||||
"MESSAGE_ACTION_REPLY" = "Reply to This Message";
|
||||
"MESSAGE_ACTION_REPLY" = "Respon aquest missatge";
|
||||
|
||||
/* Action sheet button title */
|
||||
"MESSAGE_ACTION_SAVE_MEDIA" = "Save Media";
|
||||
"MESSAGE_ACTION_SAVE_MEDIA" = "Desa el contingut";
|
||||
|
||||
/* Title for the 'message approval' dialog. */
|
||||
"MESSAGE_APPROVAL_DIALOG_TITLE" = "Missatge";
|
||||
|
@ -1267,7 +1270,7 @@
|
|||
"MESSAGE_METADATA_VIEW_MESSAGE_STATUS_DELIVERED" = "Entregat";
|
||||
|
||||
/* Status label for messages which are failed. */
|
||||
"MESSAGE_METADATA_VIEW_MESSAGE_STATUS_FAILED" = "Failed";
|
||||
"MESSAGE_METADATA_VIEW_MESSAGE_STATUS_FAILED" = "Ha fallat";
|
||||
|
||||
/* Status label for messages which are read. */
|
||||
"MESSAGE_METADATA_VIEW_MESSAGE_STATUS_READ" = "S'ha llegit";
|
||||
|
@ -1279,19 +1282,19 @@
|
|||
"MESSAGE_METADATA_VIEW_MESSAGE_STATUS_SENT" = "Enviament";
|
||||
|
||||
/* Status label for messages which were skipped. */
|
||||
"MESSAGE_METADATA_VIEW_MESSAGE_STATUS_SKIPPED" = "Skipped";
|
||||
"MESSAGE_METADATA_VIEW_MESSAGE_STATUS_SKIPPED" = "S'ha omès";
|
||||
|
||||
/* Status label for messages which are uploading. */
|
||||
"MESSAGE_METADATA_VIEW_MESSAGE_STATUS_UPLOADING" = "Uploading";
|
||||
"MESSAGE_METADATA_VIEW_MESSAGE_STATUS_UPLOADING" = "Pujant";
|
||||
|
||||
/* Label for messages without a body or attachment in the 'message metadata' view. */
|
||||
"MESSAGE_METADATA_VIEW_NO_ATTACHMENT_OR_BODY" = "Message has no content or attachment.";
|
||||
"MESSAGE_METADATA_VIEW_NO_ATTACHMENT_OR_BODY" = "El missatge no té contingut o no té cap adjunt.";
|
||||
|
||||
/* Label for the 'received date & time' field of the 'message metadata' view. */
|
||||
"MESSAGE_METADATA_VIEW_RECEIVED_DATE_TIME" = "Rebut";
|
||||
|
||||
/* Label for the 'sender' field of the 'message metadata' view. */
|
||||
"MESSAGE_METADATA_VIEW_SENDER" = "Sender";
|
||||
"MESSAGE_METADATA_VIEW_SENDER" = "Remitent";
|
||||
|
||||
/* Label for the 'sent date & time' field of the 'message metadata' view. */
|
||||
"MESSAGE_METADATA_VIEW_SENT_DATE_TIME" = "Enviament";
|
||||
|
@ -1306,22 +1309,22 @@
|
|||
"MESSAGE_STATUS_DELIVERED" = "S'ha enviat";
|
||||
|
||||
/* status message for failed messages */
|
||||
"MESSAGE_STATUS_FAILED" = "Sending failed.";
|
||||
"MESSAGE_STATUS_FAILED" = "Ha fallat l'enviament.";
|
||||
|
||||
/* status message for failed messages */
|
||||
"MESSAGE_STATUS_FAILED_SHORT" = "Failed";
|
||||
"MESSAGE_STATUS_FAILED_SHORT" = "Ha fallat";
|
||||
|
||||
/* status message for read messages */
|
||||
"MESSAGE_STATUS_READ" = "S'ha llegit";
|
||||
|
||||
/* message status if message delivery to a recipient is skipped. We skip delivering group messages to users who have left the group or unregistered their Signal account. */
|
||||
"MESSAGE_STATUS_RECIPIENT_SKIPPED" = "Skipped";
|
||||
"MESSAGE_STATUS_RECIPIENT_SKIPPED" = "S'ha omès";
|
||||
|
||||
/* Label indicating that a message failed to send. */
|
||||
"MESSAGE_STATUS_SEND_FAILED" = "Ha fallat l'enviament";
|
||||
|
||||
/* message status while message is sending. */
|
||||
"MESSAGE_STATUS_SENDING" = "Sending…";
|
||||
"MESSAGE_STATUS_SENDING" = "Enviant...";
|
||||
|
||||
/* status message for sent messages */
|
||||
"MESSAGE_STATUS_SENT" = "S'ha enviat";
|
||||
|
@ -1336,19 +1339,19 @@
|
|||
"MESSAGES_VIEW_1_MEMBER_NO_LONGER_VERIFIED_FORMAT" = "%@ ja no és marcat com a verificat. Toqueu per a més opcions.";
|
||||
|
||||
/* Indicates that this 1:1 conversation has been blocked. */
|
||||
"MESSAGES_VIEW_CONTACT_BLOCKED" = "You Blocked This User";
|
||||
"MESSAGES_VIEW_CONTACT_BLOCKED" = "Heu blocat aquest usuari";
|
||||
|
||||
/* Indicates that this 1:1 conversation is no longer verified. Embeds {{user's name or phone number}}. */
|
||||
"MESSAGES_VIEW_CONTACT_NO_LONGER_VERIFIED_FORMAT" = "%@ ja no és marcat com a verificat. Toqueu per a més opcions.";
|
||||
|
||||
/* Indicates that a single member of this group has been blocked. */
|
||||
"MESSAGES_VIEW_GROUP_1_MEMBER_BLOCKED" = "You Blocked 1 Member of This Group";
|
||||
"MESSAGES_VIEW_GROUP_1_MEMBER_BLOCKED" = "Heu blocat 1 membre d'aquest grup";
|
||||
|
||||
/* Indicates that this group conversation has been blocked. */
|
||||
"MESSAGES_VIEW_GROUP_BLOCKED" = "You Blocked This Group";
|
||||
"MESSAGES_VIEW_GROUP_BLOCKED" = "Heu blocat aquest grup";
|
||||
|
||||
/* Indicates that some members of this group has been blocked. Embeds {{the number of blocked users in this group}}. */
|
||||
"MESSAGES_VIEW_GROUP_N_MEMBERS_BLOCKED_FORMAT" = "You Blocked %@ Members of This Group";
|
||||
"MESSAGES_VIEW_GROUP_N_MEMBERS_BLOCKED_FORMAT" = "Heu blocat %@ membres d'aquest grup";
|
||||
|
||||
/* Text for banner in group conversation view that offers to share your profile with this group. */
|
||||
"MESSAGES_VIEW_GROUP_PROFILE_WHITELIST_BANNER" = "Share Your Profile With This Group?";
|
||||
|
|
|
@ -1086,6 +1086,9 @@
|
|||
/* Label for crop button in image editor. */
|
||||
"IMAGE_EDITOR_CROP_BUTTON" = "Crop";
|
||||
|
||||
/* Momentarily shown to the user when attempting to select more images than is allowed. Embeds {{max number of items}} that can be shared. */
|
||||
"IMAGE_PICKER_CAN_SELECT_NO_MORE_TOAST_FORMAT" = "You can't share more than %@ items.";
|
||||
|
||||
/* alert title */
|
||||
"IMAGE_PICKER_FAILED_TO_PROCESS_ATTACHMENTS" = "Failed to select attachment.";
|
||||
|
||||
|
|
|
@ -1086,6 +1086,9 @@
|
|||
/* Label for crop button in image editor. */
|
||||
"IMAGE_EDITOR_CROP_BUTTON" = "Crop";
|
||||
|
||||
/* Momentarily shown to the user when attempting to select more images than is allowed. Embeds {{max number of items}} that can be shared. */
|
||||
"IMAGE_PICKER_CAN_SELECT_NO_MORE_TOAST_FORMAT" = "You can't share more than %@ items.";
|
||||
|
||||
/* alert title */
|
||||
"IMAGE_PICKER_FAILED_TO_PROCESS_ATTACHMENTS" = "Failed to select attachment.";
|
||||
|
||||
|
|
|
@ -1081,13 +1081,16 @@
|
|||
"HOME_VIEW_TITLE_INBOX" = "Signal";
|
||||
|
||||
/* Label for brush button in image editor. */
|
||||
"IMAGE_EDITOR_BRUSH_BUTTON" = "Brush";
|
||||
"IMAGE_EDITOR_BRUSH_BUTTON" = "Pinsel";
|
||||
|
||||
/* Label for crop button in image editor. */
|
||||
"IMAGE_EDITOR_CROP_BUTTON" = "Crop";
|
||||
"IMAGE_EDITOR_CROP_BUTTON" = "Zuschneiden";
|
||||
|
||||
/* Momentarily shown to the user when attempting to select more images than is allowed. Embeds {{max number of items}} that can be shared. */
|
||||
"IMAGE_PICKER_CAN_SELECT_NO_MORE_TOAST_FORMAT" = "You can't share more than %@ items.";
|
||||
|
||||
/* alert title */
|
||||
"IMAGE_PICKER_FAILED_TO_PROCESS_ATTACHMENTS" = "Failed to select attachment.";
|
||||
"IMAGE_PICKER_FAILED_TO_PROCESS_ATTACHMENTS" = "Anhangsauswahl gescheitert.";
|
||||
|
||||
/* Call setup status label */
|
||||
"IN_CALL_CONNECTING" = "Verbinden …";
|
||||
|
@ -1470,7 +1473,7 @@
|
|||
"NO_CONTACTS_SEARCH_BY_PHONE_NUMBER" = "Kontakte über Rufnummer suchen";
|
||||
|
||||
/* Label for 1:1 conversation with yourself. */
|
||||
"NOTE_TO_SELF" = "Note to Self";
|
||||
"NOTE_TO_SELF" = "Notiz an mich";
|
||||
|
||||
/* Lock screen notification text presented after user powers on their device without unlocking. Embeds {{device model}} (either 'iPad' or 'iPhone') */
|
||||
"NOTIFICATION_BODY_PHONE_LOCKED_FORMAT" = "Du hast eventuell Nachrichten erhalten, während dein %@ neu gestartet wurde.";
|
||||
|
|
|
@ -228,10 +228,10 @@
|
|||
"BACKUP_IMPORT_PHASE_RESTORING_FILES" = "Επαναφορά Αρχείων";
|
||||
|
||||
/* Label for the backup restore decision section. */
|
||||
"BACKUP_RESTORE_DECISION_TITLE" = "Backup Available";
|
||||
"BACKUP_RESTORE_DECISION_TITLE" = "Αντίγραφο ασφαλείας διαθέσιμο ";
|
||||
|
||||
/* Label for the backup restore description. */
|
||||
"BACKUP_RESTORE_DESCRIPTION" = "Restoring Backup";
|
||||
"BACKUP_RESTORE_DESCRIPTION" = "Γίνεται επαναφορά του αντιγράφου ασφαλείας ";
|
||||
|
||||
/* Label for the backup restore progress. */
|
||||
"BACKUP_RESTORE_PROGRESS" = "Πρόοδος";
|
||||
|
@ -240,7 +240,7 @@
|
|||
"BACKUP_RESTORE_STATUS" = "Κατάσταση";
|
||||
|
||||
/* Error shown when backup fails due to an unexpected error. */
|
||||
"BACKUP_UNEXPECTED_ERROR" = "Unexpected Backup Error";
|
||||
"BACKUP_UNEXPECTED_ERROR" = "Αναπάντεχο σφάλμα κατά τη δημιουργία του αντιγράφου ασφαλείας ";
|
||||
|
||||
/* An explanation of the consequences of blocking a group. */
|
||||
"BLOCK_GROUP_BEHAVIOR_EXPLANATION" = "Δεν θα λαμβάνετε πλέον μηνύματα ή ενημερώσεις από αυτήν την ομάδα.";
|
||||
|
@ -402,10 +402,10 @@
|
|||
"CENSORSHIP_CIRCUMVENTION_COUNTRY_VIEW_TITLE" = "Επιλογή Χώρας";
|
||||
|
||||
/* The label for the 'do not restore backup' button. */
|
||||
"CHECK_FOR_BACKUP_DO_NOT_RESTORE" = "Do Not Restore";
|
||||
"CHECK_FOR_BACKUP_DO_NOT_RESTORE" = "Να μη γίνει επαναφορά";
|
||||
|
||||
/* Message for alert shown when the app failed to check for an existing backup. */
|
||||
"CHECK_FOR_BACKUP_FAILED_MESSAGE" = "Could not determine whether there is a backup that can be restored.";
|
||||
"CHECK_FOR_BACKUP_FAILED_MESSAGE" = "Δε μπόρεσε να διαπιστωθεί αν υπάρχει αντίγραφο ασφαλείας για επαναφορά.";
|
||||
|
||||
/* Title for alert shown when the app failed to check for an existing backup. */
|
||||
"CHECK_FOR_BACKUP_FAILED_TITLE" = "Σφάλμα";
|
||||
|
@ -414,13 +414,13 @@
|
|||
"CHECK_FOR_BACKUP_RESTORE" = "Επαναφορά";
|
||||
|
||||
/* Error indicating that the app could not determine that user's iCloud account status */
|
||||
"CLOUDKIT_STATUS_COULD_NOT_DETERMINE" = "Signal could not determine your iCloud account status. Sign in to your iCloud Account in the iOS settings app to backup your Signal data.";
|
||||
"CLOUDKIT_STATUS_COULD_NOT_DETERMINE" = "Το Signal δεν μπορεσε να εξακριβώσει την κατάσταση του iCloud λογαριασμού σας. Συνδεθείτε στον iCloud λογαριασμό σας μέσω των ρυθμίσεων του iOS ώστε να αποθηκεύσετε αντίγραφο ασφαλείας των δεδομένων του Signal.";
|
||||
|
||||
/* Error indicating that user does not have an iCloud account. */
|
||||
"CLOUDKIT_STATUS_NO_ACCOUNT" = "No iCloud Account. Sign in to your iCloud Account in the iOS settings app to backup your Signal data.";
|
||||
"CLOUDKIT_STATUS_NO_ACCOUNT" = "Δεν υπάρχει iCloud λογαριασμός. Συνδεθείτε στον iCloud λογαριασμό σας μέσω των ρυθμίσεων του iOS ώστε να αποθηκεύσετε αντίγραφο ασφαλείας των δεδομένων του Signal.";
|
||||
|
||||
/* Error indicating that the app was prevented from accessing the user's iCloud account. */
|
||||
"CLOUDKIT_STATUS_RESTRICTED" = "Signal was denied access your iCloud account for backups. Grant Signal access to your iCloud Account in the iOS settings app to backup your Signal data.";
|
||||
"CLOUDKIT_STATUS_RESTRICTED" = "Το Signal δεν μπόρεσε να αποκτήσει πρόσβαση στον iCloud λογαριασμό σας για τη δημιουργία αντιγράφου ασφαλείας. Παραχωρήστε στο Signal πρόσβαση στον iCloud λογαριασμό σας μέσω των ρυθμίσεων του iOS ώστε να μπορέσετε να δημιουργήσετε αντίγραφο ασφαλείας των Signal δεδομένων σας.";
|
||||
|
||||
/* The first of two messages demonstrating the chosen conversation color, by rendering this message in an outgoing message bubble. */
|
||||
"COLOR_PICKER_DEMO_MESSAGE_1" = "Επιλέξτε το χρώμα των εξερχόμενων μηνυμάτων σε αυτήν τη συνομιλία.";
|
||||
|
@ -561,10 +561,10 @@
|
|||
"CONTACT_WITHOUT_NAME" = "Ανώνυμη Επαφή";
|
||||
|
||||
/* Message for the 'conversation delete confirmation' alert. */
|
||||
"CONVERSATION_DELETE_CONFIRMATION_ALERT_MESSAGE" = "This cannot be undone.";
|
||||
"CONVERSATION_DELETE_CONFIRMATION_ALERT_MESSAGE" = "Η ενέρεια αυτη δεν μπορεί να αναιρεθεί.";
|
||||
|
||||
/* Title for the 'conversation delete confirmation' alert. */
|
||||
"CONVERSATION_DELETE_CONFIRMATION_ALERT_TITLE" = "Delete Conversation?";
|
||||
"CONVERSATION_DELETE_CONFIRMATION_ALERT_TITLE" = "Διαγραφή συνομιλίας;";
|
||||
|
||||
/* title for conversation settings screen */
|
||||
"CONVERSATION_SETTINGS" = "Ρυθμίσεις Συνομιλίας";
|
||||
|
@ -1081,13 +1081,16 @@
|
|||
"HOME_VIEW_TITLE_INBOX" = "Signal";
|
||||
|
||||
/* Label for brush button in image editor. */
|
||||
"IMAGE_EDITOR_BRUSH_BUTTON" = "Brush";
|
||||
"IMAGE_EDITOR_BRUSH_BUTTON" = "Βούρτσα.";
|
||||
|
||||
/* Label for crop button in image editor. */
|
||||
"IMAGE_EDITOR_CROP_BUTTON" = "Crop";
|
||||
"IMAGE_EDITOR_CROP_BUTTON" = "Περικοπή";
|
||||
|
||||
/* Momentarily shown to the user when attempting to select more images than is allowed. Embeds {{max number of items}} that can be shared. */
|
||||
"IMAGE_PICKER_CAN_SELECT_NO_MORE_TOAST_FORMAT" = "You can't share more than %@ items.";
|
||||
|
||||
/* alert title */
|
||||
"IMAGE_PICKER_FAILED_TO_PROCESS_ATTACHMENTS" = "Failed to select attachment.";
|
||||
"IMAGE_PICKER_FAILED_TO_PROCESS_ATTACHMENTS" = "Δεν ήταν δυνατό να επιλεχθεί το συνημμένο.";
|
||||
|
||||
/* Call setup status label */
|
||||
"IN_CALL_CONNECTING" = "Συνδέεται...";
|
||||
|
@ -1216,10 +1219,10 @@
|
|||
"MEDIA_GALLERY_DELETE_SINGLE_MESSAGE" = "Διαγραφή Μηνύματος";
|
||||
|
||||
/* embeds {{sender name}} and {{sent datetime}}, e.g. 'Sarah on 10/30/18, 3:29' */
|
||||
"MEDIA_GALLERY_LANDSCAPE_TITLE_FORMAT" = "%@ on %@";
|
||||
"MEDIA_GALLERY_LANDSCAPE_TITLE_FORMAT" = "%@ στις%@";
|
||||
|
||||
/* Format for the 'more items' indicator for media galleries. Embeds {{the number of additional items}}. */
|
||||
"MEDIA_GALLERY_MORE_ITEMS_FORMAT" = "+%@";
|
||||
"MEDIA_GALLERY_MORE_ITEMS_FORMAT" = "+ Περισσότερα%@";
|
||||
|
||||
/* Short sender label for media sent by you */
|
||||
"MEDIA_GALLERY_SENDER_NAME_YOU" = "Εσείς";
|
||||
|
@ -1470,7 +1473,7 @@
|
|||
"NO_CONTACTS_SEARCH_BY_PHONE_NUMBER" = "Εύρεση επαφών μέσω αριθμού τηλεφώνου";
|
||||
|
||||
/* Label for 1:1 conversation with yourself. */
|
||||
"NOTE_TO_SELF" = "Note to Self";
|
||||
"NOTE_TO_SELF" = "Να μην ξεχάσω ";
|
||||
|
||||
/* Lock screen notification text presented after user powers on their device without unlocking. Embeds {{device model}} (either 'iPad' or 'iPhone') */
|
||||
"NOTIFICATION_BODY_PHONE_LOCKED_FORMAT" = "Μπορεί να έχετε λάβει μηνύματα κατά την επανεκκίνηση του %@ σας.";
|
||||
|
@ -1566,7 +1569,7 @@
|
|||
"PHONE_NUMBER_TYPE_WORK_FAX" = "Φαξ Εργασίας";
|
||||
|
||||
/* label for system photo collections which have no name. */
|
||||
"PHOTO_PICKER_UNNAMED_COLLECTION" = "Unnamed Album";
|
||||
"PHOTO_PICKER_UNNAMED_COLLECTION" = "Ανώνυμο άλμπουμ";
|
||||
|
||||
/* Accessibility label for button to start media playback */
|
||||
"PLAY_BUTTON_ACCESSABILITY_LABEL" = "Αναπαραγωγή Πολυμέσων";
|
||||
|
@ -1992,10 +1995,10 @@
|
|||
"SETTINGS_BACKUP_ENABLING_SWITCH" = "Δημιουργία Αντιγράφων Ενεργοποιημένη";
|
||||
|
||||
/* Label for iCloud status row in the in the backup settings view. */
|
||||
"SETTINGS_BACKUP_ICLOUD_STATUS" = "iCloud Status";
|
||||
"SETTINGS_BACKUP_ICLOUD_STATUS" = "Καατάσταση iCloud";
|
||||
|
||||
/* Indicates that the last backup restore failed. */
|
||||
"SETTINGS_BACKUP_IMPORT_STATUS_FAILED" = "Backup Restore Failed";
|
||||
"SETTINGS_BACKUP_IMPORT_STATUS_FAILED" = "Η επαναφορά του αντιγράφου ασφαλείας απέτυχε ";
|
||||
|
||||
/* Indicates that app is not restoring up. */
|
||||
"SETTINGS_BACKUP_IMPORT_STATUS_IDLE" = "Backup Restore Idle";
|
||||
|
|
|
@ -1086,6 +1086,9 @@
|
|||
/* Label for crop button in image editor. */
|
||||
"IMAGE_EDITOR_CROP_BUTTON" = "Crop";
|
||||
|
||||
/* Momentarily shown to the user when attempting to select more images than is allowed. Embeds {{max number of items}} that can be shared. */
|
||||
"IMAGE_PICKER_CAN_SELECT_NO_MORE_TOAST_FORMAT" = "You can't share more than %@ items.";
|
||||
|
||||
/* alert title */
|
||||
"IMAGE_PICKER_FAILED_TO_PROCESS_ATTACHMENTS" = "Failed to select attachment.";
|
||||
|
||||
|
|
|
@ -1081,13 +1081,16 @@
|
|||
"HOME_VIEW_TITLE_INBOX" = "Signal";
|
||||
|
||||
/* Label for brush button in image editor. */
|
||||
"IMAGE_EDITOR_BRUSH_BUTTON" = "Brush";
|
||||
"IMAGE_EDITOR_BRUSH_BUTTON" = "Pincel";
|
||||
|
||||
/* Label for crop button in image editor. */
|
||||
"IMAGE_EDITOR_CROP_BUTTON" = "Crop";
|
||||
"IMAGE_EDITOR_CROP_BUTTON" = "Recortar";
|
||||
|
||||
/* Momentarily shown to the user when attempting to select more images than is allowed. Embeds {{max number of items}} that can be shared. */
|
||||
"IMAGE_PICKER_CAN_SELECT_NO_MORE_TOAST_FORMAT" = "You can't share more than %@ items.";
|
||||
|
||||
/* alert title */
|
||||
"IMAGE_PICKER_FAILED_TO_PROCESS_ATTACHMENTS" = "Failed to select attachment.";
|
||||
"IMAGE_PICKER_FAILED_TO_PROCESS_ATTACHMENTS" = "Fallo al seleccionar adjunto.";
|
||||
|
||||
/* Call setup status label */
|
||||
"IN_CALL_CONNECTING" = "Conectando ...";
|
||||
|
@ -1470,7 +1473,7 @@
|
|||
"NO_CONTACTS_SEARCH_BY_PHONE_NUMBER" = "Encontrar por núm. de teléfono";
|
||||
|
||||
/* Label for 1:1 conversation with yourself. */
|
||||
"NOTE_TO_SELF" = "Note to Self";
|
||||
"NOTE_TO_SELF" = "Tú mismo";
|
||||
|
||||
/* Lock screen notification text presented after user powers on their device without unlocking. Embeds {{device model}} (either 'iPad' or 'iPhone') */
|
||||
"NOTIFICATION_BODY_PHONE_LOCKED_FORMAT" = "Has recibido mensajes mientras tu %@ estaba reiniciándose.";
|
||||
|
|
|
@ -1086,6 +1086,9 @@
|
|||
/* Label for crop button in image editor. */
|
||||
"IMAGE_EDITOR_CROP_BUTTON" = "Crop";
|
||||
|
||||
/* Momentarily shown to the user when attempting to select more images than is allowed. Embeds {{max number of items}} that can be shared. */
|
||||
"IMAGE_PICKER_CAN_SELECT_NO_MORE_TOAST_FORMAT" = "You can't share more than %@ items.";
|
||||
|
||||
/* alert title */
|
||||
"IMAGE_PICKER_FAILED_TO_PROCESS_ATTACHMENTS" = "Failed to select attachment.";
|
||||
|
||||
|
|
|
@ -1086,6 +1086,9 @@
|
|||
/* Label for crop button in image editor. */
|
||||
"IMAGE_EDITOR_CROP_BUTTON" = "Crop";
|
||||
|
||||
/* Momentarily shown to the user when attempting to select more images than is allowed. Embeds {{max number of items}} that can be shared. */
|
||||
"IMAGE_PICKER_CAN_SELECT_NO_MORE_TOAST_FORMAT" = "You can't share more than %@ items.";
|
||||
|
||||
/* alert title */
|
||||
"IMAGE_PICKER_FAILED_TO_PROCESS_ATTACHMENTS" = "Failed to select attachment.";
|
||||
|
||||
|
|
|
@ -1086,6 +1086,9 @@
|
|||
/* Label for crop button in image editor. */
|
||||
"IMAGE_EDITOR_CROP_BUTTON" = "Crop";
|
||||
|
||||
/* Momentarily shown to the user when attempting to select more images than is allowed. Embeds {{max number of items}} that can be shared. */
|
||||
"IMAGE_PICKER_CAN_SELECT_NO_MORE_TOAST_FORMAT" = "You can't share more than %@ items.";
|
||||
|
||||
/* alert title */
|
||||
"IMAGE_PICKER_FAILED_TO_PROCESS_ATTACHMENTS" = "Failed to select attachment.";
|
||||
|
||||
|
|
|
@ -1086,6 +1086,9 @@
|
|||
/* Label for crop button in image editor. */
|
||||
"IMAGE_EDITOR_CROP_BUTTON" = "Crop";
|
||||
|
||||
/* Momentarily shown to the user when attempting to select more images than is allowed. Embeds {{max number of items}} that can be shared. */
|
||||
"IMAGE_PICKER_CAN_SELECT_NO_MORE_TOAST_FORMAT" = "You can't share more than %@ items.";
|
||||
|
||||
/* alert title */
|
||||
"IMAGE_PICKER_FAILED_TO_PROCESS_ATTACHMENTS" = "Failed to select attachment.";
|
||||
|
||||
|
|
|
@ -1086,6 +1086,9 @@
|
|||
/* Label for crop button in image editor. */
|
||||
"IMAGE_EDITOR_CROP_BUTTON" = "Crop";
|
||||
|
||||
/* Momentarily shown to the user when attempting to select more images than is allowed. Embeds {{max number of items}} that can be shared. */
|
||||
"IMAGE_PICKER_CAN_SELECT_NO_MORE_TOAST_FORMAT" = "You can't share more than %@ items.";
|
||||
|
||||
/* alert title */
|
||||
"IMAGE_PICKER_FAILED_TO_PROCESS_ATTACHMENTS" = "Failed to select attachment.";
|
||||
|
||||
|
|
|
@ -1086,6 +1086,9 @@
|
|||
/* Label for crop button in image editor. */
|
||||
"IMAGE_EDITOR_CROP_BUTTON" = "Crop";
|
||||
|
||||
/* Momentarily shown to the user when attempting to select more images than is allowed. Embeds {{max number of items}} that can be shared. */
|
||||
"IMAGE_PICKER_CAN_SELECT_NO_MORE_TOAST_FORMAT" = "You can't share more than %@ items.";
|
||||
|
||||
/* alert title */
|
||||
"IMAGE_PICKER_FAILED_TO_PROCESS_ATTACHMENTS" = "Failed to select attachment.";
|
||||
|
||||
|
|
|
@ -1081,13 +1081,16 @@
|
|||
"HOME_VIEW_TITLE_INBOX" = "Signal";
|
||||
|
||||
/* Label for brush button in image editor. */
|
||||
"IMAGE_EDITOR_BRUSH_BUTTON" = "Brush";
|
||||
"IMAGE_EDITOR_BRUSH_BUTTON" = "מברשת";
|
||||
|
||||
/* Label for crop button in image editor. */
|
||||
"IMAGE_EDITOR_CROP_BUTTON" = "Crop";
|
||||
"IMAGE_EDITOR_CROP_BUTTON" = "חתוך";
|
||||
|
||||
/* Momentarily shown to the user when attempting to select more images than is allowed. Embeds {{max number of items}} that can be shared. */
|
||||
"IMAGE_PICKER_CAN_SELECT_NO_MORE_TOAST_FORMAT" = "You can't share more than %@ items.";
|
||||
|
||||
/* alert title */
|
||||
"IMAGE_PICKER_FAILED_TO_PROCESS_ATTACHMENTS" = "Failed to select attachment.";
|
||||
"IMAGE_PICKER_FAILED_TO_PROCESS_ATTACHMENTS" = "נכשל בבחירת צרופה.";
|
||||
|
||||
/* Call setup status label */
|
||||
"IN_CALL_CONNECTING" = "מתחבר...";
|
||||
|
@ -1470,7 +1473,7 @@
|
|||
"NO_CONTACTS_SEARCH_BY_PHONE_NUMBER" = "מצא אנשי קשר לפי מספר טלפון";
|
||||
|
||||
/* Label for 1:1 conversation with yourself. */
|
||||
"NOTE_TO_SELF" = "Note to Self";
|
||||
"NOTE_TO_SELF" = "הערה לעצמי";
|
||||
|
||||
/* Lock screen notification text presented after user powers on their device without unlocking. Embeds {{device model}} (either 'iPad' or 'iPhone') */
|
||||
"NOTIFICATION_BODY_PHONE_LOCKED_FORMAT" = "ייתכן שקיבלת הודעות בזמן שה־%@ שלך הופעל מחדש.";
|
||||
|
|
|
@ -1086,6 +1086,9 @@
|
|||
/* Label for crop button in image editor. */
|
||||
"IMAGE_EDITOR_CROP_BUTTON" = "Crop";
|
||||
|
||||
/* Momentarily shown to the user when attempting to select more images than is allowed. Embeds {{max number of items}} that can be shared. */
|
||||
"IMAGE_PICKER_CAN_SELECT_NO_MORE_TOAST_FORMAT" = "You can't share more than %@ items.";
|
||||
|
||||
/* alert title */
|
||||
"IMAGE_PICKER_FAILED_TO_PROCESS_ATTACHMENTS" = "Failed to select attachment.";
|
||||
|
||||
|
|
|
@ -1086,6 +1086,9 @@
|
|||
/* Label for crop button in image editor. */
|
||||
"IMAGE_EDITOR_CROP_BUTTON" = "Crop";
|
||||
|
||||
/* Momentarily shown to the user when attempting to select more images than is allowed. Embeds {{max number of items}} that can be shared. */
|
||||
"IMAGE_PICKER_CAN_SELECT_NO_MORE_TOAST_FORMAT" = "You can't share more than %@ items.";
|
||||
|
||||
/* alert title */
|
||||
"IMAGE_PICKER_FAILED_TO_PROCESS_ATTACHMENTS" = "Failed to select attachment.";
|
||||
|
||||
|
|
|
@ -1086,6 +1086,9 @@
|
|||
/* Label for crop button in image editor. */
|
||||
"IMAGE_EDITOR_CROP_BUTTON" = "Crop";
|
||||
|
||||
/* Momentarily shown to the user when attempting to select more images than is allowed. Embeds {{max number of items}} that can be shared. */
|
||||
"IMAGE_PICKER_CAN_SELECT_NO_MORE_TOAST_FORMAT" = "You can't share more than %@ items.";
|
||||
|
||||
/* alert title */
|
||||
"IMAGE_PICKER_FAILED_TO_PROCESS_ATTACHMENTS" = "Failed to select attachment.";
|
||||
|
||||
|
|
|
@ -1086,6 +1086,9 @@
|
|||
/* Label for crop button in image editor. */
|
||||
"IMAGE_EDITOR_CROP_BUTTON" = "Crop";
|
||||
|
||||
/* Momentarily shown to the user when attempting to select more images than is allowed. Embeds {{max number of items}} that can be shared. */
|
||||
"IMAGE_PICKER_CAN_SELECT_NO_MORE_TOAST_FORMAT" = "You can't share more than %@ items.";
|
||||
|
||||
/* alert title */
|
||||
"IMAGE_PICKER_FAILED_TO_PROCESS_ATTACHMENTS" = "Failed to select attachment.";
|
||||
|
||||
|
|
|
@ -1081,13 +1081,16 @@
|
|||
"HOME_VIEW_TITLE_INBOX" = "Signal";
|
||||
|
||||
/* Label for brush button in image editor. */
|
||||
"IMAGE_EDITOR_BRUSH_BUTTON" = "Brush";
|
||||
"IMAGE_EDITOR_BRUSH_BUTTON" = "ブラシ";
|
||||
|
||||
/* Label for crop button in image editor. */
|
||||
"IMAGE_EDITOR_CROP_BUTTON" = "Crop";
|
||||
"IMAGE_EDITOR_CROP_BUTTON" = "切り取り";
|
||||
|
||||
/* Momentarily shown to the user when attempting to select more images than is allowed. Embeds {{max number of items}} that can be shared. */
|
||||
"IMAGE_PICKER_CAN_SELECT_NO_MORE_TOAST_FORMAT" = "You can't share more than %@ items.";
|
||||
|
||||
/* alert title */
|
||||
"IMAGE_PICKER_FAILED_TO_PROCESS_ATTACHMENTS" = "Failed to select attachment.";
|
||||
"IMAGE_PICKER_FAILED_TO_PROCESS_ATTACHMENTS" = "添付ファイルを選択できませんでした";
|
||||
|
||||
/* Call setup status label */
|
||||
"IN_CALL_CONNECTING" = "接続しています...";
|
||||
|
@ -1470,7 +1473,7 @@
|
|||
"NO_CONTACTS_SEARCH_BY_PHONE_NUMBER" = "連絡先を電話番号で検索";
|
||||
|
||||
/* Label for 1:1 conversation with yourself. */
|
||||
"NOTE_TO_SELF" = "Note to Self";
|
||||
"NOTE_TO_SELF" = "自分のためのメモ";
|
||||
|
||||
/* Lock screen notification text presented after user powers on their device without unlocking. Embeds {{device model}} (either 'iPad' or 'iPhone') */
|
||||
"NOTIFICATION_BODY_PHONE_LOCKED_FORMAT" = "%@の再起動中にメッセージが届いたかもしれません";
|
||||
|
|
|
@ -1086,6 +1086,9 @@
|
|||
/* Label for crop button in image editor. */
|
||||
"IMAGE_EDITOR_CROP_BUTTON" = "Crop";
|
||||
|
||||
/* Momentarily shown to the user when attempting to select more images than is allowed. Embeds {{max number of items}} that can be shared. */
|
||||
"IMAGE_PICKER_CAN_SELECT_NO_MORE_TOAST_FORMAT" = "You can't share more than %@ items.";
|
||||
|
||||
/* alert title */
|
||||
"IMAGE_PICKER_FAILED_TO_PROCESS_ATTACHMENTS" = "Failed to select attachment.";
|
||||
|
||||
|
|
|
@ -1086,6 +1086,9 @@
|
|||
/* Label for crop button in image editor. */
|
||||
"IMAGE_EDITOR_CROP_BUTTON" = "Crop";
|
||||
|
||||
/* Momentarily shown to the user when attempting to select more images than is allowed. Embeds {{max number of items}} that can be shared. */
|
||||
"IMAGE_PICKER_CAN_SELECT_NO_MORE_TOAST_FORMAT" = "You can't share more than %@ items.";
|
||||
|
||||
/* alert title */
|
||||
"IMAGE_PICKER_FAILED_TO_PROCESS_ATTACHMENTS" = "Failed to select attachment.";
|
||||
|
||||
|
|
|
@ -1081,13 +1081,16 @@
|
|||
"HOME_VIEW_TITLE_INBOX" = "Signal";
|
||||
|
||||
/* Label for brush button in image editor. */
|
||||
"IMAGE_EDITOR_BRUSH_BUTTON" = "Brush";
|
||||
"IMAGE_EDITOR_BRUSH_BUTTON" = "Teptukas";
|
||||
|
||||
/* Label for crop button in image editor. */
|
||||
"IMAGE_EDITOR_CROP_BUTTON" = "Crop";
|
||||
"IMAGE_EDITOR_CROP_BUTTON" = "Apkirpti";
|
||||
|
||||
/* Momentarily shown to the user when attempting to select more images than is allowed. Embeds {{max number of items}} that can be shared. */
|
||||
"IMAGE_PICKER_CAN_SELECT_NO_MORE_TOAST_FORMAT" = "You can't share more than %@ items.";
|
||||
|
||||
/* alert title */
|
||||
"IMAGE_PICKER_FAILED_TO_PROCESS_ATTACHMENTS" = "Failed to select attachment.";
|
||||
"IMAGE_PICKER_FAILED_TO_PROCESS_ATTACHMENTS" = "Nepavyko pasirinkti priedo.";
|
||||
|
||||
/* Call setup status label */
|
||||
"IN_CALL_CONNECTING" = "Jungiamasi…";
|
||||
|
@ -1470,7 +1473,7 @@
|
|||
"NO_CONTACTS_SEARCH_BY_PHONE_NUMBER" = "Rasti kontaktus pagal telefono numerį";
|
||||
|
||||
/* Label for 1:1 conversation with yourself. */
|
||||
"NOTE_TO_SELF" = "Note to Self";
|
||||
"NOTE_TO_SELF" = "Pastabos sau";
|
||||
|
||||
/* Lock screen notification text presented after user powers on their device without unlocking. Embeds {{device model}} (either 'iPad' or 'iPhone') */
|
||||
"NOTIFICATION_BODY_PHONE_LOCKED_FORMAT" = "Gali būti, kad kol jūsų %@ buvo paleidžiamas iš naujo, jūs gavote žinutes.";
|
||||
|
|
|
@ -1086,6 +1086,9 @@
|
|||
/* Label for crop button in image editor. */
|
||||
"IMAGE_EDITOR_CROP_BUTTON" = "Crop";
|
||||
|
||||
/* Momentarily shown to the user when attempting to select more images than is allowed. Embeds {{max number of items}} that can be shared. */
|
||||
"IMAGE_PICKER_CAN_SELECT_NO_MORE_TOAST_FORMAT" = "You can't share more than %@ items.";
|
||||
|
||||
/* alert title */
|
||||
"IMAGE_PICKER_FAILED_TO_PROCESS_ATTACHMENTS" = "Failed to select attachment.";
|
||||
|
||||
|
|
|
@ -1086,6 +1086,9 @@
|
|||
/* Label for crop button in image editor. */
|
||||
"IMAGE_EDITOR_CROP_BUTTON" = "Crop";
|
||||
|
||||
/* Momentarily shown to the user when attempting to select more images than is allowed. Embeds {{max number of items}} that can be shared. */
|
||||
"IMAGE_PICKER_CAN_SELECT_NO_MORE_TOAST_FORMAT" = "You can't share more than %@ items.";
|
||||
|
||||
/* alert title */
|
||||
"IMAGE_PICKER_FAILED_TO_PROCESS_ATTACHMENTS" = "Failed to select attachment.";
|
||||
|
||||
|
|
|
@ -1086,6 +1086,9 @@
|
|||
/* Label for crop button in image editor. */
|
||||
"IMAGE_EDITOR_CROP_BUTTON" = "Crop";
|
||||
|
||||
/* Momentarily shown to the user when attempting to select more images than is allowed. Embeds {{max number of items}} that can be shared. */
|
||||
"IMAGE_PICKER_CAN_SELECT_NO_MORE_TOAST_FORMAT" = "You can't share more than %@ items.";
|
||||
|
||||
/* alert title */
|
||||
"IMAGE_PICKER_FAILED_TO_PROCESS_ATTACHMENTS" = "Failed to select attachment.";
|
||||
|
||||
|
|
|
@ -1086,6 +1086,9 @@
|
|||
/* Label for crop button in image editor. */
|
||||
"IMAGE_EDITOR_CROP_BUTTON" = "Crop";
|
||||
|
||||
/* Momentarily shown to the user when attempting to select more images than is allowed. Embeds {{max number of items}} that can be shared. */
|
||||
"IMAGE_PICKER_CAN_SELECT_NO_MORE_TOAST_FORMAT" = "You can't share more than %@ items.";
|
||||
|
||||
/* alert title */
|
||||
"IMAGE_PICKER_FAILED_TO_PROCESS_ATTACHMENTS" = "Failed to select attachment.";
|
||||
|
||||
|
|
|
@ -1081,13 +1081,16 @@
|
|||
"HOME_VIEW_TITLE_INBOX" = "Signal";
|
||||
|
||||
/* Label for brush button in image editor. */
|
||||
"IMAGE_EDITOR_BRUSH_BUTTON" = "Brush";
|
||||
"IMAGE_EDITOR_BRUSH_BUTTON" = "Kwast";
|
||||
|
||||
/* Label for crop button in image editor. */
|
||||
"IMAGE_EDITOR_CROP_BUTTON" = "Crop";
|
||||
"IMAGE_EDITOR_CROP_BUTTON" = "Bijsnijden";
|
||||
|
||||
/* Momentarily shown to the user when attempting to select more images than is allowed. Embeds {{max number of items}} that can be shared. */
|
||||
"IMAGE_PICKER_CAN_SELECT_NO_MORE_TOAST_FORMAT" = "You can't share more than %@ items.";
|
||||
|
||||
/* alert title */
|
||||
"IMAGE_PICKER_FAILED_TO_PROCESS_ATTACHMENTS" = "Failed to select attachment.";
|
||||
"IMAGE_PICKER_FAILED_TO_PROCESS_ATTACHMENTS" = "Bijlage selecteren mislukt.";
|
||||
|
||||
/* Call setup status label */
|
||||
"IN_CALL_CONNECTING" = "Verbinden…";
|
||||
|
@ -1470,7 +1473,7 @@
|
|||
"NO_CONTACTS_SEARCH_BY_PHONE_NUMBER" = "Zoek contacten op telefoonnummer";
|
||||
|
||||
/* Label for 1:1 conversation with yourself. */
|
||||
"NOTE_TO_SELF" = "Note to Self";
|
||||
"NOTE_TO_SELF" = "Notitie aan mezelf";
|
||||
|
||||
/* Lock screen notification text presented after user powers on their device without unlocking. Embeds {{device model}} (either 'iPad' or 'iPhone') */
|
||||
"NOTIFICATION_BODY_PHONE_LOCKED_FORMAT" = "U heeft misschien berichten ontvangen terwijl uw %@ aan het herstarten was.";
|
||||
|
|
|
@ -1081,13 +1081,16 @@
|
|||
"HOME_VIEW_TITLE_INBOX" = "Signal";
|
||||
|
||||
/* Label for brush button in image editor. */
|
||||
"IMAGE_EDITOR_BRUSH_BUTTON" = "Brush";
|
||||
"IMAGE_EDITOR_BRUSH_BUTTON" = "Pędzel";
|
||||
|
||||
/* Label for crop button in image editor. */
|
||||
"IMAGE_EDITOR_CROP_BUTTON" = "Crop";
|
||||
"IMAGE_EDITOR_CROP_BUTTON" = "Przytnij";
|
||||
|
||||
/* Momentarily shown to the user when attempting to select more images than is allowed. Embeds {{max number of items}} that can be shared. */
|
||||
"IMAGE_PICKER_CAN_SELECT_NO_MORE_TOAST_FORMAT" = "You can't share more than %@ items.";
|
||||
|
||||
/* alert title */
|
||||
"IMAGE_PICKER_FAILED_TO_PROCESS_ATTACHMENTS" = "Failed to select attachment.";
|
||||
"IMAGE_PICKER_FAILED_TO_PROCESS_ATTACHMENTS" = "Nie udało się wybrać załącznika.";
|
||||
|
||||
/* Call setup status label */
|
||||
"IN_CALL_CONNECTING" = "Łączenie...";
|
||||
|
|
|
@ -1086,6 +1086,9 @@
|
|||
/* Label for crop button in image editor. */
|
||||
"IMAGE_EDITOR_CROP_BUTTON" = "Crop";
|
||||
|
||||
/* Momentarily shown to the user when attempting to select more images than is allowed. Embeds {{max number of items}} that can be shared. */
|
||||
"IMAGE_PICKER_CAN_SELECT_NO_MORE_TOAST_FORMAT" = "You can't share more than %@ items.";
|
||||
|
||||
/* alert title */
|
||||
"IMAGE_PICKER_FAILED_TO_PROCESS_ATTACHMENTS" = "Failed to select attachment.";
|
||||
|
||||
|
|
|
@ -1086,6 +1086,9 @@
|
|||
/* Label for crop button in image editor. */
|
||||
"IMAGE_EDITOR_CROP_BUTTON" = "Crop";
|
||||
|
||||
/* Momentarily shown to the user when attempting to select more images than is allowed. Embeds {{max number of items}} that can be shared. */
|
||||
"IMAGE_PICKER_CAN_SELECT_NO_MORE_TOAST_FORMAT" = "You can't share more than %@ items.";
|
||||
|
||||
/* alert title */
|
||||
"IMAGE_PICKER_FAILED_TO_PROCESS_ATTACHMENTS" = "Failed to select attachment.";
|
||||
|
||||
|
|
|
@ -1081,13 +1081,16 @@
|
|||
"HOME_VIEW_TITLE_INBOX" = "Signal";
|
||||
|
||||
/* Label for brush button in image editor. */
|
||||
"IMAGE_EDITOR_BRUSH_BUTTON" = "Brush";
|
||||
"IMAGE_EDITOR_BRUSH_BUTTON" = "Pensulă";
|
||||
|
||||
/* Label for crop button in image editor. */
|
||||
"IMAGE_EDITOR_CROP_BUTTON" = "Crop";
|
||||
"IMAGE_EDITOR_CROP_BUTTON" = "Decupare";
|
||||
|
||||
/* Momentarily shown to the user when attempting to select more images than is allowed. Embeds {{max number of items}} that can be shared. */
|
||||
"IMAGE_PICKER_CAN_SELECT_NO_MORE_TOAST_FORMAT" = "You can't share more than %@ items.";
|
||||
|
||||
/* alert title */
|
||||
"IMAGE_PICKER_FAILED_TO_PROCESS_ATTACHMENTS" = "Failed to select attachment.";
|
||||
"IMAGE_PICKER_FAILED_TO_PROCESS_ATTACHMENTS" = "Atașamentul nu a putut fi selectat.";
|
||||
|
||||
/* Call setup status label */
|
||||
"IN_CALL_CONNECTING" = "Se conectează...";
|
||||
|
@ -1470,7 +1473,7 @@
|
|||
"NO_CONTACTS_SEARCH_BY_PHONE_NUMBER" = "Găsește contacte după numărul de telefon";
|
||||
|
||||
/* Label for 1:1 conversation with yourself. */
|
||||
"NOTE_TO_SELF" = "Note to Self";
|
||||
"NOTE_TO_SELF" = "Notă personală";
|
||||
|
||||
/* Lock screen notification text presented after user powers on their device without unlocking. Embeds {{device model}} (either 'iPad' or 'iPhone') */
|
||||
"NOTIFICATION_BODY_PHONE_LOCKED_FORMAT" = "S-ar putea să fi primit mesaje în timpul repornirii %@.";
|
||||
|
|
|
@ -1081,13 +1081,16 @@
|
|||
"HOME_VIEW_TITLE_INBOX" = "Signal";
|
||||
|
||||
/* Label for brush button in image editor. */
|
||||
"IMAGE_EDITOR_BRUSH_BUTTON" = "Brush";
|
||||
"IMAGE_EDITOR_BRUSH_BUTTON" = "Кисть";
|
||||
|
||||
/* Label for crop button in image editor. */
|
||||
"IMAGE_EDITOR_CROP_BUTTON" = "Crop";
|
||||
"IMAGE_EDITOR_CROP_BUTTON" = "Обрезка";
|
||||
|
||||
/* Momentarily shown to the user when attempting to select more images than is allowed. Embeds {{max number of items}} that can be shared. */
|
||||
"IMAGE_PICKER_CAN_SELECT_NO_MORE_TOAST_FORMAT" = "You can't share more than %@ items.";
|
||||
|
||||
/* alert title */
|
||||
"IMAGE_PICKER_FAILED_TO_PROCESS_ATTACHMENTS" = "Failed to select attachment.";
|
||||
"IMAGE_PICKER_FAILED_TO_PROCESS_ATTACHMENTS" = "Не удалось выбрать вложение.";
|
||||
|
||||
/* Call setup status label */
|
||||
"IN_CALL_CONNECTING" = "Соединение...";
|
||||
|
@ -1470,7 +1473,7 @@
|
|||
"NO_CONTACTS_SEARCH_BY_PHONE_NUMBER" = "Поиск по номеру телефона";
|
||||
|
||||
/* Label for 1:1 conversation with yourself. */
|
||||
"NOTE_TO_SELF" = "Note to Self";
|
||||
"NOTE_TO_SELF" = "Примечание для себя";
|
||||
|
||||
/* Lock screen notification text presented after user powers on their device without unlocking. Embeds {{device model}} (either 'iPad' or 'iPhone') */
|
||||
"NOTIFICATION_BODY_PHONE_LOCKED_FORMAT" = "Возможно, вы получили сообщения во время перезапуска вашего %@.";
|
||||
|
|
|
@ -1086,6 +1086,9 @@
|
|||
/* Label for crop button in image editor. */
|
||||
"IMAGE_EDITOR_CROP_BUTTON" = "Crop";
|
||||
|
||||
/* Momentarily shown to the user when attempting to select more images than is allowed. Embeds {{max number of items}} that can be shared. */
|
||||
"IMAGE_PICKER_CAN_SELECT_NO_MORE_TOAST_FORMAT" = "You can't share more than %@ items.";
|
||||
|
||||
/* alert title */
|
||||
"IMAGE_PICKER_FAILED_TO_PROCESS_ATTACHMENTS" = "Failed to select attachment.";
|
||||
|
||||
|
|
|
@ -1086,6 +1086,9 @@
|
|||
/* Label for crop button in image editor. */
|
||||
"IMAGE_EDITOR_CROP_BUTTON" = "Crop";
|
||||
|
||||
/* Momentarily shown to the user when attempting to select more images than is allowed. Embeds {{max number of items}} that can be shared. */
|
||||
"IMAGE_PICKER_CAN_SELECT_NO_MORE_TOAST_FORMAT" = "You can't share more than %@ items.";
|
||||
|
||||
/* alert title */
|
||||
"IMAGE_PICKER_FAILED_TO_PROCESS_ATTACHMENTS" = "Failed to select attachment.";
|
||||
|
||||
|
|
|
@ -1081,13 +1081,16 @@
|
|||
"HOME_VIEW_TITLE_INBOX" = "Signal";
|
||||
|
||||
/* Label for brush button in image editor. */
|
||||
"IMAGE_EDITOR_BRUSH_BUTTON" = "Brush";
|
||||
"IMAGE_EDITOR_BRUSH_BUTTON" = "Lyeje";
|
||||
|
||||
/* Label for crop button in image editor. */
|
||||
"IMAGE_EDITOR_CROP_BUTTON" = "Crop";
|
||||
"IMAGE_EDITOR_CROP_BUTTON" = "Qethe";
|
||||
|
||||
/* Momentarily shown to the user when attempting to select more images than is allowed. Embeds {{max number of items}} that can be shared. */
|
||||
"IMAGE_PICKER_CAN_SELECT_NO_MORE_TOAST_FORMAT" = "You can't share more than %@ items.";
|
||||
|
||||
/* alert title */
|
||||
"IMAGE_PICKER_FAILED_TO_PROCESS_ATTACHMENTS" = "Failed to select attachment.";
|
||||
"IMAGE_PICKER_FAILED_TO_PROCESS_ATTACHMENTS" = "S’u arrit të përzgjidhej bashkëngjitje.";
|
||||
|
||||
/* Call setup status label */
|
||||
"IN_CALL_CONNECTING" = "Po lidhet…";
|
||||
|
@ -1470,7 +1473,7 @@
|
|||
"NO_CONTACTS_SEARCH_BY_PHONE_NUMBER" = "Gjeni Kontakte sipas Numri Telefoni";
|
||||
|
||||
/* Label for 1:1 conversation with yourself. */
|
||||
"NOTE_TO_SELF" = "Note to Self";
|
||||
"NOTE_TO_SELF" = "Shënim për Veten";
|
||||
|
||||
/* Lock screen notification text presented after user powers on their device without unlocking. Embeds {{device model}} (either 'iPad' or 'iPhone') */
|
||||
"NOTIFICATION_BODY_PHONE_LOCKED_FORMAT" = "Mund të keni marrë mesazhe teksa %@ po rinisej.";
|
||||
|
|
|
@ -1086,6 +1086,9 @@
|
|||
/* Label for crop button in image editor. */
|
||||
"IMAGE_EDITOR_CROP_BUTTON" = "Crop";
|
||||
|
||||
/* Momentarily shown to the user when attempting to select more images than is allowed. Embeds {{max number of items}} that can be shared. */
|
||||
"IMAGE_PICKER_CAN_SELECT_NO_MORE_TOAST_FORMAT" = "You can't share more than %@ items.";
|
||||
|
||||
/* alert title */
|
||||
"IMAGE_PICKER_FAILED_TO_PROCESS_ATTACHMENTS" = "Failed to select attachment.";
|
||||
|
||||
|
|
|
@ -1086,6 +1086,9 @@
|
|||
/* Label for crop button in image editor. */
|
||||
"IMAGE_EDITOR_CROP_BUTTON" = "Crop";
|
||||
|
||||
/* Momentarily shown to the user when attempting to select more images than is allowed. Embeds {{max number of items}} that can be shared. */
|
||||
"IMAGE_PICKER_CAN_SELECT_NO_MORE_TOAST_FORMAT" = "You can't share more than %@ items.";
|
||||
|
||||
/* alert title */
|
||||
"IMAGE_PICKER_FAILED_TO_PROCESS_ATTACHMENTS" = "Failed to select attachment.";
|
||||
|
||||
|
|
|
@ -1086,6 +1086,9 @@
|
|||
/* Label for crop button in image editor. */
|
||||
"IMAGE_EDITOR_CROP_BUTTON" = "Crop";
|
||||
|
||||
/* Momentarily shown to the user when attempting to select more images than is allowed. Embeds {{max number of items}} that can be shared. */
|
||||
"IMAGE_PICKER_CAN_SELECT_NO_MORE_TOAST_FORMAT" = "You can't share more than %@ items.";
|
||||
|
||||
/* alert title */
|
||||
"IMAGE_PICKER_FAILED_TO_PROCESS_ATTACHMENTS" = "Failed to select attachment.";
|
||||
|
||||
|
|
|
@ -1086,6 +1086,9 @@
|
|||
/* Label for crop button in image editor. */
|
||||
"IMAGE_EDITOR_CROP_BUTTON" = "Crop";
|
||||
|
||||
/* Momentarily shown to the user when attempting to select more images than is allowed. Embeds {{max number of items}} that can be shared. */
|
||||
"IMAGE_PICKER_CAN_SELECT_NO_MORE_TOAST_FORMAT" = "You can't share more than %@ items.";
|
||||
|
||||
/* alert title */
|
||||
"IMAGE_PICKER_FAILED_TO_PROCESS_ATTACHMENTS" = "Failed to select attachment.";
|
||||
|
||||
|
|
|
@ -1086,6 +1086,9 @@
|
|||
/* Label for crop button in image editor. */
|
||||
"IMAGE_EDITOR_CROP_BUTTON" = "Crop";
|
||||
|
||||
/* Momentarily shown to the user when attempting to select more images than is allowed. Embeds {{max number of items}} that can be shared. */
|
||||
"IMAGE_PICKER_CAN_SELECT_NO_MORE_TOAST_FORMAT" = "You can't share more than %@ items.";
|
||||
|
||||
/* alert title */
|
||||
"IMAGE_PICKER_FAILED_TO_PROCESS_ATTACHMENTS" = "Failed to select attachment.";
|
||||
|
||||
|
|
|
@ -1081,13 +1081,16 @@
|
|||
"HOME_VIEW_TITLE_INBOX" = "Signal";
|
||||
|
||||
/* Label for brush button in image editor. */
|
||||
"IMAGE_EDITOR_BRUSH_BUTTON" = "Brush";
|
||||
"IMAGE_EDITOR_BRUSH_BUTTON" = "筆刷";
|
||||
|
||||
/* Label for crop button in image editor. */
|
||||
"IMAGE_EDITOR_CROP_BUTTON" = "Crop";
|
||||
"IMAGE_EDITOR_CROP_BUTTON" = "裁剪";
|
||||
|
||||
/* Momentarily shown to the user when attempting to select more images than is allowed. Embeds {{max number of items}} that can be shared. */
|
||||
"IMAGE_PICKER_CAN_SELECT_NO_MORE_TOAST_FORMAT" = "You can't share more than %@ items.";
|
||||
|
||||
/* alert title */
|
||||
"IMAGE_PICKER_FAILED_TO_PROCESS_ATTACHMENTS" = "Failed to select attachment.";
|
||||
"IMAGE_PICKER_FAILED_TO_PROCESS_ATTACHMENTS" = "無法選擇附檔。";
|
||||
|
||||
/* Call setup status label */
|
||||
"IN_CALL_CONNECTING" = "連接中...";
|
||||
|
@ -1470,7 +1473,7 @@
|
|||
"NO_CONTACTS_SEARCH_BY_PHONE_NUMBER" = "透過手機號碼搜尋聯絡人";
|
||||
|
||||
/* Label for 1:1 conversation with yourself. */
|
||||
"NOTE_TO_SELF" = "Note to Self";
|
||||
"NOTE_TO_SELF" = "給自己的筆記";
|
||||
|
||||
/* Lock screen notification text presented after user powers on their device without unlocking. Embeds {{device model}} (either 'iPad' or 'iPhone') */
|
||||
"NOTIFICATION_BODY_PHONE_LOCKED_FORMAT" = "當你的%@重新起動時,你還是可以接收訊息。";
|
||||
|
|
|
@ -1531,6 +1531,10 @@ class CaptionView: UIView {
|
|||
}
|
||||
|
||||
let kMaxCaptionCharacterCount = 240
|
||||
|
||||
// Coincides with Android's max text message length
|
||||
let kMaxMessageBodyCharacterCount = 2000
|
||||
|
||||
extension CaptionView: UITextViewDelegate {
|
||||
|
||||
public func textViewShouldBeginEditing(_ textView: UITextView) -> Bool {
|
||||
|
@ -1841,6 +1845,7 @@ class MediaMessageTextToolbar: UIView, UITextViewDelegate {
|
|||
let existingText: String = textView.text ?? ""
|
||||
let proposedText: String = (existingText as NSString).replacingCharacters(in: range, with: text)
|
||||
|
||||
// Don't complicate things by mixing media attachments with oversize text attachments
|
||||
guard proposedText.utf8.count <= kOversizeTextMessageSizeThreshold else {
|
||||
Logger.debug("long text was truncated")
|
||||
self.lengthLimitLabel.isHidden = false
|
||||
|
@ -1861,6 +1866,25 @@ class MediaMessageTextToolbar: UIView, UITextViewDelegate {
|
|||
}
|
||||
self.lengthLimitLabel.isHidden = true
|
||||
|
||||
// After verifying the byte-length is sufficiently small, verify the character count is within bounds.
|
||||
guard proposedText.count <= kMaxMessageBodyCharacterCount else {
|
||||
Logger.debug("hit attachment message body character count limit")
|
||||
|
||||
self.lengthLimitLabel.isHidden = false
|
||||
|
||||
// `range` represents the section of the existing text we will replace. We can re-use that space.
|
||||
let charsAfterDelete: Int = (existingText as NSString).replacingCharacters(in: range, with: "").count
|
||||
|
||||
// Accept as much of the input as we can
|
||||
let charBudget: Int = Int(kMaxMessageBodyCharacterCount) - charsAfterDelete
|
||||
if charBudget >= 0 {
|
||||
let acceptableNewText = String(text.prefix(charBudget))
|
||||
textView.text = (existingText as NSString).replacingCharacters(in: range, with: acceptableNewText)
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// Though we can wrap the text, we don't want to encourage multline captions, plus a "done" button
|
||||
// allows the user to get the keyboard out of the way while in the attachment approval view.
|
||||
if text == "\n" {
|
||||
|
|
91
SignalMessaging/Views/DirectionalPanGestureRecognizer.swift
Normal file
91
SignalMessaging/Views/DirectionalPanGestureRecognizer.swift
Normal file
|
@ -0,0 +1,91 @@
|
|||
//
|
||||
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit.UIGestureRecognizerSubclass
|
||||
|
||||
public struct PanDirection: OptionSet {
|
||||
public let rawValue: Int
|
||||
public init(rawValue: Int) {
|
||||
self.rawValue = rawValue
|
||||
}
|
||||
|
||||
public static let left = PanDirection(rawValue: 1 << 0)
|
||||
public static let right = PanDirection(rawValue: 1 << 1)
|
||||
public static let up = PanDirection(rawValue: 1 << 2)
|
||||
public static let down = PanDirection(rawValue: 1 << 3)
|
||||
|
||||
public static let horizontal: PanDirection = [.left, .right]
|
||||
public static let vertical: PanDirection = [.up, .down]
|
||||
public static let any: PanDirection = [.left, .right, .up, .down]
|
||||
}
|
||||
|
||||
public class DirectionalPanGestureRecognizer: UIPanGestureRecognizer {
|
||||
|
||||
let direction: PanDirection
|
||||
|
||||
public init(direction: PanDirection, target: AnyObject, action: Selector) {
|
||||
self.direction = direction
|
||||
|
||||
super.init(target: target, action: action)
|
||||
}
|
||||
|
||||
override public func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent) {
|
||||
// Only start gesture if it's initially in the specified direction.
|
||||
if state == .possible {
|
||||
guard let touch = touches.first else {
|
||||
return
|
||||
}
|
||||
|
||||
let previousLocation = touch.previousLocation(in: view)
|
||||
let location = touch.location(in: view)
|
||||
let deltaY = previousLocation.y - location.y
|
||||
let deltaX = previousLocation.x - location.x
|
||||
|
||||
let isSatisified: Bool = {
|
||||
if abs(deltaY) > abs(deltaX) {
|
||||
if direction.contains(.up) && deltaY < 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
if direction.contains(.down) && deltaY > 0 {
|
||||
return true
|
||||
}
|
||||
} else {
|
||||
if direction.contains(.left) && deltaX < 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
if direction.contains(.right) && deltaX > 0 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}()
|
||||
|
||||
guard isSatisified else {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Gesture was already started, or in the correct direction.
|
||||
super.touchesMoved(touches, with: event)
|
||||
|
||||
if state == .began {
|
||||
let vel = velocity(in: view)
|
||||
switch direction {
|
||||
case .left, .right:
|
||||
if fabs(vel.y) > fabs(vel.x) {
|
||||
state = .cancelled
|
||||
}
|
||||
case .up, .down:
|
||||
if fabs(vel.x) > fabs(vel.y) {
|
||||
state = .cancelled
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,71 +1,11 @@
|
|||
//
|
||||
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
protocol ToastViewDelegate: class {
|
||||
func didTapToastView(_ toastView: ToastView)
|
||||
func didSwipeToastView(_ toastView: ToastView)
|
||||
}
|
||||
|
||||
class ToastView: UIView {
|
||||
|
||||
var text: String? {
|
||||
get {
|
||||
return label.text
|
||||
}
|
||||
set {
|
||||
label.text = newValue
|
||||
}
|
||||
}
|
||||
weak var delegate: ToastViewDelegate?
|
||||
|
||||
private let label: UILabel
|
||||
|
||||
// MARK: Initializers
|
||||
|
||||
override init(frame: CGRect) {
|
||||
label = UILabel()
|
||||
super.init(frame: frame)
|
||||
|
||||
self.layer.cornerRadius = 4
|
||||
self.backgroundColor = Theme.toastBackgroundColor
|
||||
self.layoutMargins = UIEdgeInsets(top: 8, left: 8, bottom: 8, right: 8)
|
||||
|
||||
label.textAlignment = .center
|
||||
label.textColor = Theme.toastForegroundColor
|
||||
label.font = UIFont.ows_dynamicTypeBody
|
||||
label.numberOfLines = 0
|
||||
self.addSubview(label)
|
||||
label.autoPinEdgesToSuperviewMargins()
|
||||
|
||||
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(didTap(gesture:)))
|
||||
self.addGestureRecognizer(tapGesture)
|
||||
|
||||
let swipeGesture = UISwipeGestureRecognizer(target: self, action: #selector(didSwipe(gesture:)))
|
||||
self.addGestureRecognizer(swipeGesture)
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
notImplemented()
|
||||
}
|
||||
|
||||
// MARK: Gestures
|
||||
|
||||
@objc
|
||||
func didTap(gesture: UITapGestureRecognizer) {
|
||||
self.delegate?.didTapToastView(self)
|
||||
}
|
||||
|
||||
@objc
|
||||
func didSwipe(gesture: UISwipeGestureRecognizer) {
|
||||
self.delegate?.didSwipeToastView(self)
|
||||
}
|
||||
}
|
||||
|
||||
@objc
|
||||
class ToastController: NSObject, ToastViewDelegate {
|
||||
public class ToastController: NSObject, ToastViewDelegate {
|
||||
|
||||
static var currentToastController: ToastController?
|
||||
|
||||
|
@ -75,7 +15,7 @@ class ToastController: NSObject, ToastViewDelegate {
|
|||
// MARK: Initializers
|
||||
|
||||
@objc
|
||||
required init(text: String) {
|
||||
required public init(text: String) {
|
||||
toastView = ToastView()
|
||||
toastView.text = text
|
||||
isDismissing = false
|
||||
|
@ -85,14 +25,10 @@ class ToastController: NSObject, ToastViewDelegate {
|
|||
toastView.delegate = self
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
notImplemented()
|
||||
}
|
||||
|
||||
// MARK: Public
|
||||
|
||||
@objc
|
||||
func presentToastView(fromBottomOfView view: UIView, inset: CGFloat) {
|
||||
public func presentToastView(fromBottomOfView view: UIView, inset: CGFloat) {
|
||||
Logger.debug("")
|
||||
toastView.alpha = 0
|
||||
view.addSubview(toastView)
|
||||
|
@ -153,3 +89,63 @@ class ToastController: NSObject, ToastViewDelegate {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
protocol ToastViewDelegate: class {
|
||||
func didTapToastView(_ toastView: ToastView)
|
||||
func didSwipeToastView(_ toastView: ToastView)
|
||||
}
|
||||
|
||||
class ToastView: UIView {
|
||||
|
||||
var text: String? {
|
||||
get {
|
||||
return label.text
|
||||
}
|
||||
set {
|
||||
label.text = newValue
|
||||
}
|
||||
}
|
||||
weak var delegate: ToastViewDelegate?
|
||||
|
||||
private let label: UILabel
|
||||
|
||||
// MARK: Initializers
|
||||
|
||||
override init(frame: CGRect) {
|
||||
label = UILabel()
|
||||
super.init(frame: frame)
|
||||
|
||||
self.layer.cornerRadius = 4
|
||||
self.backgroundColor = Theme.toastBackgroundColor
|
||||
self.layoutMargins = UIEdgeInsets(top: 8, left: 8, bottom: 8, right: 8)
|
||||
|
||||
label.textAlignment = .center
|
||||
label.textColor = Theme.toastForegroundColor
|
||||
label.font = UIFont.ows_dynamicTypeBody
|
||||
label.numberOfLines = 0
|
||||
self.addSubview(label)
|
||||
label.autoPinEdgesToSuperviewMargins()
|
||||
|
||||
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(didTap(gesture:)))
|
||||
self.addGestureRecognizer(tapGesture)
|
||||
|
||||
let swipeGesture = UISwipeGestureRecognizer(target: self, action: #selector(didSwipe(gesture:)))
|
||||
self.addGestureRecognizer(swipeGesture)
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
notImplemented()
|
||||
}
|
||||
|
||||
// MARK: Gestures
|
||||
|
||||
@objc
|
||||
func didTap(gesture: UITapGestureRecognizer) {
|
||||
self.delegate?.didTapToastView(self)
|
||||
}
|
||||
|
||||
@objc
|
||||
func didSwipe(gesture: UISwipeGestureRecognizer) {
|
||||
self.delegate?.didSwipeToastView(self)
|
||||
}
|
||||
}
|
|
@ -19,7 +19,7 @@
|
|||
<key>CFBundleShortVersionString</key>
|
||||
<string>2.34.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>2.34.0.25</string>
|
||||
<string>2.34.0.26</string>
|
||||
<key>ITSAppUsesNonExemptEncryption</key>
|
||||
<false/>
|
||||
<key>NSAppTransportSecurity</key>
|
||||
|
|
Loading…
Reference in a new issue