Merge remote-tracking branch 'upstream/dev' into feature/job-runner-unit-tests
# Conflicts: # Session.xcodeproj/project.pbxproj # SessionUtilitiesKit/Database/Models/Job.swift # SessionUtilitiesKit/Database/Models/JobDependencies.swift # SessionUtilitiesKit/JobRunner/JobRunner.swift
|
@ -1,40 +0,0 @@
|
|||
<!-- This is a bug report template. By following the instructions below and filling out the sections with your information, you will help the developers get all the necessary data to fix your issue.
|
||||
You can also preview your report before submitting it. You may remove sections that aren't relevant to your particular case.
|
||||
|
||||
Before we begin, please note that this tracker is only for issues. It is not for questions, comments, or feature requests.
|
||||
|
||||
If you are looking for support, please email team@oxen.io.
|
||||
|
||||
Let's begin with a checklist: Replace the empty checkboxes [ ] below with checked ones [x] accordingly. -->
|
||||
|
||||
- [ ] I have read and agree to adhere to the [Code of Conduct](https://github.com/oxen-io/session-ios/blob/master/CODE_OF_CONDUCT.md).
|
||||
- [ ] I have searched open and closed issues for duplicates
|
||||
- [ ] I am submitting a bug report for existing functionality that does not work as intended
|
||||
- [ ] This isn't a feature request or a discussion topic
|
||||
|
||||
----------------------------------------
|
||||
|
||||
### Bug description
|
||||
Describe here the issue that you are experiencing.
|
||||
|
||||
### Steps to reproduce
|
||||
- using hyphens as bullet points
|
||||
- list the steps
|
||||
- that reproduce the bug
|
||||
|
||||
#### Actual result:
|
||||
Describe here what happens after you run the steps above (i.e. the buggy behaviour).
|
||||
|
||||
#### Expected result:
|
||||
Describe here what should happen after you run the steps above (i.e. what would be the correct behaviour).
|
||||
|
||||
### Screenshots
|
||||
<!-- you can drag and drop images below -->
|
||||
|
||||
### Device info
|
||||
<!-- replace the examples with your info -->
|
||||
**Device**: iDevice X
|
||||
|
||||
**iOS version**: X.Y.Z
|
||||
|
||||
**Session version:** X.Y.Z
|
|
@ -1,34 +0,0 @@
|
|||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: bug
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Code of conduct**
|
||||
|
||||
- [ ] I have read and agree to adhere to the [Code of Conduct](https://github.com/oxen-io/session-ios/blob/master/CODE_OF_CONDUCT.md).
|
||||
|
||||
**Describe the bug**
|
||||
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**To reproduce**
|
||||
|
||||
Steps to reproduce the behavior.
|
||||
|
||||
**Screenshots or logs**
|
||||
|
||||
If applicable, add screenshots or logs to help explain your problem.
|
||||
|
||||
**Smartphone (please complete the following information):**
|
||||
|
||||
- Device: [e.g. iPhone 6]
|
||||
- OS: [e.g. iOS 8.1]
|
||||
- Version of Session or latest commit hash
|
||||
|
||||
**Additional context**
|
||||
|
||||
Add any other context about the problem here.
|
|
@ -0,0 +1,74 @@
|
|||
name: 🐞 Bug Report
|
||||
description: Create a report to help us improve
|
||||
title: "[BUG] <title>"
|
||||
labels: [bug]
|
||||
body:
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Code of conduct
|
||||
description: I have read and agree to adhere to the [Code of Conduct](https://github.com/oxen-io/session-ios/blob/master/CODE_OF_CONDUCT.md).
|
||||
options:
|
||||
- label: I have read and agree to adhere to the [Code of Conduct](https://github.com/oxen-io/session-ios/blob/master/CODE_OF_CONDUCT.md)
|
||||
required: true
|
||||
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Self-training on how to write a bug report
|
||||
description: High quality bug report can help the team save time and improve the chance of getting fixed. Please read [how to write a bug report](https://www.browserstack.com/guide/how-to-write-a-bug-report) before submitting your issue.
|
||||
options:
|
||||
- label: I have learned [how to write a bug report](https://www.browserstack.com/guide/how-to-write-a-bug-report)
|
||||
required: true
|
||||
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Is there an existing issue for this?
|
||||
description: Please search to see if an issue already exists for the bug you encountered.
|
||||
options:
|
||||
- label: I have searched the existing issues
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Current Behavior
|
||||
description: A concise description of what you're experiencing.
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Expected Behavior
|
||||
description: A concise description of what you expected to happen.
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Steps To Reproduce
|
||||
description: Steps to reproduce the behavior.
|
||||
placeholder: |
|
||||
1. In this environment...
|
||||
2. With this config...
|
||||
3. Run '...'
|
||||
4. See error...
|
||||
validations:
|
||||
required: false
|
||||
- type: input
|
||||
attributes:
|
||||
label: iOS Version
|
||||
description: What version of iOS are you running?
|
||||
placeholder: ex. iOS 16.0
|
||||
validations:
|
||||
required: false
|
||||
- type: input
|
||||
attributes:
|
||||
label: Session Version
|
||||
description: What version of Session are you running? (This can be found at the bottom of the app settings)
|
||||
placeholder: ex. 2.0.0 (375)
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Anything else?
|
||||
description: |
|
||||
Add any other context about the problem here.
|
||||
|
||||
Tip: You can attach screenshots or log files to help explain your problem by clicking this area to highlight it and then dragging files in.
|
||||
validations:
|
||||
required: false
|
|
@ -0,0 +1,26 @@
|
|||
name: 🚀 Feature request
|
||||
description: Suggest an idea for Session
|
||||
title: '[Feature] <title>'
|
||||
labels: [feature-request]
|
||||
body:
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Is there an existing request for feature?
|
||||
description: Please search to see if an issue already exists for the feature you are requesting.
|
||||
options:
|
||||
- label: I have searched the existing issues
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: What feature would you like?
|
||||
description: |
|
||||
A clear and concise description of the feature you would like added to Session
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Anything else?
|
||||
description: |
|
||||
Add any other context or screenshots about the feature request here
|
||||
validations:
|
||||
required: false
|
12
Podfile
|
@ -105,6 +105,9 @@ post_install do |installer|
|
|||
enable_whole_module_optimization_for_crypto_swift(installer)
|
||||
set_minimum_deployment_target(installer)
|
||||
enable_fts5_support(installer)
|
||||
|
||||
#FIXME: Remove this workaround once an official fix is released (hopefully Cocoapods 1.12.1)
|
||||
xcode_14_3_workaround(installer)
|
||||
end
|
||||
|
||||
def enable_whole_module_optimization_for_crypto_swift(installer)
|
||||
|
@ -135,3 +138,12 @@ def enable_fts5_support(installer)
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Workaround for Xcode 14.3:
|
||||
# Sourced from https://github.com/flutter/flutter/issues/123852#issuecomment-1493232105
|
||||
def xcode_14_3_workaround(installer)
|
||||
system('sed -i \'\' \'44s/readlink/readlink -f/\' \'Pods/Target Support Files/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit-SessionMessagingKitTests/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit-SessionMessagingKitTests-frameworks.sh\'')
|
||||
system('sed -i \'\' \'44s/readlink/readlink -f/\' \'Pods/Target Support Files/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit-SessionUtilitiesKitTests/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit-SessionUtilitiesKitTests-frameworks.sh\'')
|
||||
system('sed -i \'\' \'44s/readlink/readlink -f/\' \'Pods/Target Support Files/Pods-GlobalDependencies-Session/Pods-GlobalDependencies-Session-frameworks.sh\'')
|
||||
system('sed -i \'\' \'44s/readlink/readlink -f/\' \'Pods/Target Support Files/Pods-GlobalDependencies-Session-SessionTests/Pods-GlobalDependencies-Session-SessionTests-frameworks.sh\'')
|
||||
end
|
||||
|
|
|
@ -27,7 +27,7 @@ PODS:
|
|||
- DifferenceKit/Core (1.3.0)
|
||||
- DifferenceKit/UIKitExtension (1.3.0):
|
||||
- DifferenceKit/Core
|
||||
- GRDB.swift/SQLCipher (6.10.1):
|
||||
- GRDB.swift/SQLCipher (6.13.0):
|
||||
- SQLCipher (>= 3.4.2)
|
||||
- libwebp (1.2.1):
|
||||
- libwebp/demux (= 1.2.1)
|
||||
|
@ -222,7 +222,7 @@ SPEC CHECKSUMS:
|
|||
CryptoSwift: a532e74ed010f8c95f611d00b8bbae42e9fe7c17
|
||||
Curve25519Kit: e63f9859ede02438ae3defc5e1a87e09d1ec7ee6
|
||||
DifferenceKit: ab185c4d7f9cef8af3fcf593e5b387fb81e999ca
|
||||
GRDB.swift: 1cc67278f1a9878d6eb1b849485518112b79cab7
|
||||
GRDB.swift: fe420b1af49ec519c7e96e07887ee44f5dfa2b78
|
||||
libwebp: 98a37e597e40bfdb4c911fc98f2c53d0b12d05fc
|
||||
Nimble: 5316ef81a170ce87baf72dd961f22f89a602ff84
|
||||
NVActivityIndicatorView: 1f6c5687f1171810aa27a3296814dc2d7dec3667
|
||||
|
@ -242,6 +242,6 @@ SPEC CHECKSUMS:
|
|||
YYImage: f1ddd15ac032a58b78bbed1e012b50302d318331
|
||||
ZXingObjC: fdbb269f25dd2032da343e06f10224d62f537bdb
|
||||
|
||||
PODFILE CHECKSUM: 97324ae5888b01db2f2adc4dcc239e2e7d6867f7
|
||||
PODFILE CHECKSUM: e9443a8235dbff1fc342aa9bf08bbc66923adf68
|
||||
|
||||
COCOAPODS: 1.11.3
|
||||
|
|
|
@ -109,7 +109,13 @@
|
|||
7B1D74AA27BCC16E0030B423 /* NSENotificationPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B1D74A927BCC16E0030B423 /* NSENotificationPresenter.swift */; };
|
||||
7B1D74AC27BDE7510030B423 /* Promise+Timeout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B1D74AB27BDE7510030B423 /* Promise+Timeout.swift */; };
|
||||
7B1D74B027C365960030B423 /* Timer+MainThread.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B1D74AF27C365960030B423 /* Timer+MainThread.swift */; };
|
||||
7B2561C22978B307005C086C /* MediaInfoVC+MediaInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B2561C12978B307005C086C /* MediaInfoVC+MediaInfoView.swift */; };
|
||||
7B2561C429874851005C086C /* SessionCarouselView+Info.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B2561C329874851005C086C /* SessionCarouselView+Info.swift */; };
|
||||
7B2E985829AC227C001792D7 /* UIContextualAction+Theming.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B2E985729AC227C001792D7 /* UIContextualAction+Theming.swift */; };
|
||||
7B3A392E2977791E002FE4AC /* MediaInfoVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B3A392D2977791E002FE4AC /* MediaInfoVC.swift */; };
|
||||
7B3A3930297A3919002FE4AC /* MediaInfoVC+MediaPreviewView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B3A392F297A3919002FE4AC /* MediaInfoVC+MediaPreviewView.swift */; };
|
||||
7B3A39322980D02B002FE4AC /* SessionCarouselView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B3A39312980D02B002FE4AC /* SessionCarouselView.swift */; };
|
||||
7B3A3934298882D6002FE4AC /* SessionCarouselViewDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B3A3933298882D6002FE4AC /* SessionCarouselViewDelegate.swift */; };
|
||||
7B46AAAF28766DF4001AF2DC /* AllMediaViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B46AAAE28766DF4001AF2DC /* AllMediaViewController.swift */; };
|
||||
7B4C75CB26B37E0F0000AC89 /* UnsendRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B4C75CA26B37E0F0000AC89 /* UnsendRequest.swift */; };
|
||||
7B4C75CD26BB92060000AC89 /* DeletedMessageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B4C75CC26BB92060000AC89 /* DeletedMessageView.swift */; };
|
||||
|
@ -182,9 +188,6 @@
|
|||
B80A579F23DFF1F300876683 /* NewClosedGroupVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = B80A579E23DFF1F300876683 /* NewClosedGroupVC.swift */; };
|
||||
B817AD9A26436593009DF825 /* SimplifiedConversationCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B817AD9926436593009DF825 /* SimplifiedConversationCell.swift */; };
|
||||
B817AD9C26436F73009DF825 /* ThreadPickerVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = B817AD9B26436F73009DF825 /* ThreadPickerVC.swift */; };
|
||||
B81D25C426157F40004D1FE1 /* storage-seed-3.crt in Resources */ = {isa = PBXBuildFile; fileRef = B81D25B926157F20004D1FE1 /* storage-seed-3.crt */; };
|
||||
B81D25C526157F40004D1FE1 /* storage-seed-1.crt in Resources */ = {isa = PBXBuildFile; fileRef = B81D25B726157F20004D1FE1 /* storage-seed-1.crt */; };
|
||||
B81D25C626157F40004D1FE1 /* public-loki-foundation.crt in Resources */ = {isa = PBXBuildFile; fileRef = B81D25B826157F20004D1FE1 /* public-loki-foundation.crt */; };
|
||||
B82149C125D605C6009C0F2A /* InfoBanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = B82149C025D605C6009C0F2A /* InfoBanner.swift */; };
|
||||
B8269D2925C7A4B400488AB4 /* InputView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8269D2825C7A4B400488AB4 /* InputView.swift */; };
|
||||
B8269D3325C7A8C600488AB4 /* InputViewButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8269D3225C7A8C600488AB4 /* InputViewButton.swift */; };
|
||||
|
@ -428,9 +431,6 @@
|
|||
C38EF407255B6DF7007E1867 /* Toast.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38EF3E9255B6DF6007E1867 /* Toast.swift */; };
|
||||
C38EF40B255B6DF7007E1867 /* TappableStackView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38EF3ED255B6DF6007E1867 /* TappableStackView.swift */; };
|
||||
C38EF48A255B7E3F007E1867 /* SessionUIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C331FF1B2558F9D300070591 /* SessionUIKit.framework */; };
|
||||
C3A01E05261D24C400290BEB /* public-loki-foundation.der in Resources */ = {isa = PBXBuildFile; fileRef = C3A01E02261D24C400290BEB /* public-loki-foundation.der */; };
|
||||
C3A01E06261D24C400290BEB /* storage-seed-1.der in Resources */ = {isa = PBXBuildFile; fileRef = C3A01E03261D24C400290BEB /* storage-seed-1.der */; };
|
||||
C3A01E07261D24C400290BEB /* storage-seed-3.der in Resources */ = {isa = PBXBuildFile; fileRef = C3A01E04261D24C400290BEB /* storage-seed-3.der */; };
|
||||
C3A3A171256E1D25004D228D /* SSKReachabilityManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3A3A170256E1D25004D228D /* SSKReachabilityManager.swift */; };
|
||||
C3A71D0B2558989C0043A11F /* MessageWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3A71D0A2558989C0043A11F /* MessageWrapper.swift */; };
|
||||
C3A71D1E25589AC30043A11F /* WebSocketProto.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3A71D1C25589AC30043A11F /* WebSocketProto.swift */; };
|
||||
|
@ -832,6 +832,12 @@
|
|||
FDD250702837199200198BDA /* GarbageCollectionJob.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDD2506F2837199200198BDA /* GarbageCollectionJob.swift */; };
|
||||
FDD250722837234B00198BDA /* MediaGalleryNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDD250712837234B00198BDA /* MediaGalleryNavigationController.swift */; };
|
||||
FDDF074A29DAB36900E5E8B5 /* JobRunnerSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDDF074929DAB36900E5E8B5 /* JobRunnerSpec.swift */; };
|
||||
FDDCBDA829E776BF00303C38 /* seed2-2023-2y.crt in Resources */ = {isa = PBXBuildFile; fileRef = FDDCBDA229E776BF00303C38 /* seed2-2023-2y.crt */; };
|
||||
FDDCBDA929E776BF00303C38 /* seed1-2023-2y.crt in Resources */ = {isa = PBXBuildFile; fileRef = FDDCBDA329E776BF00303C38 /* seed1-2023-2y.crt */; };
|
||||
FDDCBDAA29E776BF00303C38 /* seed1-2023-2y.der in Resources */ = {isa = PBXBuildFile; fileRef = FDDCBDA429E776BF00303C38 /* seed1-2023-2y.der */; };
|
||||
FDDCBDAB29E776BF00303C38 /* seed2-2023-2y.der in Resources */ = {isa = PBXBuildFile; fileRef = FDDCBDA529E776BF00303C38 /* seed2-2023-2y.der */; };
|
||||
FDDCBDAC29E776BF00303C38 /* seed3-2023-2y.crt in Resources */ = {isa = PBXBuildFile; fileRef = FDDCBDA629E776BF00303C38 /* seed3-2023-2y.crt */; };
|
||||
FDDCBDAD29E776BF00303C38 /* seed3-2023-2y.der in Resources */ = {isa = PBXBuildFile; fileRef = FDDCBDA729E776BF00303C38 /* seed3-2023-2y.der */; };
|
||||
FDE77F6B280FEB28002CFC5D /* ControlMessageProcessRecord.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE77F6A280FEB28002CFC5D /* ControlMessageProcessRecord.swift */; };
|
||||
FDED2E3C282E1B5D00B2CD2A /* UICollectionView+ReusableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDED2E3B282E1B5D00B2CD2A /* UICollectionView+ReusableView.swift */; };
|
||||
FDF0B73C27FFD3D6004C14C5 /* LinkPreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF0B73B27FFD3D6004C14C5 /* LinkPreview.swift */; };
|
||||
|
@ -1184,8 +1190,14 @@
|
|||
7B1D74A927BCC16E0030B423 /* NSENotificationPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSENotificationPresenter.swift; sourceTree = "<group>"; };
|
||||
7B1D74AB27BDE7510030B423 /* Promise+Timeout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Promise+Timeout.swift"; sourceTree = "<group>"; };
|
||||
7B1D74AF27C365960030B423 /* Timer+MainThread.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Timer+MainThread.swift"; sourceTree = "<group>"; };
|
||||
7B2561C12978B307005C086C /* MediaInfoVC+MediaInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MediaInfoVC+MediaInfoView.swift"; sourceTree = "<group>"; };
|
||||
7B2561C329874851005C086C /* SessionCarouselView+Info.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SessionCarouselView+Info.swift"; sourceTree = "<group>"; };
|
||||
7B2DB2AD26F1B0FF0035B509 /* si */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = si; path = si.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
7B2E985729AC227C001792D7 /* UIContextualAction+Theming.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIContextualAction+Theming.swift"; sourceTree = "<group>"; };
|
||||
7B3A392D2977791E002FE4AC /* MediaInfoVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaInfoVC.swift; sourceTree = "<group>"; };
|
||||
7B3A392F297A3919002FE4AC /* MediaInfoVC+MediaPreviewView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MediaInfoVC+MediaPreviewView.swift"; sourceTree = "<group>"; };
|
||||
7B3A39312980D02B002FE4AC /* SessionCarouselView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionCarouselView.swift; sourceTree = "<group>"; };
|
||||
7B3A3933298882D6002FE4AC /* SessionCarouselViewDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionCarouselViewDelegate.swift; sourceTree = "<group>"; };
|
||||
7B46AAAE28766DF4001AF2DC /* AllMediaViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AllMediaViewController.swift; sourceTree = "<group>"; };
|
||||
7B4C75CA26B37E0F0000AC89 /* UnsendRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnsendRequest.swift; sourceTree = "<group>"; };
|
||||
7B4C75CC26BB92060000AC89 /* DeletedMessageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeletedMessageView.swift; sourceTree = "<group>"; };
|
||||
|
@ -1271,9 +1283,6 @@
|
|||
B80A579E23DFF1F300876683 /* NewClosedGroupVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewClosedGroupVC.swift; sourceTree = "<group>"; };
|
||||
B817AD9926436593009DF825 /* SimplifiedConversationCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimplifiedConversationCell.swift; sourceTree = "<group>"; };
|
||||
B817AD9B26436F73009DF825 /* ThreadPickerVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadPickerVC.swift; sourceTree = "<group>"; };
|
||||
B81D25B726157F20004D1FE1 /* storage-seed-1.crt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "storage-seed-1.crt"; sourceTree = "<group>"; };
|
||||
B81D25B826157F20004D1FE1 /* public-loki-foundation.crt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "public-loki-foundation.crt"; sourceTree = "<group>"; };
|
||||
B81D25B926157F20004D1FE1 /* storage-seed-3.crt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "storage-seed-3.crt"; sourceTree = "<group>"; };
|
||||
B82149C025D605C6009C0F2A /* InfoBanner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InfoBanner.swift; sourceTree = "<group>"; };
|
||||
B8269D2825C7A4B400488AB4 /* InputView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InputView.swift; sourceTree = "<group>"; };
|
||||
B8269D3225C7A8C600488AB4 /* InputViewButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InputViewButton.swift; sourceTree = "<group>"; };
|
||||
|
@ -1547,9 +1556,6 @@
|
|||
C396469D2509D3F400B0B9F5 /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
C396469E2509D40400B0B9F5 /* vi-VN */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "vi-VN"; path = "vi-VN.lproj/Localizable.strings"; sourceTree = "<group>"; };
|
||||
C396469F2509D41100B0B9F5 /* id-ID */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "id-ID"; path = "id-ID.lproj/Localizable.strings"; sourceTree = "<group>"; };
|
||||
C3A01E02261D24C400290BEB /* public-loki-foundation.der */ = {isa = PBXFileReference; lastKnownFileType = file; path = "public-loki-foundation.der"; sourceTree = "<group>"; };
|
||||
C3A01E03261D24C400290BEB /* storage-seed-1.der */ = {isa = PBXFileReference; lastKnownFileType = file; path = "storage-seed-1.der"; sourceTree = "<group>"; };
|
||||
C3A01E04261D24C400290BEB /* storage-seed-3.der */ = {isa = PBXFileReference; lastKnownFileType = file; path = "storage-seed-3.der"; sourceTree = "<group>"; };
|
||||
C3A3A170256E1D25004D228D /* SSKReachabilityManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SSKReachabilityManager.swift; sourceTree = "<group>"; };
|
||||
C3A71D0A2558989C0043A11F /* MessageWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageWrapper.swift; sourceTree = "<group>"; };
|
||||
C3A71D1C25589AC30043A11F /* WebSocketProto.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WebSocketProto.swift; sourceTree = "<group>"; };
|
||||
|
@ -1914,6 +1920,12 @@
|
|||
FDD2506F2837199200198BDA /* GarbageCollectionJob.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GarbageCollectionJob.swift; sourceTree = "<group>"; };
|
||||
FDD250712837234B00198BDA /* MediaGalleryNavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaGalleryNavigationController.swift; sourceTree = "<group>"; };
|
||||
FDDF074929DAB36900E5E8B5 /* JobRunnerSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JobRunnerSpec.swift; sourceTree = "<group>"; };
|
||||
FDDCBDA229E776BF00303C38 /* seed2-2023-2y.crt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "seed2-2023-2y.crt"; sourceTree = "<group>"; };
|
||||
FDDCBDA329E776BF00303C38 /* seed1-2023-2y.crt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "seed1-2023-2y.crt"; sourceTree = "<group>"; };
|
||||
FDDCBDA429E776BF00303C38 /* seed1-2023-2y.der */ = {isa = PBXFileReference; lastKnownFileType = file; path = "seed1-2023-2y.der"; sourceTree = "<group>"; };
|
||||
FDDCBDA529E776BF00303C38 /* seed2-2023-2y.der */ = {isa = PBXFileReference; lastKnownFileType = file; path = "seed2-2023-2y.der"; sourceTree = "<group>"; };
|
||||
FDDCBDA629E776BF00303C38 /* seed3-2023-2y.crt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "seed3-2023-2y.crt"; sourceTree = "<group>"; };
|
||||
FDDCBDA729E776BF00303C38 /* seed3-2023-2y.der */ = {isa = PBXFileReference; lastKnownFileType = file; path = "seed3-2023-2y.der"; sourceTree = "<group>"; };
|
||||
FDE7214F287E50D50093DF33 /* ProtoWrappers.py */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.python; path = ProtoWrappers.py; sourceTree = "<group>"; };
|
||||
FDE72150287E50D50093DF33 /* LintLocalizableStrings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LintLocalizableStrings.swift; sourceTree = "<group>"; };
|
||||
FDE77F68280F9EDA002CFC5D /* JobRunnerError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JobRunnerError.swift; sourceTree = "<group>"; };
|
||||
|
@ -2374,12 +2386,12 @@
|
|||
B81D260326158DF5004D1FE1 /* Certificates */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
B81D25B826157F20004D1FE1 /* public-loki-foundation.crt */,
|
||||
C3A01E02261D24C400290BEB /* public-loki-foundation.der */,
|
||||
B81D25B726157F20004D1FE1 /* storage-seed-1.crt */,
|
||||
C3A01E03261D24C400290BEB /* storage-seed-1.der */,
|
||||
B81D25B926157F20004D1FE1 /* storage-seed-3.crt */,
|
||||
C3A01E04261D24C400290BEB /* storage-seed-3.der */,
|
||||
FDDCBDA329E776BF00303C38 /* seed1-2023-2y.crt */,
|
||||
FDDCBDA429E776BF00303C38 /* seed1-2023-2y.der */,
|
||||
FDDCBDA229E776BF00303C38 /* seed2-2023-2y.crt */,
|
||||
FDDCBDA529E776BF00303C38 /* seed2-2023-2y.der */,
|
||||
FDDCBDA629E776BF00303C38 /* seed3-2023-2y.crt */,
|
||||
FDDCBDA729E776BF00303C38 /* seed3-2023-2y.der */,
|
||||
);
|
||||
path = Certificates;
|
||||
sourceTree = "<group>";
|
||||
|
@ -2596,6 +2608,9 @@
|
|||
FD52090828B59411006098F6 /* ScreenLockUI.swift */,
|
||||
FD37EA0828AA2D27003AE748 /* SessionTableViewModel.swift */,
|
||||
FD37EA0628AA2CCA003AE748 /* SessionTableViewController.swift */,
|
||||
7B3A39312980D02B002FE4AC /* SessionCarouselView.swift */,
|
||||
7B2561C329874851005C086C /* SessionCarouselView+Info.swift */,
|
||||
7B3A3933298882D6002FE4AC /* SessionCarouselViewDelegate.swift */,
|
||||
);
|
||||
path = Shared;
|
||||
sourceTree = "<group>";
|
||||
|
@ -2999,6 +3014,9 @@
|
|||
4C1885D1218F8E1C00B67051 /* PhotoGridViewCell.swift */,
|
||||
4C4AE69F224AF21900D4AF6F /* SendMediaNavigationController.swift */,
|
||||
7B46AAAE28766DF4001AF2DC /* AllMediaViewController.swift */,
|
||||
7B3A392D2977791E002FE4AC /* MediaInfoVC.swift */,
|
||||
7B3A392F297A3919002FE4AC /* MediaInfoVC+MediaPreviewView.swift */,
|
||||
7B2561C12978B307005C086C /* MediaInfoVC+MediaInfoView.swift */,
|
||||
);
|
||||
path = "Media Viewing & Editing";
|
||||
sourceTree = "<group>";
|
||||
|
@ -4727,19 +4745,16 @@
|
|||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
B81D25C526157F40004D1FE1 /* storage-seed-1.crt in Resources */,
|
||||
B81D25C426157F40004D1FE1 /* storage-seed-3.crt in Resources */,
|
||||
B81D25C626157F40004D1FE1 /* public-loki-foundation.crt in Resources */,
|
||||
4C63CC00210A620B003AE45C /* SignalTSan.supp in Resources */,
|
||||
4C6F527C20FFE8400097DEEE /* SignalUBSan.supp in Resources */,
|
||||
34CF078A203E6B78005C4D61 /* end_call_tone_cept.caf in Resources */,
|
||||
C3CA3AA2255CDADA00F4C6D4 /* english.txt in Resources */,
|
||||
B6F509971AA53F760068F56A /* Localizable.strings in Resources */,
|
||||
C3A01E05261D24C400290BEB /* public-loki-foundation.der in Resources */,
|
||||
B66DBF4A19D5BBC8006EA940 /* Images.xcassets in Resources */,
|
||||
34CF0788203E6B78005C4D61 /* ringback_tone_ansi.caf in Resources */,
|
||||
7BFD1A972747689000FB91B9 /* Session-Turn-Server in Resources */,
|
||||
34C3C78F2040A4F70000134C /* sonarping.mp3 in Resources */,
|
||||
FDDCBDA929E776BF00303C38 /* seed1-2023-2y.crt in Resources */,
|
||||
34661FB820C1C0D60056EDD6 /* message_sent.aiff in Resources */,
|
||||
45CB2FA81CB7146C00E1B343 /* Launch Screen.storyboard in Resources */,
|
||||
34C3C78D20409F320000134C /* Opening.m4r in Resources */,
|
||||
|
@ -4751,12 +4766,12 @@
|
|||
45B74A742044AAB600CD42F8 /* aurora-quiet.aifc in Resources */,
|
||||
7B0EFDF4275490EA00FFAAE7 /* ringing.mp3 in Resources */,
|
||||
45B74A852044AAB600CD42F8 /* bamboo.aifc in Resources */,
|
||||
C3A01E06261D24C400290BEB /* storage-seed-1.der in Resources */,
|
||||
45B74A782044AAB600CD42F8 /* bamboo-quiet.aifc in Resources */,
|
||||
45B74A7B2044AAB600CD42F8 /* chord.aifc in Resources */,
|
||||
45B74A812044AAB600CD42F8 /* chord-quiet.aifc in Resources */,
|
||||
45B74A832044AAB600CD42F8 /* circles.aifc in Resources */,
|
||||
45B74A892044AAB600CD42F8 /* circles-quiet.aifc in Resources */,
|
||||
FDDCBDAA29E776BF00303C38 /* seed1-2023-2y.der in Resources */,
|
||||
C34C8F7423A7830B00D82669 /* SpaceMono-Bold.ttf in Resources */,
|
||||
4503F1BF20470A5B00CEE724 /* classic.aifc in Resources */,
|
||||
B8D07405265C683300F77E07 /* ElegantIcons.ttf in Resources */,
|
||||
|
@ -4769,8 +4784,11 @@
|
|||
45B74A7C2044AAB600CD42F8 /* hello-quiet.aifc in Resources */,
|
||||
7B50D64D28AC7CF80086CCEC /* silence.aiff in Resources */,
|
||||
45B74A792044AAB600CD42F8 /* input.aifc in Resources */,
|
||||
FDDCBDAB29E776BF00303C38 /* seed2-2023-2y.der in Resources */,
|
||||
C3CA3ABE255CDB0D00F4C6D4 /* portuguese.txt in Resources */,
|
||||
45B74A8C2044AAB600CD42F8 /* input-quiet.aifc in Resources */,
|
||||
FDDCBDAC29E776BF00303C38 /* seed3-2023-2y.crt in Resources */,
|
||||
FDDCBDA829E776BF00303C38 /* seed2-2023-2y.crt in Resources */,
|
||||
45B74A7A2044AAB600CD42F8 /* keys.aifc in Resources */,
|
||||
45B74A762044AAB600CD42F8 /* keys-quiet.aifc in Resources */,
|
||||
45B74A862044AAB600CD42F8 /* note.aifc in Resources */,
|
||||
|
@ -4780,7 +4798,7 @@
|
|||
45B74A822044AAB600CD42F8 /* pulse.aifc in Resources */,
|
||||
C3CA3AC8255CDB2900F4C6D4 /* spanish.txt in Resources */,
|
||||
B8FF8E6225C10DA5004D1F22 /* GeoLite2-Country-Blocks-IPv4 in Resources */,
|
||||
C3A01E07261D24C400290BEB /* storage-seed-3.der in Resources */,
|
||||
FDDCBDAD29E776BF00303C38 /* seed3-2023-2y.der in Resources */,
|
||||
45B74A802044AAB600CD42F8 /* pulse-quiet.aifc in Resources */,
|
||||
45B74A8B2044AAB600CD42F8 /* synth.aifc in Resources */,
|
||||
45B74A752044AAB600CD42F8 /* synth-quiet.aifc in Resources */,
|
||||
|
@ -5623,6 +5641,7 @@
|
|||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
FD52090928B59411006098F6 /* ScreenLockUI.swift in Sources */,
|
||||
7B2561C429874851005C086C /* SessionCarouselView+Info.swift in Sources */,
|
||||
FDF2220B2818F38D000A4995 /* SessionApp.swift in Sources */,
|
||||
FD7115EB28C5D78E00B47552 /* ThreadSettingsViewModel.swift in Sources */,
|
||||
B8041AA725C90927003C2166 /* TypingIndicatorCell.swift in Sources */,
|
||||
|
@ -5637,6 +5656,7 @@
|
|||
7BAF54D027ACCEEC003D12F8 /* EmptySearchResultCell.swift in Sources */,
|
||||
B8783E9E23EB948D00404FB8 /* UILabel+Interaction.swift in Sources */,
|
||||
FD37E9D928A230F2003AE748 /* TraitObservingWindow.swift in Sources */,
|
||||
7B3A3930297A3919002FE4AC /* MediaInfoVC+MediaPreviewView.swift in Sources */,
|
||||
B893063F2383961A005EAA8E /* ScanQRCodeWrapperVC.swift in Sources */,
|
||||
FD848B8F283EF2A8000E298B /* UIScrollView+Utilities.swift in Sources */,
|
||||
B879D449247E1BE300DB3608 /* PathVC.swift in Sources */,
|
||||
|
@ -5680,6 +5700,7 @@
|
|||
FD71164828E2CE8700B47552 /* SessionCell+AccessoryView.swift in Sources */,
|
||||
B80A579F23DFF1F300876683 /* NewClosedGroupVC.swift in Sources */,
|
||||
FD71163A28E2C53700B47552 /* SessionAvatarCell.swift in Sources */,
|
||||
7B3A392E2977791E002FE4AC /* MediaInfoVC.swift in Sources */,
|
||||
7BA68909272A27BE00EFC32F /* SessionCall.swift in Sources */,
|
||||
B835247925C38D880089A44F /* MessageCell.swift in Sources */,
|
||||
B86BD08623399CEF000F5AE3 /* SeedModal.swift in Sources */,
|
||||
|
@ -5739,6 +5760,7 @@
|
|||
FD71164228E2C85A00B47552 /* TransitionType.swift in Sources */,
|
||||
FD848B9828422F1A000E298B /* Date+Utilities.swift in Sources */,
|
||||
FD37E9DB28A244E9003AE748 /* ThemePreviewView.swift in Sources */,
|
||||
7B3A3934298882D6002FE4AC /* SessionCarouselViewDelegate.swift in Sources */,
|
||||
B85357C323A1BD1200AAF6CD /* SeedVC.swift in Sources */,
|
||||
45B5360E206DD8BB00D61655 /* UIResponder+OWS.swift in Sources */,
|
||||
7B9F71C928470667006DFE7B /* ReactionListSheet.swift in Sources */,
|
||||
|
@ -5787,6 +5809,7 @@
|
|||
FD39352C28F382920084DADA /* VersionFooterView.swift in Sources */,
|
||||
7B9F71D22852EEE2006DFE7B /* Emoji+SkinTones.swift in Sources */,
|
||||
7B7CB18E270D066F0079FF93 /* IncomingCallBanner.swift in Sources */,
|
||||
7B2561C22978B307005C086C /* MediaInfoVC+MediaInfoView.swift in Sources */,
|
||||
B8569AE325CBB19A00DBA3DB /* DocumentView.swift in Sources */,
|
||||
7BFD1A8A2745C4F000FB91B9 /* Permissions.swift in Sources */,
|
||||
B85357BF23A1AE0800AAF6CD /* SeedReminderView.swift in Sources */,
|
||||
|
@ -5808,6 +5831,7 @@
|
|||
FD71163828E2C50700B47552 /* SessionTableViewModel.swift in Sources */,
|
||||
FD71164A28E3EA5B00B47552 /* DismissType.swift in Sources */,
|
||||
C328251F25CA3A900062D0A7 /* QuoteView.swift in Sources */,
|
||||
7B3A39322980D02B002FE4AC /* SessionCarouselView.swift in Sources */,
|
||||
FD37E9CC28A1E578003AE748 /* AppearanceViewController.swift in Sources */,
|
||||
B8EB20F02640F7F000773E52 /* OpenGroupInvitationView.swift in Sources */,
|
||||
C328254025CA55880062D0A7 /* ContextMenuVC.swift in Sources */,
|
||||
|
@ -6092,7 +6116,7 @@
|
|||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 398;
|
||||
CURRENT_PROJECT_VERSION = 406;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
DEVELOPMENT_TEAM = SUQ8J2PCT7;
|
||||
FRAMEWORK_SEARCH_PATHS = "$(inherited)";
|
||||
|
@ -6117,7 +6141,7 @@
|
|||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.2.9;
|
||||
MARKETING_VERSION = 2.2.14;
|
||||
MTL_ENABLE_DEBUG_INFO = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.ShareExtension";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
|
@ -6165,7 +6189,7 @@
|
|||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 398;
|
||||
CURRENT_PROJECT_VERSION = 406;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEVELOPMENT_TEAM = SUQ8J2PCT7;
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
|
@ -6195,7 +6219,7 @@
|
|||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.2.9;
|
||||
MARKETING_VERSION = 2.2.14;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.ShareExtension";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
|
@ -6231,7 +6255,7 @@
|
|||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 398;
|
||||
CURRENT_PROJECT_VERSION = 406;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
DEVELOPMENT_TEAM = SUQ8J2PCT7;
|
||||
FRAMEWORK_SEARCH_PATHS = "$(inherited)";
|
||||
|
@ -6254,7 +6278,7 @@
|
|||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.2.9;
|
||||
MARKETING_VERSION = 2.2.14;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.NotificationServiceExtension";
|
||||
|
@ -6305,7 +6329,7 @@
|
|||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 398;
|
||||
CURRENT_PROJECT_VERSION = 406;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEVELOPMENT_TEAM = SUQ8J2PCT7;
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
|
@ -6333,7 +6357,7 @@
|
|||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.2.9;
|
||||
MARKETING_VERSION = 2.2.14;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.NotificationServiceExtension";
|
||||
|
@ -7233,7 +7257,7 @@
|
|||
CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements;
|
||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
CURRENT_PROJECT_VERSION = 398;
|
||||
CURRENT_PROJECT_VERSION = 406;
|
||||
DEVELOPMENT_TEAM = SUQ8J2PCT7;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
|
@ -7272,7 +7296,7 @@
|
|||
"$(SRCROOT)",
|
||||
);
|
||||
LLVM_LTO = NO;
|
||||
MARKETING_VERSION = 2.2.9;
|
||||
MARKETING_VERSION = 2.2.14;
|
||||
OTHER_LDFLAGS = "$(inherited)";
|
||||
OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\"";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger";
|
||||
|
@ -7305,7 +7329,7 @@
|
|||
CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements;
|
||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
CURRENT_PROJECT_VERSION = 398;
|
||||
CURRENT_PROJECT_VERSION = 406;
|
||||
DEVELOPMENT_TEAM = SUQ8J2PCT7;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
|
@ -7344,7 +7368,7 @@
|
|||
"$(SRCROOT)",
|
||||
);
|
||||
LLVM_LTO = NO;
|
||||
MARKETING_VERSION = 2.2.9;
|
||||
MARKETING_VERSION = 2.2.14;
|
||||
OTHER_LDFLAGS = "$(inherited)";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger";
|
||||
PRODUCT_NAME = Session;
|
||||
|
|
|
@ -356,6 +356,10 @@ public final class SessionCall: CurrentCallProtocol, WebRTCSessionDelegate {
|
|||
webRTCSession.attachLocalRenderer(renderer)
|
||||
}
|
||||
|
||||
func removeLocalVideoRenderer(_ renderer: RTCVideoRenderer) {
|
||||
webRTCSession.removeLocalRenderer(renderer)
|
||||
}
|
||||
|
||||
// MARK: - Delegate
|
||||
|
||||
public func webRTCIsConnected() {
|
||||
|
|
|
@ -8,6 +8,9 @@ import SessionMessagingKit
|
|||
import SessionUtilitiesKit
|
||||
|
||||
final class CallVC: UIViewController, VideoPreviewDelegate {
|
||||
static let floatingVideoViewWidth: CGFloat = UIDevice.current.isIPad ? 160 : 80
|
||||
static let floatingVideoViewHeight: CGFloat = UIDevice.current.isIPad ? 346: 173
|
||||
|
||||
let call: SessionCall
|
||||
var latestKnownAudioOutputDeviceName: String?
|
||||
var durationTimer: Timer?
|
||||
|
@ -21,27 +24,90 @@ final class CallVC: UIViewController, VideoPreviewDelegate {
|
|||
return result
|
||||
}()
|
||||
|
||||
enum FloatingViewVideoSource {
|
||||
case local
|
||||
case remote
|
||||
}
|
||||
|
||||
var floatingViewVideoSource: FloatingViewVideoSource = .local
|
||||
|
||||
// MARK: - UI Components
|
||||
|
||||
private lazy var localVideoView: LocalVideoView = {
|
||||
private lazy var floatingLocalVideoView: LocalVideoView = {
|
||||
let result = LocalVideoView()
|
||||
result.clipsToBounds = true
|
||||
result.alpha = 0
|
||||
result.themeBackgroundColor = .backgroundSecondary
|
||||
result.isHidden = !call.isVideoEnabled
|
||||
result.layer.cornerRadius = UIDevice.current.isIPad ? 20 : 10
|
||||
result.layer.masksToBounds = true
|
||||
result.set(.width, to: LocalVideoView.width)
|
||||
result.set(.height, to: LocalVideoView.height)
|
||||
result.makeViewDraggable()
|
||||
result.set(.width, to: Self.floatingVideoViewWidth)
|
||||
result.set(.height, to: Self.floatingVideoViewHeight)
|
||||
|
||||
return result
|
||||
}()
|
||||
|
||||
private lazy var remoteVideoView: RemoteVideoView = {
|
||||
private lazy var floatingRemoteVideoView: RemoteVideoView = {
|
||||
let result = RemoteVideoView()
|
||||
result.alpha = 0
|
||||
result.themeBackgroundColor = .backgroundSecondary
|
||||
result.set(.width, to: Self.floatingVideoViewWidth)
|
||||
result.set(.height, to: Self.floatingVideoViewHeight)
|
||||
|
||||
return result
|
||||
}()
|
||||
|
||||
private lazy var fullScreenLocalVideoView: LocalVideoView = {
|
||||
let result = LocalVideoView()
|
||||
result.alpha = 0
|
||||
result.themeBackgroundColor = .backgroundPrimary
|
||||
result.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(handleFullScreenVideoViewTapped)))
|
||||
|
||||
return result
|
||||
}()
|
||||
|
||||
private lazy var fullScreenRemoteVideoView: RemoteVideoView = {
|
||||
let result = RemoteVideoView()
|
||||
result.alpha = 0
|
||||
result.themeBackgroundColor = .backgroundPrimary
|
||||
result.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(handleRemoteVieioViewTapped)))
|
||||
result.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(handleFullScreenVideoViewTapped)))
|
||||
|
||||
return result
|
||||
}()
|
||||
|
||||
private lazy var floatingViewContainer: UIView = {
|
||||
let result = UIView()
|
||||
result.isHidden = true
|
||||
result.clipsToBounds = true
|
||||
result.layer.cornerRadius = UIDevice.current.isIPad ? 20 : 10
|
||||
result.layer.masksToBounds = true
|
||||
result.themeBackgroundColor = .backgroundSecondary
|
||||
result.makeViewDraggable()
|
||||
|
||||
let noVideoIcon: UIImageView = UIImageView(
|
||||
image: UIImage(systemName: "video.slash")?
|
||||
.withRenderingMode(.alwaysTemplate)
|
||||
)
|
||||
noVideoIcon.themeTintColor = .textPrimary
|
||||
noVideoIcon.set(.width, to: 34)
|
||||
noVideoIcon.set(.height, to: 28)
|
||||
result.addSubview(noVideoIcon)
|
||||
noVideoIcon.center(in: result)
|
||||
|
||||
result.addSubview(floatingLocalVideoView)
|
||||
floatingLocalVideoView.pin(to: result)
|
||||
|
||||
result.addSubview(floatingRemoteVideoView)
|
||||
floatingRemoteVideoView.pin(to: result)
|
||||
|
||||
let swappingVideoIcon: UIImageView = UIImageView(
|
||||
image: UIImage(systemName: "arrow.2.squarepath")?
|
||||
.withRenderingMode(.alwaysTemplate)
|
||||
)
|
||||
swappingVideoIcon.themeTintColor = .textPrimary
|
||||
swappingVideoIcon.set(.width, to: 16)
|
||||
swappingVideoIcon.set(.height, to: 12)
|
||||
result.addSubview(swappingVideoIcon)
|
||||
swappingVideoIcon.pin(.top, to: .top, of: result, withInset: Values.smallSpacing)
|
||||
swappingVideoIcon.pin(.trailing, to: .trailing, of: result, withInset: -Values.smallSpacing)
|
||||
|
||||
result.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(switchVideo)))
|
||||
|
||||
return result
|
||||
}()
|
||||
|
@ -265,7 +331,8 @@ final class CallVC: UIViewController, VideoPreviewDelegate {
|
|||
self.call.remoteVideoStateDidChange = { isEnabled in
|
||||
DispatchQueue.main.async {
|
||||
UIView.animate(withDuration: 0.25) {
|
||||
self.remoteVideoView.alpha = isEnabled ? 1 : 0
|
||||
let remoteVideoView: RemoteVideoView = self.floatingViewVideoSource == .remote ? self.floatingRemoteVideoView : self.fullScreenRemoteVideoView
|
||||
remoteVideoView.alpha = isEnabled ? 1 : 0
|
||||
}
|
||||
|
||||
if self.callInfoLabel.alpha < 0.5 {
|
||||
|
@ -346,7 +413,7 @@ final class CallVC: UIViewController, VideoPreviewDelegate {
|
|||
|
||||
if shouldRestartCamera { cameraManager.prepare() }
|
||||
|
||||
touch(call.videoCapturer)
|
||||
_ = call.videoCapturer // Force the lazy var to instantiate
|
||||
titleLabel.text = self.call.contactName
|
||||
AppEnvironment.shared.callManager.startCall(call) { [weak self] error in
|
||||
DispatchQueue.main.async {
|
||||
|
@ -375,13 +442,16 @@ final class CallVC: UIViewController, VideoPreviewDelegate {
|
|||
view.addSubview(profilePictureContainer)
|
||||
|
||||
// Remote video view
|
||||
call.attachRemoteVideoRenderer(remoteVideoView)
|
||||
view.addSubview(remoteVideoView)
|
||||
remoteVideoView.translatesAutoresizingMaskIntoConstraints = false
|
||||
remoteVideoView.pin(to: view)
|
||||
call.attachRemoteVideoRenderer(fullScreenRemoteVideoView)
|
||||
view.addSubview(fullScreenRemoteVideoView)
|
||||
fullScreenRemoteVideoView.translatesAutoresizingMaskIntoConstraints = false
|
||||
fullScreenRemoteVideoView.pin(to: view)
|
||||
|
||||
// Local video view
|
||||
call.attachLocalVideoRenderer(localVideoView)
|
||||
call.attachLocalVideoRenderer(floatingLocalVideoView)
|
||||
view.addSubview(fullScreenLocalVideoView)
|
||||
fullScreenLocalVideoView.translatesAutoresizingMaskIntoConstraints = false
|
||||
fullScreenLocalVideoView.pin(to: view)
|
||||
|
||||
// Fade view
|
||||
view.addSubview(fadeView)
|
||||
|
@ -398,7 +468,8 @@ final class CallVC: UIViewController, VideoPreviewDelegate {
|
|||
view.addSubview(titleLabel)
|
||||
titleLabel.translatesAutoresizingMaskIntoConstraints = false
|
||||
titleLabel.center(.vertical, in: minimizeButton)
|
||||
titleLabel.center(.horizontal, in: view)
|
||||
titleLabel.pin(.leading, to: .leading, of: view, withInset: Values.largeSpacing)
|
||||
titleLabel.pin(.trailing, to: .trailing, of: view, withInset: -Values.largeSpacing)
|
||||
|
||||
// Response Panel
|
||||
view.addSubview(responsePanel)
|
||||
|
@ -431,12 +502,12 @@ final class CallVC: UIViewController, VideoPreviewDelegate {
|
|||
callDurationLabel.center(in: callInfoLabelContainer)
|
||||
}
|
||||
|
||||
private func addLocalVideoView() {
|
||||
private func addFloatingVideoView() {
|
||||
let safeAreaInsets = UIApplication.shared.keyWindow?.safeAreaInsets
|
||||
CurrentAppContext().mainWindow?.addSubview(localVideoView)
|
||||
localVideoView.autoPinEdge(toSuperviewEdge: .right, withInset: Values.smallSpacing)
|
||||
CurrentAppContext().mainWindow?.addSubview(floatingViewContainer)
|
||||
floatingViewContainer.autoPinEdge(toSuperviewEdge: .right, withInset: Values.smallSpacing)
|
||||
let topMargin = (safeAreaInsets?.top ?? 0) + Values.veryLargeSpacing
|
||||
localVideoView.autoPinEdge(toSuperviewEdge: .top, withInset: topMargin)
|
||||
floatingViewContainer.autoPinEdge(toSuperviewEdge: .top, withInset: topMargin)
|
||||
}
|
||||
|
||||
override func viewDidAppear(_ animated: Bool) {
|
||||
|
@ -445,7 +516,8 @@ final class CallVC: UIViewController, VideoPreviewDelegate {
|
|||
if (call.isVideoEnabled && shouldRestartCamera) { cameraManager.start() }
|
||||
|
||||
shouldRestartCamera = true
|
||||
addLocalVideoView()
|
||||
addFloatingVideoView()
|
||||
let remoteVideoView: RemoteVideoView = self.floatingViewVideoSource == .remote ? self.floatingRemoteVideoView : self.fullScreenRemoteVideoView
|
||||
remoteVideoView.alpha = (call.isRemoteVideoEnabled ? 1 : 0)
|
||||
}
|
||||
|
||||
|
@ -454,7 +526,7 @@ final class CallVC: UIViewController, VideoPreviewDelegate {
|
|||
|
||||
if (call.isVideoEnabled && shouldRestartCamera) { cameraManager.stop() }
|
||||
|
||||
localVideoView.removeFromSuperview()
|
||||
floatingViewContainer.removeFromSuperview()
|
||||
}
|
||||
|
||||
// MARK: - Orientation
|
||||
|
@ -501,7 +573,8 @@ final class CallVC: UIViewController, VideoPreviewDelegate {
|
|||
self.callInfoLabel.text = "Call Ended"
|
||||
|
||||
UIView.animate(withDuration: 0.25) {
|
||||
self.remoteVideoView.alpha = 0
|
||||
let remoteVideoView: RemoteVideoView = self.floatingViewVideoSource == .remote ? self.floatingRemoteVideoView : self.fullScreenRemoteVideoView
|
||||
remoteVideoView.alpha = 0
|
||||
self.operationPanel.alpha = 1
|
||||
self.responsePanel.alpha = 1
|
||||
self.callInfoLabel.alpha = 1
|
||||
|
@ -559,7 +632,7 @@ final class CallVC: UIViewController, VideoPreviewDelegate {
|
|||
|
||||
@objc private func operateCamera() {
|
||||
if (call.isVideoEnabled) {
|
||||
localVideoView.isHidden = true
|
||||
floatingViewContainer.isHidden = true
|
||||
cameraManager.stop()
|
||||
videoButton.themeTintColor = .textPrimary
|
||||
videoButton.themeBackgroundColor = .backgroundSecondary
|
||||
|
@ -575,7 +648,9 @@ final class CallVC: UIViewController, VideoPreviewDelegate {
|
|||
}
|
||||
|
||||
func cameraDidConfirmTurningOn() {
|
||||
localVideoView.isHidden = false
|
||||
floatingViewContainer.isHidden = false
|
||||
let localVideoView: LocalVideoView = self.floatingViewVideoSource == .local ? self.floatingLocalVideoView : self.fullScreenLocalVideoView
|
||||
localVideoView.alpha = 1
|
||||
cameraManager.prepare()
|
||||
cameraManager.start()
|
||||
videoButton.themeTintColor = .backgroundSecondary
|
||||
|
@ -584,6 +659,34 @@ final class CallVC: UIViewController, VideoPreviewDelegate {
|
|||
call.isVideoEnabled = true
|
||||
}
|
||||
|
||||
@objc private func switchVideo() {
|
||||
if self.floatingViewVideoSource == .remote {
|
||||
call.removeRemoteVideoRenderer(self.floatingRemoteVideoView)
|
||||
call.removeLocalVideoRenderer(self.fullScreenLocalVideoView)
|
||||
|
||||
self.floatingRemoteVideoView.alpha = 0
|
||||
self.floatingLocalVideoView.alpha = call.isVideoEnabled ? 1 : 0
|
||||
self.fullScreenRemoteVideoView.alpha = call.isRemoteVideoEnabled ? 1 : 0
|
||||
self.fullScreenLocalVideoView.alpha = 0
|
||||
|
||||
self.floatingViewVideoSource = .local
|
||||
call.attachRemoteVideoRenderer(self.fullScreenRemoteVideoView)
|
||||
call.attachLocalVideoRenderer(self.floatingLocalVideoView)
|
||||
} else {
|
||||
call.removeRemoteVideoRenderer(self.fullScreenRemoteVideoView)
|
||||
call.removeLocalVideoRenderer(self.floatingLocalVideoView)
|
||||
|
||||
self.floatingRemoteVideoView.alpha = call.isRemoteVideoEnabled ? 1 : 0
|
||||
self.floatingLocalVideoView.alpha = 0
|
||||
self.fullScreenRemoteVideoView.alpha = 0
|
||||
self.fullScreenLocalVideoView.alpha = call.isVideoEnabled ? 1 : 0
|
||||
|
||||
self.floatingViewVideoSource = .remote
|
||||
call.attachRemoteVideoRenderer(self.floatingRemoteVideoView)
|
||||
call.attachLocalVideoRenderer(self.fullScreenLocalVideoView)
|
||||
}
|
||||
}
|
||||
|
||||
@objc private func switchCamera() {
|
||||
cameraManager.switchCamera()
|
||||
}
|
||||
|
@ -645,7 +748,7 @@ final class CallVC: UIViewController, VideoPreviewDelegate {
|
|||
}
|
||||
}
|
||||
|
||||
@objc private func handleRemoteVieioViewTapped(gesture: UITapGestureRecognizer) {
|
||||
@objc private func handleFullScreenVideoViewTapped(gesture: UITapGestureRecognizer) {
|
||||
let isHidden = callDurationLabel.alpha < 0.5
|
||||
|
||||
UIView.animate(withDuration: 0.5) {
|
||||
|
|
|
@ -87,10 +87,7 @@ class RemoteVideoView: TargetView {
|
|||
// MARK: LocalVideoView
|
||||
|
||||
class LocalVideoView: TargetView {
|
||||
|
||||
static let width: CGFloat = UIDevice.current.isIPad ? 160 : 80
|
||||
static let height: CGFloat = UIDevice.current.isIPad ? 346: 173
|
||||
|
||||
|
||||
override func renderFrame(_ frame: RTCVideoFrame?) {
|
||||
super.renderFrame(frame)
|
||||
DispatchMainThreadSafe {
|
||||
|
|
|
@ -485,7 +485,7 @@ final class EditClosedGroupVC: BaseVC, UITableViewDataSource, UITableViewDelegat
|
|||
targetView: self.view,
|
||||
info: ConfirmationModal.Info(
|
||||
title: title,
|
||||
explanation: message,
|
||||
body: .text(message),
|
||||
cancelTitle: "BUTTON_OK".localized(),
|
||||
cancelStyle: .alert_text
|
||||
)
|
||||
|
|
|
@ -305,7 +305,7 @@ final class NewClosedGroupVC: BaseVC, UITableViewDataSource, UITableViewDelegate
|
|||
let modal: ConfirmationModal = ConfirmationModal(
|
||||
info: ConfirmationModal.Info(
|
||||
title: title,
|
||||
explanation: message,
|
||||
body: .text(message),
|
||||
cancelTitle: "BUTTON_OK".localized(),
|
||||
cancelStyle: .alert_text
|
||||
|
||||
|
@ -350,7 +350,7 @@ final class NewClosedGroupVC: BaseVC, UITableViewDataSource, UITableViewDelegate
|
|||
targetView: self?.view,
|
||||
info: ConfirmationModal.Info(
|
||||
title: "GROUP_CREATION_ERROR_TITLE".localized(),
|
||||
explanation: "GROUP_CREATION_ERROR_MESSAGE".localized(),
|
||||
body: .text("GROUP_CREATION_ERROR_MESSAGE".localized()),
|
||||
cancelTitle: "BUTTON_OK".localized(),
|
||||
cancelStyle: .alert_text
|
||||
)
|
||||
|
|
|
@ -35,6 +35,14 @@ extension ContextMenuVC {
|
|||
|
||||
// MARK: - Actions
|
||||
|
||||
static func info(_ cellViewModel: MessageViewModel, _ delegate: ContextMenuActionDelegate?) -> Action {
|
||||
return Action(
|
||||
icon: UIImage(named: "ic_info"),
|
||||
title: "context_menu_info".localized(),
|
||||
accessibilityLabel: "Message info"
|
||||
) { delegate?.info(cellViewModel) }
|
||||
}
|
||||
|
||||
static func retry(_ cellViewModel: MessageViewModel, _ delegate: ContextMenuActionDelegate?) -> Action {
|
||||
return Action(
|
||||
icon: UIImage(systemName: "arrow.triangle.2.circlepath"),
|
||||
|
@ -207,6 +215,8 @@ extension ContextMenuVC {
|
|||
return !currentThreadIsMessageRequest
|
||||
}()
|
||||
|
||||
let shouldShowInfo: Bool = (cellViewModel.attachments?.isEmpty == false)
|
||||
|
||||
let generatedActions: [Action] = [
|
||||
(canRetry ? Action.retry(cellViewModel, delegate) : nil),
|
||||
(canReply ? Action.reply(cellViewModel, delegate) : nil),
|
||||
|
@ -216,6 +226,7 @@ extension ContextMenuVC {
|
|||
(canDelete ? Action.delete(cellViewModel, delegate) : nil),
|
||||
(canBan ? Action.ban(cellViewModel, delegate) : nil),
|
||||
(canBan ? Action.banAndDeleteAllMessages(cellViewModel, delegate) : nil),
|
||||
(shouldShowInfo ? Action.info(cellViewModel, delegate) : nil),
|
||||
]
|
||||
.appending(contentsOf: (shouldShowEmojiActions ? recentEmojis : []).map { Action.react(cellViewModel, $0, delegate) })
|
||||
.appending(Action.emojiPlusButton(cellViewModel, delegate))
|
||||
|
@ -230,6 +241,7 @@ extension ContextMenuVC {
|
|||
// MARK: - Delegate
|
||||
|
||||
protocol ContextMenuActionDelegate {
|
||||
func info(_ cellViewModel: MessageViewModel)
|
||||
func retry(_ cellViewModel: MessageViewModel)
|
||||
func reply(_ cellViewModel: MessageViewModel)
|
||||
func copy(_ cellViewModel: MessageViewModel)
|
||||
|
|
|
@ -164,7 +164,9 @@ final class ContextMenuVC: UIViewController {
|
|||
let menuStackView = UIStackView(
|
||||
arrangedSubviews: actions
|
||||
.filter { !$0.isEmojiAction && !$0.isEmojiPlus && !$0.isDismissAction }
|
||||
.map { action -> ActionView in ActionView(for: action, dismiss: snDismiss) }
|
||||
.map { action -> ActionView in
|
||||
ActionView(for: action, dismiss: snDismiss)
|
||||
}
|
||||
)
|
||||
menuStackView.axis = .vertical
|
||||
menuBackgroundView.addSubview(menuStackView)
|
||||
|
|
|
@ -68,7 +68,7 @@ extension ConversationVC:
|
|||
let confirmationModal: ConfirmationModal = ConfirmationModal(
|
||||
info: ConfirmationModal.Info(
|
||||
title: "modal_call_permission_request_title".localized(),
|
||||
explanation: "modal_call_permission_request_explanation".localized(),
|
||||
body: .text("modal_call_permission_request_explanation".localized()),
|
||||
confirmTitle: "vc_settings_title".localized(),
|
||||
confirmAccessibilityLabel: "Settings",
|
||||
cancelAccessibilityLabel: "Cancel",
|
||||
|
@ -132,11 +132,13 @@ extension ConversationVC:
|
|||
format: "modal_blocked_title".localized(),
|
||||
self.viewModel.threadData.displayName
|
||||
),
|
||||
attributedExplanation: NSAttributedString(string: message)
|
||||
.adding(
|
||||
attributes: [ .font: UIFont.boldSystemFont(ofSize: Values.smallFontSize) ],
|
||||
range: (message as NSString).range(of: self.viewModel.threadData.displayName)
|
||||
),
|
||||
body: .attributedText(
|
||||
NSAttributedString(string: message)
|
||||
.adding(
|
||||
attributes: [ .font: UIFont.boldSystemFont(ofSize: Values.smallFontSize) ],
|
||||
range: (message as NSString).range(of: self.viewModel.threadData.displayName)
|
||||
)
|
||||
),
|
||||
confirmTitle: "modal_blocked_button_title".localized(),
|
||||
confirmAccessibilityLabel: "Confirm block",
|
||||
cancelAccessibilityLabel: "Cancel block",
|
||||
|
@ -205,7 +207,7 @@ extension ConversationVC:
|
|||
let modal: ConfirmationModal = ConfirmationModal(
|
||||
info: ConfirmationModal.Info(
|
||||
title: "GIPHY_PERMISSION_TITLE".localized(),
|
||||
explanation: "GIPHY_PERMISSION_MESSAGE".localized(),
|
||||
body: .text("GIPHY_PERMISSION_MESSAGE".localized()),
|
||||
confirmTitle: "continue_2".localized()
|
||||
) { [weak self] _ in
|
||||
Storage.shared.writeAsync(
|
||||
|
@ -295,7 +297,7 @@ extension ConversationVC:
|
|||
targetView: self?.view,
|
||||
info: ConfirmationModal.Info(
|
||||
title: "Session",
|
||||
explanation: "An error occurred.",
|
||||
body: .text("An error occurred."),
|
||||
cancelTitle: "BUTTON_OK".localized(),
|
||||
cancelStyle: .alert_text
|
||||
)
|
||||
|
@ -312,7 +314,7 @@ extension ConversationVC:
|
|||
targetView: self?.view,
|
||||
info: ConfirmationModal.Info(
|
||||
title: "ATTACHMENT_PICKER_DOCUMENTS_PICKED_DIRECTORY_FAILED_ALERT_TITLE".localized(),
|
||||
explanation: "ATTACHMENT_PICKER_DOCUMENTS_PICKED_DIRECTORY_FAILED_ALERT_BODY".localized(),
|
||||
body: .text("ATTACHMENT_PICKER_DOCUMENTS_PICKED_DIRECTORY_FAILED_ALERT_BODY".localized()),
|
||||
cancelTitle: "BUTTON_OK".localized(),
|
||||
cancelStyle: .alert_text
|
||||
)
|
||||
|
@ -410,7 +412,7 @@ extension ConversationVC:
|
|||
let modal: ConfirmationModal = ConfirmationModal(
|
||||
info: ConfirmationModal.Info(
|
||||
title: "modal_send_seed_title".localized(),
|
||||
explanation: "modal_send_seed_explanation".localized(),
|
||||
body: .text("modal_send_seed_explanation".localized()),
|
||||
confirmTitle: "modal_send_seed_send_button_title".localized(),
|
||||
confirmStyle: .danger,
|
||||
cancelStyle: .alert_text,
|
||||
|
@ -540,7 +542,7 @@ extension ConversationVC:
|
|||
let modal: ConfirmationModal = ConfirmationModal(
|
||||
info: ConfirmationModal.Info(
|
||||
title: "modal_send_seed_title".localized(),
|
||||
explanation: "modal_send_seed_explanation".localized(),
|
||||
body: .text("modal_send_seed_explanation".localized()),
|
||||
confirmTitle: "modal_send_seed_send_button_title".localized(),
|
||||
confirmStyle: .danger,
|
||||
cancelStyle: .alert_text,
|
||||
|
@ -646,7 +648,7 @@ extension ConversationVC:
|
|||
let linkPreviewModal: ConfirmationModal = ConfirmationModal(
|
||||
info: ConfirmationModal.Info(
|
||||
title: "modal_link_previews_title".localized(),
|
||||
explanation: "modal_link_previews_explanation".localized(),
|
||||
body: .text("modal_link_previews_explanation".localized()),
|
||||
confirmTitle: "modal_link_previews_button_title".localized()
|
||||
) { [weak self] _ in
|
||||
Storage.shared.writeAsync { db in
|
||||
|
@ -661,6 +663,10 @@ extension ConversationVC:
|
|||
}
|
||||
|
||||
func inputTextViewDidChangeContent(_ inputTextView: InputTextView) {
|
||||
// Note: If there is a 'draft' message then we don't want it to trigger the typing indicator to
|
||||
// appear (as that is not expected/correct behaviour)
|
||||
guard !viewIsAppearing else { return }
|
||||
|
||||
let newText: String = (inputTextView.text ?? "")
|
||||
|
||||
if !newText.isEmpty {
|
||||
|
@ -886,11 +892,13 @@ extension ConversationVC:
|
|||
format: "modal_download_attachment_title".localized(),
|
||||
cellViewModel.authorName
|
||||
),
|
||||
attributedExplanation: NSAttributedString(string: message)
|
||||
.adding(
|
||||
attributes: [ .font: UIFont.boldSystemFont(ofSize: Values.smallFontSize) ],
|
||||
range: (message as NSString).range(of: cellViewModel.authorName)
|
||||
),
|
||||
body: .attributedText(
|
||||
NSAttributedString(string: message)
|
||||
.adding(
|
||||
attributes: [ .font: UIFont.boldSystemFont(ofSize: Values.smallFontSize) ],
|
||||
range: (message as NSString).range(of: cellViewModel.authorName)
|
||||
)
|
||||
),
|
||||
confirmTitle: "modal_download_button_title".localized(),
|
||||
confirmAccessibilityLabel: "Download media",
|
||||
cancelAccessibilityLabel: "Don't download media",
|
||||
|
@ -1537,11 +1545,13 @@ extension ConversationVC:
|
|||
let modal: ConfirmationModal = ConfirmationModal(
|
||||
info: ConfirmationModal.Info(
|
||||
title: "Join \(finalName)?",
|
||||
attributedExplanation: NSMutableAttributedString(string: message)
|
||||
.adding(
|
||||
attributes: [ .font: UIFont.boldSystemFont(ofSize: Values.smallFontSize) ],
|
||||
range: (message as NSString).range(of: finalName)
|
||||
),
|
||||
body: .attributedText(
|
||||
NSMutableAttributedString(string: message)
|
||||
.adding(
|
||||
attributes: [ .font: UIFont.boldSystemFont(ofSize: Values.smallFontSize) ],
|
||||
range: (message as NSString).range(of: finalName)
|
||||
)
|
||||
),
|
||||
confirmTitle: "JOIN_COMMUNITY_BUTTON_TITLE".localized(),
|
||||
onConfirm: { modal in
|
||||
guard let presentingViewController: UIViewController = modal.presentingViewController else {
|
||||
|
@ -1578,7 +1588,7 @@ extension ConversationVC:
|
|||
let errorModal: ConfirmationModal = ConfirmationModal(
|
||||
info: ConfirmationModal.Info(
|
||||
title: "COMMUNITY_ERROR_GENERIC".localized(),
|
||||
explanation: error.localizedDescription,
|
||||
body: .text(error.localizedDescription),
|
||||
cancelTitle: "BUTTON_OK".localized(),
|
||||
cancelStyle: .alert_text
|
||||
)
|
||||
|
@ -1596,6 +1606,17 @@ extension ConversationVC:
|
|||
|
||||
// MARK: - ContextMenuActionDelegate
|
||||
|
||||
func info(_ cellViewModel: MessageViewModel) {
|
||||
let mediaInfoVC = MediaInfoVC(
|
||||
attachments: (cellViewModel.attachments ?? []),
|
||||
isOutgoing: (cellViewModel.variant == .standardOutgoing),
|
||||
threadId: self.viewModel.threadData.threadId,
|
||||
threadVariant: self.viewModel.threadData.threadVariant,
|
||||
interactionId: cellViewModel.id
|
||||
)
|
||||
navigationController?.pushViewController(mediaInfoVC, animated: true)
|
||||
}
|
||||
|
||||
func retry(_ cellViewModel: MessageViewModel) {
|
||||
Storage.shared.writeAsync { [weak self] db in
|
||||
guard
|
||||
|
@ -2033,7 +2054,7 @@ extension ConversationVC:
|
|||
targetView: self.view,
|
||||
info: ConfirmationModal.Info(
|
||||
title: "Session",
|
||||
explanation: "This will ban the selected user from this room. It won't ban them from other rooms.",
|
||||
body: .text("This will ban the selected user from this room. It won't ban them from other rooms."),
|
||||
confirmTitle: "BUTTON_OK".localized(),
|
||||
cancelStyle: .alert_text,
|
||||
onConfirm: { [weak self] _ in
|
||||
|
@ -2057,7 +2078,7 @@ extension ConversationVC:
|
|||
targetView: self?.view,
|
||||
info: ConfirmationModal.Info(
|
||||
title: CommonStrings.errorAlertTitle,
|
||||
explanation: "context_menu_ban_user_error_alert_message".localized(),
|
||||
body: .text("context_menu_ban_user_error_alert_message".localized()),
|
||||
cancelTitle: "BUTTON_OK".localized(),
|
||||
cancelStyle: .alert_text
|
||||
)
|
||||
|
@ -2082,7 +2103,7 @@ extension ConversationVC:
|
|||
targetView: self.view,
|
||||
info: ConfirmationModal.Info(
|
||||
title: "Session",
|
||||
explanation: "This will ban the selected user from this room and delete all messages sent by them. It won't ban them from other rooms or delete the messages they sent there.",
|
||||
body: .text("This will ban the selected user from this room and delete all messages sent by them. It won't ban them from other rooms or delete the messages they sent there."),
|
||||
confirmTitle: "BUTTON_OK".localized(),
|
||||
cancelStyle: .alert_text,
|
||||
onConfirm: { [weak self] _ in
|
||||
|
@ -2106,7 +2127,7 @@ extension ConversationVC:
|
|||
targetView: self?.view,
|
||||
info: ConfirmationModal.Info(
|
||||
title: CommonStrings.errorAlertTitle,
|
||||
explanation: "context_menu_ban_user_error_alert_message".localized(),
|
||||
body: .text("context_menu_ban_user_error_alert_message".localized()),
|
||||
cancelTitle: "BUTTON_OK".localized(),
|
||||
cancelStyle: .alert_text
|
||||
)
|
||||
|
@ -2216,7 +2237,7 @@ extension ConversationVC:
|
|||
targetView: self.view,
|
||||
info: ConfirmationModal.Info(
|
||||
title: "VOICE_MESSAGE_TOO_SHORT_ALERT_TITLE".localized(),
|
||||
explanation: "VOICE_MESSAGE_TOO_SHORT_ALERT_MESSAGE".localized(),
|
||||
body: .text("VOICE_MESSAGE_TOO_SHORT_ALERT_MESSAGE".localized()),
|
||||
cancelTitle: "BUTTON_OK".localized(),
|
||||
cancelStyle: .alert_text
|
||||
)
|
||||
|
@ -2287,7 +2308,7 @@ extension ConversationVC:
|
|||
targetView: self.view,
|
||||
info: ConfirmationModal.Info(
|
||||
title: "ATTACHMENT_ERROR_ALERT_TITLE".localized(),
|
||||
explanation: (attachment.localizedErrorDescription ?? SignalAttachment.missingDataErrorMessage),
|
||||
body: .text(attachment.localizedErrorDescription ?? SignalAttachment.missingDataErrorMessage),
|
||||
cancelTitle: "BUTTON_OK".localized(),
|
||||
cancelStyle: .alert_text,
|
||||
afterClosed: onDismiss
|
||||
|
|
|
@ -169,7 +169,7 @@ final class ConversationVC: BaseVC, ConversationSearchControllerDelegate, UITabl
|
|||
}()
|
||||
|
||||
lazy var snInputView: InputView = InputView(
|
||||
threadVariant: self.viewModel.threadData.threadVariant,
|
||||
threadVariant: self.viewModel.initialThreadVariant,
|
||||
delegate: self
|
||||
)
|
||||
|
||||
|
@ -180,6 +180,7 @@ final class ConversationVC: BaseVC, ConversationSearchControllerDelegate, UITabl
|
|||
result.layer.cornerRadius = (ConversationVC.unreadCountViewSize / 2)
|
||||
result.set(.width, greaterThanOrEqualTo: ConversationVC.unreadCountViewSize)
|
||||
result.set(.height, to: ConversationVC.unreadCountViewSize)
|
||||
result.isHidden = true
|
||||
|
||||
return result
|
||||
}()
|
||||
|
@ -361,12 +362,12 @@ final class ConversationVC: BaseVC, ConversationSearchControllerDelegate, UITabl
|
|||
scrollButton.pin(.right, to: .right, of: view, withInset: -20)
|
||||
messageRequestView.pin(.left, to: .left, of: view)
|
||||
messageRequestView.pin(.right, to: .right, of: view)
|
||||
self.messageRequestsViewBotomConstraint = messageRequestView.pin(.bottom, to: .bottom, of: view, withInset: -16)
|
||||
self.scrollButtonBottomConstraint = scrollButton.pin(.bottom, to: .bottom, of: view, withInset: -16)
|
||||
self.scrollButtonBottomConstraint?.isActive = false // Note: Need to disable this to avoid a conflict with the other bottom constraint
|
||||
self.scrollButtonMessageRequestsBottomConstraint = scrollButton.pin(.bottom, to: .top, of: messageRequestView, withInset: -16)
|
||||
self.scrollButtonPendingMessageRequestInfoBottomConstraint = scrollButton.pin(.bottom, to: .top, of: pendingMessageRequestExplanationLabel, withInset: -16)
|
||||
|
||||
messageRequestsViewBotomConstraint = messageRequestView.pin(.bottom, to: .bottom, of: view, withInset: -16)
|
||||
scrollButtonBottomConstraint = scrollButton.pin(.bottom, to: .bottom, of: view, withInset: -16)
|
||||
scrollButtonBottomConstraint?.isActive = false // Note: Need to disable this to avoid a conflict with the other bottom constraint
|
||||
scrollButtonMessageRequestsBottomConstraint = scrollButton.pin(.bottom, to: .top, of: messageRequestView, withInset: -16)
|
||||
scrollButtonPendingMessageRequestInfoBottomConstraint = scrollButton.pin(.bottom, to: .top, of: pendingMessageRequestExplanationLabel, withInset: -16)
|
||||
|
||||
messageRequestBlockButton.pin(.top, to: .top, of: messageRequestView, withInset: 10)
|
||||
messageRequestBlockButton.center(.horizontal, in: messageRequestView)
|
||||
|
||||
|
@ -441,11 +442,6 @@ final class ConversationVC: BaseVC, ConversationSearchControllerDelegate, UITabl
|
|||
override func viewDidAppear(_ animated: Bool) {
|
||||
super.viewDidAppear(animated)
|
||||
|
||||
// Flag that the initial layout has been completed (the flag blocks and unblocks a number
|
||||
// of different behaviours)
|
||||
didFinishInitialLayout = true
|
||||
viewIsAppearing = false
|
||||
|
||||
if delayFirstResponder || isShowingSearchUI {
|
||||
delayFirstResponder = false
|
||||
|
||||
|
@ -457,7 +453,12 @@ final class ConversationVC: BaseVC, ConversationSearchControllerDelegate, UITabl
|
|||
}
|
||||
}
|
||||
|
||||
recoverInputView()
|
||||
recoverInputView { [weak self] in
|
||||
// Flag that the initial layout has been completed (the flag blocks and unblocks a number
|
||||
// of different behaviours)
|
||||
self?.didFinishInitialLayout = true
|
||||
self?.viewIsAppearing = false
|
||||
}
|
||||
}
|
||||
|
||||
override func viewWillDisappear(_ animated: Bool) {
|
||||
|
@ -483,7 +484,11 @@ final class ConversationVC: BaseVC, ConversationSearchControllerDelegate, UITabl
|
|||
}
|
||||
|
||||
@objc func applicationDidBecomeActive(_ notification: Notification) {
|
||||
startObservingChanges(didReturnFromBackground: true)
|
||||
/// Need to dispatch to the next run loop to prevent a possible crash caused by the database resuming mid-query
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
self?.startObservingChanges(didReturnFromBackground: true)
|
||||
}
|
||||
|
||||
recoverInputView()
|
||||
|
||||
if !isShowingSearchUI {
|
||||
|
@ -1261,11 +1266,12 @@ final class ConversationVC: BaseVC, ConversationSearchControllerDelegate, UITabl
|
|||
self.blockedBanner.pin([ UIView.HorizontalEdge.left, UIView.VerticalEdge.top, UIView.HorizontalEdge.right ], to: self.view)
|
||||
}
|
||||
|
||||
func recoverInputView() {
|
||||
func recoverInputView(completion: (() -> ())? = nil) {
|
||||
// This is a workaround for an issue where the textview is not scrollable
|
||||
// after the app goes into background and goes back in foreground.
|
||||
DispatchQueue.main.async {
|
||||
self.snInputView.text = self.snInputView.text
|
||||
completion?()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1298,7 +1304,7 @@ final class ConversationVC: BaseVC, ConversationSearchControllerDelegate, UITabl
|
|||
targetView: self?.view,
|
||||
info: ConfirmationModal.Info(
|
||||
title: CommonStrings.errorAlertTitle,
|
||||
explanation: "INVALID_AUDIO_FILE_ALERT_ERROR_MESSAGE".localized(),
|
||||
body: .text("INVALID_AUDIO_FILE_ALERT_ERROR_MESSAGE".localized()),
|
||||
cancelTitle: "BUTTON_OK".localized(),
|
||||
cancelStyle: .alert_text
|
||||
)
|
||||
|
|
|
@ -53,27 +53,65 @@ public class ConversationViewModel: OWSAudioPlayerDelegate {
|
|||
// MARK: - Initialization
|
||||
|
||||
init(threadId: String, threadVariant: SessionThread.Variant, focusedInteractionId: Int64?) {
|
||||
// If we have a specified 'focusedInteractionId' then use that, otherwise retrieve the oldest
|
||||
// unread interaction and start focused around that one
|
||||
let targetInteractionId: Int64? = {
|
||||
if let focusedInteractionId: Int64 = focusedInteractionId { return focusedInteractionId }
|
||||
typealias InitialData = (
|
||||
targetInteractionId: Int64?,
|
||||
currentUserIsClosedGroupMember: Bool?,
|
||||
openGroupPermissions: OpenGroup.Permissions?,
|
||||
blindedKey: String?
|
||||
)
|
||||
|
||||
let initialData: InitialData? = Storage.shared.read { db -> InitialData in
|
||||
let interaction: TypedTableAlias<Interaction> = TypedTableAlias()
|
||||
let groupMember: TypedTableAlias<GroupMember> = TypedTableAlias()
|
||||
|
||||
return Storage.shared.read { db in
|
||||
let interaction: TypedTableAlias<Interaction> = TypedTableAlias()
|
||||
|
||||
return try Interaction
|
||||
// If we have a specified 'focusedInteractionId' then use that, otherwise retrieve the oldest
|
||||
// unread interaction and start focused around that one
|
||||
let targetInteractionId: Int64? = (focusedInteractionId != nil ? focusedInteractionId :
|
||||
try Interaction
|
||||
.select(.id)
|
||||
.filter(interaction[.wasRead] == false)
|
||||
.filter(interaction[.threadId] == threadId)
|
||||
.order(interaction[.timestampMs].asc)
|
||||
.asRequest(of: Int64.self)
|
||||
.fetchOne(db)
|
||||
}
|
||||
}()
|
||||
)
|
||||
let currentUserIsClosedGroupMember: Bool? = (threadVariant != .closedGroup ? nil :
|
||||
try GroupMember
|
||||
.filter(groupMember[.groupId] == threadId)
|
||||
.filter(groupMember[.profileId] == getUserHexEncodedPublicKey(db))
|
||||
.filter(groupMember[.role] == GroupMember.Role.standard)
|
||||
.isNotEmpty(db)
|
||||
)
|
||||
let openGroupPermissions: OpenGroup.Permissions? = (threadVariant != .openGroup ? nil :
|
||||
try OpenGroup
|
||||
.filter(id: threadId)
|
||||
.select(.permissions)
|
||||
.asRequest(of: OpenGroup.Permissions.self)
|
||||
.fetchOne(db)
|
||||
)
|
||||
let blindedKey: String? = SessionThread.getUserHexEncodedBlindedKey(
|
||||
db,
|
||||
threadId: threadId,
|
||||
threadVariant: threadVariant
|
||||
)
|
||||
|
||||
return (
|
||||
targetInteractionId,
|
||||
currentUserIsClosedGroupMember,
|
||||
openGroupPermissions,
|
||||
blindedKey
|
||||
)
|
||||
}
|
||||
|
||||
self.threadId = threadId
|
||||
self.initialThreadVariant = threadVariant
|
||||
self.focusedInteractionId = targetInteractionId
|
||||
self.focusedInteractionId = initialData?.targetInteractionId
|
||||
self.threadData = SessionThreadViewModel(
|
||||
threadId: threadId,
|
||||
threadVariant: threadVariant,
|
||||
currentUserIsClosedGroupMember: initialData?.currentUserIsClosedGroupMember,
|
||||
openGroupPermissions: initialData?.openGroupPermissions
|
||||
).populatingCurrentUserBlindedKey(currentUserBlindedPublicKeyForThisThread: initialData?.blindedKey)
|
||||
self.pagedDataObserver = nil
|
||||
|
||||
// Note: Since this references self we need to finish initializing before setting it, we
|
||||
|
@ -93,7 +131,7 @@ public class ConversationViewModel: OWSAudioPlayerDelegate {
|
|||
DispatchQueue.global(qos: .userInitiated).async { [weak self] in
|
||||
// If we don't have a `initialFocusedId` then default to `.pageBefore` (it'll query
|
||||
// from a `0` offset)
|
||||
guard let initialFocusedId: Int64 = targetInteractionId else {
|
||||
guard let initialFocusedId: Int64 = initialData?.targetInteractionId else {
|
||||
self?.pagedDataObserver?.load(.pageBefore)
|
||||
return
|
||||
}
|
||||
|
@ -105,21 +143,7 @@ public class ConversationViewModel: OWSAudioPlayerDelegate {
|
|||
// MARK: - Thread Data
|
||||
|
||||
/// This value is the current state of the view
|
||||
public private(set) lazy var threadData: SessionThreadViewModel = SessionThreadViewModel(
|
||||
threadId: self.threadId,
|
||||
threadVariant: self.initialThreadVariant,
|
||||
currentUserIsClosedGroupMember: (self.initialThreadVariant != .closedGroup ?
|
||||
nil :
|
||||
Storage.shared.read { db in
|
||||
try GroupMember
|
||||
.filter(GroupMember.Columns.groupId == self.threadId)
|
||||
.filter(GroupMember.Columns.profileId == getUserHexEncodedPublicKey(db))
|
||||
.filter(GroupMember.Columns.role == GroupMember.Role.standard)
|
||||
.isNotEmpty(db)
|
||||
}
|
||||
)
|
||||
)
|
||||
.populatingCurrentUserBlindedKey()
|
||||
public private(set) var threadData: SessionThreadViewModel
|
||||
|
||||
/// This is all the data the screen needs to populate itself, please see the following link for tips to help optimise
|
||||
/// performance https://github.com/groue/GRDB.swift#valueobservation-performance
|
||||
|
|
|
@ -37,8 +37,6 @@ final class InputView: UIView, InputViewButtonDelegate, InputTextViewDelegate, M
|
|||
set { inputTextView.selectedRange = newValue }
|
||||
}
|
||||
|
||||
var inputTextViewIsFirstResponder: Bool { inputTextView.isFirstResponder }
|
||||
|
||||
var enabledMessageTypes: MessageInputTypes = .all {
|
||||
didSet {
|
||||
setEnabledMessageTypes(enabledMessageTypes, message: nil)
|
||||
|
@ -440,10 +438,6 @@ final class InputView: UIView, InputViewButtonDelegate, InputTextViewDelegate, M
|
|||
override func resignFirstResponder() -> Bool {
|
||||
inputTextView.resignFirstResponder()
|
||||
}
|
||||
|
||||
func inputTextViewBecomeFirstResponder() {
|
||||
inputTextView.becomeFirstResponder()
|
||||
}
|
||||
|
||||
func handleLongPress(_ gestureRecognizer: UITapGestureRecognizer) {
|
||||
// Not relevant in this case
|
||||
|
|
|
@ -46,7 +46,7 @@ final class DocumentView: UIView {
|
|||
// Size label
|
||||
let sizeLabel = UILabel()
|
||||
sizeLabel.font = .systemFont(ofSize: Values.verySmallFontSize)
|
||||
sizeLabel.text = OWSFormat.formatFileSize(UInt(attachment.byteCount))
|
||||
sizeLabel.text = OWSFormat.formatFileSize(attachment.byteCount)
|
||||
sizeLabel.themeTextColor = textColor
|
||||
sizeLabel.lineBreakMode = .byTruncatingTail
|
||||
|
||||
|
|
|
@ -29,7 +29,7 @@ public class MediaAlbumView: UIStackView {
|
|||
mediaCache: mediaCache,
|
||||
attachment: $0,
|
||||
isOutgoing: isOutgoing,
|
||||
maxMessageWidth: maxMessageWidth
|
||||
cornerRadius: VisibleMessageCell.largeCornerRadius
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -16,10 +16,9 @@ public class MediaView: UIView {
|
|||
|
||||
// MARK: -
|
||||
|
||||
private let mediaCache: NSCache<NSString, AnyObject>
|
||||
private let mediaCache: NSCache<NSString, AnyObject>?
|
||||
public let attachment: Attachment
|
||||
private let isOutgoing: Bool
|
||||
private let maxMessageWidth: CGFloat
|
||||
private var loadBlock: (() -> Void)?
|
||||
private var unloadBlock: (() -> Void)?
|
||||
|
||||
|
@ -46,22 +45,21 @@ public class MediaView: UIView {
|
|||
// MARK: - Initializers
|
||||
|
||||
public required init(
|
||||
mediaCache: NSCache<NSString, AnyObject>,
|
||||
mediaCache: NSCache<NSString, AnyObject>? = nil,
|
||||
attachment: Attachment,
|
||||
isOutgoing: Bool,
|
||||
maxMessageWidth: CGFloat
|
||||
cornerRadius: CGFloat
|
||||
) {
|
||||
self.mediaCache = mediaCache
|
||||
self.attachment = attachment
|
||||
self.isOutgoing = isOutgoing
|
||||
self.maxMessageWidth = maxMessageWidth
|
||||
|
||||
super.init(frame: .zero)
|
||||
|
||||
themeBackgroundColor = .backgroundSecondary
|
||||
clipsToBounds = true
|
||||
layer.masksToBounds = true
|
||||
layer.cornerRadius = VisibleMessageCell.largeCornerRadius
|
||||
layer.cornerRadius = cornerRadius
|
||||
|
||||
createContents()
|
||||
}
|
||||
|
@ -396,7 +394,7 @@ public class MediaView: UIView {
|
|||
|
||||
applyMediaBlock(media)
|
||||
|
||||
self?.mediaCache.setObject(media, forKey: cacheKey as NSString)
|
||||
self?.mediaCache?.setObject(media, forKey: cacheKey as NSString)
|
||||
self?.loadState.mutate { $0 = .loaded }
|
||||
}
|
||||
|
||||
|
@ -405,7 +403,7 @@ public class MediaView: UIView {
|
|||
return
|
||||
}
|
||||
|
||||
if let media: AnyObject = self.mediaCache.object(forKey: cacheKey as NSString) {
|
||||
if let media: AnyObject = self.mediaCache?.object(forKey: cacheKey as NSString) {
|
||||
Logger.verbose("media cache hit")
|
||||
|
||||
guard Thread.isMainThread else {
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
import UIKit
|
||||
import SessionUIKit
|
||||
import SessionMessagingKit
|
||||
import SessionUtilitiesKit
|
||||
|
||||
final class QuoteView: UIView {
|
||||
static let thumbnailSize: CGFloat = 48
|
||||
|
@ -237,17 +238,27 @@ final class QuoteView: UIView {
|
|||
.compactMap { $0 }
|
||||
.asSet()
|
||||
.contains(authorId)
|
||||
|
||||
let authorLabel = UILabel()
|
||||
authorLabel.font = .boldSystemFont(ofSize: Values.smallFontSize)
|
||||
authorLabel.text = (isCurrentUser ?
|
||||
"MEDIA_GALLERY_SENDER_NAME_YOU".localized() :
|
||||
Profile.displayName(
|
||||
authorLabel.text = {
|
||||
guard !isCurrentUser else { return "MEDIA_GALLERY_SENDER_NAME_YOU".localized() }
|
||||
guard body != nil else {
|
||||
// When we can't find the quoted message we want to hide the author label
|
||||
return Profile.displayNameNoFallback(
|
||||
id: authorId,
|
||||
threadVariant: threadVariant
|
||||
)
|
||||
}
|
||||
|
||||
return Profile.displayName(
|
||||
id: authorId,
|
||||
threadVariant: threadVariant
|
||||
)
|
||||
)
|
||||
}()
|
||||
authorLabel.themeTextColor = targetThemeColor
|
||||
authorLabel.lineBreakMode = .byTruncatingTail
|
||||
authorLabel.isHidden = (authorLabel.text == nil)
|
||||
|
||||
let authorLabelSize = authorLabel.systemLayoutSizeFitting(availableSpace)
|
||||
authorLabel.set(.height, to: authorLabelSize.height)
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
#import "OWSMath.h"
|
||||
#import "UIView+OWS.h"
|
||||
#import <QuartzCore/QuartzCore.h>
|
||||
#import <PureLayout/PureLayout.h>
|
||||
#import <SignalCoreKit/NSDate+OWS.h>
|
||||
#import <SessionUtilitiesKit/NSTimer+Proxying.h>
|
||||
#import <SessionSnodeKit/SessionSnodeKit.h>
|
||||
|
|
|
@ -98,7 +98,12 @@ class ThreadDisappearingMessagesViewModel: SessionTableViewModel<ThreadDisappear
|
|||
/// fetch (after the ones in `ValueConcurrentObserver.asyncStart`/`ValueConcurrentObserver.syncStart`)
|
||||
/// just in case the database has changed between the two reads - unfortunately it doesn't look like there is a way to prevent this
|
||||
private lazy var _observableSettingsData: ObservableData = ValueObservation
|
||||
.trackingConstantRegion { [weak self, config] db -> [SectionModel] in
|
||||
.trackingConstantRegion { [weak self, config, dependencies, threadId = self.threadId] db -> [SectionModel] in
|
||||
let userPublicKey: String = getUserHexEncodedPublicKey(db, dependencies: dependencies)
|
||||
let maybeThreadViewModel: SessionThreadViewModel? = try SessionThreadViewModel
|
||||
.conversationSettingsQuery(threadId: threadId, userPublicKey: userPublicKey)
|
||||
.fetchOne(db)
|
||||
|
||||
return [
|
||||
SectionModel(
|
||||
model: .content,
|
||||
|
@ -109,6 +114,10 @@ class ThreadDisappearingMessagesViewModel: SessionTableViewModel<ThreadDisappear
|
|||
rightAccessory: .radio(
|
||||
isSelected: { (self?.currentSelection.value == 0) }
|
||||
),
|
||||
isEnabled: (
|
||||
maybeThreadViewModel?.threadVariant != .closedGroup ||
|
||||
maybeThreadViewModel?.currentUserIsClosedGroupMember == true
|
||||
),
|
||||
onTap: { self?.currentSelection.send(0) }
|
||||
)
|
||||
].appending(
|
||||
|
@ -122,6 +131,10 @@ class ThreadDisappearingMessagesViewModel: SessionTableViewModel<ThreadDisappear
|
|||
rightAccessory: .radio(
|
||||
isSelected: { (self?.currentSelection.value == duration) }
|
||||
),
|
||||
isEnabled: (
|
||||
maybeThreadViewModel?.threadVariant != .closedGroup ||
|
||||
maybeThreadViewModel?.currentUserIsClosedGroupMember == true
|
||||
),
|
||||
onTap: { self?.currentSelection.send(duration) }
|
||||
)
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
import Foundation
|
||||
import Combine
|
||||
import GRDB
|
||||
import YYImage
|
||||
import DifferenceKit
|
||||
import SessionUIKit
|
||||
import SessionMessagingKit
|
||||
|
@ -395,7 +396,7 @@ class ThreadSettingsViewModel: SessionTableViewModel<ThreadSettingsViewModel.Nav
|
|||
accessibilityLabel: "Leave group",
|
||||
confirmationInfo: ConfirmationModal.Info(
|
||||
title: "leave_group_confirmation_alert_title".localized(),
|
||||
attributedExplanation: {
|
||||
body: .attributedText({
|
||||
if currentUserIsClosedGroupAdmin {
|
||||
return NSAttributedString(string: "admin_group_leave_warning".localized())
|
||||
}
|
||||
|
@ -412,7 +413,7 @@ class ThreadSettingsViewModel: SessionTableViewModel<ThreadSettingsViewModel.Nav
|
|||
range: (mutableAttributedString.string as NSString).range(of: threadViewModel.displayName)
|
||||
)
|
||||
return mutableAttributedString
|
||||
}(),
|
||||
}()),
|
||||
confirmTitle: "LEAVE_BUTTON_TITLE".localized(),
|
||||
confirmStyle: .danger,
|
||||
cancelStyle: .alert_text
|
||||
|
@ -548,9 +549,8 @@ class ThreadSettingsViewModel: SessionTableViewModel<ThreadSettingsViewModel.Nav
|
|||
threadViewModel.displayName
|
||||
)
|
||||
}(),
|
||||
explanation: (threadViewModel.threadIsBlocked == true ?
|
||||
nil :
|
||||
"BLOCK_USER_BEHAVIOR_EXPLANATION".localized()
|
||||
body: (threadViewModel.threadIsBlocked == true ? .none :
|
||||
.text("BLOCK_USER_BEHAVIOR_EXPLANATION".localized())
|
||||
),
|
||||
confirmTitle: (threadViewModel.threadIsBlocked == true ?
|
||||
"BLOCK_LIST_UNBLOCK_BUTTON".localized() :
|
||||
|
@ -688,13 +688,12 @@ class ThreadSettingsViewModel: SessionTableViewModel<ThreadSettingsViewModel.Nav
|
|||
displayName
|
||||
)
|
||||
),
|
||||
explanation: (oldBlockedState == false ?
|
||||
body: (oldBlockedState == true ? .none : .text(
|
||||
String(
|
||||
format: "BLOCK_LIST_VIEW_BLOCKED_ALERT_MESSAGE_FORMAT".localized(),
|
||||
displayName
|
||||
) :
|
||||
nil
|
||||
),
|
||||
)
|
||||
)),
|
||||
accessibilityLabel: oldBlockedState == false ? "User blocked" : "Confirm unblock",
|
||||
accessibilityId: "Test_name",
|
||||
cancelTitle: "BUTTON_OK".localized(),
|
||||
|
|
|
@ -91,14 +91,17 @@ class GlobalSearchViewController: BaseVC, UITableViewDelegate, UITableViewDataSo
|
|||
setupNavigationBar()
|
||||
}
|
||||
|
||||
public override func viewWillAppear(_ animated: Bool) {
|
||||
super.viewWillAppear(animated)
|
||||
public override func viewDidAppear(_ animated: Bool) {
|
||||
super.viewDidAppear(animated)
|
||||
searchBar.becomeFirstResponder()
|
||||
}
|
||||
|
||||
public override func viewWillDisappear(_ animated: Bool) {
|
||||
super.viewWillDisappear(animated)
|
||||
searchBar.resignFirstResponder()
|
||||
|
||||
UIView.performWithoutAnimation {
|
||||
searchBar.resignFirstResponder()
|
||||
}
|
||||
}
|
||||
|
||||
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
|
||||
|
@ -138,10 +141,6 @@ class GlobalSearchViewController: BaseVC, UITableViewDelegate, UITableViewDataSo
|
|||
}
|
||||
}
|
||||
|
||||
private func reloadTableData() {
|
||||
tableView.reloadData()
|
||||
}
|
||||
|
||||
// MARK: - Update Search Results
|
||||
|
||||
private func refreshSearchResults() {
|
||||
|
@ -155,9 +154,11 @@ class GlobalSearchViewController: BaseVC, UITableViewDelegate, UITableViewDataSo
|
|||
let searchText = rawSearchText.stripped
|
||||
|
||||
guard searchText.count > 0 else {
|
||||
guard searchText != (lastSearchText ?? "") else { return }
|
||||
|
||||
searchResultSet = defaultSearchResults
|
||||
lastSearchText = nil
|
||||
reloadTableData()
|
||||
tableView.reloadData()
|
||||
return
|
||||
}
|
||||
guard lastSearchText != searchText else { return }
|
||||
|
@ -212,7 +213,7 @@ class GlobalSearchViewController: BaseVC, UITableViewDelegate, UITableViewDataSo
|
|||
.compactMap { $0 }
|
||||
.flatMap { $0 }
|
||||
self?.isLoading = false
|
||||
self?.reloadTableData()
|
||||
self?.tableView.reloadData()
|
||||
self?.refreshTimer = nil
|
||||
|
||||
default: break
|
||||
|
@ -283,18 +284,12 @@ extension GlobalSearchViewController {
|
|||
return
|
||||
}
|
||||
|
||||
if let presentedVC = self.presentedViewController {
|
||||
presentedVC.dismiss(animated: false, completion: nil)
|
||||
}
|
||||
|
||||
let viewControllers: [UIViewController] = (self.navigationController?
|
||||
.viewControllers)
|
||||
.defaulting(to: [])
|
||||
.appending(
|
||||
ConversationVC(threadId: threadId, threadVariant: threadVariant, focusedInteractionId: focusedInteractionId)
|
||||
)
|
||||
|
||||
self.navigationController?.setViewControllers(viewControllers, animated: true)
|
||||
let viewController: ConversationVC = ConversationVC(
|
||||
threadId: threadId,
|
||||
threadVariant: threadVariant,
|
||||
focusedInteractionId: focusedInteractionId
|
||||
)
|
||||
self.navigationController?.pushViewController(viewController, animated: true)
|
||||
}
|
||||
|
||||
// MARK: - UITableViewDataSource
|
||||
|
|
|
@ -308,7 +308,10 @@ final class HomeVC: BaseVC, UITableViewDataSource, UITableViewDelegate, SeedRemi
|
|||
}
|
||||
|
||||
@objc func applicationDidBecomeActive(_ notification: Notification) {
|
||||
startObservingChanges(didReturnFromBackground: true)
|
||||
/// Need to dispatch to the next run loop to prevent a possible crash caused by the database resuming mid-query
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
self?.startObservingChanges(didReturnFromBackground: true)
|
||||
}
|
||||
}
|
||||
|
||||
@objc func applicationDidResignActive(_ notification: Notification) {
|
||||
|
@ -393,8 +396,18 @@ final class HomeVC: BaseVC, UITableViewDataSource, UITableViewDelegate, SeedRemi
|
|||
// in from a frame of CGRect.zero)
|
||||
guard hasLoadedInitialThreadData else {
|
||||
hasLoadedInitialThreadData = true
|
||||
UIView.performWithoutAnimation {
|
||||
handleThreadUpdates(updatedData, changeset: changeset, initialLoad: true)
|
||||
|
||||
UIView.performWithoutAnimation { [weak self] in
|
||||
// Hide the 'loading conversations' label (now that we have received conversation data)
|
||||
self?.loadingConversationsLabel.isHidden = true
|
||||
|
||||
// Show the empty state if there is no data
|
||||
self?.emptyStateView.isHidden = (
|
||||
!updatedData.isEmpty &&
|
||||
updatedData.contains(where: { !$0.elements.isEmpty })
|
||||
)
|
||||
|
||||
self?.viewModel.updateThreadData(updatedData)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
@ -739,7 +752,7 @@ final class HomeVC: BaseVC, UITableViewDataSource, UITableViewDelegate, SeedRemi
|
|||
let confirmationModal: ConfirmationModal = ConfirmationModal(
|
||||
info: ConfirmationModal.Info(
|
||||
title: "delete_conversation_confirmation_alert_title".localized(),
|
||||
attributedExplanation: confirmationModalExplanation,
|
||||
body: .attributedText(confirmationModalExplanation),
|
||||
confirmTitle: "TXT_DELETE_TITLE".localized(),
|
||||
confirmStyle: .danger,
|
||||
cancelStyle: .alert_text,
|
||||
|
@ -824,7 +837,7 @@ final class HomeVC: BaseVC, UITableViewDataSource, UITableViewDelegate, SeedRemi
|
|||
let confirmationModal: ConfirmationModal = ConfirmationModal(
|
||||
info: ConfirmationModal.Info(
|
||||
title: confirmationModalTitle,
|
||||
attributedExplanation: confirmationModalExplanation,
|
||||
body: .attributedText(confirmationModalExplanation),
|
||||
confirmTitle: "LEAVE_BUTTON_TITLE".localized(),
|
||||
confirmStyle: .danger,
|
||||
cancelStyle: .alert_text,
|
||||
|
|
|
@ -162,7 +162,10 @@ class MessageRequestsViewController: BaseVC, UITableViewDelegate, UITableViewDat
|
|||
}
|
||||
|
||||
@objc func applicationDidBecomeActive(_ notification: Notification) {
|
||||
startObservingChanges(didReturnFromBackground: true)
|
||||
/// Need to dispatch to the next run loop to prevent a possible crash caused by the database resuming mid-query
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
self?.startObservingChanges(didReturnFromBackground: true)
|
||||
}
|
||||
}
|
||||
|
||||
@objc func applicationDidResignActive(_ notification: Notification) {
|
||||
|
|
|
@ -214,7 +214,7 @@ final class NewDMVC: BaseVC, UIPageViewControllerDataSource, UIPageViewControlle
|
|||
targetView: self?.view,
|
||||
info: ConfirmationModal.Info(
|
||||
title: "ALERT_ERROR_TITLE".localized(),
|
||||
explanation: message,
|
||||
body: .text(message),
|
||||
cancelTitle: "BUTTON_OK".localized(),
|
||||
cancelStyle: .alert_text
|
||||
)
|
||||
|
|
|
@ -119,7 +119,10 @@ public class DocumentTileViewController: UIViewController, UITableViewDelegate,
|
|||
}
|
||||
|
||||
@objc func applicationDidBecomeActive(_ notification: Notification) {
|
||||
startObservingChanges()
|
||||
/// Need to dispatch to the next run loop to prevent a possible crash caused by the database resuming mid-query
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
self?.startObservingChanges()
|
||||
}
|
||||
}
|
||||
|
||||
@objc func applicationDidResignActive(_ notification: Notification) {
|
||||
|
|
|
@ -389,7 +389,7 @@ class GifPickerViewController: OWSViewController, UISearchBarDelegate, UICollect
|
|||
targetView: self?.view,
|
||||
info: ConfirmationModal.Info(
|
||||
title: "GIF_PICKER_FAILURE_ALERT_TITLE".localized(),
|
||||
explanation: error.localizedDescription,
|
||||
body: .text(error.localizedDescription),
|
||||
confirmTitle: CommonStrings.retryButton,
|
||||
cancelTitle: CommonStrings.dismissButton,
|
||||
cancelStyle: .alert_text,
|
||||
|
@ -458,7 +458,7 @@ class GifPickerViewController: OWSViewController, UISearchBarDelegate, UICollect
|
|||
targetView: self.view,
|
||||
info: ConfirmationModal.Info(
|
||||
title: CommonStrings.errorAlertTitle,
|
||||
explanation: "GIF_PICKER_VIEW_MISSING_QUERY".localized(),
|
||||
body: .text("GIF_PICKER_VIEW_MISSING_QUERY".localized()),
|
||||
cancelTitle: "BUTTON_OK".localized(),
|
||||
cancelStyle: .alert_text
|
||||
)
|
||||
|
|
|
@ -0,0 +1,193 @@
|
|||
// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved.
|
||||
|
||||
import UIKit
|
||||
import SessionUIKit
|
||||
import SessionUtilitiesKit
|
||||
import SessionMessagingKit
|
||||
import SignalUtilitiesKit
|
||||
|
||||
extension MediaInfoVC {
|
||||
final class MediaInfoView: UIView {
|
||||
private static let cornerRadius: CGFloat = 12
|
||||
|
||||
private var attachment: Attachment?
|
||||
private let width: CGFloat = MediaInfoVC.mediaSize - 2 * MediaInfoVC.arrowSize.width
|
||||
|
||||
// MARK: - UI
|
||||
|
||||
private lazy var fileIdLabel: UILabel = {
|
||||
let result: UILabel = UILabel()
|
||||
result.font = .systemFont(ofSize: Values.mediumFontSize)
|
||||
result.themeTextColor = .textPrimary
|
||||
|
||||
return result
|
||||
}()
|
||||
|
||||
private lazy var fileTypeLabel: UILabel = {
|
||||
let result: UILabel = UILabel()
|
||||
result.font = .systemFont(ofSize: Values.mediumFontSize)
|
||||
result.themeTextColor = .textPrimary
|
||||
|
||||
return result
|
||||
}()
|
||||
|
||||
private lazy var fileSizeLabel: UILabel = {
|
||||
let result: UILabel = UILabel()
|
||||
result.font = .systemFont(ofSize: Values.mediumFontSize)
|
||||
result.themeTextColor = .textPrimary
|
||||
|
||||
return result
|
||||
}()
|
||||
|
||||
private lazy var resolutionLabel: UILabel = {
|
||||
let result: UILabel = UILabel()
|
||||
result.font = .systemFont(ofSize: Values.mediumFontSize)
|
||||
result.themeTextColor = .textPrimary
|
||||
|
||||
return result
|
||||
}()
|
||||
|
||||
private lazy var durationLabel: UILabel = {
|
||||
let result: UILabel = UILabel()
|
||||
result.font = .systemFont(ofSize: Values.mediumFontSize)
|
||||
result.themeTextColor = .textPrimary
|
||||
|
||||
return result
|
||||
}()
|
||||
|
||||
// MARK: - Lifecycle
|
||||
|
||||
init(attachment: Attachment?) {
|
||||
self.attachment = attachment
|
||||
|
||||
super.init(frame: CGRect.zero)
|
||||
self.accessibilityLabel = "Media info"
|
||||
setUpViewHierarchy()
|
||||
update(attachment: attachment)
|
||||
}
|
||||
|
||||
override init(frame: CGRect) {
|
||||
preconditionFailure("Use init(attachment:) instead.")
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
preconditionFailure("Use init(attachment:) instead.")
|
||||
}
|
||||
|
||||
private func setUpViewHierarchy() {
|
||||
let backgroundView: UIView = UIView()
|
||||
backgroundView.clipsToBounds = true
|
||||
backgroundView.themeBackgroundColor = .contextMenu_background
|
||||
backgroundView.layer.cornerRadius = Self.cornerRadius
|
||||
addSubview(backgroundView)
|
||||
backgroundView.pin(to: self)
|
||||
|
||||
let container: UIView = UIView()
|
||||
container.set(.width, to: self.width)
|
||||
|
||||
// File ID
|
||||
let fileIdTitleLabel: UILabel = {
|
||||
let result = UILabel()
|
||||
result.font = .boldSystemFont(ofSize: Values.mediumFontSize)
|
||||
result.text = "ATTACHMENT_INFO_FILE_ID".localized() + ":"
|
||||
result.themeTextColor = .textPrimary
|
||||
|
||||
return result
|
||||
}()
|
||||
let fileIdContainerStackView: UIStackView = UIStackView(arrangedSubviews: [ fileIdTitleLabel, fileIdLabel ])
|
||||
fileIdContainerStackView.axis = .vertical
|
||||
fileIdContainerStackView.spacing = 6
|
||||
container.addSubview(fileIdContainerStackView)
|
||||
fileIdContainerStackView.pin([ UIView.HorizontalEdge.leading, UIView.HorizontalEdge.trailing, UIView.VerticalEdge.top ], to: container)
|
||||
|
||||
// File Type
|
||||
let fileTypeTitleLabel: UILabel = {
|
||||
let result = UILabel()
|
||||
result.font = .boldSystemFont(ofSize: Values.mediumFontSize)
|
||||
result.text = "ATTACHMENT_INFO_FILE_TYPE".localized() + ":"
|
||||
result.themeTextColor = .textPrimary
|
||||
|
||||
return result
|
||||
}()
|
||||
let fileTypeContainerStackView: UIStackView = UIStackView(arrangedSubviews: [ fileTypeTitleLabel, fileTypeLabel ])
|
||||
fileTypeContainerStackView.axis = .vertical
|
||||
fileTypeContainerStackView.spacing = 6
|
||||
container.addSubview(fileTypeContainerStackView)
|
||||
fileTypeContainerStackView.pin(.leading, to: .leading, of: container)
|
||||
fileTypeContainerStackView.pin(.top, to: .bottom, of: fileIdContainerStackView, withInset: Values.largeSpacing)
|
||||
|
||||
// File Size
|
||||
let fileSizeTitleLabel: UILabel = {
|
||||
let result = UILabel()
|
||||
result.font = .boldSystemFont(ofSize: Values.mediumFontSize)
|
||||
result.text = "ATTACHMENT_INFO_FILE_SIZE".localized() + ":"
|
||||
result.themeTextColor = .textPrimary
|
||||
|
||||
return result
|
||||
}()
|
||||
let fileSizeContainerStackView: UIStackView = UIStackView(arrangedSubviews: [ fileSizeTitleLabel, fileSizeLabel ])
|
||||
fileSizeContainerStackView.axis = .vertical
|
||||
fileSizeContainerStackView.spacing = 6
|
||||
container.addSubview(fileSizeContainerStackView)
|
||||
fileSizeContainerStackView.pin(.trailing, to: .trailing, of: container)
|
||||
fileSizeContainerStackView.pin(.top, to: .bottom, of: fileIdContainerStackView, withInset: Values.largeSpacing)
|
||||
fileSizeContainerStackView.set(.width, to: 90)
|
||||
|
||||
// Resolution
|
||||
let resolutionTitleLabel: UILabel = {
|
||||
let result = UILabel()
|
||||
result.font = .boldSystemFont(ofSize: Values.mediumFontSize)
|
||||
result.text = "ATTACHMENT_INFO_RESOLUTION".localized() + ":"
|
||||
result.themeTextColor = .textPrimary
|
||||
|
||||
return result
|
||||
}()
|
||||
let resolutionContainerStackView: UIStackView = UIStackView(arrangedSubviews: [ resolutionTitleLabel, resolutionLabel ])
|
||||
resolutionContainerStackView.axis = .vertical
|
||||
resolutionContainerStackView.spacing = 6
|
||||
container.addSubview(resolutionContainerStackView)
|
||||
resolutionContainerStackView.pin(.leading, to: .leading, of: container)
|
||||
resolutionContainerStackView.pin(.top, to: .bottom, of: fileTypeContainerStackView, withInset: Values.largeSpacing)
|
||||
|
||||
// Duration
|
||||
let durationTitleLabel: UILabel = {
|
||||
let result = UILabel()
|
||||
result.font = .boldSystemFont(ofSize: Values.mediumFontSize)
|
||||
result.text = "ATTACHMENT_INFO_DURATION".localized() + ":"
|
||||
result.themeTextColor = .textPrimary
|
||||
|
||||
return result
|
||||
}()
|
||||
let durationContainerStackView: UIStackView = UIStackView(arrangedSubviews: [ durationTitleLabel, durationLabel ])
|
||||
durationContainerStackView.axis = .vertical
|
||||
durationContainerStackView.spacing = 6
|
||||
container.addSubview(durationContainerStackView)
|
||||
durationContainerStackView.pin(.trailing, to: .trailing, of: container)
|
||||
durationContainerStackView.pin(.top, to: .bottom, of: fileSizeContainerStackView, withInset: Values.largeSpacing)
|
||||
durationContainerStackView.set(.width, to: 90)
|
||||
container.pin(.bottom, to: .bottom, of: durationContainerStackView)
|
||||
|
||||
backgroundView.addSubview(container)
|
||||
container.pin(to: backgroundView, withInset: Values.largeSpacing)
|
||||
}
|
||||
|
||||
// MARK: - Interaction
|
||||
public func update(attachment: Attachment?) {
|
||||
guard let attachment: Attachment = attachment else { return }
|
||||
|
||||
self.attachment = attachment
|
||||
|
||||
fileIdLabel.text = attachment.serverId
|
||||
fileTypeLabel.text = attachment.contentType
|
||||
fileSizeLabel.text = OWSFormat.formatFileSize(attachment.byteCount)
|
||||
resolutionLabel.text = {
|
||||
guard let width = attachment.width, let height = attachment.height else { return "N/A" }
|
||||
return "\(width)×\(height)"
|
||||
}()
|
||||
durationLabel.text = {
|
||||
guard let duration = attachment.duration else { return "N/A" }
|
||||
return floor(duration).formatted(format: .videoDuration)
|
||||
}()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved.
|
||||
|
||||
import UIKit
|
||||
import SessionUIKit
|
||||
import SessionUtilitiesKit
|
||||
import SessionMessagingKit
|
||||
|
||||
extension MediaInfoVC {
|
||||
final class MediaPreviewView: UIView {
|
||||
private static let cornerRadius: CGFloat = 8
|
||||
|
||||
private let attachment: Attachment
|
||||
private let isOutgoing: Bool
|
||||
|
||||
// MARK: - UI
|
||||
|
||||
private lazy var mediaView: MediaView = {
|
||||
let result: MediaView = MediaView.init(
|
||||
attachment: attachment,
|
||||
isOutgoing: isOutgoing,
|
||||
cornerRadius: 0
|
||||
)
|
||||
|
||||
return result
|
||||
}()
|
||||
|
||||
// MARK: - Lifecycle
|
||||
|
||||
init(attachment: Attachment, isOutgoing: Bool) {
|
||||
self.attachment = attachment
|
||||
self.isOutgoing = isOutgoing
|
||||
|
||||
super.init(frame: CGRect.zero)
|
||||
self.accessibilityLabel = "Media info"
|
||||
setUpViewHierarchy()
|
||||
}
|
||||
|
||||
override init(frame: CGRect) {
|
||||
preconditionFailure("Use init(attachment:) instead.")
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
preconditionFailure("Use init(attachment:) instead.")
|
||||
}
|
||||
|
||||
private func setUpViewHierarchy() {
|
||||
set(.width, to: MediaInfoVC.mediaSize)
|
||||
set(.height, to: MediaInfoVC.mediaSize)
|
||||
|
||||
addSubview(mediaView)
|
||||
mediaView.pin(to: self)
|
||||
|
||||
mediaView.loadMedia()
|
||||
}
|
||||
|
||||
// MARK: - Copy
|
||||
|
||||
/// This function is used to make sure the carousel view contains this class can loop infinitely
|
||||
func copyView() -> MediaPreviewView {
|
||||
return MediaPreviewView(attachment: self.attachment, isOutgoing: self.isOutgoing)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,151 @@
|
|||
// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved.
|
||||
|
||||
import UIKit
|
||||
import SessionUIKit
|
||||
import SessionMessagingKit
|
||||
import SessionUtilitiesKit
|
||||
import SignalUtilitiesKit
|
||||
|
||||
final class MediaInfoVC: BaseVC, SessionCarouselViewDelegate {
|
||||
internal static let mediaSize: CGFloat = UIScreen.main.bounds.width - 2 * Values.veryLargeSpacing
|
||||
internal static let arrowSize: CGSize = CGSize(width: 20, height: 30)
|
||||
|
||||
private let attachments: [Attachment]
|
||||
private let isOutgoing: Bool
|
||||
private let threadId: String
|
||||
private let threadVariant: SessionThread.Variant
|
||||
private let interactionId: Int64
|
||||
|
||||
private var currentPage: Int = 0
|
||||
|
||||
// MARK: - UI
|
||||
private lazy var mediaInfoView: MediaInfoView = MediaInfoView(attachment: nil)
|
||||
private lazy var mediaCarouselView: SessionCarouselView = {
|
||||
let slices: [MediaPreviewView] = self.attachments.map {
|
||||
MediaPreviewView(
|
||||
attachment: $0,
|
||||
isOutgoing: self.isOutgoing
|
||||
)
|
||||
}
|
||||
let result: SessionCarouselView = SessionCarouselView(
|
||||
info: SessionCarouselView.Info(
|
||||
slices: slices,
|
||||
copyOfFirstSlice: slices.first?.copyView(),
|
||||
copyOfLastSlice: slices.last?.copyView(),
|
||||
sliceSize: CGSize(
|
||||
width: Self.mediaSize,
|
||||
height: Self.mediaSize
|
||||
),
|
||||
shouldShowPageControl: true,
|
||||
pageControlStyle: SessionCarouselView.PageControlStyle(
|
||||
size: .medium,
|
||||
backgroundColor: .init(white: 0, alpha: 0.4),
|
||||
bottomInset: Values.mediumSpacing
|
||||
),
|
||||
shouldShowArrows: true,
|
||||
arrowsSize: Self.arrowSize,
|
||||
cornerRadius: 8
|
||||
)
|
||||
)
|
||||
result.set(.height, to: Self.mediaSize)
|
||||
result.delegate = self
|
||||
|
||||
return result
|
||||
}()
|
||||
|
||||
private lazy var fullScreenButton: UIButton = {
|
||||
let result: UIButton = UIButton(type: .custom)
|
||||
result.setImage(
|
||||
UIImage(systemName: "arrow.up.left.and.arrow.down.right")?
|
||||
.withRenderingMode(.alwaysTemplate),
|
||||
for: .normal
|
||||
)
|
||||
result.themeTintColor = .textPrimary
|
||||
result.backgroundColor = .init(white: 0, alpha: 0.4)
|
||||
result.layer.cornerRadius = 14
|
||||
result.set(.width, to: 28)
|
||||
result.set(.height, to: 28)
|
||||
result.addTarget(self, action: #selector(showMediaFullScreen), for: .touchUpInside)
|
||||
|
||||
return result
|
||||
}()
|
||||
|
||||
// MARK: - Initialization
|
||||
|
||||
init(
|
||||
attachments: [Attachment],
|
||||
isOutgoing: Bool,
|
||||
threadId: String,
|
||||
threadVariant: SessionThread.Variant,
|
||||
interactionId: Int64
|
||||
) {
|
||||
self.threadId = threadId
|
||||
self.threadVariant = threadVariant
|
||||
self.interactionId = interactionId
|
||||
self.isOutgoing = isOutgoing
|
||||
self.attachments = attachments
|
||||
super.init(nibName: nil, bundle: nil)
|
||||
}
|
||||
|
||||
override init(nibName: String?, bundle: Bundle?) {
|
||||
preconditionFailure("Use init(attachments:) instead.")
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
preconditionFailure("Use init(attachments:) instead.")
|
||||
}
|
||||
|
||||
// MARK: - Lifecycle
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
ViewControllerUtilities.setUpDefaultSessionStyle(
|
||||
for: self,
|
||||
title: "message_info_title".localized(),
|
||||
hasCustomBackButton: false
|
||||
)
|
||||
|
||||
let mediaStackView: UIStackView = UIStackView()
|
||||
mediaStackView.axis = .horizontal
|
||||
|
||||
mediaInfoView.update(attachment: attachments[0])
|
||||
|
||||
mediaCarouselView.addSubview(fullScreenButton)
|
||||
fullScreenButton.pin(.trailing, to: .trailing, of: mediaCarouselView, withInset: -(Values.smallSpacing + Values.veryLargeSpacing))
|
||||
fullScreenButton.pin(.bottom, to: .bottom, of: mediaCarouselView, withInset: -Values.smallSpacing)
|
||||
|
||||
let stackView: UIStackView = UIStackView(arrangedSubviews: [ mediaCarouselView, mediaInfoView ])
|
||||
stackView.axis = .vertical
|
||||
stackView.alignment = .center
|
||||
stackView.spacing = Values.largeSpacing
|
||||
|
||||
self.view.addSubview(stackView)
|
||||
stackView.pin([ UIView.HorizontalEdge.leading, UIView.HorizontalEdge.trailing ], to: self.view)
|
||||
stackView.pin(.top, to: .top, of: self.view, withInset: Values.veryLargeSpacing)
|
||||
}
|
||||
|
||||
// MARK: - Interaction
|
||||
|
||||
@objc func showMediaFullScreen() {
|
||||
let attachment = self.attachments[self.currentPage]
|
||||
let viewController: UIViewController? = MediaGalleryViewModel.createDetailViewController(
|
||||
for: self.threadId,
|
||||
threadVariant: self.threadVariant,
|
||||
interactionId: self.interactionId,
|
||||
selectedAttachmentId: attachment.id,
|
||||
options: [ .sliderEnabled ]
|
||||
)
|
||||
if let viewController: UIViewController = viewController {
|
||||
viewController.transitioningDelegate = nil
|
||||
self.present(viewController, animated: true)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - SessionCarouselViewDelegate
|
||||
|
||||
func carouselViewDidScrollToNewSlice(currentPage: Int) {
|
||||
self.currentPage = currentPage
|
||||
mediaInfoView.update(attachment: attachments[currentPage])
|
||||
}
|
||||
}
|
|
@ -245,7 +245,10 @@ class MediaPageViewController: UIPageViewController, UIPageViewControllerDataSou
|
|||
}
|
||||
|
||||
@objc func applicationDidBecomeActive(_ notification: Notification) {
|
||||
startObservingChanges()
|
||||
/// Need to dispatch to the next run loop to prevent a possible crash caused by the database resuming mid-query
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
self?.startObservingChanges()
|
||||
}
|
||||
}
|
||||
|
||||
@objc func applicationDidResignActive(_ notification: Notification) {
|
||||
|
|
|
@ -175,7 +175,10 @@ public class MediaTileViewController: UIViewController, UICollectionViewDataSour
|
|||
}
|
||||
|
||||
@objc func applicationDidBecomeActive(_ notification: Notification) {
|
||||
startObservingChanges(didReturnFromBackground: true)
|
||||
/// Need to dispatch to the next run loop to prevent a possible crash caused by the database resuming mid-query
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
self?.startObservingChanges(didReturnFromBackground: true)
|
||||
}
|
||||
}
|
||||
|
||||
@objc func applicationDidResignActive(_ notification: Notification) {
|
||||
|
|
|
@ -315,7 +315,7 @@ class PhotoCaptureViewController: OWSViewController {
|
|||
let modal: ConfirmationModal = ConfirmationModal(
|
||||
info: ConfirmationModal.Info(
|
||||
title: CommonStrings.errorAlertTitle,
|
||||
explanation: error.localizedDescription,
|
||||
body: .text(error.localizedDescription),
|
||||
cancelTitle: CommonStrings.dismissButton,
|
||||
cancelStyle: .alert_text,
|
||||
afterClosed: { [weak self] in self?.dismiss(animated: true) }
|
||||
|
|
|
@ -446,19 +446,18 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
|
|||
|
||||
guard CurrentAppContext().isMainApp else { return }
|
||||
|
||||
CurrentAppContext().setMainAppBadgeNumber(
|
||||
Storage.shared
|
||||
/// On application startup the `Storage.read` can be slightly slow while GRDB spins up it's database
|
||||
/// read pools (up to a few seconds), since this read is blocking we want to dispatch it to run async to ensure
|
||||
/// we don't block user interaction while it's running
|
||||
DispatchQueue.global(qos: .default).async {
|
||||
let unreadCount: Int = Storage.shared
|
||||
.read { db in
|
||||
let userPublicKey: String = getUserHexEncodedPublicKey(db)
|
||||
let thread: TypedTableAlias<SessionThread> = TypedTableAlias()
|
||||
|
||||
return try Interaction
|
||||
.filter(Interaction.Columns.wasRead == false)
|
||||
.filter(
|
||||
// Exclude outgoing and deleted messages from the count
|
||||
Interaction.Columns.variant != Interaction.Variant.standardOutgoing &&
|
||||
Interaction.Columns.variant != Interaction.Variant.standardIncomingDeleted
|
||||
)
|
||||
.filter(Interaction.Variant.variantsToIncrementUnreadCount.contains(Interaction.Columns.variant))
|
||||
.filter(
|
||||
// Only count mentions if 'onlyNotifyForMentions' is set
|
||||
thread[.onlyNotifyForMentions] == false ||
|
||||
|
@ -482,7 +481,11 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
|
|||
.fetchCount(db)
|
||||
}
|
||||
.defaulting(to: 0)
|
||||
)
|
||||
|
||||
DispatchQueue.main.async {
|
||||
CurrentAppContext().setMainAppBadgeNumber(unreadCount)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,24 +0,0 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIIEEzCCAvugAwIBAgIUY9RQqbjhsQEkdeSgV9L0os9xZ7AwDQYJKoZIhvcNAQEL
|
||||
BQAwfDELMAkGA1UEBhMCQVUxETAPBgNVBAgMCFZpY3RvcmlhMRIwEAYDVQQHDAlN
|
||||
ZWxib3VybmUxJTAjBgNVBAoMHE94ZW4gUHJpdmFjeSBUZWNoIEZvdW5kYXRpb24x
|
||||
HzAdBgNVBAMMFnB1YmxpYy5sb2tpLmZvdW5kYXRpb24wHhcNMjEwNDA3MDExMDMx
|
||||
WhcNMjMwNDA3MDExMDMxWjB8MQswCQYDVQQGEwJBVTERMA8GA1UECAwIVmljdG9y
|
||||
aWExEjAQBgNVBAcMCU1lbGJvdXJuZTElMCMGA1UECgwcT3hlbiBQcml2YWN5IFRl
|
||||
Y2ggRm91bmRhdGlvbjEfMB0GA1UEAwwWcHVibGljLmxva2kuZm91bmRhdGlvbjCC
|
||||
ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAM5dBJSIR5+VNNUxUOo6FG0e
|
||||
RmZteRqBt50KXGbOi2A23a6sa57pLFh9Yw3hmlWV+QCL7ipG1X4IC55OStgoesf+
|
||||
K65VwEMP6Mtq0sSJS3R5TiuV2ZSRdSZTVjUyRXVe5T4Aw6wXVTAbc/HsyS780tDh
|
||||
GclfDHhonPhZpmTAnSbfMOS+BfOnBNvDxdto0kVh6k5nrGlkT4ECloulHTQF2lwJ
|
||||
0D6IOtv9AJplPdg6s2c4dY7durOdvr3NNVfvn5PTeRvbEPqzZur4WUUKIPNGu6mY
|
||||
PxImqd4eUsL0Vod4aAsTIx4YMmCTi0m9W6zJI6nXcK/6a+iiA3+NTNMzEA9gQhEC
|
||||
AwEAAaOBjDCBiTAdBgNVHQ4EFgQU/zahokxLvvFUpbnM6z/pwS1KsvwwHwYDVR0j
|
||||
BBgwFoAU/zahokxLvvFUpbnM6z/pwS1KsvwwDwYDVR0TAQH/BAUwAwEB/zAhBgNV
|
||||
HREEGjAYghZwdWJsaWMubG9raS5mb3VuZGF0aW9uMBMGA1UdJQQMMAoGCCsGAQUF
|
||||
BwMBMA0GCSqGSIb3DQEBCwUAA4IBAQBql+JvoqpaYrFFTOuDn08U+pdcd3GM7tbI
|
||||
zRH5LU+YnIpp9aRheek+2COW8DXsIy/kUngETCMLmX6ZaUj/WdHnTDkB0KTgxSHv
|
||||
ad3ZznKPKZ26qJOklr+0ZWj4J3jHbisSzql6mqq7R2Kp4ESwzwqxvkbykM5RUnmz
|
||||
Go/3Ol7bpN/ZVwwEkGfD/5rRHf57E/gZn2pBO+zotlQgr7HKRsIXQ2hIXVQqWmPQ
|
||||
lvfIwrwAZlfES7BARFnHOpyVQxV8uNcV5K5eXzuVFjHBqvq+BtyGhWkP9yKJCHS9
|
||||
OUXxch0rzRsH2C/kRVVhEk0pI3qlFiRC8pCJs98SNE9l69EQtG7I
|
||||
-----END CERTIFICATE-----
|
|
@ -0,0 +1,24 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIIEDTCCAvWgAwIBAgIUPwyEuBgX6kfxt+G2tQ4GNTZErMMwDQYJKoZIhvcNAQEL
|
||||
BQAwejELMAkGA1UEBhMCQVUxETAPBgNVBAgMCFZpY3RvcmlhMRIwEAYDVQQHDAlN
|
||||
ZWxib3VybmUxJTAjBgNVBAoMHE94ZW4gUHJpdmFjeSBUZWNoIEZvdW5kYXRpb24x
|
||||
HTAbBgNVBAMMFHNlZWQxLmdldHNlc3Npb24ub3JnMB4XDTIzMDQxMjEyNTYyMloX
|
||||
DTI1MDQxMTEyNTYyMlowejELMAkGA1UEBhMCQVUxETAPBgNVBAgMCFZpY3Rvcmlh
|
||||
MRIwEAYDVQQHDAlNZWxib3VybmUxJTAjBgNVBAoMHE94ZW4gUHJpdmFjeSBUZWNo
|
||||
IEZvdW5kYXRpb24xHTAbBgNVBAMMFHNlZWQxLmdldHNlc3Npb24ub3JnMIIBIjAN
|
||||
BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwxkbApgfFA1upIFj47y+7k+qrM0l
|
||||
MLDvtX3U95icVgb7HGhxKzkzbCOscKZnVsq1N90drYVh7to0H69b2t6y7l+9q6Zd
|
||||
Ytzi9U0NoL/OabmR6F+w/XpokRM7CMz9zeg84VLnyu2yRdR26keG4/AZRXk+j8Dy
|
||||
6xp09+hTF7kfdfzL3HdYyUsyx+/CqoyzU01yn4aVgJ9aufYu38QKnnjfROiVahJf
|
||||
Xm1MvHLmDCe+WbDFgsp2Y0NjNbpASUgrOEPNnIJeY3Lw4kzwNVGsbSBHgvLgSfaD
|
||||
p5L6k89TUUKA0onlGFAN/MDXL4DNfjSpmfzHyhM8XwKJ9COSXsvvpX5hHQIDAQAB
|
||||
o4GKMIGHMB0GA1UdDgQWBBRypjuvZ+5vWDB4kcKE9MkFrVp0tzAfBgNVHSMEGDAW
|
||||
gBRypjuvZ+5vWDB4kcKE9MkFrVp0tzAPBgNVHRMBAf8EBTADAQH/MB8GA1UdEQQY
|
||||
MBaCFHNlZWQxLmdldHNlc3Npb24ub3JnMBMGA1UdJQQMMAoGCCsGAQUFBwMBMA0G
|
||||
CSqGSIb3DQEBCwUAA4IBAQBW8q3DzJWVXZew9pJ1MqjqsMuNt2OlnptwIZUme/Lh
|
||||
krhqBj5o87218542ao1Hkgph4IuuwEQPwJvUoUbh7dT/k+4D6Ua3oUxhmdeyFUv+
|
||||
mjQKZ1mfcfrwW+6rCWJRa2mAVYfOhdfBQZgLP7NqYdskVQF5LWXSs1IF3XLTyROy
|
||||
gCeapTexTvKlr/TMW4spE4ewaQ4AfB2c24iVLcpAWT+12GaJ0AYO+gY2o7LQqywN
|
||||
qIxt2mbvXyf2wuhr489tmGz53mKa3Xu7JC1uU6g9zqJ4FGMYsI8pa0Ec2ODRBb8s
|
||||
8W54r5LN472aTYn+UGgV8wadzPFd0FZtQABkDTuWSZY7
|
||||
-----END CERTIFICATE-----
|
|
@ -0,0 +1,24 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIIEDTCCAvWgAwIBAgIUaPiMYcZh7cZZfacCni2NwT5DKh4wDQYJKoZIhvcNAQEL
|
||||
BQAwejELMAkGA1UEBhMCQVUxETAPBgNVBAgMCFZpY3RvcmlhMRIwEAYDVQQHDAlN
|
||||
ZWxib3VybmUxJTAjBgNVBAoMHE94ZW4gUHJpdmFjeSBUZWNoIEZvdW5kYXRpb24x
|
||||
HTAbBgNVBAMMFHNlZWQyLmdldHNlc3Npb24ub3JnMB4XDTIzMDQxMjEyNTY0NVoX
|
||||
DTI1MDQxMTEyNTY0NVowejELMAkGA1UEBhMCQVUxETAPBgNVBAgMCFZpY3Rvcmlh
|
||||
MRIwEAYDVQQHDAlNZWxib3VybmUxJTAjBgNVBAoMHE94ZW4gUHJpdmFjeSBUZWNo
|
||||
IEZvdW5kYXRpb24xHTAbBgNVBAMMFHNlZWQyLmdldHNlc3Npb24ub3JnMIIBIjAN
|
||||
BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAh2UcfW0I+1QWRa3cj7RnMGelYkGK
|
||||
7l4V6q7je1IkudXBNretkvVF1NCpfZ8dz72JmdGPJ5/uIEW15HDD2L63OmSDVPhA
|
||||
2JCb/NqmXfeO91lyxgb0sDnN1UH0wzuS75aBjaQ0nXQV3ffmqKnNNv0HK+LTMFD+
|
||||
Dv2yGDtZTWH6H3VzPLCvHHYXVdyuQHwchAcNQar5k4dbdEIcYIV+ANccPg7iQ81a
|
||||
ITZ9bCeACdMqbB9gILq21KWdkxCu1fwSXs/B6n+U4UpJyv87fprvAyU3HqQhqlU7
|
||||
dHnzA1dPn8D4a/3CMYZogVm8USNjv4HmWIwKbYDX+VahvuZwEi6+pwEurQIDAQAB
|
||||
o4GKMIGHMB0GA1UdDgQWBBRxVM4+gFFipZFAg+Fs4x580js+2TAfBgNVHSMEGDAW
|
||||
gBRxVM4+gFFipZFAg+Fs4x580js+2TAPBgNVHRMBAf8EBTADAQH/MB8GA1UdEQQY
|
||||
MBaCFHNlZWQyLmdldHNlc3Npb24ub3JnMBMGA1UdJQQMMAoGCCsGAQUFBwMBMA0G
|
||||
CSqGSIb3DQEBCwUAA4IBAQBIFj6hsOgNVr2kZufimTxoT1TE8uvycIWyt04q6/nP
|
||||
8h33u/sHuNPdnr2UewqRyDRFefxrGlqBUQAQJVyzJGIlju/HTZaBnVB0H2smCRtK
|
||||
ZRHAJ/cwcnAp+STjqgPqt1ZZ6JcfFwJZID4pPmrW8WaQNAtQPi2Ly2JLQ+Ym5wus
|
||||
aGxGjbDRQSWGmUpg5TE+XdDsHeJtCl6HAEjvtXfq1uzKedRzmqYfIa8Rd7b2tmuy
|
||||
dN27swR4DRJOK4rAxHnI8jt7GKVtPXnYfRuk2+0dVZ4CD6qHw+CO5mcdCabnflgT
|
||||
XS8BYlOvkAyVbtmZNAacoUZvPRx3o186BMJoK2coQyFN
|
||||
-----END CERTIFICATE-----
|
|
@ -0,0 +1,24 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIIEDTCCAvWgAwIBAgIUEZkKsCM3Leodz+JB0ADefbWoRbswDQYJKoZIhvcNAQEL
|
||||
BQAwejELMAkGA1UEBhMCQVUxETAPBgNVBAgMCFZpY3RvcmlhMRIwEAYDVQQHDAlN
|
||||
ZWxib3VybmUxJTAjBgNVBAoMHE94ZW4gUHJpdmFjeSBUZWNoIEZvdW5kYXRpb24x
|
||||
HTAbBgNVBAMMFHNlZWQzLmdldHNlc3Npb24ub3JnMB4XDTIzMDUxNzAyNDAwOFoX
|
||||
DTI1MDQxMjAyNDAwOFowejELMAkGA1UEBhMCQVUxETAPBgNVBAgMCFZpY3Rvcmlh
|
||||
MRIwEAYDVQQHDAlNZWxib3VybmUxJTAjBgNVBAoMHE94ZW4gUHJpdmFjeSBUZWNo
|
||||
IEZvdW5kYXRpb24xHTAbBgNVBAMMFHNlZWQzLmdldHNlc3Npb24ub3JnMIIBIjAN
|
||||
BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAx4Yz/kIXn5t+VMATXsortcyK3DFF
|
||||
hjNICxAt8qdLwyCCJDnedBdfeQb7zrn2A3btzfKrBD0x3JrbVHabUrtI+wFqfDLS
|
||||
id2WOIIM/8RP2V/e4zanpKsk9yB/euKga+M+fybfTn1WTqQU5nEuU6eZyyEEZBk6
|
||||
1rzWJstxWhcfN4rfl+ciSWLcmFLC2LuNZqwm6To77oLPj+DGrUHyRKFZ4Tw9ilcU
|
||||
TpMKFaMmNzrHEzS5lPJIRa+2LD5vDYR/sv+lPiKMXTb64OTOJjTfucdsyZqWrI0R
|
||||
mV2pBcrYBoDbxO+7pnr8GrJIcFqTLDI6MbjH6eseZqRHJSYKrNCyGlDeSQIDAQAB
|
||||
o4GKMIGHMB0GA1UdDgQWBBRUYnrMlCbDZo6YXpnivhBui51XhDAfBgNVHSMEGDAW
|
||||
gBRUYnrMlCbDZo6YXpnivhBui51XhDAPBgNVHRMBAf8EBTADAQH/MB8GA1UdEQQY
|
||||
MBaCFHNlZWQzLmdldHNlc3Npb24ub3JnMBMGA1UdJQQMMAoGCCsGAQUFBwMBMA0G
|
||||
CSqGSIb3DQEBCwUAA4IBAQBFYRlRODyQTIhNQC+pTapKtHdS9GJqKvyJX6NVFF6w
|
||||
+oBzZGNYsDTmzaelraAuUz+uS7d0vngu5cV+3jG0DgksELT6hbpuHcad1rxAhuDv
|
||||
wv/f02qJyB1F2luXma2n+NHgRFhvIYulWjV/DSSmwea2XD4DH+ZKcYeEXyT71b2T
|
||||
VZfGnxLPVMz99iA6sQxsNfccFMvDxKofha7teRkUJ+SVzyutrneYySqrjGie6+Nb
|
||||
oOw4CnpiqiUKIf47B6ZKlsJ8MAS8zAo6O9UqfmNdVoXFrZDjaQGPAjSH1oxL7iP5
|
||||
pED6BUMytm8spiTEVBYIer/gcXaA4zWSKZ/Fd24OK0GL
|
||||
-----END CERTIFICATE-----
|
|
@ -1,25 +0,0 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIIEITCCAwmgAwIBAgIUJsox1ZQPK/6iDsCC+MUJfNAlFuYwDQYJKoZIhvcNAQEL
|
||||
BQAwgYAxCzAJBgNVBAYTAkFVMREwDwYDVQQIDAhWaWN0b3JpYTESMBAGA1UEBwwJ
|
||||
TWVsYm91cm5lMSUwIwYDVQQKDBxPeGVuIFByaXZhY3kgVGVjaCBGb3VuZGF0aW9u
|
||||
MSMwIQYDVQQDDBpzdG9yYWdlLnNlZWQxLmxva2kubmV0d29yazAeFw0yMTA0MDcw
|
||||
MTE5MjZaFw0yMzA0MDcwMTE5MjZaMIGAMQswCQYDVQQGEwJBVTERMA8GA1UECAwI
|
||||
VmljdG9yaWExEjAQBgNVBAcMCU1lbGJvdXJuZTElMCMGA1UECgwcT3hlbiBQcml2
|
||||
YWN5IFRlY2ggRm91bmRhdGlvbjEjMCEGA1UEAwwac3RvcmFnZS5zZWVkMS5sb2tp
|
||||
Lm5ldHdvcmswggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCtWH3Rz8Dd
|
||||
kEmM7tcBWHrJ/G8drr/+qidboEVYzxpyRjszaDxKXVhx4eBBsAD5RuCWuTuZmM8k
|
||||
TKEDLtf8xfb5SQ7YNX+346s9NXS5Poy4CIPASiW/QWXgIHFbVdv2hC+cKOP61OLM
|
||||
OGnOxfig6tQyd6EaCkedpY1DvSa2lPnQSOwC/jXCx6Vboc0zTY5R2bHtNc9hjIFP
|
||||
F4VClLAQSh2F4R1V9MH5KZMW+CCP6oaJY658W9JYXYRwlLrL2EFOVxHgcxq/6+fw
|
||||
+axXK9OXJrGZjuA+hiz+L/uAOtE4WuxrSeuNMHSrMtM9QqVn4bBuMJ21mAzfNoMP
|
||||
OIwgMT9DwUjVAgMBAAGjgZAwgY0wHQYDVR0OBBYEFOubJp9SoXIw+ONiWgkOaW8K
|
||||
zI/TMB8GA1UdIwQYMBaAFOubJp9SoXIw+ONiWgkOaW8KzI/TMA8GA1UdEwEB/wQF
|
||||
MAMBAf8wJQYDVR0RBB4wHIIac3RvcmFnZS5zZWVkMS5sb2tpLm5ldHdvcmswEwYD
|
||||
VR0lBAwwCgYIKwYBBQUHAwEwDQYJKoZIhvcNAQELBQADggEBAIiHNhNrjYvwXVWs
|
||||
gacx8T/dpqpu9GE3L17LotgQr4R+IYHpNtcmwOTdtWWFfUTr75OCs+c3DqgRKEoj
|
||||
lnULOsVcalpAGIvW15/fmZWOf66Dpa4+ljDmAc3SOQiD0gGNtqblgI5zG1HF38QP
|
||||
hjYRhCZ5CVeGOLucvQ8tVVwQvArPFIkBr0jH9jHVgRWEI2MeI3FsU2H93D4TfGln
|
||||
N4SmmCfYBqygaaZBWkJEt0bYhn8uGHdU9UY9L2FPtfHVKkmFgO7cASGlvXS7B/TT
|
||||
/8IgbtM3O8mZc2asmdQhGwoAKz93ryyCd8X2UZJg/IwCSCayOlYZWY2fR4OPQmmV
|
||||
gxJsm+g=
|
||||
-----END CERTIFICATE-----
|
|
@ -1,25 +0,0 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIIEITCCAwmgAwIBAgIUc486Dy9Y00bUFfDeYmJIgSS5xREwDQYJKoZIhvcNAQEL
|
||||
BQAwgYAxCzAJBgNVBAYTAkFVMREwDwYDVQQIDAhWaWN0b3JpYTESMBAGA1UEBwwJ
|
||||
TWVsYm91cm5lMSUwIwYDVQQKDBxPeGVuIFByaXZhY3kgVGVjaCBGb3VuZGF0aW9u
|
||||
MSMwIQYDVQQDDBpzdG9yYWdlLnNlZWQzLmxva2kubmV0d29yazAeFw0yMTA0MDcw
|
||||
MTIwNTJaFw0yMzA0MDcwMTIwNTJaMIGAMQswCQYDVQQGEwJBVTERMA8GA1UECAwI
|
||||
VmljdG9yaWExEjAQBgNVBAcMCU1lbGJvdXJuZTElMCMGA1UECgwcT3hlbiBQcml2
|
||||
YWN5IFRlY2ggRm91bmRhdGlvbjEjMCEGA1UEAwwac3RvcmFnZS5zZWVkMy5sb2tp
|
||||
Lm5ldHdvcmswggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCtokMlsFzf
|
||||
piYeD0EVNikMyvjltpF6fUEde9NOVrTtNTQT6kkDk+/0HF5LYgPaatv6v7fpUQHi
|
||||
kIwd6F0LTRGeWDFdsaWMdtlR1n/GxLPrOROsE8dcLt6GLavPf9rDabgva93m/JD6
|
||||
XW+Ne+MPEwqS8dAmFGhZd0gju6AtKFoSHnIf5pSQN6fSZUF/JQtHLVprAKKWKDiS
|
||||
ZwmWbmrZR2aofLD/VRpetabajnZlv9EeWloQwvUsw1C1hkAmmtFeeXtg7ePwrOzo
|
||||
6CnmcUJwOmi+LWqQV4A+58RZPFKaZoC5pzaKd0OYB8eZ8HB1F41UjGJgheX5Cyl4
|
||||
+amfF3l8dSq1AgMBAAGjgZAwgY0wHQYDVR0OBBYEFM9VSq4pGydjtX92Beul4+ml
|
||||
jBKtMB8GA1UdIwQYMBaAFM9VSq4pGydjtX92Beul4+mljBKtMA8GA1UdEwEB/wQF
|
||||
MAMBAf8wJQYDVR0RBB4wHIIac3RvcmFnZS5zZWVkMy5sb2tpLm5ldHdvcmswEwYD
|
||||
VR0lBAwwCgYIKwYBBQUHAwEwDQYJKoZIhvcNAQELBQADggEBAAYxmhhkcKE1n6g1
|
||||
JqOa3UCBo4EfbqY5+FDZ0FVqv/cwemwVpKLbe6luRIS8poomdPCyMOS45V7wN3H9
|
||||
cFpfJ1TW19ydPVKmCXrl29ngmnY1q7YDwE/4qi3VK/UiqDkTHMKWjVPkenOyi8u6
|
||||
VVQANXSnKrn6GtigNFjGyD38O+j7AUSXBtXOJczaoF6r6BWgwQZ2WmgjuwvKTWSN
|
||||
4r8uObERoAQYVaeXfgdr4e9X/JdskBDaLFfoW/rrSozHB4FqVNFW96k+aIUgRa5p
|
||||
9kv115QcBPCSh9qOyTHij4tswS6SyOFaiKrNC4hgHQXP4QgioKmtsR/2Y+qJ6ddH
|
||||
6oo+4QU=
|
||||
-----END CERTIFICATE-----
|
Before Width: | Height: | Size: 318 B After Width: | Height: | Size: 369 B |
Before Width: | Height: | Size: 573 B After Width: | Height: | Size: 628 B |
Before Width: | Height: | Size: 959 B After Width: | Height: | Size: 893 B |
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "profile_placeholder.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "profile_placeholder@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "profile_placeholder@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
BIN
Session/Meta/Images.xcassets/profile_placeholder.imageset/profile_placeholder.png
vendored
Normal file
After Width: | Height: | Size: 2.1 KiB |
BIN
Session/Meta/Images.xcassets/profile_placeholder.imageset/profile_placeholder@2x.png
vendored
Normal file
After Width: | Height: | Size: 3.9 KiB |
BIN
Session/Meta/Images.xcassets/profile_placeholder.imageset/profile_placeholder@3x.png
vendored
Normal file
After Width: | Height: | Size: 5.8 KiB |
|
@ -66,17 +66,17 @@
|
|||
<dict>
|
||||
<key>NSExceptionDomains</key>
|
||||
<dict>
|
||||
<key>public.loki.foundation</key>
|
||||
<key>seed1.getsession.org</key>
|
||||
<dict>
|
||||
<key>NSExceptionRequiresForwardSecrecy</key>
|
||||
<false/>
|
||||
</dict>
|
||||
<key>storage.seed1.loki.network</key>
|
||||
<key>seed2.getsession.org</key>
|
||||
<dict>
|
||||
<key>NSExceptionRequiresForwardSecrecy</key>
|
||||
<false/>
|
||||
</dict>
|
||||
<key>storage.seed3.loki.network</key>
|
||||
<key>seed3.getsession.org</key>
|
||||
<dict>
|
||||
<key>NSExceptionRequiresForwardSecrecy</key>
|
||||
<false/>
|
||||
|
@ -84,21 +84,19 @@
|
|||
</dict>
|
||||
</dict>
|
||||
<key>NSAppleMusicUsageDescription</key>
|
||||
<string>Signal needs to use Apple Music to play media attachments.</string>
|
||||
<string>Session needs to use Apple Music to play media attachments.</string>
|
||||
<key>NSCameraUsageDescription</key>
|
||||
<string>Session needs camera access to take pictures and scan QR codes.</string>
|
||||
<key>NSContactsUsageDescription</key>
|
||||
<string>Signal uses your contacts to find users you know. We do not store your contacts on the server.</string>
|
||||
<key>NSFaceIDUsageDescription</key>
|
||||
<string>Session's Screen Lock feature uses Face ID.</string>
|
||||
<key>NSHumanReadableCopyright</key>
|
||||
<string>com.loki-project.loki-messenger</string>
|
||||
<key>NSMicrophoneUsageDescription</key>
|
||||
<string>Session needs access to your microphone to record media.</string>
|
||||
<string>Session needs access to your microphone for calls and to send to audio messages.</string>
|
||||
<key>NSPhotoLibraryAddUsageDescription</key>
|
||||
<string>Session needs access to your library to save photos.</string>
|
||||
<key>NSPhotoLibraryUsageDescription</key>
|
||||
<string>Session needs access to your library to send photos.</string>
|
||||
<string>Session needs access to your library to update your avatar and send photos.</string>
|
||||
<key>PHPhotoLibraryPreventAutomaticLimitedAccessAlert</key>
|
||||
<true/>
|
||||
<key>UIAppFonts</key>
|
||||
|
|
|
@ -4,19 +4,6 @@
|
|||
<dict>
|
||||
<key>aps-environment</key>
|
||||
<string>production</string>
|
||||
<key>com.apple.developer.icloud-container-identifiers</key>
|
||||
<array>
|
||||
<string>iCloud.$(CFBundleIdentifier)</string>
|
||||
</array>
|
||||
<key>com.apple.developer.icloud-services</key>
|
||||
<array>
|
||||
<string>CloudDocuments</string>
|
||||
<string>CloudKit</string>
|
||||
</array>
|
||||
<key>com.apple.developer.ubiquity-container-identifiers</key>
|
||||
<array>
|
||||
<string>iCloud.$(CFBundleIdentifier)</string>
|
||||
</array>
|
||||
<key>com.apple.security.application-groups</key>
|
||||
<array>
|
||||
<string>group.com.loki-project.loki-messenger</string>
|
||||
|
|
|
@ -366,6 +366,7 @@
|
|||
"delete_message_for_me" = "Nur für mich löschen";
|
||||
"delete_message_for_everyone" = "Für jeden löschen";
|
||||
"delete_message_for_me_and_recipient" = "Für mich und %@ löschen";
|
||||
"context_menu_info" = "Info";
|
||||
"context_menu_reply" = "Antworten";
|
||||
"context_menu_save" = "Speichern";
|
||||
"context_menu_ban_user" = "Nutzer sperren";
|
||||
|
@ -592,6 +593,14 @@
|
|||
"MESSAGE_DELIVERY_STATUS_SENT" = "Sent";
|
||||
"MESSAGE_DELIVERY_STATUS_READ" = "Read";
|
||||
"MESSAGE_DELIVERY_STATUS_FAILED" = "Failed to send";
|
||||
"MESSAGE_INFO_SENT" = "Sent";
|
||||
"MESSAGE_INFO_RECEIVED" = "Received";
|
||||
"MESSAGE_INFO_FROM" = "From";
|
||||
"ATTACHMENT_INFO_FILE_ID" = "File ID";
|
||||
"ATTACHMENT_INFO_FILE_TYPE" = "File Type";
|
||||
"ATTACHMENT_INFO_FILE_SIZE" = "File Size";
|
||||
"ATTACHMENT_INFO_RESOLUTION" = "Resolution";
|
||||
"ATTACHMENT_INFO_DURATION" = "Duration";
|
||||
"MESSAGE_DELIVERY_STATUS_FAILED_SYNC" = "Failed to sync";
|
||||
"MESSAGE_DELIVERY_STATUS_SYNCING" = "Syncing";
|
||||
"MESSAGE_DELIVERY_FAILED_TITLE" = "Failed to send message";
|
||||
|
@ -601,6 +610,7 @@
|
|||
"context_menu_resync" = "Resync";
|
||||
"GIPHY_PERMISSION_TITLE" = "Search GIFs?";
|
||||
"GIPHY_PERMISSION_MESSAGE" = "Session will connect to Giphy to provide search results. You will not have full metadata protection when sending GIFs.";
|
||||
"message_info_title" = "Message Info";
|
||||
"mute_button_text" = "Mute";
|
||||
"unmute_button_text" = "Unmute";
|
||||
"mark_read_button_text" = "Mark read";
|
||||
|
@ -613,3 +623,6 @@
|
|||
"group_unable_to_leave" = "Unable to leave the Group, please try again";
|
||||
"delete_conversation_confirmation_alert_message" = "Are you sure you want to delete your conversation with %@?";
|
||||
"delete_conversation_confirmation_alert_title" = "Delete Conversation";
|
||||
"update_profile_modal_title" = "Set Display Picture";
|
||||
"update_profile_modal_upload" = "Upload";
|
||||
"update_profile_modal_remove" = "Remove";
|
||||
|
|
|
@ -366,6 +366,7 @@
|
|||
"delete_message_for_me" = "Delete just for me";
|
||||
"delete_message_for_everyone" = "Delete for everyone";
|
||||
"delete_message_for_me_and_recipient" = "Delete for me and %@";
|
||||
"context_menu_info" = "Info";
|
||||
"context_menu_reply" = "Reply";
|
||||
"context_menu_save" = "Save";
|
||||
"context_menu_ban_user" = "Ban User";
|
||||
|
@ -592,6 +593,14 @@
|
|||
"MESSAGE_DELIVERY_STATUS_SENT" = "Sent";
|
||||
"MESSAGE_DELIVERY_STATUS_READ" = "Read";
|
||||
"MESSAGE_DELIVERY_STATUS_FAILED" = "Failed to send";
|
||||
"MESSAGE_INFO_SENT" = "Sent";
|
||||
"MESSAGE_INFO_RECEIVED" = "Received";
|
||||
"MESSAGE_INFO_FROM" = "From";
|
||||
"ATTACHMENT_INFO_FILE_ID" = "File ID";
|
||||
"ATTACHMENT_INFO_FILE_TYPE" = "File Type";
|
||||
"ATTACHMENT_INFO_FILE_SIZE" = "File Size";
|
||||
"ATTACHMENT_INFO_RESOLUTION" = "Resolution";
|
||||
"ATTACHMENT_INFO_DURATION" = "Duration";
|
||||
"MESSAGE_DELIVERY_STATUS_FAILED_SYNC" = "Failed to sync";
|
||||
"MESSAGE_DELIVERY_STATUS_SYNCING" = "Syncing";
|
||||
"MESSAGE_DELIVERY_FAILED_TITLE" = "Failed to send message";
|
||||
|
@ -601,6 +610,7 @@
|
|||
"context_menu_resync" = "Resync";
|
||||
"GIPHY_PERMISSION_TITLE" = "Search GIFs?";
|
||||
"GIPHY_PERMISSION_MESSAGE" = "Session will connect to Giphy to provide search results. You will not have full metadata protection when sending GIFs.";
|
||||
"message_info_title" = "Message Info";
|
||||
"mute_button_text" = "Mute";
|
||||
"unmute_button_text" = "Unmute";
|
||||
"mark_read_button_text" = "Mark read";
|
||||
|
@ -613,3 +623,6 @@
|
|||
"group_unable_to_leave" = "Unable to leave the Group, please try again";
|
||||
"delete_conversation_confirmation_alert_message" = "Are you sure you want to delete your conversation with %@?";
|
||||
"delete_conversation_confirmation_alert_title" = "Delete Conversation";
|
||||
"update_profile_modal_title" = "Set Display Picture";
|
||||
"update_profile_modal_upload" = "Upload";
|
||||
"update_profile_modal_remove" = "Remove";
|
||||
|
|
|
@ -366,6 +366,7 @@
|
|||
"delete_message_for_me" = "Eliminar solo para mí";
|
||||
"delete_message_for_everyone" = "Eliminar para todos";
|
||||
"delete_message_for_me_and_recipient" = "Eliminar para mí y para %@";
|
||||
"context_menu_info" = "Info";
|
||||
"context_menu_reply" = "Responder";
|
||||
"context_menu_save" = "Guardar";
|
||||
"context_menu_ban_user" = "Banear Usuario";
|
||||
|
@ -592,6 +593,14 @@
|
|||
"MESSAGE_DELIVERY_STATUS_SENT" = "Sent";
|
||||
"MESSAGE_DELIVERY_STATUS_READ" = "Read";
|
||||
"MESSAGE_DELIVERY_STATUS_FAILED" = "Failed to send";
|
||||
"MESSAGE_INFO_SENT" = "Sent";
|
||||
"MESSAGE_INFO_RECEIVED" = "Received";
|
||||
"MESSAGE_INFO_FROM" = "From";
|
||||
"ATTACHMENT_INFO_FILE_ID" = "File ID";
|
||||
"ATTACHMENT_INFO_FILE_TYPE" = "File Type";
|
||||
"ATTACHMENT_INFO_FILE_SIZE" = "File Size";
|
||||
"ATTACHMENT_INFO_RESOLUTION" = "Resolution";
|
||||
"ATTACHMENT_INFO_DURATION" = "Duration";
|
||||
"MESSAGE_DELIVERY_STATUS_FAILED_SYNC" = "Failed to sync";
|
||||
"MESSAGE_DELIVERY_STATUS_SYNCING" = "Syncing";
|
||||
"MESSAGE_DELIVERY_FAILED_TITLE" = "Failed to send message";
|
||||
|
@ -601,6 +610,7 @@
|
|||
"context_menu_resync" = "Resync";
|
||||
"GIPHY_PERMISSION_TITLE" = "Search GIFs?";
|
||||
"GIPHY_PERMISSION_MESSAGE" = "Session will connect to Giphy to provide search results. You will not have full metadata protection when sending GIFs.";
|
||||
"message_info_title" = "Message Info";
|
||||
"mute_button_text" = "Mute";
|
||||
"unmute_button_text" = "Unmute";
|
||||
"mark_read_button_text" = "Mark read";
|
||||
|
@ -613,3 +623,6 @@
|
|||
"group_unable_to_leave" = "Unable to leave the Group, please try again";
|
||||
"delete_conversation_confirmation_alert_message" = "Are you sure you want to delete your conversation with %@?";
|
||||
"delete_conversation_confirmation_alert_title" = "Delete Conversation";
|
||||
"update_profile_modal_title" = "Set Display Picture";
|
||||
"update_profile_modal_upload" = "Upload";
|
||||
"update_profile_modal_remove" = "Remove";
|
||||
|
|
|
@ -366,6 +366,7 @@
|
|||
"delete_message_for_me" = "حذف برای من";
|
||||
"delete_message_for_everyone" = "حذف برای همه";
|
||||
"delete_message_for_me_and_recipient" = "حذف برای من و %@";
|
||||
"context_menu_info" = "Info";
|
||||
"context_menu_reply" = "پاسخ";
|
||||
"context_menu_save" = "ذخیره";
|
||||
"context_menu_ban_user" = "مسدود کردن کاربر";
|
||||
|
@ -592,6 +593,14 @@
|
|||
"MESSAGE_DELIVERY_STATUS_SENT" = "Sent";
|
||||
"MESSAGE_DELIVERY_STATUS_READ" = "Read";
|
||||
"MESSAGE_DELIVERY_STATUS_FAILED" = "Failed to send";
|
||||
"MESSAGE_INFO_SENT" = "Sent";
|
||||
"MESSAGE_INFO_RECEIVED" = "Received";
|
||||
"MESSAGE_INFO_FROM" = "From";
|
||||
"ATTACHMENT_INFO_FILE_ID" = "File ID";
|
||||
"ATTACHMENT_INFO_FILE_TYPE" = "File Type";
|
||||
"ATTACHMENT_INFO_FILE_SIZE" = "File Size";
|
||||
"ATTACHMENT_INFO_RESOLUTION" = "Resolution";
|
||||
"ATTACHMENT_INFO_DURATION" = "Duration";
|
||||
"MESSAGE_DELIVERY_STATUS_FAILED_SYNC" = "Failed to sync";
|
||||
"MESSAGE_DELIVERY_STATUS_SYNCING" = "Syncing";
|
||||
"MESSAGE_DELIVERY_FAILED_TITLE" = "Failed to send message";
|
||||
|
@ -601,6 +610,7 @@
|
|||
"context_menu_resync" = "Resync";
|
||||
"GIPHY_PERMISSION_TITLE" = "Search GIFs?";
|
||||
"GIPHY_PERMISSION_MESSAGE" = "Session will connect to Giphy to provide search results. You will not have full metadata protection when sending GIFs.";
|
||||
"message_info_title" = "Message Info";
|
||||
"mute_button_text" = "Mute";
|
||||
"unmute_button_text" = "Unmute";
|
||||
"mark_read_button_text" = "Mark read";
|
||||
|
@ -613,3 +623,6 @@
|
|||
"group_unable_to_leave" = "Unable to leave the Group, please try again";
|
||||
"delete_conversation_confirmation_alert_message" = "Are you sure you want to delete your conversation with %@?";
|
||||
"delete_conversation_confirmation_alert_title" = "Delete Conversation";
|
||||
"update_profile_modal_title" = "Set Display Picture";
|
||||
"update_profile_modal_upload" = "Upload";
|
||||
"update_profile_modal_remove" = "Remove";
|
||||
|
|
|
@ -366,6 +366,7 @@
|
|||
"delete_message_for_me" = "Poista vain minun nähtäväksi";
|
||||
"delete_message_for_everyone" = "Poista kaikkien näkyviltä";
|
||||
"delete_message_for_me_and_recipient" = "Poista minulta ja vastaanottajalta";
|
||||
"context_menu_info" = "Info";
|
||||
"context_menu_reply" = "Vastaa";
|
||||
"context_menu_save" = "Tallenna";
|
||||
"context_menu_ban_user" = "Estä Käyttäjä";
|
||||
|
@ -592,6 +593,14 @@
|
|||
"MESSAGE_DELIVERY_STATUS_SENT" = "Sent";
|
||||
"MESSAGE_DELIVERY_STATUS_READ" = "Read";
|
||||
"MESSAGE_DELIVERY_STATUS_FAILED" = "Failed to send";
|
||||
"MESSAGE_INFO_SENT" = "Sent";
|
||||
"MESSAGE_INFO_RECEIVED" = "Received";
|
||||
"MESSAGE_INFO_FROM" = "From";
|
||||
"ATTACHMENT_INFO_FILE_ID" = "File ID";
|
||||
"ATTACHMENT_INFO_FILE_TYPE" = "File Type";
|
||||
"ATTACHMENT_INFO_FILE_SIZE" = "File Size";
|
||||
"ATTACHMENT_INFO_RESOLUTION" = "Resolution";
|
||||
"ATTACHMENT_INFO_DURATION" = "Duration";
|
||||
"MESSAGE_DELIVERY_STATUS_FAILED_SYNC" = "Failed to sync";
|
||||
"MESSAGE_DELIVERY_STATUS_SYNCING" = "Syncing";
|
||||
"MESSAGE_DELIVERY_FAILED_TITLE" = "Failed to send message";
|
||||
|
@ -601,6 +610,7 @@
|
|||
"context_menu_resync" = "Resync";
|
||||
"GIPHY_PERMISSION_TITLE" = "Search GIFs?";
|
||||
"GIPHY_PERMISSION_MESSAGE" = "Session will connect to Giphy to provide search results. You will not have full metadata protection when sending GIFs.";
|
||||
"message_info_title" = "Message Info";
|
||||
"mute_button_text" = "Mute";
|
||||
"unmute_button_text" = "Unmute";
|
||||
"mark_read_button_text" = "Mark read";
|
||||
|
@ -613,3 +623,6 @@
|
|||
"group_unable_to_leave" = "Unable to leave the Group, please try again";
|
||||
"delete_conversation_confirmation_alert_message" = "Are you sure you want to delete your conversation with %@?";
|
||||
"delete_conversation_confirmation_alert_title" = "Delete Conversation";
|
||||
"update_profile_modal_title" = "Set Display Picture";
|
||||
"update_profile_modal_upload" = "Upload";
|
||||
"update_profile_modal_remove" = "Remove";
|
||||
|
|
|
@ -61,7 +61,7 @@
|
|||
/* Button text to enable batch selection mode */
|
||||
"BUTTON_SELECT" = "Sélectionner";
|
||||
/* keyboard toolbar label when starting to search with no current results */
|
||||
"CONVERSATION_SEARCH_SEARCHING" = "Searching...";
|
||||
"CONVERSATION_SEARCH_SEARCHING" = "Recherche...";
|
||||
/* keyboard toolbar label when no messages match the search string */
|
||||
"CONVERSATION_SEARCH_NO_RESULTS" = "Aucune correspondance";
|
||||
/* keyboard toolbar label when exactly 1 message matches the search string */
|
||||
|
@ -289,7 +289,7 @@
|
|||
"vc_create_private_chat_title" = "Nouvelle Session";
|
||||
"vc_create_private_chat_enter_session_id_tab_title" = "Saisir un Session ID";
|
||||
"vc_create_private_chat_scan_qr_code_tab_title" = "Scanner un Code QR";
|
||||
"vc_enter_public_key_explanation" = "Start a new conversation by entering someone's Session ID or share your Session ID with them.";
|
||||
"vc_enter_public_key_explanation" = "Démarrez une nouvelle conversation en saisissant l'ID Session de quelqu'un ou en partageant votre ID Session avec eux.";
|
||||
"vc_scan_qr_code_camera_access_explanation" = "Session a besoin d'accéder à l'appareil photo pour scanner les codes QR";
|
||||
"vc_scan_qr_code_grant_camera_access_button_title" = "Autoriser l'accès";
|
||||
"vc_create_closed_group_title" = "Nouveau groupe privé";
|
||||
|
@ -304,10 +304,10 @@
|
|||
"vc_join_public_chat_scan_qr_code_tab_title" = "Scannez le code QR";
|
||||
"vc_enter_chat_url_text_field_hint" = "Saisissez une URL de groupe public";
|
||||
"vc_settings_title" = "Paramètres";
|
||||
"vc_group_settings_title" = "Group Settings";
|
||||
"vc_group_settings_title" = "Paramètres de Groupe";
|
||||
"vc_settings_display_name_missing_error" = "Veuillez choisir un nom d'utilisateur";
|
||||
"vc_settings_display_name_too_long_error" = "Veuillez choisir un nom d'utilisateur plus court";
|
||||
"vc_settings_privacy_button_title" = "Confidientalité ";
|
||||
"vc_settings_privacy_button_title" = "Confidentialité ";
|
||||
"vc_settings_notifications_button_title" = "Notifications";
|
||||
"vc_settings_recovery_phrase_button_title" = "Phrase de récupération";
|
||||
"vc_settings_clear_all_data_button_title" = "Effacer les données";
|
||||
|
@ -319,13 +319,13 @@
|
|||
// MARK: - Not Yet Translated
|
||||
"fast_mode_explanation" = "Vous serez notifiés de nouveaux messages de manière certaine et immédiate en utilisant les serveurs de notification d’Apple.";
|
||||
"fast_mode" = "Mode rapide";
|
||||
"slow_mode_explanation" = "Session vérifiera occasionnellement la présence de nouveaux message en tâche de fond.";
|
||||
"slow_mode_explanation" = "Session vérifiera occasionnellement la présence de nouveaux messages en tâche de fond.";
|
||||
"slow_mode" = "Mode lent";
|
||||
"vc_pn_mode_title" = "Notifications de message";
|
||||
"vc_link_device_recovery_phrase_tab_title" = "Phrase de récupération";
|
||||
"vc_link_device_scan_qr_code_explanation" = "Allez dans paramètre → Phrase de récupération sur votre autre appareil pour afficher votre QR Code.";
|
||||
"vc_enter_recovery_phrase_title" = "Phrase de récupération";
|
||||
"vc_enter_recovery_phrase_explanation" = "Pour lier votre appareil, entrez la phrase de récupération qui vous a été donné lors de la création du compte.";
|
||||
"vc_enter_recovery_phrase_explanation" = "Pour lier votre appareil, entrez la phrase de récupération qui vous a été donnée lors de la création du compte.";
|
||||
"vc_enter_public_key_text_field_hint" = "Entrez un ID Session ou un nom ONS";
|
||||
"admin_group_leave_warning" = "Puisque vous êtes le créateur de ce groupe, il sera supprimé pour tout le monde. Ceci ne peut pas être annulé.";
|
||||
"vc_join_open_group_suggestions_title" = "Ou rejoignez un de ceux-ci...";
|
||||
|
@ -360,17 +360,18 @@
|
|||
"modal_send_seed_explanation" = "Voici votre phrase de récupération. Si vous l'envoyez à quelqu'un, cette personne aura un accès complet à votre compte.";
|
||||
"modal_send_seed_send_button_title" = "Envoyer";
|
||||
"vc_conversation_settings_notify_for_mentions_only_title" = "Activer les notifications que sur mention";
|
||||
"vc_conversation_settings_notify_for_mentions_only_explanation" = "Quand activer, vous recevrez les notifications d’uniquement les messages vous notifiant.";
|
||||
"vc_conversation_settings_notify_for_mentions_only_explanation" = "Quand activé, vous recevrez uniquement les notifications des messages vous mentionnant.";
|
||||
"view_conversation_title_notify_for_mentions_only" = "Me notifier que si je suis mentionné(e)";
|
||||
"message_deleted" = "Ce message a été supprimé";
|
||||
"delete_message_for_me" = "Supprimer pour moi uniquement";
|
||||
"delete_message_for_everyone" = "Supprimer pour tout le monde";
|
||||
"delete_message_for_me_and_recipient" = "Supprimer pour moi et %@";
|
||||
"context_menu_info" = "Info";
|
||||
"context_menu_reply" = "Répondre";
|
||||
"context_menu_save" = "Enregistrer";
|
||||
"context_menu_ban_user" = "Bannir l'utilisateur";
|
||||
"context_menu_ban_and_delete_all" = "Bannir et supprimer tout";
|
||||
"context_menu_ban_user_error_alert_message" = "Unable to ban user";
|
||||
"context_menu_ban_user_error_alert_message" = "Impossible de bannir l'utilisateur";
|
||||
"accessibility_expanding_attachments_button" = "Ajouter une pièce jointe";
|
||||
"accessibility_gif_button" = "Gif";
|
||||
"accessibility_document_button" = "Document";
|
||||
|
@ -400,28 +401,28 @@
|
|||
"MESSAGE_REQUESTS_CLEAR_ALL_CONFIRMATION_TITLE" = "Êtes-vous sûr de vouloir supprimer toutes les demandes de messages ?";
|
||||
"MESSAGE_REQUESTS_CLEAR_ALL_CONFIRMATION_ACTON" = "Effacer";
|
||||
"MESSAGE_REQUESTS_DELETE_CONFIRMATION_ACTON" = "Êtes-vous sûr de vouloir supprimer cette demande de message ?";
|
||||
"MESSAGE_REQUESTS_BLOCK_CONFIRMATION_ACTON" = "Are you sure you want to block this contact?";
|
||||
"MESSAGE_REQUESTS_BLOCK_CONFIRMATION_ACTON" = "Êtes-vous sûr de vouloir bloquer ce contact ?";
|
||||
"MESSAGE_REQUESTS_INFO" = "Envoyer un message à cet utilisateur acceptera automatiquement sa demande de message.";
|
||||
"MESSAGE_REQUESTS_ACCEPTED" = "Votre demande de message a été réceptionnée.";
|
||||
"MESSAGE_REQUESTS_NOTIFICATION" = "Vous avez une nouvelle demande de message";
|
||||
"TXT_HIDE_TITLE" = "Masquer";
|
||||
"TXT_DELETE_ACCEPT" = "Accepter";
|
||||
"TXT_DECLINE_TITLE" = "Decline";
|
||||
"TXT_BLOCK_USER_TITLE" = "Block User";
|
||||
"TXT_DECLINE_TITLE" = "Refuser";
|
||||
"TXT_BLOCK_USER_TITLE" = "Bloquer Utilisateur";
|
||||
"ALERT_ERROR_TITLE" = "Erreur";
|
||||
"modal_call_permission_request_title" = "Autorisations d'appel requises";
|
||||
"modal_call_permission_request_title" = "Autorisation d'appel requise";
|
||||
"modal_call_permission_request_explanation" = "Vous pouvez activer la permission \"Appels vocaux et vidéo\" dans les paramètres de confidentialité.";
|
||||
"DEFAULT_OPEN_GROUP_LOAD_ERROR_TITLE" = "Oops, an error occurred";
|
||||
"DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later";
|
||||
"LOADING_CONVERSATIONS" = "Loading Conversations...";
|
||||
"DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks";
|
||||
"RECOVERY_PHASE_ERROR_GENERIC" = "Something went wrong. Please check your recovery phrase and try again.";
|
||||
"RECOVERY_PHASE_ERROR_LENGTH" = "Looks like you didn't enter enough words. Please check your recovery phrase and try again.";
|
||||
"RECOVERY_PHASE_ERROR_LAST_WORD" = "You seem to be missing the last word of your recovery phrase. Please check what you entered and try again.";
|
||||
"RECOVERY_PHASE_ERROR_INVALID_WORD" = "There appears to be an invalid word in your recovery phrase. Please check what you entered and try again.";
|
||||
"RECOVERY_PHASE_ERROR_FAILED" = "Your recovery phrase couldn't be verified. Please check what you entered and try again.";
|
||||
"DEFAULT_OPEN_GROUP_LOAD_ERROR_TITLE" = "Oups, une erreur est survenue";
|
||||
"DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Veuillez réessayer plus tard";
|
||||
"LOADING_CONVERSATIONS" = "Chargement des conversations...";
|
||||
"DATABASE_MIGRATION_FAILED" = "Une erreur est survenue pendant l'optimisation de la base de données\n\nVous pouvez exporter votre journal d'application pour le partager et aider à régler le problème ou vous pouvez restaurer votre appareil\n\nAttention : restaurer votre appareil résultera en une perte des données des deux dernières semaines";
|
||||
"RECOVERY_PHASE_ERROR_GENERIC" = "Quelque chose s'est mal passé. Vérifiez votre phrase de récupération et réessayez s'il vous plaît.";
|
||||
"RECOVERY_PHASE_ERROR_LENGTH" = "Il semble que vous n'avez pas saisi tous les mots. Vérifiez votre phrase de récupération et réessayez s'il vous plaît.";
|
||||
"RECOVERY_PHASE_ERROR_LAST_WORD" = "Il semble qu'il vous manque le dernier mot de votre phrase de récupération. Vérifiez votre saisie et réessayez s'il vous plaît.";
|
||||
"RECOVERY_PHASE_ERROR_INVALID_WORD" = "Il semble qu'il y a un mot invalide dans votre phrase de récupération. Vérifiez votre saisie et réessayez s'il vous plaît.";
|
||||
"RECOVERY_PHASE_ERROR_FAILED" = "Votre phrase de récupération n'a pas pu être validée. Vérifiez votre saisie et réessayez s'il vous plaît.";
|
||||
/* Indicates that an unknown error occurred while using Touch ID/Face ID/Phone Passcode. */
|
||||
"SCREEN_LOCK_ENABLE_UNKNOWN_ERROR" = "Authentication could not be accessed.";
|
||||
"SCREEN_LOCK_ENABLE_UNKNOWN_ERROR" = "L'authentification a échoué.";
|
||||
/* Indicates that Touch ID/Face ID/Phone Passcode authentication failed. */
|
||||
"SCREEN_LOCK_ERROR_LOCAL_AUTHENTICATION_FAILED" = "Échec d’authentification";
|
||||
/* Indicates that Touch ID/Face ID/Phone Passcode is 'locked out' on this device due to authentication failures. */
|
||||
|
@ -433,183 +434,195 @@
|
|||
/* Indicates that Touch ID/Face ID/Phone Passcode passcode is not set. */
|
||||
"SCREEN_LOCK_ERROR_LOCAL_AUTHENTICATION_PASSCODE_NOT_SET" = "Vous devez activer un code dans vos réglages iOS pour utiliser le verrou d’écran.";
|
||||
/* Label for the button to send a message */
|
||||
"SEND_BUTTON_TITLE" = "Send";
|
||||
"SEND_BUTTON_TITLE" = "Envoyer";
|
||||
/* Generic text for button that retries whatever the last action was. */
|
||||
"RETRY_BUTTON_TEXT" = "Retry";
|
||||
"RETRY_BUTTON_TEXT" = "Réessayer";
|
||||
/* notification action */
|
||||
"SHOW_THREAD_BUTTON_TITLE" = "Show Chat";
|
||||
"SHOW_THREAD_BUTTON_TITLE" = "Montrer Discussion";
|
||||
/* notification body */
|
||||
"SEND_FAILED_NOTIFICATION_BODY" = "Your message failed to send.";
|
||||
"INVALID_SESSION_ID_MESSAGE" = "Please check the Session ID and try again.";
|
||||
"INVALID_RECOVERY_PHRASE_MESSAGE" = "Please check the Recovery Phrase and try again.";
|
||||
"QUOTED_MESSAGE_NOT_FOUND" = "Original message not found.";
|
||||
"MEDIA_TAB_TITLE" = "Media";
|
||||
"SEND_FAILED_NOTIFICATION_BODY" = "Échec d'envoi de votre message.";
|
||||
"INVALID_SESSION_ID_MESSAGE" = "Veuillez vérifier l'ID Session et réessayez.";
|
||||
"INVALID_RECOVERY_PHRASE_MESSAGE" = "Veuillez vérifier la phrase de récupération et réessayez.";
|
||||
"QUOTED_MESSAGE_NOT_FOUND" = "Message original non trouvé.";
|
||||
"MEDIA_TAB_TITLE" = "Média";
|
||||
"DOCUMENT_TAB_TITLE" = "Documents";
|
||||
"DOCUMENT_TILES_EMPTY_DOCUMENT" = "You don't have any document in this conversation.";
|
||||
"DOCUMENT_TILES_LOADING_MORE_RECENT_LABEL" = "Loading Newer Document…";
|
||||
"DOCUMENT_TILES_LOADING_OLDER_LABEL" = "Loading Older Document…";
|
||||
"DOCUMENT_TILES_EMPTY_DOCUMENT" = "Vous n'avez aucun document dans cette conversation.";
|
||||
"DOCUMENT_TILES_LOADING_MORE_RECENT_LABEL" = "Chargement des documents les plus récents…";
|
||||
"DOCUMENT_TILES_LOADING_OLDER_LABEL" = "Chargement des documents les plus anciens…";
|
||||
/* The name for the emoji category 'Activities' */
|
||||
"EMOJI_CATEGORY_ACTIVITIES_NAME" = "Activities";
|
||||
"EMOJI_CATEGORY_ACTIVITIES_NAME" = "Activités";
|
||||
/* The name for the emoji category 'Animals & Nature' */
|
||||
"EMOJI_CATEGORY_ANIMALS_NAME" = "Animals & Nature";
|
||||
"EMOJI_CATEGORY_ANIMALS_NAME" = "Animaux & Nature";
|
||||
/* The name for the emoji category 'Flags' */
|
||||
"EMOJI_CATEGORY_FLAGS_NAME" = "Flags";
|
||||
"EMOJI_CATEGORY_FLAGS_NAME" = "Drapeaux";
|
||||
/* The name for the emoji category 'Food & Drink' */
|
||||
"EMOJI_CATEGORY_FOOD_NAME" = "Food & Drink";
|
||||
"EMOJI_CATEGORY_FOOD_NAME" = "Nourriture & Boissons";
|
||||
/* The name for the emoji category 'Objects' */
|
||||
"EMOJI_CATEGORY_OBJECTS_NAME" = "Objects";
|
||||
"EMOJI_CATEGORY_OBJECTS_NAME" = "Objets";
|
||||
/* The name for the emoji category 'Recents' */
|
||||
"EMOJI_CATEGORY_RECENTS_NAME" = "Recently Used";
|
||||
"EMOJI_CATEGORY_RECENTS_NAME" = "Récents";
|
||||
/* The name for the emoji category 'Smileys & People' */
|
||||
"EMOJI_CATEGORY_SMILEYSANDPEOPLE_NAME" = "Smileys & People";
|
||||
"EMOJI_CATEGORY_SMILEYSANDPEOPLE_NAME" = "Smileys & Personnes";
|
||||
/* The name for the emoji category 'Symbols' */
|
||||
"EMOJI_CATEGORY_SYMBOLS_NAME" = "Symbols";
|
||||
"EMOJI_CATEGORY_SYMBOLS_NAME" = "Symboles";
|
||||
/* The name for the emoji category 'Travel & Places' */
|
||||
"EMOJI_CATEGORY_TRAVEL_NAME" = "Travel & Places";
|
||||
"EMOJI_REACTS_NOTIFICATION" = "%@ reacts to a message with %@.";
|
||||
"EMOJI_REACTS_MORE_REACTORS_ONE" = "And 1 other has reacted %@ to this message.";
|
||||
"EMOJI_REACTS_MORE_REACTORS_MUTIPLE" = "And %@ others have reacted %@ to this message.";
|
||||
"EMOJI_REACTS_SHOW_LESS" = "Show less";
|
||||
"EMOJI_REACTS_RATE_LIMIT_TOAST" = "Slow down! You've sent too many emoji reacts. Try again soon.";
|
||||
"EMOJI_CATEGORY_TRAVEL_NAME" = "Voyages et Lieux";
|
||||
"EMOJI_REACTS_NOTIFICATION" = "%@ a réagi au message de %@.";
|
||||
"EMOJI_REACTS_MORE_REACTORS_ONE" = "et 1 autre personne a réagi %@ à ce message.";
|
||||
"EMOJI_REACTS_MORE_REACTORS_MUTIPLE" = "et %@ autres personnes ont réagi %@ à ce message.";
|
||||
"EMOJI_REACTS_SHOW_LESS" = "Moins";
|
||||
"EMOJI_REACTS_RATE_LIMIT_TOAST" = "Ralentissez ! Vous avez envoyé trop de réactions Emojis. Réessayez un peu plus tard.";
|
||||
/* New conversation screen*/
|
||||
"vc_new_conversation_title" = "New Conversation";
|
||||
"CREATE_GROUP_BUTTON_TITLE" = "Create";
|
||||
"JOIN_COMMUNITY_BUTTON_TITLE" = "Join";
|
||||
"PRIVACY_TITLE" = "Confidientalité";
|
||||
"vc_new_conversation_title" = "Nouvelle Conversation";
|
||||
"CREATE_GROUP_BUTTON_TITLE" = "Créer";
|
||||
"JOIN_COMMUNITY_BUTTON_TITLE" = "Rejoindre";
|
||||
"PRIVACY_TITLE" = "Confidentialité";
|
||||
"PRIVACY_SECTION_SCREEN_SECURITY" = "Sécurité de l’écran";
|
||||
"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_TITLE" = "Lock Session";
|
||||
"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_DESCRIPTION" = "Require Touch ID, Face ID or your passcode to unlock Session.";
|
||||
"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_TITLE" = "Verrouiller Session";
|
||||
"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_DESCRIPTION" = "Requiert Touch ID, Face ID ou votre code pour déverrouiller Session.";
|
||||
"PRIVACY_SECTION_READ_RECEIPTS" = "Accusés de lecture";
|
||||
"PRIVACY_READ_RECEIPTS_TITLE" = "Accusés de lecture";
|
||||
"PRIVACY_READ_RECEIPTS_DESCRIPTION" = "Send read receipts in one-to-one chats.";
|
||||
"PRIVACY_READ_RECEIPTS_DESCRIPTION" = "Envoyer un accusé réception dans les conversations 1 à 1.";
|
||||
"PRIVACY_SECTION_TYPING_INDICATORS" = "Indicateurs de saisie";
|
||||
"PRIVACY_TYPING_INDICATORS_TITLE" = "Indicateurs de saisie";
|
||||
"PRIVACY_TYPING_INDICATORS_DESCRIPTION" = "See and share typing indicators in one-to-one conversations.";
|
||||
"PRIVACY_SECTION_LINK_PREVIEWS" = "Link Previews";
|
||||
"PRIVACY_TYPING_INDICATORS_DESCRIPTION" = "Voir et partager l'indicateur de saisie dans les conversions 1 à 1.";
|
||||
"PRIVACY_SECTION_LINK_PREVIEWS" = "Aperçus des liens";
|
||||
"PRIVACY_LINK_PREVIEWS_TITLE" = "Envoyer des aperçus de liens.";
|
||||
"PRIVACY_LINK_PREVIEWS_DESCRIPTION" = "Generate link previews for supported URLs.";
|
||||
"PRIVACY_SECTION_CALLS" = "Calls (Beta)";
|
||||
"PRIVACY_LINK_PREVIEWS_DESCRIPTION" = "Générer un lien d'aperçu pour les URL supportées.";
|
||||
"PRIVACY_SECTION_CALLS" = "Appels (Béta)";
|
||||
"PRIVACY_CALLS_TITLE" = "Appels audio et vidéo";
|
||||
"PRIVACY_CALLS_DESCRIPTION" = "Enables voice and video calls to and from other users.";
|
||||
"PRIVACY_CALLS_WARNING_TITLE" = "Voice and Video Calls (Beta)";
|
||||
"PRIVACY_CALLS_WARNING_DESCRIPTION" = "Your IP address is visible to your call partner and an Oxen Foundation server while using beta calls. Are you sure you want to enable Voice and Video Calls?";
|
||||
"PRIVACY_CALLS_DESCRIPTION" = "Active les appels voix et vidéos de et vers d'autres utilisateurs.";
|
||||
"PRIVACY_CALLS_WARNING_TITLE" = "Appels voix et vidéo (Béta)";
|
||||
"PRIVACY_CALLS_WARNING_DESCRIPTION" = "Votre adresse IP est visible de votre partenaire d'appel et d'un serveur de Oxen Foundation pendant l'utilisation d'un appel. Êtes-vous certain de vouloir activer les appels voix et vidéo ?";
|
||||
"NOTIFICATIONS_TITLE" = "Notifications";
|
||||
"NOTIFICATIONS_SECTION_STRATEGY" = "Stratégie de notification";
|
||||
"NOTIFICATIONS_STRATEGY_FAST_MODE_TITLE" = "Utiliser le mode rapide";
|
||||
"NOTIFICATIONS_STRATEGY_FAST_MODE_DESCRIPTION" = "You'll be notified of new message reliably and immediately using Apple's notification servers.";
|
||||
"NOTIFICATIONS_STRATEGY_FAST_MODE_ACTION" = "Go to device notification settings";
|
||||
"NOTIFICATIONS_SECTION_STYLE" = "Notification Style";
|
||||
"NOTIFICATIONS_STYLE_SOUND_TITLE" = "Sound";
|
||||
"NOTIFICATIONS_STYLE_SOUND_WHEN_OPEN_TITLE" = "Sound When App is Open";
|
||||
"NOTIFICATIONS_STRATEGY_FAST_MODE_DESCRIPTION" = "Vous serez notifiés des nouveaux messages de manière fiable et rapide en utilisant les serveurs de notifications d'Apple.";
|
||||
"NOTIFICATIONS_STRATEGY_FAST_MODE_ACTION" = "Aller aux paramètres de notification";
|
||||
"NOTIFICATIONS_SECTION_STYLE" = "Style de notification";
|
||||
"NOTIFICATIONS_STYLE_SOUND_TITLE" = "Son";
|
||||
"NOTIFICATIONS_STYLE_SOUND_WHEN_OPEN_TITLE" = "Son quand l'application est ouverte";
|
||||
"NOTIFICATIONS_STYLE_CONTENT_TITLE" = "Contenu des notifications";
|
||||
"NOTIFICATIONS_STYLE_CONTENT_DESCRIPTION" = "The information shown in notifications.";
|
||||
"NOTIFICATIONS_STYLE_CONTENT_OPTION_NAME_AND_CONTENT" = "Name & Content";
|
||||
"NOTIFICATIONS_STYLE_CONTENT_DESCRIPTION" = "L'information qui apparaît dans les notifications.";
|
||||
"NOTIFICATIONS_STYLE_CONTENT_OPTION_NAME_AND_CONTENT" = "Nom et contenu";
|
||||
"NOTIFICATIONS_STYLE_CONTENT_OPTION_NAME_ONLY" = "Nom seulement";
|
||||
"NOTIFICATIONS_STYLE_CONTENT_OPTION_NO_NAME_OR_CONTENT" = "Ni nom ni contenu";
|
||||
"CONVERSATION_SETTINGS_TITLE" = "Conversations";
|
||||
"CONVERSATION_SETTINGS_SECTION_MESSAGE_TRIMMING" = "Message Trimming";
|
||||
"CONVERSATION_SETTINGS_MESSAGE_TRIMMING_TITLE" = "Trim Communities";
|
||||
"CONVERSATION_SETTINGS_MESSAGE_TRIMMING_DESCRIPTION" = "Delete messages older than 6 months from Communities that have over 2,000 messages.";
|
||||
"CONVERSATION_SETTINGS_SECTION_AUDIO_MESSAGES" = "Audio Messages";
|
||||
"CONVERSATION_SETTINGS_AUDIO_MESSAGES_AUTOPLAY_TITLE" = "Autoplay Audio Messages";
|
||||
"CONVERSATION_SETTINGS_AUDIO_MESSAGES_AUTOPLAY_DESCRIPTION" = "Autoplay consecutive audio messages.";
|
||||
"CONVERSATION_SETTINGS_BLOCKED_CONTACTS_TITLE" = "Blocked Contacts";
|
||||
"CONVERSATION_SETTINGS_BLOCKED_CONTACTS_EMPTY_STATE" = "You have no blocked contacts.";
|
||||
"CONVERSATION_SETTINGS_BLOCKED_CONTACTS_UNBLOCK" = "Unblock";
|
||||
"CONVERSATION_SETTINGS_BLOCKED_CONTACTS_UNBLOCK_CONFIRMATION_TITLE_SINGLE" = "Are you sure you want to unblock %@?";
|
||||
"CONVERSATION_SETTINGS_BLOCKED_CONTACTS_UNBLOCK_CONFIRMATION_TITLE_FALLBACK" = "this contact";
|
||||
"CONVERSATION_SETTINGS_BLOCKED_CONTACTS_UNBLOCK_CONFIRMATION_TITLE_MULTIPLE_1" = "Are you sure you want to unblock %@";
|
||||
"CONVERSATION_SETTINGS_BLOCKED_CONTACTS_UNBLOCK_CONFIRMATION_TITLE_MULTIPLE_2_SINGLE" = "and %@?";
|
||||
"CONVERSATION_SETTINGS_BLOCKED_CONTACTS_UNBLOCK_CONFIRMATION_TITLE_MULTIPLE_3" = "and %d others?";
|
||||
"CONVERSATION_SETTINGS_BLOCKED_CONTACTS_UNBLOCK_CONFIRMATION_ACTON" = "Unblock";
|
||||
"APPEARANCE_TITLE" = "Appearance";
|
||||
"APPEARANCE_THEMES_TITLE" = "Themes";
|
||||
"APPEARANCE_PRIMARY_COLOR_TITLE" = "Primary colour";
|
||||
"APPEARANCE_PRIMARY_COLOR_PREVIEW_INC_QUOTE" = "How are you?";
|
||||
"APPEARANCE_PRIMARY_COLOR_PREVIEW_INC_MESSAGE" = "I'm good thanks, you?";
|
||||
"APPEARANCE_PRIMARY_COLOR_PREVIEW_OUT_MESSAGE" = "I'm doing great, thanks.";
|
||||
"APPEARANCE_NIGHT_MODE_TITLE" = "Auto night-mode";
|
||||
"APPEARANCE_NIGHT_MODE_TOGGLE" = "Match system settings";
|
||||
"HELP_TITLE" = "Help";
|
||||
"HELP_REPORT_BUG_TITLE" = "Report a Bug";
|
||||
"HELP_REPORT_BUG_DESCRIPTION" = "Export your logs, then upload the file though Session's Help Desk.";
|
||||
"HELP_REPORT_BUG_ACTION_TITLE" = "Export Logs";
|
||||
"HELP_TRANSLATE_TITLE" = "Translate Session";
|
||||
"HELP_FEEDBACK_TITLE" = "We'd love your Feedback";
|
||||
"CONVERSATION_SETTINGS_SECTION_MESSAGE_TRIMMING" = "Épuration des messages";
|
||||
"CONVERSATION_SETTINGS_MESSAGE_TRIMMING_TITLE" = "Épuration des communautés";
|
||||
"CONVERSATION_SETTINGS_MESSAGE_TRIMMING_DESCRIPTION" = "Supprimer les messages datant de plus de 6 mois dans les communautés ayant plus de 2000 messages.";
|
||||
"CONVERSATION_SETTINGS_SECTION_AUDIO_MESSAGES" = "Messages audio";
|
||||
"CONVERSATION_SETTINGS_AUDIO_MESSAGES_AUTOPLAY_TITLE" = "Jouer automatiquement les messages audio";
|
||||
"CONVERSATION_SETTINGS_AUDIO_MESSAGES_AUTOPLAY_DESCRIPTION" = "Jouer automatiquement les messages audio de manière consécutive.";
|
||||
"CONVERSATION_SETTINGS_BLOCKED_CONTACTS_TITLE" = "Contacts bloqués";
|
||||
"CONVERSATION_SETTINGS_BLOCKED_CONTACTS_EMPTY_STATE" = "Vous n'avez aucun contact bloqué.";
|
||||
"CONVERSATION_SETTINGS_BLOCKED_CONTACTS_UNBLOCK" = "Débloquer";
|
||||
"CONVERSATION_SETTINGS_BLOCKED_CONTACTS_UNBLOCK_CONFIRMATION_TITLE_SINGLE" = "Êtes-vous sûr de vouloir débloquer %@?";
|
||||
"CONVERSATION_SETTINGS_BLOCKED_CONTACTS_UNBLOCK_CONFIRMATION_TITLE_FALLBACK" = "Ce contact";
|
||||
"CONVERSATION_SETTINGS_BLOCKED_CONTACTS_UNBLOCK_CONFIRMATION_TITLE_MULTIPLE_1" = "Êtes-vous sûr de vouloir débloquer %@";
|
||||
"CONVERSATION_SETTINGS_BLOCKED_CONTACTS_UNBLOCK_CONFIRMATION_TITLE_MULTIPLE_2_SINGLE" = "et %@?";
|
||||
"CONVERSATION_SETTINGS_BLOCKED_CONTACTS_UNBLOCK_CONFIRMATION_TITLE_MULTIPLE_3" = "et %d autres?";
|
||||
"CONVERSATION_SETTINGS_BLOCKED_CONTACTS_UNBLOCK_CONFIRMATION_ACTON" = "Débloquer";
|
||||
"APPEARANCE_TITLE" = "Apparence";
|
||||
"APPEARANCE_THEMES_TITLE" = "Thèmes";
|
||||
"APPEARANCE_PRIMARY_COLOR_TITLE" = "Couleur primaire";
|
||||
"APPEARANCE_PRIMARY_COLOR_PREVIEW_INC_QUOTE" = "Comment allez-vous ?";
|
||||
"APPEARANCE_PRIMARY_COLOR_PREVIEW_INC_MESSAGE" = "Je vais bien, et vous ?";
|
||||
"APPEARANCE_PRIMARY_COLOR_PREVIEW_OUT_MESSAGE" = "Je vais très bien. Merci.";
|
||||
"APPEARANCE_NIGHT_MODE_TITLE" = "Mode nuit automatique";
|
||||
"APPEARANCE_NIGHT_MODE_TOGGLE" = "Se conformer aux paramètres système";
|
||||
"HELP_TITLE" = "Aide";
|
||||
"HELP_REPORT_BUG_TITLE" = "Rapporter un bogue";
|
||||
"HELP_REPORT_BUG_DESCRIPTION" = "Exporter votre journal d'application et téléverser le fichier via le support Session.";
|
||||
"HELP_REPORT_BUG_ACTION_TITLE" = "Exporter Journal";
|
||||
"HELP_TRANSLATE_TITLE" = "Traduire Session";
|
||||
"HELP_FEEDBACK_TITLE" = "Nous aimerions votre retour d'expérience";
|
||||
"HELP_FAQ_TITLE" = "FAQ";
|
||||
"HELP_SUPPORT_TITLE" = "Support";
|
||||
"modal_clear_all_data_title" = "Effacer toutes les données";
|
||||
"modal_clear_all_data_explanation" = "This will permanently delete your messages and contacts. Would you like to clear this device only, or delete your data from the network as well?";
|
||||
"modal_clear_all_data_explanation_2" = "Are you sure you want to delete your data from the network? If you continue, you will not be able to restore your messages or contacts.";
|
||||
"modal_clear_all_data_device_only_button_title" = "Clear Device Only";
|
||||
"modal_clear_all_data_entire_account_button_title" = "Clear Device and Network";
|
||||
"modal_clear_all_data_explanation" = "Ceci supprimera de manière permanente vos messages et contacts. Voulez-vous effacer vos données sur cet appareil seulement ou sur le réseau aussi ?";
|
||||
"modal_clear_all_data_explanation_2" = "Êtes-vous sûr de vouloir effacer les données sur le réseau ? Si vous continuez, vous ne pourrez restaurer ni vos messages ni vos contacts.";
|
||||
"modal_clear_all_data_device_only_button_title" = "Effacer sur l'appareil seulement";
|
||||
"modal_clear_all_data_entire_account_button_title" = "Effacer sur l'appareil et le réseau";
|
||||
"dialog_clear_all_data_deletion_failed_1" = "Les données n’ont pas été supprimées sur un nœud de service. ID du nœud de service : %@.";
|
||||
"dialog_clear_all_data_deletion_failed_2" = "Les données n’ont pas été supprimées sur %@ nœuds de service. ID des nœuds de service : %@.";
|
||||
"modal_clear_all_data_confirm" = "Clear";
|
||||
"modal_clear_all_data_confirm" = "Effacer";
|
||||
"modal_seed_title" = "Votre phrase de récupération";
|
||||
"modal_seed_explanation" = "You can use your recovery phrase to restore your account or link a device.";
|
||||
"modal_permission_explanation" = "Session needs %@ access to continue. You can enable access in the iOS settings.";
|
||||
"modal_permission_settings_title" = "Settings";
|
||||
"modal_permission_camera" = "camera";
|
||||
"modal_seed_explanation" = "Vous pouvez utiliser votre phrase de récupération pour restaurer votre compte ou pour lier un autre appareil.";
|
||||
"modal_permission_explanation" = "Session a besoin de l'accès %@ pour pouvoir continuer. Vous pouvez donner cet accès depuis les paramètres iOS.";
|
||||
"modal_permission_settings_title" = "Paramètres";
|
||||
"modal_permission_camera" = "caméra";
|
||||
"modal_permission_microphone" = "microphone";
|
||||
"modal_permission_library" = "library";
|
||||
"DISAPPEARING_MESSAGES_OFF" = "Off";
|
||||
"DISAPPEARING_MESSAGES_SUBTITLE_OFF" = "Off";
|
||||
"DISAPPEARING_MESSAGES_SUBTITLE_DISAPPEAR_AFTER" = "Disappear After: %@";
|
||||
"COPY_GROUP_URL" = "Copy Group URL";
|
||||
"modal_permission_library" = "disque";
|
||||
"DISAPPEARING_MESSAGES_OFF" = "Éteint";
|
||||
"DISAPPEARING_MESSAGES_SUBTITLE_OFF" = "Éteint";
|
||||
"DISAPPEARING_MESSAGES_SUBTITLE_DISAPPEAR_AFTER" = "Disparaît après : %@";
|
||||
"COPY_GROUP_URL" = "Copier l'URL de Groupe";
|
||||
"NEW_CONVERSATION_CONTACTS_SECTION_TITLE" = "Contacts";
|
||||
"GROUP_ERROR_NO_MEMBER_SELECTION" = "Please pick at least 1 group member";
|
||||
"GROUP_CREATION_PLEASE_WAIT" = "Please wait while the group is created...";
|
||||
"GROUP_CREATION_ERROR_TITLE" = "Couldn't Create Group";
|
||||
"GROUP_CREATION_ERROR_MESSAGE" = "Please check your internet connection and try again.";
|
||||
"GROUP_UPDATE_ERROR_TITLE" = "Couldn't Update Group";
|
||||
"GROUP_UPDATE_ERROR_MESSAGE" = "Can't leave while adding or removing other members.";
|
||||
"GROUP_ACTION_REMOVE" = "Remove";
|
||||
"GROUP_TITLE_MEMBERS" = "Members";
|
||||
"GROUP_TITLE_FALLBACK" = "Group";
|
||||
"DM_ERROR_DIRECT_BLINDED_ID" = "You can only send messages to Blinded IDs from within a Community";
|
||||
"DM_ERROR_INVALID" = "Please check the Session ID or ONS name and try again";
|
||||
"COMMUNITY_ERROR_INVALID_URL" = "Please check the URL you entered and try again.";
|
||||
"COMMUNITY_ERROR_GENERIC" = "Couldn't Join";
|
||||
"DISAPPERING_MESSAGES_TITLE" = "Disappearing Messages";
|
||||
"DISAPPERING_MESSAGES_TYPE_TITLE" = "Delete Type";
|
||||
"DISAPPERING_MESSAGES_TYPE_AFTER_READ_TITLE" = "Disappear After Read";
|
||||
"DISAPPERING_MESSAGES_TYPE_AFTER_READ_DESCRIPTION" = "Messages delete after they have been read.";
|
||||
"DISAPPERING_MESSAGES_TYPE_AFTER_SEND_TITLE" = "Disappear After Send";
|
||||
"DISAPPERING_MESSAGES_TYPE_AFTER_SEND_DESCRIPTION" = "Messages delete after they have been sent.";
|
||||
"DISAPPERING_MESSAGES_TIMER_TITLE" = "Timer";
|
||||
"DISAPPERING_MESSAGES_SAVE_TITLE" = "Set";
|
||||
"DISAPPERING_MESSAGES_GROUP_WARNING" = "This setting applies to everyone in this conversation.";
|
||||
"DISAPPERING_MESSAGES_GROUP_WARNING_ADMIN_ONLY" = "This setting applies to everyone in this conversation. Only group admins can change this setting.";
|
||||
"DISAPPERING_MESSAGES_SUMMARY" = "Disappear After %@ - %@";
|
||||
"DISAPPERING_MESSAGES_INFO_ENABLE" = "%@ has set messages to disappear %@ after they have been %@";
|
||||
"DISAPPERING_MESSAGES_INFO_UPDATE" = "%@ has changed messages to disappear %@ after they have been %@";
|
||||
"DISAPPERING_MESSAGES_INFO_DISABLE" = "%@ has turned off disappearing messages";
|
||||
"MESSAGE_STATE_READ" = "Read";
|
||||
"MESSAGE_STATE_SENT" = "Sent";
|
||||
"MESSAGE_REQUEST_PENDING_APPROVAL_INFO" = "You will be able to send voice messages and attachments once the recipient has approved this message request";
|
||||
"MESSAGE_DELIVERY_STATUS_SENDING" = "Sending";
|
||||
"MESSAGE_DELIVERY_STATUS_SENT" = "Sent";
|
||||
"MESSAGE_DELIVERY_STATUS_READ" = "Read";
|
||||
"MESSAGE_DELIVERY_STATUS_FAILED" = "Failed to send";
|
||||
"MESSAGE_DELIVERY_STATUS_FAILED_SYNC" = "Failed to sync";
|
||||
"MESSAGE_DELIVERY_STATUS_SYNCING" = "Syncing";
|
||||
"MESSAGE_DELIVERY_FAILED_TITLE" = "Failed to send message";
|
||||
"MESSAGE_DELIVERY_FAILED_SYNC_TITLE" = "Failed to sync message to your other devices";
|
||||
"delete_message_for_me_and_my_devices" = "Delete from all of my devices";
|
||||
"context_menu_resend" = "Resend";
|
||||
"context_menu_resync" = "Resync";
|
||||
"GIPHY_PERMISSION_TITLE" = "Search GIFs?";
|
||||
"GIPHY_PERMISSION_MESSAGE" = "Session will connect to Giphy to provide search results. You will not have full metadata protection when sending GIFs.";
|
||||
"mute_button_text" = "Mute";
|
||||
"unmute_button_text" = "Unmute";
|
||||
"mark_read_button_text" = "Mark read";
|
||||
"mark_unread_button_text" = "Mark unread";
|
||||
"leave_group_confirmation_alert_title" = "Leave Group";
|
||||
"leave_community_confirmation_alert_title" = "Leave Community";
|
||||
"leave_community_confirmation_alert_message" = "Are you sure you want to leave %@?";
|
||||
"group_you_leaving" = "Leaving...";
|
||||
"group_leave_error" = "Failed to leave Group!";
|
||||
"group_unable_to_leave" = "Unable to leave the Group, please try again";
|
||||
"delete_conversation_confirmation_alert_message" = "Are you sure you want to delete your conversation with %@?";
|
||||
"delete_conversation_confirmation_alert_title" = "Delete Conversation";
|
||||
"GROUP_ERROR_NO_MEMBER_SELECTION" = "Veuillez choisir au moins 1 membre de groupe";
|
||||
"GROUP_CREATION_PLEASE_WAIT" = "Veuillez patienter pendant la création du groupe...";
|
||||
"GROUP_CREATION_ERROR_TITLE" = "Impossible de créer le groupe";
|
||||
"GROUP_CREATION_ERROR_MESSAGE" = "Veuillez vérifier votre connexion internet et réessayez.";
|
||||
"GROUP_UPDATE_ERROR_TITLE" = "Impossible de mettre à jour le groupe";
|
||||
"GROUP_UPDATE_ERROR_MESSAGE" = "Impossible de quitter pendant l'ajout ou la suppression d'un autre membre.";
|
||||
"GROUP_ACTION_REMOVE" = "Retirer";
|
||||
"GROUP_TITLE_MEMBERS" = "Membres";
|
||||
"GROUP_TITLE_FALLBACK" = "Groupe";
|
||||
"DM_ERROR_DIRECT_BLINDED_ID" = "Vous pouvez seulement envoyer des messages à des IDs anonymes depuis une communauté";
|
||||
"DM_ERROR_INVALID" = "Veuillez vérifier l'ID Session ou l'ONS et réessayez";
|
||||
"COMMUNITY_ERROR_INVALID_URL" = "Veuillez vérifier l'URL et réessayez";
|
||||
"COMMUNITY_ERROR_GENERIC" = "Impossible de rejoindre";
|
||||
"DISAPPERING_MESSAGES_TITLE" = "Messages éphémères";
|
||||
"DISAPPERING_MESSAGES_TYPE_TITLE" = "Type de suppression";
|
||||
"DISAPPERING_MESSAGES_TYPE_AFTER_READ_TITLE" = "Disparaît après lecture";
|
||||
"DISAPPERING_MESSAGES_TYPE_AFTER_READ_DESCRIPTION" = "Les messages disparaissent une fois lus.";
|
||||
"DISAPPERING_MESSAGES_TYPE_AFTER_SEND_TITLE" = "Disparaît après envoi";
|
||||
"DISAPPERING_MESSAGES_TYPE_AFTER_SEND_DESCRIPTION" = "Les messages disparaissent une fois envoyés.";
|
||||
"DISAPPERING_MESSAGES_TIMER_TITLE" = "Compteur";
|
||||
"DISAPPERING_MESSAGES_SAVE_TITLE" = "Sauver";
|
||||
"DISAPPERING_MESSAGES_GROUP_WARNING" = "Ce paramètre s'applique à toutes les personnes de cette conversation.";
|
||||
"DISAPPERING_MESSAGES_GROUP_WARNING_ADMIN_ONLY" = "Ce paramètre s'applique à toutes les personnes de cette conversation. Seuls les administrateurs du groupe peuvent changer ce paramètre.";
|
||||
"DISAPPERING_MESSAGES_SUMMARY" = "Disparaît après %@ - %@";
|
||||
"DISAPPERING_MESSAGES_INFO_ENABLE" = "%@ a paramétré les messages pour qu'ils disparaissent %@ après avoir été %@";
|
||||
"DISAPPERING_MESSAGES_INFO_UPDATE" = "%@ a paramétré les messages pour qu'ils disparaissent %@ après avoir été %@";
|
||||
"DISAPPERING_MESSAGES_INFO_DISABLE" = "%@ a désactivé les messages éphémères.";
|
||||
"MESSAGE_STATE_READ" = "Lu";
|
||||
"MESSAGE_STATE_SENT" = "Envoyé";
|
||||
"MESSAGE_REQUEST_PENDING_APPROVAL_INFO" = "Vous pourrez envoyer des messages et des pièces jointes une fois que le destinataire aura approuvé votre demande.";
|
||||
"MESSAGE_DELIVERY_STATUS_SENDING" = "Envoi";
|
||||
"MESSAGE_DELIVERY_STATUS_SENT" = "Envoyé";
|
||||
"MESSAGE_DELIVERY_STATUS_READ" = "Lu";
|
||||
"MESSAGE_DELIVERY_STATUS_FAILED" = "Échec de l'envoi";
|
||||
"MESSAGE_INFO_SENT" = "Envoyé";
|
||||
"MESSAGE_INFO_RECEIVED" = "Reçu";
|
||||
"MESSAGE_INFO_FROM" = "De";
|
||||
"ATTACHMENT_INFO_FILE_ID" = "ID Fichier";
|
||||
"ATTACHMENT_INFO_FILE_TYPE" = "Type Fichier";
|
||||
"ATTACHMENT_INFO_FILE_SIZE" = "Taille Fichier";
|
||||
"ATTACHMENT_INFO_RESOLUTION" = "Résolution";
|
||||
"ATTACHMENT_INFO_DURATION" = "Durée";
|
||||
"MESSAGE_DELIVERY_STATUS_FAILED_SYNC" = "Échec de synchronisation";
|
||||
"MESSAGE_DELIVERY_STATUS_SYNCING" = "Synchronisation";
|
||||
"MESSAGE_DELIVERY_FAILED_TITLE" = "Échec d'envoi du message";
|
||||
"MESSAGE_DELIVERY_FAILED_SYNC_TITLE" = "Échec de synchronisation vers vos autres appareils";
|
||||
"delete_message_for_me_and_my_devices" = "Effacer sur mes autres appareils";
|
||||
"context_menu_resend" = "Réenvoyer";
|
||||
"context_menu_resync" = "Resynchroniser";
|
||||
"GIPHY_PERMISSION_TITLE" = "Rechercher GIFs?";
|
||||
"GIPHY_PERMISSION_MESSAGE" = "Session va se connecter à Giphy. Envoyer des GIFs empêchera la protection de vos métadonnées.";
|
||||
"message_info_title" = "Info Message";
|
||||
"mute_button_text" = "Silence";
|
||||
"unmute_button_text" = "Actif";
|
||||
"mark_read_button_text" = "Marquer comme lu";
|
||||
"mark_unread_button_text" = "Marquer comme non lu";
|
||||
"leave_group_confirmation_alert_title" = "Quitter le groupe";
|
||||
"leave_community_confirmation_alert_title" = "Quitter la communauté";
|
||||
"leave_community_confirmation_alert_message" = "Êtes-vous sûr de vouloir quitter %@?";
|
||||
"group_you_leaving" = "Quitter...";
|
||||
"group_leave_error" = "Impossible de quitter le groupe!";
|
||||
"group_unable_to_leave" = "Impossible de quitter le groupe, veuillez réessayer";
|
||||
"delete_conversation_confirmation_alert_message" = "Êtes-vous sûr de vouloir supprimer votre conversation avec %@ ?";
|
||||
"delete_conversation_confirmation_alert_title" = "Supprimer conversation";
|
||||
"update_profile_modal_title" = "Set Display Picture";
|
||||
"update_profile_modal_upload" = "Upload";
|
||||
"update_profile_modal_remove" = "Remove";
|
||||
|
|
|
@ -366,6 +366,7 @@
|
|||
"delete_message_for_me" = "Delete just for me";
|
||||
"delete_message_for_everyone" = "Delete for everyone";
|
||||
"delete_message_for_me_and_recipient" = "Delete for me and %@";
|
||||
"context_menu_info" = "Info";
|
||||
"context_menu_reply" = "Reply";
|
||||
"context_menu_save" = "Save";
|
||||
"context_menu_ban_user" = "Ban User";
|
||||
|
@ -592,6 +593,14 @@
|
|||
"MESSAGE_DELIVERY_STATUS_SENT" = "Sent";
|
||||
"MESSAGE_DELIVERY_STATUS_READ" = "Read";
|
||||
"MESSAGE_DELIVERY_STATUS_FAILED" = "Failed to send";
|
||||
"MESSAGE_INFO_SENT" = "Sent";
|
||||
"MESSAGE_INFO_RECEIVED" = "Received";
|
||||
"MESSAGE_INFO_FROM" = "From";
|
||||
"ATTACHMENT_INFO_FILE_ID" = "File ID";
|
||||
"ATTACHMENT_INFO_FILE_TYPE" = "File Type";
|
||||
"ATTACHMENT_INFO_FILE_SIZE" = "File Size";
|
||||
"ATTACHMENT_INFO_RESOLUTION" = "Resolution";
|
||||
"ATTACHMENT_INFO_DURATION" = "Duration";
|
||||
"MESSAGE_DELIVERY_STATUS_FAILED_SYNC" = "Failed to sync";
|
||||
"MESSAGE_DELIVERY_STATUS_SYNCING" = "Syncing";
|
||||
"MESSAGE_DELIVERY_FAILED_TITLE" = "Failed to send message";
|
||||
|
@ -601,6 +610,7 @@
|
|||
"context_menu_resync" = "Resync";
|
||||
"GIPHY_PERMISSION_TITLE" = "Search GIFs?";
|
||||
"GIPHY_PERMISSION_MESSAGE" = "Session will connect to Giphy to provide search results. You will not have full metadata protection when sending GIFs.";
|
||||
"message_info_title" = "Message Info";
|
||||
"mute_button_text" = "Mute";
|
||||
"unmute_button_text" = "Unmute";
|
||||
"mark_read_button_text" = "Mark read";
|
||||
|
@ -613,3 +623,6 @@
|
|||
"group_unable_to_leave" = "Unable to leave the Group, please try again";
|
||||
"delete_conversation_confirmation_alert_message" = "Are you sure you want to delete your conversation with %@?";
|
||||
"delete_conversation_confirmation_alert_title" = "Delete Conversation";
|
||||
"update_profile_modal_title" = "Set Display Picture";
|
||||
"update_profile_modal_upload" = "Upload";
|
||||
"update_profile_modal_remove" = "Remove";
|
||||
|
|
|
@ -366,6 +366,7 @@
|
|||
"delete_message_for_me" = "Izbriši samo za mene";
|
||||
"delete_message_for_everyone" = "Izbriši za sve";
|
||||
"delete_message_for_me_and_recipient" = "Izbriši za mene i %@";
|
||||
"context_menu_info" = "Info";
|
||||
"context_menu_reply" = "Odgovori";
|
||||
"context_menu_save" = "Spremi";
|
||||
"context_menu_ban_user" = "Zabrani korisnik";
|
||||
|
@ -592,6 +593,14 @@
|
|||
"MESSAGE_DELIVERY_STATUS_SENT" = "Sent";
|
||||
"MESSAGE_DELIVERY_STATUS_READ" = "Read";
|
||||
"MESSAGE_DELIVERY_STATUS_FAILED" = "Failed to send";
|
||||
"MESSAGE_INFO_SENT" = "Sent";
|
||||
"MESSAGE_INFO_RECEIVED" = "Received";
|
||||
"MESSAGE_INFO_FROM" = "From";
|
||||
"ATTACHMENT_INFO_FILE_ID" = "File ID";
|
||||
"ATTACHMENT_INFO_FILE_TYPE" = "File Type";
|
||||
"ATTACHMENT_INFO_FILE_SIZE" = "File Size";
|
||||
"ATTACHMENT_INFO_RESOLUTION" = "Resolution";
|
||||
"ATTACHMENT_INFO_DURATION" = "Duration";
|
||||
"MESSAGE_DELIVERY_STATUS_FAILED_SYNC" = "Failed to sync";
|
||||
"MESSAGE_DELIVERY_STATUS_SYNCING" = "Syncing";
|
||||
"MESSAGE_DELIVERY_FAILED_TITLE" = "Failed to send message";
|
||||
|
@ -601,6 +610,7 @@
|
|||
"context_menu_resync" = "Resync";
|
||||
"GIPHY_PERMISSION_TITLE" = "Search GIFs?";
|
||||
"GIPHY_PERMISSION_MESSAGE" = "Session will connect to Giphy to provide search results. You will not have full metadata protection when sending GIFs.";
|
||||
"message_info_title" = "Message Info";
|
||||
"mute_button_text" = "Mute";
|
||||
"unmute_button_text" = "Unmute";
|
||||
"mark_read_button_text" = "Mark read";
|
||||
|
@ -613,3 +623,6 @@
|
|||
"group_unable_to_leave" = "Unable to leave the Group, please try again";
|
||||
"delete_conversation_confirmation_alert_message" = "Are you sure you want to delete your conversation with %@?";
|
||||
"delete_conversation_confirmation_alert_title" = "Delete Conversation";
|
||||
"update_profile_modal_title" = "Set Display Picture";
|
||||
"update_profile_modal_upload" = "Upload";
|
||||
"update_profile_modal_remove" = "Remove";
|
||||
|
|
|
@ -366,6 +366,7 @@
|
|||
"delete_message_for_me" = "Delete just for me";
|
||||
"delete_message_for_everyone" = "Delete for everyone";
|
||||
"delete_message_for_me_and_recipient" = "Delete for me and %@";
|
||||
"context_menu_info" = "Info";
|
||||
"context_menu_reply" = "Reply";
|
||||
"context_menu_save" = "Save";
|
||||
"context_menu_ban_user" = "Ban User";
|
||||
|
@ -592,6 +593,14 @@
|
|||
"MESSAGE_DELIVERY_STATUS_SENT" = "Sent";
|
||||
"MESSAGE_DELIVERY_STATUS_READ" = "Read";
|
||||
"MESSAGE_DELIVERY_STATUS_FAILED" = "Failed to send";
|
||||
"MESSAGE_INFO_SENT" = "Sent";
|
||||
"MESSAGE_INFO_RECEIVED" = "Received";
|
||||
"MESSAGE_INFO_FROM" = "From";
|
||||
"ATTACHMENT_INFO_FILE_ID" = "File ID";
|
||||
"ATTACHMENT_INFO_FILE_TYPE" = "File Type";
|
||||
"ATTACHMENT_INFO_FILE_SIZE" = "File Size";
|
||||
"ATTACHMENT_INFO_RESOLUTION" = "Resolution";
|
||||
"ATTACHMENT_INFO_DURATION" = "Duration";
|
||||
"MESSAGE_DELIVERY_STATUS_FAILED_SYNC" = "Failed to sync";
|
||||
"MESSAGE_DELIVERY_STATUS_SYNCING" = "Syncing";
|
||||
"MESSAGE_DELIVERY_FAILED_TITLE" = "Failed to send message";
|
||||
|
@ -601,6 +610,7 @@
|
|||
"context_menu_resync" = "Resync";
|
||||
"GIPHY_PERMISSION_TITLE" = "Search GIFs?";
|
||||
"GIPHY_PERMISSION_MESSAGE" = "Session will connect to Giphy to provide search results. You will not have full metadata protection when sending GIFs.";
|
||||
"message_info_title" = "Message Info";
|
||||
"mute_button_text" = "Mute";
|
||||
"unmute_button_text" = "Unmute";
|
||||
"mark_read_button_text" = "Mark read";
|
||||
|
@ -613,3 +623,6 @@
|
|||
"group_unable_to_leave" = "Unable to leave the Group, please try again";
|
||||
"delete_conversation_confirmation_alert_message" = "Are you sure you want to delete your conversation with %@?";
|
||||
"delete_conversation_confirmation_alert_title" = "Delete Conversation";
|
||||
"update_profile_modal_title" = "Set Display Picture";
|
||||
"update_profile_modal_upload" = "Upload";
|
||||
"update_profile_modal_remove" = "Remove";
|
||||
|
|
|
@ -366,6 +366,7 @@
|
|||
"delete_message_for_me" = "Elimina solo per me";
|
||||
"delete_message_for_everyone" = "Elimina per tutti";
|
||||
"delete_message_for_me_and_recipient" = "Elimina per me e %@";
|
||||
"context_menu_info" = "Info";
|
||||
"context_menu_reply" = "Rispondi";
|
||||
"context_menu_save" = "Salva";
|
||||
"context_menu_ban_user" = "Banna utente";
|
||||
|
@ -592,6 +593,14 @@
|
|||
"MESSAGE_DELIVERY_STATUS_SENT" = "Sent";
|
||||
"MESSAGE_DELIVERY_STATUS_READ" = "Read";
|
||||
"MESSAGE_DELIVERY_STATUS_FAILED" = "Failed to send";
|
||||
"MESSAGE_INFO_SENT" = "Sent";
|
||||
"MESSAGE_INFO_RECEIVED" = "Received";
|
||||
"MESSAGE_INFO_FROM" = "From";
|
||||
"ATTACHMENT_INFO_FILE_ID" = "File ID";
|
||||
"ATTACHMENT_INFO_FILE_TYPE" = "File Type";
|
||||
"ATTACHMENT_INFO_FILE_SIZE" = "File Size";
|
||||
"ATTACHMENT_INFO_RESOLUTION" = "Resolution";
|
||||
"ATTACHMENT_INFO_DURATION" = "Duration";
|
||||
"MESSAGE_DELIVERY_STATUS_FAILED_SYNC" = "Failed to sync";
|
||||
"MESSAGE_DELIVERY_STATUS_SYNCING" = "Syncing";
|
||||
"MESSAGE_DELIVERY_FAILED_TITLE" = "Failed to send message";
|
||||
|
@ -601,6 +610,7 @@
|
|||
"context_menu_resync" = "Resync";
|
||||
"GIPHY_PERMISSION_TITLE" = "Search GIFs?";
|
||||
"GIPHY_PERMISSION_MESSAGE" = "Session will connect to Giphy to provide search results. You will not have full metadata protection when sending GIFs.";
|
||||
"message_info_title" = "Message Info";
|
||||
"mute_button_text" = "Mute";
|
||||
"unmute_button_text" = "Unmute";
|
||||
"mark_read_button_text" = "Mark read";
|
||||
|
@ -613,3 +623,6 @@
|
|||
"group_unable_to_leave" = "Unable to leave the Group, please try again";
|
||||
"delete_conversation_confirmation_alert_message" = "Are you sure you want to delete your conversation with %@?";
|
||||
"delete_conversation_confirmation_alert_title" = "Delete Conversation";
|
||||
"update_profile_modal_title" = "Set Display Picture";
|
||||
"update_profile_modal_upload" = "Upload";
|
||||
"update_profile_modal_remove" = "Remove";
|
||||
|
|
|
@ -366,6 +366,7 @@
|
|||
"delete_message_for_me" = "自分の端末から削除";
|
||||
"delete_message_for_everyone" = "全員の端末から削除";
|
||||
"delete_message_for_me_and_recipient" = "自分と %@ の端末から削除する";
|
||||
"context_menu_info" = "Info";
|
||||
"context_menu_reply" = "返信";
|
||||
"context_menu_save" = "保存";
|
||||
"context_menu_ban_user" = "ユーザーをBAN";
|
||||
|
@ -592,6 +593,14 @@
|
|||
"MESSAGE_DELIVERY_STATUS_SENT" = "Sent";
|
||||
"MESSAGE_DELIVERY_STATUS_READ" = "Read";
|
||||
"MESSAGE_DELIVERY_STATUS_FAILED" = "Failed to send";
|
||||
"MESSAGE_INFO_SENT" = "Sent";
|
||||
"MESSAGE_INFO_RECEIVED" = "Received";
|
||||
"MESSAGE_INFO_FROM" = "From";
|
||||
"ATTACHMENT_INFO_FILE_ID" = "File ID";
|
||||
"ATTACHMENT_INFO_FILE_TYPE" = "File Type";
|
||||
"ATTACHMENT_INFO_FILE_SIZE" = "File Size";
|
||||
"ATTACHMENT_INFO_RESOLUTION" = "Resolution";
|
||||
"ATTACHMENT_INFO_DURATION" = "Duration";
|
||||
"MESSAGE_DELIVERY_STATUS_FAILED_SYNC" = "Failed to sync";
|
||||
"MESSAGE_DELIVERY_STATUS_SYNCING" = "Syncing";
|
||||
"MESSAGE_DELIVERY_FAILED_TITLE" = "Failed to send message";
|
||||
|
@ -601,6 +610,7 @@
|
|||
"context_menu_resync" = "Resync";
|
||||
"GIPHY_PERMISSION_TITLE" = "Search GIFs?";
|
||||
"GIPHY_PERMISSION_MESSAGE" = "Session will connect to Giphy to provide search results. You will not have full metadata protection when sending GIFs.";
|
||||
"message_info_title" = "Message Info";
|
||||
"mute_button_text" = "Mute";
|
||||
"unmute_button_text" = "Unmute";
|
||||
"mark_read_button_text" = "Mark read";
|
||||
|
@ -613,3 +623,6 @@
|
|||
"group_unable_to_leave" = "Unable to leave the Group, please try again";
|
||||
"delete_conversation_confirmation_alert_message" = "Are you sure you want to delete your conversation with %@?";
|
||||
"delete_conversation_confirmation_alert_title" = "Delete Conversation";
|
||||
"update_profile_modal_title" = "Set Display Picture";
|
||||
"update_profile_modal_upload" = "Upload";
|
||||
"update_profile_modal_remove" = "Remove";
|
||||
|
|
|
@ -366,6 +366,7 @@
|
|||
"delete_message_for_me" = "Verwijder alleen voor mij";
|
||||
"delete_message_for_everyone" = "Verwijder voor iedereen";
|
||||
"delete_message_for_me_and_recipient" = "Verwijderen voor mij en %@";
|
||||
"context_menu_info" = "Info";
|
||||
"context_menu_reply" = "Antwoord";
|
||||
"context_menu_save" = "Opslaan";
|
||||
"context_menu_ban_user" = "Gebruiker verbannen";
|
||||
|
@ -592,6 +593,14 @@
|
|||
"MESSAGE_DELIVERY_STATUS_SENT" = "Sent";
|
||||
"MESSAGE_DELIVERY_STATUS_READ" = "Read";
|
||||
"MESSAGE_DELIVERY_STATUS_FAILED" = "Failed to send";
|
||||
"MESSAGE_INFO_SENT" = "Sent";
|
||||
"MESSAGE_INFO_RECEIVED" = "Received";
|
||||
"MESSAGE_INFO_FROM" = "From";
|
||||
"ATTACHMENT_INFO_FILE_ID" = "File ID";
|
||||
"ATTACHMENT_INFO_FILE_TYPE" = "File Type";
|
||||
"ATTACHMENT_INFO_FILE_SIZE" = "File Size";
|
||||
"ATTACHMENT_INFO_RESOLUTION" = "Resolution";
|
||||
"ATTACHMENT_INFO_DURATION" = "Duration";
|
||||
"MESSAGE_DELIVERY_STATUS_FAILED_SYNC" = "Failed to sync";
|
||||
"MESSAGE_DELIVERY_STATUS_SYNCING" = "Syncing";
|
||||
"MESSAGE_DELIVERY_FAILED_TITLE" = "Failed to send message";
|
||||
|
@ -601,6 +610,7 @@
|
|||
"context_menu_resync" = "Resync";
|
||||
"GIPHY_PERMISSION_TITLE" = "Search GIFs?";
|
||||
"GIPHY_PERMISSION_MESSAGE" = "Session will connect to Giphy to provide search results. You will not have full metadata protection when sending GIFs.";
|
||||
"message_info_title" = "Message Info";
|
||||
"mute_button_text" = "Mute";
|
||||
"unmute_button_text" = "Unmute";
|
||||
"mark_read_button_text" = "Mark read";
|
||||
|
@ -613,3 +623,6 @@
|
|||
"group_unable_to_leave" = "Unable to leave the Group, please try again";
|
||||
"delete_conversation_confirmation_alert_message" = "Are you sure you want to delete your conversation with %@?";
|
||||
"delete_conversation_confirmation_alert_title" = "Delete Conversation";
|
||||
"update_profile_modal_title" = "Set Display Picture";
|
||||
"update_profile_modal_upload" = "Upload";
|
||||
"update_profile_modal_remove" = "Remove";
|
||||
|
|
|
@ -366,11 +366,12 @@
|
|||
"delete_message_for_me" = "Usuń tylko dla mnie";
|
||||
"delete_message_for_everyone" = "Usuń dla wszystkich";
|
||||
"delete_message_for_me_and_recipient" = "Usuń dla mnie i %@";
|
||||
"context_menu_ban_user_error_alert_message" = "Unable to ban user";
|
||||
"context_menu_info" = "Info";
|
||||
"context_menu_reply" = "Odpowiedz";
|
||||
"context_menu_save" = "Zapisz";
|
||||
"context_menu_ban_user" = "Zbanuj użytkownika";
|
||||
"context_menu_ban_and_delete_all" = "Zbanuj i usuń wszystko";
|
||||
"context_menu_ban_user_error_alert_message" = "Unable to ban user";
|
||||
"accessibility_expanding_attachments_button" = "Dodaj załączniki";
|
||||
"accessibility_gif_button" = "Gif";
|
||||
"accessibility_document_button" = "Dokument";
|
||||
|
@ -592,6 +593,14 @@
|
|||
"MESSAGE_DELIVERY_STATUS_SENT" = "Sent";
|
||||
"MESSAGE_DELIVERY_STATUS_READ" = "Read";
|
||||
"MESSAGE_DELIVERY_STATUS_FAILED" = "Failed to send";
|
||||
"MESSAGE_INFO_SENT" = "Sent";
|
||||
"MESSAGE_INFO_RECEIVED" = "Received";
|
||||
"MESSAGE_INFO_FROM" = "From";
|
||||
"ATTACHMENT_INFO_FILE_ID" = "File ID";
|
||||
"ATTACHMENT_INFO_FILE_TYPE" = "File Type";
|
||||
"ATTACHMENT_INFO_FILE_SIZE" = "File Size";
|
||||
"ATTACHMENT_INFO_RESOLUTION" = "Resolution";
|
||||
"ATTACHMENT_INFO_DURATION" = "Duration";
|
||||
"MESSAGE_DELIVERY_STATUS_FAILED_SYNC" = "Failed to sync";
|
||||
"MESSAGE_DELIVERY_STATUS_SYNCING" = "Syncing";
|
||||
"MESSAGE_DELIVERY_FAILED_TITLE" = "Failed to send message";
|
||||
|
@ -601,6 +610,7 @@
|
|||
"context_menu_resync" = "Resync";
|
||||
"GIPHY_PERMISSION_TITLE" = "Search GIFs?";
|
||||
"GIPHY_PERMISSION_MESSAGE" = "Session will connect to Giphy to provide search results. You will not have full metadata protection when sending GIFs.";
|
||||
"message_info_title" = "Message Info";
|
||||
"mute_button_text" = "Mute";
|
||||
"unmute_button_text" = "Unmute";
|
||||
"mark_read_button_text" = "Mark read";
|
||||
|
@ -613,3 +623,6 @@
|
|||
"group_unable_to_leave" = "Unable to leave the Group, please try again";
|
||||
"delete_conversation_confirmation_alert_message" = "Are you sure you want to delete your conversation with %@?";
|
||||
"delete_conversation_confirmation_alert_title" = "Delete Conversation";
|
||||
"update_profile_modal_title" = "Set Display Picture";
|
||||
"update_profile_modal_upload" = "Upload";
|
||||
"update_profile_modal_remove" = "Remove";
|
||||
|
|
|
@ -366,6 +366,7 @@
|
|||
"delete_message_for_me" = "Apagar para mim";
|
||||
"delete_message_for_everyone" = "Apagar para todos";
|
||||
"delete_message_for_me_and_recipient" = "Apagar para mim e para %@";
|
||||
"context_menu_info" = "Info";
|
||||
"context_menu_reply" = "Responder";
|
||||
"context_menu_save" = "Salvar";
|
||||
"context_menu_ban_user" = "Banir Usuário";
|
||||
|
@ -592,6 +593,14 @@
|
|||
"MESSAGE_DELIVERY_STATUS_SENT" = "Sent";
|
||||
"MESSAGE_DELIVERY_STATUS_READ" = "Read";
|
||||
"MESSAGE_DELIVERY_STATUS_FAILED" = "Failed to send";
|
||||
"MESSAGE_INFO_SENT" = "Sent";
|
||||
"MESSAGE_INFO_RECEIVED" = "Received";
|
||||
"MESSAGE_INFO_FROM" = "From";
|
||||
"ATTACHMENT_INFO_FILE_ID" = "File ID";
|
||||
"ATTACHMENT_INFO_FILE_TYPE" = "File Type";
|
||||
"ATTACHMENT_INFO_FILE_SIZE" = "File Size";
|
||||
"ATTACHMENT_INFO_RESOLUTION" = "Resolution";
|
||||
"ATTACHMENT_INFO_DURATION" = "Duration";
|
||||
"MESSAGE_DELIVERY_STATUS_FAILED_SYNC" = "Failed to sync";
|
||||
"MESSAGE_DELIVERY_STATUS_SYNCING" = "Syncing";
|
||||
"MESSAGE_DELIVERY_FAILED_TITLE" = "Failed to send message";
|
||||
|
@ -601,6 +610,7 @@
|
|||
"context_menu_resync" = "Resync";
|
||||
"GIPHY_PERMISSION_TITLE" = "Search GIFs?";
|
||||
"GIPHY_PERMISSION_MESSAGE" = "Session will connect to Giphy to provide search results. You will not have full metadata protection when sending GIFs.";
|
||||
"message_info_title" = "Message Info";
|
||||
"mute_button_text" = "Mute";
|
||||
"unmute_button_text" = "Unmute";
|
||||
"mark_read_button_text" = "Mark read";
|
||||
|
@ -613,3 +623,6 @@
|
|||
"group_unable_to_leave" = "Unable to leave the Group, please try again";
|
||||
"delete_conversation_confirmation_alert_message" = "Are you sure you want to delete your conversation with %@?";
|
||||
"delete_conversation_confirmation_alert_title" = "Delete Conversation";
|
||||
"update_profile_modal_title" = "Set Display Picture";
|
||||
"update_profile_modal_upload" = "Upload";
|
||||
"update_profile_modal_remove" = "Remove";
|
||||
|
|
|
@ -366,6 +366,7 @@
|
|||
"delete_message_for_me" = "Удалить только для меня";
|
||||
"delete_message_for_everyone" = "Удалить для всех";
|
||||
"delete_message_for_me_and_recipient" = "Удалить для меня и %@";
|
||||
"context_menu_info" = "Info";
|
||||
"context_menu_reply" = "Ответить";
|
||||
"context_menu_save" = "Сохранить";
|
||||
"context_menu_ban_user" = "Заблокировать пользователя";
|
||||
|
@ -592,6 +593,14 @@
|
|||
"MESSAGE_DELIVERY_STATUS_SENT" = "Sent";
|
||||
"MESSAGE_DELIVERY_STATUS_READ" = "Read";
|
||||
"MESSAGE_DELIVERY_STATUS_FAILED" = "Failed to send";
|
||||
"MESSAGE_INFO_SENT" = "Sent";
|
||||
"MESSAGE_INFO_RECEIVED" = "Received";
|
||||
"MESSAGE_INFO_FROM" = "From";
|
||||
"ATTACHMENT_INFO_FILE_ID" = "File ID";
|
||||
"ATTACHMENT_INFO_FILE_TYPE" = "File Type";
|
||||
"ATTACHMENT_INFO_FILE_SIZE" = "File Size";
|
||||
"ATTACHMENT_INFO_RESOLUTION" = "Resolution";
|
||||
"ATTACHMENT_INFO_DURATION" = "Duration";
|
||||
"MESSAGE_DELIVERY_STATUS_FAILED_SYNC" = "Failed to sync";
|
||||
"MESSAGE_DELIVERY_STATUS_SYNCING" = "Syncing";
|
||||
"MESSAGE_DELIVERY_FAILED_TITLE" = "Failed to send message";
|
||||
|
@ -601,6 +610,7 @@
|
|||
"context_menu_resync" = "Resync";
|
||||
"GIPHY_PERMISSION_TITLE" = "Search GIFs?";
|
||||
"GIPHY_PERMISSION_MESSAGE" = "Session will connect to Giphy to provide search results. You will not have full metadata protection when sending GIFs.";
|
||||
"message_info_title" = "Message Info";
|
||||
"mute_button_text" = "Mute";
|
||||
"unmute_button_text" = "Unmute";
|
||||
"mark_read_button_text" = "Mark read";
|
||||
|
@ -613,3 +623,6 @@
|
|||
"group_unable_to_leave" = "Unable to leave the Group, please try again";
|
||||
"delete_conversation_confirmation_alert_message" = "Are you sure you want to delete your conversation with %@?";
|
||||
"delete_conversation_confirmation_alert_title" = "Delete Conversation";
|
||||
"update_profile_modal_title" = "Set Display Picture";
|
||||
"update_profile_modal_upload" = "Upload";
|
||||
"update_profile_modal_remove" = "Remove";
|
||||
|
|
|
@ -366,6 +366,7 @@
|
|||
"delete_message_for_me" = "Delete just for me";
|
||||
"delete_message_for_everyone" = "Delete for everyone";
|
||||
"delete_message_for_me_and_recipient" = "Delete for me and %@";
|
||||
"context_menu_info" = "Info";
|
||||
"context_menu_reply" = "පිළිතුරු";
|
||||
"context_menu_save" = "සුරකින්න";
|
||||
"context_menu_ban_user" = "Ban User";
|
||||
|
@ -592,6 +593,14 @@
|
|||
"MESSAGE_DELIVERY_STATUS_SENT" = "Sent";
|
||||
"MESSAGE_DELIVERY_STATUS_READ" = "Read";
|
||||
"MESSAGE_DELIVERY_STATUS_FAILED" = "Failed to send";
|
||||
"MESSAGE_INFO_SENT" = "Sent";
|
||||
"MESSAGE_INFO_RECEIVED" = "Received";
|
||||
"MESSAGE_INFO_FROM" = "From";
|
||||
"ATTACHMENT_INFO_FILE_ID" = "File ID";
|
||||
"ATTACHMENT_INFO_FILE_TYPE" = "File Type";
|
||||
"ATTACHMENT_INFO_FILE_SIZE" = "File Size";
|
||||
"ATTACHMENT_INFO_RESOLUTION" = "Resolution";
|
||||
"ATTACHMENT_INFO_DURATION" = "Duration";
|
||||
"MESSAGE_DELIVERY_STATUS_FAILED_SYNC" = "Failed to sync";
|
||||
"MESSAGE_DELIVERY_STATUS_SYNCING" = "Syncing";
|
||||
"MESSAGE_DELIVERY_FAILED_TITLE" = "Failed to send message";
|
||||
|
@ -601,6 +610,7 @@
|
|||
"context_menu_resync" = "Resync";
|
||||
"GIPHY_PERMISSION_TITLE" = "Search GIFs?";
|
||||
"GIPHY_PERMISSION_MESSAGE" = "Session will connect to Giphy to provide search results. You will not have full metadata protection when sending GIFs.";
|
||||
"message_info_title" = "Message Info";
|
||||
"mute_button_text" = "Mute";
|
||||
"unmute_button_text" = "Unmute";
|
||||
"mark_read_button_text" = "Mark read";
|
||||
|
@ -613,3 +623,6 @@
|
|||
"group_unable_to_leave" = "Unable to leave the Group, please try again";
|
||||
"delete_conversation_confirmation_alert_message" = "Are you sure you want to delete your conversation with %@?";
|
||||
"delete_conversation_confirmation_alert_title" = "Delete Conversation";
|
||||
"update_profile_modal_title" = "Set Display Picture";
|
||||
"update_profile_modal_upload" = "Upload";
|
||||
"update_profile_modal_remove" = "Remove";
|
||||
|
|
|
@ -366,6 +366,7 @@
|
|||
"delete_message_for_me" = "Vymazať len u mňa";
|
||||
"delete_message_for_everyone" = "Vymazať u všetkých";
|
||||
"delete_message_for_me_and_recipient" = "Vymazať pre mňa a %@";
|
||||
"context_menu_info" = "Info";
|
||||
"context_menu_reply" = "Odpovedať";
|
||||
"context_menu_save" = "Uložiť";
|
||||
"context_menu_ban_user" = "Zablokovanie používateľa";
|
||||
|
@ -592,6 +593,14 @@
|
|||
"MESSAGE_DELIVERY_STATUS_SENT" = "Sent";
|
||||
"MESSAGE_DELIVERY_STATUS_READ" = "Read";
|
||||
"MESSAGE_DELIVERY_STATUS_FAILED" = "Failed to send";
|
||||
"MESSAGE_INFO_SENT" = "Sent";
|
||||
"MESSAGE_INFO_RECEIVED" = "Received";
|
||||
"MESSAGE_INFO_FROM" = "From";
|
||||
"ATTACHMENT_INFO_FILE_ID" = "File ID";
|
||||
"ATTACHMENT_INFO_FILE_TYPE" = "File Type";
|
||||
"ATTACHMENT_INFO_FILE_SIZE" = "File Size";
|
||||
"ATTACHMENT_INFO_RESOLUTION" = "Resolution";
|
||||
"ATTACHMENT_INFO_DURATION" = "Duration";
|
||||
"MESSAGE_DELIVERY_STATUS_FAILED_SYNC" = "Failed to sync";
|
||||
"MESSAGE_DELIVERY_STATUS_SYNCING" = "Syncing";
|
||||
"MESSAGE_DELIVERY_FAILED_TITLE" = "Failed to send message";
|
||||
|
@ -601,6 +610,7 @@
|
|||
"context_menu_resync" = "Resync";
|
||||
"GIPHY_PERMISSION_TITLE" = "Search GIFs?";
|
||||
"GIPHY_PERMISSION_MESSAGE" = "Session will connect to Giphy to provide search results. You will not have full metadata protection when sending GIFs.";
|
||||
"message_info_title" = "Message Info";
|
||||
"mute_button_text" = "Mute";
|
||||
"unmute_button_text" = "Unmute";
|
||||
"mark_read_button_text" = "Mark read";
|
||||
|
@ -613,3 +623,6 @@
|
|||
"group_unable_to_leave" = "Unable to leave the Group, please try again";
|
||||
"delete_conversation_confirmation_alert_message" = "Are you sure you want to delete your conversation with %@?";
|
||||
"delete_conversation_confirmation_alert_title" = "Delete Conversation";
|
||||
"update_profile_modal_title" = "Set Display Picture";
|
||||
"update_profile_modal_upload" = "Upload";
|
||||
"update_profile_modal_remove" = "Remove";
|
||||
|
|
|
@ -366,6 +366,7 @@
|
|||
"delete_message_for_me" = "Delete just for me";
|
||||
"delete_message_for_everyone" = "Delete for everyone";
|
||||
"delete_message_for_me_and_recipient" = "Delete for me and %@";
|
||||
"context_menu_info" = "Info";
|
||||
"context_menu_reply" = "Reply";
|
||||
"context_menu_save" = "Spara";
|
||||
"context_menu_ban_user" = "Ban User";
|
||||
|
@ -592,6 +593,14 @@
|
|||
"MESSAGE_DELIVERY_STATUS_SENT" = "Sent";
|
||||
"MESSAGE_DELIVERY_STATUS_READ" = "Read";
|
||||
"MESSAGE_DELIVERY_STATUS_FAILED" = "Failed to send";
|
||||
"MESSAGE_INFO_SENT" = "Sent";
|
||||
"MESSAGE_INFO_RECEIVED" = "Received";
|
||||
"MESSAGE_INFO_FROM" = "From";
|
||||
"ATTACHMENT_INFO_FILE_ID" = "File ID";
|
||||
"ATTACHMENT_INFO_FILE_TYPE" = "File Type";
|
||||
"ATTACHMENT_INFO_FILE_SIZE" = "File Size";
|
||||
"ATTACHMENT_INFO_RESOLUTION" = "Resolution";
|
||||
"ATTACHMENT_INFO_DURATION" = "Duration";
|
||||
"MESSAGE_DELIVERY_STATUS_FAILED_SYNC" = "Failed to sync";
|
||||
"MESSAGE_DELIVERY_STATUS_SYNCING" = "Syncing";
|
||||
"MESSAGE_DELIVERY_FAILED_TITLE" = "Failed to send message";
|
||||
|
@ -601,6 +610,7 @@
|
|||
"context_menu_resync" = "Resync";
|
||||
"GIPHY_PERMISSION_TITLE" = "Search GIFs?";
|
||||
"GIPHY_PERMISSION_MESSAGE" = "Session will connect to Giphy to provide search results. You will not have full metadata protection when sending GIFs.";
|
||||
"message_info_title" = "Message Info";
|
||||
"mute_button_text" = "Mute";
|
||||
"unmute_button_text" = "Unmute";
|
||||
"mark_read_button_text" = "Mark read";
|
||||
|
@ -613,3 +623,6 @@
|
|||
"group_unable_to_leave" = "Unable to leave the Group, please try again";
|
||||
"delete_conversation_confirmation_alert_message" = "Are you sure you want to delete your conversation with %@?";
|
||||
"delete_conversation_confirmation_alert_title" = "Delete Conversation";
|
||||
"update_profile_modal_title" = "Set Display Picture";
|
||||
"update_profile_modal_upload" = "Upload";
|
||||
"update_profile_modal_remove" = "Remove";
|
||||
|
|
|
@ -366,6 +366,7 @@
|
|||
"delete_message_for_me" = "Delete just for me";
|
||||
"delete_message_for_everyone" = "Delete for everyone";
|
||||
"delete_message_for_me_and_recipient" = "Delete for me and %@";
|
||||
"context_menu_info" = "Info";
|
||||
"context_menu_reply" = "Reply";
|
||||
"context_menu_save" = "Save";
|
||||
"context_menu_ban_user" = "Ban User";
|
||||
|
@ -592,6 +593,14 @@
|
|||
"MESSAGE_DELIVERY_STATUS_SENT" = "Sent";
|
||||
"MESSAGE_DELIVERY_STATUS_READ" = "Read";
|
||||
"MESSAGE_DELIVERY_STATUS_FAILED" = "Failed to send";
|
||||
"MESSAGE_INFO_SENT" = "Sent";
|
||||
"MESSAGE_INFO_RECEIVED" = "Received";
|
||||
"MESSAGE_INFO_FROM" = "From";
|
||||
"ATTACHMENT_INFO_FILE_ID" = "File ID";
|
||||
"ATTACHMENT_INFO_FILE_TYPE" = "File Type";
|
||||
"ATTACHMENT_INFO_FILE_SIZE" = "File Size";
|
||||
"ATTACHMENT_INFO_RESOLUTION" = "Resolution";
|
||||
"ATTACHMENT_INFO_DURATION" = "Duration";
|
||||
"MESSAGE_DELIVERY_STATUS_FAILED_SYNC" = "Failed to sync";
|
||||
"MESSAGE_DELIVERY_STATUS_SYNCING" = "Syncing";
|
||||
"MESSAGE_DELIVERY_FAILED_TITLE" = "Failed to send message";
|
||||
|
@ -601,6 +610,7 @@
|
|||
"context_menu_resync" = "Resync";
|
||||
"GIPHY_PERMISSION_TITLE" = "Search GIFs?";
|
||||
"GIPHY_PERMISSION_MESSAGE" = "Session will connect to Giphy to provide search results. You will not have full metadata protection when sending GIFs.";
|
||||
"message_info_title" = "Message Info";
|
||||
"mute_button_text" = "Mute";
|
||||
"unmute_button_text" = "Unmute";
|
||||
"mark_read_button_text" = "Mark read";
|
||||
|
@ -613,3 +623,6 @@
|
|||
"group_unable_to_leave" = "Unable to leave the Group, please try again";
|
||||
"delete_conversation_confirmation_alert_message" = "Are you sure you want to delete your conversation with %@?";
|
||||
"delete_conversation_confirmation_alert_title" = "Delete Conversation";
|
||||
"update_profile_modal_title" = "Set Display Picture";
|
||||
"update_profile_modal_upload" = "Upload";
|
||||
"update_profile_modal_remove" = "Remove";
|
||||
|
|
|
@ -366,6 +366,7 @@
|
|||
"delete_message_for_me" = "Delete just for me";
|
||||
"delete_message_for_everyone" = "Delete for everyone";
|
||||
"delete_message_for_me_and_recipient" = "Delete for me and %@";
|
||||
"context_menu_info" = "Info";
|
||||
"context_menu_reply" = "Reply";
|
||||
"context_menu_save" = "Save";
|
||||
"context_menu_ban_user" = "Ban User";
|
||||
|
@ -592,6 +593,14 @@
|
|||
"MESSAGE_DELIVERY_STATUS_SENT" = "Sent";
|
||||
"MESSAGE_DELIVERY_STATUS_READ" = "Read";
|
||||
"MESSAGE_DELIVERY_STATUS_FAILED" = "Failed to send";
|
||||
"MESSAGE_INFO_SENT" = "Sent";
|
||||
"MESSAGE_INFO_RECEIVED" = "Received";
|
||||
"MESSAGE_INFO_FROM" = "From";
|
||||
"ATTACHMENT_INFO_FILE_ID" = "File ID";
|
||||
"ATTACHMENT_INFO_FILE_TYPE" = "File Type";
|
||||
"ATTACHMENT_INFO_FILE_SIZE" = "File Size";
|
||||
"ATTACHMENT_INFO_RESOLUTION" = "Resolution";
|
||||
"ATTACHMENT_INFO_DURATION" = "Duration";
|
||||
"MESSAGE_DELIVERY_STATUS_FAILED_SYNC" = "Failed to sync";
|
||||
"MESSAGE_DELIVERY_STATUS_SYNCING" = "Syncing";
|
||||
"MESSAGE_DELIVERY_FAILED_TITLE" = "Failed to send message";
|
||||
|
@ -601,6 +610,7 @@
|
|||
"context_menu_resync" = "Resync";
|
||||
"GIPHY_PERMISSION_TITLE" = "Search GIFs?";
|
||||
"GIPHY_PERMISSION_MESSAGE" = "Session will connect to Giphy to provide search results. You will not have full metadata protection when sending GIFs.";
|
||||
"message_info_title" = "Message Info";
|
||||
"mute_button_text" = "Mute";
|
||||
"unmute_button_text" = "Unmute";
|
||||
"mark_read_button_text" = "Mark read";
|
||||
|
@ -613,3 +623,6 @@
|
|||
"group_unable_to_leave" = "Unable to leave the Group, please try again";
|
||||
"delete_conversation_confirmation_alert_message" = "Are you sure you want to delete your conversation with %@?";
|
||||
"delete_conversation_confirmation_alert_title" = "Delete Conversation";
|
||||
"update_profile_modal_title" = "Set Display Picture";
|
||||
"update_profile_modal_upload" = "Upload";
|
||||
"update_profile_modal_remove" = "Remove";
|
||||
|
|
|
@ -366,6 +366,7 @@
|
|||
"delete_message_for_me" = "只為我自己刪除";
|
||||
"delete_message_for_everyone" = "從所有人的裝置上刪除";
|
||||
"delete_message_for_me_and_recipient" = "為我和 %@ 刪除";
|
||||
"context_menu_info" = "Info";
|
||||
"context_menu_reply" = "回覆";
|
||||
"context_menu_save" = "儲存";
|
||||
"context_menu_ban_user" = "封鎖用戶";
|
||||
|
@ -592,6 +593,14 @@
|
|||
"MESSAGE_DELIVERY_STATUS_SENT" = "Sent";
|
||||
"MESSAGE_DELIVERY_STATUS_READ" = "Read";
|
||||
"MESSAGE_DELIVERY_STATUS_FAILED" = "Failed to send";
|
||||
"MESSAGE_INFO_SENT" = "Sent";
|
||||
"MESSAGE_INFO_RECEIVED" = "Received";
|
||||
"MESSAGE_INFO_FROM" = "From";
|
||||
"ATTACHMENT_INFO_FILE_ID" = "File ID";
|
||||
"ATTACHMENT_INFO_FILE_TYPE" = "File Type";
|
||||
"ATTACHMENT_INFO_FILE_SIZE" = "File Size";
|
||||
"ATTACHMENT_INFO_RESOLUTION" = "Resolution";
|
||||
"ATTACHMENT_INFO_DURATION" = "Duration";
|
||||
"MESSAGE_DELIVERY_STATUS_FAILED_SYNC" = "Failed to sync";
|
||||
"MESSAGE_DELIVERY_STATUS_SYNCING" = "Syncing";
|
||||
"MESSAGE_DELIVERY_FAILED_TITLE" = "Failed to send message";
|
||||
|
@ -601,6 +610,7 @@
|
|||
"context_menu_resync" = "Resync";
|
||||
"GIPHY_PERMISSION_TITLE" = "Search GIFs?";
|
||||
"GIPHY_PERMISSION_MESSAGE" = "Session will connect to Giphy to provide search results. You will not have full metadata protection when sending GIFs.";
|
||||
"message_info_title" = "Message Info";
|
||||
"mute_button_text" = "Mute";
|
||||
"unmute_button_text" = "Unmute";
|
||||
"mark_read_button_text" = "Mark read";
|
||||
|
@ -613,3 +623,6 @@
|
|||
"group_unable_to_leave" = "Unable to leave the Group, please try again";
|
||||
"delete_conversation_confirmation_alert_message" = "Are you sure you want to delete your conversation with %@?";
|
||||
"delete_conversation_confirmation_alert_title" = "Delete Conversation";
|
||||
"update_profile_modal_title" = "Set Display Picture";
|
||||
"update_profile_modal_upload" = "Upload";
|
||||
"update_profile_modal_remove" = "Remove";
|
||||
|
|
|
@ -366,6 +366,7 @@
|
|||
"delete_message_for_me" = "仅为我删除";
|
||||
"delete_message_for_everyone" = "为所有人删除";
|
||||
"delete_message_for_me_and_recipient" = "为我和 %@ 删除";
|
||||
"context_menu_info" = "Info";
|
||||
"context_menu_reply" = "回复";
|
||||
"context_menu_save" = "保存";
|
||||
"context_menu_ban_user" = "封禁用户";
|
||||
|
@ -592,6 +593,14 @@
|
|||
"MESSAGE_DELIVERY_STATUS_SENT" = "Sent";
|
||||
"MESSAGE_DELIVERY_STATUS_READ" = "Read";
|
||||
"MESSAGE_DELIVERY_STATUS_FAILED" = "Failed to send";
|
||||
"MESSAGE_INFO_SENT" = "Sent";
|
||||
"MESSAGE_INFO_RECEIVED" = "Received";
|
||||
"MESSAGE_INFO_FROM" = "From";
|
||||
"ATTACHMENT_INFO_FILE_ID" = "File ID";
|
||||
"ATTACHMENT_INFO_FILE_TYPE" = "File Type";
|
||||
"ATTACHMENT_INFO_FILE_SIZE" = "File Size";
|
||||
"ATTACHMENT_INFO_RESOLUTION" = "Resolution";
|
||||
"ATTACHMENT_INFO_DURATION" = "Duration";
|
||||
"MESSAGE_DELIVERY_STATUS_FAILED_SYNC" = "Failed to sync";
|
||||
"MESSAGE_DELIVERY_STATUS_SYNCING" = "Syncing";
|
||||
"MESSAGE_DELIVERY_FAILED_TITLE" = "Failed to send message";
|
||||
|
@ -601,6 +610,7 @@
|
|||
"context_menu_resync" = "Resync";
|
||||
"GIPHY_PERMISSION_TITLE" = "Search GIFs?";
|
||||
"GIPHY_PERMISSION_MESSAGE" = "Session will connect to Giphy to provide search results. You will not have full metadata protection when sending GIFs.";
|
||||
"message_info_title" = "Message Info";
|
||||
"mute_button_text" = "Mute";
|
||||
"unmute_button_text" = "Unmute";
|
||||
"mark_read_button_text" = "Mark read";
|
||||
|
@ -613,3 +623,6 @@
|
|||
"group_unable_to_leave" = "Unable to leave the Group, please try again";
|
||||
"delete_conversation_confirmation_alert_message" = "Are you sure you want to delete your conversation with %@?";
|
||||
"delete_conversation_confirmation_alert_title" = "Delete Conversation";
|
||||
"update_profile_modal_title" = "Set Display Picture";
|
||||
"update_profile_modal_upload" = "Upload";
|
||||
"update_profile_modal_remove" = "Remove";
|
||||
|
|
|
@ -158,7 +158,7 @@ final class DisplayNameVC: BaseVC {
|
|||
targetView: self.view,
|
||||
info: ConfirmationModal.Info(
|
||||
title: title,
|
||||
explanation: message,
|
||||
body: .text(message),
|
||||
cancelTitle: "BUTTON_OK".localized(),
|
||||
cancelStyle: .alert_text
|
||||
)
|
||||
|
|
|
@ -143,7 +143,7 @@ final class LinkDeviceVC: BaseVC, UIPageViewControllerDataSource, UIPageViewCont
|
|||
let modal: ConfirmationModal = ConfirmationModal(
|
||||
info: ConfirmationModal.Info(
|
||||
title: "invalid_recovery_phrase".localized(),
|
||||
explanation: "INVALID_RECOVERY_PHRASE_MESSAGE".localized(),
|
||||
body: .text("INVALID_RECOVERY_PHRASE_MESSAGE".localized()),
|
||||
cancelTitle: "BUTTON_OK".localized(),
|
||||
cancelStyle: .alert_text,
|
||||
afterClosed: { [weak self] in
|
||||
|
@ -321,7 +321,7 @@ private final class RecoveryPhraseVC: UIViewController {
|
|||
targetView: self.view,
|
||||
info: ConfirmationModal.Info(
|
||||
title: title,
|
||||
explanation: message,
|
||||
body: .text(message),
|
||||
cancelTitle: "BUTTON_OK".localized(),
|
||||
cancelStyle: .alert_text
|
||||
)
|
||||
|
|
|
@ -186,7 +186,7 @@ final class RestoreVC: BaseVC {
|
|||
let modal: ConfirmationModal = ConfirmationModal(
|
||||
info: ConfirmationModal.Info(
|
||||
title: title,
|
||||
explanation: message,
|
||||
body: .text(message),
|
||||
cancelTitle: "BUTTON_OK".localized(),
|
||||
cancelStyle: .alert_text
|
||||
)
|
||||
|
|
|
@ -211,7 +211,7 @@ final class JoinOpenGroupVC: BaseVC, UIPageViewControllerDataSource, UIPageViewC
|
|||
let confirmationModal: ConfirmationModal = ConfirmationModal(
|
||||
info: ConfirmationModal.Info(
|
||||
title: title,
|
||||
explanation: message,
|
||||
body: .text(message),
|
||||
cancelTitle: "BUTTON_OK".localized(),
|
||||
cancelStyle: .alert_text
|
||||
)
|
||||
|
|
|
@ -218,9 +218,21 @@ final class PathVC: BaseVC {
|
|||
}
|
||||
|
||||
private func getPathRow(snode: Snode, location: LineView.Location, dotAnimationStartDelay: Double, dotAnimationRepeatInterval: Double, isGuardSnode: Bool) -> UIStackView {
|
||||
let country = IP2Country.isInitialized ? (IP2Country.shared.countryNamesCache[snode.ip] ?? "Resolving...") : "Resolving..."
|
||||
let title = isGuardSnode ? NSLocalizedString("vc_path_guard_node_row_title", comment: "") : NSLocalizedString("vc_path_service_node_row_title", comment: "")
|
||||
return getPathRow(title: title, subtitle: country, location: location, dotAnimationStartDelay: dotAnimationStartDelay, dotAnimationRepeatInterval: dotAnimationRepeatInterval)
|
||||
let country: String = (IP2Country.isInitialized ?
|
||||
IP2Country.shared.countryNamesCache.wrappedValue[snode.ip].defaulting(to: "Resolving...") :
|
||||
"Resolving..."
|
||||
)
|
||||
|
||||
return getPathRow(
|
||||
title: (isGuardSnode ?
|
||||
"vc_path_guard_node_row_title".localized() :
|
||||
"vc_path_service_node_row_title".localized()
|
||||
),
|
||||
subtitle: country,
|
||||
location: location,
|
||||
dotAnimationStartDelay: dotAnimationStartDelay,
|
||||
dotAnimationRepeatInterval: dotAnimationRepeatInterval
|
||||
)
|
||||
}
|
||||
|
||||
// MARK: - Interaction
|
||||
|
|
|
@ -145,7 +145,10 @@ class BlockedContactsViewController: BaseVC, UITableViewDelegate, UITableViewDat
|
|||
}
|
||||
|
||||
@objc func applicationDidBecomeActive(_ notification: Notification) {
|
||||
startObservingChanges(didReturnFromBackground: true)
|
||||
/// Need to dispatch to the next run loop to prevent a possible crash caused by the database resuming mid-query
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
self?.startObservingChanges(didReturnFromBackground: true)
|
||||
}
|
||||
}
|
||||
|
||||
@objc func applicationDidResignActive(_ notification: Notification) {
|
||||
|
|
|
@ -9,8 +9,8 @@ import SignalUtilitiesKit
|
|||
final class NukeDataModal: Modal {
|
||||
// MARK: - Initialization
|
||||
|
||||
override init(targetView: UIView? = nil, afterClosed: (() -> ())? = nil) {
|
||||
super.init(targetView: targetView, afterClosed: afterClosed)
|
||||
override init(targetView: UIView? = nil, dismissType: DismissType = .recursive, afterClosed: (() -> ())? = nil) {
|
||||
super.init(targetView: targetView, dismissType: dismissType, afterClosed: afterClosed)
|
||||
|
||||
self.modalPresentationStyle = .overFullScreen
|
||||
self.modalTransitionStyle = .crossDissolve
|
||||
|
@ -135,7 +135,7 @@ final class NukeDataModal: Modal {
|
|||
let confirmationModal: ConfirmationModal = ConfirmationModal(
|
||||
info: ConfirmationModal.Info(
|
||||
title: "modal_clear_all_data_title".localized(),
|
||||
explanation: "modal_clear_all_data_explanation_2".localized(),
|
||||
body: .text("modal_clear_all_data_explanation_2".localized()),
|
||||
confirmTitle: "modal_clear_all_data_confirm".localized(),
|
||||
confirmStyle: .danger,
|
||||
cancelStyle: .alert_text,
|
||||
|
@ -184,7 +184,7 @@ final class NukeDataModal: Modal {
|
|||
targetView: self?.view,
|
||||
info: ConfirmationModal.Info(
|
||||
title: "ALERT_ERROR_TITLE".localized(),
|
||||
explanation: message,
|
||||
body: .text(message),
|
||||
cancelTitle: "BUTTON_OK".localized(),
|
||||
cancelStyle: .alert_text
|
||||
)
|
||||
|
@ -199,7 +199,7 @@ final class NukeDataModal: Modal {
|
|||
targetView: self?.view,
|
||||
info: ConfirmationModal.Info(
|
||||
title: "ALERT_ERROR_TITLE".localized(),
|
||||
explanation: error.localizedDescription,
|
||||
body: .text(error.localizedDescription),
|
||||
cancelTitle: "BUTTON_OK".localized(),
|
||||
cancelStyle: .alert_text
|
||||
)
|
||||
|
|
|
@ -209,8 +209,8 @@ class PrivacySettingsViewModel: SessionTableViewModel<PrivacySettingsViewModel.N
|
|||
accessibilityLabel: "Allow voice and video calls",
|
||||
confirmationInfo: ConfirmationModal.Info(
|
||||
title: "PRIVACY_CALLS_WARNING_TITLE".localized(),
|
||||
explanation: "PRIVACY_CALLS_WARNING_DESCRIPTION".localized(),
|
||||
stateToShow: .whenDisabled,
|
||||
body: .text("PRIVACY_CALLS_WARNING_DESCRIPTION".localized()),
|
||||
showCondition: .disabled,
|
||||
confirmTitle: "continue_2".localized(),
|
||||
confirmAccessibilityLabel: "Enable",
|
||||
confirmStyle: .textPrimary,
|
||||
|
|
|
@ -131,7 +131,7 @@ final class QRCodeVC : BaseVC, UIPageViewControllerDataSource, UIPageViewControl
|
|||
targetView: self.view,
|
||||
info: ConfirmationModal.Info(
|
||||
title: "invalid_session_id".localized(),
|
||||
explanation: "INVALID_SESSION_ID_MESSAGE".localized(),
|
||||
body: .text("INVALID_SESSION_ID_MESSAGE".localized()),
|
||||
cancelTitle: "BUTTON_OK".localized(),
|
||||
cancelStyle: .alert_text
|
||||
)
|
||||
|
|
|
@ -16,8 +16,8 @@ final class SeedModal: Modal {
|
|||
|
||||
// MARK: - Initialization
|
||||
|
||||
override init(targetView: UIView? = nil, afterClosed: (() -> ())? = nil) {
|
||||
super.init(targetView: targetView, afterClosed: afterClosed)
|
||||
override init(targetView: UIView? = nil, dismissType: DismissType = .recursive, afterClosed: (() -> ())? = nil) {
|
||||
super.init(targetView: targetView, dismissType: dismissType, afterClosed: afterClosed)
|
||||
|
||||
self.modalPresentationStyle = .overFullScreen
|
||||
self.modalTransitionStyle = .crossDissolve
|
||||
|
|
|
@ -50,6 +50,10 @@ class SettingsViewModel: SessionTableViewModel<SettingsViewModel.NavButton, Sett
|
|||
private lazy var imagePickerHandler: ImagePickerHandler = ImagePickerHandler(viewModel: self)
|
||||
fileprivate var oldDisplayName: String
|
||||
private var editedDisplayName: String?
|
||||
private var editProfilePictureModal: ConfirmationModal?
|
||||
private var editProfilePictureModalInfo: ConfirmationModal.Info?
|
||||
private var editedProfilePicture: UIImage?
|
||||
private var editedProfilePictureFileName: String?
|
||||
|
||||
// MARK: - Initialization
|
||||
|
||||
|
@ -102,11 +106,13 @@ class SettingsViewModel: SessionTableViewModel<SettingsViewModel.NavButton, Sett
|
|||
}
|
||||
|
||||
override var rightNavItems: AnyPublisher<[NavItem]?, Never> {
|
||||
navState
|
||||
.map { [weak self] navState -> [NavItem] in
|
||||
switch navState {
|
||||
case .standard:
|
||||
return [
|
||||
let userSessionId: String = self.userSessionId
|
||||
|
||||
return navState
|
||||
.map { [weak self] navState -> [NavItem] in
|
||||
switch navState {
|
||||
case .standard:
|
||||
return [
|
||||
NavItem(
|
||||
id: .qrCode,
|
||||
image: UIImage(named: "QRCode")?
|
||||
|
@ -117,10 +123,10 @@ class SettingsViewModel: SessionTableViewModel<SettingsViewModel.NavButton, Sett
|
|||
self?.transitionToScreen(QRCodeVC())
|
||||
}
|
||||
)
|
||||
]
|
||||
]
|
||||
|
||||
case .editing:
|
||||
return [
|
||||
case .editing:
|
||||
return [
|
||||
NavItem(
|
||||
id: .done,
|
||||
systemItem: .done,
|
||||
|
@ -161,7 +167,7 @@ class SettingsViewModel: SessionTableViewModel<SettingsViewModel.NavButton, Sett
|
|||
self?.updateProfile(
|
||||
name: updatedNickname,
|
||||
profilePicture: nil,
|
||||
profilePictureFilePath: nil,
|
||||
profilePictureFilePath: ProfileManager.profileAvatarFilepath(id: userSessionId),
|
||||
isUpdatingDisplayName: true,
|
||||
isUpdatingProfilePicture: false
|
||||
)
|
||||
|
@ -389,23 +395,90 @@ class SettingsViewModel: SessionTableViewModel<SettingsViewModel.NavButton, Sett
|
|||
}
|
||||
|
||||
private func updateProfilePicture() {
|
||||
let actionSheet: UIAlertController = UIAlertController(
|
||||
title: "Update Profile Picture",
|
||||
message: nil,
|
||||
preferredStyle: .actionSheet
|
||||
)
|
||||
let action = UIAlertAction(
|
||||
title: "MEDIA_FROM_LIBRARY_BUTTON".localized(),
|
||||
style: .default,
|
||||
handler: { [weak self] _ in
|
||||
self?.showPhotoLibraryForAvatar()
|
||||
let existingDisplayName: String = self.oldDisplayName
|
||||
let existingImage: UIImage? = ProfileManager
|
||||
.profileAvatar(id: self.userSessionId)
|
||||
.map { UIImage(data: $0) }
|
||||
let editProfilePictureModalInfo: ConfirmationModal.Info = ConfirmationModal.Info(
|
||||
title: "update_profile_modal_title".localized(),
|
||||
body: .image(
|
||||
placeholder: UIImage(named: "profile_placeholder"),
|
||||
value: existingImage,
|
||||
style: .circular,
|
||||
onClick: { [weak self] in self?.showPhotoLibraryForAvatar() }
|
||||
),
|
||||
confirmTitle: "update_profile_modal_upload".localized(),
|
||||
confirmEnabled: false,
|
||||
cancelTitle: "update_profile_modal_remove".localized(),
|
||||
cancelEnabled: (existingImage != nil),
|
||||
hasCloseButton: true,
|
||||
dismissOnConfirm: false,
|
||||
onConfirm: { [weak self] modal in
|
||||
self?.updateProfile(
|
||||
name: existingDisplayName,
|
||||
profilePicture: self?.editedProfilePicture,
|
||||
profilePictureFilePath: self?.editedProfilePictureFileName,
|
||||
isUpdatingDisplayName: false,
|
||||
isUpdatingProfilePicture: true,
|
||||
onComplete: { [weak modal] in modal?.close() }
|
||||
)
|
||||
},
|
||||
onCancel: { [weak self] modal in
|
||||
self?.updateProfile(
|
||||
name: existingDisplayName,
|
||||
profilePicture: nil,
|
||||
profilePictureFilePath: nil,
|
||||
isUpdatingDisplayName: false,
|
||||
isUpdatingProfilePicture: true,
|
||||
onComplete: { [weak modal] in modal?.close() }
|
||||
)
|
||||
},
|
||||
afterClosed: { [weak self] in
|
||||
self?.editedProfilePicture = nil
|
||||
self?.editedProfilePictureFileName = nil
|
||||
self?.editProfilePictureModal = nil
|
||||
self?.editProfilePictureModalInfo = nil
|
||||
}
|
||||
)
|
||||
action.accessibilityLabel = "Photo library"
|
||||
actionSheet.addAction(action)
|
||||
actionSheet.addAction(UIAlertAction(title: "cancel".localized(), style: .cancel, handler: nil))
|
||||
let modal: ConfirmationModal = ConfirmationModal(info: editProfilePictureModalInfo)
|
||||
|
||||
self.editProfilePictureModalInfo = editProfilePictureModalInfo
|
||||
self.editProfilePictureModal = modal
|
||||
self.transitionToScreen(modal, transitionType: .present)
|
||||
}
|
||||
|
||||
fileprivate func updatedProfilePictureSelected(image: UIImage?, filePath: String?) {
|
||||
guard let info: ConfirmationModal.Info = self.editProfilePictureModalInfo else { return }
|
||||
|
||||
self.transitionToScreen(actionSheet, transitionType: .present)
|
||||
self.editedProfilePicture = image
|
||||
self.editedProfilePictureFileName = filePath
|
||||
|
||||
if let image: UIImage = image {
|
||||
self.editProfilePictureModal?.updateContent(
|
||||
with: info.with(
|
||||
body: .image(
|
||||
placeholder: UIImage(named: "profile_placeholder"),
|
||||
value: image,
|
||||
style: .circular,
|
||||
onClick: { [weak self] in self?.showPhotoLibraryForAvatar() }
|
||||
),
|
||||
confirmEnabled: true
|
||||
)
|
||||
)
|
||||
}
|
||||
else if let filePath: String = filePath {
|
||||
self.editProfilePictureModal?.updateContent(
|
||||
with: info.with(
|
||||
body: .image(
|
||||
placeholder: UIImage(named: "profile_placeholder"),
|
||||
value: UIImage(contentsOfFile: filePath),
|
||||
style: .circular,
|
||||
onClick: { [weak self] in self?.showPhotoLibraryForAvatar() }
|
||||
),
|
||||
confirmEnabled: true
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private func showPhotoLibraryForAvatar() {
|
||||
|
@ -421,24 +494,20 @@ class SettingsViewModel: SessionTableViewModel<SettingsViewModel.NavButton, Sett
|
|||
}
|
||||
}
|
||||
|
||||
fileprivate func updateProfile(
|
||||
private func updateProfile(
|
||||
name: String,
|
||||
profilePicture: UIImage?,
|
||||
profilePictureFilePath: String?,
|
||||
isUpdatingDisplayName: Bool,
|
||||
isUpdatingProfilePicture: Bool
|
||||
isUpdatingProfilePicture: Bool,
|
||||
onComplete: (() -> ())? = nil
|
||||
) {
|
||||
let imageFilePath: String? = (
|
||||
profilePictureFilePath ??
|
||||
ProfileManager.profileAvatarFilepath(id: self.userSessionId)
|
||||
)
|
||||
|
||||
let viewController = ModalActivityIndicatorViewController(canCancel: false) { [weak self] modalActivityIndicator in
|
||||
ProfileManager.updateLocal(
|
||||
queue: DispatchQueue.global(qos: .default),
|
||||
profileName: name,
|
||||
image: profilePicture,
|
||||
imageFilePath: imageFilePath,
|
||||
imageFilePath: profilePictureFilePath,
|
||||
success: { db, updatedProfile in
|
||||
if isUpdatingDisplayName {
|
||||
UserDefaults.standard[.lastDisplayNameUpdate] = Date()
|
||||
|
@ -453,7 +522,9 @@ class SettingsViewModel: SessionTableViewModel<SettingsViewModel.NavButton, Sett
|
|||
// Wait for the database transaction to complete before updating the UI
|
||||
db.afterNextTransaction { _ in
|
||||
DispatchQueue.main.async {
|
||||
modalActivityIndicator.dismiss(completion: {})
|
||||
modalActivityIndicator.dismiss(completion: {
|
||||
onComplete?()
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -469,12 +540,13 @@ class SettingsViewModel: SessionTableViewModel<SettingsViewModel.NavButton, Sett
|
|||
"Maximum File Size Exceeded" :
|
||||
"Couldn't Update Profile"
|
||||
),
|
||||
explanation: (isMaxFileSizeExceeded ?
|
||||
body: .text(isMaxFileSizeExceeded ?
|
||||
"Please select a smaller photo and try again" :
|
||||
"Please check your internet connection and try again"
|
||||
),
|
||||
cancelTitle: "BUTTON_OK".localized(),
|
||||
cancelStyle: .alert_text
|
||||
cancelStyle: .alert_text,
|
||||
dismissType: .single
|
||||
)
|
||||
),
|
||||
transitionType: .present
|
||||
|
@ -497,8 +569,6 @@ class SettingsViewModel: SessionTableViewModel<SettingsViewModel.NavButton, Sett
|
|||
DispatchQueue.main.async {
|
||||
button.isUserInteractionEnabled = false
|
||||
|
||||
|
||||
|
||||
UIView.transition(
|
||||
with: button,
|
||||
duration: 0.25,
|
||||
|
@ -560,7 +630,6 @@ class ImagePickerHandler: NSObject, UIImagePickerControllerDelegate & UINavigati
|
|||
picker.presentingViewController?.dismiss(animated: true)
|
||||
return
|
||||
}
|
||||
let name: String = self.viewModel.oldDisplayName
|
||||
|
||||
picker.presentingViewController?.dismiss(animated: true) { [weak self] in
|
||||
// Check if the user selected an animated image (if so then don't crop, just
|
||||
|
@ -574,12 +643,9 @@ class ImagePickerHandler: NSObject, UIImagePickerControllerDelegate & UINavigati
|
|||
let viewController: CropScaleImageViewController = CropScaleImageViewController(
|
||||
srcImage: rawAvatar,
|
||||
successCompletion: { resultImage in
|
||||
self?.viewModel.updateProfile(
|
||||
name: name,
|
||||
profilePicture: resultImage,
|
||||
profilePictureFilePath: nil,
|
||||
isUpdatingDisplayName: false,
|
||||
isUpdatingProfilePicture: true
|
||||
self?.viewModel.updatedProfilePictureSelected(
|
||||
image: resultImage,
|
||||
filePath: nil
|
||||
)
|
||||
}
|
||||
)
|
||||
|
@ -587,12 +653,9 @@ class ImagePickerHandler: NSObject, UIImagePickerControllerDelegate & UINavigati
|
|||
return
|
||||
}
|
||||
|
||||
self?.viewModel.updateProfile(
|
||||
name: name,
|
||||
profilePicture: nil,
|
||||
profilePictureFilePath: imageUrl.path,
|
||||
isUpdatingDisplayName: false,
|
||||
isUpdatingProfilePicture: true
|
||||
self?.viewModel.updatedProfilePictureSelected(
|
||||
image: nil,
|
||||
filePath: imageUrl.path
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,6 +8,11 @@ import SessionMessagingKit
|
|||
public final class FullConversationCell: UITableViewCell {
|
||||
public static let unreadCountViewSize: CGFloat = 20
|
||||
private static let statusIndicatorSize: CGFloat = 14
|
||||
// If a message is much too long, it will take forever to calculate its width and
|
||||
// cause the app to be frozen. So if a search result string is longer than 100
|
||||
// characters, we assume it cannot be shown within one line and need to be truncated
|
||||
// to avoid the calculation.
|
||||
private static let maxApproxCharactersCanBeShownInOneLine: Int = 100
|
||||
|
||||
// MARK: - UI
|
||||
|
||||
|
@ -657,14 +662,25 @@ public final class FullConversationCell: UITableViewCell {
|
|||
return authorPrefix
|
||||
.appending(
|
||||
truncatingIfNeeded(
|
||||
approxWidth: (authorPrefix.size().width + result.size().width),
|
||||
approxWidth: (
|
||||
authorPrefix.size().width +
|
||||
(
|
||||
result.length > Self.maxApproxCharactersCanBeShownInOneLine ?
|
||||
bounds.width :
|
||||
result.size().width
|
||||
)
|
||||
),
|
||||
content: result
|
||||
)
|
||||
)
|
||||
}
|
||||
.defaulting(
|
||||
to: truncatingIfNeeded(
|
||||
approxWidth: result.size().width,
|
||||
approxWidth: (
|
||||
result.length > Self.maxApproxCharactersCanBeShownInOneLine ?
|
||||
bounds.width :
|
||||
result.size().width
|
||||
),
|
||||
content: result
|
||||
)
|
||||
)
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
||||
|
||||
import UIKit
|
||||
import SessionUIKit
|
||||
import SessionMessagingKit
|
||||
import SessionUtilitiesKit
|
||||
import SignalUtilitiesKit
|
||||
|
@ -271,7 +272,7 @@ class ScreenLockUI {
|
|||
targetView: screenBlockingWindow.rootViewController?.view,
|
||||
info: ConfirmationModal.Info(
|
||||
title: "SCREEN_LOCK_UNLOCK_FAILED".localized(),
|
||||
explanation: message,
|
||||
body: .text(message),
|
||||
cancelTitle: "BUTTON_OK".localized(),
|
||||
cancelStyle: .alert_text,
|
||||
afterClosed: { [weak self] in self?.ensureUI() } // After the alert, update the UI
|
||||
|
|
|
@ -0,0 +1,72 @@
|
|||
// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved.
|
||||
|
||||
import UIKit
|
||||
import SessionUIKit
|
||||
import SessionUtilitiesKit
|
||||
|
||||
extension SessionCarouselView {
|
||||
public struct Info {
|
||||
let slices: [UIView]
|
||||
let copyOfFirstSlice: UIView?
|
||||
let copyOfLastSlice: UIView?
|
||||
let sliceSize: CGSize
|
||||
let sliceCount: Int
|
||||
let shouldShowPageControl: Bool
|
||||
let pageControlStyle: PageControlStyle
|
||||
let shouldShowArrows: Bool
|
||||
let arrowsSize: CGSize
|
||||
let cornerRadius: CGFloat
|
||||
|
||||
// MARK: - Initialization
|
||||
|
||||
init(
|
||||
slices: [UIView] = [],
|
||||
copyOfFirstSlice: UIView? = nil,
|
||||
copyOfLastSlice: UIView? = nil,
|
||||
sliceSize: CGSize = .zero,
|
||||
shouldShowPageControl: Bool = true,
|
||||
pageControlStyle: PageControlStyle,
|
||||
shouldShowArrows: Bool = true,
|
||||
arrowsSize: CGSize = .zero,
|
||||
cornerRadius: CGFloat = 0
|
||||
) {
|
||||
self.slices = slices
|
||||
self.copyOfFirstSlice = copyOfFirstSlice
|
||||
self.copyOfLastSlice = copyOfLastSlice
|
||||
self.sliceSize = sliceSize
|
||||
self.sliceCount = slices.count
|
||||
self.shouldShowPageControl = shouldShowPageControl && (self.sliceCount > 1)
|
||||
self.pageControlStyle = pageControlStyle
|
||||
self.shouldShowArrows = shouldShowArrows && (self.sliceCount > 1)
|
||||
self.arrowsSize = arrowsSize
|
||||
self.cornerRadius = cornerRadius
|
||||
}
|
||||
}
|
||||
|
||||
public struct PageControlStyle {
|
||||
enum DotSize: CGFloat {
|
||||
case mini = 0.5
|
||||
case medium = 0.8
|
||||
case original = 1
|
||||
}
|
||||
|
||||
let height: CGFloat?
|
||||
let size: DotSize
|
||||
let backgroundColor: UIColor
|
||||
let bottomInset: CGFloat
|
||||
|
||||
// MARK: - Initialization
|
||||
|
||||
init(
|
||||
height: CGFloat? = nil,
|
||||
size: DotSize = .original,
|
||||
backgroundColor: UIColor = .clear,
|
||||
bottomInset: CGFloat = 0
|
||||
) {
|
||||
self.height = height
|
||||
self.size = size
|
||||
self.backgroundColor = backgroundColor
|
||||
self.bottomInset = bottomInset
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,202 @@
|
|||
// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved.
|
||||
|
||||
import UIKit
|
||||
import SessionUIKit
|
||||
import SessionUtilitiesKit
|
||||
|
||||
final class SessionCarouselView: UIView, UIScrollViewDelegate {
|
||||
private let slicesForLoop: [UIView]
|
||||
private let info: SessionCarouselView.Info
|
||||
var delegate: SessionCarouselViewDelegate?
|
||||
|
||||
// MARK: - UI
|
||||
private lazy var scrollView: UIScrollView = {
|
||||
let result: UIScrollView = UIScrollView()
|
||||
result.delegate = self
|
||||
result.isPagingEnabled = true
|
||||
result.showsHorizontalScrollIndicator = false
|
||||
result.showsVerticalScrollIndicator = false
|
||||
result.contentSize = CGSize(
|
||||
width: self.info.sliceSize.width * CGFloat(self.slicesForLoop.count),
|
||||
height: self.info.sliceSize.height
|
||||
)
|
||||
result.layer.cornerRadius = self.info.cornerRadius
|
||||
result.layer.masksToBounds = true
|
||||
|
||||
return result
|
||||
}()
|
||||
|
||||
private lazy var pageControl: UIPageControl = {
|
||||
let result: UIPageControl = UIPageControl()
|
||||
result.numberOfPages = self.info.sliceCount
|
||||
result.currentPage = 0
|
||||
result.isHidden = !self.info.shouldShowPageControl
|
||||
result.transform = CGAffineTransform(
|
||||
scaleX: self.info.pageControlStyle.size.rawValue,
|
||||
y: self.info.pageControlStyle.size.rawValue
|
||||
)
|
||||
|
||||
return result
|
||||
}()
|
||||
|
||||
private lazy var arrowLeft: UIButton = {
|
||||
let result = UIButton(type: .custom)
|
||||
result.setImage(UIImage(systemName: "chevron.left")?.withRenderingMode(.alwaysTemplate), for: .normal)
|
||||
result.addTarget(self, action: #selector(scrollToPreviousSlice), for: .touchUpInside)
|
||||
result.themeTintColor = .textPrimary
|
||||
result.set(.width, to: self.info.arrowsSize.width)
|
||||
result.set(.height, to: self.info.arrowsSize.height)
|
||||
result.isHidden = !self.info.shouldShowArrows
|
||||
|
||||
return result
|
||||
}()
|
||||
|
||||
private lazy var arrowRight: UIButton = {
|
||||
let result = UIButton(type: .custom)
|
||||
result.setImage(UIImage(systemName: "chevron.right")?.withRenderingMode(.alwaysTemplate), for: .normal)
|
||||
result.addTarget(self, action: #selector(scrollToNextSlice), for: .touchUpInside)
|
||||
result.themeTintColor = .textPrimary
|
||||
result.set(.width, to: self.info.arrowsSize.width)
|
||||
result.set(.height, to: self.info.arrowsSize.height)
|
||||
result.isHidden = !self.info.shouldShowArrows
|
||||
|
||||
return result
|
||||
}()
|
||||
|
||||
// MARK: - Lifecycle
|
||||
|
||||
init(info: SessionCarouselView.Info) {
|
||||
self.info = info
|
||||
if self.info.sliceCount > 1,
|
||||
let copyOfFirstSlice: UIView = self.info.copyOfFirstSlice,
|
||||
let copyOfLastSlice: UIView = self.info.copyOfLastSlice
|
||||
{
|
||||
self.slicesForLoop = [copyOfLastSlice]
|
||||
.appending(contentsOf: self.info.slices)
|
||||
.appending(copyOfFirstSlice)
|
||||
} else {
|
||||
self.slicesForLoop = self.info.slices
|
||||
}
|
||||
|
||||
super.init(frame: CGRect.zero)
|
||||
setUpViewHierarchy()
|
||||
}
|
||||
|
||||
override init(frame: CGRect) {
|
||||
preconditionFailure("Use init(attachment:) instead.")
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
preconditionFailure("Use init(attachment:) instead.")
|
||||
}
|
||||
|
||||
private func setUpViewHierarchy() {
|
||||
set(.width, to: self.info.sliceSize.width + Values.largeSpacing + 2 * self.info.arrowsSize.width)
|
||||
set(.height, to: self.info.sliceSize.height)
|
||||
|
||||
let stackView: UIStackView = UIStackView(arrangedSubviews: self.slicesForLoop)
|
||||
stackView.axis = .horizontal
|
||||
stackView.set(.width, to: self.info.sliceSize.width * CGFloat(self.slicesForLoop.count))
|
||||
stackView.set(.height, to: self.info.sliceSize.height)
|
||||
|
||||
addSubview(self.scrollView)
|
||||
scrollView.center(in: self)
|
||||
scrollView.set(.width, to: self.info.sliceSize.width)
|
||||
scrollView.set(.height, to: self.info.sliceSize.height)
|
||||
scrollView.addSubview(stackView)
|
||||
scrollView.setContentOffset(
|
||||
CGPoint(
|
||||
x: Int(self.info.sliceSize.width) * (self.info.sliceCount > 1 ? 1 : 0),
|
||||
y: 0
|
||||
),
|
||||
animated: false
|
||||
)
|
||||
|
||||
addSubview(self.pageControl)
|
||||
self.pageControl.center(.horizontal, in: self)
|
||||
self.pageControl.pin(.bottom, to: .bottom, of: self)
|
||||
|
||||
addSubview(self.arrowLeft)
|
||||
self.arrowLeft.pin(.leading, to: .leading, of: self)
|
||||
self.arrowLeft.center(.vertical, in: self)
|
||||
|
||||
addSubview(self.arrowRight)
|
||||
self.arrowRight.pin(.trailing, to: .trailing, of: self)
|
||||
self.arrowRight.center(.vertical, in: self)
|
||||
}
|
||||
|
||||
// MARK: - UIScrollViewDelegate
|
||||
|
||||
func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
||||
let pageIndex: Int = {
|
||||
let maybeCurrentPageIndex: Int = Int(round(scrollView.contentOffset.x/self.info.sliceSize.width))
|
||||
if self.info.sliceCount > 1 {
|
||||
if maybeCurrentPageIndex == 0 {
|
||||
return pageControl.numberOfPages - 1
|
||||
}
|
||||
if maybeCurrentPageIndex == self.slicesForLoop.count - 1 {
|
||||
return 0
|
||||
}
|
||||
return maybeCurrentPageIndex - 1
|
||||
}
|
||||
return maybeCurrentPageIndex
|
||||
}()
|
||||
|
||||
pageControl.currentPage = pageIndex
|
||||
}
|
||||
|
||||
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
|
||||
setCorrectCotentOffsetIfNeeded(scrollView)
|
||||
delegate?.carouselViewDidScrollToNewSlice(currentPage: pageControl.currentPage)
|
||||
}
|
||||
|
||||
func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) {
|
||||
setCorrectCotentOffsetIfNeeded(scrollView)
|
||||
delegate?.carouselViewDidScrollToNewSlice(currentPage: pageControl.currentPage)
|
||||
}
|
||||
|
||||
private func setCorrectCotentOffsetIfNeeded(_ scrollView: UIScrollView) {
|
||||
if pageControl.currentPage == 0 {
|
||||
scrollView.setContentOffset(
|
||||
CGPoint(
|
||||
x: Int(self.info.sliceSize.width) * 1,
|
||||
y: 0
|
||||
),
|
||||
animated: false
|
||||
)
|
||||
}
|
||||
|
||||
if pageControl.currentPage == pageControl.numberOfPages - 1 {
|
||||
let realLastIndex: Int = self.slicesForLoop.count - 2
|
||||
scrollView.setContentOffset(
|
||||
CGPoint(
|
||||
x: Int(self.info.sliceSize.width) * realLastIndex,
|
||||
y: 0
|
||||
),
|
||||
animated: false
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Interaction
|
||||
|
||||
@objc func scrollToNextSlice() {
|
||||
self.scrollView.setContentOffset(
|
||||
CGPoint(
|
||||
x: self.scrollView.contentOffset.x + self.info.sliceSize.width,
|
||||
y: 0
|
||||
),
|
||||
animated: true
|
||||
)
|
||||
}
|
||||
|
||||
@objc func scrollToPreviousSlice() {
|
||||
self.scrollView.setContentOffset(
|
||||
CGPoint(
|
||||
x: self.scrollView.contentOffset.x - self.info.sliceSize.width,
|
||||
y: 0
|
||||
),
|
||||
animated: true
|
||||
)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved.
|
||||
|
||||
import Foundation
|
||||
|
||||
public protocol SessionCarouselViewDelegate: AnyObject {
|
||||
func carouselViewDidScrollToNewSlice(currentPage: Int)
|
||||
}
|
|
@ -132,7 +132,10 @@ class SessionTableViewController<NavItemId: Equatable, Section: SessionTableSect
|
|||
}
|
||||
|
||||
@objc func applicationDidBecomeActive(_ notification: Notification) {
|
||||
startObservingChanges()
|
||||
/// Need to dispatch to the next run loop to prevent a possible crash caused by the database resuming mid-query
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
self?.startObservingChanges()
|
||||
}
|
||||
}
|
||||
|
||||
@objc func applicationDidResignActive(_ notification: Notification) {
|
||||
|
@ -336,13 +339,15 @@ class SessionTableViewController<NavItemId: Equatable, Section: SessionTableSect
|
|||
self?.navigationController?.pushViewController(viewController, animated: true)
|
||||
|
||||
case .present:
|
||||
let presenter: UIViewController? = (self?.presentedViewController ?? self)
|
||||
|
||||
if UIDevice.current.isIPad {
|
||||
viewController.popoverPresentationController?.permittedArrowDirections = []
|
||||
viewController.popoverPresentationController?.sourceView = self?.view
|
||||
viewController.popoverPresentationController?.sourceRect = (self?.view.bounds ?? UIScreen.main.bounds)
|
||||
viewController.popoverPresentationController?.sourceView = presenter?.view
|
||||
viewController.popoverPresentationController?.sourceRect = (presenter?.view.bounds ?? UIScreen.main.bounds)
|
||||
}
|
||||
|
||||
self?.present(viewController, animated: true)
|
||||
presenter?.present(viewController, animated: true)
|
||||
}
|
||||
}
|
||||
.store(in: &disposables)
|
||||
|
@ -520,7 +525,7 @@ class SessionTableViewController<NavItemId: Equatable, Section: SessionTableSect
|
|||
|
||||
guard
|
||||
let confirmationInfo: ConfirmationModal.Info = info.confirmationInfo,
|
||||
confirmationInfo.stateToShow.shouldShow(for: info.currentBoolValue)
|
||||
confirmationInfo.showCondition.shouldShow(for: info.currentBoolValue)
|
||||
else {
|
||||
performAction()
|
||||
return
|
||||
|
|
|
@ -301,19 +301,33 @@ extension SessionCell {
|
|||
let wasOldSelection: Bool = (!isSelected && storedSelection)
|
||||
|
||||
radioBorderView.isHidden = false
|
||||
radioBorderView.themeBorderColor = (isSelected ?
|
||||
.radioButton_selectedBorder :
|
||||
.radioButton_unselectedBorder
|
||||
)
|
||||
radioBorderView.themeBorderColor = {
|
||||
guard isEnabled else { return .radioButton_disabledBorder }
|
||||
|
||||
return (isSelected ?
|
||||
.radioButton_selectedBorder :
|
||||
.radioButton_unselectedBorder
|
||||
)
|
||||
}()
|
||||
|
||||
radioBorderView.layer.cornerRadius = (size.borderSize / 2)
|
||||
|
||||
radioView.accessibilityLabel = accessibilityLabel
|
||||
radioView.alpha = (wasOldSelection ? 0.3 : 1)
|
||||
radioView.isHidden = (!isSelected && !storedSelection)
|
||||
radioView.themeBackgroundColor = (isSelected || wasOldSelection ?
|
||||
.radioButton_selectedBackground :
|
||||
.radioButton_unselectedBackground
|
||||
)
|
||||
radioView.themeBackgroundColor = {
|
||||
guard isEnabled else {
|
||||
return (isSelected || wasOldSelection ?
|
||||
.radioButton_disabledSelectedBackground :
|
||||
.radioButton_disabledUnselectedBackground
|
||||
)
|
||||
}
|
||||
|
||||
return (isSelected || wasOldSelection ?
|
||||
.radioButton_selectedBackground :
|
||||
.radioButton_unselectedBackground
|
||||
)
|
||||
}()
|
||||
radioView.layer.cornerRadius = (size.selectionSize / 2)
|
||||
|
||||
radioViewWidthConstraint.constant = size.selectionSize
|
||||
|
|
|
@ -29,6 +29,14 @@ public extension Date {
|
|||
|
||||
return "DATE_NOW".localized()
|
||||
}
|
||||
|
||||
var fromattedForMessageInfo: String {
|
||||
let formatter: DateFormatter = DateFormatter()
|
||||
formatter.locale = Locale.current
|
||||
formatter.dateFormat = "h:mm a EEE, DD/MM/YYYY"
|
||||
|
||||
return formatter.string(from: self)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Formatters
|
||||
|
|