diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 000000000..c6b229539 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,31 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: 'bug' +assignees: '' + +--- + +#### Describe the bug. +A clear and concise description of what the bug is. + +#### Steps to reproduce: +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +#### Expected behavior +A clear and concise description of what you expected to happen. + +#### Screenshots +If applicable, add screenshots to help explain your problem. + +#### Contextual information: + + - Device: + - OS: + + - App Version: \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/config.yaml b/.github/ISSUE_TEMPLATE/config.yaml new file mode 100644 index 000000000..b30282798 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yaml @@ -0,0 +1,8 @@ +blank_issues_enabled: true +contact_links: + - name: Element iOS Community Support + url: "https://matrix.to/#/#element-ios:matrix.org" + about: General Element iOS support questions can be asked here. + - name: Matrix Security Policy + url: https://www.matrix.org/security-disclosure-policy/ + about: Learn more about our security disclosure policy. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 000000000..5a372c146 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: 'feature' +assignees: '' + +--- + +#### Is your feature request related to a problem? Please describe. +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +#### Describe the solution you'd like. +A clear and concise description of what you want to happen. + +#### Describe alternatives you've considered. +A clear and concise description of any alternative solutions or features you've considered. + +#### Additional context. +Add any other context or screenshots about the feature request here. \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci-build.yml similarity index 56% rename from .github/workflows/ci.yml rename to .github/workflows/ci-build.yml index 3e9e33f5c..d1cd1b3a9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci-build.yml @@ -1,4 +1,4 @@ -name: CI +name: Build CI on: # Triggers the workflow on any pull request and push to develop @@ -52,43 +52,3 @@ jobs: # Main step - name: Build iOS simulator run: bundle exec fastlane build - - - tests: - name: Tests - runs-on: macos-latest - - steps: - - uses: actions/checkout@v2 - - # Common cache - # Note: GH actions do not support yaml anchor yet. We need to duplicate this for every job - - uses: actions/cache@v2 - with: - path: Pods - key: ${{ runner.os }}-pods-${{ hashFiles('**/Podfile.lock') }} - restore-keys: | - ${{ runner.os }}-pods- - - uses: actions/cache@v2 - with: - path: vendor/bundle - key: ${{ runner.os }}-gems-${{ hashFiles('**/Gemfile.lock') }} - restore-keys: | - ${{ runner.os }}-gems- - - # Make sure we use the latest version of MatrixKit - - name: Reset MatrixKit pod - run: rm -rf Pods/MatrixKit - - # Common setup - # Note: GH actions do not support yaml anchor yet. We need to duplicate this for every job - - name: Bundle install - run: | - bundle config path vendor/bundle - bundle install --jobs 4 --retry 3 - - name: Use right MatrixKit and MatrixSDK versions - run: bundle exec fastlane point_dependencies_to_related_branches - - # Main step - - name: Unit tests - run: bundle exec fastlane test diff --git a/.github/workflows/ci-tests.yml b/.github/workflows/ci-tests.yml new file mode 100644 index 000000000..eac0c885d --- /dev/null +++ b/.github/workflows/ci-tests.yml @@ -0,0 +1,54 @@ +name: Tests CI + +on: + # Triggers the workflow on any pull request and push to develop + push: + branches: [ develop ] + pull_request: + + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + +env: + # Make the git branch for a PR available to our Fastfile + MX_GIT_BRANCH: ${{ github.event.pull_request.head.ref }} + +jobs: + tests: + name: Tests + runs-on: macos-latest + + steps: + - uses: actions/checkout@v2 + + # Common cache + # Note: GH actions do not support yaml anchor yet. We need to duplicate this for every job + - uses: actions/cache@v2 + with: + path: Pods + key: ${{ runner.os }}-pods-${{ hashFiles('**/Podfile.lock') }} + restore-keys: | + ${{ runner.os }}-pods- + - uses: actions/cache@v2 + with: + path: vendor/bundle + key: ${{ runner.os }}-gems-${{ hashFiles('**/Gemfile.lock') }} + restore-keys: | + ${{ runner.os }}-gems- + + # Make sure we use the latest version of MatrixKit + - name: Reset MatrixKit pod + run: rm -rf Pods/MatrixKit + + # Common setup + # Note: GH actions do not support yaml anchor yet. We need to duplicate this for every job + - name: Bundle install + run: | + bundle config path vendor/bundle + bundle install --jobs 4 --retry 3 + - name: Use right MatrixKit and MatrixSDK versions + run: bundle exec fastlane point_dependencies_to_related_branches + + # Main step + - name: Unit tests + run: bundle exec fastlane test diff --git a/.swiftlint.yml b/.swiftlint.yml index 8682b5912..765c414b4 100755 --- a/.swiftlint.yml +++ b/.swiftlint.yml @@ -2,7 +2,6 @@ disabled_rules: - trailing_whitespace - todo - - conditional_binding_cascade - mark - vertical_whitespace - syntactic_sugar diff --git a/CHANGES.rst b/CHANGES.rst index a262d8492..aada3fec2 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,13 +5,15 @@ Changes to be released in next version * 🙌 Improvements + * Settings: The notifications toggle no longer detects the system's "Deliver Quietly" configuration as disabled (#2368). + * Settings: Adds a link to open the Settings app to quickly configure app notifications. + * VoIP: Text & icon changes on call tiles (#4642). + * Voice messages: Stop recording and go into locked mode when the application becomes inactive (#4656) + * Voice messages: Allow voice message playback control from the iOS lock screen and control center (#4655) * MXSessionState: Use Swifty versions. 🐛 Bugfix - * VoIP: Do not present ended calls. - * More fixes to Main.storyboard layout on iPhone 12 Pro Max (#4527) - * Fix crash on Apple Silicon Macs. - * Media Picker: Generate video thumbnails with the correct orientation (#4515). + * ⚠️ API Changes * @@ -25,6 +27,164 @@ Changes to be released in next version Others * +Changes in 1.4.9 (2021-08-03) +================================================= + +✨ Features + * + +🙌 Improvements + * Voice Messages: Increased recording state microphone icon size + * Voice Messages: Using "Voice message - MM.dd.yyyy HH.mm.ss" as the format for recorded audio files + +🐛 Bugfix + * Voice Messages: Fixed race conditions when sending voice messages (#4641) + +⚠️ API Changes + * + +🗣 Translations + * + +🧱 Build + * + +Others + * + +Improvements: + + +Changes in 1.4.8 (2021-07-29) +================================================= + +✨ Features + * + +🙌 Improvements + * Room: Added support for Voice Messages (#4090, #4091, #4092, #4094, #4095, #4096) + * Rooms Tab: Remove the directory section (#4521). + * Notifications: Show decrypted content is enabled by default (#4519). + * People Tab: Remove the local contacts section (#4523). + * Contacts: Delay access to local contacts until they're needed for display (#4616). + * RecentsDataSource: Factorize section reset in one place (target #4591). + * Voice Messages: Tap/hold to send voice messages isn't intuitive (#4601). + * Voice Messages: copy could be improved (#4604). + * Slide to lock should be more generous (#4602). + +🐛 Bugfix + * Room: Fixed mentioning users from room info member details (#4583) + * Settings: Disabled autocorrection when entering an identity server (#4593). + * Room Notification Settings: Fix Crash when opening the new Room Notification Settings Screen (Not yet released) (#4599). + * AuthenticationViewController: Fix crash on authentication if an intermediate view was presented (#4606). + * Room: Fixed crash when opening a read-only room (#4620). + * Voice Messages: Tapping on waveform in composer glitches UI (#4603). + +⚠️ API Changes + * + +🗣 Translations + * + +🧱 Build + * + +Others + * Separated CI jobs into individual actions + * Update Gemfile.lock + +Improvements: + * Upgrade MatrixKit version ([v0.15.6](https://github.com/matrix-org/matrix-ios-kit/releases/tag/v0.15.6)). + +Changes in 1.4.7 (2021-07-22) +================================================= + +✨ Features + * + +🙌 Improvements + * + +🐛 Bugfix + * + +⚠️ API Changes + * + +🗣 Translations + * + +🧱 Build + * + +Others + * Updated issue templates. + +Improvements: + * Upgrade MatrixKit version ([v0.15.5](https://github.com/matrix-org/matrix-ios-kit/releases/tag/v0.15.5)). + +Changes in 1.4.6 (2021-07-16) +================================================= + +✨ Features + * +>>>>>>> develop + +🙌 Improvements + * Room Notification Settings: Ability to change between "All Messages", "Mentions and Keywords" and "None". Not yet exposed in Element UI. (#4458). + * Add support for sending slow motion videos (#4483). + +🐛 Bugfix + * VoIP: Do not present ended calls. + * More fixes to Main.storyboard layout on iPhone 12 Pro Max (#4527) + * Fix crash on Apple Silicon Macs. + * Media Picker: Generate video thumbnails with the correct orientation (#4515). + * Directory List (pop-up one): Fix duplicate rooms being shown (#4537). + * Use different title for scan button for self verification (#4525). + * it's easy for the back button to trigger a leftpanel reveal (#4438). + * Show / hide reset button in secrets recovery screen (#4546). + * Share Extension: Fix layout when searching (#4258). + * Timeline: Fix incorrect crop of media thumbnails (#4552). + +⚠️ API Changes + * + +🗣 Translations + * + +🧱 Build + * + +Others + * Silenced some documentation, deprecations and SwiftLint warnings. + +Improvements: + * Upgrade MatrixKit version ([v0.15.4](https://github.com/matrix-org/matrix-ios-kit/releases/tag/v0.15.4)). + +Changes in 1.4.5 (2021-07-07) +================================================= + +✨ Features + * + +🙌 Improvements + * + +🐛 Bugfix + * Notifications: Fix an issue where the app is unresponsive after getting some notifications (#4534). + +⚠️ API Changes + * + +🗣 Translations + * + +🧱 Build + * + +Others + * + Changes in 1.4.4 (2021-06-30) ================================================= diff --git a/Config/AppIdentifiers.xcconfig b/Config/AppIdentifiers.xcconfig index 391931749..87a13956f 100644 --- a/Config/AppIdentifiers.xcconfig +++ b/Config/AppIdentifiers.xcconfig @@ -22,8 +22,8 @@ APPLICATION_GROUP_IDENTIFIER = group.im.vector APPLICATION_SCHEME = element // Version -MARKETING_VERSION = 1.4.5 -CURRENT_PROJECT_VERSION = 1.4.5 +MARKETING_VERSION = 1.4.10 +CURRENT_PROJECT_VERSION = 1.4.10 // Team diff --git a/Config/BuildSettings.swift b/Config/BuildSettings.swift index 3cfcf65dc..ed613fa9b 100644 --- a/Config/BuildSettings.swift +++ b/Config/BuildSettings.swift @@ -148,7 +148,7 @@ final class BuildSettings: NSObject { // MARK: - Public rooms Directory - static let publicRoomsShowDirectory: Bool = true + #warning("Unused build setting: should this be implemented in ShowDirectory?") static let publicRoomsAllowServerChange: Bool = true // List of homeservers for the public rooms directory static let publicRoomsDirectoryServers = [ @@ -295,6 +295,7 @@ final class BuildSettings: NSObject { static let roomSettingsScreenShowFlairSettings: Bool = true static let roomSettingsScreenShowAdvancedSettings: Bool = true static let roomSettingsScreenAdvancedShowEncryptToVerifiedOption: Bool = true + static let roomSettingsScreenShowNotificationsV2: Bool = false // MARK: - Room Member Screen @@ -308,6 +309,13 @@ final class BuildSettings: NSObject { static let messageDetailsAllowCopyMedia: Bool = true static let messageDetailsAllowPasteMedia: Bool = true + // MARK: - Voice Message + + static let voiceMessagesEnabled = false + + // MARK: - Notifications + static let decryptNotificationsByDefault: Bool = true + // MARK: - HTTP /// Additional HTTP headers will be sent by all requests. Not recommended to use request-specific headers, like `Authorization`. /// Empty dictionary by default. @@ -320,6 +328,9 @@ final class BuildSettings: NSObject { static let authScreenShowForgotPassword = true static let authScreenShowCustomServerOptions = true - // Mark: - Unified Search + // MARK: - Unified Search static let unifiedSearchScreenShowPublicDirectory = true + + // MARK: - Secrets Recovery + static let secretsRecoveryAllowReset = true } diff --git a/DesignKit/Source/Colors.swift b/DesignKit/Source/Colors.swift index b5dc66261..fc96f3608 100644 --- a/DesignKit/Source/Colors.swift +++ b/DesignKit/Source/Colors.swift @@ -45,6 +45,10 @@ import UIKit /// - Icons var quarterlyContent: UIColor { get } + /// - Text + /// - Icons + var quinaryContent: UIColor { get } + /// Separating line var separator: UIColor { get } diff --git a/DesignKit/Variants/Colors/Dark/DarkColors.swift b/DesignKit/Variants/Colors/Dark/DarkColors.swift index a1cf2ac54..39a3b0893 100644 --- a/DesignKit/Variants/Colors/Dark/DarkColors.swift +++ b/DesignKit/Variants/Colors/Dark/DarkColors.swift @@ -32,6 +32,8 @@ public class DarkColors: Colors { public let quarterlyContent: UIColor = UIColor(rgb: 0x6F7882) + public let quinaryContent: UIColor = UIColor(rgb: 0x394049) + public let separator: UIColor = UIColor(rgb: 0x21262C) public let tile: UIColor = UIColor(rgb: 0x394049) diff --git a/DesignKit/Variants/Colors/Light/LightColors.swift b/DesignKit/Variants/Colors/Light/LightColors.swift index d8b5e108b..53a9566e2 100644 --- a/DesignKit/Variants/Colors/Light/LightColors.swift +++ b/DesignKit/Variants/Colors/Light/LightColors.swift @@ -32,6 +32,8 @@ public class LightColors: Colors { public let quarterlyContent: UIColor = UIColor(rgb: 0xC1C6CD) + public let quinaryContent: UIColor = UIColor(rgb: 0xE3E8F0) + public let separator: UIColor = UIColor(rgb: 0xE3E8F0) public let tile: UIColor = UIColor(rgb: 0xF3F8FD) diff --git a/Gemfile.lock b/Gemfile.lock index 31e7491fe..3548311ee 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -7,7 +7,7 @@ GEM i18n (>= 0.7, < 2) minitest (~> 5.1) tzinfo (~> 1.1) - addressable (2.7.0) + addressable (2.8.0) public_suffix (>= 2.0.2, < 5.0) algoliasearch (1.27.5) httpclient (~> 2.8, >= 2.8.3) @@ -252,4 +252,4 @@ DEPENDENCIES xcode-install BUNDLED WITH - 2.2.14 + 2.2.21 diff --git a/Podfile b/Podfile index d008c31f2..53b05e0e6 100644 --- a/Podfile +++ b/Podfile @@ -11,7 +11,7 @@ use_frameworks! # - `{ {kit spec hash} => {sdk spec hash}` to depend on specific pod options (:git => …, :podspec => …) for each repo. Used by Fastfile during CI # # Warning: our internal tooling depends on the name of this variable name, so be sure not to change it -$matrixKitVersion = '= 0.15.3' +$matrixKitVersion = '= 0.15.6' # $matrixKitVersion = :local # $matrixKitVersion = {'develop' => 'develop'} @@ -69,6 +69,8 @@ abstract_target 'RiotPods' do pod 'SwiftBase32', '~> 0.9.0' pod 'SwiftJWT', '~> 3.6.200' pod 'SideMenu', '~> 6.5' + pod 'DSWaveformImage', '~> 6.1.1' + pod 'ffmpeg-kit-ios-audio', '~> 4.4.LTS' pod 'FLEX', '~> 4.4.1', :configurations => ['Debug'] diff --git a/Podfile.lock b/Podfile.lock index 8c6193dfa..0024fd9b1 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -19,6 +19,7 @@ PODS: - BlueRSA (1.0.34) - DGCollectionViewLeftAlignFlowLayout (1.0.4) - Down (0.11.0) + - DSWaveformImage (6.1.1) - DTCoreText (1.6.26): - DTCoreText/Core (= 1.6.26) - DTFoundation/Core (~> 1.7.5) @@ -36,6 +37,7 @@ PODS: - DTFoundation/Core - DTFoundation/UIKit (1.7.18): - DTFoundation/Core + - ffmpeg-kit-ios-audio (4.4) - FLEX (4.4.1) - FlowCommoniOS (1.10.0) - GBDeviceInfo (6.6.0): @@ -56,29 +58,29 @@ PODS: - MatomoTracker (7.4.1): - MatomoTracker/Core (= 7.4.1) - MatomoTracker/Core (7.4.1) - - MatrixKit (0.15.3): + - MatrixKit (0.15.6): - Down (~> 0.11.0) - DTCoreText (~> 1.6.25) - HPGrowingTextView (~> 1.1) - libPhoneNumber-iOS (~> 0.9.13) - - MatrixKit/Core (= 0.15.3) - - MatrixSDK (= 0.19.3) - - MatrixKit/Core (0.15.3): + - MatrixKit/Core (= 0.15.6) + - MatrixSDK (= 0.19.6) + - MatrixKit/Core (0.15.6): - Down (~> 0.11.0) - DTCoreText (~> 1.6.25) - HPGrowingTextView (~> 1.1) - libPhoneNumber-iOS (~> 0.9.13) - - MatrixSDK (= 0.19.3) - - MatrixSDK (0.19.3): - - MatrixSDK/Core (= 0.19.3) - - MatrixSDK/Core (0.19.3): + - MatrixSDK (= 0.19.6) + - MatrixSDK (0.19.6): + - MatrixSDK/Core (= 0.19.6) + - MatrixSDK/Core (0.19.6): - AFNetworking (~> 4.0.0) - GZIP (~> 1.3.0) - libbase58 (~> 0.1.4) - OLMKit (~> 3.2.4) - Realm (= 10.7.6) - SwiftyBeaver (= 1.9.5) - - MatrixSDK/JingleCallStack (0.19.3): + - MatrixSDK/JingleCallStack (0.19.6): - JitsiMeetSDK (= 3.5.0) - MatrixSDK/Core - OLMKit (3.2.4): @@ -104,7 +106,7 @@ PODS: - BlueRSA (~> 1.0) - KituraContracts (~> 1.2) - LoggerAPI (~> 1.7) - - SwiftLint (0.43.0) + - SwiftLint (0.43.1) - SwiftyBeaver (1.9.5) - zxcvbn-ios (1.0.4) - ZXingObjC (3.6.5): @@ -113,6 +115,8 @@ PODS: DEPENDENCIES: - DGCollectionViewLeftAlignFlowLayout (~> 1.0.4) + - DSWaveformImage (~> 6.1.1) + - ffmpeg-kit-ios-audio (~> 4.4.LTS) - FLEX (~> 4.4.1) - FlowCommoniOS (~> 1.10.0) - GBDeviceInfo (~> 6.6.0) @@ -120,7 +124,7 @@ DEPENDENCIES: - KeychainAccess (~> 4.2.2) - KTCenterFlowLayout (~> 1.3.1) - MatomoTracker (~> 7.4.1) - - MatrixKit (= 0.15.3) + - MatrixKit (= 0.15.6) - MatrixSDK - MatrixSDK/JingleCallStack - OLMKit @@ -142,8 +146,10 @@ SPEC REPOS: - BlueRSA - DGCollectionViewLeftAlignFlowLayout - Down + - DSWaveformImage - DTCoreText - DTFoundation + - ffmpeg-kit-ios-audio - FLEX - FlowCommoniOS - GBDeviceInfo @@ -180,8 +186,10 @@ SPEC CHECKSUMS: BlueRSA: 6f9776d62d9773502415a7db3bcbb2bbb3f71fc3 DGCollectionViewLeftAlignFlowLayout: a0fa58797373ded039cafba8133e79373d048399 Down: b6ba1bc985c9d2f4e15e3b293d2207766fa12612 + DSWaveformImage: 3c718a0cf99291887ee70d1d0c18d80101d3d9ce DTCoreText: ec749e013f2e1f76de5e7c7634642e600a7467ce DTFoundation: a53f8cda2489208cbc71c648be177f902ee17536 + ffmpeg-kit-ios-audio: ddfc3dac6f574e83d53f8ae33586711162685d3e FLEX: 7ca2c8cd3a435ff501ff6d2f2141e9bdc934eaab FlowCommoniOS: bcdf81a5f30717e711af08a8c812eb045411ba94 GBDeviceInfo: ed0db16230d2fa280e1cbb39a5a7f60f6946aaec @@ -196,8 +204,8 @@ SPEC CHECKSUMS: LoggerAPI: ad9c4a6f1e32f518fdb43a1347ac14d765ab5e3d Logging: beeb016c9c80cf77042d62e83495816847ef108b MatomoTracker: 24a846c9d3aa76933183fe9d47fd62c9efa863fb - MatrixKit: 6cbe65db11a5450ec8cc02d51660f43b5e95a141 - MatrixSDK: c15663c67bfd2991d897d973c1551ba4de900e25 + MatrixKit: 740fee40187fe84099678c56b2f080d877dd7a65 + MatrixSDK: 04a7f15b03b3def5af644444f3364b8272fb8efc OLMKit: 2d73cd67d149b5c3e3a8eb8ecae93d0b429d8a02 ReadMoreTextView: 19147adf93abce6d7271e14031a00303fe28720d Realm: ed860452717c8db8f4bf832b6807f7f2ce708839 @@ -206,11 +214,11 @@ SPEC CHECKSUMS: SwiftBase32: 9399c25a80666dc66b51e10076bf591e3bbb8f17 SwiftGen: 67860cc7c3cfc2ed25b9b74cfd55495fc89f9108 SwiftJWT: 88c412708f58c169d431d344c87bc79a87c830ae - SwiftLint: 0c645fdc6feed3e390c1701ab3cc669f88b42752 + SwiftLint: 99f82d07b837b942dd563c668de129a03fc3fb52 SwiftyBeaver: 84069991dd5dca07d7069100985badaca7f0ce82 zxcvbn-ios: fef98b7c80f1512ff0eec47ac1fa399fc00f7e3c ZXingObjC: fdbb269f25dd2032da343e06f10224d62f537bdb -PODFILE CHECKSUM: c39d88adc5ec2df412af32b64ceb99a9a1ee92a8 +PODFILE CHECKSUM: c1f1d1137ebacb6c74706cb28d5c10f26d6fe655 COCOAPODS: 1.10.1 diff --git a/Riot/Assets/Base.lproj/Main.storyboard b/Riot/Assets/Base.lproj/Main.storyboard index c562c4d0b..0fc069dcc 100644 --- a/Riot/Assets/Base.lproj/Main.storyboard +++ b/Riot/Assets/Base.lproj/Main.storyboard @@ -1,9 +1,9 @@ - + - + @@ -356,22 +356,6 @@ - - - - - - - - - - - - - - - - @@ -479,7 +463,6 @@ - @@ -560,25 +543,6 @@ - - - - - - - - - - - - - - - - - - - diff --git a/Riot/Assets/Images.xcassets/Call/call_missed_video.imageset/Contents.json b/Riot/Assets/Images.xcassets/Call/call_missed_video.imageset/Contents.json new file mode 100644 index 000000000..20f5e66df --- /dev/null +++ b/Riot/Assets/Images.xcassets/Call/call_missed_video.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "Video.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Video@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Video@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Riot/Assets/Images.xcassets/Call/call_missed_video.imageset/Video.png b/Riot/Assets/Images.xcassets/Call/call_missed_video.imageset/Video.png new file mode 100644 index 000000000..e3258d174 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Call/call_missed_video.imageset/Video.png differ diff --git a/Riot/Assets/Images.xcassets/Call/call_missed_video.imageset/Video@2x.png b/Riot/Assets/Images.xcassets/Call/call_missed_video.imageset/Video@2x.png new file mode 100644 index 000000000..4bdc782a8 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Call/call_missed_video.imageset/Video@2x.png differ diff --git a/Riot/Assets/Images.xcassets/Call/call_missed_video.imageset/Video@3x.png b/Riot/Assets/Images.xcassets/Call/call_missed_video.imageset/Video@3x.png new file mode 100644 index 000000000..f27d51f50 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Call/call_missed_video.imageset/Video@3x.png differ diff --git a/Riot/Assets/Images.xcassets/Call/call_missed_voice.imageset/Contents.json b/Riot/Assets/Images.xcassets/Call/call_missed_voice.imageset/Contents.json new file mode 100644 index 000000000..5e3dda044 --- /dev/null +++ b/Riot/Assets/Images.xcassets/Call/call_missed_voice.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "Voice call.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Voice call@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Voice call@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Riot/Assets/Images.xcassets/Call/call_missed_voice.imageset/Voice call.png b/Riot/Assets/Images.xcassets/Call/call_missed_voice.imageset/Voice call.png new file mode 100644 index 000000000..f24b41a57 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Call/call_missed_voice.imageset/Voice call.png differ diff --git a/Riot/Assets/Images.xcassets/Call/call_missed_voice.imageset/Voice call@2x.png b/Riot/Assets/Images.xcassets/Call/call_missed_voice.imageset/Voice call@2x.png new file mode 100644 index 000000000..4a900f161 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Call/call_missed_voice.imageset/Voice call@2x.png differ diff --git a/Riot/Assets/Images.xcassets/Call/call_missed_voice.imageset/Voice call@3x.png b/Riot/Assets/Images.xcassets/Call/call_missed_voice.imageset/Voice call@3x.png new file mode 100644 index 000000000..9613162d2 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Call/call_missed_voice.imageset/Voice call@3x.png differ diff --git a/Riot/Assets/Images.xcassets/Call/call_video_icon.imageset/24.png b/Riot/Assets/Images.xcassets/Call/call_video_icon.imageset/24.png deleted file mode 100644 index aa4d94502..000000000 Binary files a/Riot/Assets/Images.xcassets/Call/call_video_icon.imageset/24.png and /dev/null differ diff --git a/Riot/Assets/Images.xcassets/Call/call_video_icon.imageset/24@2x.png b/Riot/Assets/Images.xcassets/Call/call_video_icon.imageset/24@2x.png deleted file mode 100644 index b359725c4..000000000 Binary files a/Riot/Assets/Images.xcassets/Call/call_video_icon.imageset/24@2x.png and /dev/null differ diff --git a/Riot/Assets/Images.xcassets/Call/call_video_icon.imageset/24@3x.png b/Riot/Assets/Images.xcassets/Call/call_video_icon.imageset/24@3x.png deleted file mode 100644 index 2c487f4aa..000000000 Binary files a/Riot/Assets/Images.xcassets/Call/call_video_icon.imageset/24@3x.png and /dev/null differ diff --git a/Riot/Assets/Images.xcassets/Call/call_video_icon.imageset/Contents.json b/Riot/Assets/Images.xcassets/Call/call_video_icon.imageset/Contents.json index 453722c5e..044af1b2e 100644 --- a/Riot/Assets/Images.xcassets/Call/call_video_icon.imageset/Contents.json +++ b/Riot/Assets/Images.xcassets/Call/call_video_icon.imageset/Contents.json @@ -1,17 +1,17 @@ { "images" : [ { - "filename" : "24.png", + "filename" : "Video.png", "idiom" : "universal", "scale" : "1x" }, { - "filename" : "24@2x.png", + "filename" : "Video@2x.png", "idiom" : "universal", "scale" : "2x" }, { - "filename" : "24@3x.png", + "filename" : "Video@3x.png", "idiom" : "universal", "scale" : "3x" } diff --git a/Riot/Assets/Images.xcassets/Call/call_video_icon.imageset/Video.png b/Riot/Assets/Images.xcassets/Call/call_video_icon.imageset/Video.png new file mode 100644 index 000000000..b43277beb Binary files /dev/null and b/Riot/Assets/Images.xcassets/Call/call_video_icon.imageset/Video.png differ diff --git a/Riot/Assets/Images.xcassets/Call/call_video_icon.imageset/Video@2x.png b/Riot/Assets/Images.xcassets/Call/call_video_icon.imageset/Video@2x.png new file mode 100644 index 000000000..75db1057d Binary files /dev/null and b/Riot/Assets/Images.xcassets/Call/call_video_icon.imageset/Video@2x.png differ diff --git a/Riot/Assets/Images.xcassets/Call/call_video_icon.imageset/Video@3x.png b/Riot/Assets/Images.xcassets/Call/call_video_icon.imageset/Video@3x.png new file mode 100644 index 000000000..8f2acb229 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Call/call_video_icon.imageset/Video@3x.png differ diff --git a/Riot/Assets/Images.xcassets/Home/RoomContextualMenu/room_action_notification_muted.imageset/Contents.json b/Riot/Assets/Images.xcassets/Home/RoomContextualMenu/room_action_notification_muted.imageset/Contents.json new file mode 100644 index 000000000..f60c4497c --- /dev/null +++ b/Riot/Assets/Images.xcassets/Home/RoomContextualMenu/room_action_notification_muted.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "room_action_notification_muted.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "room_action_notification_muted@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "room_action_notification_muted@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Riot/Assets/Images.xcassets/Home/RoomContextualMenu/room_action_notification_muted.imageset/room_action_notification_muted.png b/Riot/Assets/Images.xcassets/Home/RoomContextualMenu/room_action_notification_muted.imageset/room_action_notification_muted.png new file mode 100644 index 000000000..02ca4c76f Binary files /dev/null and b/Riot/Assets/Images.xcassets/Home/RoomContextualMenu/room_action_notification_muted.imageset/room_action_notification_muted.png differ diff --git a/Riot/Assets/Images.xcassets/Home/RoomContextualMenu/room_action_notification_muted.imageset/room_action_notification_muted@2x.png b/Riot/Assets/Images.xcassets/Home/RoomContextualMenu/room_action_notification_muted.imageset/room_action_notification_muted@2x.png new file mode 100644 index 000000000..c264ba430 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Home/RoomContextualMenu/room_action_notification_muted.imageset/room_action_notification_muted@2x.png differ diff --git a/Riot/Assets/Images.xcassets/Home/RoomContextualMenu/room_action_notification_muted.imageset/room_action_notification_muted@3x.png b/Riot/Assets/Images.xcassets/Home/RoomContextualMenu/room_action_notification_muted.imageset/room_action_notification_muted@3x.png new file mode 100644 index 000000000..b9738ab8b Binary files /dev/null and b/Riot/Assets/Images.xcassets/Home/RoomContextualMenu/room_action_notification_muted.imageset/room_action_notification_muted@3x.png differ diff --git a/Riot/Assets/Images.xcassets/Room/ContextMenu/room_context_menu_delete.imageset/Contents.json b/Riot/Assets/Images.xcassets/Room/ContextMenu/room_context_menu_delete.imageset/Contents.json index bb4344e92..a255919c5 100644 --- a/Riot/Assets/Images.xcassets/Room/ContextMenu/room_context_menu_delete.imageset/Contents.json +++ b/Riot/Assets/Images.xcassets/Room/ContextMenu/room_context_menu_delete.imageset/Contents.json @@ -19,5 +19,8 @@ "info" : { "author" : "xcode", "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" } } diff --git a/Riot/Assets/Images.xcassets/Room/Input/upload_icon_dark.imageset/upload_icon_dark.png b/Riot/Assets/Images.xcassets/Room/Input/upload_icon_dark.imageset/upload_icon_dark.png index cb0316cf9..e48bf546b 100644 Binary files a/Riot/Assets/Images.xcassets/Room/Input/upload_icon_dark.imageset/upload_icon_dark.png and b/Riot/Assets/Images.xcassets/Room/Input/upload_icon_dark.imageset/upload_icon_dark.png differ diff --git a/Riot/Assets/Images.xcassets/Room/Input/upload_icon_dark.imageset/upload_icon_dark@2x.png b/Riot/Assets/Images.xcassets/Room/Input/upload_icon_dark.imageset/upload_icon_dark@2x.png index b58c78907..b35cce7f6 100644 Binary files a/Riot/Assets/Images.xcassets/Room/Input/upload_icon_dark.imageset/upload_icon_dark@2x.png and b/Riot/Assets/Images.xcassets/Room/Input/upload_icon_dark.imageset/upload_icon_dark@2x.png differ diff --git a/Riot/Assets/Images.xcassets/Room/Input/upload_icon_dark.imageset/upload_icon_dark@3x.png b/Riot/Assets/Images.xcassets/Room/Input/upload_icon_dark.imageset/upload_icon_dark@3x.png index b04f2a977..4c3eb73ea 100644 Binary files a/Riot/Assets/Images.xcassets/Room/Input/upload_icon_dark.imageset/upload_icon_dark@3x.png and b/Riot/Assets/Images.xcassets/Room/Input/upload_icon_dark.imageset/upload_icon_dark@3x.png differ diff --git a/Riot/Assets/Images.xcassets/Room/VoiceMessages/Contents.json b/Riot/Assets/Images.xcassets/Room/VoiceMessages/Contents.json new file mode 100644 index 000000000..73c00596a --- /dev/null +++ b/Riot/Assets/Images.xcassets/Room/VoiceMessages/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_cancel_gradient.imageset/Contents.json b/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_cancel_gradient.imageset/Contents.json new file mode 100644 index 000000000..246bbbbf8 --- /dev/null +++ b/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_cancel_gradient.imageset/Contents.json @@ -0,0 +1,26 @@ +{ + "images" : [ + { + "filename" : "voice_message_cancel_gradient.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "voice_message_cancel_gradient@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "voice_message_cancel_gradient@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_cancel_gradient.imageset/voice_message_cancel_gradient.png b/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_cancel_gradient.imageset/voice_message_cancel_gradient.png new file mode 100644 index 000000000..aad31deb7 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_cancel_gradient.imageset/voice_message_cancel_gradient.png differ diff --git a/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_cancel_gradient.imageset/voice_message_cancel_gradient@2x.png b/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_cancel_gradient.imageset/voice_message_cancel_gradient@2x.png new file mode 100644 index 000000000..5d0595c22 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_cancel_gradient.imageset/voice_message_cancel_gradient@2x.png differ diff --git a/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_cancel_gradient.imageset/voice_message_cancel_gradient@3x.png b/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_cancel_gradient.imageset/voice_message_cancel_gradient@3x.png new file mode 100644 index 000000000..6c08777d3 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_cancel_gradient.imageset/voice_message_cancel_gradient@3x.png differ diff --git a/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_lock_chevron.imageset/Contents.json b/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_lock_chevron.imageset/Contents.json new file mode 100644 index 000000000..6b40d80e4 --- /dev/null +++ b/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_lock_chevron.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "voice_message_lock_chevron.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "voice_message_lock_chevron@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "voice_message_lock_chevron@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_lock_chevron.imageset/voice_message_lock_chevron.png b/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_lock_chevron.imageset/voice_message_lock_chevron.png new file mode 100644 index 000000000..332200ef8 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_lock_chevron.imageset/voice_message_lock_chevron.png differ diff --git a/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_lock_chevron.imageset/voice_message_lock_chevron@2x.png b/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_lock_chevron.imageset/voice_message_lock_chevron@2x.png new file mode 100644 index 000000000..ff29e0808 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_lock_chevron.imageset/voice_message_lock_chevron@2x.png differ diff --git a/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_lock_chevron.imageset/voice_message_lock_chevron@3x.png b/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_lock_chevron.imageset/voice_message_lock_chevron@3x.png new file mode 100644 index 000000000..b9f5c04d5 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_lock_chevron.imageset/voice_message_lock_chevron@3x.png differ diff --git a/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_lock_icon_locked.imageset/Contents.json b/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_lock_icon_locked.imageset/Contents.json new file mode 100644 index 000000000..93de75077 --- /dev/null +++ b/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_lock_icon_locked.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "voice_message_lock_icon_locked.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "voice_message_lock_icon_locked@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "voice_message_lock_icon_locked@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_lock_icon_locked.imageset/voice_message_lock_icon_locked.png b/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_lock_icon_locked.imageset/voice_message_lock_icon_locked.png new file mode 100644 index 000000000..35a6569c0 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_lock_icon_locked.imageset/voice_message_lock_icon_locked.png differ diff --git a/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_lock_icon_locked.imageset/voice_message_lock_icon_locked@2x.png b/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_lock_icon_locked.imageset/voice_message_lock_icon_locked@2x.png new file mode 100644 index 000000000..615ab682d Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_lock_icon_locked.imageset/voice_message_lock_icon_locked@2x.png differ diff --git a/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_lock_icon_locked.imageset/voice_message_lock_icon_locked@3x.png b/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_lock_icon_locked.imageset/voice_message_lock_icon_locked@3x.png new file mode 100644 index 000000000..3108b7652 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_lock_icon_locked.imageset/voice_message_lock_icon_locked@3x.png differ diff --git a/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_lock_icon_unlocked.imageset/Contents.json b/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_lock_icon_unlocked.imageset/Contents.json new file mode 100644 index 000000000..43a311cf4 --- /dev/null +++ b/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_lock_icon_unlocked.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "voice_message_lock_icon_unlocked.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "voice_message_lock_icon_unlocked@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "voice_message_lock_icon_unlocked@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_lock_icon_unlocked.imageset/voice_message_lock_icon_unlocked.png b/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_lock_icon_unlocked.imageset/voice_message_lock_icon_unlocked.png new file mode 100644 index 000000000..1a3c05594 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_lock_icon_unlocked.imageset/voice_message_lock_icon_unlocked.png differ diff --git a/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_lock_icon_unlocked.imageset/voice_message_lock_icon_unlocked@2x.png b/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_lock_icon_unlocked.imageset/voice_message_lock_icon_unlocked@2x.png new file mode 100644 index 000000000..bcd1b2a44 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_lock_icon_unlocked.imageset/voice_message_lock_icon_unlocked@2x.png differ diff --git a/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_lock_icon_unlocked.imageset/voice_message_lock_icon_unlocked@3x.png b/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_lock_icon_unlocked.imageset/voice_message_lock_icon_unlocked@3x.png new file mode 100644 index 000000000..a2087f349 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_lock_icon_unlocked.imageset/voice_message_lock_icon_unlocked@3x.png differ diff --git a/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_pause_button.imageset/Contents.json b/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_pause_button.imageset/Contents.json new file mode 100644 index 000000000..bfc1545db --- /dev/null +++ b/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_pause_button.imageset/Contents.json @@ -0,0 +1,26 @@ +{ + "images" : [ + { + "filename" : "voice_message_pause_button.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "voice_message_pause_button@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "voice_message_pause_button@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_pause_button.imageset/voice_message_pause_button.png b/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_pause_button.imageset/voice_message_pause_button.png new file mode 100644 index 000000000..aa185b04b Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_pause_button.imageset/voice_message_pause_button.png differ diff --git a/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_pause_button.imageset/voice_message_pause_button@2x.png b/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_pause_button.imageset/voice_message_pause_button@2x.png new file mode 100644 index 000000000..321565890 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_pause_button.imageset/voice_message_pause_button@2x.png differ diff --git a/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_pause_button.imageset/voice_message_pause_button@3x.png b/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_pause_button.imageset/voice_message_pause_button@3x.png new file mode 100644 index 000000000..93bdf2f28 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_pause_button.imageset/voice_message_pause_button@3x.png differ diff --git a/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_play_button.imageset/Contents.json b/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_play_button.imageset/Contents.json new file mode 100644 index 000000000..3c88e866c --- /dev/null +++ b/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_play_button.imageset/Contents.json @@ -0,0 +1,26 @@ +{ + "images" : [ + { + "filename" : "voice_message_play_button.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "voice_message_play_button@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "voice_message_play_button@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_play_button.imageset/voice_message_play_button.png b/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_play_button.imageset/voice_message_play_button.png new file mode 100644 index 000000000..274211f74 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_play_button.imageset/voice_message_play_button.png differ diff --git a/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_play_button.imageset/voice_message_play_button@2x.png b/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_play_button.imageset/voice_message_play_button@2x.png new file mode 100644 index 000000000..0c8da2212 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_play_button.imageset/voice_message_play_button@2x.png differ diff --git a/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_play_button.imageset/voice_message_play_button@3x.png b/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_play_button.imageset/voice_message_play_button@3x.png new file mode 100644 index 000000000..22176d475 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_play_button.imageset/voice_message_play_button@3x.png differ diff --git a/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_record_button_default.imageset/Contents.json b/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_record_button_default.imageset/Contents.json new file mode 100644 index 000000000..ead86edbb --- /dev/null +++ b/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_record_button_default.imageset/Contents.json @@ -0,0 +1,26 @@ +{ + "images" : [ + { + "filename" : "action_voice_message.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "action_voice_message@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "action_voice_message@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_record_button_default.imageset/action_voice_message.png b/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_record_button_default.imageset/action_voice_message.png new file mode 100644 index 000000000..b969cb3aa Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_record_button_default.imageset/action_voice_message.png differ diff --git a/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_record_button_default.imageset/action_voice_message@2x.png b/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_record_button_default.imageset/action_voice_message@2x.png new file mode 100644 index 000000000..32c6236a6 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_record_button_default.imageset/action_voice_message@2x.png differ diff --git a/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_record_button_default.imageset/action_voice_message@3x.png b/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_record_button_default.imageset/action_voice_message@3x.png new file mode 100644 index 000000000..e8cc54c29 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_record_button_default.imageset/action_voice_message@3x.png differ diff --git a/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_record_button_recording.imageset/Contents.json b/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_record_button_recording.imageset/Contents.json new file mode 100644 index 000000000..900874ca1 --- /dev/null +++ b/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_record_button_recording.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "voice_message_record_button_recording.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "voice_message_record_button_recording@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "voice_message_record_button_recording@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_record_button_recording.imageset/voice_message_record_button_recording.png b/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_record_button_recording.imageset/voice_message_record_button_recording.png new file mode 100644 index 000000000..5972e1272 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_record_button_recording.imageset/voice_message_record_button_recording.png differ diff --git a/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_record_button_recording.imageset/voice_message_record_button_recording@2x.png b/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_record_button_recording.imageset/voice_message_record_button_recording@2x.png new file mode 100644 index 000000000..802268ba0 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_record_button_recording.imageset/voice_message_record_button_recording@2x.png differ diff --git a/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_record_button_recording.imageset/voice_message_record_button_recording@3x.png b/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_record_button_recording.imageset/voice_message_record_button_recording@3x.png new file mode 100644 index 000000000..b1def35e1 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_record_button_recording.imageset/voice_message_record_button_recording@3x.png differ diff --git a/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_record_icon.imageset/Contents.json b/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_record_icon.imageset/Contents.json new file mode 100644 index 000000000..521b5c2dc --- /dev/null +++ b/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_record_icon.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "voice_message_record_icon.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "voice_message_record_icon@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "voice_message_record_icon@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_record_icon.imageset/voice_message_record_icon.png b/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_record_icon.imageset/voice_message_record_icon.png new file mode 100644 index 000000000..fb7a1660c Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_record_icon.imageset/voice_message_record_icon.png differ diff --git a/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_record_icon.imageset/voice_message_record_icon@2x.png b/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_record_icon.imageset/voice_message_record_icon@2x.png new file mode 100644 index 000000000..72830fabe Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_record_icon.imageset/voice_message_record_icon@2x.png differ diff --git a/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_record_icon.imageset/voice_message_record_icon@3x.png b/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_record_icon.imageset/voice_message_record_icon@3x.png new file mode 100644 index 000000000..c792f8173 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_record_icon.imageset/voice_message_record_icon@3x.png differ diff --git a/Riot/Assets/Images.xcassets/Room/notifications.imageset/Contents.json b/Riot/Assets/Images.xcassets/Room/notifications.imageset/Contents.json new file mode 100644 index 000000000..66a053751 --- /dev/null +++ b/Riot/Assets/Images.xcassets/Room/notifications.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "notifications.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "notifications@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "notifications@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Riot/Assets/Images.xcassets/Room/notifications.imageset/notifications.png b/Riot/Assets/Images.xcassets/Room/notifications.imageset/notifications.png new file mode 100644 index 000000000..6b100741d Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/notifications.imageset/notifications.png differ diff --git a/Riot/Assets/Images.xcassets/Room/notifications.imageset/notifications@2x.png b/Riot/Assets/Images.xcassets/Room/notifications.imageset/notifications@2x.png new file mode 100644 index 000000000..98d86c152 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/notifications.imageset/notifications@2x.png differ diff --git a/Riot/Assets/Images.xcassets/Room/notifications.imageset/notifications@3x.png b/Riot/Assets/Images.xcassets/Room/notifications.imageset/notifications@3x.png new file mode 100644 index 000000000..ddc1a6e7a Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/notifications.imageset/notifications@3x.png differ diff --git a/Riot/Assets/ar.lproj/Vector.strings b/Riot/Assets/ar.lproj/Vector.strings index 8687e7f26..7aa1dddd8 100644 --- a/Riot/Assets/ar.lproj/Vector.strings +++ b/Riot/Assets/ar.lproj/Vector.strings @@ -261,3 +261,88 @@ "room_participants_filter_room_members_for_dm" = "تَصفِيَةُ الأعضَاء"; "room_participants_filter_room_members" = "تَصفِيَةُ أعضَاءِ الغُرفَة"; "room_participants_invite_prompt_msg" = "هَل أنتَ مُتَأكِّدٌ أنَّكَ تٌريدُ دَعوةَ %@ إلَى هَذِهِ المُحادَثَة؟"; +"room_event_action_reaction_show_less" = "إظهَارُ أقَل"; +"room_event_action_reaction_show_all" = "إظهَارُ الكُل"; +"room_event_action_edit" = "تَحرِير"; +"room_event_action_reply" = "الرَّدّ"; +"room_event_action_view_encryption" = "مَعلُومَاتُ التَّعمِيَة"; +"room_event_action_cancel_download" = "إلغَاءُ التَّنزيل"; +"room_event_action_cancel_send" = "إلغَاءُ الإرسَال"; +"room_event_action_delete_confirmation_message" = "هَل أنتَ مُتَأكِّدٌ أنَّكَ تٌريدُ حَذفَ هَذِهِ الرِّسَالَةَ غَيرِ المُرسَلَة؟"; +"room_event_action_delete_confirmation_title" = "حَذفُ الرَّسَائِلِ غَيرِ المُرسَلَة"; +"room_event_action_delete" = "حَذف"; +"room_event_action_resend" = "إعادَةُ الإرسَال"; +"room_event_action_save" = "حِفظ"; +"room_event_action_report_prompt_ignore_user" = "هَل تُريدُ إخفَاءَ جَميعِ الرَّسَائِلِ مِن هَذَا المُستَخدِم؟"; +"room_event_action_ban_prompt_reason" = "سَبَبُ حَظْرِ هَذَا المُستَخدِم"; +"room_event_action_kick_prompt_reason" = "سَبَبُ طَردِ هَذَا المُستَخدِم"; +"room_event_action_report_prompt_reason" = "سَبَبُ الإبلَاغِ عَن هَذَا المُحتَوى"; +"room_event_action_report" = "التَّبلِيغُ عَنِ المُحتَوَى"; +"room_event_action_view_decrypted_source" = "الاِطِّلاعُ عَلَى المَصدَرِ مَفكُوكِ التَّعميَة"; +"room_event_action_view_source" = "الاِطِّلاعُ عَلَى المَصدَر"; +"room_event_action_permalink" = "رَابِطٌ دَائِم"; +"room_event_action_share" = "مُشارَكَة"; +"room_event_action_more" = "مَزيد"; +"room_event_action_redact" = "إزالَة"; +"room_event_action_quote" = "اِقتِبَاس"; +"room_event_action_copy" = "نَسخ"; +"room_delete_unsent_messages" = "حَذفُ الرَّسائِلِ غَيرِ المُرسَلَة"; +"room_resend_unsent_messages" = "إعادَةُ إرسَالِ الرَّسائِلِ غَيرِ المُرسَلَة"; +"room_prompt_cancel" = "إلغَاءُ الكُل"; +"room_prompt_resend" = "إعادَةُ إرسَالِ الكُل"; +"room_conference_call_no_power" = "تَحتَاجُ إلَى إذنٍ لِإدَارَةِ مُكالَمَةِ اِجتِمَاعٍ فِي هَذِهِ الغُرفَة"; +"room_ongoing_conference_call_with_close" = "مُكالَمَةُ اِجتِمَاعٍ مُستَمِرَّة. الاِنضِمَامُ كَـ%@ أَو %@. %@ها."; +"room_ongoing_conference_call_close" = "إِغلَاق"; +"room_ongoing_conference_call" = "مُكالَمَةُ اِجتِمَاعٍ مُستَمِرَّة. الاِنضِمَامُ كَـ%@ أَو %@."; +"room_unsent_messages_cancel_message" = "هَل أنتَ مُتَأكِّدٌ أنَّكَ تٌريدُ حَذفَ جَميعِ الرَّسائِلِ غَيرِ المُرسَلَةِ فِي هَذِهِ الغُرفَة؟"; +"room_unsent_messages_cancel_title" = "حَذفُ الرَّسائِلِ غَيرِ المُرسَلَة"; +"room_unsent_messages_unknown_devices_notification" = "فَشَلَ إرسَالُ الرَّسائِلِ بِسَبَبِ جَلسَاتٍ حَاليةٍ غَيرِ مَعرُوفَة."; +"room_unsent_messages_notification" = "فَشَلَ إرسَالُ الرَّسائِل."; +"room_offline_notification" = "فُقِدَ الاِتِّصَالُ بِالخَادِم."; +"room_message_reply_to_short_placeholder" = "إرسَالُ رَدّ…"; +"room_message_short_placeholder" = "إرسَالُ رِسَالَة…"; +"room_participants_security_information_room_encrypted_for_dm" = "الرَّسائِلُ هُنا مُعمَاةٌ مِنَ النِّهايَةِ إلَى النِّهايَةِ. \n\nيَتِمُ تَأمينُ رَسائِلَكَ بِأقفَال، فَقَط أنتَ وَالمُستَلِمُ مَن لَديهِم مِفتَاحَينِ فَريدَينِ لِفتحِهَا."; +"room_participants_security_information_room_encrypted" = "الرَّسائِلُ فِي هَذِهِ الغُرفَة مُعمَاةٌ مِنَ النِّهايَةِ إلَى النِّهايَةِ. \n\nيَتِمُ تَأمينُ رَسائِلَكَ بِأقفَال، فَقَط أنتَ وَالمُستَلِمُ مَن لَديهِم مِفتَاحَينِ فَريدَينِ لِفتحِهَا."; +"room_participants_security_information_room_not_encrypted_for_dm" = "الرَّسائِلُ هُنا لَيسَت مُعمَاةً مِنَ النِّهايَةِ إلَى النِّهايَةِ."; +"room_participants_security_information_room_not_encrypted" = "الرَّسائِلُ فِي هَذِهِ الغُرفَة لَيسَت مُعمَاةً مِنَ النِّهايَةِ إلَى النِّهايَةِ."; +"encrypted_room_message_reply_to_placeholder" = "إرسَالُ رَدٍّ مُعمَى…"; +"encrypted_room_message_placeholder" = "إرسَالُ رِسَالَةٍ مُعمَاة…"; +"room_do_not_have_permission_to_post" = "لَيسَ لَديكَ إذنٌ بِالنَّشرِ فِي هَذِهِ الغُرفَة"; +"room_message_editing" = "التَّحرِير"; +"room_message_replying_to" = "الرَّدُّ عَلَى %@"; +"room_message_unable_open_link_error_message" = "يَتَعَذَّرُ فَتحُ الرَّابِط."; +"room_message_reply_to_placeholder" = "إرسَالُ رَدّ (غَيرُ مُعَمَى)…"; +"room_message_placeholder" = "إرسَالُ رِسَالَة (غَيرُ مُعَمَاة)…"; +"room_many_users_are_typing" = "إنَّ %@، %@ وَآخَرُونَ يَكتُبُون…"; +"room_two_users_are_typing" = "إنَّ %@ وَ %@ يَكتُبَان…"; +"room_one_user_is_typing" = "إنَّ %@ يَكتُب…"; +"room_new_messages_notification" = "عَدَدُ %d رَسَائِلَ جَدِيدَة"; +"room_new_message_notification" = "عَدَدُ %d رِسَالَةً جَدِيدَة"; +"room_accessiblity_scroll_to_bottom" = "التَّمرِيرُ إلَى الأَسفَل"; +"room_jump_to_first_unread" = "القَفزُ إلَى غَيرِ المَقرُوء"; + +// Chat +"room_slide_to_end_group_call" = "مَرِّرِ لِإنهَاءِ المُكالَمَةِ لِلجَّميع"; +"room_member_power_level_short_custom" = "مُتَخَصِّص"; +"room_member_power_level_short_moderator" = "مُشرِف"; +"room_member_power_level_short_admin" = "مُدير"; +"room_member_power_level_custom_in" = "مُتَخَصِّصٌ (%@) في %@"; +"room_member_power_level_moderator_in" = "مُشرِفٌ فِي %@"; +"room_member_power_level_admin_in" = "مُديرٌ فِي %@"; +"room_participants_security_loading" = "التَّحمِيلُ جَارٍ…"; +"room_participants_action_security_status_loading" = "التَّحمِيلُ جَارٍ…"; +"room_participants_action_security_status_warning" = "تَحذِير"; +"room_participants_action_security_status_complete_security" = "الأمَانُ الكَامِل"; +"room_participants_action_security_status_verify" = "تَأكِيدُ التَّحَقُّق"; +"room_participants_action_security_status_verified" = "مُتَحَقَّقٌ مِنه"; +"room_participants_action_mention" = "ذِكْر"; +"room_participants_action_start_video_call" = "بَدْءُ مُكالَمَةٍ مَرئيَّة"; +"room_participants_action_start_voice_call" = "بَدْءُ مُكالَمَةٍ صَوتيَّة"; +"room_participants_action_start_new_chat" = "بَدْءُ مُحادَثَةٍ جَدِيدَة"; +"room_participants_action_set_admin" = "جَعْلَهُ مُدِير"; +"room_participants_action_set_moderator" = "جَعْلَهُ مُشرِف"; +"room_participants_action_set_default_power_level" = "إعَادَةُ الضَّبطِ إلَى مُستَخدِمٍ عَادِيّ"; +"room_participants_action_unignore" = "إِظْهَارُ جَمِيعِ الرَّسائِلِ مِن هَذَا المُستَخدِم"; +"room_participants_action_ignore" = "إخفَاءُ جَمِيعِ الرَّسائِلِ مِن هَذَا المُستَخدِم"; +"room_recents_unknown_room_error_message" = "يَتَعَذَّر العُثُور عَلَى هَذِهِ الغُرفَة. تأكَّد مِن وجودِهَا"; +"room_creation_dm_error" = "يَتَعَذَّرُ عَلينَا إنشَاء المُحادَثَة المُباشِرَة الخَّاصَةِ بِك. يُرجَى التَّحَقُقُ مِنَ المُستَخدِمِيَنَ اللَّذِينَ تُريدُ دَعوَتَهُم ثُمَّ المُحاوَلَةُ مَرةً أُخرَى."; diff --git a/Riot/Assets/bg.lproj/Vector.strings b/Riot/Assets/bg.lproj/Vector.strings index d9fa91f5a..854c51242 100644 --- a/Riot/Assets/bg.lproj/Vector.strings +++ b/Riot/Assets/bg.lproj/Vector.strings @@ -106,7 +106,6 @@ // Room recents "room_recents_directory_section" = "ДИРЕКТОРИЯ СЪС СТАИ"; "room_creation_invite_another_user" = "Търси потребител по ID, име, имейл"; -"room_recents_directory_section_network" = "Мрежа"; "room_recents_favourites_section" = "ЛЮБИМИ"; "room_recents_people_section" = "ХОРА"; "room_recents_conversations_section" = "СТАИ"; @@ -299,7 +298,6 @@ "settings_global_settings_info" = "Глобални настройки на известия са налични на Вашия %@ уеб клиент"; "settings_pin_rooms_with_missed_notif" = "Закачане на стаи с пропуснати известия"; "settings_pin_rooms_with_unread" = "Закачане на стаи с непрочетени съобщения"; -"settings_on_denied_notification" = "Известията са отказани за %@. Моля, включете ги в настройките на устройството"; "settings_enable_callkit" = "Интегрирани разговори"; "settings_callkit_info" = "Получаване на входящи повиквания при заключен екран. Показване на Element разговори в историята на системата. Ако iCloud е включен, историята на разговорите се споделя с Apple."; "settings_ui_language" = "Език"; diff --git a/Riot/Assets/ca.lproj/Vector.strings b/Riot/Assets/ca.lproj/Vector.strings index f11645924..d690d7efe 100644 --- a/Riot/Assets/ca.lproj/Vector.strings +++ b/Riot/Assets/ca.lproj/Vector.strings @@ -106,7 +106,6 @@ "room_creation_invite_another_user" = "Cerca / convida per l'identificador d'usuari, nom o correu electrònic"; // Room recents "room_recents_directory_section" = "Directori de Sales"; -"room_recents_directory_section_network" = "Xarxa"; "room_recents_favourites_section" = "Favorits"; "room_recents_people_section" = "Contactes"; "room_recents_conversations_section" = "Sales"; @@ -296,7 +295,6 @@ "settings_global_settings_info" = "Els paràmetres de notificació globals estan disponibles al teu client web %@"; "settings_pin_rooms_with_missed_notif" = "Fixa sales amb notificacions pendents"; "settings_pin_rooms_with_unread" = "Fixa sales amb missatges pendents"; -"settings_on_denied_notification" = "%@ no permet notificacions, si us plau activa-les en els ajustos del teu dispositiu"; "settings_enable_callkit" = "Trucades integrades"; "settings_callkit_info" = "Rep les trucades entrants a la pantalla de bloqueig. Consulta les trucades de Element a l'historial de trucades del sistema. Si està habilitat iCloud, aquest historial de trucades es compartirà amb Apple."; "settings_ui_language" = "Llenguatge"; diff --git a/Riot/Assets/cs.lproj/Localizable.strings b/Riot/Assets/cs.lproj/Localizable.strings new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/Riot/Assets/cs.lproj/Localizable.strings @@ -0,0 +1 @@ + diff --git a/Riot/Assets/cs.lproj/Vector.strings b/Riot/Assets/cs.lproj/Vector.strings index ce499a826..23f79ca59 100644 --- a/Riot/Assets/cs.lproj/Vector.strings +++ b/Riot/Assets/cs.lproj/Vector.strings @@ -4,11 +4,11 @@ "title_people" = "Lidé"; "title_rooms" = "Místnosti"; "title_groups" = "Komunity"; -"warning" = "Varování!"; +"warning" = "Varování"; // Actions -"view" = "Zobrazit zdroj"; +"view" = "Zobrazit"; "next" = "Další"; -"back" = "Pro zabezpečení se při odhlašování odstraní všechny šifrovací klíče typu end-to-end, a i po přihlašení bude historie šifrovaných kanálů nečitelná.\nZvolte export, chcete-li je zálohovat, než se odhlásíte."; +"back" = "Zpět"; "continue" = "Pokračovat"; "create" = "Vytvořit"; "start" = "Spustit"; @@ -52,7 +52,7 @@ "auth_repeat_password_placeholder" = "Zopakovat heslo"; "auth_repeat_new_password_placeholder" = "Potvrďte své nové heslo"; "auth_home_server_placeholder" = "URL (např. https://matrix.org)"; -"auth_identity_server_placeholder" = "URL (např. https://matrix.org)"; +"auth_identity_server_placeholder" = "URL (např. https://vector.im)"; "auth_invalid_login_param" = "Nesprávné uživatelské jméno nebo heslo"; "auth_invalid_user_name" = "Uživatelské jméno může obsahovat pouze písmena, číslice, tečky, pomlčky a podtržítka"; "auth_invalid_password" = "Heslo je velmi krátké (min 6)"; @@ -130,7 +130,7 @@ // Directory "directory_cell_title" = "Procházet adresář"; "directory_cell_description" = "%tu místnosti"; -"directory_search_results_title" = "Procházet výsledky výhledávání v adresáři"; +"directory_search_results_title" = "Procházet výsledky v adresáři"; "directory_search_results" = "%tu výsledky nalezeny pro %@"; "directory_search_results_more_than" = ">%tu výsledky nalezeny pro %@"; "directory_searching_title" = "Vyhledávání v adresáři…"; @@ -139,7 +139,7 @@ "contacts_address_book_section" = "LOKÁLNÍ KONTAKTY"; "contacts_address_book_matrix_users_toggle" = "Pouze Matrix uživatelé"; "contacts_address_book_no_contact" = "Žádné lokální kontakty"; -"contacts_address_book_permission_denied" = "Nepovolil jste přístup aplikace Riot k místním kontaktům"; +"contacts_address_book_permission_denied" = "Nepovolil jste přístup aplikace Element k místním kontaktům"; "contacts_user_directory_section" = "UŽIVATELSKÝ ADRESÁŘ"; "contacts_user_directory_offline_section" = "UŽIVATELSKÝ ADRESÁŘ (offline)"; // Chat participants @@ -162,8 +162,8 @@ "room_participants_ago" = "před"; "room_participants_action_section_admin_tools" = "Nástroje pro správce"; "room_participants_action_section_direct_chats" = "Přímé chaty"; -"room_participants_action_section_devices" = "Zařízení"; -"room_participants_action_section_other" = "Další"; +"room_participants_action_section_devices" = "Relace"; +"room_participants_action_section_other" = "Možnosti"; "room_participants_action_invite" = "Pozvat"; "room_participants_action_leave" = "Odejít z místnosti"; "room_participants_action_remove" = "Odebrat z této místnosti"; @@ -178,7 +178,7 @@ "room_participants_action_start_video_call" = "Začít video hovor"; "room_participants_action_mention" = "Zmínka"; // Chat -"room_jump_to_first_unread" = "Přeskočit na první nepřečtenou zprávu"; +"room_jump_to_first_unread" = "Přeskočit na nepřečtenou zprávu"; "room_new_message_notification" = "%d nová zpráva"; "room_new_messages_notification" = "%d nových zpráv"; "room_one_user_is_typing" = "%@ právě píše…"; @@ -196,7 +196,7 @@ "room_delete_unsent_messages" = "Smazat neodeslané zprávy"; "room_event_action_copy" = "Kopírovat"; "room_event_action_quote" = "Citovat"; -"room_event_action_redact" = "Redigovat"; +"room_event_action_redact" = "Odstranit"; "room_event_action_more" = "Víc"; "room_event_action_share" = "Sdílet"; "room_event_action_permalink" = "Trvalý odkaz"; @@ -211,12 +211,12 @@ "room_event_action_view_encryption" = "Informace o šifrování"; "room_event_failed_to_send" = "Odeslání se nezdařilo"; // Unknown devices -"unknown_devices_alert_title" = "V místnosti jsou neznámá zařízení"; +"unknown_devices_alert_title" = "V místnosti jsou neznámé relace"; "unknown_devices_send_anyway" = "Přesto poslat"; "unknown_devices_call_anyway" = "Přesto zavolat"; "unknown_devices_answer_anyway" = "Přesto přijmout"; "unknown_devices_verify" = "Ověřit…"; -"unknown_devices_title" = "Neznámá zařízení"; +"unknown_devices_title" = "Neznámé relace"; // Room Title "room_title_new_room" = "Nová místnost"; "room_participants_invite_another_user" = "Hledat / Pozvat podle uživatelského ID, jména nebo emailu"; @@ -231,10 +231,117 @@ "settings_title" = "Nastavení"; "account_logout_all" = "Odhlásit všechny účty"; "settings_report_bug" = "Nahlásit chybu"; -"settings_config_home_server" = "Domácího serveru je %@"; +"settings_config_home_server" = "Domácí server je %@"; "settings_config_identity_server" = "Server identit je %@"; "settings_config_user_id" = "Přihlášen/a jako %@"; "auth_msisdn_validation_message" = "Odeslali jsme vám SMS aktivační kod. Prosím, zadejte jej níže."; "auth_msisdn_validation_error" = "Nelze ověřit telefonní číslo."; -"auth_reset_password_success_message" = "Vaše heslo bylo úspěšně resetováno.\n\nByl jste právě odhlášen na všech vašich zařízeních a nebudete vánm nadále zasíláno oznámení. Pro znovu povolení zasílání oznámení, přihlašte se znovu na každém zařízení."; +"auth_reset_password_success_message" = "Vaše heslo bylo úspěšně resetováno.\n\nByl jste právě odhlášen na všech vašich relací a nebudete vánm nadále zasíláno oznámení. Pro znovu povolení zasílání oznámení, přihlašte se znovu na každém zařízení."; "store_full_description" = "Element je novým typem komunikátoru a propojovací aplikace která:\n\n1. Dává vám kontrolu nad vaším soukromím\n2. Vás nechá komunikovat s kýmkoli v Matrix síti a dokonce mimo ni, díky integrace s aplikacemi jako je například Slack\n3. Vás chrání před reklamou, těžbou Vašich dat, nechráněnými přístupy nebo nezdokumentovanými fukncemi\n4. Zabezpečuje Vaši komunikaci pomocí koncového šifrování s distribuovaným ověřením ostatních\n\nElement se liší od ostatních komunikačních řešeních především tím, že je decentralizovaný a open-source\n\nElement vám umožňuje provozovat vlastní server anebo si vybrat nějakyý z veřejných, takže máte controlu nad vašimi konverzacemi a soukromím. Dává vám přístup do otevřené sítě, takže nejste odkázání jen ke komunikaci s ostatními uživateli Elementu. A je vysoce bezpečný.\n\nElement je toho všeho schopen díky svému operačnímu protokolu - Matrix, otevřeného standartu pro decentralizovanou komunikaci.\n\nElement vás nechává vybrat, kdo bude hostovat vaše konverzace. Přímo z aplikace si můžete vybrat několik rozdílných řešení:\n\n1. Účet zdarma na veřejném serveru matrix.org\n2. Vlastní hosting serveru na vlastním hardwaru\n3. Účet na přizpůsobeném serveru jednodýúchým přihlášením na hosting Element Matrix Services\n\nProč Element?\n\nVLASTNĚTE SVÁ DATA: Vy rozhodujete kde jsou vaše data a zprávy uchovávány. Svá data vlastníte a spravujete Vy, ne nějaká obří korporace, která o vás sbírá osobní data nebo poskytuje přístup dalším stranám.\n\nOTEVŘENÁ KOMUNIKACE A SPOLUPRÁCE: Máte možnost spojit se s kýmkoli v síti Matrix bez ohledu na jeho softwarové řešení, a dokonce se můžete připojit i na jiné komunikační protokoly, jako je Slack, IRC nebo XMPP (Jabber). Komunita podporuje i komunikátory jako Whatsapp, Telegram nebo iMessage.\n\nNEPROLOMITELNÉ ŠIFROVÁNÍ: Skutečné koncové šifrování (pouze přímí účastníci konverzace mají možnost rozšifrovat jejich zprávy) a pokročilé ověřování kontaktů.\n\nVŠESTRANNÉ KOMUNIKAČNÍ MOŽNOSTI: Textové zprávy, hlasové nebo videohovory, přenos souborů, sdílení obrazovky a mnoho dalších funkcí a možností pro implementaci. Vytvářejte místnosti a komunity a zůstaňte v kontaktu.\n\nKDEKOLI SE NACHÁZÍTE: Přístup k plně synchronizované historii konverzací máte kdekoli se nacházíte, ať už z aplikace anebo webového rozhraní na https://element.io/app."; +"user_verification_sessions_list_session_untrusted" = "Nedůvěryhodná"; +"user_verification_sessions_list_session_trusted" = "Důvěryhodná"; +"user_verification_sessions_list_table_title" = "Relace"; +"user_verification_sessions_list_information" = "Zprávy s tímto uživatelem v této místnosti jsou šifrovány end-to-end a nemohou je číst třetí strany."; +"user_verification_sessions_list_user_trust_level_unknown_title" = "Není známo"; +"user_verification_sessions_list_user_trust_level_warning_title" = "Varování"; + +// Sessions list + +"user_verification_sessions_list_user_trust_level_trusted_title" = "Důvěryhodný"; +"user_verification_start_additional_information" = "Abyste byli v bezpečí, dělejte to osobně nebo použijte jiný způsob komunikace."; +"user_verification_start_waiting_partner" = "Čekám na %@…"; +"user_verification_start_information_part2" = " kontrolou jednorázového kódu na obou zařízeních."; +"user_verification_start_information_part1" = "Pro větší bezpečnost ověřit "; + +// MARK: - User verification + +// Start + +"user_verification_start_verify_action" = "Spustit ověřování"; +"key_verification_user_title" = "Ověřit je"; +"room_participants_security_information_room_encrypted" = "Zprávy v této místnosti jsou šifrovány end-to-end.\n\nVaše zprávy jsou zabezpečeny zámky a jedinečnými klíči je můžete odemknout pouze vy a příjemce."; +"room_participants_security_information_room_not_encrypted" = "Zprávy v této místnosti nejsou šifrovány end-to-end."; +"room_participants_security_loading" = "Načítání…"; +"room_participants_action_security_status_warning" = "Varování"; +"room_participants_action_security_status_verify" = "Ověřit"; +"room_participants_action_security_status_verified" = "Ověřeno"; +"room_participants_action_section_security" = "Zabezpečení"; +"room_participants_action_ban" = "Vyhodit z této místnosti"; +"room_creation_error_invite_user_by_email_without_identity_server" = "Není nakonfigurován žádný server identity, takže nemůžete přidat účastníka pomocí e-mailu."; +"auth_softlogout_recover_encryption_keys" = "Přihlaste se a obnovte šifrovací klíče uložené výhradně v tomto zařízení. Potřebujete je ke čtení všech zabezpečených zpráv na jakémkoli zařízení."; +"auth_softlogout_reason" = "Váš správce domovského serveru (%1$@) vás odhlásil z vašeho účtu %2 @ (%3$@)."; +"auth_softlogout_signed_out" = "Jste odhlášeni"; +"auth_autodiscover_invalid_response" = "Neplatná odpověď na objevení domovského serveru"; +"auth_accept_policies" = "Přečtěte si a přijměte zásady tohoto domovského serveru:"; +"auth_add_email_and_phone_warning" = "Registrace pomocí e-mailu a telefonního čísla najednou ještě není podporována, dokud neexistuje rozhraní API. Zohledněno bude pouze telefonní číslo. Svůj e-mail můžete přidat do svého profilu v nastavení."; +"auth_reset_password_error_is_required" = "Není nakonfigurován žádný server identity: pro obnovení hesla přidejte jeden server z možností ."; +"auth_forgot_password_error_no_configured_identity_server" = "Není nakonfigurován žádný server identity: pro obnovení hesla jeden přidejte ."; +"settings_key_backup_button_delete" = "Smazat zálohu"; +"settings_key_backup_button_restore" = "Obnovit ze zálohy"; +"auth_login_single_sign_on" = "Přihlásit se"; + +// Accessibility +"accessibility_checkbox_label" = "zaškrtávací políčko"; +"callbar_only_single_active_group" = "Klepnutím se připojíte ke skupinovému hovoru (%@)"; +"callbar_return" = "Vrátit se"; +"callbar_only_multiple_paused" = "%@ pozastavené hovory"; +"callbar_only_single_paused" = "Pozastavený hovor"; +"callbar_active_and_multiple_paused" = "1 aktivní hovor (%@) · %@ pozastavené hovory"; +"callbar_active_and_single_paused" = "1 aktivní hovor (%@) · 1 pozastavený hovor"; + +// Call Bar +"callbar_only_single_active" = "Klepnutím se vrátíte k hovoru (%@)"; +"less" = "Méně"; +"more" = "Více"; +"switch" = "Přepnout"; +"joined" = "Připojil se"; +"skip" = "Přeskočit"; +"close" = "Zavřít"; +"store_promotional_text" = "Aplikace pro chat a spolupráci chránící soukromí v otevřené síti. Žádný sběr dat, žádná zadní vrátka a žádný přístup třetích stran."; +// String for App Store +"store_short_description" = "Zabezpečený decentralizovaný chat/VoIP"; +"e2e_key_backup_wrong_version_button_settings" = "Nastavení"; +"side_menu_action_settings" = "Nastavení"; +"room_details_photo" = "Obrázek místnosti"; +"room_details_settings" = "Nastavení"; +"room_details_integrations" = "Integrace"; +"room_details_search" = "Vyhledat místnost"; +"room_details_files" = "Nahrávání"; +"room_details_people" = "Členové"; +"room_details_title_for_dm" = "Podrobnosti"; +"room_avatar_view_accessibility_hint" = "Změnit avatar místnosti"; +"room_creation_appearance_picture" = "Obrázek chatu (volitelné)"; + +// Errors +"error_user_already_logged_in" = "Vypadá to, že se pokoušíte připojit k jinému domovskému serveru. Chcete se odhlásit?"; +"social_login_button_title_sign_up" = "Zaregistrovat se pomocí %@"; +"social_login_button_title_sign_in" = "Přihlásit se pomocí %@"; +"social_login_button_title_continue" = "Pokračovat s %@"; +"social_login_list_title_sign_up" = "nebo"; +"social_login_list_title_sign_in" = "nebo"; + +// Social login + +"social_login_list_title_continue" = "Pokračovat s"; +"auth_softlogout_clear_data_sign_out" = "Odhlásit se"; +"auth_softlogout_clear_data_sign_out_msg" = "Opravdu chcete vymazat všechny údaje aktuálně uložené v tomto zařízení? Chcete-li získat přístup k údajům a zprávám svého účtu, znovu se přihlaste."; +"auth_softlogout_clear_data_sign_out_title" = "Jste si jisti?"; +"auth_softlogout_clear_data_button" = "Vymazat všechny údaje"; +"auth_softlogout_clear_data_message_2" = "Vymažte, pokud jste s tímto zařízením skončili nebo se chcete přihlásit k jinému účtu."; +"auth_softlogout_clear_data_message_1" = "Varování: Vaše osobní údaje (včetně šifrovacích klíčů) jsou stále uloženy v tomto zařízení."; +"auth_softlogout_clear_data" = "Vymazat osobní údaje"; +"auth_softlogout_sign_in" = "Přihlásit se"; +"biometrics_setup_subtitle" = "Ušetřete si čas"; +"biometrics_setup_enable_button_title_x" = "Povolit %@"; +"biometrics_setup_title_x" = "Povolit %@"; +"biometrics_settings_enable_x" = "Povolit %@"; +"biometrics_mode_face_id" = "Face ID"; +"biometrics_cant_unlocked_alert_title" = "Aplikaci nelze odemknout"; +"biometrics_usage_reason" = "Pro přístup k vaší aplikaci je nutné ověření"; +"biometrics_desetup_disable_button_title_x" = "Zakázat %@"; +"biometrics_desetup_title_x" = "Zakázat %@"; +"public_room_section_title" = "Veřejné místnosti (v %@):"; +"homeserver_connection_lost" = "Nelze se připojit k domovskému serveru."; +"network_offline_prompt" = "Zdá se, že připojení k internetu je offline."; +"yesterday" = "Včera"; +"today" = "Dnes"; diff --git a/Riot/Assets/cy.lproj/Vector.strings b/Riot/Assets/cy.lproj/Vector.strings index e1524bbc8..36b72d7b5 100644 --- a/Riot/Assets/cy.lproj/Vector.strings +++ b/Riot/Assets/cy.lproj/Vector.strings @@ -130,7 +130,6 @@ "room_creation_error_invite_user_by_email_without_identity_server" = "Nid oes unrhyw weinydd adnabod wedi'i osod felly ni allwch ychwanegu cyfranogwr gydag e-bost."; // Room recents "room_recents_directory_section" = "CYFEIRIADUR YSTAFELLOEDD"; -"room_recents_directory_section_network" = "Rhwydwaith"; "room_recents_favourites_section" = "FFEFRYNAU"; "room_recents_people_section" = "POBL"; "room_recents_conversations_section" = "YSTAFELLOEDD"; @@ -374,7 +373,6 @@ "settings_global_settings_info" = "Mae gosodiadau hysbysu eang ar gael ar eich cleient %@ gwe"; "settings_pin_rooms_with_missed_notif" = "Pinio ystafelloedd gyda hysbysiadau heb eu gweld"; "settings_pin_rooms_with_unread" = "Pinio ystafelloedd gyda negeseuon heb eu darllen"; -"settings_on_denied_notification" = "Gwrthodir hysbysiadau i %@, caniatewch nhw yn eich gosodiadau dyfais"; "settings_enable_callkit" = "Galw integredig"; "settings_callkit_info" = "Derbyn galwadau sy'n dod i mewn ar eich sgrin clo. Gwelwch eich galwadau Element yn hanes galwadau'r system. Os yw iCloud wedi'i alluogi, bydd yr hanes galw hwn yn cael ei rannu gydag Apple."; "settings_calls_stun_server_fallback_button" = "Caniatáu gweinydd cymorth galw wrth gefn"; diff --git a/Riot/Assets/de.lproj/Vector.strings b/Riot/Assets/de.lproj/Vector.strings index 52afbc691..32aabeaf5 100644 --- a/Riot/Assets/de.lproj/Vector.strings +++ b/Riot/Assets/de.lproj/Vector.strings @@ -79,7 +79,6 @@ "room_creation_wait_for_creation" = "Es wird gerade schon ein Raum erstellt. Bitte warten."; // Room recents "room_recents_directory_section" = "RAUM VERZEICHNIS"; -"room_recents_directory_section_network" = "Netzwerk"; "room_recents_favourites_section" = "FAVORITEN"; "room_recents_people_section" = "PERSONEN"; "room_recents_conversations_section" = "RÄUME"; @@ -317,7 +316,6 @@ "settings_global_settings_info" = "Globale Benachrichtigungseinstellungen sind auf deinem %@ web-Client verfügbar"; "settings_pin_rooms_with_missed_notif" = "Pinnen von Räumen mit verpassten Benachrichtigungen"; "settings_pin_rooms_with_unread" = "Pinnen von Räumen mit ungelesenen Nachrichten"; -"settings_on_denied_notification" = "Benachrichtigungen verboten für %@, bitte in den Geräte-Einstellungen erlauben"; "settings_contacts_discover_matrix_users" = "Entdecke andere Benutzer mittels E-Mail-Adressen oder Telefonnummern"; "settings_labs_e2e_encryption_prompt_message" = "Zum Fertigstellen der Verschlüsselung bitte neu anmelden."; "settings_third_party_notices" = "Anmerkungen von Dritten"; @@ -506,7 +504,7 @@ "group_rooms_filter_rooms" = "Filtere Community-Räume"; "e2e_room_key_request_message_new_device" = "Du hast die neue Sitzung '%@' hinzugefügt, welche Verschlüsselungs-Schlüssel anfordert."; "room_do_not_have_permission_to_post" = "Du hast keine Berechtigung Nachrichten in diesem Raum zu senden"; -"room_event_action_kick_prompt_reason" = "Grund für den Rauswurf des Benutzers"; +"room_event_action_kick_prompt_reason" = "Grund für das Entfernen des Benutzers"; "room_event_action_ban_prompt_reason" = "Grund für die Verbannung des Benutzers"; "room_action_send_photo_or_video" = "Foto oder Video senden"; "room_action_send_sticker" = "Aufkleber senden"; @@ -1389,3 +1387,16 @@ "room_recents_unknown_room_error_message" = "Raum kann nicht gefunden werden. Überprüfe bitte, dass er existiert"; "room_creation_dm_error" = "Fehler beim Erstellen der Direktnachricht. Bitte überprüfe die eingeladenen Leute und versuche es erneut."; "settings_ui_theme_picker_message_invert_colours" = "\"Auto\" verwendet die Farbinvertierungseinstellung deines Geräts"; +"key_verification_verify_qr_code_scan_code_other_device_action" = "Mit diesem Gerät scannen"; +"room_notifs_settings_encrypted_room_notice" = "Am Handy sind Benachrichtigungen bei Erwähnungen und Schlüsselwörtern in verschlüsselten Räumen nicht verfügbar."; +"room_notifs_settings_account_settings" = "Kontoeinstellungen"; +"room_notifs_settings_manage_notifications" = "Benachrichtigungen kannst du in %@ verwalten"; +"room_notifs_settings_cancel_action" = "Abbrechen"; +"room_notifs_settings_done_action" = "Fertig"; +"room_notifs_settings_none" = "Nichts"; +"room_notifs_settings_mentions_and_keywords" = "Nur Erwähnungen und Schlüsselwörtern"; +"room_notifs_settings_all_messages" = "Allen Nachrichten"; + +// Room Notification Settings +"room_notifs_settings_notify_me_for" = "Benachrichtige mich bei"; +"room_details_notifs" = "Benachrichtigungen"; diff --git a/Riot/Assets/en.lproj/InfoPlist.strings b/Riot/Assets/en.lproj/InfoPlist.strings index 6387f123d..468d42cce 100644 --- a/Riot/Assets/en.lproj/InfoPlist.strings +++ b/Riot/Assets/en.lproj/InfoPlist.strings @@ -17,7 +17,7 @@ // Permissions usage explanations "NSCameraUsageDescription" = "The camera is used to take photos and videos, make video calls."; "NSPhotoLibraryUsageDescription" = "The photo library is used to send photos and videos."; -"NSMicrophoneUsageDescription" = "The microphone is used to take videos, make calls."; +"NSMicrophoneUsageDescription" = "Element needs to access your microphone to make and receive calls, take videos, and record voice messages."; "NSContactsUsageDescription" = "To discover contacts already using Matrix, Element can send email addresses and phone numbers in your address book to your chosen Matrix identity server. Where supported, personal data is hashed before sending - please check your identity server's privacy policy for more details."; "NSCalendarsUsageDescription" = "See your scheduled meetings in the app."; "NSFaceIDUsageDescription" = "Face ID is used to access your app."; diff --git a/Riot/Assets/en.lproj/Vector.strings b/Riot/Assets/en.lproj/Vector.strings index 234022cf2..4193d23ca 100644 --- a/Riot/Assets/en.lproj/Vector.strings +++ b/Riot/Assets/en.lproj/Vector.strings @@ -182,7 +182,6 @@ // Room recents "room_recents_directory_section" = "ROOM DIRECTORY"; -"room_recents_directory_section_network" = "Network"; "room_recents_favourites_section" = "FAVOURITES"; "room_recents_people_section" = "PEOPLE"; "room_recents_conversations_section" = "ROOMS"; @@ -493,11 +492,13 @@ Tap the + to start adding people."; "settings_security" = "SECURITY"; "settings_enable_push_notif" = "Notifications on this device"; +"settings_device_notifications" = "Device notifications"; "settings_show_decrypted_content" = "Show decrypted content"; "settings_global_settings_info" = "Global notification settings are available on your %@ web client"; "settings_pin_rooms_with_missed_notif" = "Pin rooms with missed notifications"; "settings_pin_rooms_with_unread" = "Pin rooms with unread messages"; -"settings_on_denied_notification" = "Notifications are denied for %@, please allow them in your device settings"; +"settings_notifications_disabled_alert_title" = "Notifications disabled"; +"settings_notifications_disabled_alert_message" = "To enable notifications, go to your device settings."; //"settings_enable_all_notif" = "Enable all notifications"; //"settings_messages_my_display_name" = "Msg containing my display name"; //"settings_messages_my_user_name" = "Msg containing my user name"; @@ -534,6 +535,7 @@ Tap the + to start adding people."; "settings_labs_create_conference_with_jitsi" = "Create conference calls with jitsi"; "settings_labs_message_reaction" = "React to messages with emoji"; "settings_labs_enable_ringing_for_group_calls" = "Ring for group calls"; +"settings_labs_voice_messages" = "Voice messages"; "settings_version" = "Version %@"; "settings_olm_version" = "Olm Version %@"; @@ -710,6 +712,7 @@ Tap the + to start adding people."; "room_details_topic" = "Topic"; "room_details_favourite_tag" = "Favourite"; "room_details_low_priority_tag" = "Low priority"; +"room_details_notifs" = "Notifications"; "room_details_mute_notifs" = "Mute notifications"; "room_details_direct_chat" = "Direct Chat"; "room_details_access_section"="Who can access this room?"; @@ -772,6 +775,17 @@ Tap the + to start adding people."; "room_details_copy_room_address" = "Copy Room Address"; "room_details_copy_room_url" = "Copy Room URL"; +// Room Notification Settings +"room_notifs_settings_notify_me_for" = "Notify me for"; +"room_notifs_settings_all_messages" = "All Messages"; +"room_notifs_settings_mentions_and_keywords" = "Mentions and Keywords only"; +"room_notifs_settings_none" = "None"; +"room_notifs_settings_done_action" = "Done"; +"room_notifs_settings_cancel_action" = "Cancel"; +"room_notifs_settings_manage_notifications" = "You can manage notifications in %@"; +"room_notifs_settings_account_settings" = "Account settings"; +"room_notifs_settings_encrypted_room_notice" = "Please note that mentions & keyword notifications are not available in encrypted rooms on mobile."; + // Group Details "group_details_title" = "Community Details"; "group_details_home" = "Home"; @@ -832,14 +846,16 @@ Tap the + to start adding people."; "event_formatter_rerequest_keys_part1_link" = "Re-request encryption keys"; "event_formatter_rerequest_keys_part2" = " from your other sessions."; "event_formatter_message_edited_mention" = "(edited)"; -"event_formatter_call_voice" = "Voice call"; -"event_formatter_call_video" = "Video call"; "event_formatter_call_connecting" = "Connecting…"; "event_formatter_call_ringing" = "Ringing…"; -"event_formatter_call_has_ended" = "Ended %@"; -"event_formatter_call_you_currently_in" = "Active call"; -"event_formatter_call_you_declined" = "You declined this call"; -"event_formatter_call_you_missed" = "You missed this call"; +"event_formatter_call_has_ended" = "Call ended %@"; +"event_formatter_call_incoming_voice" = "Incoming voice call"; +"event_formatter_call_incoming_video" = "Incoming video call"; +"event_formatter_call_active_voice" = "Active voice call"; +"event_formatter_call_active_video" = "Active video call"; +"event_formatter_call_you_declined" = "Call declined"; +"event_formatter_call_missed_voice" = "Missed voice call"; +"event_formatter_call_missed_video" = "Missed video call"; "event_formatter_call_connection_failed" = "Connection failed"; "event_formatter_call_back" = "Call back"; "event_formatter_call_decline" = "Decline"; @@ -1375,6 +1391,7 @@ Tap the + to start adding people."; "key_verification_verify_qr_code_information_other_device" = "Scan the code below to verify:"; "key_verification_verify_qr_code_emoji_information" = "Verify by comparing unique emoji."; "key_verification_verify_qr_code_scan_code_action" = "Scan their code"; +"key_verification_verify_qr_code_scan_code_other_device_action" = "Scan with this device"; "key_verification_verify_qr_code_cannot_scan_action" = "Can't scan?"; "key_verification_verify_qr_code_start_emoji_action" = "Verify by emoji"; @@ -1668,3 +1685,10 @@ Tap the + to start adding people."; "side_menu_action_help" = "Help"; "side_menu_action_feedback" = "Feedback"; "side_menu_app_version" = "Version %@"; + +// Mark: - Voice Messages + +"voice_message_release_to_send" = "Hold to record, release to send"; +"voice_message_remaining_recording_time" = "%@s left"; +"voice_message_stop_locked_mode_recording" = "Tap on your recording to stop or listen"; +"voice_message_lock_screen_placeholder" = "Voice message"; diff --git a/Riot/Assets/eo.lproj/Vector.strings b/Riot/Assets/eo.lproj/Vector.strings index 8a4575e64..5fb7c6707 100644 --- a/Riot/Assets/eo.lproj/Vector.strings +++ b/Riot/Assets/eo.lproj/Vector.strings @@ -244,7 +244,6 @@ "room_recents_server_notice_section" = "SISTEMAJ AVERTOJ"; "room_recents_low_priority_section" = "MALALTA PRIORITATO"; "room_recents_favourites_section" = "ELSTARIGITAJ"; -"room_recents_directory_section_network" = "Reto"; // Room recents "room_recents_directory_section" = "KATALOGO DE ĈAMBROJ"; @@ -976,7 +975,6 @@ "settings_calls_stun_server_fallback_description" = "Permesi repaŝan servilon %@ asistan je vokoj, kiam la hejmservilo ne provizas servilon (via IP-adreso ne doniĝus dum voko)."; "settings_calls_stun_server_fallback_button" = "Permesi repaŝan servilon asistan je vokoj"; "settings_callkit_info" = "Ricevi vokpetojn ĉe via ŝlosa ekrano. Vidi viajn vokojn de Element ĉe la sistema vokhistorio. Se iCloud estas ŝaltita, la vokhistorio doniĝas ankaŭ al Apple."; -"settings_on_denied_notification" = "Sciigo por %s estas malŝaltitaj, bonvole permesu ilin per la agordoj de via aparato"; "settings_pin_rooms_with_unread" = "Alpingli ĉambrojn kun nelegitaj mesaĝoj"; "settings_pin_rooms_with_missed_notif" = "Alpinigli ĉambrojn kun nerimarkitaj sciigoj"; "settings_show_decrypted_content" = "Montri malĉifritajn enhavojn"; diff --git a/Riot/Assets/es.lproj/Vector.strings b/Riot/Assets/es.lproj/Vector.strings index c14cdda6b..bcb6247d5 100644 --- a/Riot/Assets/es.lproj/Vector.strings +++ b/Riot/Assets/es.lproj/Vector.strings @@ -144,7 +144,6 @@ "room_creation_invite_another_user" = "Buscar / invitar por ID de Usuario, Nombre o correo electrónico"; // Room recents "room_recents_directory_section" = "DIRECTORIO DE SALAS"; -"room_recents_directory_section_network" = "Red"; "room_recents_favourites_section" = "FAVORITOS"; "room_recents_people_section" = "PERSONAS"; "room_recents_conversations_section" = "SALAS"; @@ -322,7 +321,6 @@ "settings_global_settings_info" = "Los ajustes de notificación globales están disponibles en tu cliente web %@"; "settings_pin_rooms_with_missed_notif" = "Fijar salas con notificaciones pendientes"; "settings_pin_rooms_with_unread" = "Fijar salas con mensajes no leídos"; -"settings_on_denied_notification" = "Las notificaciones están denegadas para %@, por favor habilita notificaciones en los ajustes de tu dispositivo"; "settings_enable_callkit" = "Integración de llamadas"; "settings_callkit_info" = "Recibe llamadas entrantes en tu pantalla de bloqueo. Ve tus llamadas de Element en el historial de llamadas del sistema. Si iCloud está habilitado, este historial de llamadas se compartirá con Apple."; "settings_ui_language" = "Idioma"; diff --git a/Riot/Assets/et.lproj/InfoPlist.strings b/Riot/Assets/et.lproj/InfoPlist.strings index df3ae7fb3..238ddd486 100644 --- a/Riot/Assets/et.lproj/InfoPlist.strings +++ b/Riot/Assets/et.lproj/InfoPlist.strings @@ -1,7 +1,7 @@ // Permissions usage explanations "NSCameraUsageDescription" = "Kaameraga salvestatakse pilte ning videosid ja tehakse videokõnesid."; "NSPhotoLibraryUsageDescription" = "Fotogaleriid kasutatakse fotode ja videote saatmiseks teistele kasutajatele."; -"NSMicrophoneUsageDescription" = "Mikrofoni kasutatakse videote salvestamisel ning kõnede tegemisel."; +"NSMicrophoneUsageDescription" = "Kõnede tegemiseks, videote ja häälsõnumite salvestamiseks vajab Element ligipääsu sinu seadme mikrofonile."; "NSCalendarsUsageDescription" = "Vaata päevakavasse lisatud koosolekuid vastvast rakendusest."; "NSContactsUsageDescription" = "Selleks, et leida Matrixi võrgu kasutajaid, võib Element saata sinu aadressiraamatus leiduvad e-posti aadressid ja telefoninumbrid sinu valitud Matrixi isikutuvastusserverile. Kui server seda toetab, siis andmed muudetakse enne saatmist räsideks - täpsema teabe leiad oma isikutuvastusserveri privaatsuspoliitikast."; "NSFaceIDUsageDescription" = "Ligipääsuks sinu rakendusele on kasutusel Face ID."; diff --git a/Riot/Assets/et.lproj/Vector.strings b/Riot/Assets/et.lproj/Vector.strings index 32ede1f7f..7deb01246 100644 --- a/Riot/Assets/et.lproj/Vector.strings +++ b/Riot/Assets/et.lproj/Vector.strings @@ -218,7 +218,6 @@ "auth_login_single_sign_on" = "Logi sisse"; // Room recents "room_recents_directory_section" = "JUTUTUBADE LOEND"; -"room_recents_directory_section_network" = "Võrk"; "room_recents_favourites_section" = "LEMMIKUD"; "room_recents_people_section" = "INIMESED"; "room_recents_conversations_section" = "JUTUTOAD"; @@ -498,7 +497,6 @@ "settings_global_settings_info" = "Üldised teavituste seadistused leiduvad sinu %@ veebikliendis"; "settings_pin_rooms_with_missed_notif" = "Klammerda jututoad, kus leidub lugemata teavitusi"; "settings_pin_rooms_with_unread" = "Klammerda jututoad, kus leidub lugemata sõnumeid"; -"settings_on_denied_notification" = "Teavitused on %@ jaoks keelatud, palun luba nad oma seadme seadistustes"; "settings_enable_callkit" = "Lõimitud helistamine"; "settings_callkit_info" = "Vasta kõnedele lukustuskuvalt. Vaata Element'i kõnesid süsteemi kõnelogist. Kui iCloud on kasutusel, siis kõnede ajalugu jagatakse Applega."; "settings_calls_stun_server_fallback_button" = "Kasuta kõnehõlbustusserverit"; @@ -1097,7 +1095,7 @@ "deactivate_account_informations_part3" = "\n\nSinu konto kustutamine "; "deactivate_account_informations_part4_emphasize" = "vaikimisi ei tähenda, et unustatakse ka sinu saadetud sõnumid. "; "deactivate_account_informations_part5" = "Kui sa siiski soovid seda, siis palun tee märge alljärgnevasse kasti.\n\nMatrix'i sõnumite nähtavus on sarnane e-posti kirjadega. Sõnumite unustamine tegelikult tähendab seda, et sinu varemsaadetud sõnumeid ei jagata uute või veel registreerumata kasutajatega, kuid registeerunud kasutajad, kes juba on need sõnumid saanud, võivad neid ka jätkuvalt lugeda."; -"rerequest_keys_alert_message" = "Palun käivita Element mõnes muus seadmes, mis suudab neid sõnumeid dekrüptoda ja seega saata krüptovõtmeid siia sessiooni."; +"rerequest_keys_alert_message" = "Palun käivita Element mõnes muus seadmes, mis suudab neid sõnumeid dekrüptida ja seega saata krüptovõtmeid siia sessiooni."; "settings_discovery_three_pids_management_information_part1" = "Halda missuguse e-posti aadressi ja telefoninumbri alusel teised kasutajad saavad sind kutsuda jututubadesse. Lisa või eemalda e-posti aadresse ja telefoninumbreid sellest loendist "; "settings_discovery_three_pid_details_information_email" = "Halda selle e-posti aadressi eelistusi, mille alusel teised kasutajad saavad sind leida ja kutsuda jututubade liikmeks. E-posti aadresse lisada ja muuta saad kasutajakonto seadistustest."; "settings_discovery_three_pid_details_title_phone_number" = "Halda telefoninumbrit"; @@ -1349,3 +1347,23 @@ "settings_ui_theme_picker_message_invert_colours" = "Automaatne valik kasutab sinu seadme pööratud värvide seadistust"; "room_recents_unknown_room_error_message" = "Ei leia sellist jututuba. Palun kontrolli, et ta ikka olemas on"; "room_creation_dm_error" = "Otsevestluse loomine ei õnnestunud. Palun kontrolli, et kasutajanimed oleks õiged ja proovi uuesti."; +"key_verification_verify_qr_code_scan_code_other_device_action" = "Skaneeri selle seadmega"; +"room_notifs_settings_encrypted_room_notice" = "Teavitused mainimiste ja võtmesõnade esinemise puhul pole mobiilirakenduses krüptitud jututoas saadaval."; +"room_notifs_settings_account_settings" = "Kasutajakonto seadistused"; +"room_notifs_settings_manage_notifications" = "Sa võid hallata teavitusi %@ jututoas"; +"room_notifs_settings_cancel_action" = "Katkesta"; +"room_notifs_settings_done_action" = "Valmis"; +"room_notifs_settings_none" = "mitte ühelgi juhul"; +"room_notifs_settings_mentions_and_keywords" = "mainimiste ja võtmesõnade leidumise puhul"; +"room_notifs_settings_all_messages" = "kõikide sõnumite puhul"; + +// Room Notification Settings +"room_notifs_settings_notify_me_for" = "Teavita mind"; +"room_details_notifs" = "Teavitused"; +"voice_message_stop_locked_mode_recording" = "Salvestuse peatamiseks ja taasesituseks vajuta salvestuse vaadet"; +"voice_message_remaining_recording_time" = "salvestusaega jäänud %@s"; + +// Mark: - Voice Messages + +"voice_message_release_to_send" = "Salvestamiseks vajuta nuppu, saatmiseks lase nupp lahti"; +"settings_labs_voice_messages" = "Häälsõnumid"; diff --git a/Riot/Assets/eu.lproj/Vector.strings b/Riot/Assets/eu.lproj/Vector.strings index 38a6e4d34..b3651544f 100644 --- a/Riot/Assets/eu.lproj/Vector.strings +++ b/Riot/Assets/eu.lproj/Vector.strings @@ -238,7 +238,6 @@ "room_creation_invite_another_user" = "Bilatu / gonbidatu erabiltzaile ID-a, izena edo e-maila erabiliz"; // Room recents "room_recents_directory_section" = "GELEN DIREKTORIOA"; -"room_recents_directory_section_network" = "Sarea"; "room_recents_favourites_section" = "GOGOKOAK"; "room_recents_invites_section" = "GONBIDAPENAK"; // People tab @@ -397,7 +396,6 @@ "room_participants_remove_third_party_invite_msg" = "Hirugarrengoen gonbidapenak kentzea ez da onartzen APIa ez dagoen bitartean"; "settings_sign_out_e2e_warn" = "Zure muturretik muturrerako zifratze gakoak galduko dituzu. Horrek esan nahi du ezin izango dituzula mezu zaharrak gehiago irakurri zifratutako geletan gailu honetatik."; "settings_global_settings_info" = "Jakinarazpen orokorren ezarpenak eskuragarri daude zure %@ web bezeroan"; -"settings_on_denied_notification" = "Jakinarazpenak ukatu dira %@(e)n, baimendu zure gailuaren ezarpenetan"; "room_details_history_section_prompt_msg" = "Historiala nork irakurri dezakeen aldatzea gelak honetara aurrerantzean bidalitako mezuei besterik ez zaie aplikatuko. Badagoen historialaren ikusgaitasuna ez da aldatuko."; // Call "call_incoming_voice_prompt" = "%@ erabiltzailearen deia jasotzen"; diff --git a/Riot/Assets/fr.lproj/Vector.strings b/Riot/Assets/fr.lproj/Vector.strings index f78010dfe..d7734038b 100644 --- a/Riot/Assets/fr.lproj/Vector.strings +++ b/Riot/Assets/fr.lproj/Vector.strings @@ -97,7 +97,6 @@ "room_creation_invite_another_user" = "Rechercher/inviter par identifiant, nom ou e-mail"; // Room recents "room_recents_directory_section" = "RÉPERTOIRE DES SALONS"; -"room_recents_directory_section_network" = "Réseau"; "room_recents_favourites_section" = "FAVORIS"; "room_recents_people_section" = "PERSONNES"; "room_recents_conversations_section" = "SALONS"; @@ -279,7 +278,6 @@ "settings_global_settings_info" = "Les paramètres de notification globaux sont disponibles sur votre client web %@"; "settings_pin_rooms_with_missed_notif" = "Épingler les salons avec des notifications non lues"; "settings_pin_rooms_with_unread" = "Épingler les salons avec des messages non lus"; -"settings_on_denied_notification" = "Les notifications sont refusées pour %@, merci de les autoriser dans les paramètres de votre appareil"; "settings_unignore_user" = "Afficher tous les messages de %@ ?"; "settings_contacts_discover_matrix_users" = "Utiliser un e-mail ou un numéro de téléphone pour retrouver des utilisateurs"; "settings_contacts_phonebook_country" = "Pays pour le répertoire téléphonique"; @@ -1426,3 +1424,16 @@ "settings_ui_theme_picker_message_invert_colours" = "« Auto » utilise le paramètre « Inverser les couleurs » de l’appreil"; "room_recents_unknown_room_error_message" = "Aucun résultat dans ce salon. Assurez vous de son existence"; "room_creation_dm_error" = "Nous n’avons pas pu créer votre message direct. Merci de vérifier les utilisateurs que vous voulez inviter et réessayer."; +"key_verification_verify_qr_code_scan_code_other_device_action" = "Scanner avec cet appareil"; +"room_notifs_settings_encrypted_room_notice" = "Veuillez noter que les mentions et mots-clés ne sont pas disponibles dans les salons chiffrés sur mobile."; +"room_notifs_settings_account_settings" = "Paramètres du compte"; +"room_notifs_settings_manage_notifications" = "Vous pouvez gérer les notifications dans %@"; +"room_notifs_settings_cancel_action" = "Annuler"; +"room_notifs_settings_done_action" = "Terminé"; +"room_notifs_settings_none" = "Aucun"; +"room_notifs_settings_mentions_and_keywords" = "Seulement les mentions et les mots-clés"; +"room_notifs_settings_all_messages" = "Tous les messages"; + +// Room Notification Settings +"room_notifs_settings_notify_me_for" = "Me notifier pour"; +"room_details_notifs" = "Notifications"; diff --git a/Riot/Assets/hu.lproj/InfoPlist.strings b/Riot/Assets/hu.lproj/InfoPlist.strings index ee665818e..f874c48d5 100644 --- a/Riot/Assets/hu.lproj/InfoPlist.strings +++ b/Riot/Assets/hu.lproj/InfoPlist.strings @@ -1,7 +1,7 @@ // Permissions usage explanations "NSCameraUsageDescription" = "A kamera fényképek, videók készítéséhez és videóhívásokhoz lesz használva."; "NSPhotoLibraryUsageDescription" = "A fénykép galéria fényképek és videók küldéséhez lesz használva."; -"NSMicrophoneUsageDescription" = "A mikrofon videók készítéséhez és hívásokhoz lesz használva."; +"NSMicrophoneUsageDescription" = "A hívás indításához és fogadásához, videó és hangüzenet felvételéhez az Elementnek hozzáférési engedélyre van szüksége a mikrofonhoz."; "NSContactsUsageDescription" = "Az olyan ismerősök felderítéséhez akik már használják a Matrixot, Elementet el tudja küldeni a címjegyzékben található e-mail címeket és telefonszámokat az általad választott Matrix azonosítási szervernek. Ahol lehetséges a személyes adatok hash-elve lesznek - kérlek ellenőrizd az azonosítási szervered adatvédelmi szabályait."; "NSCalendarsUsageDescription" = "Nézd meg a találkozóidat az alkalmazásban."; "NSFaceIDUsageDescription" = "Arc felismerés használata az alkalmazás eléréséhez."; diff --git a/Riot/Assets/hu.lproj/Vector.strings b/Riot/Assets/hu.lproj/Vector.strings index 718f7de9b..da19f6cb8 100644 --- a/Riot/Assets/hu.lproj/Vector.strings +++ b/Riot/Assets/hu.lproj/Vector.strings @@ -108,7 +108,6 @@ "room_creation_invite_another_user" = "Keresés / meghívás felhasználói azonosítás, név vagy e-mail cím alapján"; // Room recents "room_recents_directory_section" = "SZOBA KÖNYVTÁR"; -"room_recents_directory_section_network" = "Hálózat"; "room_recents_favourites_section" = "KEDVENCEK"; "room_recents_people_section" = "EMBEREK"; "room_recents_conversations_section" = "SZOBÁK"; @@ -312,7 +311,6 @@ "settings_global_settings_info" = "Globális értesítési beállításokat a webes kliensedben találod: %@"; "settings_pin_rooms_with_missed_notif" = "Szobák kitűzése elszalasztott értesítésekkel"; "settings_pin_rooms_with_unread" = "Szobák kitűzése olvasatlan üzenetekkel"; -"settings_on_denied_notification" = "Az értesítések tiltva vannak ehhez: %@, kérlek engedélyezd az eszköz beállításaiban"; "settings_enable_callkit" = "Beépített hívás"; "settings_callkit_info" = "Hívások fogadása a zárolt képernyőn. Element hívások megjelenítése a rendszer hívás naplójában. Ha az iCloud engedélyezett akkor a hívásnapló az Apple-el megosztásra kerül."; "settings_ui_language" = "Nyelv"; @@ -1412,3 +1410,23 @@ "settings_ui_theme_picker_message_invert_colours" = "„Autó” az eszközöd „invertált színek” beállítását használja"; "room_recents_unknown_room_error_message" = "A szoba nem található. Győződj meg róla, hogy létezik"; "room_creation_dm_error" = "Nem lehet elkészíteni a közvetlen beszélgetést. Ellenőrizd a meghívni kívánt felhasználót és próbáld újra."; +"key_verification_verify_qr_code_scan_code_other_device_action" = "Beolvasás ezzel az eszközzel"; +"room_notifs_settings_encrypted_room_notice" = "Megjegyzendő, hogy titkosított szobákban a megemlítésekre és kulcsszavakra való értesítés mobil eszközökön nem érhető el."; +"room_notifs_settings_account_settings" = "Fiók beállítások"; +"room_notifs_settings_manage_notifications" = "Értesítések kezelése itt: %@"; +"room_notifs_settings_cancel_action" = "Mégsem"; +"room_notifs_settings_done_action" = "Kész"; +"room_notifs_settings_none" = "Semmi"; +"room_notifs_settings_mentions_and_keywords" = "Csak megemlítések és kulcsszavak"; +"room_notifs_settings_all_messages" = "Minden üzenet"; + +// Room Notification Settings +"room_notifs_settings_notify_me_for" = "Értesítés ezért:"; +"room_details_notifs" = "Értesítések"; +"voice_message_stop_locked_mode_recording" = "Megállításhoz és visszajátszáshoz koppints a felvételre"; +"voice_message_remaining_recording_time" = "%@s távozott"; + +// Mark: - Voice Messages + +"voice_message_release_to_send" = "Felvételhez tartsd nyomva, a küldéshez engedd el"; +"settings_labs_voice_messages" = "Hang üzenetek"; diff --git a/Riot/Assets/is.lproj/Vector.strings b/Riot/Assets/is.lproj/Vector.strings index f6fcd78c3..a34e891ad 100644 --- a/Riot/Assets/is.lproj/Vector.strings +++ b/Riot/Assets/is.lproj/Vector.strings @@ -63,7 +63,6 @@ "room_creation_appearance_name" = "Heiti"; "room_creation_privacy" = "Meðferð persónuupplýsinga"; "room_creation_make_private" = "Gera einka"; -"room_recents_directory_section_network" = "Netkerfi"; "room_recents_favourites_section" = "Eftirlæti"; "room_recents_people_section" = "FÓLK"; "room_recents_conversations_section" = "SPJALLRÁSIR"; @@ -443,7 +442,6 @@ "settings_fail_to_update_profile" = "Mistókst að uppfæra notandasnið"; "settings_pin_rooms_with_missed_notif" = "Festa spjallrásir með óskoðuðum tilkynningum"; "settings_pin_rooms_with_unread" = "Festa spjallrásir með ólesnum skilaboðum"; -"settings_on_denied_notification" = "Tilkynningum er hafnað fyrir %@, leyfðu þær í stillingum tækisins"; "settings_ui_theme_picker_message" = "\"Sjálfvirkt\" notar \"Umsnúa litum\" stillingar tækisins"; "settings_contacts_discover_matrix_users" = "Notaðu tölvupóstföng og símanúmer til að finna notendur"; "settings_labs_e2e_encryption_prompt_message" = "Til að ljúka við uppsetningu á dulritun verðurðu að skrá þig inn aftur."; diff --git a/Riot/Assets/it.lproj/InfoPlist.strings b/Riot/Assets/it.lproj/InfoPlist.strings index d46035a77..8e49c5075 100644 --- a/Riot/Assets/it.lproj/InfoPlist.strings +++ b/Riot/Assets/it.lproj/InfoPlist.strings @@ -1,7 +1,7 @@ // Permissions usage explanations "NSCameraUsageDescription" = "La fotocamera viene utilizzata per scattare fotografie, registrare video ed eseguire videochiamate."; "NSPhotoLibraryUsageDescription" = "La libreria fotografica viene utilizzata per inviare foto e video."; -"NSMicrophoneUsageDescription" = "Il microfono viene utilizzato per registrare video ed effettuare chiamate."; +"NSMicrophoneUsageDescription" = "Element ha bisogno di accedere al microfono per effettuare e ricevere chiamate, registrare video e messaggi vocali."; "NSContactsUsageDescription" = "Per scoprire i contatti che già usano Matrix, Element può inviare gli indirizzi email e i numeri di telefono della tua rubrica al server identità che hai scelto. Se supportato, viene fatto un hash dei dati personali prima dell'invio - controlla la politica sulla privacy del tuo server di identità per maggiori informazioni."; "NSCalendarsUsageDescription" = "Vedi le tue riunioni programmate nell'app."; "NSFaceIDUsageDescription" = "Face ID viene usato per accedere all'app."; diff --git a/Riot/Assets/it.lproj/Vector.strings b/Riot/Assets/it.lproj/Vector.strings index b6a2f0829..18d8dd011 100644 --- a/Riot/Assets/it.lproj/Vector.strings +++ b/Riot/Assets/it.lproj/Vector.strings @@ -112,7 +112,6 @@ "room_creation_invite_another_user" = "Cerca / invita per ID utente, nome o email"; // Room recents "room_recents_directory_section" = "ELENCO STANZE"; -"room_recents_directory_section_network" = "Rete"; "room_recents_favourites_section" = "PREFERITI"; "room_recents_people_section" = "CHAT DIRETTE"; "room_recents_conversations_section" = "STANZE"; @@ -328,7 +327,6 @@ "settings_global_settings_info" = "Le impostazioni di notifica avanzate sono disponibili nel tuo %@ web client"; "settings_pin_rooms_with_missed_notif" = "Segna le stanze con notifiche perse"; "settings_pin_rooms_with_unread" = "Segna le stanze con messaggi non letti"; -"settings_on_denied_notification" = "Le notifiche non sono permesse per %@, abilitale nelle impostazioni del tuo dispositivo"; "settings_enable_callkit" = "Chiamate integrate"; "settings_callkit_info" = "Ricevi le chiamate in arrivo sul blocca schermo. Mostra le chiamate Element nella cronologia di chiamate del dispositivo. Se iCloud è attivo, questa cronologia sarà condivisa con Apple."; "settings_ui_language" = "Lingua"; @@ -1383,3 +1381,23 @@ "settings_ui_theme_picker_message_invert_colours" = "\"Automatico\" usa l'impostazione \"Inverti Colori\" del tuo dispositivo"; "room_recents_unknown_room_error_message" = "Impossibile trovare questa stanza. Assicurati che esista"; "room_creation_dm_error" = "Impossibile creare il messaggio diretto. Ricontrolla gli utenti che vuoi invitare e riprova."; +"key_verification_verify_qr_code_scan_code_other_device_action" = "Scansiona con questo dispositivo"; +"room_notifs_settings_encrypted_room_notice" = "Nota che le notifiche per menzioni e parole chiave non sono disponibili nelle stanze cifrate su mobile."; +"room_notifs_settings_account_settings" = "impostazioni dell'account"; +"room_notifs_settings_manage_notifications" = "Puoi gestire le notifiche nelle %@"; +"room_notifs_settings_cancel_action" = "Annulla"; +"room_notifs_settings_done_action" = "Fatto"; +"room_notifs_settings_none" = "Niente"; +"room_notifs_settings_mentions_and_keywords" = "Solo menzioni e parole chiave"; +"room_notifs_settings_all_messages" = "Tutti i messaggi"; + +// Room Notification Settings +"room_notifs_settings_notify_me_for" = "Inviami notifiche per"; +"room_details_notifs" = "Notifiche"; +"voice_message_stop_locked_mode_recording" = "Tocca la registrazione per fermare o ascoltare"; +"voice_message_remaining_recording_time" = "%@s rimasti"; + +// Mark: - Voice Messages + +"voice_message_release_to_send" = "Tieni premuto per registrare, rilascia per inviare"; +"settings_labs_voice_messages" = "Messaggi vocali"; diff --git a/Riot/Assets/ja.lproj/Localizable.strings b/Riot/Assets/ja.lproj/Localizable.strings index 51a5c3d85..a2cbb08e6 100644 --- a/Riot/Assets/ja.lproj/Localizable.strings +++ b/Riot/Assets/ja.lproj/Localizable.strings @@ -50,3 +50,27 @@ "SINGLE_UNREAD_IN_ROOM" = "%@にメッセージを受け取りました"; /* A single unread message */ "SINGLE_UNREAD" = "あなたはメッセージを受け取りました"; + +/** Key verification **/ + +"KEY_VERIFICATION_REQUEST_FROM_USER" = "%@は検証したい"; + +/* New message indicator on a room */ +"MESSAGE_IN_X" = "%@ 内のメッセージ"; + +/* Sticker from a specific person, not referencing a room. */ +"STICKER_FROM_USER" = "%@ さんからのスタンプ"; +/* Message title for a specific person in a named room */ +"MSG_FROM_USER_IN_ROOM_TITLE" = "%@(%@ から)"; + +/* Group call from user, CallKit caller name */ +"GROUP_CALL_FROM_USER" = "%@ (グループ通話)"; +"MESSAGE_PROTECTED" = "新しいメッセージ"; + +/* New message indicator from a DM */ +"MESSAGE_FROM_X" = "%@ からのメッセージ"; + +/** Notification messages **/ + +/* New message indicator on unknown room */ +"MESSAGE" = "メッセージ"; diff --git a/Riot/Assets/ja.lproj/Vector.strings b/Riot/Assets/ja.lproj/Vector.strings index a3d3a68e7..2ca4ac3b8 100644 --- a/Riot/Assets/ja.lproj/Vector.strings +++ b/Riot/Assets/ja.lproj/Vector.strings @@ -2,7 +2,7 @@ "title_home" = "ホーム"; "title_favourites" = "お気に入り"; "title_people" = "対話"; -"title_rooms" = "部屋"; +"title_rooms" = "ルーム"; "warning" = "警告"; // Actions "view" = "表示"; @@ -92,39 +92,38 @@ "room_creation_account" = "アカウント"; "room_creation_appearance" = "外観"; "room_creation_appearance_name" = "名前"; -"room_creation_appearance_picture" = "部屋のアイコン画像 (任意で)"; +"room_creation_appearance_picture" = "チャット画像 (任意)"; "room_creation_privacy" = "個人情報保護"; "room_creation_private_room" = "この会話は非公開です"; "room_creation_public_room" = "この会話は公開されます"; -"room_creation_make_public" = "公開部屋作成"; -"room_creation_make_public_prompt_title" = "この部屋を公開しますか?"; -"room_creation_make_public_prompt_msg" = "この部屋を一般公開してもよろしいですか?誰でもあなたの発言を読み、部屋に参加できます。"; -"room_creation_keep_private" = "非公開部屋"; -"room_creation_make_private" = "非公開部屋を作成"; -"room_creation_wait_for_creation" = "部屋はすでに作成されています。お待ちください。"; +"room_creation_make_public" = "パブリックにする"; +"room_creation_make_public_prompt_title" = "このチャットをパブリックしますか?"; +"room_creation_make_public_prompt_msg" = "このチャットをパブリックしてもよろしいですか? 誰でもあなたのメッセージを読んでチャットに参加できます。"; +"room_creation_keep_private" = "プライベートに保つ"; +"room_creation_make_private" = "プライベートにする"; +"room_creation_wait_for_creation" = "ルームはすでに作成されています。 お待ちください。"; "room_creation_invite_another_user" = "ユーザID, 表示名, 電子メールで検索と招待"; // Room recents -"room_recents_directory_section" = "部屋一覧"; -"room_recents_directory_section_network" = "通信回線"; +"room_recents_directory_section" = "ルーム一覧"; "room_recents_favourites_section" = "お気に入り"; "room_recents_people_section" = "対話"; -"room_recents_conversations_section" = "部屋"; -"room_recents_no_conversation" = "部屋がありません"; +"room_recents_conversations_section" = "ルーム"; +"room_recents_no_conversation" = "ルームがありません"; "room_recents_low_priority_section" = "低優先度"; "room_recents_invites_section" = "招待中"; "room_recents_start_chat_with" = "対話を開始"; -"room_recents_create_empty_room" = "部屋を作成"; -"room_recents_join_room" = "部屋へ参加"; -"room_recents_join_room_title" = "部屋へ参加"; -"room_recents_join_room_prompt" = "部屋の固有IDまたは住所表記を入力"; +"room_recents_create_empty_room" = "ルームを作成"; +"room_recents_join_room" = "ルームへ参加"; +"room_recents_join_room_title" = "ルームへ参加"; +"room_recents_join_room_prompt" = "ルームIDまたはルームのエイリアスを入力します"; // People tab "people_invites_section" = "招待中"; "people_conversation_section" = "会話"; "people_no_conversation" = "会話なし"; // Rooms tab -"room_directory_no_public_room" = "公開された部屋がありません"; +"room_directory_no_public_room" = "利用可能なパブリックのルームはありません"; // Search -"search_rooms" = "部屋"; +"search_rooms" = "ルーム"; "search_messages" = "発言"; "search_people" = "対話"; "search_files" = "添付ファイル"; @@ -133,12 +132,12 @@ "search_no_result" = "結果なし"; "search_in_progress" = "検索中…"; // Directory -"directory_cell_title" = "部屋一覧を見る"; -"directory_cell_description" = "%tu 部屋"; -"directory_search_results_title" = "部屋一覧検索結果"; +"directory_cell_title" = "ルーム一覧を見る"; +"directory_cell_description" = "%tu ルーム"; +"directory_search_results_title" = "ルーム一覧検索結果"; "directory_search_results" = "%tu 件の検索結果 for %@"; "directory_search_results_more_than" = ">%tu 件の検索結果 for %@"; -"directory_searching_title" = "部屋一覧を検索中…"; +"directory_searching_title" = "ルーム一覧を検索中…"; "directory_search_fail" = "一覧を取得できませんでした"; // Contacts "contacts_address_book_section" = "端末の電話帳"; @@ -153,13 +152,13 @@ "room_participants_add_participant" = "参加者を追加"; "room_participants_one_participant" = "参加者 1名"; "room_participants_multi_participants" = "参加者 %d名"; -"room_participants_leave_prompt_title" = "部屋を退室"; -"room_participants_leave_prompt_msg" = "部屋を退室して本当によろしいですか?"; +"room_participants_leave_prompt_title" = "ルームを出る"; +"room_participants_leave_prompt_msg" = "ルームを退室して本当によろしいですか?"; "room_participants_remove_prompt_title" = "確認"; -"room_participants_remove_prompt_msg" = "本当に %@ さんを部屋から退去させますか?"; +"room_participants_remove_prompt_msg" = "本当に %@ さんをチャットから退去させますか?"; "room_participants_remove_third_party_invite_msg" = "サードパーティの招待を削除することは、APIが存在するまでサポートされていません"; "room_participants_invite_prompt_title" = "確認"; -"room_participants_invite_prompt_msg" = "本当に %@ さんを部屋へ招待しますか?"; +"room_participants_invite_prompt_msg" = "本当に %@ さんをチャットへ招待しますか?"; "room_participants_filter_room_members" = "参加者を検索"; "room_participants_invite_another_user" = "ユーザID, 表示名, 電子メールで検索と招待"; "room_participants_invite_malformed_id_title" = "招待エラー"; @@ -176,9 +175,9 @@ "room_participants_action_section_devices" = "セッション"; "room_participants_action_section_other" = "オプション"; "room_participants_action_invite" = "招待"; -"room_participants_action_leave" = "部屋を退室"; -"room_participants_action_remove" = "部屋から退室させる"; -"room_participants_action_ban" = "この部屋からブロックする"; +"room_participants_action_leave" = "このルームを出る"; +"room_participants_action_remove" = "このルームから退室させる"; +"room_participants_action_ban" = "このルームからブロックする"; "room_participants_action_unban" = "ブロック解除"; "room_participants_action_ignore" = "この参加者の発言を全て非表示にする"; "room_participants_action_unignore" = "この参加者の発言を全て表示する"; @@ -205,7 +204,7 @@ "room_ongoing_conference_call" = "会議通話実施中。 %@ または %@で参加してください。"; "room_ongoing_conference_call_with_close" = "会議通話実施中。%@または%@で参加してください。 %@。"; "room_ongoing_conference_call_close" = "閉じる"; -"room_conference_call_no_power" = "この部屋で会議通話を管理する権限が必要です"; +"room_conference_call_no_power" = "このルームで会議通話を管理する権限が必要です"; "room_prompt_resend" = "全て再送信"; "room_prompt_cancel" = "全て中止"; "room_resend_unsent_messages" = "未送信の文を再送信"; @@ -290,7 +289,6 @@ "settings_show_decrypted_content" = "復号化された文章を表示"; "settings_global_settings_info" = "あなたの %@ webクライアント上で、全体の通知設定が可能です"; "settings_pin_rooms_with_missed_notif" = "通知の届かなかった部屋をピン止めする"; -"settings_on_denied_notification" = "%@で通知されないように設定されています。あなたの端末設定で許可してください"; "settings_callkit_info" = "画面がロックされているときに着信がありました。Elementの着信はシステムの通話履歴で確認できます。 iCloudが有効になっている場合、この通話履歴はAppleと共有されます。"; "settings_ui_language" = "言語"; "settings_ui_theme" = "外観"; @@ -307,7 +305,7 @@ "settings_labs_matrix_apps" = "Matrixアプリ"; "settings_labs_create_conference_with_jitsi" = "jitsiの会議通話を作成する"; "settings_version" = "Version %@"; -"settings_olm_version" = "Olm Version %@"; +"settings_olm_version" = "Olm バージョン %@"; "settings_copyright" = "著作権"; "settings_term_conditions" = "利用規約"; "settings_privacy_policy" = "個人情報保護方針"; @@ -323,7 +321,7 @@ "settings_password_updated" = "あなたのパスワードは更新されました"; "settings_crypto_device_name" = "セッション名: "; "settings_crypto_device_id" = "\nセッションID: "; -"settings_crypto_device_key" = "\n端末鍵: "; +"settings_crypto_device_key" = "\nセッションキー:\n"; "settings_crypto_export" = "暗号鍵を外部へ保存"; "settings_crypto_blacklist_unverified_devices" = "検証されたセッションのみで暗号化"; // Room Details @@ -354,7 +352,7 @@ "room_details_addresses_section" = "住所表記"; "room_details_no_local_addresses" = "この部屋はサーバ内住所表記がありません"; "room_details_new_address" = "新しい住所表記を追加"; -"room_details_new_address_placeholder" = "新しい住所表記を追加 (例 #foo%@)"; +"room_details_new_address_placeholder" = "新しいアドレスを追加(例 #foo%@)"; "room_details_addresses_invalid_address_prompt_title" = "住所表記が正しくありません"; "room_details_addresses_invalid_address_prompt_msg" = "%@ は正しくない形式の住所表記です"; "room_details_addresses_disable_main_address_prompt_title" = "代表住所表記の警告"; @@ -473,14 +471,14 @@ "group_invite_section" = "招待"; "group_section" = "コミュニティ"; "room_message_reply_to_placeholder" = "返信を送る (暗号化されていない)…"; -"room_do_not_have_permission_to_post" = "この部屋に投稿する権限がありません"; +"room_do_not_have_permission_to_post" = "このルームに投稿する権限がありません"; "encrypted_room_message_reply_to_placeholder" = "暗号化された返信を送る…"; "room_message_reply_to_short_placeholder" = "返信を送る…"; "room_event_action_view_decrypted_source" = "復号化されたソースを見る"; "room_event_action_kick_prompt_reason" = "このユーザーを追放する理由"; "room_action_send_photo_or_video" = "写真か動画を送る"; "room_action_send_sticker" = "スタンプ送信"; -"room_replacement_information" = "この部屋は交換されており、もうアクティブではありません。"; +"room_replacement_information" = "このルームは交換されており、もうアクティブではありません。"; "room_replacement_link" = "会話はここで続けられます。"; "room_predecessor_information" = "この部屋は別の会話の続きです。"; "room_predecessor_link" = "より古いメッセージを見るにはここをタップしてください。"; @@ -534,7 +532,7 @@ "e2e_room_key_request_title" = "暗号化キー要求"; "e2e_room_key_request_message_new_device" = "暗号化キーを要求している新しい端末 '%@'を追加しました。"; "e2e_room_key_request_message" = "検証されていない端末 '%@'が暗号化キーを要求しています。"; -"e2e_room_key_request_start_verification" = "検証開始..."; +"e2e_room_key_request_start_verification" = "検証を始めます…"; "e2e_room_key_request_share_without_verifying" = "検証せずに共有"; "e2e_room_key_request_ignore_request" = "要求を無視"; // GDPR @@ -556,7 +554,7 @@ "rerequest_keys_alert_title" = "リクエスト送信"; "rerequest_keys_alert_message" = "この端末にキーを送信できるように、メッセージを復号化できる別の端末でElementを起動してください。"; "room_event_action_ban_prompt_reason" = "このユーザーをブロックする理由"; -"room_resource_limit_exceeded_message_contact_1" = " Please "; +"room_resource_limit_exceeded_message_contact_1" = " お願い "; "settings_ui_theme_black" = "Black"; "settings_flair" = "特色を表示する"; // String for App Store @@ -599,8 +597,8 @@ // Mark: - Room creation introduction cell "room_intro_cell_add_participants_action" = "参加者を追加"; -"room_participants_security_information_room_encrypted" = "この部屋で送受信されるメッセージはエンドツーエンド暗号化されます。\n\nメッセージは安全に保護されており、この部屋の参加者のみがメッセージの閲覧に必要な鍵を所持します。"; -"room_participants_security_information_room_not_encrypted" = "この部屋で送受信されるメッセージはエンドツーエンド暗号化されません。"; +"room_participants_security_information_room_encrypted" = "このルームで送受信されるメッセージはエンドツーエンド暗号化されます。\n\nメッセージは安全に保護されており、このルームの参加者のみがメッセージの閲覧に必要な鍵を所持します。"; +"room_participants_security_information_room_not_encrypted" = "このルームのメッセージはエンドツーエンド暗号化されていません。"; "room_intro_cell_information_dm_sentence1_part3" = ". "; "callbar_active_and_single_paused" = "ひとつのアクティブな通話 (%@) · ひとつの一時停止された通話"; @@ -729,7 +727,7 @@ "room_multiple_typing_notification" = "%@とその他のユーザーが入力中です"; "external_link_confirmation_message" = "リンク%@は別のサイトに移動します: %@\n\n本当に続けますか?"; "room_event_action_delete_confirmation_title" = "未送信メッセージを削除"; -"room_unsent_messages_cancel_message" = "この部屋にある未送信のメッセージをすべて削除してもよろしいですか?"; +"room_unsent_messages_cancel_message" = "このルームにある未送信のメッセージをすべて削除してもよろしいですか?"; "room_unsent_messages_cancel_title" = "未送信メッセージを削除"; "room_message_replying_to" = "%@に返信中"; "room_message_editing" = "編集中"; @@ -753,7 +751,7 @@ "room_participants_leave_prompt_msg_for_dm" = "退出してよろしいですか?"; "room_participants_leave_prompt_title_for_dm" = "退出する"; "contacts_address_book_no_identity_server" = "IDサーバーが設定されていません"; -"rooms_empty_view_information" = "ルームはプライベートでもパブリックでも、あらゆるグループチャットに最適です。+をタップすると既にある部屋を探したり、新しい部屋を作ることができます。"; +"rooms_empty_view_information" = "ルームはプライベートでもパブリックでも、あらゆるグループチャットに最適です。+をタップすると、既にあるルームを見つけたり、新しいルームを作ることができます。"; "rooms_empty_view_title" = "ルーム"; "people_empty_view_information" = "誰とでも安全にチャットできます。+をタップすると会話相手を追加できます。"; "people_empty_view_title" = "人々"; diff --git a/Riot/Assets/kab.lproj/Vector.strings b/Riot/Assets/kab.lproj/Vector.strings index 812f4def9..58166fb8f 100644 --- a/Riot/Assets/kab.lproj/Vector.strings +++ b/Riot/Assets/kab.lproj/Vector.strings @@ -383,7 +383,6 @@ "room_recents_no_conversation" = "Ulac tixxamin"; "room_recents_conversations_section" = "TIXXAMIN"; "room_recents_people_section" = "IMDANEN"; -"room_recents_directory_section_network" = "Aẓeṭṭa"; "room_creation_make_private" = "Err-it d uslig"; "room_creation_privacy" = "Tabaḍnit"; "room_creation_appearance_name" = "Isem"; @@ -1306,7 +1305,6 @@ "security_settings_blacklist_unverified_devices" = "Ɣur-k·m ad tazneḍ akk iznan ɣer tɣimiyin ur nettwattkal ara"; "security_settings_crypto_sessions_description_2" = "MA yella ur tessineḍ ara anekcum, senfel awal-ik·imuffir, rnu wennez aḥraz aɣellsan."; "settings_send_crash_report" = "Azen tura tura isefka yerrẓen & useqdec"; -"settings_on_denied_notification" = "Ttwagin yilɣa i %@, ttxil-k·m sireg-iten deg yiɣewaren n yibenk-inek·inem"; "settings_global_settings_info" = "Iɣewwaren n yilɣa imatuyen llan ɣef umsaɣ-inek·inem web %@"; "room_participants_start_new_chat_error_using_user_email_without_identity_server" = "Ulac aqeddac n timagit i yettusbadun, ihi ur tezmireḍ ara ad tebduḍ adiwenni akked unermis isseqdacen imayl."; "contacts_address_book_permission_required" = "Ttusrant tsirag i unekcum ɣer yinermisen idiganen"; diff --git a/Riot/Assets/nb-NO.lproj/Localizable.strings b/Riot/Assets/nb-NO.lproj/Localizable.strings index 26102b751..5f6a77573 100644 --- a/Riot/Assets/nb-NO.lproj/Localizable.strings +++ b/Riot/Assets/nb-NO.lproj/Localizable.strings @@ -111,3 +111,9 @@ "MSG_FROM_USER" = "%@ sendte en melding"; /* Message title for a specific person in a named room */ "MSG_FROM_USER_IN_ROOM_TITLE" = "%@ i %@"; + +/* Group call from user, CallKit caller name */ +"GROUP_CALL_FROM_USER" = "%@ (Gruppeanrop)"; + +/* A user added a Jitsi call to a room */ +"GROUP_CALL_STARTED" = "Gruppeanrop startet"; diff --git a/Riot/Assets/nb-NO.lproj/Vector.strings b/Riot/Assets/nb-NO.lproj/Vector.strings index c47bf5f51..418c89a9c 100644 --- a/Riot/Assets/nb-NO.lproj/Vector.strings +++ b/Riot/Assets/nb-NO.lproj/Vector.strings @@ -64,7 +64,6 @@ "room_creation_appearance" = "Utseende"; "room_creation_appearance_name" = "Navn"; "room_creation_privacy" = "Personvern"; -"room_recents_directory_section_network" = "Nettverk"; "room_recents_create_empty_room" = "Opprett et rom"; "room_recents_join_room" = "Bli med i rommet"; "room_recents_join_room_title" = "Bli med i et rom"; @@ -479,7 +478,7 @@ "settings_discovery_error_message" = "Det oppstod en feil. Vennligst prøv igjen."; "security_settings_crypto_sessions" = "MINE ØKTER"; "security_settings_secure_backup_setup" = "Sett opp"; -"security_settings_secure_backup_delete" = "Slett"; +"security_settings_secure_backup_delete" = "Slett sikkerhetskopi"; "security_settings_crosssigning_complete_security" = "Komplett sikkerhet"; "security_settings_cryptography" = "KRYPTOGRAFI"; "security_settings_complete_security_alert_title" = "Komplett sikkerhet"; @@ -513,7 +512,7 @@ "key_backup_setup_passphrase_confirm_passphrase_placeholder" = "Bekreft passordfrasen"; "key_backup_setup_passphrase_confirm_passphrase_invalid" = "Passordfrasen samsvarer ikke"; "key_backup_setup_passphrase_set_passphrase_action" = "Velg passordfrase"; -"key_backup_setup_success_from_recovery_key_recovery_key_title" = "Gjenopprettingsnøkkel"; +"key_backup_setup_success_from_recovery_key_recovery_key_title" = "Sikkerhetsnøkkel"; "key_backup_setup_success_from_recovery_key_make_copy_action" = "Lag en kpi"; "key_backup_setup_success_from_recovery_key_made_copy_action" = "Jeg har laget en kopi"; "key_backup_recover_from_recovery_key_recovery_key_placeholder" = "Skriv inn gjenopprettingsnøkkelen"; @@ -834,7 +833,7 @@ "settings_key_backup_info_not_valid" = "Denne økten sikkerhetskopierer ikke dine nøkler, men du har en eksisterende sikkerhetskopi du kan gjenopprette fra og legge til, for å gå videre."; "settings_key_backup_info_valid" = "Denne økten sikkerhetskopierer dine nøkler."; "settings_key_backup_info_version" = "Sikkerhetskopi av nøkler versjon : %@"; -"settings_key_backup_info_signout_warning" = "Før du logger ut, koble denne sesjonen til sikkerhetskopi av nøkler for å unngå tap av nøkler som kanskje bare er lagret på denne enheten."; +"settings_key_backup_info_signout_warning" = "Sikkerhetskopier nøklene dine før du logger av for å unngå å miste dem."; "settings_key_backup_info_none" = "Nøklene dine for denne sesjonen blir ikke sikkerhetskopiert."; "settings_third_party_notices" = "Tredjepartsmerknader"; "settings_labs_e2e_encryption_prompt_message" = "Vennligst logg inn igjen for å ferdigstille oppsett av kryptering."; @@ -858,13 +857,12 @@ "room_details_photo_for_dm" = "Bilde"; "room_details_photo" = "Rombilde"; "settings_flair" = "Vis kobling hvor tillatt"; -"settings_on_denied_notification" = "Varsler er ikke tillat for %@, vennligst tillat dem i enhetens innstillinger"; "settings_pin_rooms_with_missed_notif" = "Fest rom med tapte varsler"; "room_info_list_several_members" = "%@ medlemmer"; "pin_protection_not_allowed_pin" = "Av sikkerhetsårsaker er denne PIN-koden ikke tilgjengelig. Prøv en annen PIN-kode"; "secrets_setup_recovery_passphrase_information" = "Skriv inn en sikkerhetsfrase bare du kjenner, brukes til å sikre hemmeligheter på serveren."; "secrets_recovery_with_passphrase_lost_passphrase_action_part2" = "bruke gjenopprettingsnøkkelen din"; -"key_backup_recover_from_passphrase_lost_passphrase_action_part2" = "bruke gjenopprettingsnøkkelen"; +"key_backup_recover_from_passphrase_lost_passphrase_action_part2" = "bruk sikkerhetsnøkkelen"; "room_details_access_section_for_dm" = "Hvem har tilgang til dette?"; "identity_server_settings_place_holder" = "Legg inn en identitetsserver"; "identity_server_settings_description" = "Du bruker for øyeblikket %@ for å finne og bli funnet av dine eksisterende kontakter."; @@ -920,13 +918,13 @@ "security_settings_crosssigning" = "KRYSS-SIGNERING"; "security_settings_backup" = "SIKKERHETSKOPI-MELDINGER"; "security_settings_secure_backup_synchronise" = "Synkroniser"; -"security_settings_secure_backup_description" = "Sikre deg mot å miste tilgang til krypterte meldinger og data ved å lagre sikkerhetskopi av krypteringsnøkler på din server."; +"security_settings_secure_backup_description" = "Sikkerhetskopier krypteringsnøklene med kontodataene dine hvis du mister tilgangen til øktene dine. Nøklene dine blir sikret med en unik sikkerhetsnøkkel."; "security_settings_secure_backup" = "SIKKERHETSKOPI"; "security_settings_crosssigning_info_trusted" = "Kryss-signering er aktivert. Du kan stole på andre brukere og dine andre økter basert på kryss-signering, men du kan ikke kryss-signere fra denne økten fordi den ikke har private nøkler for kryss-signering. Fullfør sikkerheten for denne økten."; "security_settings_export_keys_manually" = "Eksporter nøkler manuelt"; -"security_settings_crosssigning_reset" = "Tilbakestill kryss-signering"; -"security_settings_crosssigning_bootstrap" = "Bootstrap kryss-signering"; -"security_settings_crosssigning_info_ok" = "Kryss-signering er aktivert."; +"security_settings_crosssigning_reset" = "Nullstill"; +"security_settings_crosssigning_bootstrap" = "Sett opp"; +"security_settings_crosssigning_info_ok" = "Kryss-signering er klar til bruk."; "security_settings_blacklist_unverified_devices" = "Aldri send meldinger til ikke-klarerte økter"; // AuthenticatedSessionViewControllerFactory @@ -1052,7 +1050,7 @@ "bug_report_prompt" = "Applikasjonen krasjet sist gang. Vil du sende inn en krasj-rapport?"; "public_room_section_title" = "Offentlige rom (på %@):"; "call_no_stun_server_error_message_2" = "Alternativt kan du prøve å bruke den offentlige serveren ved %@, men denne vil være mindre pålitelig, og vil dele din IP-adresse med serveren. Du kan også administrere dette i innstillinger"; -"e2e_key_backup_wrong_version" = "Det har blitt oppdaget en ny sikkerhetskopi av meldingsnøkler .\n\nHvis dette ikke ble intitiert av deg bør du endre passordfrase i innstillinger."; +"e2e_key_backup_wrong_version" = "Det har blitt oppdaget en ny sikkerhetskopi av meldingsnøkler .\n\nHvis dette ikke var deg, angir du en ny sikkerhetsfrase i Innstillinger."; // Key backup wrong version "e2e_key_backup_wrong_version_title" = "Ny sikkerhetskopi av nøkler"; @@ -1136,7 +1134,7 @@ "secure_key_backup_setup_intro_title" = "Sikkert lagringsområde"; "secure_key_backup_setup_intro_use_security_key_info" = "Generer en sikkerhetsnøkkel og lagre den på et trygt sted som i en passordadministrator eller en safe."; "secure_key_backup_setup_intro_use_security_key_title" = "Bruk en sikkerhetsnøkkel"; -"secure_key_backup_setup_intro_use_security_passphrase_title" = "Bruk en passordfrase"; +"secure_key_backup_setup_intro_use_security_passphrase_title" = "Bruk en sikkerhetsfrase"; "key_backup_setup_intro_manual_export_action" = "Eksporter nøkler manuelt"; "key_backup_setup_intro_setup_connect_action_with_existing_backup" = "Koble denne enheten til sikkerhetskopi av meldingsnøkler"; "key_backup_setup_skip_alert_message" = "Du kan miste dine krypterte meldinger dersom du logger ut eller mister enheten."; @@ -1154,12 +1152,12 @@ "secure_key_backup_setup_existing_backup_error_info" = "Lås den opp for å gjenbruke den på sikkert lagringsområde, eller slett den for å opprette en ny sikkerhetskopi av meldinger på sikkert lagringsområde."; "secure_key_backup_setup_existing_backup_error_title" = "Det finnes allerede en sikkerhetskopi for meldinger"; "secure_key_backup_setup_intro_use_security_passphrase_info" = "Skriv inn en hemmelig frase bare du vet, og generer en nøkkel for sikkerhetskopiering."; -"key_backup_setup_passphrase_setup_recovery_key_info" = "Eller sikre sikkerhetskopien din med en gjenopprettingsnøkkel, og lagre den på et trygt sted."; -"key_backup_setup_passphrase_info" = "Vi lagrer en kryptert kopi av nøklene dine på serveren vår. Beskytt sikkerhetskopien din med en passordfrase for å holde den sikker.\n\nFor maksimal sikkerhet bør dette være forskjellig fra kontopassordet ditt."; +"key_backup_setup_passphrase_setup_recovery_key_info" = "Eller, sikre sikkerhetskopien med en sikkerhetsnøkkel, og lagre den et trygt sted."; +"key_backup_setup_passphrase_info" = "Vi lagrer en kryptert kopi av nøklene dine på serveren vår. Beskytt sikkerhetskopien med en setning for å holde den sikker.\n\nFor maksimal sikkerhet bør dette være forskjellig fra kontopassordet ditt."; // Passphrase -"key_backup_setup_passphrase_title" = "Gjør sikkerhetskopien din sikker med en passordfrase"; +"key_backup_setup_passphrase_title" = "Sikre sikkerhetskopien din med en sikkerhetsfrase"; "key_backup_setup_intro_info" = "Meldinger i krypterte rom er sikret med ende-til-ende-kryptering. Bare du og mottakeren(e) har nøklene til å lese disse meldingene.\n\nLagre nøklene dine på et trygt sted for å unngå å miste dem."; // MARK: Key backup recover @@ -1168,29 +1166,29 @@ // Success from recovery key "key_backup_setup_success_from_recovery_key_info" = "Nøklene dine blir sikkerhetskopiert.\n\nKopier denne gjenopprettingsnøkkelen og lagre den på et trygt sted."; -"key_backup_setup_success_from_passphrase_save_recovery_key_action" = "Lagre gjenopprettingsnøkkel"; +"key_backup_setup_success_from_passphrase_save_recovery_key_action" = "Lagre sikkerhetsnøkkel"; // Success from passphrase "key_backup_setup_success_from_passphrase_info" = "Nøklene dine blir sikkerhetskopiert.\n\nGjenopprettingsnøkkelen din er et sikkerhetsnett - du kan bruke den til å gjenopprette tilgangen til de krypterte meldingene dine hvis du glemmer passordfrasen.\n\nLagre gjenopprettingsnøkkelen din på en trygg måte, f.eks. ved hjelp av en passordadministrator (eller i en safe)."; -"key_backup_setup_passphrase_setup_recovery_key_action" = "(Avansert) Sett opp med gjenopprettingsnøkkel"; -"key_backup_recover_invalid_passphrase" = "Sikkerhetskopi kunne ikke dekrypteres med denne passordfrasen: Vennligst sjekk at du har angitt riktig passordfrase."; -"key_backup_recover_invalid_passphrase_title" = "Feil gjenopprettingsfrase"; -"key_backup_recover_invalid_recovery_key" = "Sikkerhetskopi kunne ikke dekrypteres med denne nøkkelen: bekreft at du skrev inn riktig gjenopprettingsnøkkel."; +"key_backup_setup_passphrase_setup_recovery_key_action" = "(Avansert) Sett opp med sikkerhetsnøkkel"; +"key_backup_recover_invalid_passphrase" = "Sikkerhetskopiering kunne ikke dekrypteres med denne setningen: bekreft at du har skrevet riktig sikkerhetsfrase."; +"key_backup_recover_invalid_passphrase_title" = "Feil sikkerhetsfrase"; +"key_backup_recover_invalid_recovery_key" = "Sikkerhetskopiering kunne ikke dekrypteres med denne nøkkelen: bekreft at du har angitt riktig sikkerhetsnøkkel."; "key_backup_recover_invalid_recovery_key_title" = "Feil i gjenopprettingsnøkkel"; // Recover from passphrase -"key_backup_recover_from_passphrase_info" = "Bruk gjenopprettingspassordet for å låse opp historikken for dine sikrede meldinger"; +"key_backup_recover_from_passphrase_info" = "Bruk sikkerhetsfrasen for å låse opp den sikre meldingsloggen"; // Recover from private key "key_backup_recover_from_private_key_info" = "Gjenoppretter sikkerhetskopi …"; -"key_backup_recover_from_passphrase_lost_passphrase_action_part1" = "Kjenner du ikke gjenopprettingspassordet ditt? Du kan "; +"key_backup_recover_from_passphrase_lost_passphrase_action_part1" = "Kjenner du ikke sikkerhetsfrasen din? Du kan "; "key_backup_recover_from_passphrase_recover_action" = "Lås opp historikk"; "key_backup_recover_from_passphrase_passphrase_placeholder" = "Skriv inn passordfrase"; // Recover from recovery key -"key_backup_recover_from_recovery_key_info" = "Bruk gjenopprettingsnøkkel for å låse opp historikken for sikrede meldinger"; +"key_backup_recover_from_recovery_key_info" = "Bruk sikkerhetsnøkkelen til å låse opp den sikre meldingsloggen"; "key_backup_recover_from_passphrase_lost_passphrase_action_part3" = "."; "sign_out_non_existing_key_backup_alert_setup_secure_backup_action" = "Begynn å bruke Sikkert lagringsområde"; "sign_out_non_existing_key_backup_alert_title" = "Du mister tilgangen til de krypterte meldingene dine hvis du logger ut nå"; @@ -1418,3 +1416,50 @@ "room_intro_cell_information_multiple_dm_sentence2" = "Bare dere er i denne samtalen, med mindre noen av dere inviterer andre til å bli med."; "room_intro_cell_information_dm_sentence2" = "Bare dere to er i denne samtalen, ingen andre kan bli med."; "room_intro_cell_information_dm_sentence1_part3" = ". "; +"side_menu_app_version" = "Versjon %@"; +"side_menu_action_feedback" = "Tilbakemelding"; +"side_menu_action_help" = "Hjelp"; +"side_menu_action_settings" = "Innstillinger"; +"side_menu_action_invite_friends" = "Inviter venner"; + +// Mark: - Side menu + +"side_menu_reveal_action_accessibility_label" = "Venstre panel"; +"user_avatar_view_accessibility_hint" = "Endre bruker avatar"; + +// Mark: - User avatar view + +"user_avatar_view_accessibility_label" = "avatar"; +"space_beta_announce_information" = "Plasser er en ny måte å gruppere rom og mennesker på. De er ikke på iOS ennå, men du kan bruke dem nå på nettet og på skrivebordet."; +"space_beta_announce_subtitle" = "Den nye versjonen av lokalsamfunn"; +"space_beta_announce_title" = "Plasser kommer snart"; +"space_beta_announce_badge" = "BETA"; +"space_feature_unavailable_information" = "Plasser er en ny måte å gruppere rom og mennesker på.\n\nDe kommer snart. For nå, hvis du blir med på en annen plattform, vil du kunne få tilgang til alle rom du blir med her."; +"space_feature_unavailable_subtitle" = "Plasser er ikke på iOS ennå, men du kan bruke dem nå på nettet og på skrivebordet"; + +// Mark: - Spaces + +"space_feature_unavailable_title" = "Plasser er ikke her ennå"; +"secrets_recovery_with_key_information_unlock_secure_backup_with_key" = "Skriv inn sikkerhetsnøkkelen din for å fortsette."; +"secrets_recovery_with_key_information_unlock_secure_backup_with_phrase" = "Skriv inn sikkerhetsfrasen for å fortsette."; + +// Success from secure backup +"key_backup_setup_success_from_secure_backup_info" = "Nøklene dine blir sikkerhetskopiert."; +"event_formatter_group_call_incoming" = "%@ i %@"; +"event_formatter_group_call_leave" = "Forlat"; +"event_formatter_group_call_join" = "Bli med"; +"event_formatter_group_call" = "Gruppeanrop"; +"event_formatter_call_end_call" = "Avslutt samtale"; +"event_formatter_call_retry" = "Prøv på nytt"; +"event_formatter_call_answer" = "Svar"; +"security_settings_secure_backup_restore" = "Gjenopprett fra sikkerhetskopi"; +"security_settings_secure_backup_reset" = "Nullstill"; +"security_settings_secure_backup_info_valid" = "Denne økten tar sikkerhetskopi av nøklene dine."; +"security_settings_secure_backup_info_checking" = "Sjekker…"; +"settings_ui_theme_picker_message_match_system_theme" = "\"Auto\" samsvarer med enhetens systemtema"; +"settings_ui_theme_picker_message_invert_colours" = "\"Auto\" bruker enhetens \"Inverter farger\" innstillinger"; + +// Chat +"room_slide_to_end_group_call" = "Skyv for å avslutte samtalen for alle"; +"room_recents_unknown_room_error_message" = "Finner ikke dette rommet. Forsikre deg om at den eksisterer"; +"room_creation_dm_error" = "Vi kunne ikke opprette DM. Kontroller brukerne du vil invitere, og prøv på nytt."; diff --git a/Riot/Assets/nl.lproj/Vector.strings b/Riot/Assets/nl.lproj/Vector.strings index 4b144ba88..a7ba063a6 100644 --- a/Riot/Assets/nl.lproj/Vector.strings +++ b/Riot/Assets/nl.lproj/Vector.strings @@ -112,7 +112,6 @@ "room_creation_invite_another_user" = "Zoeken/uitnodigen met gebruikers-ID, naam of e-mailadres"; // Room recents "room_recents_directory_section" = "GESPREKSCATALOGUS"; -"room_recents_directory_section_network" = "Netwerk"; "room_recents_favourites_section" = "FAVORIETEN"; "room_recents_people_section" = "PERSONEN"; "room_recents_conversations_section" = "GESPREKKEN"; @@ -290,7 +289,6 @@ "settings_global_settings_info" = "Globale meldingsinstellingen zijn beschikbaar op uw %@-webcliënt"; "settings_pin_rooms_with_missed_notif" = "Gesprekken met gemiste meldingen vastprikken"; "settings_pin_rooms_with_unread" = "Gesprekken met ongelezen berichten vastprikken"; -"settings_on_denied_notification" = "Meldingen worden geweigerd voor %@, sta ze toe in uw apparaatinstellingen"; //"settings_enable_all_notif" = "Alle notificaties aanzetten"; //"settings_messages_my_display_name" = "Bericht dat mijn naam bevat"; //"settings_messages_my_user_name" = "Bericht dat mijn gebruikersnaam bevat"; @@ -611,7 +609,7 @@ "room_does_not_exist" = "%@ bestaat niet"; // Key backup wrong version "e2e_key_backup_wrong_version_title" = "Nieuwe sleutelback-up"; -"e2e_key_backup_wrong_version" = "Er is een nieuwe sleutelback-up voor versleutelde berichten gedetecteerd.\n\nIndien deze niet van u komt, stel dan een nieuw wachtwoord in in de instellingen."; +"e2e_key_backup_wrong_version" = "Er is een nieuwe sleutelback-up voor versleutelde berichten gedetecteerd.\n\nIndien deze niet van u komt, stel dan een nieuw veiligheidswachtwoord in in de instellingen."; "e2e_key_backup_wrong_version_button_settings" = "Instellingen"; "e2e_key_backup_wrong_version_button_wasme" = "Ik was het"; "key_backup_setup_title" = "Sleutelback-up"; @@ -623,10 +621,10 @@ "key_backup_setup_intro_setup_action_without_existing_backup" = "Begin sleutelback-up te gebruiken"; "key_backup_setup_intro_manual_export_info" = "(Geavanceerd)"; "key_backup_setup_intro_manual_export_action" = "Sleutels handmatig exporteren"; -"key_backup_setup_passphrase_title" = "Beveilig uw back-up met een wachtwoord"; -"key_backup_setup_passphrase_info" = "We bewaren een versleutelde kopie van uw sleutels op onze server. Bescherm uw back-up met een wachtwoord om deze veilig te houden.\n\nVoor maximale beveiliging zou dit moeten verschillen van uw accountwachtwoord."; +"key_backup_setup_passphrase_title" = "Beveilig uw back-up met een veiligheidswachtwoord"; +"key_backup_setup_passphrase_info" = "We bewaren een versleutelde kopie van uw sleutels op onze server. Bescherm uw back-up met een veiligheidswachtwoord om deze veilig te houden.\n\nVoor maximale beveiliging zou dit moeten verschillen van uw accountwachtwoord."; "key_backup_setup_passphrase_passphrase_title" = "Invoeren"; -"key_backup_setup_passphrase_passphrase_placeholder" = "Voer wachtwoord in"; +"key_backup_setup_passphrase_passphrase_placeholder" = "Wachtwoord invoeren"; "key_backup_setup_passphrase_passphrase_valid" = "Top!"; "key_backup_setup_passphrase_passphrase_invalid" = "Probeer nog een woord toe te voegen"; "key_backup_setup_passphrase_confirm_passphrase_title" = "Bevestigen"; @@ -634,35 +632,35 @@ "key_backup_setup_passphrase_confirm_passphrase_valid" = "Top!"; "key_backup_setup_passphrase_confirm_passphrase_invalid" = "Wachtwoorden komen niet overeen"; "key_backup_setup_passphrase_set_passphrase_action" = "Wachtwoord instellen"; -"key_backup_setup_passphrase_setup_recovery_key_info" = "Of beveilig uw back-up met een herstelsleutel, en bewaar deze op een veilige plaats."; -"key_backup_setup_passphrase_setup_recovery_key_action" = "(Geavanceerd) Instellen met herstelsleutel"; +"key_backup_setup_passphrase_setup_recovery_key_info" = "Of beveilig uw back-up met een veiligheidssleutel en bewaar deze op een veilige plaats."; +"key_backup_setup_passphrase_setup_recovery_key_action" = "(Geavanceerd) Instellen met veiligheidssleutel"; "key_backup_setup_success_title" = "Klaar!"; // Success from passphrase -"key_backup_setup_success_from_passphrase_info" = "Er wordt een back-up van uw sleutels gemaakt.\n\nUw herstelsleutel is een veiligheidsnet - u kunt deze gebruiken om de toegang tot uw versleutelde berichten te herstellen als u uw wachtwoord zou vergeten.\n\nBewaar uw herstelsleutel op een heel veilig plaats, zoals een wachtwoordbeheerder (of een kluis)."; -"key_backup_setup_success_from_passphrase_save_recovery_key_action" = "Herstelsleutel opslaan"; +"key_backup_setup_success_from_passphrase_info" = "Er wordt een back-up van uw sleutels gemaakt.\n\nUw veiligheidssleutel is een veiligheidsnet - u kunt deze gebruiken om de toegang tot uw versleutelde berichten te herstellen als u uw wachtwoord zou vergeten.\n\nBewaar uw veiligheidssleutel op een heel veilige plaats, zoals een wachtwoordbeheerder (of een kluis)."; +"key_backup_setup_success_from_passphrase_save_recovery_key_action" = "Veiligheidssleutel opslaan"; "key_backup_setup_success_from_passphrase_done_action" = "Klaar"; // Success from recovery key -"key_backup_setup_success_from_recovery_key_info" = "Er wordt een back-up van uw sleutels gemaakt.\n\nMaak een kopie van deze herstelsleutel en bewaar deze op een veilige plaats."; -"key_backup_setup_success_from_recovery_key_recovery_key_title" = "Herstelsleutel"; +"key_backup_setup_success_from_recovery_key_info" = "Er wordt een back-up van uw sleutels gemaakt.\n\nMaak een kopie van deze veiligheidssleutel en bewaar deze op een veilige plaats."; +"key_backup_setup_success_from_recovery_key_recovery_key_title" = "Veiligheidssleutel"; "key_backup_setup_success_from_recovery_key_make_copy_action" = "Maak een kopie"; "key_backup_setup_success_from_recovery_key_made_copy_action" = "Ik heb een kopie gemaakt"; "key_backup_recover_title" = "Versleutelde berichten"; -"key_backup_recover_invalid_passphrase_title" = "Onjuist herstelwachtwoord"; -"key_backup_recover_invalid_passphrase" = "De back-up kon niet ontsleuteld worden met dit wachtwoord: controleer of u het herstelwachtwoord juist hebt ingevoerd."; -"key_backup_recover_invalid_recovery_key_title" = "Herstelsleutel komt niet overeen"; -"key_backup_recover_invalid_recovery_key" = "De back-up kon niet ontsleuteld worden met deze sleutel: controleer of u de juiste herstelsleutel hebt ingevoerd."; -"key_backup_recover_from_passphrase_info" = "Gebruik uw herstelwachtwoord om uw versleutelde berichtgeschiedenis te ontgrendelen"; +"key_backup_recover_invalid_passphrase_title" = "Onjuist veiligheidswachtwoord"; +"key_backup_recover_invalid_passphrase" = "De back-up kon niet ontsleuteld worden met dit wachtwoord: controleer of u het veiligheidswachtwoord juist hebt ingevoerd."; +"key_backup_recover_invalid_recovery_key_title" = "Veiligheidssleutel komt niet overeen"; +"key_backup_recover_invalid_recovery_key" = "De back-up kon niet ontsleuteld worden met deze sleutel: controleer of u de juiste veiligheidssleutel hebt ingevoerd."; +"key_backup_recover_from_passphrase_info" = "Gebruik uw veiligheidswachtwoord om uw versleutelde berichtengeschiedenis te ontgrendelen"; "key_backup_recover_from_passphrase_passphrase_title" = "Invoeren"; -"key_backup_recover_from_passphrase_passphrase_placeholder" = "Voer wachtwoord in"; +"key_backup_recover_from_passphrase_passphrase_placeholder" = "Wachtwoord invoeren"; "key_backup_recover_from_passphrase_recover_action" = "Geschiedenis ontgrendelen"; -"key_backup_recover_from_passphrase_lost_passphrase_action_part1" = "Herstelwachtwoord vergeten? Dan kunt u "; -"key_backup_recover_from_passphrase_lost_passphrase_action_part2" = "uw herstelsleutel gebruiken"; +"key_backup_recover_from_passphrase_lost_passphrase_action_part1" = "Veiligheidswachtwoord vergeten? Dan kunt u "; +"key_backup_recover_from_passphrase_lost_passphrase_action_part2" = "uw veiligheidssleutel gebruiken"; "key_backup_recover_from_passphrase_lost_passphrase_action_part3" = "."; -"key_backup_recover_from_recovery_key_info" = "Gebruik uw herstelsleutel om uw versleutelde berichtgeschiedenis te ontgrendelen"; +"key_backup_recover_from_recovery_key_info" = "Gebruik uw veiligheidssleutel om uw versleutelde berichtengeschiedenis te ontgrendelen"; "key_backup_recover_from_recovery_key_recovery_key_title" = "Invoeren"; -"key_backup_recover_from_recovery_key_recovery_key_placeholder" = "Voer herstelsleutel in"; +"key_backup_recover_from_recovery_key_recovery_key_placeholder" = "Veiligheidssleutel invoeren"; "key_backup_recover_from_recovery_key_recover_action" = "Geschiedenis ontgrendelen"; -"key_backup_recover_from_recovery_key_lost_recovery_key_action" = "Herstelsleutel verloren? U kunt er een nieuwe aanmaken in de instellingen."; +"key_backup_recover_from_recovery_key_lost_recovery_key_action" = "Veiligheidssleutel verloren? U kunt er een nieuwe aanmaken in de instellingen."; "key_backup_recover_success_info" = "Back-up hersteld!"; "key_backup_recover_done_action" = "Klaar"; "key_backup_setup_banner_title" = "Verlies nooit uw versleutelde berichten"; @@ -913,7 +911,7 @@ // MARK: - Favourites "favourites_empty_view_title" = "Favoriete gesprekken en personen"; -"home_empty_view_information" = "De alles-in-één veilige chat app voor teams, vrienden en organisaties. Klik op de onderstaande + knop om gesprekken te starten met personen en groepen."; +"home_empty_view_information" = "De alles-in-één veilige chat-app voor teams, vrienden en organisaties. Druk op de + knop hieronder om personen en gesprekken toe te voegen."; // MARK: - Home @@ -1032,32 +1030,32 @@ "secrets_setup_recovery_key_done_action" = "Klaar"; "secrets_setup_recovery_key_export_action" = "Opslaan"; "secrets_setup_recovery_key_loading" = "Laden…"; -"secrets_setup_recovery_key_information" = "Bewaar uw Herstelsleutel op een veilige plek. Het kan gebruikt worden voor het ontgrendelen van uw versleutelde berichten en data."; -"secrets_recovery_with_key_invalid_recovery_key_message" = "Verifieer dat u de juiste herstelsleutel heeft ingevoerd."; +"secrets_setup_recovery_key_information" = "Bewaar uw veiligheidssleutel op een veilige plek. Deze kan gebruikt worden om uw versleutelde berichten en data te ontsleutelen."; +"secrets_recovery_with_key_invalid_recovery_key_message" = "Verifieer dat u de juiste veiligheidssleutel heeft ingevoerd."; "secrets_recovery_with_key_invalid_recovery_key_title" = "Geen toegang tot geheime opslag"; "secrets_recovery_with_key_recover_action" = "Gebruik sleutel"; -"secrets_recovery_with_key_recovery_key_placeholder" = "Voer de herstelsleutel in"; +"secrets_recovery_with_key_recovery_key_placeholder" = "Veiligheidssleutel invoeren"; "secrets_recovery_with_key_recovery_key_title" = "Invoeren"; -"secrets_recovery_with_key_information_verify_device" = "Gebruik uw herstelsleutel om dit apparaat te verifiëren."; -"secrets_recovery_with_key_information_default" = "Ontvang toegang tot uw versleutelde berichtengeschiedenis en uw kruislings ondertekenen ID voor het verifiëren van andere sessie door het invoeren van uw Herstelsleutel."; +"secrets_recovery_with_key_information_verify_device" = "Gebruik uw veiligheidssleutel om dit apparaat te verifiëren."; +"secrets_recovery_with_key_information_default" = "Ontvang toegang tot uw versleutelde berichtengeschiedenis en kruislings ondertekenen voor het verifiëren van andere sessie door het invoeren van uw veiligheidssleutel."; // Recover with key -"secrets_recovery_with_key_title" = "Herstelsleutel"; -"secrets_recovery_with_passphrase_invalid_passphrase_message" = "Verifieer dat u het juiste Herstelwachtwoord heeft ingevoerd."; +"secrets_recovery_with_key_title" = "Veiligheidssleutel"; +"secrets_recovery_with_passphrase_invalid_passphrase_message" = "Verifieer dat u het juiste veiligheidswachtwoord heeft ingevoerd."; "secrets_recovery_with_passphrase_invalid_passphrase_title" = "Geen toegang tot geheime opslag"; "secrets_recovery_with_passphrase_lost_passphrase_action_part3" = "."; -"secrets_recovery_with_passphrase_lost_passphrase_action_part2" = "uw Herstelsleutel gebruiken"; -"secrets_recovery_with_passphrase_lost_passphrase_action_part1" = "Herstelwachtwoord vergeten? Dan kunt u "; -"secrets_recovery_with_passphrase_recover_action" = "Gebruik Wachtwoord"; -"secrets_recovery_with_passphrase_passphrase_placeholder" = "Voer uw Herstelwachtwoord in"; +"secrets_recovery_with_passphrase_lost_passphrase_action_part2" = "uw veiligheidssleutel gebruiken"; +"secrets_recovery_with_passphrase_lost_passphrase_action_part1" = "Veiligheidswachtwoord vergeten? Dan kunt u "; +"secrets_recovery_with_passphrase_recover_action" = "Gebruik wachtwoord"; +"secrets_recovery_with_passphrase_passphrase_placeholder" = "Voer uw veiligheidswachtwoord in"; "secrets_recovery_with_passphrase_passphrase_title" = "Invoeren"; -"secrets_recovery_with_passphrase_information_verify_device" = "Gebruik uw Herstelwachtwoord om dit apparaat te verifiëren."; -"secrets_recovery_with_passphrase_information_default" = "Ontvang toegang tot uw versleutelde berichtengeschiedenis en uw kruislings ondertekenen ID voor het verifiëren van andere sessies door het invoeren van uw Herstelwachtwoord."; +"secrets_recovery_with_passphrase_information_verify_device" = "Gebruik uw veiligheidswachtwoord om dit apparaat te verifiëren."; +"secrets_recovery_with_passphrase_information_default" = "Ontvang toegang tot uw versleutelde berichtengeschiedenis en kruislings ondertekenen voor het verifiëren van andere sessies door het invoeren van uw veiligheidswachtwoord."; // Recover with passphrase -"secrets_recovery_with_passphrase_title" = "Herstelwachtwoord"; +"secrets_recovery_with_passphrase_title" = "Veiligheidswachtwoord"; "secrets_recovery_reset_action_part_2" = "Alles opnieuw instellen"; // MARK: - Secrets recovery @@ -1065,9 +1063,9 @@ "secrets_recovery_reset_action_part_1" = "Alle herstelopties vergeten of verloren? "; "user_verification_session_details_verify_action_other_user" = "Handmatig verifiëren"; "user_verification_session_details_verify_action_current_user_manually" = "Handmatig middels een tekst"; -"user_verification_session_details_verify_action_current_user" = "Interactief Verifiëren"; +"user_verification_session_details_verify_action_current_user" = "Interactief verifiëren"; "user_verification_session_details_additional_information_untrusted_current_user" = "Als u zich niet heeft aangemeld bij deze sessie, is uw account wellicht geschonden."; -"user_verification_session_details_additional_information_untrusted_other_user" = "Totdat deze persoon de sessie vertrouwd zijn berichten gelabeld met een waarschuwing. Een alternatief is handmatig verifiëren."; +"user_verification_session_details_additional_information_untrusted_other_user" = "Totdat deze persoon deze sessie vertrouwd, zijn berichten gelabeld met waarschuwingen. Een andere mogelijkheid is om de persoon handmatig te verifiëren."; "user_verification_session_details_information_untrusted_other_user" = " heeft zich in een nieuwe sessie aangemeld:"; "user_verification_session_details_information_untrusted_current_user" = "Verifieer deze sessie om het als vertrouwd te markeren en het toegang te geven tot versleutelde berichten:"; "user_verification_session_details_information_trusted_other_user_part2" = " verifieer het:"; @@ -1180,8 +1178,8 @@ "key_verification_verify_sas_title_emoji" = "Vergelijk de emoji's"; "device_verification_self_verify_wait_recover_secrets_checking_availability" = "Controleren op andere verificatie mogelijkheden..."; "device_verification_self_verify_wait_recover_secrets_additional_information" = "Wanneer u geen toegang meer heeft tot een bestaande sessie"; -"device_verification_self_verify_wait_recover_secrets_with_passphrase" = "Uw Herstelwachtwoord of -sleutel gebruiken"; -"device_verification_self_verify_wait_recover_secrets_without_passphrase" = "Herstelsleutel gebruiken"; +"device_verification_self_verify_wait_recover_secrets_with_passphrase" = "Uw veiligheidswachtwoord of -sleutel gebruiken"; +"device_verification_self_verify_wait_recover_secrets_without_passphrase" = "Veiligheidssleutel gebruiken"; "device_verification_self_verify_wait_additional_information" = "Dit werkt met Element en andere Matrix-apps die kruislings ondertekenen ondersteunen."; "device_verification_self_verify_wait_information" = "Verifieer deze sessie vanaf een van uw andere sessies, om toegang te krijgen tot de versleutelde berichten.\n\nGebruik de laatste versie van Element op uw andere apparaten:"; "device_verification_self_verify_wait_new_sign_in_title" = "Verifieer deze login"; @@ -1266,7 +1264,7 @@ // Room widget permissions "room_widget_permission_title" = "Widget laden"; "widget_picker_manage_integrations" = "Beheer integraties…"; -"widget_integration_manager_disabled" = "U moet een integratebeheerder inschakelen in uw instellingen"; +"widget_integration_manager_disabled" = "U moet integratiebeheer inschakelen in de instellingen"; "widget_menu_remove" = "Verwijderen voor iedereen"; "widget_menu_revoke_permission" = "Toegang intrekken voor mij"; "widget_menu_open_outside" = "Openen in browser"; @@ -1292,11 +1290,11 @@ "room_details_advanced_e2e_encryption_disabled_for_dm" = "Versleuteling is hier niet ingeschakeld."; "room_details_advanced_e2e_encryption_enabled_for_dm" = "Versleuteling is hier ingeschakeld"; "room_details_advanced_room_id_for_dm" = "ID:"; -"room_details_no_local_addresses_for_dm" = "Dit heeft geen lokaaladres"; +"room_details_no_local_addresses_for_dm" = "Geen lokaaladres bekend"; "room_details_access_section_directory_toggle_for_dm" = "Weergeven in publieke groepsgesprekkencatalogus"; "room_details_access_section_anyone_for_dm" = "Iedereen die de koppeling kent, inclusief gasten"; "room_details_access_section_anyone_apart_from_guest_for_dm" = "Iedereen die de koppeling kent, behalve gasten"; -"room_details_access_section_for_dm" = "Wie mag toegang hebben?"; +"room_details_access_section_for_dm" = "Wie heeft toegang?"; "room_details_room_name_for_dm" = "Naam"; "room_details_photo_for_dm" = "Foto"; "room_details_title_for_dm" = "Details"; @@ -1343,17 +1341,17 @@ "security_settings_cryptography" = "CRYPTOGRAFIE"; "security_settings_crosssigning_complete_security" = "Beveiliging afronden"; "security_settings_crosssigning_reset" = "Reset"; -"security_settings_crosssigning_bootstrap" = "Stel in"; -"security_settings_crosssigning_info_ok" = "Cross-signing is klaar voor gebruik."; +"security_settings_crosssigning_bootstrap" = "Instellen"; +"security_settings_crosssigning_info_ok" = "Kruiselings ondertekenen is klaar voor gebruik."; "security_settings_crosssigning_info_trusted" = "Kruislings ondertekenen is ingeschakeld. U kunt andere personen en sessies verifiëren met kruislings ondertekenen, maar u kunt dit nog niet vanaf deze sessie doordat de versleutelingssleutel ontbreekt. Rond de beveiliging van deze sessie af."; "security_settings_crosssigning_info_exists" = "Uw account heeft een kruislings ondertekenen ID, maar is nog niet geverifieerd door deze sessie. Rond de beveiliging van deze sessie af."; "security_settings_crosssigning_info_not_bootstrapped" = "Kruislings ondertekenen is nog niet ingesteld."; "security_settings_crosssigning" = "KRUISLINGS ONDERTEKENEN"; "security_settings_backup" = "BERICHTENBACK-UP"; -"security_settings_secure_backup_delete" = "Verwijder backup"; +"security_settings_secure_backup_delete" = "Back-up verwijderen"; "security_settings_secure_backup_synchronise" = "Synchroniseren"; "security_settings_secure_backup_setup" = "Instellen"; -"security_settings_secure_backup_description" = "Waarborg uw toegang tot uw versleutelde berichten & data door de versleutelingssleutels op te slaan. Uw sleutels zullen worden beveiligd met een unieke beveiligingssleutel."; +"security_settings_secure_backup_description" = "Maak een back-up van uw versleutelingssleutel bij uw account data voor het geval u toegang verliest tot uw sessies. Uw sleutels zullen worden beveiligd met een unieke veiligheidssleutel."; "security_settings_secure_backup" = "VEILIGE BACK-UP"; "security_settings_crypto_sessions_description_2" = "Als u deze inlog niet herkent, verander uw wachtwoord en reset uw Veilige Back-up."; "security_settings_crypto_sessions_loading" = "Sessies laden…"; @@ -1512,3 +1510,16 @@ "settings_ui_theme_picker_message_invert_colours" = "‘Automatisch’ gebruikt de instelling ‘Kleurweergave omkeren’ van uw apparaat"; "room_recents_unknown_room_error_message" = "Dit gesprek is niet gevonden. Controleer of het bestaat"; "room_creation_dm_error" = "Uw direct gesprek kon niet aangemaakt worden. Controleer de gebruikers die u wilt uitnodigen en probeer het opnieuw."; +"key_verification_verify_qr_code_scan_code_other_device_action" = "Scan met dit apparaat"; +"room_notifs_settings_encrypted_room_notice" = "Let op dat vermeldingen & trefwoorden-meldingen niet beschikbaar zijn in versleutelde gesprekken op mobiel."; +"room_notifs_settings_account_settings" = "Accountinstellingen"; +"room_notifs_settings_manage_notifications" = "U kunt uw meldingen beheren in %@"; +"room_notifs_settings_cancel_action" = "Annuleer"; +"room_notifs_settings_done_action" = "Klaar"; +"room_notifs_settings_none" = "Geen"; +"room_notifs_settings_mentions_and_keywords" = "Alleen vermeldingen en trefwoorden"; +"room_notifs_settings_all_messages" = "Alle berichten"; + +// Room Notification Settings +"room_notifs_settings_notify_me_for" = "Stuur een melding voor"; +"room_details_notifs" = "Meldingen"; diff --git a/Riot/Assets/pl.lproj/Vector.strings b/Riot/Assets/pl.lproj/Vector.strings index 9e57955c2..96af5fb0b 100644 --- a/Riot/Assets/pl.lproj/Vector.strings +++ b/Riot/Assets/pl.lproj/Vector.strings @@ -86,7 +86,6 @@ "room_creation_appearance" = "Wygląd"; "room_creation_appearance_name" = "Nazwa"; "room_creation_privacy" = "Prywatność"; -"room_recents_directory_section_network" = "Sieć"; "room_recents_favourites_section" = "ULUBIONE"; "room_recents_people_section" = "OSOBY"; "room_recents_conversations_section" = "POKOJE"; @@ -504,7 +503,6 @@ "settings_key_backup" = "KOPIA ZAPASOWA KLUCZY"; "settings_enable_push_notif" = "Powiadomienia na tym urządzeniu"; "settings_global_settings_info" = "Globalne i szczegółowe ustawienia powiadomień są dostępne z poziomu klienta webowego: %@"; -"settings_on_denied_notification" = "Powiadomienia dla aplikacji %@ są wyłączone. Proszę zezwól na nie w ustawieniach urządzenia"; "settings_callkit_info" = "Odbieraj połączenia przychodzące na ekranie blokady. Zobacz swoje połęczenia Element w historii połączeń w systemie. Jeśli usługa iCloud jest włączona, historia połączeń zostanie udostępniona Apple."; "settings_ui_theme_picker_message" = "\"Auto\" używa ustawienia \"Odwróć kolory\" Twojego urządzenia"; "close" = "Zamknij"; diff --git a/Riot/Assets/pt_BR.lproj/InfoPlist.strings b/Riot/Assets/pt_BR.lproj/InfoPlist.strings index 035cfaba2..6449e513c 100644 --- a/Riot/Assets/pt_BR.lproj/InfoPlist.strings +++ b/Riot/Assets/pt_BR.lproj/InfoPlist.strings @@ -1,7 +1,7 @@ // Permissions usage explanations "NSCameraUsageDescription" = "A câmera é usada para tirar fotos e vídeos, fazer chamadas de vídeo."; "NSPhotoLibraryUsageDescription" = "A biblioteca de fotos é usada para enviar fotos e vídeos."; -"NSMicrophoneUsageDescription" = "O microfone é usado para tirar vídeos, fazer chamadas."; +"NSMicrophoneUsageDescription" = "Element precisa acessar seu microfone para fazer e receber chamadas, tirar vídeos, e gravar mensagens de voz."; "NSContactsUsageDescription" = "Para descobrir contatos já usando Matrix, Element pode enviar endereços de email e números de telefone em seu livro de endereços para seu servidor de identidade Matrix escolhido. Onde suportado, dados pessoais são hashados antes do envio - por favor cheque a política de privacidade de seu servidor de identidade para mais detalhes."; "NSCalendarsUsageDescription" = "Ver suas reuniões agendadas no app."; "NSFaceIDUsageDescription" = "Face ID é usada para acessar seu app."; diff --git a/Riot/Assets/pt_BR.lproj/Vector.strings b/Riot/Assets/pt_BR.lproj/Vector.strings index 9a9f7396b..3c1086f35 100644 --- a/Riot/Assets/pt_BR.lproj/Vector.strings +++ b/Riot/Assets/pt_BR.lproj/Vector.strings @@ -101,7 +101,6 @@ "room_creation_make_private" = "Fazer privado"; "room_creation_wait_for_creation" = "Uma sala já está sendo criada. Por favor espere."; "room_creation_invite_another_user" = "Buscar / convidar por ID de usuária(o), Nome ou email"; -"room_recents_directory_section_network" = "Rede"; "room_recents_favourites_section" = "FAVORITOS"; "room_recents_people_section" = "PESSOAS"; "room_recents_conversations_section" = "SALAS"; @@ -318,7 +317,6 @@ "settings_enable_push_notif" = "Notificações neste dispositivo"; "settings_show_decrypted_content" = "Mostrar conteúdo decriptado"; "settings_global_settings_info" = "Configurações de notificação globais estão disponíveis em seu cliente web %@"; -"settings_on_denied_notification" = "Notificações são negadas para %@, por favor permita-as nas configurações de seu dispositivo"; "settings_enable_callkit" = "Chamamento integrado"; "settings_callkit_info" = "Receba chamadas chegando em sua tela de bloqueio. Veja suas chamadas Element no histórico de chamadas do sistema. Se iCloud está ativado, este histórico de chamadas vai ser compartilhado com Apple."; "settings_ui_language" = "Língua"; @@ -1380,3 +1378,23 @@ "settings_ui_theme_picker_message_invert_colours" = "\"Auto\" usa as configurações \"Inverter Cores\" de seu dispositivo"; "room_recents_unknown_room_error_message" = "Não dá para encontrar esta sala. Assegure que ela existe"; "room_creation_dm_error" = "Nós não conseguimos criar sua DM. Por favor cheque as/os usuárias(os) que você quer convidar e tente de novo."; +"key_verification_verify_qr_code_scan_code_other_device_action" = "Scannar com este dispositivo"; +"room_notifs_settings_encrypted_room_notice" = "Por favor note que notificações de menções & palavrachave não estão disponíveis em salas encriptadas no celular."; +"room_notifs_settings_account_settings" = "Configurações de conta"; +"room_notifs_settings_manage_notifications" = "Você pode gerenciar notificações em %@"; +"room_notifs_settings_cancel_action" = "Cancelar"; +"room_notifs_settings_done_action" = "Feito"; +"room_notifs_settings_none" = "Nenhuma"; +"room_notifs_settings_mentions_and_keywords" = "Menções e Palavrachaves somente"; +"room_notifs_settings_all_messages" = "Todas as Mensagens"; + +// Room Notification Settings +"room_notifs_settings_notify_me_for" = "Notifique-me para"; +"room_details_notifs" = "Notificações"; +"voice_message_remaining_recording_time" = "%@s restando"; +"voice_message_stop_locked_mode_recording" = "Toque em sua gravação para parar ou escutar"; + +// Mark: - Voice Messages + +"voice_message_release_to_send" = "Segure para gravar, solte para enviar"; +"settings_labs_voice_messages" = "Mensagens de voz"; diff --git a/Riot/Assets/ru.lproj/Vector.strings b/Riot/Assets/ru.lproj/Vector.strings index 4b717f3f8..0091c576a 100644 --- a/Riot/Assets/ru.lproj/Vector.strings +++ b/Riot/Assets/ru.lproj/Vector.strings @@ -98,7 +98,6 @@ "room_creation_invite_another_user" = "Поиск / приглашение по идентификатору пользователя, имени или адресу электронной почты"; // Room recents "room_recents_directory_section" = "КАТАЛОГ КОМНАТ"; -"room_recents_directory_section_network" = "Сеть"; "room_recents_favourites_section" = "ИЗБРАННЫЕ"; "room_recents_people_section" = "ЛЮДИ"; "room_recents_conversations_section" = "КОМНАТЫ"; @@ -260,7 +259,6 @@ "settings_fail_to_update_profile" = "Не удалось обновить профиль"; "settings_enable_push_notif" = "Уведомления на этом устройстве"; "settings_global_settings_info" = "Глобальные настройки уведомлений доступны в вашем %@ веб-клиенте"; -"settings_on_denied_notification" = "Уведомления для %@ запрещены, пожалуйста, разрешите их в настройках вашего устройства"; "settings_ui_language" = "Язык"; "settings_unignore_user" = "Показать все сообщения от %@?"; "settings_labs_e2e_encryption" = "Сквозное шифрование"; diff --git a/Riot/Assets/si.lproj/Localizable.strings b/Riot/Assets/si.lproj/Localizable.strings index 432026982..ac8956825 100644 --- a/Riot/Assets/si.lproj/Localizable.strings +++ b/Riot/Assets/si.lproj/Localizable.strings @@ -17,3 +17,6 @@ /* New message from a specific person, not referencing a room. Content included. */ "MSG_FROM_USER_WITH_CONTENT" = "%@: %@"; + +/* Group call from user, CallKit caller name */ +"GROUP_CALL_FROM_USER" = "%@ (සමූහ ඇමතුම)"; diff --git a/Riot/Assets/si.lproj/Vector.strings b/Riot/Assets/si.lproj/Vector.strings index 982e41a3b..59e0d02cf 100644 --- a/Riot/Assets/si.lproj/Vector.strings +++ b/Riot/Assets/si.lproj/Vector.strings @@ -1,3 +1,16 @@ // Titles "title_home" = "මුල් පිටුව"; "warning" = "අවවාදයයි"; +"join" = "එක්වන්න"; +"save" = "සුරකින්න"; +"cancel" = "අවලංගු කරන්න"; +"remove" = "ඉවත් කරන්න"; +"leave" = "හැරයන්න"; +"start" = "අරඹන්න"; +"create" = "සාදන්න"; +"continue" = "ඉදිරියට"; +"back" = "ආපසු"; +"next" = "ඊලඟ"; +"title_rooms" = "කාමර"; +"title_people" = "මිනිසුන්"; +"title_favourites" = "ප්‍රියතමයින්"; diff --git a/Riot/Assets/sq.lproj/Vector.strings b/Riot/Assets/sq.lproj/Vector.strings index 0794ad097..7a6eb2f8c 100644 --- a/Riot/Assets/sq.lproj/Vector.strings +++ b/Riot/Assets/sq.lproj/Vector.strings @@ -84,7 +84,6 @@ "room_creation_make_private" = "Bëje private"; // Room recents "room_recents_directory_section" = "DREJTORI DHOMASH"; -"room_recents_directory_section_network" = "Rrjet"; "room_recents_people_section" = "PERSONA"; "room_recents_conversations_section" = "DHOMA"; "room_recents_no_conversation" = "S’ka dhoma"; @@ -494,7 +493,6 @@ "settings_sign_out_e2e_warn" = "Do të humbni kyçet tuaj të fshehtëzimit skaj-më-skaj. Kjo do të thotë se s’do të jeni më në gjendje të lexoni mesazhe të vjetër te dhoma të fshehtëzuara në këtë pajisje."; "settings_surname" = "Mbiemër"; "settings_global_settings_info" = "Rregullimet globale për njoftime i gjeni te klienti juaj %@ web"; -"settings_on_denied_notification" = "Njoftimet për %@ s’pranohen, ju lutemi, lejojini që nga rregullimet e pajisjes tuaj"; "settings_enable_callkit" = "Thirrje të integruara"; "settings_callkit_info" = "Merrini thirrjet ardhëse edhe me ekran të kyçur. Shihni thirrjet tuaja nën Element te historiku i thirrjeve të sistemit. Nëse iCloud është i aktivizuar, ky historik thirrjesh do t’i jepet kompanisë Apple."; "settings_ui_theme_picker_message" = "\"Auto\" përdor rregullimet \"Përmbysi Ngjyrat\" të pajisjes tuaj"; @@ -1401,3 +1399,16 @@ "settings_ui_theme_picker_message_invert_colours" = "“Auto” përdor rregullimet “Ktheji Së Prapthi Ngjyrat”"; "room_recents_unknown_room_error_message" = "S’gjendet dot kjo dhomë. Sigurohuni se ekziston"; "room_creation_dm_error" = "S’mundëm të krijojmë dot MD-në tuaj. Ju lutemi, kontrolloni përdoruesit të cilëve doni t’u dërgohet dhe riprovoni."; +"key_verification_verify_qr_code_scan_code_other_device_action" = "Skanoje me këtë pajisje"; +"room_notifs_settings_encrypted_room_notice" = "Ju lutemi, kini parasysh se njoftimet për përmendje & fjalëkyçe s’mund të kihen në celular për dhoma të fshehtëzuara."; +"room_notifs_settings_account_settings" = "Rregullime llogarie"; +"room_notifs_settings_manage_notifications" = "Njoftimet mund t’i administroni që nga %@"; +"room_notifs_settings_cancel_action" = "Anuloje"; +"room_notifs_settings_done_action" = "U bë"; +"room_notifs_settings_none" = "Asnjë"; +"room_notifs_settings_mentions_and_keywords" = "Vetëm Përmendje dhe Fjalëkyçe"; +"room_notifs_settings_all_messages" = "Krejt Mesazhet"; + +// Room Notification Settings +"room_notifs_settings_notify_me_for" = "Njoftomëni për"; +"room_details_notifs" = "Njoftime"; diff --git a/Riot/Assets/sv.lproj/Vector.strings b/Riot/Assets/sv.lproj/Vector.strings index 14a8abd72..8316784a1 100644 --- a/Riot/Assets/sv.lproj/Vector.strings +++ b/Riot/Assets/sv.lproj/Vector.strings @@ -100,7 +100,6 @@ "room_creation_make_private" = "Gör privat"; "room_creation_wait_for_creation" = "Ett rum håller redan på att skapas. Vänligen vänta."; "room_creation_invite_another_user" = "Sök / bjud in efter användar-ID, namn eller e-postadress"; -"room_recents_directory_section_network" = "Nätverk"; "room_recents_favourites_section" = "FAVORITER"; "room_recents_people_section" = "PERSONER"; "room_recents_conversations_section" = "RUM"; @@ -310,7 +309,6 @@ "settings_show_decrypted_content" = "Visa avkrypterat innehåll"; "settings_pin_rooms_with_missed_notif" = "Fäst rum med missade aviseringar"; "settings_pin_rooms_with_unread" = "Fäst rum med olästa meddelanden"; -"settings_on_denied_notification" = "Aviseringar har nekats för %@, vänligen tillåt dem i dina enhetsinställningar"; "settings_enable_callkit" = "Integrerade samtal"; "settings_integrations_allow_button" = "Hantera integrationer"; "settings_ui_language" = "Språk"; diff --git a/Riot/Assets/third_party_licenses.html b/Riot/Assets/third_party_licenses.html index 4dfc32ac7..6e491fdca 100644 --- a/Riot/Assets/third_party_licenses.html +++ b/Riot/Assets/third_party_licenses.html @@ -1688,7 +1688,203 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

- + +
  • + DSWaveformImage (https://github.com/dmrschmidt/DSWaveformImage) +

    + The MIT License (MIT) +

    + Copyright (c) 2013 Dennis Schmidt +

    + Permission is hereby granted, free of charge, to any person obtaining a copy of + this software and associated documentation files (the "Software"), to deal in + the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: +

    + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. +

    + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +

    +
  • +
  • + ffmpeg-kit-ios-audio (https://github.com/tanersener/ffmpeg-kit) +

    +
    +                   GNU LESSER GENERAL PUBLIC LICENSE
    +                       Version 3, 29 June 2007
    +
    + Copyright (C) 2007 Free Software Foundation, Inc. 
    + Everyone is permitted to copy and distribute verbatim copies
    + of this license document, but changing it is not allowed.
    +
    +
    +  This version of the GNU Lesser General Public License incorporates
    +the terms and conditions of version 3 of the GNU General Public
    +License, supplemented by the additional permissions listed below.
    +
    +  0. Additional Definitions.
    +
    +  As used herein, "this License" refers to version 3 of the GNU Lesser
    +General Public License, and the "GNU GPL" refers to version 3 of the GNU
    +General Public License.
    +
    +  "The Library" refers to a covered work governed by this License,
    +other than an Application or a Combined Work as defined below.
    +
    +  An "Application" is any work that makes use of an interface provided
    +by the Library, but which is not otherwise based on the Library.
    +Defining a subclass of a class defined by the Library is deemed a mode
    +of using an interface provided by the Library.
    +
    +  A "Combined Work" is a work produced by combining or linking an
    +Application with the Library.  The particular version of the Library
    +with which the Combined Work was made is also called the "Linked
    +Version".
    +
    +  The "Minimal Corresponding Source" for a Combined Work means the
    +Corresponding Source for the Combined Work, excluding any source code
    +for portions of the Combined Work that, considered in isolation, are
    +based on the Application, and not on the Linked Version.
    +
    +  The "Corresponding Application Code" for a Combined Work means the
    +object code and/or source code for the Application, including any data
    +and utility programs needed for reproducing the Combined Work from the
    +Application, but excluding the System Libraries of the Combined Work.
    +
    +  1. Exception to Section 3 of the GNU GPL.
    +
    +  You may convey a covered work under sections 3 and 4 of this License
    +without being bound by section 3 of the GNU GPL.
    +
    +  2. Conveying Modified Versions.
    +
    +  If you modify a copy of the Library, and, in your modifications, a
    +facility refers to a function or data to be supplied by an Application
    +that uses the facility (other than as an argument passed when the
    +facility is invoked), then you may convey a copy of the modified
    +version:
    +
    +   a) under this License, provided that you make a good faith effort to
    +   ensure that, in the event an Application does not supply the
    +   function or data, the facility still operates, and performs
    +   whatever part of its purpose remains meaningful, or
    +
    +   b) under the GNU GPL, with none of the additional permissions of
    +   this License applicable to that copy.
    +
    +  3. Object Code Incorporating Material from Library Header Files.
    +
    +  The object code form of an Application may incorporate material from
    +a header file that is part of the Library.  You may convey such object
    +code under terms of your choice, provided that, if the incorporated
    +material is not limited to numerical parameters, data structure
    +layouts and accessors, or small macros, inline functions and templates
    +(ten or fewer lines in length), you do both of the following:
    +
    +   a) Give prominent notice with each copy of the object code that the
    +   Library is used in it and that the Library and its use are
    +   covered by this License.
    +
    +   b) Accompany the object code with a copy of the GNU GPL and this license
    +   document.
    +
    +  4. Combined Works.
    +
    +  You may convey a Combined Work under terms of your choice that,
    +taken together, effectively do not restrict modification of the
    +portions of the Library contained in the Combined Work and reverse
    +engineering for debugging such modifications, if you also do each of
    +the following:
    +
    +   a) Give prominent notice with each copy of the Combined Work that
    +   the Library is used in it and that the Library and its use are
    +   covered by this License.
    +
    +   b) Accompany the Combined Work with a copy of the GNU GPL and this license
    +   document.
    +
    +   c) For a Combined Work that displays copyright notices during
    +   execution, include the copyright notice for the Library among
    +   these notices, as well as a reference directing the user to the
    +   copies of the GNU GPL and this license document.
    +
    +   d) Do one of the following:
    +
    +       0) Convey the Minimal Corresponding Source under the terms of this
    +       License, and the Corresponding Application Code in a form
    +       suitable for, and under terms that permit, the user to
    +       recombine or relink the Application with a modified version of
    +       the Linked Version to produce a modified Combined Work, in the
    +       manner specified by section 6 of the GNU GPL for conveying
    +       Corresponding Source.
    +
    +       1) Use a suitable shared library mechanism for linking with the
    +       Library.  A suitable mechanism is one that (a) uses at run time
    +       a copy of the Library already present on the user's computer
    +       system, and (b) will operate properly with a modified version
    +       of the Library that is interface-compatible with the Linked
    +       Version.
    +
    +   e) Provide Installation Information, but only if you would otherwise
    +   be required to provide such information under section 6 of the
    +   GNU GPL, and only to the extent that such information is
    +   necessary to install and execute a modified version of the
    +   Combined Work produced by recombining or relinking the
    +   Application with a modified version of the Linked Version. (If
    +   you use option 4d0, the Installation Information must accompany
    +   the Minimal Corresponding Source and Corresponding Application
    +   Code. If you use option 4d1, you must provide the Installation
    +   Information in the manner specified by section 6 of the GNU GPL
    +   for conveying Corresponding Source.)
    +
    +  5. Combined Libraries.
    +
    +  You may place library facilities that are a work based on the
    +Library side by side in a single library together with other library
    +facilities that are not Applications and are not covered by this
    +License, and convey such a combined library under terms of your
    +choice, if you do both of the following:
    +
    +   a) Accompany the combined library with a copy of the same work based
    +   on the Library, uncombined with any other library facilities,
    +   conveyed under the terms of this License.
    +
    +   b) Give prominent notice with the combined library that part of it
    +   is a work based on the Library, and explaining where to find the
    +   accompanying uncombined form of the same work.
    +
    +  6. Revised Versions of the GNU Lesser General Public License.
    +
    +  The Free Software Foundation may publish revised and/or new versions
    +of the GNU Lesser General Public License from time to time. Such new
    +versions will be similar in spirit to the present version, but may
    +differ in detail to address new problems or concerns.
    +
    +  Each version is given a distinguishing version number. If the
    +Library as you received it specifies that a certain numbered version
    +of the GNU Lesser General Public License "or any later version"
    +applies to it, you have the option of following the terms and
    +conditions either of that published version or of any later version
    +published by the Free Software Foundation. If the Library as you
    +received it does not specify a version number of the GNU Lesser
    +General Public License, you may choose any version of the GNU Lesser
    +General Public License ever published by the Free Software Foundation.
    +
    +  If the Library as you received it specifies that a proxy can decide
    +whether future versions of the GNU Lesser General Public License shall
    +apply, that proxy's public statement of acceptance of any version is
    +permanent authorization for you to choose that version for the
    +Library.
    +        
    +
  • diff --git a/Riot/Assets/uk.lproj/InfoPlist.strings b/Riot/Assets/uk.lproj/InfoPlist.strings index ba2fc5b9d..6973b0ae6 100644 --- a/Riot/Assets/uk.lproj/InfoPlist.strings +++ b/Riot/Assets/uk.lproj/InfoPlist.strings @@ -1,7 +1,7 @@ // Permissions usage explanations "NSCameraUsageDescription" = "Камера використовується для знімків фото і відео, а також для відео-викликів."; "NSPhotoLibraryUsageDescription" = "Фотографії використовуються для надсилання фото і відео."; -"NSMicrophoneUsageDescription" = "Мікрофон використовується для відео і викликів."; +"NSMicrophoneUsageDescription" = "Element потребує доступу до вашого мікрофона, щоб здійснювати та отримувати виклики, знімати відео та записувати голосові повідомлення."; "NSContactsUsageDescription" = "Щоб показати, які з ваших контактів вже використовують Matrix, Element може надіслати адреси електронної пошти і номери телефонів з вашої адресної книги до вашого ідентифікаційного сервера Matrix. При наявності підтримки, перед надсиланням створюється хеш особистих даних. Для докладних відомостей ознайомтеся з політикою приватності свого ідентифікаційного сервера."; "NSCalendarsUsageDescription" = "Переглядайте свої заплановані зустрічі в додатку."; "NSFaceIDUsageDescription" = "Face ID використовується для доступу до вашого додатку."; diff --git a/Riot/Assets/vi.lproj/Vector.strings b/Riot/Assets/vi.lproj/Vector.strings index 9b2d328c4..c401d3268 100644 --- a/Riot/Assets/vi.lproj/Vector.strings +++ b/Riot/Assets/vi.lproj/Vector.strings @@ -105,7 +105,6 @@ "room_creation_invite_another_user" = "Tìm / mời bằng ID người dùng, tên hoặc email"; // Room recents "room_recents_directory_section" = "DANH MỤC PHÒNG"; -"room_recents_directory_section_network" = "Mạng"; "room_recents_favourites_section" = "YÊU THÍCH"; "room_recents_people_section" = "DANH BẠ"; "room_recents_conversations_section" = "PHÒNG"; @@ -291,7 +290,6 @@ "settings_global_settings_info" = "Cài đặt thông báo toàn cầu khả dụng trên %@ trình duyệt khách của bạn"; "settings_pin_rooms_with_missed_notif" = "Neo phòng có thông báo bỏ lỡ"; "settings_pin_rooms_with_unread" = "Neo phòng có tin nhắn chưa đọc"; -"settings_on_denied_notification" = "Thông báo bị từ chối cho %@, vui lòng cho phép trong cài đặt thiết bị của bạn"; "settings_enable_callkit" = "Cuộc gọi tích hợp"; "settings_callkit_info" = "Nhận cuộc gọi tới trên màn hình khóa. Xem lịch sử cuộc gọi trong lịch sử cuộc gọi của hệ thống. Nếu iCloud được kích hoạt, lịch sử cuộc gọi sẽ được chia sẻ với Apple."; "settings_ui_language" = "Ngôn ngữ"; diff --git a/Riot/Assets/zh_Hans.lproj/InfoPlist.strings b/Riot/Assets/zh_Hans.lproj/InfoPlist.strings index b548711be..b4daf9c55 100644 --- a/Riot/Assets/zh_Hans.lproj/InfoPlist.strings +++ b/Riot/Assets/zh_Hans.lproj/InfoPlist.strings @@ -1,7 +1,7 @@ // Permissions usage explanations "NSCameraUsageDescription" = "摄像头权限用于拍摄照片、录制视频或进行视频聊天。"; "NSPhotoLibraryUsageDescription" = "照片库访问权限用于发送图片与视频。"; -"NSMicrophoneUsageDescription" = "麦克风权限用于录制视频或进行通话。"; +"NSMicrophoneUsageDescription" = "Element 需要访问您的麦克风才能拨打和接听电话、拍摄视频和录制语音消息。"; "NSContactsUsageDescription" = "为了发现已在使用 Matrix 的联系人,Element 可以把你地址簿里的邮箱地址和电话号码发送到你所选择的 Matrix 身份认证服务器。如果支持的话,个人数据在发送前会被哈希处理——请检查你的身份认证服务器的隐私政策以获取详细信息。"; "NSCalendarsUsageDescription" = "在此应用中查看你计划的会议。"; "NSFaceIDUsageDescription" = "Face ID 权限用于访问您的应用。"; diff --git a/Riot/Assets/zh_Hans.lproj/Vector.strings b/Riot/Assets/zh_Hans.lproj/Vector.strings index 43a665081..732a53609 100644 --- a/Riot/Assets/zh_Hans.lproj/Vector.strings +++ b/Riot/Assets/zh_Hans.lproj/Vector.strings @@ -96,7 +96,6 @@ "room_creation_invite_another_user" = "通过用户 ID、名称或电子邮件进行搜索/邀请"; // Room recents "room_recents_directory_section" = "聊天室目录"; -"room_recents_directory_section_network" = "网络"; "room_recents_favourites_section" = "收藏夹"; "room_recents_people_section" = "联系人"; "room_recents_conversations_section" = "聊天室"; @@ -273,7 +272,6 @@ "settings_global_settings_info" = "全局通知设置可在 %@ 的网页客户端中修改"; "settings_pin_rooms_with_missed_notif" = "置顶含有错过的通知的聊天室"; "settings_pin_rooms_with_unread" = "置顶含有未读消息的聊天室"; -"settings_on_denied_notification" = "%@ 的通知请求被拒绝,请在系统设置中允许"; "settings_unignore_user" = "显示所有来自 %@ 的消息?"; "settings_contacts_discover_matrix_users" = "使用电子邮件和手机号码来发现用户"; "settings_contacts_phonebook_country" = "电话本国家"; @@ -1427,3 +1425,23 @@ "settings_ui_theme_picker_message_invert_colours" = "“自动”使用您设备的“反转颜色”设置"; "room_recents_unknown_room_error_message" = "找不到这个房间。 确保它存在"; "room_creation_dm_error" = "我们无法创建您的 DM。 请检查您要邀请的用户,然后重试。"; +"voice_message_stop_locked_mode_recording" = "轻按录音停止或收听"; +"voice_message_remaining_recording_time" = "剩 %@s"; + +// Mark: - Voice Messages + +"voice_message_release_to_send" = "按住录音,松开发送"; +"key_verification_verify_qr_code_scan_code_other_device_action" = "用这部设备扫描"; +"room_notifs_settings_encrypted_room_notice" = "请注意,移动设备上的加密聊天室不提供提及和关键字通知。"; +"room_notifs_settings_account_settings" = "账户设置"; +"room_notifs_settings_manage_notifications" = "你可以管理 %@ 中的消息"; +"room_notifs_settings_cancel_action" = "取消"; +"room_notifs_settings_done_action" = "完成"; +"room_notifs_settings_none" = "无"; +"room_notifs_settings_mentions_and_keywords" = "仅提及和关键词"; +"room_notifs_settings_all_messages" = "所有消息"; + +// Room Notification Settings +"room_notifs_settings_notify_me_for" = "通知内容"; +"room_details_notifs" = "通知"; +"settings_labs_voice_messages" = "语音消息"; diff --git a/Riot/Assets/zh_Hant.lproj/Vector.strings b/Riot/Assets/zh_Hant.lproj/Vector.strings index 685722293..bf0c0648c 100644 --- a/Riot/Assets/zh_Hant.lproj/Vector.strings +++ b/Riot/Assets/zh_Hant.lproj/Vector.strings @@ -184,7 +184,6 @@ "room_creation_invite_another_user" = "透過使用者ID、名稱、電子郵件地址來搜尋/邀請"; // Room recents "room_recents_directory_section" = "聊天室目錄"; -"room_recents_directory_section_network" = "網路"; "room_recents_favourites_section" = "收藏夾"; "room_recents_people_section" = "聯絡人"; "room_recents_conversations_section" = "聊天室"; @@ -345,7 +344,6 @@ "room_ongoing_conference_call_with_close" = "群組通話進行中。 以 %@ 或 %@ 加入。%@ 該通話。"; "settings_pin_rooms_with_missed_notif" = "釘選含有錯過的通知的聊天室"; "settings_pin_rooms_with_unread" = "釘選含有未讀訊息的聊天室"; -"settings_on_denied_notification" = "因 %@ 的通知不被允許,請在裝置設定中允許"; "settings_enable_callkit" = "整合式通話"; "settings_callkit_info" = "在鎖定畫面接聽 Element 來電、在通話紀錄中顯示 Element 通話。 如果您已啟用 iCloud ,則這些通話紀錄會與蘋果公司共享。"; "settings_ui_language" = "語言"; diff --git a/Riot/Categories/MXKRoomBubbleTableViewCell+Riot.m b/Riot/Categories/MXKRoomBubbleTableViewCell+Riot.m index a85ea69be..11a0fa5a1 100644 --- a/Riot/Categories/MXKRoomBubbleTableViewCell+Riot.m +++ b/Riot/Categories/MXKRoomBubbleTableViewCell+Riot.m @@ -397,7 +397,7 @@ NSString *const kMXKRoomBubbleCellKeyVerificationIncomingRequestDeclinePressed = } // Move this view in front - [self.contentView bringSubviewToFront:self.bubbleOverlayContainer]; + [self.bubbleOverlayContainer.superview bringSubviewToFront:self.bubbleOverlayContainer]; } else { diff --git a/Riot/Coordinators/Coordinator.swift b/Riot/Coordinators/Coordinator.swift index 4d7f27b6a..3345c2f94 100755 --- a/Riot/Coordinators/Coordinator.swift +++ b/Riot/Coordinators/Coordinator.swift @@ -19,7 +19,7 @@ import UIKit /// Protocol describing a [Coordinator](http://khanlou.com/2015/10/coordinators-redux/). /// Coordinators are the objects which control the navigation flow of the application. /// It helps to isolate and reuse view controllers and pass dependencies down the navigation hierarchy. -protocol Coordinator: class { +protocol Coordinator: AnyObject { /// Starts job of the coordinator. func start() diff --git a/Riot/Generated/Images.swift b/Riot/Generated/Images.swift index afc57884a..c6b056c71 100644 --- a/Riot/Generated/Images.swift +++ b/Riot/Generated/Images.swift @@ -36,6 +36,8 @@ internal enum Asset { internal static let callDialpadCallIcon = ImageAsset(name: "call_dialpad_call_icon") internal static let callGoToChatIcon = ImageAsset(name: "call_go_to_chat_icon") internal static let callHangupLarge = ImageAsset(name: "call_hangup_large") + internal static let callMissedVideo = ImageAsset(name: "call_missed_video") + internal static let callMissedVoice = ImageAsset(name: "call_missed_voice") internal static let callMoreIcon = ImageAsset(name: "call_more_icon") internal static let callPausedIcon = ImageAsset(name: "call_paused_icon") internal static let callPausedWhiteIcon = ImageAsset(name: "call_paused_white_icon") @@ -85,6 +87,7 @@ internal enum Asset { internal static let roomActionFavourite = ImageAsset(name: "room_action_favourite") internal static let roomActionLeave = ImageAsset(name: "room_action_leave") internal static let roomActionNotification = ImageAsset(name: "room_action_notification") + internal static let roomActionNotificationMuted = ImageAsset(name: "room_action_notification_muted") internal static let roomActionPriorityHigh = ImageAsset(name: "room_action_priority_high") internal static let roomActionPriorityLow = ImageAsset(name: "room_action_priority_low") internal static let homeEmptyScreenArtwork = ImageAsset(name: "home_empty_screen_artwork") @@ -134,6 +137,15 @@ internal enum Asset { internal static let videoCall = ImageAsset(name: "video_call") internal static let voiceCallHangonIcon = ImageAsset(name: "voice_call_hangon_icon") internal static let voiceCallHangupIcon = ImageAsset(name: "voice_call_hangup_icon") + internal static let voiceMessageCancelGradient = ImageAsset(name: "voice_message_cancel_gradient") + internal static let voiceMessageLockChevron = ImageAsset(name: "voice_message_lock_chevron") + internal static let voiceMessageLockIconLocked = ImageAsset(name: "voice_message_lock_icon_locked") + internal static let voiceMessageLockIconUnlocked = ImageAsset(name: "voice_message_lock_icon_unlocked") + internal static let voiceMessagePauseButton = ImageAsset(name: "voice_message_pause_button") + internal static let voiceMessagePlayButton = ImageAsset(name: "voice_message_play_button") + internal static let voiceMessageRecordButtonDefault = ImageAsset(name: "voice_message_record_button_default") + internal static let voiceMessageRecordButtonRecording = ImageAsset(name: "voice_message_record_button_recording") + internal static let voiceMessageRecordIcon = ImageAsset(name: "voice_message_record_icon") internal static let addMemberFloatingAction = ImageAsset(name: "add_member_floating_action") internal static let addParticipant = ImageAsset(name: "add_participant") internal static let addParticipants = ImageAsset(name: "add_participants") @@ -144,6 +156,7 @@ internal enum Asset { internal static let membersListIcon = ImageAsset(name: "members_list_icon") internal static let modIcon = ImageAsset(name: "mod_icon") internal static let moreReactions = ImageAsset(name: "more_reactions") + internal static let notifications = ImageAsset(name: "notifications") internal static let scrollup = ImageAsset(name: "scrollup") internal static let roomsEmptyScreenArtwork = ImageAsset(name: "rooms_empty_screen_artwork") internal static let roomsEmptyScreenArtworkDark = ImageAsset(name: "rooms_empty_screen_artwork_dark") diff --git a/Riot/Generated/Storyboards.swift b/Riot/Generated/Storyboards.swift index c66d40f6a..0b77710ce 100644 --- a/Riot/Generated/Storyboards.swift +++ b/Riot/Generated/Storyboards.swift @@ -172,6 +172,11 @@ internal enum StoryboardScene { internal static let initialScene = InitialSceneType(storyboard: RoomInfoListViewController.self) } + internal enum RoomNotificationSettingsViewController: StoryboardType { + internal static let storyboardName = "RoomNotificationSettingsViewController" + + internal static let initialScene = InitialSceneType(storyboard: RoomNotificationSettingsViewController.self) + } internal enum SecretsRecoveryWithKeyViewController: StoryboardType { internal static let storyboardName = "SecretsRecoveryWithKeyViewController" diff --git a/Riot/Generated/Strings.swift b/Riot/Generated/Strings.swift index ba05c0233..3598c2003 100644 --- a/Riot/Generated/Strings.swift +++ b/Riot/Generated/Strings.swift @@ -1242,6 +1242,14 @@ internal enum VectorL10n { internal static var errorUserAlreadyLoggedIn: String { return VectorL10n.tr("Vector", "error_user_already_logged_in") } + /// Active video call + internal static var eventFormatterCallActiveVideo: String { + return VectorL10n.tr("Vector", "event_formatter_call_active_video") + } + /// Active voice call + internal static var eventFormatterCallActiveVoice: String { + return VectorL10n.tr("Vector", "event_formatter_call_active_voice") + } /// Answer internal static var eventFormatterCallAnswer: String { return VectorL10n.tr("Vector", "event_formatter_call_answer") @@ -1266,10 +1274,26 @@ internal enum VectorL10n { internal static var eventFormatterCallEndCall: String { return VectorL10n.tr("Vector", "event_formatter_call_end_call") } - /// Ended %@ + /// Call ended %@ internal static func eventFormatterCallHasEnded(_ p1: String) -> String { return VectorL10n.tr("Vector", "event_formatter_call_has_ended", p1) } + /// Incoming video call + internal static var eventFormatterCallIncomingVideo: String { + return VectorL10n.tr("Vector", "event_formatter_call_incoming_video") + } + /// Incoming voice call + internal static var eventFormatterCallIncomingVoice: String { + return VectorL10n.tr("Vector", "event_formatter_call_incoming_voice") + } + /// Missed video call + internal static var eventFormatterCallMissedVideo: String { + return VectorL10n.tr("Vector", "event_formatter_call_missed_video") + } + /// Missed voice call + internal static var eventFormatterCallMissedVoice: String { + return VectorL10n.tr("Vector", "event_formatter_call_missed_voice") + } /// Retry internal static var eventFormatterCallRetry: String { return VectorL10n.tr("Vector", "event_formatter_call_retry") @@ -1278,26 +1302,10 @@ internal enum VectorL10n { internal static var eventFormatterCallRinging: String { return VectorL10n.tr("Vector", "event_formatter_call_ringing") } - /// Video call - internal static var eventFormatterCallVideo: String { - return VectorL10n.tr("Vector", "event_formatter_call_video") - } - /// Voice call - internal static var eventFormatterCallVoice: String { - return VectorL10n.tr("Vector", "event_formatter_call_voice") - } - /// Active call - internal static var eventFormatterCallYouCurrentlyIn: String { - return VectorL10n.tr("Vector", "event_formatter_call_you_currently_in") - } - /// You declined this call + /// Call declined internal static var eventFormatterCallYouDeclined: String { return VectorL10n.tr("Vector", "event_formatter_call_you_declined") } - /// You missed this call - internal static var eventFormatterCallYouMissed: String { - return VectorL10n.tr("Vector", "event_formatter_call_you_missed") - } /// Group call internal static var eventFormatterGroupCall: String { return VectorL10n.tr("Vector", "event_formatter_group_call") @@ -2014,6 +2022,10 @@ internal enum VectorL10n { internal static var keyVerificationVerifyQrCodeScanCodeAction: String { return VectorL10n.tr("Vector", "key_verification_verify_qr_code_scan_code_action") } + /// Scan with this device + internal static var keyVerificationVerifyQrCodeScanCodeOtherDeviceAction: String { + return VectorL10n.tr("Vector", "key_verification_verify_qr_code_scan_code_other_device_action") + } /// QR code has been successfully validated. internal static var keyVerificationVerifyQrCodeScanOtherCodeSuccessMessage: String { return VectorL10n.tr("Vector", "key_verification_verify_qr_code_scan_other_code_success_message") @@ -2710,6 +2722,10 @@ internal enum VectorL10n { internal static var roomDetailsNoLocalAddressesForDm: String { return VectorL10n.tr("Vector", "room_details_no_local_addresses_for_dm") } + /// Notifications + internal static var roomDetailsNotifs: String { + return VectorL10n.tr("Vector", "room_details_notifs") + } /// Members internal static var roomDetailsPeople: String { return VectorL10n.tr("Vector", "room_details_people") @@ -3018,6 +3034,42 @@ internal enum VectorL10n { internal static var roomNoPrivilegesToCreateGroupCall: String { return VectorL10n.tr("Vector", "room_no_privileges_to_create_group_call") } + /// Account settings + internal static var roomNotifsSettingsAccountSettings: String { + return VectorL10n.tr("Vector", "room_notifs_settings_account_settings") + } + /// All Messages + internal static var roomNotifsSettingsAllMessages: String { + return VectorL10n.tr("Vector", "room_notifs_settings_all_messages") + } + /// Cancel + internal static var roomNotifsSettingsCancelAction: String { + return VectorL10n.tr("Vector", "room_notifs_settings_cancel_action") + } + /// Done + internal static var roomNotifsSettingsDoneAction: String { + return VectorL10n.tr("Vector", "room_notifs_settings_done_action") + } + /// Please note that mentions & keyword notifications are not available in encrypted rooms on mobile. + internal static var roomNotifsSettingsEncryptedRoomNotice: String { + return VectorL10n.tr("Vector", "room_notifs_settings_encrypted_room_notice") + } + /// You can manage notifications in %@ + internal static func roomNotifsSettingsManageNotifications(_ p1: String) -> String { + return VectorL10n.tr("Vector", "room_notifs_settings_manage_notifications", p1) + } + /// Mentions and Keywords only + internal static var roomNotifsSettingsMentionsAndKeywords: String { + return VectorL10n.tr("Vector", "room_notifs_settings_mentions_and_keywords") + } + /// None + internal static var roomNotifsSettingsNone: String { + return VectorL10n.tr("Vector", "room_notifs_settings_none") + } + /// Notify me for + internal static var roomNotifsSettingsNotifyMeFor: String { + return VectorL10n.tr("Vector", "room_notifs_settings_notify_me_for") + } /// Connectivity to the server has been lost. internal static var roomOfflineNotification: String { return VectorL10n.tr("Vector", "room_offline_notification") @@ -3314,10 +3366,6 @@ internal enum VectorL10n { internal static var roomRecentsDirectorySection: String { return VectorL10n.tr("Vector", "room_recents_directory_section") } - /// Network - internal static var roomRecentsDirectorySectionNetwork: String { - return VectorL10n.tr("Vector", "room_recents_directory_section_network") - } /// FAVOURITES internal static var roomRecentsFavouritesSection: String { return VectorL10n.tr("Vector", "room_recents_favourites_section") @@ -4074,6 +4122,10 @@ internal enum VectorL10n { internal static var settingsDeactivateMyAccount: String { return VectorL10n.tr("Vector", "settings_deactivate_my_account") } + /// Device notifications + internal static var settingsDeviceNotifications: String { + return VectorL10n.tr("Vector", "settings_device_notifications") + } /// SESSIONS internal static var settingsDevices: String { return VectorL10n.tr("Vector", "settings_devices") @@ -4334,6 +4386,10 @@ internal enum VectorL10n { internal static var settingsLabsMessageReaction: String { return VectorL10n.tr("Vector", "settings_labs_message_reaction") } + /// Voice messages + internal static var settingsLabsVoiceMessages: String { + return VectorL10n.tr("Vector", "settings_labs_voice_messages") + } /// Mark all messages as read internal static var settingsMarkAllAsRead: String { return VectorL10n.tr("Vector", "settings_mark_all_as_read") @@ -4346,6 +4402,14 @@ internal enum VectorL10n { internal static var settingsNightMode: String { return VectorL10n.tr("Vector", "settings_night_mode") } + /// To enable notifications, go to your device settings. + internal static var settingsNotificationsDisabledAlertMessage: String { + return VectorL10n.tr("Vector", "settings_notifications_disabled_alert_message") + } + /// Notifications disabled + internal static var settingsNotificationsDisabledAlertTitle: String { + return VectorL10n.tr("Vector", "settings_notifications_disabled_alert_title") + } /// NOTIFICATION SETTINGS internal static var settingsNotificationsSettings: String { return VectorL10n.tr("Vector", "settings_notifications_settings") @@ -4358,10 +4422,6 @@ internal enum VectorL10n { internal static func settingsOlmVersion(_ p1: String) -> String { return VectorL10n.tr("Vector", "settings_olm_version", p1) } - /// Notifications are denied for %@, please allow them in your device settings - internal static func settingsOnDeniedNotification(_ p1: String) -> String { - return VectorL10n.tr("Vector", "settings_on_denied_notification", p1) - } /// OTHER internal static var settingsOther: String { return VectorL10n.tr("Vector", "settings_other") @@ -4838,6 +4898,22 @@ internal enum VectorL10n { internal static var voice: String { return VectorL10n.tr("Vector", "voice") } + /// Voice message + internal static var voiceMessageLockScreenPlaceholder: String { + return VectorL10n.tr("Vector", "voice_message_lock_screen_placeholder") + } + /// Hold to record, release to send + internal static var voiceMessageReleaseToSend: String { + return VectorL10n.tr("Vector", "voice_message_release_to_send") + } + /// %@s left + internal static func voiceMessageRemainingRecordingTime(_ p1: String) -> String { + return VectorL10n.tr("Vector", "voice_message_remaining_recording_time", p1) + } + /// Tap on your recording to stop or listen + internal static var voiceMessageStopLockedModeRecording: String { + return VectorL10n.tr("Vector", "voice_message_stop_locked_mode_recording") + } /// Warning internal static var warning: String { return VectorL10n.tr("Vector", "warning") diff --git a/Riot/Managers/Call/CallPresenterDelegate.swift b/Riot/Managers/Call/CallPresenterDelegate.swift index 9c7f44687..a9f6df03e 100644 --- a/Riot/Managers/Call/CallPresenterDelegate.swift +++ b/Riot/Managers/Call/CallPresenterDelegate.swift @@ -17,7 +17,7 @@ import Foundation @objc -protocol CallPresenterDelegate: class { +protocol CallPresenterDelegate: AnyObject { // Call screens func callPresenter(_ presenter: CallPresenter, presentCallViewController viewController: UIViewController, diff --git a/Riot/Managers/Call/PiPView.swift b/Riot/Managers/Call/PiPView.swift index 8f5a0587a..54a199044 100644 --- a/Riot/Managers/Call/PiPView.swift +++ b/Riot/Managers/Call/PiPView.swift @@ -23,7 +23,7 @@ import UIKit case topLeft } -@objc protocol PiPViewDelegate: class { +@objc protocol PiPViewDelegate: AnyObject { @objc optional func pipView(_ view: PiPView, didMoveTo position: PiPViewPosition) @objc optional func pipViewDidTap(_ view: PiPView) } diff --git a/Riot/Managers/Settings/RiotSettings.swift b/Riot/Managers/Settings/RiotSettings.swift index 533702c2b..7d41bc4df 100644 --- a/Riot/Managers/Settings/RiotSettings.swift +++ b/Riot/Managers/Settings/RiotSettings.swift @@ -52,6 +52,7 @@ final class RiotSettings: NSObject { static let roomCreationScreenRoomIsPublic = "roomCreationScreenRoomIsPublic" static let allowInviteExernalUsers = "allowInviteExernalUsers" static let enableRingingForGroupCalls = "enableRingingForGroupCalls" + static let enableVoiceMessages = "enableVoiceMessages" static let roomSettingsScreenShowLowPriorityOption = "roomSettingsScreenShowLowPriorityOption" static let roomSettingsScreenShowDirectChatOption = "roomSettingsScreenShowDirectChatOption" static let roomSettingsScreenAllowChangingAccessSettings = "roomSettingsScreenAllowChangingAccessSettings" @@ -80,6 +81,7 @@ final class RiotSettings: NSObject { static let roomMemberScreenShowIgnore = "roomMemberScreenShowIgnore" static let unifiedSearchScreenShowPublicDirectory = "unifiedSearchScreenShowPublicDirectory" static let hideSpaceBetaAnnounce = "hideSpaceBetaAnnounce" + static let secretsRecoveryAllowReset = "secretsRecoveryAllowReset" } static let shared = RiotSettings() @@ -92,6 +94,11 @@ final class RiotSettings: NSObject { return userDefaults }() + private override init() { + super.init() + defaults.register(defaults: [UserDefaultsKeys.enableVoiceMessages: BuildSettings.voiceMessagesEnabled]) + } + // MARK: Servers var homeserverUrlString: String { @@ -214,6 +221,14 @@ final class RiotSettings: NSObject { } } + var enableVoiceMessages: Bool { + get { + return defaults.bool(forKey: UserDefaultsKeys.enableVoiceMessages) + } set { + defaults.set(newValue, forKey: UserDefaultsKeys.enableVoiceMessages) + } + } + // MARK: Calls /// Indicate if `allowStunServerFallback` settings has been set once. @@ -695,7 +710,7 @@ final class RiotSettings: NSObject { } } - // Mark: - Unified Search + // MARK: - Unified Search var unifiedSearchScreenShowPublicDirectory: Bool { get { @@ -708,6 +723,19 @@ final class RiotSettings: NSObject { } } + // MARK: - Secrets Recovery + + var secretsRecoveryAllowReset: Bool { + get { + guard defaults.object(forKey: UserDefaultsKeys.secretsRecoveryAllowReset) != nil else { + return BuildSettings.secretsRecoveryAllowReset + } + return defaults.bool(forKey: UserDefaultsKeys.secretsRecoveryAllowReset) + } set { + defaults.set(newValue, forKey: UserDefaultsKeys.secretsRecoveryAllowReset) + } + } + // MARK: - Beta var hideSpaceBetaAnnounce: Bool { diff --git a/Riot/Managers/Theme/Themable.swift b/Riot/Managers/Theme/Themable.swift index f4f59f1e9..71931bfe8 100644 --- a/Riot/Managers/Theme/Themable.swift +++ b/Riot/Managers/Theme/Themable.swift @@ -16,6 +16,6 @@ import Foundation -@objc protocol Themable: class { +@objc protocol Themable: AnyObject { func update(theme: Theme) } diff --git a/Riot/Managers/Widgets/WidgetManager.h b/Riot/Managers/Widgets/WidgetManager.h index 09c2b2588..f5279a216 100644 --- a/Riot/Managers/Widgets/WidgetManager.h +++ b/Riot/Managers/Widgets/WidgetManager.h @@ -183,7 +183,7 @@ FOUNDATION_EXPORT NSString *const WidgetManagerErrorOpenIdTokenKey; /** Store the integration manager configuration for a user. - @param the integration manager configuration. + @param config the integration manager configuration. @param userId the user id. */ - (void)setConfig:(WidgetManagerConfig*)config forUser:(NSString*)userId; diff --git a/Riot/Model/Room/RoomPreviewData.h b/Riot/Model/Room/RoomPreviewData.h index 71280b099..c10295617 100644 --- a/Riot/Model/Room/RoomPreviewData.h +++ b/Riot/Model/Room/RoomPreviewData.h @@ -76,10 +76,17 @@ Contructors. @param roomId the id of the room. - @param emailInvitationParams, in case of an email invitation link, the query parameters extracted from the link. @param mxSession the session to open the room preview with. */ - (instancetype)initWithRoomId:(NSString*)roomId andSession:(MXSession*)mxSession; + +/** + Contructors. + + @param roomId the id of the room. + @param emailInvitationParams in case of an email invitation link, the query parameters extracted from the link. + @param mxSession the session to open the room preview with. + */ - (instancetype)initWithRoomId:(NSString*)roomId emailInvitationParams:(NSDictionary*)emailInvitationParams andSession:(MXSession*)mxSession; /** diff --git a/Riot/Modules/Application/AppCoordinator.swift b/Riot/Modules/Application/AppCoordinator.swift index 4469fbbfa..8a1949f75 100755 --- a/Riot/Modules/Application/AppCoordinator.swift +++ b/Riot/Modules/Application/AppCoordinator.swift @@ -21,8 +21,12 @@ import Intents import FLEX #endif -/// The AppCoordinator is responsible of screen navigation and data injection at root application level. It decides if authentication or home screen should be shown and inject data needed for these flows, it changes the navigation stack on deep link, displays global warning. -/// This class should avoid to contain too many data management code not related to screen navigation logic. For example `MXSession` or push notification management should be handled in dedicated classes and report only navigation changes to the AppCoordinator. +/// The AppCoordinator is responsible of screen navigation and data injection at root application level. It decides +/// if authentication or home screen should be shown and inject data needed for these flows, it changes the navigation +/// stack on deep link, displays global warning. +/// This class should avoid to contain too many data management code not related to screen navigation logic. For example +/// `MXSession` or push notification management should be handled in dedicated classes and report only navigation +/// changes to the AppCoordinator. final class AppCoordinator: NSObject, AppCoordinatorType { // MARK: - Constants @@ -83,7 +87,8 @@ final class AppCoordinator: NSObject, AppCoordinatorType { } func open(url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool { - // NOTE: As said in the Apple documentation be careful on security issues with Custom Scheme URL (see https://developer.apple.com/documentation/xcode/allowing_apps_and_websites_to_link_to_your_content/defining_a_custom_url_scheme_for_your_app) + // NOTE: As said in the Apple documentation be careful on security issues with Custom Scheme URL: + // https://developer.apple.com/documentation/xcode/allowing_apps_and_websites_to_link_to_your_content/defining_a_custom_url_scheme_for_your_app do { let deepLinkOption = try self.customSchemeURLParser.parse(url: url, options: options) diff --git a/Riot/Modules/Application/LegacyAppDelegate.h b/Riot/Modules/Application/LegacyAppDelegate.h index 78269f348..1b06d8cbe 100644 --- a/Riot/Modules/Application/LegacyAppDelegate.h +++ b/Riot/Modules/Application/LegacyAppDelegate.h @@ -173,7 +173,7 @@ UINavigationControllerDelegate Log out all the accounts without confirmation. Show the authentication screen on successful logout. - @param sendLogoutRequest Indicate whether send logout request to homeserver. + @param sendLogoutServerRequest Indicate whether send logout request to homeserver. @param completion the block to execute at the end of the operation. */ - (void)logoutSendingRequestServer:(BOOL)sendLogoutServerRequest @@ -183,7 +183,7 @@ UINavigationControllerDelegate Present incoming key verification request to accept. @param incomingKeyVerificationRequest The incoming key verification request. - @param The matrix session. + @param session The matrix session. @return Indicate NO if the key verification screen could not be presented. */ - (BOOL)presentIncomingKeyVerificationRequest:(MXKeyVerificationRequest*)incomingKeyVerificationRequest diff --git a/Riot/Modules/Application/LegacyAppDelegate.m b/Riot/Modules/Application/LegacyAppDelegate.m index 32010f9bb..a6df6cb48 100644 --- a/Riot/Modules/Application/LegacyAppDelegate.m +++ b/Riot/Modules/Application/LegacyAppDelegate.m @@ -1854,16 +1854,6 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni [account addObserver:self forKeyPath:@"enableInAppNotifications" options:0 context:nil]; } - // Load the local contacts on first account creation. - if ([MXKAccountManager sharedManager].accounts.count == 1) - { - dispatch_async(dispatch_get_main_queue(), ^{ - - [self refreshLocalContacts]; - - }); - } - [self.delegate legacyAppDelegate:self didAddAccount:account]; }]; @@ -1976,14 +1966,6 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni // during this blocking task. dispatch_after(dispatch_walltime(DISPATCH_TIME_NOW, 0.3 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{ [[MXKContactManager sharedManager] addMatrixSession:mxSession]; - - // Load the local contacts on first account - if ([MXKAccountManager sharedManager].accounts.count == 1) - { - dispatch_async(dispatch_get_main_queue(), ^{ - [self refreshLocalContacts]; - }); - } }); // Register the session to the widgets manager @@ -2939,54 +2921,6 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni }]; } -- (void)refreshLocalContacts -{ - if (!BuildSettings.allowLocalContactsAccess) - { - return; - } - - // Do not scan local contacts in background if the user has not decided yet about using - // an identity server - BOOL doRefreshLocalContacts = NO; - for (MXSession *session in mxSessionArray) - { - if (session.hasAccountDataIdentityServerValue) - { - doRefreshLocalContacts = YES; - break; - } - } - - // Check whether the application is allowed to access the local contacts. - if (doRefreshLocalContacts - && [CNContactStore authorizationStatusForEntityType:CNEntityTypeContacts] == CNAuthorizationStatusAuthorized) - { - // Check the user permission for syncing local contacts. This permission was handled independently on previous application version. - if (![MXKAppSettings standardAppSettings].syncLocalContacts) - { - // Check whether it was not requested yet. - if (![MXKAppSettings standardAppSettings].syncLocalContactsPermissionRequested) - { - [MXKAppSettings standardAppSettings].syncLocalContactsPermissionRequested = YES; - - [MXKContactManager requestUserConfirmationForLocalContactsSyncInViewController:self.presentedViewController completionHandler:^(BOOL granted) { - - if (granted) - { - // Allow local contacts sync in order to discover matrix users. - [MXKAppSettings standardAppSettings].syncLocalContacts = YES; - } - - }]; - } - } - - // Refresh the local contacts list. - [[MXKContactManager sharedManager] refreshLocalContacts]; - } -} - #pragma mark - Matrix Groups handling - (void)showGroup:(MXGroup*)group withMatrixSession:(MXSession*)mxSession @@ -4202,18 +4136,16 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni [[NSUserDefaults standardUserDefaults] registerDefaults:defaults]; + // Migrates old UserDefaults values if showDecryptedContentInNotifications hasn't been set if (!RiotSettings.shared.isUserDefaultsMigrated) { [RiotSettings.shared migrate]; } - // Now use RiotSettings and NSUserDefaults to store `showDecryptedContentInNotifications` setting option - // Migrate this information from main MXKAccount to RiotSettings, if value is not in UserDefaults - + // Show encrypted message notification content by default. if (!RiotSettings.shared.isShowDecryptedContentInNotificationsHasBeenSetOnce) { - MXKAccount *currentAccount = [MXKAccountManager sharedManager].activeAccounts.firstObject; - RiotSettings.shared.showDecryptedContentInNotifications = currentAccount.showDecryptedContentInNotifications; + RiotSettings.shared.showDecryptedContentInNotifications = BuildSettings.decryptNotificationsByDefault; } } diff --git a/Riot/Modules/Authentication/AuthenticationViewController.m b/Riot/Modules/Authentication/AuthenticationViewController.m index 4d7e65964..ad064f196 100644 --- a/Riot/Modules/Authentication/AuthenticationViewController.m +++ b/Riot/Modules/Authentication/AuthenticationViewController.m @@ -354,8 +354,6 @@ static const CGFloat kAuthInputContainerViewMinHeightConstraintConstant = 150.0; { [_keyboardAvoider stopAvoiding]; - [self.authenticationActivityIndicator removeObserver:self forKeyPath:@"hidden"]; - [super viewDidDisappear:animated]; } @@ -384,6 +382,8 @@ static const CGFloat kAuthInputContainerViewMinHeightConstraintConstant = 150.0; [[NSNotificationCenter defaultCenter] removeObserver:universalLinkDidChangeNotificationObserver]; universalLinkDidChangeNotificationObserver = nil; } + + [self.authenticationActivityIndicator removeObserver:self forKeyPath:@"hidden"]; autoDiscovery = nil; _keyVerificationCoordinatorBridgePresenter = nil; diff --git a/Riot/Modules/Authentication/SocialLogin/SocialLoginListView.swift b/Riot/Modules/Authentication/SocialLogin/SocialLoginListView.swift index 6a66da782..e2e1a586f 100644 --- a/Riot/Modules/Authentication/SocialLogin/SocialLoginListView.swift +++ b/Riot/Modules/Authentication/SocialLogin/SocialLoginListView.swift @@ -17,7 +17,7 @@ import UIKit import Reusable -@objc protocol SocialLoginListViewDelegate: class { +@objc protocol SocialLoginListViewDelegate: AnyObject { func socialLoginListView(_ socialLoginListView: SocialLoginListView, didTapSocialButtonWithIdentifier identifier: String) } diff --git a/Riot/Modules/Call/Dialpad/DialpadViewController.swift b/Riot/Modules/Call/Dialpad/DialpadViewController.swift index 23e2c21ce..ce9a9fc66 100644 --- a/Riot/Modules/Call/Dialpad/DialpadViewController.swift +++ b/Riot/Modules/Call/Dialpad/DialpadViewController.swift @@ -19,7 +19,7 @@ import UIKit import libPhoneNumber_iOS -@objc protocol DialpadViewControllerDelegate: class { +@objc protocol DialpadViewControllerDelegate: AnyObject { @objc optional func dialpadViewControllerDidTapCall(_ viewController: DialpadViewController, withPhoneNumber phoneNumber: String) @objc optional func dialpadViewControllerDidTapClose(_ viewController: DialpadViewController) diff --git a/Riot/Modules/CallTransfer/CallTransferMainViewController.swift b/Riot/Modules/CallTransfer/CallTransferMainViewController.swift index af4b093d1..f7e23d0cd 100644 --- a/Riot/Modules/CallTransfer/CallTransferMainViewController.swift +++ b/Riot/Modules/CallTransfer/CallTransferMainViewController.swift @@ -18,7 +18,7 @@ import UIKit -@objc protocol CallTransferMainViewControllerDelegate: class { +@objc protocol CallTransferMainViewControllerDelegate: AnyObject { func callTransferMainViewControllerDidComplete(_ viewController: CallTransferMainViewController, consult: Bool, contact: MXKContact?, diff --git a/Riot/Modules/CallTransfer/SelectContact/CallTransferSelectContactViewController.swift b/Riot/Modules/CallTransfer/SelectContact/CallTransferSelectContactViewController.swift index 786cc4f71..df5c0a5a8 100644 --- a/Riot/Modules/CallTransfer/SelectContact/CallTransferSelectContactViewController.swift +++ b/Riot/Modules/CallTransfer/SelectContact/CallTransferSelectContactViewController.swift @@ -19,7 +19,7 @@ import UIKit import Reusable -protocol CallTransferSelectContactViewControllerDelegate: class { +protocol CallTransferSelectContactViewControllerDelegate: AnyObject { func callTransferSelectContactViewControllerDidSelectContact(_ viewController: CallTransferSelectContactViewController, contact: MXKContact?) } diff --git a/Riot/Modules/Camera/CameraPresenter.swift b/Riot/Modules/Camera/CameraPresenter.swift index 749e9abe5..a39bda7c0 100644 --- a/Riot/Modules/Camera/CameraPresenter.swift +++ b/Riot/Modules/Camera/CameraPresenter.swift @@ -18,7 +18,7 @@ import Foundation import UIKit import AVFoundation -@objc protocol CameraPresenterDelegate: class { +@objc protocol CameraPresenterDelegate: AnyObject { func cameraPresenter(_ presenter: CameraPresenter, didSelectImageData imageData: Data, withUTI uti: MXKUTI?) func cameraPresenter(_ presenter: CameraPresenter, didSelectVideoAt url: URL) func cameraPresenterDidCancel(_ cameraPresenter: CameraPresenter) diff --git a/Riot/Modules/Common/Recents/DataSources/RecentsDataSource.m b/Riot/Modules/Common/Recents/DataSources/RecentsDataSource.m index 3857f61fc..123d9da69 100644 --- a/Riot/Modules/Common/Recents/DataSources/RecentsDataSource.m +++ b/Riot/Modules/Common/Recents/DataSources/RecentsDataSource.m @@ -19,7 +19,6 @@ #import "RecentCellData.h" #import "SectionHeaderView.h" -#import "DirectorySectionHeaderContainerView.h" #import "ThemeService.h" @@ -37,7 +36,6 @@ #define RECENTSDATASOURCE_SECTION_PEOPLE 0x40 #define RECENTSDATASOURCE_DEFAULT_SECTION_HEADER_HEIGHT 30.0 -#define RECENTSDATASOURCE_DIRECTORY_SECTION_HEADER_HEIGHT 65.0 NSString *const kRecentsDataSourceTapOnDirectoryServerChange = @"kRecentsDataSourceTapOnDirectoryServerChange"; @@ -48,10 +46,6 @@ NSString *const kRecentsDataSourceTapOnDirectoryServerChange = @"kRecentsDataSou NSInteger shrinkedSectionsBitMask; - DirectorySectionHeaderContainerView *directorySectionContainer; - UILabel *networkLabel; - UILabel *directoryServerLabel; - NSMutableDictionary *roomTagsListenerByUserId; // Timer to not refresh publicRoomsDirectoryDataSource on every keystroke. @@ -77,17 +71,9 @@ NSString *const kRecentsDataSourceTapOnDirectoryServerChange = @"kRecentsDataSou processingQueue = dispatch_queue_create("RecentsDataSource", DISPATCH_QUEUE_SERIAL); _crossSigningBannerDisplay = CrossSigningBannerDisplayNone; - crossSigningBannerSection = -1; - _secureBackupBannerDisplay = SecureBackupBannerDisplayNone; - secureBackupBannerSection = -1; - directorySection = -1; - invitesSection = -1; - favoritesSection = -1; - peopleSection = -1; - conversationSection = -1; - lowPrioritySection = -1; - serverNoticeSection = -1; + + [self resetSectionIndexes]; _areSectionsShrinkable = NO; shrinkedSectionsBitMask = 0; @@ -102,6 +88,19 @@ NSString *const kRecentsDataSourceTapOnDirectoryServerChange = @"kRecentsDataSou return self; } +- (void)resetSectionIndexes +{ + crossSigningBannerSection = -1; + secureBackupBannerSection = -1; + directorySection = -1; + invitesSection = -1; + favoritesSection = -1; + peopleSection = -1; + conversationSection = -1; + lowPrioritySection = -1; + serverNoticeSection = -1; +} + #pragma mark - Properties @@ -451,7 +450,7 @@ NSString *const kRecentsDataSourceTapOnDirectoryServerChange = @"kRecentsDataSou // Check whether all data sources are ready before rendering recents if (self.state == MXKDataSourceStateReady) { - crossSigningBannerSection = secureBackupBannerSection = directorySection = favoritesSection = peopleSection = conversationSection = lowPrioritySection = invitesSection = serverNoticeSection = -1; + [self resetSectionIndexes]; if (self.crossSigningBannerDisplay != CrossSigningBannerDisplayNone) { @@ -483,13 +482,6 @@ NSString *const kRecentsDataSourceTapOnDirectoryServerChange = @"kRecentsDataSou conversationSection = sectionsCount++; } - if (_recentsDataSourceMode == RecentsDataSourceModeRooms - && BuildSettings.publicRoomsShowDirectory) - { - // Add the directory section after "ROOMS" - directorySection = sectionsCount++; - } - if (self.lowPriorityCellDataArray.count > 0) { lowPrioritySection = sectionsCount++; @@ -572,12 +564,6 @@ NSString *const kRecentsDataSourceTapOnDirectoryServerChange = @"kRecentsDataSou { return 0.0; } - else if (section == directorySection - && !(shrinkedSectionsBitMask & RECENTSDATASOURCE_SECTION_DIRECTORY) - && BuildSettings.publicRoomsAllowServerChange) - { - return RECENTSDATASOURCE_DIRECTORY_SECTION_HEADER_HEIGHT; - } return RECENTSDATASOURCE_DEFAULT_SECTION_HEADER_HEIGHT; } @@ -823,57 +809,6 @@ NSString *const kRecentsDataSourceTapOnDirectoryServerChange = @"kRecentsDataSou [sectionHeader addSubview:headerLabel]; sectionHeader.headerLabel = headerLabel; - if (section == directorySection - && _recentsDataSourceMode == RecentsDataSourceModeRooms - && !(shrinkedSectionsBitMask & RECENTSDATASOURCE_SECTION_DIRECTORY) - && BuildSettings.publicRoomsAllowServerChange) - { - if (!directorySectionContainer) - { - directorySectionContainer = [[DirectorySectionHeaderContainerView alloc] initWithFrame:CGRectZero]; - directorySectionContainer.backgroundColor = [UIColor clearColor]; - - // Add the "Network" label at the left - networkLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 100, 30)]; - networkLabel.font = [UIFont systemFontOfSize:16.0]; - networkLabel.text = NSLocalizedStringFromTable(@"room_recents_directory_section_network", @"Vector", nil); - [directorySectionContainer addSubview:networkLabel]; - directorySectionContainer.networkLabel = networkLabel; - - // Add label for selected directory server - directoryServerLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 0, 30)]; - directoryServerLabel.font = [UIFont systemFontOfSize:16.0]; - directoryServerLabel.textAlignment = NSTextAlignmentRight; - [directorySectionContainer addSubview:directoryServerLabel]; - directorySectionContainer.directoryServerLabel = directoryServerLabel; - - // Chevron - UIImageView *chevronImageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 6, 12)]; - chevronImageView.contentMode = UIViewContentModeScaleAspectFit; - chevronImageView.image = [UIImage imageNamed:@"disclosure_icon"]; - chevronImageView.tintColor = ThemeService.shared.theme.textSecondaryColor; - [directorySectionContainer addSubview:chevronImageView]; - directorySectionContainer.disclosureView = chevronImageView; - - // Set a tap listener on all the container - UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(onDirectoryServerPickerTap:)]; - [tapGesture setNumberOfTouchesRequired:1]; - [tapGesture setNumberOfTapsRequired:1]; - [directorySectionContainer addGestureRecognizer:tapGesture]; - } - - // Apply the current UI theme. - networkLabel.textColor = ThemeService.shared.theme.textPrimaryColor; - directoryServerLabel.textColor = ThemeService.shared.theme.textSecondaryColor; - - // Set the current directory server name - directoryServerLabel.text = _publicRoomsDirectoryDataSource.directoryServerDisplayname; - - // Add the check box container - [sectionHeader addSubview:directorySectionContainer]; - sectionHeader.bottomView = directorySectionContainer; - } - return sectionHeader; } @@ -1189,10 +1124,8 @@ NSString *const kRecentsDataSourceTapOnDirectoryServerChange = @"kRecentsDataSou #pragma mark - MXKDataSourceDelegate -- (void)refreshRoomsSection:(void (^)(void))onComplete; +- (void)refreshRoomsSection:(void (^)(void))onComplete { - secureBackupBannerSection = directorySection = favoritesSection = peopleSection = conversationSection = lowPrioritySection = serverNoticeSection = invitesSection = -1; - if (displayedRecentsDataSourceArray.count > 0) { // FIXME manage multi accounts diff --git a/Riot/Modules/Common/Recents/RecentsViewController.h b/Riot/Modules/Common/Recents/RecentsViewController.h index b2b640d80..d3968cfd6 100644 --- a/Riot/Modules/Common/Recents/RecentsViewController.h +++ b/Riot/Modules/Common/Recents/RecentsViewController.h @@ -161,10 +161,15 @@ - (void)makeDirectEditedRoom:(BOOL)isDirect; /** - Enable/disable the notifications for the selected room. - */ +Enable/disable the notifications for the selected room. +*/ - (void)muteEditedRoomNotifications:(BOOL)mute; +/** + Edit notification settings for the selected room. + */ +- (void)changeEditedRoomNotificationSettings; + /** Show room directory. */ diff --git a/Riot/Modules/Common/Recents/RecentsViewController.m b/Riot/Modules/Common/Recents/RecentsViewController.m index 6ac8c618b..e76a83cab 100644 --- a/Riot/Modules/Common/Recents/RecentsViewController.m +++ b/Riot/Modules/Common/Recents/RecentsViewController.m @@ -34,7 +34,7 @@ #import "Riot-Swift.h" -@interface RecentsViewController () +@interface RecentsViewController () { // Tell whether a recents refresh is pending (suspended during editing mode). BOOL isRefreshPending; @@ -74,6 +74,8 @@ @property (nonatomic, strong) CustomSizedPresentationController *customSizedPresentationController; +@property (nonatomic, strong) RoomNotificationSettingsCoordinatorBridgePresenter *roomNotificationSettingsCoordinatorBridgePresenter; + @end @implementation RecentsViewController @@ -1031,12 +1033,31 @@ UIContextualAction *muteAction = [UIContextualAction contextualActionWithStyle:UIContextualActionStyleDestructive title:title handler:^(UIContextualAction * _Nonnull action, __kindof UIView * _Nonnull sourceView, void (^ _Nonnull completionHandler)(BOOL)) { - [self muteEditedRoomNotifications:!isMuted]; + + if ([BuildSettings roomSettingsScreenShowNotificationsV2]) + { + [self changeEditedRoomNotificationSettings]; + } + else + { + [self muteEditedRoomNotifications:!isMuted]; + } + + completionHandler(YES); }]; muteAction.backgroundColor = actionBackgroundColor; - UIImage *notificationImage = [UIImage imageNamed:@"room_action_notification"]; + UIImage *notificationImage; + if([BuildSettings roomSettingsScreenShowNotificationsV2]) + { + notificationImage = isMuted ? [UIImage imageNamed:@"room_action_notification_muted"] : [UIImage imageNamed:@"room_action_notification"]; + } + else + { + notificationImage = [UIImage imageNamed:@"room_action_notification"]; + } + notificationImage = [notificationImage vc_tintedImageUsingColor:isMuted ? unselectedColor : selectedColor]; muteAction.image = [notificationImage vc_notRenderedImage]; @@ -1298,6 +1319,23 @@ } } +- (void)changeEditedRoomNotificationSettings +{ + if (editedRoomId) + { + // Check whether the user didn't leave the room + MXRoom *room = [self.mainSession roomWithRoomId:editedRoomId]; + if (room) + { + // navigate + self.roomNotificationSettingsCoordinatorBridgePresenter = [[RoomNotificationSettingsCoordinatorBridgePresenter alloc] initWithRoom:room]; + self.roomNotificationSettingsCoordinatorBridgePresenter.delegate = self; + [self.roomNotificationSettingsCoordinatorBridgePresenter presentFrom:self animated:YES]; + } + [self cancelEditionMode:isRefreshPending]; + } +} + - (void)muteEditedRoomNotifications:(BOOL)mute { if (editedRoomId) @@ -1307,27 +1345,27 @@ if (room) { [self startActivityIndicator]; - + if (mute) { [room mentionsOnly:^{ - + [self stopActivityIndicator]; - + // Leave editing mode - [self cancelEditionMode:isRefreshPending]; - + [self cancelEditionMode:self->isRefreshPending]; + }]; } else { [room allMessages:^{ - + [self stopActivityIndicator]; - + // Leave editing mode - [self cancelEditionMode:isRefreshPending]; - + [self cancelEditionMode:self->isRefreshPending]; + }]; } } @@ -2219,4 +2257,11 @@ } } +#pragma mark - RoomNotificationSettingsCoordinatorBridgePresenterDelegate +-(void)roomNotificationSettingsCoordinatorBridgePresenterDelegateDidComplete:(RoomNotificationSettingsCoordinatorBridgePresenter *)coordinatorBridgePresenter +{ + [coordinatorBridgePresenter dismissWithAnimated:YES completion:nil]; + self.roomNotificationSettingsCoordinatorBridgePresenter = nil; +} + @end diff --git a/Riot/Modules/Common/SectionHeaders/TitleHeaderView.swift b/Riot/Modules/Common/SectionHeaders/TitleHeaderView.swift new file mode 100644 index 000000000..a26d99820 --- /dev/null +++ b/Riot/Modules/Common/SectionHeaders/TitleHeaderView.swift @@ -0,0 +1,39 @@ +// +// Copyright 2021 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation +import Reusable + +class TitleHeaderView: UITableViewHeaderFooterView { + + @IBOutlet weak var label: UILabel! + + func update(title: String) { + label.text = title.uppercased() + } + +} + + +extension TitleHeaderView: NibReusable {} +extension TitleHeaderView: Themable { + + func update(theme: Theme) { + contentView.backgroundColor = theme.headerBackgroundColor + label.textColor = theme.headerTextSecondaryColor + label.font = theme.fonts.body + } +} diff --git a/Riot/Modules/Common/SectionHeaders/TitleHeaderView.xib b/Riot/Modules/Common/SectionHeaders/TitleHeaderView.xib new file mode 100644 index 000000000..c49ab7fff --- /dev/null +++ b/Riot/Modules/Common/SectionHeaders/TitleHeaderView.xib @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Riot/Modules/Common/SegmentedViewController/SegmentedViewController.m b/Riot/Modules/Common/SegmentedViewController/SegmentedViewController.m index a4e4151f7..8480205af 100644 --- a/Riot/Modules/Common/SegmentedViewController/SegmentedViewController.m +++ b/Riot/Modules/Common/SegmentedViewController/SegmentedViewController.m @@ -74,7 +74,7 @@ /** init the segmentedViewController with a list of UIViewControllers. @param titles the section tiles - @param viewControllers the list of viewControllers to display. + @param someViewControllers the list of viewControllers to display. @param defaultSelected index of the default selected UIViewController in the list. */ - (void)initWithTitles:(NSArray*)titles viewControllers:(NSArray*)someViewControllers defaultSelected:(NSUInteger)defaultSelected diff --git a/Riot/Modules/Contacts/ContactsTableViewController.m b/Riot/Modules/Contacts/ContactsTableViewController.m index 5437cd149..c9af2e4c5 100644 --- a/Riot/Modules/Contacts/ContactsTableViewController.m +++ b/Riot/Modules/Contacts/ContactsTableViewController.m @@ -174,6 +174,15 @@ [self refreshContactsTable]; } +- (void)viewDidAppear:(BOOL)animated +{ + [super viewDidAppear:animated]; + + // Load the local contacts for display. + // In viewDidAppear as it may trigger a request for contacts access. + [self refreshLocalContacts]; +} + - (void)viewWillDisappear:(BOOL)animated { [super viewWillDisappear:animated]; @@ -212,6 +221,54 @@ } } +- (void)refreshLocalContacts +{ + if (!BuildSettings.allowLocalContactsAccess) + { + return; + } + + // Do not scan local contacts in background if the user has not decided yet about using + // an identity server + BOOL doRefreshLocalContacts = NO; + for (MXSession *session in self.mxSessions) + { + if (session.hasAccountDataIdentityServerValue) + { + doRefreshLocalContacts = YES; + break; + } + } + + // Check whether the application is allowed to access the local contacts. + if (doRefreshLocalContacts + && [CNContactStore authorizationStatusForEntityType:CNEntityTypeContacts] == CNAuthorizationStatusAuthorized) + { + // Check the user permission for syncing local contacts. This permission was handled independently on previous application version. + if (![MXKAppSettings standardAppSettings].syncLocalContacts) + { + // Check whether it was not requested yet. + if (![MXKAppSettings standardAppSettings].syncLocalContactsPermissionRequested) + { + [MXKAppSettings standardAppSettings].syncLocalContactsPermissionRequested = YES; + + [MXKContactManager requestUserConfirmationForLocalContactsSyncInViewController:self completionHandler:^(BOOL granted) { + + if (granted) + { + // Allow local contacts sync in order to discover matrix users. + [MXKAppSettings standardAppSettings].syncLocalContacts = YES; + } + + }]; + } + } + + // Refresh the local contacts list. + [[MXKContactManager sharedManager] refreshLocalContacts]; + } +} + - (void)refreshContactsTable { [self.contactsTableView reloadData]; diff --git a/Riot/Modules/CreateRoom/CreateRoomCoordinatorType.swift b/Riot/Modules/CreateRoom/CreateRoomCoordinatorType.swift index 2bddc933c..585538ddb 100644 --- a/Riot/Modules/CreateRoom/CreateRoomCoordinatorType.swift +++ b/Riot/Modules/CreateRoom/CreateRoomCoordinatorType.swift @@ -18,7 +18,7 @@ import Foundation -protocol CreateRoomCoordinatorDelegate: class { +protocol CreateRoomCoordinatorDelegate: AnyObject { func createRoomCoordinator(_ coordinator: CreateRoomCoordinatorType, didCreateNewRoom room: MXRoom) func createRoomCoordinatorDidCancel(_ coordinator: CreateRoomCoordinatorType) } diff --git a/Riot/Modules/CreateRoom/EnterNewRoomDetails/Cells/ChooseAvatarTableViewCell.swift b/Riot/Modules/CreateRoom/EnterNewRoomDetails/Cells/ChooseAvatarTableViewCell.swift index 82160a816..bcca816cf 100644 --- a/Riot/Modules/CreateRoom/EnterNewRoomDetails/Cells/ChooseAvatarTableViewCell.swift +++ b/Riot/Modules/CreateRoom/EnterNewRoomDetails/Cells/ChooseAvatarTableViewCell.swift @@ -17,7 +17,7 @@ import UIKit import Reusable -protocol ChooseAvatarTableViewCellDelegate: class { +protocol ChooseAvatarTableViewCellDelegate: AnyObject { func chooseAvatarTableViewCellDidTapChooseAvatar(_ cell: ChooseAvatarTableViewCell, sourceView: UIView) } diff --git a/Riot/Modules/CreateRoom/EnterNewRoomDetails/EnterNewRoomDetailsCoordinatorType.swift b/Riot/Modules/CreateRoom/EnterNewRoomDetails/EnterNewRoomDetailsCoordinatorType.swift index d2798fcb5..2d02d6cda 100644 --- a/Riot/Modules/CreateRoom/EnterNewRoomDetails/EnterNewRoomDetailsCoordinatorType.swift +++ b/Riot/Modules/CreateRoom/EnterNewRoomDetails/EnterNewRoomDetailsCoordinatorType.swift @@ -18,7 +18,7 @@ import Foundation -protocol EnterNewRoomDetailsCoordinatorDelegate: class { +protocol EnterNewRoomDetailsCoordinatorDelegate: AnyObject { func enterNewRoomDetailsCoordinator(_ coordinator: EnterNewRoomDetailsCoordinatorType, didCreateNewRoom room: MXRoom) func enterNewRoomDetailsCoordinatorDidCancel(_ coordinator: EnterNewRoomDetailsCoordinatorType) } diff --git a/Riot/Modules/CreateRoom/EnterNewRoomDetails/EnterNewRoomDetailsViewModelType.swift b/Riot/Modules/CreateRoom/EnterNewRoomDetails/EnterNewRoomDetailsViewModelType.swift index ea6b388c2..be053323d 100644 --- a/Riot/Modules/CreateRoom/EnterNewRoomDetails/EnterNewRoomDetailsViewModelType.swift +++ b/Riot/Modules/CreateRoom/EnterNewRoomDetails/EnterNewRoomDetailsViewModelType.swift @@ -18,11 +18,11 @@ import Foundation -protocol EnterNewRoomDetailsViewModelViewDelegate: class { +protocol EnterNewRoomDetailsViewModelViewDelegate: AnyObject { func enterNewRoomDetailsViewModel(_ viewModel: EnterNewRoomDetailsViewModelType, didUpdateViewState viewSate: EnterNewRoomDetailsViewState) } -protocol EnterNewRoomDetailsViewModelCoordinatorDelegate: class { +protocol EnterNewRoomDetailsViewModelCoordinatorDelegate: AnyObject { func enterNewRoomDetailsViewModel(_ viewModel: EnterNewRoomDetailsViewModelType, didCreateNewRoom room: MXRoom) func enterNewRoomDetailsViewModel(_ viewModel: EnterNewRoomDetailsViewModelType, didTapChooseAvatar sourceView: UIView) func enterNewRoomDetailsViewModelDidCancel(_ viewModel: EnterNewRoomDetailsViewModelType) diff --git a/Riot/Modules/CrossSigning/Banners/CrossSigningSetupBannerCell.swift b/Riot/Modules/CrossSigning/Banners/CrossSigningSetupBannerCell.swift index 6a7161bea..f0c13b154 100644 --- a/Riot/Modules/CrossSigning/Banners/CrossSigningSetupBannerCell.swift +++ b/Riot/Modules/CrossSigning/Banners/CrossSigningSetupBannerCell.swift @@ -16,7 +16,7 @@ import UIKit -@objc protocol CrossSigningSetupBannerCellDelegate: class { +@objc protocol CrossSigningSetupBannerCellDelegate: AnyObject { func crossSigningSetupBannerCellDidTapCloseAction(_ cell: CrossSigningSetupBannerCell) } diff --git a/Riot/Modules/CrossSigning/Setup/CrossSigningSetupCoordinatorType.swift b/Riot/Modules/CrossSigning/Setup/CrossSigningSetupCoordinatorType.swift index 1dc620d3f..ccd85e17b 100644 --- a/Riot/Modules/CrossSigning/Setup/CrossSigningSetupCoordinatorType.swift +++ b/Riot/Modules/CrossSigning/Setup/CrossSigningSetupCoordinatorType.swift @@ -18,7 +18,7 @@ import Foundation -protocol CrossSigningSetupCoordinatorDelegate: class { +protocol CrossSigningSetupCoordinatorDelegate: AnyObject { func crossSigningSetupCoordinatorDidComplete(_ coordinator: CrossSigningSetupCoordinatorType) func crossSigningSetupCoordinatorDidCancel(_ coordinator: CrossSigningSetupCoordinatorType) func crossSigningSetupCoordinator(_ coordinator: CrossSigningSetupCoordinatorType, didFailWithError error: Error) diff --git a/Riot/Modules/GDPR/GDPRConsentViewController.swift b/Riot/Modules/GDPR/GDPRConsentViewController.swift index 29ebdc22c..4a5cdee8d 100644 --- a/Riot/Modules/GDPR/GDPRConsentViewController.swift +++ b/Riot/Modules/GDPR/GDPRConsentViewController.swift @@ -16,7 +16,7 @@ import Foundation -@objc protocol GDPRConsentViewControllerDelegate: class { +@objc protocol GDPRConsentViewControllerDelegate: AnyObject { func gdprConsentViewControllerDidConsentToGDPRWithSuccess(_ gdprConsentViewController: GDPRConsentViewController) } diff --git a/Riot/Modules/GlobalSearch/Files/Views/FilesSearchTableViewCell.m b/Riot/Modules/GlobalSearch/Files/Views/FilesSearchTableViewCell.m index b853631cd..4dfbe686f 100644 --- a/Riot/Modules/GlobalSearch/Files/Views/FilesSearchTableViewCell.m +++ b/Riot/Modules/GlobalSearch/Files/Views/FilesSearchTableViewCell.m @@ -112,6 +112,9 @@ case MXKAttachmentTypeAudio: image = [UIImage imageNamed:@"file_music_icon"]; break; + case MXKAttachmentTypeVoiceMessage: + image = [UIImage imageNamed:@"file_music_icon"]; + break; case MXKAttachmentTypeVideo: image = [UIImage imageNamed:@"file_video_icon"]; break; diff --git a/Riot/Modules/GlobalSearch/Views/DirectoryRecentTableViewCell.m b/Riot/Modules/GlobalSearch/Views/DirectoryRecentTableViewCell.m index 63cdbfbce..26c103fb2 100644 --- a/Riot/Modules/GlobalSearch/Views/DirectoryRecentTableViewCell.m +++ b/Riot/Modules/GlobalSearch/Views/DirectoryRecentTableViewCell.m @@ -54,20 +54,20 @@ self.titleLabel.text = NSLocalizedStringFromTable(@"directory_search_results_title", @"Vector", nil); // Do we need to display like ">20 results found" or "18 results found"? - NSString *descriptionLabel = (publicRoomsDirectoryDataSource.moreThanRoomsCount && publicRoomsDirectoryDataSource.roomsCount > 0) ? NSLocalizedStringFromTable(@"directory_search_results_more_than", @"Vector", nil) : NSLocalizedStringFromTable(@"directory_search_results", @"Vector", nil); + NSString *descriptionLabel = (publicRoomsDirectoryDataSource.searchResultsCountIsLimited && publicRoomsDirectoryDataSource.searchResultsCount > 0) ? NSLocalizedStringFromTable(@"directory_search_results_more_than", @"Vector", nil) : NSLocalizedStringFromTable(@"directory_search_results", @"Vector", nil); self.descriptionLabel.text = [NSString stringWithFormat:descriptionLabel, - publicRoomsDirectoryDataSource.roomsCount, + publicRoomsDirectoryDataSource.searchResultsCount, publicRoomsDirectoryDataSource.searchPattern]; } else { self.titleLabel.text = NSLocalizedStringFromTable(@"directory_cell_title", @"Vector", nil); self.descriptionLabel.text = [NSString stringWithFormat:NSLocalizedStringFromTable(@"directory_cell_description", @"Vector", nil), - publicRoomsDirectoryDataSource.roomsCount]; + publicRoomsDirectoryDataSource.searchResultsCount]; } - if (publicRoomsDirectoryDataSource.roomsCount) + if (publicRoomsDirectoryDataSource.searchResultsCount) { self.userInteractionEnabled = YES; self.chevronImageView.hidden = NO; diff --git a/Riot/Modules/Home/HomeViewController.m b/Riot/Modules/Home/HomeViewController.m index d8d5382fc..74fef7d37 100644 --- a/Riot/Modules/Home/HomeViewController.m +++ b/Riot/Modules/Home/HomeViewController.m @@ -347,7 +347,16 @@ tableViewCell.notificationsButton.tag = room.isMute || room.isMentionsOnly; [tableViewCell.notificationsButton addTarget:self action:@selector(onNotificationsButtonPressed:) forControlEvents:UIControlEventTouchUpInside]; - tableViewCell.notificationsImageView.image = [UIImage imageNamed:@"room_action_notification"]; + + if ([BuildSettings roomSettingsScreenShowNotificationsV2]) + { + tableViewCell.notificationsImageView.image = tableViewCell.notificationsButton.tag ? [UIImage imageNamed:@"room_action_notification_muted"] : [UIImage imageNamed:@"room_action_notification"]; + } + else + { + tableViewCell.notificationsImageView.image = [UIImage imageNamed:@"room_action_notification"]; + } + tableViewCell.notificationsImageView.tintColor = tableViewCell.notificationsButton.tag ? unselectedColor : selectedColor; // Get the room tag (use only the first one). @@ -663,8 +672,15 @@ MXRoom *room = [self.mainSession roomWithRoomId:editedRoomId]; if (room) { - UIButton *button = (UIButton*)sender; - [self muteEditedRoomNotifications:!button.tag]; + if ([BuildSettings roomSettingsScreenShowNotificationsV2]) + { + [self changeEditedRoomNotificationSettings]; + } + else + { + UIButton *button = (UIButton*)sender; + [self muteEditedRoomNotifications:!button.tag]; + } } } } diff --git a/Riot/Modules/KeyBackup/Recover/KeyBackupRecoverCoordinatorType.swift b/Riot/Modules/KeyBackup/Recover/KeyBackupRecoverCoordinatorType.swift index 30d9c34d3..52bb891ea 100644 --- a/Riot/Modules/KeyBackup/Recover/KeyBackupRecoverCoordinatorType.swift +++ b/Riot/Modules/KeyBackup/Recover/KeyBackupRecoverCoordinatorType.swift @@ -16,7 +16,7 @@ import Foundation -protocol KeyBackupRecoverCoordinatorDelegate: class { +protocol KeyBackupRecoverCoordinatorDelegate: AnyObject { func keyBackupRecoverCoordinatorDidRecover(_ keyBackupRecoverCoordinator: KeyBackupRecoverCoordinatorType) func keyBackupRecoverCoordinatorDidCancel(_ keyBackupRecoverCoordinator: KeyBackupRecoverCoordinatorType) } diff --git a/Riot/Modules/KeyBackup/Recover/Passphrase/KeyBackupRecoverFromPassphraseCoordinatorType.swift b/Riot/Modules/KeyBackup/Recover/Passphrase/KeyBackupRecoverFromPassphraseCoordinatorType.swift index c33cdcf06..a3bc9030b 100644 --- a/Riot/Modules/KeyBackup/Recover/Passphrase/KeyBackupRecoverFromPassphraseCoordinatorType.swift +++ b/Riot/Modules/KeyBackup/Recover/Passphrase/KeyBackupRecoverFromPassphraseCoordinatorType.swift @@ -16,7 +16,7 @@ import Foundation -protocol KeyBackupRecoverFromPassphraseCoordinatorDelegate: class { +protocol KeyBackupRecoverFromPassphraseCoordinatorDelegate: AnyObject { func keyBackupRecoverFromPassphraseCoordinatorDidRecover(_ keyBackupRecoverFromPassphraseCoordinator: KeyBackupRecoverFromPassphraseCoordinatorType) func keyBackupRecoverFromPassphraseCoordinatorDoNotKnowPassphrase(_ keyBackupRecoverFromPassphraseCoordinator: KeyBackupRecoverFromPassphraseCoordinatorType) func keyBackupRecoverFromPassphraseCoordinatorDidCancel(_ keyBackupRecoverFromPassphraseCoordinator: KeyBackupRecoverFromPassphraseCoordinatorType) diff --git a/Riot/Modules/KeyBackup/Recover/Passphrase/KeyBackupRecoverFromPassphraseViewModelType.swift b/Riot/Modules/KeyBackup/Recover/Passphrase/KeyBackupRecoverFromPassphraseViewModelType.swift index 8b19bc589..69bc84b01 100644 --- a/Riot/Modules/KeyBackup/Recover/Passphrase/KeyBackupRecoverFromPassphraseViewModelType.swift +++ b/Riot/Modules/KeyBackup/Recover/Passphrase/KeyBackupRecoverFromPassphraseViewModelType.swift @@ -16,11 +16,11 @@ import Foundation -protocol KeyBackupRecoverFromPassphraseViewModelViewDelegate: class { +protocol KeyBackupRecoverFromPassphraseViewModelViewDelegate: AnyObject { func keyBackupRecoverFromPassphraseViewModel(_ viewModel: KeyBackupRecoverFromPassphraseViewModelType, didUpdateViewState viewSate: KeyBackupRecoverFromPassphraseViewState) } -protocol KeyBackupRecoverFromPassphraseViewModelCoordinatorDelegate: class { +protocol KeyBackupRecoverFromPassphraseViewModelCoordinatorDelegate: AnyObject { func keyBackupRecoverFromPassphraseViewModelDidRecover(_ viewModel: KeyBackupRecoverFromPassphraseViewModelType) func keyBackupRecoverFromPassphraseViewModelDidCancel(_ viewModel: KeyBackupRecoverFromPassphraseViewModelType) func keyBackupRecoverFromPassphraseViewModelDoNotKnowPassphrase(_ viewModel: KeyBackupRecoverFromPassphraseViewModelType) diff --git a/Riot/Modules/KeyBackup/Recover/PrivateKey/KeyBackupRecoverFromPrivateKeyCoordinatorType.swift b/Riot/Modules/KeyBackup/Recover/PrivateKey/KeyBackupRecoverFromPrivateKeyCoordinatorType.swift index aab042ba5..7146eeeae 100644 --- a/Riot/Modules/KeyBackup/Recover/PrivateKey/KeyBackupRecoverFromPrivateKeyCoordinatorType.swift +++ b/Riot/Modules/KeyBackup/Recover/PrivateKey/KeyBackupRecoverFromPrivateKeyCoordinatorType.swift @@ -18,7 +18,7 @@ import Foundation -protocol KeyBackupRecoverFromPrivateKeyCoordinatorDelegate: class { +protocol KeyBackupRecoverFromPrivateKeyCoordinatorDelegate: AnyObject { func keyBackupRecoverFromPrivateKeyCoordinatorDidRecover(_ coordinator: KeyBackupRecoverFromPrivateKeyCoordinatorType) func keyBackupRecoverFromPrivateKeyCoordinatorDidPrivateKeyFail(_ coordinator: KeyBackupRecoverFromPrivateKeyCoordinatorType) func keyBackupRecoverFromPrivateKeyCoordinatorDidCancel(_ coordinator: KeyBackupRecoverFromPrivateKeyCoordinatorType) diff --git a/Riot/Modules/KeyBackup/Recover/PrivateKey/KeyBackupRecoverFromPrivateKeyViewModelType.swift b/Riot/Modules/KeyBackup/Recover/PrivateKey/KeyBackupRecoverFromPrivateKeyViewModelType.swift index 1188e0560..396f401f1 100644 --- a/Riot/Modules/KeyBackup/Recover/PrivateKey/KeyBackupRecoverFromPrivateKeyViewModelType.swift +++ b/Riot/Modules/KeyBackup/Recover/PrivateKey/KeyBackupRecoverFromPrivateKeyViewModelType.swift @@ -18,11 +18,11 @@ import Foundation -protocol KeyBackupRecoverFromPrivateKeyViewModelViewDelegate: class { +protocol KeyBackupRecoverFromPrivateKeyViewModelViewDelegate: AnyObject { func keyBackupRecoverFromPrivateKeyViewModel(_ viewModel: KeyBackupRecoverFromPrivateKeyViewModelType, didUpdateViewState viewSate: KeyBackupRecoverFromPrivateKeyViewState) } -protocol KeyBackupRecoverFromPrivateKeyViewModelCoordinatorDelegate: class { +protocol KeyBackupRecoverFromPrivateKeyViewModelCoordinatorDelegate: AnyObject { func keyBackupRecoverFromPrivateKeyViewModelDidRecover(_ viewModel: KeyBackupRecoverFromPrivateKeyViewModelType) func keyBackupRecoverFromPrivateKeyViewModelDidPrivateKeyFail(_ viewModel: KeyBackupRecoverFromPrivateKeyViewModelType) func keyBackupRecoverFromPrivateKeyViewModelDidCancel(_ viewModel: KeyBackupRecoverFromPrivateKeyViewModelType) diff --git a/Riot/Modules/KeyBackup/Recover/RecoveryKey/KeyBackupRecoverFromRecoveryKeyCoordinatorType.swift b/Riot/Modules/KeyBackup/Recover/RecoveryKey/KeyBackupRecoverFromRecoveryKeyCoordinatorType.swift index 11d528b4f..c354fd13f 100644 --- a/Riot/Modules/KeyBackup/Recover/RecoveryKey/KeyBackupRecoverFromRecoveryKeyCoordinatorType.swift +++ b/Riot/Modules/KeyBackup/Recover/RecoveryKey/KeyBackupRecoverFromRecoveryKeyCoordinatorType.swift @@ -16,7 +16,7 @@ import Foundation -protocol KeyBackupRecoverFromRecoveryKeyCoordinatorDelegate: class { +protocol KeyBackupRecoverFromRecoveryKeyCoordinatorDelegate: AnyObject { func keyBackupRecoverFromPassphraseCoordinatorDidRecover(_ keyBackupRecoverFromRecoveryKeyCoordinator: KeyBackupRecoverFromRecoveryKeyCoordinatorType) func keyBackupRecoverFromPassphraseCoordinatorDidCancel(_ keyBackupRecoverFromRecoveryKeyCoordinator: KeyBackupRecoverFromRecoveryKeyCoordinatorType) } diff --git a/Riot/Modules/KeyBackup/Recover/RecoveryKey/KeyBackupRecoverFromRecoveryKeyViewModelType.swift b/Riot/Modules/KeyBackup/Recover/RecoveryKey/KeyBackupRecoverFromRecoveryKeyViewModelType.swift index e952c70a3..366d547d0 100644 --- a/Riot/Modules/KeyBackup/Recover/RecoveryKey/KeyBackupRecoverFromRecoveryKeyViewModelType.swift +++ b/Riot/Modules/KeyBackup/Recover/RecoveryKey/KeyBackupRecoverFromRecoveryKeyViewModelType.swift @@ -16,11 +16,11 @@ import Foundation -protocol KeyBackupRecoverFromRecoveryKeyViewModelViewDelegate: class { +protocol KeyBackupRecoverFromRecoveryKeyViewModelViewDelegate: AnyObject { func keyBackupRecoverFromPassphraseViewModel(_ viewModel: KeyBackupRecoverFromRecoveryKeyViewModelType, didUpdateViewState viewSate: KeyBackupRecoverFromRecoveryKeyViewState) } -protocol KeyBackupRecoverFromRecoveryKeyViewModelCoordinatorDelegate: class { +protocol KeyBackupRecoverFromRecoveryKeyViewModelCoordinatorDelegate: AnyObject { func keyBackupRecoverFromRecoveryKeyViewModelDidRecover(_ viewModel: KeyBackupRecoverFromRecoveryKeyViewModelType) func keyBackupRecoverFromRecoveryKeyViewModelDidCancel(_ viewModel: KeyBackupRecoverFromRecoveryKeyViewModelType) } diff --git a/Riot/Modules/KeyBackup/Recover/Success/KeyBackupRecoverSuccessViewController.swift b/Riot/Modules/KeyBackup/Recover/Success/KeyBackupRecoverSuccessViewController.swift index dd5a834da..8b58b241a 100644 --- a/Riot/Modules/KeyBackup/Recover/Success/KeyBackupRecoverSuccessViewController.swift +++ b/Riot/Modules/KeyBackup/Recover/Success/KeyBackupRecoverSuccessViewController.swift @@ -16,7 +16,7 @@ import UIKit -protocol KeyBackupRecoverSuccessViewControllerDelegate: class { +protocol KeyBackupRecoverSuccessViewControllerDelegate: AnyObject { func keyBackupRecoverSuccessViewControllerDidTapDone(_ keyBackupRecoverSuccessViewController: KeyBackupRecoverSuccessViewController) } diff --git a/Riot/Modules/KeyBackup/Setup/Intro/KeyBackupSetupIntroViewController.swift b/Riot/Modules/KeyBackup/Setup/Intro/KeyBackupSetupIntroViewController.swift index c968500ed..2f4284fbd 100644 --- a/Riot/Modules/KeyBackup/Setup/Intro/KeyBackupSetupIntroViewController.swift +++ b/Riot/Modules/KeyBackup/Setup/Intro/KeyBackupSetupIntroViewController.swift @@ -16,7 +16,7 @@ import UIKit -protocol KeyBackupSetupIntroViewControllerDelegate: class { +protocol KeyBackupSetupIntroViewControllerDelegate: AnyObject { func keyBackupSetupIntroViewControllerDidTapSetupAction(_ keyBackupSetupIntroViewController: KeyBackupSetupIntroViewController) func keyBackupSetupIntroViewControllerDidCancel(_ keyBackupSetupIntroViewController: KeyBackupSetupIntroViewController) } diff --git a/Riot/Modules/KeyBackup/Setup/KeyBackupSetupCoordinatorType.swift b/Riot/Modules/KeyBackup/Setup/KeyBackupSetupCoordinatorType.swift index f5a5dd0b0..612be7a1d 100644 --- a/Riot/Modules/KeyBackup/Setup/KeyBackupSetupCoordinatorType.swift +++ b/Riot/Modules/KeyBackup/Setup/KeyBackupSetupCoordinatorType.swift @@ -16,7 +16,7 @@ import Foundation -protocol KeyBackupSetupCoordinatorDelegate: class { +protocol KeyBackupSetupCoordinatorDelegate: AnyObject { func keyBackupSetupCoordinatorDidCancel(_ keyBackupSetupCoordinator: KeyBackupSetupCoordinatorType) func keyBackupSetupCoordinatorDidSetupRecoveryKey(_ keyBackupSetupCoordinator: KeyBackupSetupCoordinatorType) } diff --git a/Riot/Modules/KeyBackup/Setup/Passphrase/KeyBackupSetupPassphraseCoordinatorType.swift b/Riot/Modules/KeyBackup/Setup/Passphrase/KeyBackupSetupPassphraseCoordinatorType.swift index f94722adb..24403a387 100644 --- a/Riot/Modules/KeyBackup/Setup/Passphrase/KeyBackupSetupPassphraseCoordinatorType.swift +++ b/Riot/Modules/KeyBackup/Setup/Passphrase/KeyBackupSetupPassphraseCoordinatorType.swift @@ -16,7 +16,7 @@ import Foundation -protocol KeyBackupSetupPassphraseCoordinatorDelegate: class { +protocol KeyBackupSetupPassphraseCoordinatorDelegate: AnyObject { func keyBackupSetupPassphraseCoordinator(_ keyBackupSetupPassphraseCoordinator: KeyBackupSetupPassphraseCoordinatorType, didCreateBackupFromPassphraseWithResultingRecoveryKey recoveryKey: String) func keyBackupSetupPassphraseCoordinator(_ keyBackupSetupPassphraseCoordinator: KeyBackupSetupPassphraseCoordinatorType, didCreateBackupFromRecoveryKey recoveryKey: String) func keyBackupSetupPassphraseCoordinatorDidCancel(_ keyBackupSetupPassphraseCoordinator: KeyBackupSetupPassphraseCoordinatorType) diff --git a/Riot/Modules/KeyBackup/Setup/Passphrase/KeyBackupSetupPassphraseViewModelType.swift b/Riot/Modules/KeyBackup/Setup/Passphrase/KeyBackupSetupPassphraseViewModelType.swift index 2f4b2dfaa..89da31b4d 100644 --- a/Riot/Modules/KeyBackup/Setup/Passphrase/KeyBackupSetupPassphraseViewModelType.swift +++ b/Riot/Modules/KeyBackup/Setup/Passphrase/KeyBackupSetupPassphraseViewModelType.swift @@ -16,12 +16,12 @@ import Foundation -protocol KeyBackupSetupPassphraseViewModelViewDelegate: class { +protocol KeyBackupSetupPassphraseViewModelViewDelegate: AnyObject { func keyBackupSetupPassphraseViewModel(_ viewModel: KeyBackupSetupPassphraseViewModelType, didUpdateViewState viewSate: KeyBackupSetupPassphraseViewState) func keyBackupSetupPassphraseViewModelShowSkipAlert(_ viewModel: KeyBackupSetupPassphraseViewModelType) } -protocol KeyBackupSetupPassphraseViewModelCoordinatorDelegate: class { +protocol KeyBackupSetupPassphraseViewModelCoordinatorDelegate: AnyObject { func keyBackupSetupPassphraseViewModel(_ viewModel: KeyBackupSetupPassphraseViewModelType, didCreateBackupFromPassphraseWithResultingRecoveryKey recoveryKey: String) func keyBackupSetupPassphraseViewModel(_ viewModel: KeyBackupSetupPassphraseViewModelType, didCreateBackupFromRecoveryKey recoveryKey: String) func keyBackupSetupPassphraseViewModelDidCancel(_ viewModel: KeyBackupSetupPassphraseViewModelType) diff --git a/Riot/Modules/KeyBackup/Setup/Success/KeyBackupSetupSuccessFromPassphraseViewController.swift b/Riot/Modules/KeyBackup/Setup/Success/KeyBackupSetupSuccessFromPassphraseViewController.swift index 57fe04ba9..563d4b0b6 100644 --- a/Riot/Modules/KeyBackup/Setup/Success/KeyBackupSetupSuccessFromPassphraseViewController.swift +++ b/Riot/Modules/KeyBackup/Setup/Success/KeyBackupSetupSuccessFromPassphraseViewController.swift @@ -16,7 +16,7 @@ import UIKit -protocol KeyBackupSetupSuccessFromPassphraseViewControllerDelegate: class { +protocol KeyBackupSetupSuccessFromPassphraseViewControllerDelegate: AnyObject { func keyBackupSetupSuccessFromPassphraseViewControllerDidTapDoneAction(_ viewController: KeyBackupSetupSuccessFromPassphraseViewController) } diff --git a/Riot/Modules/KeyBackup/Setup/Success/KeyBackupSetupSuccessFromRecoveryKeyViewController.swift b/Riot/Modules/KeyBackup/Setup/Success/KeyBackupSetupSuccessFromRecoveryKeyViewController.swift index 6f474f753..0c33c5915 100644 --- a/Riot/Modules/KeyBackup/Setup/Success/KeyBackupSetupSuccessFromRecoveryKeyViewController.swift +++ b/Riot/Modules/KeyBackup/Setup/Success/KeyBackupSetupSuccessFromRecoveryKeyViewController.swift @@ -16,7 +16,7 @@ import UIKit -protocol KeyBackupSetupSuccessFromRecoveryKeyViewControllerDelegate: class { +protocol KeyBackupSetupSuccessFromRecoveryKeyViewControllerDelegate: AnyObject { func keyBackupSetupSuccessFromRecoveryKeyViewControllerDidTapDoneAction(_ viewController: KeyBackupSetupSuccessFromRecoveryKeyViewController) } diff --git a/Riot/Modules/KeyBackup/Setup/Success/KeyBackupSetupSuccessFromSecureBackupViewController.swift b/Riot/Modules/KeyBackup/Setup/Success/KeyBackupSetupSuccessFromSecureBackupViewController.swift index c369cb5e9..dcd23e6d6 100644 --- a/Riot/Modules/KeyBackup/Setup/Success/KeyBackupSetupSuccessFromSecureBackupViewController.swift +++ b/Riot/Modules/KeyBackup/Setup/Success/KeyBackupSetupSuccessFromSecureBackupViewController.swift @@ -16,7 +16,7 @@ import UIKit -protocol KeyBackupSetupSuccessFromSecureBackupViewControllerDelegate: class { +protocol KeyBackupSetupSuccessFromSecureBackupViewControllerDelegate: AnyObject { func keyBackupSetupSuccessFromSecureBackupViewControllerDidTapDoneAction(_ viewController: KeyBackupSetupSuccessFromSecureBackupViewController) } diff --git a/Riot/Modules/KeyVerification/Common/KeyVerificationCoordinatorType.swift b/Riot/Modules/KeyVerification/Common/KeyVerificationCoordinatorType.swift index 901d6b11d..ca1731afd 100644 --- a/Riot/Modules/KeyVerification/Common/KeyVerificationCoordinatorType.swift +++ b/Riot/Modules/KeyVerification/Common/KeyVerificationCoordinatorType.swift @@ -18,7 +18,7 @@ import Foundation -protocol KeyVerificationCoordinatorDelegate: class { +protocol KeyVerificationCoordinatorDelegate: AnyObject { func keyVerificationCoordinatorDidComplete(_ coordinator: KeyVerificationCoordinatorType, otherUserId: String, otherDeviceId: String) func keyVerificationCoordinatorDidCancel(_ coordinator: KeyVerificationCoordinatorType) } diff --git a/Riot/Modules/KeyVerification/Common/Loading/KeyVerificationDataLoadingCoordinatorType.swift b/Riot/Modules/KeyVerification/Common/Loading/KeyVerificationDataLoadingCoordinatorType.swift index 8a7ba1dae..f0ff8bfa2 100644 --- a/Riot/Modules/KeyVerification/Common/Loading/KeyVerificationDataLoadingCoordinatorType.swift +++ b/Riot/Modules/KeyVerification/Common/Loading/KeyVerificationDataLoadingCoordinatorType.swift @@ -18,7 +18,7 @@ import Foundation -protocol KeyVerificationDataLoadingCoordinatorDelegate: class { +protocol KeyVerificationDataLoadingCoordinatorDelegate: AnyObject { func keyVerificationDataLoadingCoordinator(_ coordinator: KeyVerificationDataLoadingCoordinatorType, didLoadUser user: MXUser, device: MXDeviceInfo) func keyVerificationDataLoadingCoordinator(_ coordinator: KeyVerificationDataLoadingCoordinatorType, didAcceptKeyVerificationRequestWithTransaction transaction: MXKeyVerificationTransaction) func keyVerificationDataLoadingCoordinator(_ coordinator: KeyVerificationDataLoadingCoordinatorType, didAcceptKeyVerificationRequest keyVerificationRequest: MXKeyVerificationRequest) diff --git a/Riot/Modules/KeyVerification/Common/Loading/KeyVerificationDataLoadingViewModelType.swift b/Riot/Modules/KeyVerification/Common/Loading/KeyVerificationDataLoadingViewModelType.swift index d55bcc3bd..223ce6f50 100644 --- a/Riot/Modules/KeyVerification/Common/Loading/KeyVerificationDataLoadingViewModelType.swift +++ b/Riot/Modules/KeyVerification/Common/Loading/KeyVerificationDataLoadingViewModelType.swift @@ -18,11 +18,11 @@ import Foundation -protocol KeyVerificationDataLoadingViewModelViewDelegate: class { +protocol KeyVerificationDataLoadingViewModelViewDelegate: AnyObject { func keyVerificationDataLoadingViewModel(_ viewModel: KeyVerificationDataLoadingViewModelType, didUpdateViewState viewSate: KeyVerificationDataLoadingViewState) } -protocol KeyVerificationDataLoadingViewModelCoordinatorDelegate: class { +protocol KeyVerificationDataLoadingViewModelCoordinatorDelegate: AnyObject { func keyVerificationDataLoadingViewModel(_ viewModel: KeyVerificationDataLoadingViewModelType, didLoadUser user: MXUser, device: MXDeviceInfo) func keyVerificationDataLoadingViewModel(_ viewModel: KeyVerificationDataLoadingViewModelType, didAcceptKeyVerificationWithTransaction transaction: MXKeyVerificationTransaction) func keyVerificationDataLoadingViewModel(_ viewModel: KeyVerificationDataLoadingViewModelType, didAcceptKeyVerificationRequest keyVerificationRequest: MXKeyVerificationRequest) diff --git a/Riot/Modules/KeyVerification/Common/ScanConfirmation/KeyVerificationScanConfirmationCoordinatorType.swift b/Riot/Modules/KeyVerification/Common/ScanConfirmation/KeyVerificationScanConfirmationCoordinatorType.swift index 35a2381c4..8087fadb2 100644 --- a/Riot/Modules/KeyVerification/Common/ScanConfirmation/KeyVerificationScanConfirmationCoordinatorType.swift +++ b/Riot/Modules/KeyVerification/Common/ScanConfirmation/KeyVerificationScanConfirmationCoordinatorType.swift @@ -18,7 +18,7 @@ import Foundation -protocol KeyVerificationScanConfirmationCoordinatorDelegate: class { +protocol KeyVerificationScanConfirmationCoordinatorDelegate: AnyObject { func keyVerificationScanConfirmationCoordinatorDidComplete(_ coordinator: KeyVerificationScanConfirmationCoordinatorType) func keyVerificationScanConfirmationCoordinatorDidCancel(_ coordinator: KeyVerificationScanConfirmationCoordinatorType) } diff --git a/Riot/Modules/KeyVerification/Common/ScanConfirmation/KeyVerificationScanConfirmationViewModelType.swift b/Riot/Modules/KeyVerification/Common/ScanConfirmation/KeyVerificationScanConfirmationViewModelType.swift index 03d843560..fc11ea2bd 100644 --- a/Riot/Modules/KeyVerification/Common/ScanConfirmation/KeyVerificationScanConfirmationViewModelType.swift +++ b/Riot/Modules/KeyVerification/Common/ScanConfirmation/KeyVerificationScanConfirmationViewModelType.swift @@ -18,11 +18,11 @@ import Foundation -protocol KeyVerificationScanConfirmationViewModelViewDelegate: class { +protocol KeyVerificationScanConfirmationViewModelViewDelegate: AnyObject { func keyVerificationScanConfirmationViewModel(_ viewModel: KeyVerificationScanConfirmationViewModelType, didUpdateViewState viewSate: KeyVerificationScanConfirmationViewState) } -protocol KeyVerificationScanConfirmationViewModelCoordinatorDelegate: class { +protocol KeyVerificationScanConfirmationViewModelCoordinatorDelegate: AnyObject { func keyVerificationScanConfirmationViewModelDidComplete(_ viewModel: KeyVerificationScanConfirmationViewModelType) func keyVerificationScanConfirmationViewModelDidCancel(_ viewModel: KeyVerificationScanConfirmationViewModelType) } diff --git a/Riot/Modules/KeyVerification/Common/Verified/KeyVerificationVerifiedViewController.swift b/Riot/Modules/KeyVerification/Common/Verified/KeyVerificationVerifiedViewController.swift index ed869052f..32cd0cf6a 100644 --- a/Riot/Modules/KeyVerification/Common/Verified/KeyVerificationVerifiedViewController.swift +++ b/Riot/Modules/KeyVerification/Common/Verified/KeyVerificationVerifiedViewController.swift @@ -18,7 +18,7 @@ import UIKit -protocol KeyVerificationVerifiedViewControllerDelegate: class { +protocol KeyVerificationVerifiedViewControllerDelegate: AnyObject { func keyVerificationVerifiedViewControllerDidTapSetupAction(_ viewController: KeyVerificationVerifiedViewController) func keyVerificationVerifiedViewControllerDidCancel(_ viewController: KeyVerificationVerifiedViewController) } diff --git a/Riot/Modules/KeyVerification/Common/Verify/SAS/KeyVerificationVerifyBySASCoordinatorType.swift b/Riot/Modules/KeyVerification/Common/Verify/SAS/KeyVerificationVerifyBySASCoordinatorType.swift index 378e2517e..77ac567ff 100644 --- a/Riot/Modules/KeyVerification/Common/Verify/SAS/KeyVerificationVerifyBySASCoordinatorType.swift +++ b/Riot/Modules/KeyVerification/Common/Verify/SAS/KeyVerificationVerifyBySASCoordinatorType.swift @@ -18,7 +18,7 @@ import Foundation -protocol KeyVerificationVerifyBySASCoordinatorDelegate: class { +protocol KeyVerificationVerifyBySASCoordinatorDelegate: AnyObject { func keyVerificationVerifyBySASCoordinatorDidComplete(_ coordinator: KeyVerificationVerifyBySASCoordinatorType) func keyVerificationVerifyBySASCoordinatorDidCancel(_ coordinator: KeyVerificationVerifyBySASCoordinatorType) } diff --git a/Riot/Modules/KeyVerification/Common/Verify/SAS/KeyVerificationVerifyBySASViewModelType.swift b/Riot/Modules/KeyVerification/Common/Verify/SAS/KeyVerificationVerifyBySASViewModelType.swift index a5686bc7a..10c26756a 100644 --- a/Riot/Modules/KeyVerification/Common/Verify/SAS/KeyVerificationVerifyBySASViewModelType.swift +++ b/Riot/Modules/KeyVerification/Common/Verify/SAS/KeyVerificationVerifyBySASViewModelType.swift @@ -18,11 +18,11 @@ import Foundation -protocol KeyVerificationVerifyBySASViewModelViewDelegate: class { +protocol KeyVerificationVerifyBySASViewModelViewDelegate: AnyObject { func keyVerificationVerifyBySASViewModel(_ viewModel: KeyVerificationVerifyBySASViewModelType, didUpdateViewState viewSate: KeyVerificationVerifyViewState) } -protocol KeyVerificationVerifyBySASViewModelCoordinatorDelegate: class { +protocol KeyVerificationVerifyBySASViewModelCoordinatorDelegate: AnyObject { func keyVerificationVerifyViewModelDidComplete(_ viewModel: KeyVerificationVerifyBySASViewModelType) func keyVerificationVerifyViewModelDidCancel(_ viewModel: KeyVerificationVerifyBySASViewModelType) } diff --git a/Riot/Modules/KeyVerification/Common/Verify/Scanning/KeyVerificationVerifyByScanningCoordinatorType.swift b/Riot/Modules/KeyVerification/Common/Verify/Scanning/KeyVerificationVerifyByScanningCoordinatorType.swift index 0bf2711f6..5df1ebde6 100644 --- a/Riot/Modules/KeyVerification/Common/Verify/Scanning/KeyVerificationVerifyByScanningCoordinatorType.swift +++ b/Riot/Modules/KeyVerification/Common/Verify/Scanning/KeyVerificationVerifyByScanningCoordinatorType.swift @@ -18,7 +18,7 @@ import Foundation -protocol KeyVerificationVerifyByScanningCoordinatorDelegate: class { +protocol KeyVerificationVerifyByScanningCoordinatorDelegate: AnyObject { func keyVerificationVerifyByScanningCoordinatorDidCancel(_ coordinator: KeyVerificationVerifyByScanningCoordinatorType) func keyVerificationVerifyByScanningCoordinator(_ coordinator: KeyVerificationVerifyByScanningCoordinatorType, didScanOtherQRCodeData qrCodeData: MXQRCodeData, withTransaction transaction: MXQRCodeTransaction) func keyVerificationVerifyByScanningCoordinator(_ coordinator: KeyVerificationVerifyByScanningCoordinatorType, qrCodeDidScannedByOtherWithTransaction transaction: MXQRCodeTransaction) diff --git a/Riot/Modules/KeyVerification/Common/Verify/Scanning/KeyVerificationVerifyByScanningViewController.swift b/Riot/Modules/KeyVerification/Common/Verify/Scanning/KeyVerificationVerifyByScanningViewController.swift index fc01906da..49f1a80ca 100644 --- a/Riot/Modules/KeyVerification/Common/Verify/Scanning/KeyVerificationVerifyByScanningViewController.swift +++ b/Riot/Modules/KeyVerification/Common/Verify/Scanning/KeyVerificationVerifyByScanningViewController.swift @@ -143,7 +143,9 @@ final class KeyVerificationVerifyByScanningViewController: UIViewController { self.titleLabel.text = VectorL10n.keyVerificationVerifyQrCodeTitle self.informationLabel.text = VectorL10n.keyVerificationVerifyQrCodeInformation - self.scanCodeButton.setTitle(VectorL10n.keyVerificationVerifyQrCodeScanCodeAction, for: .normal) + // Hide until we have the type of the verification request + self.scanCodeButton.isHidden = true + self.cannotScanButton.setTitle(VectorL10n.keyVerificationVerifyQrCodeCannotScanAction, for: .normal) } @@ -157,8 +159,8 @@ final class KeyVerificationVerifyByScanningViewController: UIViewController { self.render(error: error) case .scannedCodeValidated(let isValid): self.renderScannedCode(valid: isValid) - case .cancelled(let reason): - self.renderCancelled(reason: reason) + case .cancelled(let reason, let verificationKind): + self.renderCancelled(reason: reason, verificationKind: verificationKind) case .cancelledByMe(let reason): self.renderCancelledByMe(reason: reason) } @@ -195,10 +197,13 @@ final class KeyVerificationVerifyByScanningViewController: UIViewController { switch viewData.verificationKind { case .user: informationText = VectorL10n.keyVerificationVerifyQrCodeInformation + self.scanCodeButton.setTitle(VectorL10n.keyVerificationVerifyQrCodeScanCodeAction, for: .normal) default: informationText = VectorL10n.keyVerificationVerifyQrCodeInformationOtherDevice + self.scanCodeButton.setTitle(VectorL10n.keyVerificationVerifyQrCodeScanCodeOtherDeviceAction, for: .normal) } + self.scanCodeButton.isHidden = false self.informationLabel.text = informationText } } @@ -231,12 +236,21 @@ final class KeyVerificationVerifyByScanningViewController: UIViewController { } } - private func renderCancelled(reason: MXTransactionCancelCode) { + private func renderCancelled(reason: MXTransactionCancelCode, + verificationKind: KeyVerificationKind) { self.activityPresenter.removeCurrentActivityIndicator(animated: true) self.stopQRCodeScanningIfPresented() - self.errorPresenter.presentError(from: self.alertPresentingViewController, title: "", message: VectorL10n.deviceVerificationCancelled, animated: true) { + // if we're verifying with someone else, let the user know they cancelled. + // if we're verifying our own device, assume the user probably knows since it was them who + // cancelled on their other device + if verificationKind == .user { + self.errorPresenter.presentError(from: self.alertPresentingViewController, title: "", message: VectorL10n.deviceVerificationCancelled, animated: true) { + self.dismissQRCodeScanningIfPresented(animated: false) + self.viewModel.process(viewAction: .cancel) + } + } else { self.dismissQRCodeScanningIfPresented(animated: false) self.viewModel.process(viewAction: .cancel) } diff --git a/Riot/Modules/KeyVerification/Common/Verify/Scanning/KeyVerificationVerifyByScanningViewModel.swift b/Riot/Modules/KeyVerification/Common/Verify/Scanning/KeyVerificationVerifyByScanningViewModel.swift index 706a121d9..de4b9e189 100644 --- a/Riot/Modules/KeyVerification/Common/Verify/Scanning/KeyVerificationVerifyByScanningViewModel.swift +++ b/Riot/Modules/KeyVerification/Common/Verify/Scanning/KeyVerificationVerifyByScanningViewModel.swift @@ -225,7 +225,7 @@ final class KeyVerificationVerifyByScanningViewModel: KeyVerificationVerifyBySca return } self.unregisterTransactionDidStateChangeNotification() - self.update(viewState: .cancelled(reason)) + self.update(viewState: .cancelled(cancelCode: reason, verificationKind: verificationKind)) case MXSASTransactionStateCancelledByMe: guard let reason = transaction.reasonCancelCode else { return @@ -251,7 +251,7 @@ final class KeyVerificationVerifyByScanningViewModel: KeyVerificationVerifyBySca return } self.unregisterTransactionDidStateChangeNotification() - self.update(viewState: .cancelled(reason)) + self.update(viewState: .cancelled(cancelCode: reason, verificationKind: verificationKind)) case .cancelledByMe: guard let reason = transaction.reasonCancelCode else { return diff --git a/Riot/Modules/KeyVerification/Common/Verify/Scanning/KeyVerificationVerifyByScanningViewModelType.swift b/Riot/Modules/KeyVerification/Common/Verify/Scanning/KeyVerificationVerifyByScanningViewModelType.swift index 4be0568d5..d7eedfabc 100644 --- a/Riot/Modules/KeyVerification/Common/Verify/Scanning/KeyVerificationVerifyByScanningViewModelType.swift +++ b/Riot/Modules/KeyVerification/Common/Verify/Scanning/KeyVerificationVerifyByScanningViewModelType.swift @@ -18,11 +18,11 @@ import Foundation -protocol KeyVerificationVerifyByScanningViewModelViewDelegate: class { +protocol KeyVerificationVerifyByScanningViewModelViewDelegate: AnyObject { func keyVerificationVerifyByScanningViewModel(_ viewModel: KeyVerificationVerifyByScanningViewModelType, didUpdateViewState viewSate: KeyVerificationVerifyByScanningViewState) } -protocol KeyVerificationVerifyByScanningViewModelCoordinatorDelegate: class { +protocol KeyVerificationVerifyByScanningViewModelCoordinatorDelegate: AnyObject { func keyVerificationVerifyByScanningViewModelDidCancel(_ viewModel: KeyVerificationVerifyByScanningViewModelType) func keyVerificationVerifyByScanningViewModel(_ viewModel: KeyVerificationVerifyByScanningViewModelType, didScanOtherQRCodeData qrCodeData: MXQRCodeData, withTransaction transaction: MXQRCodeTransaction) diff --git a/Riot/Modules/KeyVerification/Common/Verify/Scanning/KeyVerificationVerifyByScanningViewState.swift b/Riot/Modules/KeyVerification/Common/Verify/Scanning/KeyVerificationVerifyByScanningViewState.swift index b6323eb70..e3ecc8238 100644 --- a/Riot/Modules/KeyVerification/Common/Verify/Scanning/KeyVerificationVerifyByScanningViewState.swift +++ b/Riot/Modules/KeyVerification/Common/Verify/Scanning/KeyVerificationVerifyByScanningViewState.swift @@ -29,7 +29,7 @@ enum KeyVerificationVerifyByScanningViewState { case loading case loaded(viewData: KeyVerificationVerifyByScanningViewData) case scannedCodeValidated(isValid: Bool) - case cancelled(MXTransactionCancelCode) + case cancelled(cancelCode: MXTransactionCancelCode, verificationKind: KeyVerificationKind) case cancelledByMe(MXTransactionCancelCode) case error(Error) } diff --git a/Riot/Modules/KeyVerification/Device/Incoming/DeviceVerificationIncomingCoordinatorType.swift b/Riot/Modules/KeyVerification/Device/Incoming/DeviceVerificationIncomingCoordinatorType.swift index 46e135212..1d5e600ab 100644 --- a/Riot/Modules/KeyVerification/Device/Incoming/DeviceVerificationIncomingCoordinatorType.swift +++ b/Riot/Modules/KeyVerification/Device/Incoming/DeviceVerificationIncomingCoordinatorType.swift @@ -18,7 +18,7 @@ import Foundation -protocol DeviceVerificationIncomingCoordinatorDelegate: class { +protocol DeviceVerificationIncomingCoordinatorDelegate: AnyObject { func deviceVerificationIncomingCoordinator(_ coordinator: DeviceVerificationIncomingCoordinatorType, didAcceptTransaction message: MXSASTransaction) func deviceVerificationIncomingCoordinatorDidCancel(_ coordinator: DeviceVerificationIncomingCoordinatorType) } diff --git a/Riot/Modules/KeyVerification/Device/Incoming/DeviceVerificationIncomingViewModelType.swift b/Riot/Modules/KeyVerification/Device/Incoming/DeviceVerificationIncomingViewModelType.swift index 465fb4013..48b148aa1 100644 --- a/Riot/Modules/KeyVerification/Device/Incoming/DeviceVerificationIncomingViewModelType.swift +++ b/Riot/Modules/KeyVerification/Device/Incoming/DeviceVerificationIncomingViewModelType.swift @@ -18,11 +18,11 @@ import Foundation -protocol DeviceVerificationIncomingViewModelViewDelegate: class { +protocol DeviceVerificationIncomingViewModelViewDelegate: AnyObject { func deviceVerificationIncomingViewModel(_ viewModel: DeviceVerificationIncomingViewModelType, didUpdateViewState viewSate: DeviceVerificationIncomingViewState) } -protocol DeviceVerificationIncomingViewModelCoordinatorDelegate: class { +protocol DeviceVerificationIncomingViewModelCoordinatorDelegate: AnyObject { func deviceVerificationIncomingViewModel(_ viewModel: DeviceVerificationIncomingViewModelType, didAcceptTransaction transaction: MXSASTransaction) func deviceVerificationIncomingViewModelDidCancel(_ viewModel: DeviceVerificationIncomingViewModelType) } diff --git a/Riot/Modules/KeyVerification/Device/ManuallyVerify/KeyVerificationManuallyVerifyCoordinatorType.swift b/Riot/Modules/KeyVerification/Device/ManuallyVerify/KeyVerificationManuallyVerifyCoordinatorType.swift index 7f37b9564..ef098fcd2 100644 --- a/Riot/Modules/KeyVerification/Device/ManuallyVerify/KeyVerificationManuallyVerifyCoordinatorType.swift +++ b/Riot/Modules/KeyVerification/Device/ManuallyVerify/KeyVerificationManuallyVerifyCoordinatorType.swift @@ -18,7 +18,7 @@ import Foundation -protocol KeyVerificationManuallyVerifyCoordinatorDelegate: class { +protocol KeyVerificationManuallyVerifyCoordinatorDelegate: AnyObject { func keyVerificationManuallyVerifyCoordinator(_ coordinator: KeyVerificationManuallyVerifyCoordinatorType, didVerifiedDeviceWithId deviceId: String, of userId: String) func keyVerificationManuallyVerifyCoordinatorDidCancel(_ coordinator: KeyVerificationManuallyVerifyCoordinatorType) } diff --git a/Riot/Modules/KeyVerification/Device/ManuallyVerify/KeyVerificationManuallyVerifyViewModelType.swift b/Riot/Modules/KeyVerification/Device/ManuallyVerify/KeyVerificationManuallyVerifyViewModelType.swift index 49f1972a5..1f1a73a57 100644 --- a/Riot/Modules/KeyVerification/Device/ManuallyVerify/KeyVerificationManuallyVerifyViewModelType.swift +++ b/Riot/Modules/KeyVerification/Device/ManuallyVerify/KeyVerificationManuallyVerifyViewModelType.swift @@ -18,11 +18,11 @@ import Foundation -protocol KeyVerificationManuallyVerifyViewModelViewDelegate: class { +protocol KeyVerificationManuallyVerifyViewModelViewDelegate: AnyObject { func keyVerificationManuallyVerifyViewModel(_ viewModel: KeyVerificationManuallyVerifyViewModelType, didUpdateViewState viewSate: KeyVerificationManuallyVerifyViewState) } -protocol KeyVerificationManuallyVerifyViewModelCoordinatorDelegate: class { +protocol KeyVerificationManuallyVerifyViewModelCoordinatorDelegate: AnyObject { func keyVerificationManuallyVerifyViewModel(_ viewModel: KeyVerificationManuallyVerifyViewModelType, didVerifiedDeviceWithId deviceId: String, of userId: String) func keyVerificationManuallyVerifyViewModelDidCancel(_ viewModel: KeyVerificationManuallyVerifyViewModelType) } diff --git a/Riot/Modules/KeyVerification/Device/SelfVerifyStart/KeyVerificationSelfVerifyStartCoordinatorType.swift b/Riot/Modules/KeyVerification/Device/SelfVerifyStart/KeyVerificationSelfVerifyStartCoordinatorType.swift index faa2a8555..6799602d3 100644 --- a/Riot/Modules/KeyVerification/Device/SelfVerifyStart/KeyVerificationSelfVerifyStartCoordinatorType.swift +++ b/Riot/Modules/KeyVerification/Device/SelfVerifyStart/KeyVerificationSelfVerifyStartCoordinatorType.swift @@ -18,7 +18,7 @@ import Foundation -protocol KeyVerificationSelfVerifyStartCoordinatorDelegate: class { +protocol KeyVerificationSelfVerifyStartCoordinatorDelegate: AnyObject { func keyVerificationSelfVerifyStartCoordinator(_ coordinator: KeyVerificationSelfVerifyStartCoordinatorType, otherDidAcceptRequest request: MXKeyVerificationRequest) diff --git a/Riot/Modules/KeyVerification/Device/SelfVerifyStart/KeyVerificationSelfVerifyStartViewModelType.swift b/Riot/Modules/KeyVerification/Device/SelfVerifyStart/KeyVerificationSelfVerifyStartViewModelType.swift index 0de1eccf5..2f70b5a31 100644 --- a/Riot/Modules/KeyVerification/Device/SelfVerifyStart/KeyVerificationSelfVerifyStartViewModelType.swift +++ b/Riot/Modules/KeyVerification/Device/SelfVerifyStart/KeyVerificationSelfVerifyStartViewModelType.swift @@ -18,11 +18,11 @@ import Foundation -protocol KeyVerificationSelfVerifyStartViewModelViewDelegate: class { +protocol KeyVerificationSelfVerifyStartViewModelViewDelegate: AnyObject { func keyVerificationSelfVerifyStartViewModel(_ viewModel: KeyVerificationSelfVerifyStartViewModelType, didUpdateViewState viewSate: KeyVerificationSelfVerifyStartViewState) } -protocol KeyVerificationSelfVerifyStartViewModelCoordinatorDelegate: class { +protocol KeyVerificationSelfVerifyStartViewModelCoordinatorDelegate: AnyObject { func keyVerificationSelfVerifyStartViewModel(_ viewModel: KeyVerificationSelfVerifyStartViewModelType, otherDidAcceptRequest request: MXKeyVerificationRequest) func keyVerificationSelfVerifyStartViewModelDidCancel(_ viewModel: KeyVerificationSelfVerifyStartViewModelType) } diff --git a/Riot/Modules/KeyVerification/Device/SelfVerifyWait/KeyVerificationSelfVerifyWaitCoordinatorType.swift b/Riot/Modules/KeyVerification/Device/SelfVerifyWait/KeyVerificationSelfVerifyWaitCoordinatorType.swift index 6c0219c4a..ba1b6c410 100644 --- a/Riot/Modules/KeyVerification/Device/SelfVerifyWait/KeyVerificationSelfVerifyWaitCoordinatorType.swift +++ b/Riot/Modules/KeyVerification/Device/SelfVerifyWait/KeyVerificationSelfVerifyWaitCoordinatorType.swift @@ -18,7 +18,7 @@ import Foundation -protocol KeyVerificationSelfVerifyWaitCoordinatorDelegate: class { +protocol KeyVerificationSelfVerifyWaitCoordinatorDelegate: AnyObject { func keyVerificationSelfVerifyWaitCoordinator(_ coordinator: KeyVerificationSelfVerifyWaitCoordinatorType, didAcceptKeyVerificationRequest keyVerificationRequest: MXKeyVerificationRequest) func keyVerificationSelfVerifyWaitCoordinator(_ coordinator: KeyVerificationSelfVerifyWaitCoordinatorType, didAcceptIncomingSASTransaction incomingSASTransaction: MXIncomingSASTransaction) func keyVerificationSelfVerifyWaitCoordinatorDidCancel(_ coordinator: KeyVerificationSelfVerifyWaitCoordinatorType) diff --git a/Riot/Modules/KeyVerification/Device/SelfVerifyWait/KeyVerificationSelfVerifyWaitViewModelType.swift b/Riot/Modules/KeyVerification/Device/SelfVerifyWait/KeyVerificationSelfVerifyWaitViewModelType.swift index c6fc85ff0..264a73b83 100644 --- a/Riot/Modules/KeyVerification/Device/SelfVerifyWait/KeyVerificationSelfVerifyWaitViewModelType.swift +++ b/Riot/Modules/KeyVerification/Device/SelfVerifyWait/KeyVerificationSelfVerifyWaitViewModelType.swift @@ -18,11 +18,11 @@ import Foundation -protocol KeyVerificationSelfVerifyWaitViewModelViewDelegate: class { +protocol KeyVerificationSelfVerifyWaitViewModelViewDelegate: AnyObject { func keyVerificationSelfVerifyWaitViewModel(_ viewModel: KeyVerificationSelfVerifyWaitViewModelType, didUpdateViewState viewSate: KeyVerificationSelfVerifyWaitViewState) } -protocol KeyVerificationSelfVerifyWaitViewModelCoordinatorDelegate: class { +protocol KeyVerificationSelfVerifyWaitViewModelCoordinatorDelegate: AnyObject { func keyVerificationSelfVerifyWaitViewModel(_ viewModel: KeyVerificationSelfVerifyWaitViewModelType, didAcceptKeyVerificationRequest keyVerificationRequest: MXKeyVerificationRequest) func keyVerificationSelfVerifyWaitViewModel(_ viewModel: KeyVerificationSelfVerifyWaitViewModelType, didAcceptIncomingSASTransaction incomingSASTransaction: MXIncomingSASTransaction) func keyVerificationSelfVerifyWaitViewModelDidCancel(_ viewModel: KeyVerificationSelfVerifyWaitViewModelType) diff --git a/Riot/Modules/KeyVerification/Device/Start/DeviceVerificationStartCoordinatorType.swift b/Riot/Modules/KeyVerification/Device/Start/DeviceVerificationStartCoordinatorType.swift index 72871ee60..16a79760c 100644 --- a/Riot/Modules/KeyVerification/Device/Start/DeviceVerificationStartCoordinatorType.swift +++ b/Riot/Modules/KeyVerification/Device/Start/DeviceVerificationStartCoordinatorType.swift @@ -18,7 +18,7 @@ import Foundation -protocol DeviceVerificationStartCoordinatorDelegate: class { +protocol DeviceVerificationStartCoordinatorDelegate: AnyObject { func deviceVerificationStartCoordinator(_ coordinator: DeviceVerificationStartCoordinatorType, didCompleteWithOutgoingTransaction transaction: MXSASTransaction) func deviceVerificationStartCoordinator(_ coordinator: DeviceVerificationStartCoordinatorType, didTransactionCancelled transaction: MXSASTransaction) diff --git a/Riot/Modules/KeyVerification/Device/Start/DeviceVerificationStartViewModelType.swift b/Riot/Modules/KeyVerification/Device/Start/DeviceVerificationStartViewModelType.swift index 07da5a197..015e80faf 100644 --- a/Riot/Modules/KeyVerification/Device/Start/DeviceVerificationStartViewModelType.swift +++ b/Riot/Modules/KeyVerification/Device/Start/DeviceVerificationStartViewModelType.swift @@ -18,11 +18,11 @@ import Foundation -protocol DeviceVerificationStartViewModelViewDelegate: class { +protocol DeviceVerificationStartViewModelViewDelegate: AnyObject { func deviceVerificationStartViewModel(_ viewModel: DeviceVerificationStartViewModelType, didUpdateViewState viewSate: DeviceVerificationStartViewState) } -protocol DeviceVerificationStartViewModelCoordinatorDelegate: class { +protocol DeviceVerificationStartViewModelCoordinatorDelegate: AnyObject { func deviceVerificationStartViewModelDidUseLegacyVerification(_ viewModel: DeviceVerificationStartViewModelType) func deviceVerificationStartViewModel(_ viewModel: DeviceVerificationStartViewModelType, didCompleteWithOutgoingTransaction transaction: MXSASTransaction) diff --git a/Riot/Modules/KeyVerification/User/SessionStatus/UserVerificationSessionStatusCoordinatorType.swift b/Riot/Modules/KeyVerification/User/SessionStatus/UserVerificationSessionStatusCoordinatorType.swift index 05d2c73b8..107fa42fa 100644 --- a/Riot/Modules/KeyVerification/User/SessionStatus/UserVerificationSessionStatusCoordinatorType.swift +++ b/Riot/Modules/KeyVerification/User/SessionStatus/UserVerificationSessionStatusCoordinatorType.swift @@ -18,7 +18,7 @@ import Foundation -protocol UserVerificationSessionStatusCoordinatorDelegate: class { +protocol UserVerificationSessionStatusCoordinatorDelegate: AnyObject { func userVerificationSessionStatusCoordinator(_ coordinator: UserVerificationSessionStatusCoordinatorType, wantsToVerifyDeviceWithId deviceId: String, for userId: String) func userVerificationSessionStatusCoordinator(_ coordinator: UserVerificationSessionStatusCoordinatorType, wantsToManuallyVerifyDeviceWithId deviceId: String, for userId: String) func userVerificationSessionStatusCoordinatorDidClose(_ coordinator: UserVerificationSessionStatusCoordinatorType) diff --git a/Riot/Modules/KeyVerification/User/SessionStatus/UserVerificationSessionStatusViewModelType.swift b/Riot/Modules/KeyVerification/User/SessionStatus/UserVerificationSessionStatusViewModelType.swift index 4598b4fa8..b600e235e 100644 --- a/Riot/Modules/KeyVerification/User/SessionStatus/UserVerificationSessionStatusViewModelType.swift +++ b/Riot/Modules/KeyVerification/User/SessionStatus/UserVerificationSessionStatusViewModelType.swift @@ -18,11 +18,11 @@ import Foundation -protocol UserVerificationSessionStatusViewModelViewDelegate: class { +protocol UserVerificationSessionStatusViewModelViewDelegate: AnyObject { func userVerificationSessionStatusViewModel(_ viewModel: UserVerificationSessionStatusViewModelType, didUpdateViewState viewSate: UserVerificationSessionStatusViewState) } -protocol UserVerificationSessionStatusViewModelCoordinatorDelegate: class { +protocol UserVerificationSessionStatusViewModelCoordinatorDelegate: AnyObject { func userVerificationSessionStatusViewModel(_ viewModel: UserVerificationSessionStatusViewModelType, wantsToVerifyDeviceWithId deviceId: String, for userId: String) func userVerificationSessionStatusViewModel(_ viewModel: UserVerificationSessionStatusViewModelType, wantsToManuallyVerifyDeviceWithId deviceId: String, for userId: String) func userVerificationSessionStatusViewModelDidClose(_ viewModel: UserVerificationSessionStatusViewModelType) diff --git a/Riot/Modules/KeyVerification/User/SessionsStatus/UserVerificationSessionsStatusCoordinatorType.swift b/Riot/Modules/KeyVerification/User/SessionsStatus/UserVerificationSessionsStatusCoordinatorType.swift index 46eccaaa5..67c39e688 100644 --- a/Riot/Modules/KeyVerification/User/SessionsStatus/UserVerificationSessionsStatusCoordinatorType.swift +++ b/Riot/Modules/KeyVerification/User/SessionsStatus/UserVerificationSessionsStatusCoordinatorType.swift @@ -18,7 +18,7 @@ import Foundation -protocol UserVerificationSessionsStatusCoordinatorDelegate: class { +protocol UserVerificationSessionsStatusCoordinatorDelegate: AnyObject { func userVerificationSessionsStatusCoordinatorDidClose(_ coordinator: UserVerificationSessionsStatusCoordinatorType) func userVerificationSessionsStatusCoordinator(_ coordinator: UserVerificationSessionsStatusCoordinatorType, didSelectDeviceWithId deviceId: String, for userId: String) } diff --git a/Riot/Modules/KeyVerification/User/SessionsStatus/UserVerificationSessionsStatusViewModelType.swift b/Riot/Modules/KeyVerification/User/SessionsStatus/UserVerificationSessionsStatusViewModelType.swift index 4df41221d..fe704b6ad 100644 --- a/Riot/Modules/KeyVerification/User/SessionsStatus/UserVerificationSessionsStatusViewModelType.swift +++ b/Riot/Modules/KeyVerification/User/SessionsStatus/UserVerificationSessionsStatusViewModelType.swift @@ -18,11 +18,11 @@ import Foundation -protocol UserVerificationSessionsStatusViewModelViewDelegate: class { +protocol UserVerificationSessionsStatusViewModelViewDelegate: AnyObject { func userVerificationSessionsStatusViewModel(_ viewModel: UserVerificationSessionsStatusViewModelType, didUpdateViewState viewSate: UserVerificationSessionsStatusViewState) } -protocol UserVerificationSessionsStatusViewModelCoordinatorDelegate: class { +protocol UserVerificationSessionsStatusViewModelCoordinatorDelegate: AnyObject { func userVerificationSessionsStatusViewModel(_ viewModel: UserVerificationSessionsStatusViewModelType, didSelectDeviceWithId deviceId: String, for userId: String) func userVerificationSessionsStatusViewModelDidClose(_ viewModel: UserVerificationSessionsStatusViewModelType) } diff --git a/Riot/Modules/KeyVerification/User/Start/UserVerificationStartCoordinatorType.swift b/Riot/Modules/KeyVerification/User/Start/UserVerificationStartCoordinatorType.swift index ba5c677d9..7f0130b5a 100644 --- a/Riot/Modules/KeyVerification/User/Start/UserVerificationStartCoordinatorType.swift +++ b/Riot/Modules/KeyVerification/User/Start/UserVerificationStartCoordinatorType.swift @@ -18,7 +18,7 @@ import Foundation -protocol UserVerificationStartCoordinatorDelegate: class { +protocol UserVerificationStartCoordinatorDelegate: AnyObject { func userVerificationStartCoordinator(_ coordinator: UserVerificationStartCoordinatorType, otherDidAcceptRequest request: MXKeyVerificationRequest) diff --git a/Riot/Modules/KeyVerification/User/Start/UserVerificationStartViewModelType.swift b/Riot/Modules/KeyVerification/User/Start/UserVerificationStartViewModelType.swift index 40582e0bf..b84043938 100644 --- a/Riot/Modules/KeyVerification/User/Start/UserVerificationStartViewModelType.swift +++ b/Riot/Modules/KeyVerification/User/Start/UserVerificationStartViewModelType.swift @@ -18,11 +18,11 @@ import Foundation -protocol UserVerificationStartViewModelViewDelegate: class { +protocol UserVerificationStartViewModelViewDelegate: AnyObject { func userVerificationStartViewModel(_ viewModel: UserVerificationStartViewModelType, didUpdateViewState viewSate: UserVerificationStartViewState) } -protocol UserVerificationStartViewModelCoordinatorDelegate: class { +protocol UserVerificationStartViewModelCoordinatorDelegate: AnyObject { func userVerificationStartViewModel(_ viewModel: UserVerificationStartViewModelType, otherDidAcceptRequest request: MXKeyVerificationRequest) diff --git a/Riot/Modules/KeyVerification/User/UserVerificationCoordinatorType.swift b/Riot/Modules/KeyVerification/User/UserVerificationCoordinatorType.swift index feeba8d3b..db665607e 100644 --- a/Riot/Modules/KeyVerification/User/UserVerificationCoordinatorType.swift +++ b/Riot/Modules/KeyVerification/User/UserVerificationCoordinatorType.swift @@ -18,7 +18,7 @@ import Foundation -protocol UserVerificationCoordinatorDelegate: class { +protocol UserVerificationCoordinatorDelegate: AnyObject { func userVerificationCoordinatorDidComplete(_ coordinator: UserVerificationCoordinatorType) } diff --git a/Riot/Modules/MediaPicker/MediaPickerCoordinator.swift b/Riot/Modules/MediaPicker/MediaPickerCoordinator.swift index d3b9c2c0d..61586be8d 100644 --- a/Riot/Modules/MediaPicker/MediaPickerCoordinator.swift +++ b/Riot/Modules/MediaPicker/MediaPickerCoordinator.swift @@ -83,8 +83,8 @@ extension MediaPickerCoordinator: MediaPickerViewControllerDelegate { self.delegate?.mediaPickerCoordinator(self, didSelectImageData: imageData, withUTI: uti) } - func mediaPickerController(_ mediaPickerController: MediaPickerViewController!, didSelectVideo videoURL: URL!) { - self.delegate?.mediaPickerCoordinator(self, didSelectVideoAt: videoURL) + func mediaPickerController(_ mediaPickerController: MediaPickerViewController!, didSelectVideo videoAsset: AVAsset!) { + self.delegate?.mediaPickerCoordinator(self, didSelectVideo: videoAsset) } func mediaPickerController(_ mediaPickerController: MediaPickerViewController!, didSelect assets: [PHAsset]!) { diff --git a/Riot/Modules/MediaPicker/MediaPickerCoordinatorBridgePresenter.swift b/Riot/Modules/MediaPicker/MediaPickerCoordinatorBridgePresenter.swift index 0ba21d893..782662ea9 100644 --- a/Riot/Modules/MediaPicker/MediaPickerCoordinatorBridgePresenter.swift +++ b/Riot/Modules/MediaPicker/MediaPickerCoordinatorBridgePresenter.swift @@ -20,7 +20,7 @@ import Foundation @objc protocol MediaPickerCoordinatorBridgePresenterDelegate { func mediaPickerCoordinatorBridgePresenter(_ coordinatorBridgePresenter: MediaPickerCoordinatorBridgePresenter, didSelectImageData imageData: Data, withUTI uti: MXKUTI?) - func mediaPickerCoordinatorBridgePresenter(_ coordinatorBridgePresenter: MediaPickerCoordinatorBridgePresenter, didSelectVideoAt url: URL) + func mediaPickerCoordinatorBridgePresenter(_ coordinatorBridgePresenter: MediaPickerCoordinatorBridgePresenter, didSelectVideo videoAsset: AVAsset) func mediaPickerCoordinatorBridgePresenter(_ coordinatorBridgePresenter: MediaPickerCoordinatorBridgePresenter, didSelectAssets assets: [PHAsset]) func mediaPickerCoordinatorBridgePresenterDidCancel(_ coordinatorBridgePresenter: MediaPickerCoordinatorBridgePresenter) } @@ -110,8 +110,8 @@ extension MediaPickerCoordinatorBridgePresenter: MediaPickerCoordinatorDelegate self.delegate?.mediaPickerCoordinatorBridgePresenter(self, didSelectImageData: imageData, withUTI: uti) } - func mediaPickerCoordinator(_ coordinator: MediaPickerCoordinatorType, didSelectVideoAt url: URL) { - self.delegate?.mediaPickerCoordinatorBridgePresenter(self, didSelectVideoAt: url) + func mediaPickerCoordinator(_ coordinator: MediaPickerCoordinatorType, didSelectVideo videoAsset: AVAsset) { + self.delegate?.mediaPickerCoordinatorBridgePresenter(self, didSelectVideo: videoAsset) } func mediaPickerCoordinator(_ coordinator: MediaPickerCoordinatorType, didSelectAssets assets: [PHAsset]) { diff --git a/Riot/Modules/MediaPicker/MediaPickerCoordinatorType.swift b/Riot/Modules/MediaPicker/MediaPickerCoordinatorType.swift index 53f154536..4f8fc5d8f 100644 --- a/Riot/Modules/MediaPicker/MediaPickerCoordinatorType.swift +++ b/Riot/Modules/MediaPicker/MediaPickerCoordinatorType.swift @@ -18,9 +18,9 @@ import Foundation -protocol MediaPickerCoordinatorDelegate: class { +protocol MediaPickerCoordinatorDelegate: AnyObject { func mediaPickerCoordinator(_ coordinator: MediaPickerCoordinatorType, didSelectImageData imageData: Data, withUTI uti: MXKUTI?) - func mediaPickerCoordinator(_ coordinator: MediaPickerCoordinatorType, didSelectVideoAt url: URL) + func mediaPickerCoordinator(_ coordinator: MediaPickerCoordinatorType, didSelectVideo videoAsset: AVAsset) func mediaPickerCoordinator(_ coordinator: MediaPickerCoordinatorType, didSelectAssets assets: [PHAsset]) func mediaPickerCoordinatorDidCancel(_ coordinator: MediaPickerCoordinatorType) } diff --git a/Riot/Modules/MediaPicker/MediaPickerViewController.h b/Riot/Modules/MediaPicker/MediaPickerViewController.h index 99e9365fe..71487e1ea 100644 --- a/Riot/Modules/MediaPicker/MediaPickerViewController.h +++ b/Riot/Modules/MediaPicker/MediaPickerViewController.h @@ -39,9 +39,9 @@ Tells the delegate that the user select a video. @param mediaPickerController the `MediaPickerViewController` instance. - @param videoURL the local url of the video to send. + @param videoAsset an `AVAsset` that represents the video to send. */ -- (void)mediaPickerController:(MediaPickerViewController *)mediaPickerController didSelectVideo:(NSURL*)videoURL; +- (void)mediaPickerController:(MediaPickerViewController *)mediaPickerController didSelectVideo:(AVAsset*)videoAsset; /** Tells the delegate that the user wants to cancel media picking. diff --git a/Riot/Modules/MediaPicker/MediaPickerViewController.m b/Riot/Modules/MediaPicker/MediaPickerViewController.m index d08e6ab17..e34617241 100644 --- a/Riot/Modules/MediaPicker/MediaPickerViewController.m +++ b/Riot/Modules/MediaPicker/MediaPickerViewController.m @@ -608,28 +608,19 @@ if (asset) { - if ([asset isKindOfClass:[AVURLAsset class]]) - { - MXLogDebug(@"[MediaPickerVC] didSelectAsset: Got AVAsset for video"); - AVURLAsset *avURLAsset = (AVURLAsset*)asset; + MXLogDebug(@"[MediaPickerVC] didSelectAsset: Got AVAsset for video"); + + // Validate first the selected video + [self validateSelectedVideo:asset responseHandler:^(BOOL isValidated) { + + if (isValidated) + { + [self.delegate mediaPickerController:self didSelectVideo:asset]; + } - // Validate first the selected video - [self validateSelectedVideo:[avURLAsset URL] responseHandler:^(BOOL isValidated) { - - if (isValidated) - { - [self.delegate mediaPickerController:self didSelectVideo:[avURLAsset URL]]; - } - - self->isValidationInProgress = NO; - - }]; - } - else - { - MXLogDebug(@"[MediaPickerVC] Selected video asset is not initialized from an URL!"); self->isValidationInProgress = NO; - } + + }]; } else { @@ -693,7 +684,7 @@ [self setNeedsStatusBarAppearanceUpdate]; } -- (void)validateSelectedVideo:(NSURL*)selectedVideoURL responseHandler:(void (^)(BOOL isValidated))handler +- (void)validateSelectedVideo:(AVAsset*)selectedVideo responseHandler:(void (^)(BOOL isValidated))handler { [self dismissImageValidationView]; @@ -727,15 +718,15 @@ videoPlayer = [[AVPlayerViewController alloc] init]; if (videoPlayer) { + AVPlayerItem *item = [AVPlayerItem playerItemWithAsset:selectedVideo]; videoPlayer.allowsPictureInPicturePlayback = NO; videoPlayer.updatesNowPlayingInfoCenter = NO; - videoPlayer.player = [AVPlayer playerWithURL:selectedVideoURL]; + videoPlayer.player = [AVPlayer playerWithPlayerItem:item]; videoPlayer.videoGravity = AVLayerVideoGravityResizeAspect; videoPlayer.showsPlaybackControls = NO; // create a thumbnail for the first frame - AVAsset *asset = [AVAsset assetWithURL:selectedVideoURL]; - AVAssetImageGenerator *generator = [AVAssetImageGenerator assetImageGeneratorWithAsset:asset]; + AVAssetImageGenerator *generator = [AVAssetImageGenerator assetImageGeneratorWithAsset:selectedVideo]; generator.appliesPreferredTrackTransform = YES; CGImageRef thumbnailRef = [generator copyCGImageAtTime:kCMTimeZero actualTime:nil error:nil]; diff --git a/Riot/Modules/MediaPicker/SingleImagePickerPresenter.swift b/Riot/Modules/MediaPicker/SingleImagePickerPresenter.swift index f8bc066ee..1a1d20169 100644 --- a/Riot/Modules/MediaPicker/SingleImagePickerPresenter.swift +++ b/Riot/Modules/MediaPicker/SingleImagePickerPresenter.swift @@ -18,7 +18,7 @@ import Foundation import UIKit import AVFoundation -@objc protocol SingleImagePickerPresenterDelegate: class { +@objc protocol SingleImagePickerPresenterDelegate: AnyObject { func singleImagePickerPresenter(_ presenter: SingleImagePickerPresenter, didSelectImageData imageData: Data, withUTI uti: MXKUTI?) func singleImagePickerPresenterDidCancel(_ presenter: SingleImagePickerPresenter) } @@ -135,7 +135,7 @@ extension SingleImagePickerPresenter: MediaPickerCoordinatorBridgePresenterDeleg self.delegate?.singleImagePickerPresenter(self, didSelectImageData: imageData, withUTI: uti) } - func mediaPickerCoordinatorBridgePresenter(_ coordinatorBridgePresenter: MediaPickerCoordinatorBridgePresenter, didSelectVideoAt url: URL) { + func mediaPickerCoordinatorBridgePresenter(_ coordinatorBridgePresenter: MediaPickerCoordinatorBridgePresenter, didSelectVideo videoAsset: AVAsset) { self.delegate?.singleImagePickerPresenterDidCancel(self) } diff --git a/Riot/Modules/People/PeopleViewController.h b/Riot/Modules/People/PeopleViewController.h index 8cc3607a4..696ebbb5b 100644 --- a/Riot/Modules/People/PeopleViewController.h +++ b/Riot/Modules/People/PeopleViewController.h @@ -20,7 +20,7 @@ /** 'PeopleViewController' instance is used to display/filter the direct rooms and a list of contacts. */ -@interface PeopleViewController : RecentsViewController +@interface PeopleViewController : RecentsViewController + (instancetype)instantiate; diff --git a/Riot/Modules/People/PeopleViewController.m b/Riot/Modules/People/PeopleViewController.m index 5985ce346..26b0ca3f0 100644 --- a/Riot/Modules/People/PeopleViewController.m +++ b/Riot/Modules/People/PeopleViewController.m @@ -14,7 +14,6 @@ limitations under the License. */ -#import #import "PeopleViewController.h" #import "UIViewController+RiotSearch.h" @@ -25,17 +24,11 @@ #import "RecentTableViewCell.h" #import "InviteRecentTableViewCell.h" -#import "ContactTableViewCell.h" - #import "Riot-Swift.h" @interface PeopleViewController () { NSInteger directRoomsSectionNumber; - - ContactsDataSource *contactsDataSource; - NSInteger contactsSectionNumber; - RecentsDataSource *recentsDataSource; } @@ -55,7 +48,6 @@ [super finalizeInit]; directRoomsSectionNumber = 0; - contactsSectionNumber = 0; self.screenName = @"People"; } @@ -76,14 +68,6 @@ plusButtonImageView = [self vc_addFABWithImage:[UIImage imageNamed:@"people_floating_action"] target:self action:@selector(onPlusButtonPressed)]; - - // Register table view cell for contacts. - [self.recentsTableView registerClass:ContactTableViewCell.class forCellReuseIdentifier:ContactTableViewCell.defaultReuseIdentifier]; - - // Change the table data source. It must be the people view controller itself. - self.recentsTableView.dataSource = self; - - self.enableStickyHeaders = YES; } - (void)didReceiveMemoryWarning @@ -92,302 +76,36 @@ // Dispose of any resources that can be recreated. } -- (void)destroy -{ - contactsDataSource.delegate = nil; - [contactsDataSource destroy]; - contactsDataSource = nil; - - [super destroy]; -} - - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; - - if (BuildSettings.allowLocalContactsAccess) - { - // Check whether the access to the local contacts has not been already asked - // and check that the user has decided to use or not to use an identity server - if ([CNContactStore authorizationStatusForEntityType:CNEntityTypeContacts] == CNAuthorizationStatusNotDetermined - || !contactsDataSource.mxSession.hasAccountDataIdentityServerValue) - { - // Allow by default the local contacts sync in order to discover matrix users. - // This setting change will trigger the loading of the local contacts, which will automatically - // ask user permission to access their local contacts. - [MXKAppSettings standardAppSettings].syncLocalContacts = YES; - } - - // Refresh the local contacts list. - [[MXKContactManager sharedManager] refreshLocalContacts]; - } [AppDelegate theDelegate].masterTabBarController.navigationItem.title = NSLocalizedStringFromTable(@"title_people", @"Vector", nil); [AppDelegate theDelegate].masterTabBarController.tabBar.tintColor = ThemeService.shared.theme.tintColor; - if (recentsDataSource) + if ([self.dataSource isKindOfClass:RecentsDataSource.class]) { // Take the lead on the shared data source. + recentsDataSource = (RecentsDataSource*)self.dataSource; recentsDataSource.areSectionsShrinkable = NO; [recentsDataSource setDelegate:self andRecentsDataSourceMode:RecentsDataSourceModePeople]; } } -#pragma mark - - -- (void)displayList:(MXKRecentsDataSource *)listDataSource -{ - [super displayList:listDataSource]; - - // Change the table data source. It must be the people view controller itself. - self.recentsTableView.dataSource = self; - - // Keep a ref on the recents data source - if ([listDataSource isKindOfClass:RecentsDataSource.class]) - { - recentsDataSource = (RecentsDataSource*)listDataSource; - } - - if (BuildSettings.allowLocalContactsAccess) - { - if (!contactsDataSource) - { - // Prepare its contacts data source - contactsDataSource = [[ContactsDataSource alloc] initWithMatrixSession:listDataSource.mxSession]; - contactsDataSource.contactCellAccessoryImage = [[UIImage imageNamed: @"disclosure_icon"] vc_tintedImageUsingColor:ThemeService.shared.theme.textSecondaryColor]; - contactsDataSource.delegate = self; - } - } -} - -#pragma mark - MXKDataSourceDelegate - -- (Class)cellViewClassForCellData:(MXKCellData*)cellData -{ - if ([cellData isKindOfClass:MXKContact.class]) - { - return ContactTableViewCell.class; - } - - return [super cellViewClassForCellData:cellData]; -} - -#pragma mark - UITableView data source - -- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView -{ - // Retrieve the current number of sections related to the direct rooms. - // Sanity check: check whether the recents data source is correctly configured. - directRoomsSectionNumber = 0; - - if (recentsDataSource.recentsDataSourceMode == RecentsDataSourceModePeople) - { - directRoomsSectionNumber = [self.dataSource numberOfSectionsInTableView:self.recentsTableView]; - } - - // Retrieve the current number of sections related to the contacts - contactsSectionNumber = [contactsDataSource numberOfSectionsInTableView:self.recentsTableView]; - - return (directRoomsSectionNumber + contactsSectionNumber); -} - -- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section -{ - NSInteger count = 0; - - if (section < directRoomsSectionNumber) - { - count = [self.dataSource tableView:tableView numberOfRowsInSection:section]; - } - else - { - section -= directRoomsSectionNumber; - if (section < contactsSectionNumber) - { - count = [contactsDataSource tableView:tableView numberOfRowsInSection:section]; - } - } - - return count; -} - -- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath -{ - NSInteger section = indexPath.section; - - if (section < directRoomsSectionNumber) - { - return [self.dataSource tableView:tableView cellForRowAtIndexPath:indexPath]; - } - else - { - section -= directRoomsSectionNumber; - if (section < contactsSectionNumber) - { - return [contactsDataSource tableView:tableView cellForRowAtIndexPath:[NSIndexPath indexPathForRow:indexPath.row inSection:section]]; - } - } - - // Return a fake cell to prevent app from crashing. - return [[UITableViewCell alloc] init]; -} - -- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath -{ - NSInteger section = indexPath.section; - - if (section < directRoomsSectionNumber) - { - return [self.dataSource tableView:tableView canEditRowAtIndexPath:indexPath]; - } - else - { - section -= directRoomsSectionNumber; - if (section < contactsSectionNumber) - { - return [contactsDataSource tableView:tableView canEditRowAtIndexPath:[NSIndexPath indexPathForRow:indexPath.row inSection:section]]; - } - } - - return NO; -} - #pragma mark - UITableView delegate - (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section { - if (section >= directRoomsSectionNumber) - { - // Let the contact dataSource provide the height of the section header. - section -= directRoomsSectionNumber; - if (section < contactsSectionNumber) - { - return [contactsDataSource heightForHeaderInSection:section]; - } - else - { - return 0.0; - } - } - - return [super tableView:tableView heightForHeaderInSection:section]; + return 0.0; } - (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section { - if (section >= directRoomsSectionNumber) - { - // Let the contact dataSource provide the section header. - CGRect frame = [tableView rectForHeaderInSection:section]; - section -= directRoomsSectionNumber; - if (section < contactsSectionNumber) - { - UIView *sectionHeader = [contactsDataSource viewForHeaderInSection:section withFrame:frame]; - sectionHeader.tag = section + directRoomsSectionNumber; - - if (self.enableStickyHeaders) - { - while (sectionHeader.gestureRecognizers.count) - { - UIGestureRecognizer *gestureRecognizer = sectionHeader.gestureRecognizers.lastObject; - [sectionHeader removeGestureRecognizer:gestureRecognizer]; - } - - // Handle tap gesture - UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(didTapOnSectionHeader:)]; - [tap setNumberOfTouchesRequired:1]; - [tap setNumberOfTapsRequired:1]; - [sectionHeader addGestureRecognizer:tap]; - } - - return sectionHeader; - } - else - { - return nil; - } - } - - return [super tableView:tableView viewForHeaderInSection:section]; -} - -- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath -{ - NSInteger section = indexPath.section; - if (section >= directRoomsSectionNumber) - { - section -= directRoomsSectionNumber; - if (section < contactsSectionNumber) - { - if ([contactsDataSource contactAtIndexPath:[NSIndexPath indexPathForRow:indexPath.row inSection:section]]) - { - // Return the default height of the contact cell - return 74.0; - } - - return 50; - } - else - { - return 0.0; - } - } - - return [super tableView:tableView heightForRowAtIndexPath:indexPath]; -} - -- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath -{ - NSInteger section = indexPath.section; - if (section >= directRoomsSectionNumber) - { - section -= directRoomsSectionNumber; - if (section < contactsSectionNumber) - { - MXKContact *mxkContact = [contactsDataSource contactAtIndexPath:[NSIndexPath indexPathForRow:indexPath.row inSection:section]]; - - if (mxkContact) - { - [[AppDelegate theDelegate].masterTabBarController selectContact:mxkContact]; - - // Keep selected the cell by default. - return; - } - } - else - { - [tableView deselectRowAtIndexPath:indexPath animated:NO]; - return; - } - } - - return [super tableView:tableView didSelectRowAtIndexPath:indexPath]; + return nil; } #pragma mark - Override RecentsViewController -- (UIView *)tableView:(UITableView *)tableView viewForStickyHeaderInSection:(NSInteger)section -{ - CGRect frame = [tableView rectForHeaderInSection:section]; - frame.size.height = self.stickyHeaderHeight; - - if (section >= directRoomsSectionNumber) - { - // Let the contact dataSource provide this header. - section -= directRoomsSectionNumber; - if (section < contactsSectionNumber) - { - return [contactsDataSource viewForStickyHeaderInSection:section withFrame:frame]; - } - } - else if (recentsDataSource) - { - return [recentsDataSource viewForStickyHeaderInSection:section withFrame:frame]; - } - - return nil; -} - - (void)refreshCurrentSelectedCell:(BOOL)forceVisible { // Check whether the recents data source is correctly configured. @@ -396,41 +114,7 @@ return; } - // Update here the index of the current selected cell (if any) - Useful in landscape mode with split view controller. - NSIndexPath *currentSelectedCellIndexPath = nil; - MasterTabBarController *masterTabBarController = [AppDelegate theDelegate].masterTabBarController; - if (masterTabBarController.currentContactDetailViewController) - { - // Look for the rank of this selected contact - currentSelectedCellIndexPath = [contactsDataSource cellIndexPathWithContact:masterTabBarController.selectedContact]; - - if (currentSelectedCellIndexPath) - { - // Select the right row - currentSelectedCellIndexPath = [NSIndexPath indexPathForRow:currentSelectedCellIndexPath.row inSection:(directRoomsSectionNumber + currentSelectedCellIndexPath.section)]; - [self.recentsTableView selectRowAtIndexPath:currentSelectedCellIndexPath animated:YES scrollPosition:UITableViewScrollPositionNone]; - - if (forceVisible) - { - // Scroll table view to make the selected row appear at second position - NSInteger topCellIndexPathRow = currentSelectedCellIndexPath.row ? currentSelectedCellIndexPath.row - 1: currentSelectedCellIndexPath.row; - NSIndexPath* indexPath = [NSIndexPath indexPathForRow:topCellIndexPathRow inSection:currentSelectedCellIndexPath.section]; - [self.recentsTableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionTop animated:NO]; - } - } - else - { - NSIndexPath *indexPath = [self.recentsTableView indexPathForSelectedRow]; - if (indexPath) - { - [self.recentsTableView deselectRowAtIndexPath:indexPath animated:NO]; - } - } - } - else - { - [super refreshCurrentSelectedCell:forceVisible]; - } + [super refreshCurrentSelectedCell:forceVisible]; } - (void)onPlusButtonPressed @@ -449,24 +133,6 @@ } } -#pragma mark - UISearchBarDelegate - -- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText -{ - // Apply filter on contact source - [contactsDataSource searchWithPattern:searchText forceReset:NO]; - - [super searchBar:searchBar textDidChange:searchText]; -} - -- (void)searchBarCancelButtonClicked:(UISearchBar *)searchBar -{ - // Reset filtering - [contactsDataSource searchWithPattern:nil forceReset:NO]; - - [super searchBarCancelButtonClicked:searchBar]; -} - #pragma mark - Empty view management - (void)updateEmptyView @@ -503,35 +169,7 @@ - (NSUInteger)totalItemCounts { return recentsDataSource.invitesCellDataArray.count - + recentsDataSource.conversationCellDataArray.count - + recentsDataSource.peopleCellDataArray.count - + [self numberOfContactsInContactsDataSource]; -} - -- (NSUInteger)numberOfContactsInContactsDataSource -{ - BOOL areLocalContactsAccessAuthorized = [CNContactStore authorizationStatusForEntityType:CNEntityTypeContacts] == CNAuthorizationStatusAuthorized; - - NSInteger nbOfItemsInContactDataSource = 0; - - for (NSInteger i = 0; i < contactsSectionNumber; i++) - { - nbOfItemsInContactDataSource += [contactsDataSource tableView:self.recentsTableView numberOfRowsInSection:i]; - } - - NSInteger numberOfContactsInContactsDataSource; - - // No local contacts to show and no search in directory - if (!areLocalContactsAccessAuthorized && contactsSectionNumber == 1 && nbOfItemsInContactDataSource <= 1) - { - numberOfContactsInContactsDataSource = 0; - } - else - { - numberOfContactsInContactsDataSource = nbOfItemsInContactDataSource; - } - - return numberOfContactsInContactsDataSource; + + recentsDataSource.conversationCellDataArray.count; } @end diff --git a/Riot/Modules/PublicRoomList/DataSources/PublicRoomsDirectoryDataSource.h b/Riot/Modules/PublicRoomList/DataSources/PublicRoomsDirectoryDataSource.h index 752cce049..750daac05 100644 --- a/Riot/Modules/PublicRoomList/DataSources/PublicRoomsDirectoryDataSource.h +++ b/Riot/Modules/PublicRoomList/DataSources/PublicRoomsDirectoryDataSource.h @@ -62,11 +62,16 @@ @property (nonatomic, readonly) NSString *directoryServerDisplayname; /** - The number of public rooms matching `searchPattern`. - It is accurate only if 'moreThanRoomsCount' is NO. + The number of public rooms that have been fetched so far. */ @property (nonatomic, readonly) NSUInteger roomsCount; +/** + The total number of public rooms matching `searchPattern`. + It is accurate only if 'searchResultsCountIsLimited' is NO. + */ +@property (nonatomic, readonly) NSUInteger searchResultsCount; + /** In case of search with a lot of matching public rooms, we cannot return an accurate value except by paginating the full list of rooms, which is not expected. @@ -74,7 +79,7 @@ This flag indicates that we know that there is more matching rooms than we got so far. */ -@property (nonatomic, readonly) BOOL moreThanRoomsCount; +@property (nonatomic, readonly) BOOL searchResultsCountIsLimited; /** The maximum number of public rooms to retrieve during a pagination. diff --git a/Riot/Modules/PublicRoomList/DataSources/PublicRoomsDirectoryDataSource.m b/Riot/Modules/PublicRoomList/DataSources/PublicRoomsDirectoryDataSource.m index e69035aa0..fa8ac0ab0 100644 --- a/Riot/Modules/PublicRoomList/DataSources/PublicRoomsDirectoryDataSource.m +++ b/Riot/Modules/PublicRoomList/DataSources/PublicRoomsDirectoryDataSource.m @@ -165,6 +165,11 @@ static NSString *const kNSFWKeyword = @"nsfw"; } } +- (NSUInteger)roomsCount +{ + return rooms.count; +} + - (NSIndexPath*)cellIndexPathWithRoomId:(NSString*)roomId andMatrixSession:(MXSession*)matrixSession { NSIndexPath *indexPath = nil; @@ -217,8 +222,8 @@ static NSString *const kNSFWKeyword = @"nsfw"; // Reset all pagination vars [rooms removeAllObjects]; nextBatch = nil; - _roomsCount = 0; - _moreThanRoomsCount = NO; + _searchResultsCount = 0; + _searchResultsCountIsLimited = NO; _hasReachedPaginationEnd = NO; } @@ -264,14 +269,14 @@ static NSString *const kNSFWKeyword = @"nsfw"; if (!self->_searchPattern) { // When there is no search, we can use totalRoomCountEstimate returned by the server - self->_roomsCount = publicRoomsResponse.totalRoomCountEstimate; - self->_moreThanRoomsCount = NO; + self->_searchResultsCount = publicRoomsResponse.totalRoomCountEstimate; + self->_searchResultsCountIsLimited = NO; } else { // Else we can only display something like ">20 matching rooms" - self->_roomsCount = self->rooms.count; - self->_moreThanRoomsCount = publicRoomsResponse.nextBatch ? YES : NO; + self->_searchResultsCount = self->rooms.count; + self->_searchResultsCountIsLimited = publicRoomsResponse.nextBatch ? YES : NO; } // Detect pagination end diff --git a/Riot/Modules/QRCode/Reader/QRCodeReaderViewController.swift b/Riot/Modules/QRCode/Reader/QRCodeReaderViewController.swift index 766795d63..4d35af705 100644 --- a/Riot/Modules/QRCode/Reader/QRCodeReaderViewController.swift +++ b/Riot/Modules/QRCode/Reader/QRCodeReaderViewController.swift @@ -17,7 +17,7 @@ import UIKit import ZXingObjC -protocol QRCodeReaderViewControllerDelegate: class { +protocol QRCodeReaderViewControllerDelegate: AnyObject { func qrCodeReaderViewController(_ viewController: QRCodeReaderViewController, didFound payloadData: Data) func qrCodeReaderViewControllerDidCancel(_ viewController: QRCodeReaderViewController) } diff --git a/Riot/Modules/Reauthentication/ReauthenticationCoordinatorType.swift b/Riot/Modules/Reauthentication/ReauthenticationCoordinatorType.swift index bdbc31ce2..96c48215d 100644 --- a/Riot/Modules/Reauthentication/ReauthenticationCoordinatorType.swift +++ b/Riot/Modules/Reauthentication/ReauthenticationCoordinatorType.swift @@ -18,7 +18,7 @@ import Foundation -protocol ReauthenticationCoordinatorDelegate: class { +protocol ReauthenticationCoordinatorDelegate: AnyObject { func reauthenticationCoordinatorDidComplete(_ coordinator: ReauthenticationCoordinatorType, withAuthenticationParameters: [String: Any]?) func reauthenticationCoordinatorDidCancel(_ coordinator: ReauthenticationCoordinatorType) func reauthenticationCoordinator(_ coordinator: ReauthenticationCoordinatorType, didFailWithError: Error) diff --git a/Riot/Modules/Room/BubbleReactions/BubbleReactionsViewModelType.swift b/Riot/Modules/Room/BubbleReactions/BubbleReactionsViewModelType.swift index e67dcfdbd..77cab862e 100644 --- a/Riot/Modules/Room/BubbleReactions/BubbleReactionsViewModelType.swift +++ b/Riot/Modules/Room/BubbleReactions/BubbleReactionsViewModelType.swift @@ -39,7 +39,7 @@ enum BubbleReactionsViewState { } } -@objc protocol BubbleReactionsViewModelDelegate: class { +@objc protocol BubbleReactionsViewModelDelegate: AnyObject { func bubbleReactionsViewModel(_ viewModel: BubbleReactionsViewModel, didAddReaction reactionCount: MXReactionCount, forEventId eventId: String) func bubbleReactionsViewModel(_ viewModel: BubbleReactionsViewModel, didRemoveReaction reactionCount: MXReactionCount, forEventId eventId: String) func bubbleReactionsViewModel(_ viewModel: BubbleReactionsViewModel, didShowAllTappedForEventId eventId: String) @@ -47,7 +47,7 @@ enum BubbleReactionsViewState { func bubbleReactionsViewModel(_ viewModel: BubbleReactionsViewModel, didLongPressForEventId eventId: String) } -protocol BubbleReactionsViewModelViewDelegate: class { +protocol BubbleReactionsViewModelViewDelegate: AnyObject { func bubbleReactionsViewModel(_ viewModel: BubbleReactionsViewModel, didUpdateViewState viewState: BubbleReactionsViewState) } diff --git a/Riot/Modules/Room/CellData/RoomBubbleCellData.m b/Riot/Modules/Room/CellData/RoomBubbleCellData.m index 423b84426..2a216c44f 100644 --- a/Riot/Modules/Room/CellData/RoomBubbleCellData.m +++ b/Riot/Modules/Room/CellData/RoomBubbleCellData.m @@ -986,6 +986,9 @@ static NSAttributedString *timestampVerticalWhitespace = nil; case MXKAttachmentTypeAudio: accessibilityLabel = NSLocalizedStringFromTable(@"media_type_accessibility_audio", @"Vector", nil); break; + case MXKAttachmentTypeVoiceMessage: + accessibilityLabel = NSLocalizedStringFromTable(@"media_type_accessibility_audio", @"Vector", nil); + break; case MXKAttachmentTypeVideo: accessibilityLabel = NSLocalizedStringFromTable(@"media_type_accessibility_video", @"Vector", nil); break; diff --git a/Riot/Modules/Room/ContextualMenu/ReactionsMenu/ReactionsMenuViewModelType.swift b/Riot/Modules/Room/ContextualMenu/ReactionsMenu/ReactionsMenuViewModelType.swift index fee8b41d9..2171e3ab5 100644 --- a/Riot/Modules/Room/ContextualMenu/ReactionsMenu/ReactionsMenuViewModelType.swift +++ b/Riot/Modules/Room/ContextualMenu/ReactionsMenu/ReactionsMenuViewModelType.swift @@ -16,11 +16,11 @@ import Foundation -protocol ReactionsMenuViewModelViewDelegate: class { +protocol ReactionsMenuViewModelViewDelegate: AnyObject { func reactionsMenuViewModel(_ viewModel: ReactionsMenuViewModel, didUpdateViewState viewState: ReactionsMenuViewState) } -@objc protocol ReactionsMenuViewModelCoordinatorDelegate: class { +@objc protocol ReactionsMenuViewModelCoordinatorDelegate: AnyObject { func reactionsMenuViewModel(_ viewModel: ReactionsMenuViewModel, didAddReaction reaction: String, forEventId eventId: String) func reactionsMenuViewModel(_ viewModel: ReactionsMenuViewModel, didRemoveReaction reaction: String, forEventId eventId: String) func reactionsMenuViewModelDidTapMoreReactions(_ viewModel: ReactionsMenuViewModel, forEventId eventId: String) diff --git a/Riot/Modules/Room/ContextualMenu/RoomContextualMenuViewController.swift b/Riot/Modules/Room/ContextualMenu/RoomContextualMenuViewController.swift index 32052dc06..e236a6ec1 100644 --- a/Riot/Modules/Room/ContextualMenu/RoomContextualMenuViewController.swift +++ b/Riot/Modules/Room/ContextualMenu/RoomContextualMenuViewController.swift @@ -16,7 +16,7 @@ import UIKit -@objc protocol RoomContextualMenuViewControllerDelegate: class { +@objc protocol RoomContextualMenuViewControllerDelegate: AnyObject { func roomContextualMenuViewControllerDidTapBackgroundOverlay(_ viewController: RoomContextualMenuViewController) } diff --git a/Riot/Modules/Room/CreationModal/RoomCreationEventsModal/RoomCreationEventsModalCoordinatorType.swift b/Riot/Modules/Room/CreationModal/RoomCreationEventsModal/RoomCreationEventsModalCoordinatorType.swift index 02ff0e62e..0629043b0 100644 --- a/Riot/Modules/Room/CreationModal/RoomCreationEventsModal/RoomCreationEventsModalCoordinatorType.swift +++ b/Riot/Modules/Room/CreationModal/RoomCreationEventsModal/RoomCreationEventsModalCoordinatorType.swift @@ -18,7 +18,7 @@ import Foundation -protocol RoomCreationEventsModalCoordinatorDelegate: class { +protocol RoomCreationEventsModalCoordinatorDelegate: AnyObject { func roomCreationEventsModalCoordinatorDidTapClose(_ coordinator: RoomCreationEventsModalCoordinatorType) } diff --git a/Riot/Modules/Room/CreationModal/RoomCreationEventsModal/RoomCreationEventsModalViewModelType.swift b/Riot/Modules/Room/CreationModal/RoomCreationEventsModal/RoomCreationEventsModalViewModelType.swift index 6a670c1f5..5378da659 100644 --- a/Riot/Modules/Room/CreationModal/RoomCreationEventsModal/RoomCreationEventsModalViewModelType.swift +++ b/Riot/Modules/Room/CreationModal/RoomCreationEventsModal/RoomCreationEventsModalViewModelType.swift @@ -18,11 +18,11 @@ import Foundation -protocol RoomCreationEventsModalViewModelViewDelegate: class { +protocol RoomCreationEventsModalViewModelViewDelegate: AnyObject { func roomCreationEventsModalViewModel(_ viewModel: RoomCreationEventsModalViewModelType, didUpdateViewState viewSate: RoomCreationEventsModalViewState) } -protocol RoomCreationEventsModalViewModelCoordinatorDelegate: class { +protocol RoomCreationEventsModalViewModelCoordinatorDelegate: AnyObject { func roomCreationEventsModalViewModelDidTapClose(_ viewModel: RoomCreationEventsModalViewModelType) } diff --git a/Riot/Modules/Room/CreationModal/RoomCreationModalCoordinatorType.swift b/Riot/Modules/Room/CreationModal/RoomCreationModalCoordinatorType.swift index 11f3a1c21..f1830a0c1 100644 --- a/Riot/Modules/Room/CreationModal/RoomCreationModalCoordinatorType.swift +++ b/Riot/Modules/Room/CreationModal/RoomCreationModalCoordinatorType.swift @@ -18,7 +18,7 @@ import Foundation -protocol RoomCreationModalCoordinatorDelegate: class { +protocol RoomCreationModalCoordinatorDelegate: AnyObject { func roomCreationModalCoordinatorDidComplete(_ coordinator: RoomCreationModalCoordinatorType) } diff --git a/Riot/Modules/Room/DataSources/RoomDataSource.m b/Riot/Modules/Room/DataSources/RoomDataSource.m index 831f96634..434aee6ac 100644 --- a/Riot/Modules/Room/DataSources/RoomDataSource.m +++ b/Riot/Modules/Room/DataSources/RoomDataSource.m @@ -887,8 +887,10 @@ const CGFloat kTypingCellHeight = 24; success:(void (^)(NSString *eventId))success failure:(void (^)(NSError *error))failure { + AVURLAsset *videoAsset = [AVURLAsset assetWithURL:videoLocalURL]; UIImage *videoThumbnail = [MXKVideoThumbnailGenerator.shared generateThumbnailFrom:videoLocalURL]; - [self sendVideo:videoLocalURL withThumbnail:videoThumbnail success:success failure:failure]; + + [self sendVideoAsset:videoAsset withThumbnail:videoThumbnail success:success failure:failure]; } - (void)acceptVerificationRequestForEventId:(NSString*)eventId success:(void(^)(void))success failure:(void(^)(NSError*))failure @@ -1037,12 +1039,10 @@ const CGFloat kTypingCellHeight = 24; - (void)applyMaskToAttachmentViewOfBubbleCell:(MXKRoomBubbleTableViewCell *)cell { - if (cell.attachmentView && !cell.attachmentView.layer.mask) + if (cell.attachmentView) { - UIBezierPath *myClippingPath = [UIBezierPath bezierPathWithRoundedRect:cell.attachmentView.bounds cornerRadius:6]; - CAShapeLayer *mask = [CAShapeLayer layer]; - mask.path = myClippingPath.CGPath; - cell.attachmentView.layer.mask = mask; + cell.attachmentView.layer.cornerRadius = 6; + cell.attachmentView.layer.masksToBounds = YES; } } diff --git a/Riot/Modules/Room/EditHistory/EditHistoryCoordinatorType.swift b/Riot/Modules/Room/EditHistory/EditHistoryCoordinatorType.swift index 2f02bd705..61788b24b 100644 --- a/Riot/Modules/Room/EditHistory/EditHistoryCoordinatorType.swift +++ b/Riot/Modules/Room/EditHistory/EditHistoryCoordinatorType.swift @@ -18,7 +18,7 @@ import Foundation -protocol EditHistoryCoordinatorDelegate: class { +protocol EditHistoryCoordinatorDelegate: AnyObject { func editHistoryCoordinatorDidComplete(_ coordinator: EditHistoryCoordinatorType) } diff --git a/Riot/Modules/Room/EditHistory/EditHistoryViewModelType.swift b/Riot/Modules/Room/EditHistory/EditHistoryViewModelType.swift index 4ed9ff707..25d73396d 100644 --- a/Riot/Modules/Room/EditHistory/EditHistoryViewModelType.swift +++ b/Riot/Modules/Room/EditHistory/EditHistoryViewModelType.swift @@ -18,11 +18,11 @@ import Foundation -protocol EditHistoryViewModelViewDelegate: class { +protocol EditHistoryViewModelViewDelegate: AnyObject { func editHistoryViewModel(_ viewModel: EditHistoryViewModelType, didUpdateViewState viewSate: EditHistoryViewState) } -protocol EditHistoryViewModelCoordinatorDelegate: class { +protocol EditHistoryViewModelCoordinatorDelegate: AnyObject { func editHistoryViewModelDidClose(_ viewModel: EditHistoryViewModelType) } diff --git a/Riot/Modules/Room/EmojiPicker/EmojiPickerCoordinatorType.swift b/Riot/Modules/Room/EmojiPicker/EmojiPickerCoordinatorType.swift index e414ea29b..5d99c6118 100644 --- a/Riot/Modules/Room/EmojiPicker/EmojiPickerCoordinatorType.swift +++ b/Riot/Modules/Room/EmojiPicker/EmojiPickerCoordinatorType.swift @@ -18,7 +18,7 @@ import Foundation -protocol EmojiPickerCoordinatorDelegate: class { +protocol EmojiPickerCoordinatorDelegate: AnyObject { func emojiPickerCoordinator(_ coordinator: EmojiPickerCoordinatorType, didAddEmoji emoji: String, forEventId eventId: String) func emojiPickerCoordinator(_ coordinator: EmojiPickerCoordinatorType, didRemoveEmoji emoji: String, forEventId eventId: String) func emojiPickerCoordinatorDidCancel(_ coordinator: EmojiPickerCoordinatorType) diff --git a/Riot/Modules/Room/EmojiPicker/EmojiPickerViewModelType.swift b/Riot/Modules/Room/EmojiPicker/EmojiPickerViewModelType.swift index 3f15484f1..ba9b44222 100644 --- a/Riot/Modules/Room/EmojiPicker/EmojiPickerViewModelType.swift +++ b/Riot/Modules/Room/EmojiPicker/EmojiPickerViewModelType.swift @@ -18,11 +18,11 @@ import Foundation -protocol EmojiPickerViewModelViewDelegate: class { +protocol EmojiPickerViewModelViewDelegate: AnyObject { func emojiPickerViewModel(_ viewModel: EmojiPickerViewModelType, didUpdateViewState viewSate: EmojiPickerViewState) } -protocol EmojiPickerViewModelCoordinatorDelegate: class { +protocol EmojiPickerViewModelCoordinatorDelegate: AnyObject { func emojiPickerViewModel(_ viewModel: EmojiPickerViewModelType, didAddEmoji emoji: String, forEventId eventId: String) func emojiPickerViewModel(_ viewModel: EmojiPickerViewModelType, didRemoveEmoji emoji: String, forEventId eventId: String) func emojiPickerViewModelDidCancel(_ viewModel: EmojiPickerViewModelType) diff --git a/Riot/Modules/Room/NotificationSettings/RoomNotificationSettingsAvatarView.swift b/Riot/Modules/Room/NotificationSettings/RoomNotificationSettingsAvatarView.swift new file mode 100644 index 000000000..c9bf9af89 --- /dev/null +++ b/Riot/Modules/Room/NotificationSettings/RoomNotificationSettingsAvatarView.swift @@ -0,0 +1,51 @@ +// +// Copyright 2021 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation +import Reusable + +class RoomNotificationSettingsAvatarView: UIView { + + @IBOutlet weak var avatarView: MXKImageView! + @IBOutlet weak var nameLabel: UILabel! + + func configure(viewData: AvatarViewDataProtocol) { + let avatarImage = AvatarGenerator.generateAvatar(forMatrixItem: viewData.matrixItemId, withDisplayName: viewData.displayName) + + if let avatarUrl = viewData.avatarUrl { + avatarView.enableInMemoryCache = true + + avatarView.setImageURI(avatarUrl, + withType: nil, + andImageOrientation: .up, + toFitViewSize: avatarView.frame.size, + with: MXThumbnailingMethodCrop, + previewImage: avatarImage, + mediaManager: viewData.mediaManager) + } else { + avatarView.image = avatarImage + } + nameLabel.text = viewData.displayName + } +} + +extension RoomNotificationSettingsAvatarView: NibLoadable { } +extension RoomNotificationSettingsAvatarView: Themable { + func update(theme: Theme) { + nameLabel?.font = theme.fonts.title3SB + nameLabel?.textColor = theme.textPrimaryColor + } +} diff --git a/Riot/Modules/Room/NotificationSettings/RoomNotificationSettingsAvatarView.xib b/Riot/Modules/Room/NotificationSettings/RoomNotificationSettingsAvatarView.xib new file mode 100644 index 000000000..b9c21f892 --- /dev/null +++ b/Riot/Modules/Room/NotificationSettings/RoomNotificationSettingsAvatarView.xib @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Riot/Modules/Room/NotificationSettings/RoomNotificationSettingsBridgePresenter.swift b/Riot/Modules/Room/NotificationSettings/RoomNotificationSettingsBridgePresenter.swift new file mode 100644 index 000000000..0be2f18a1 --- /dev/null +++ b/Riot/Modules/Room/NotificationSettings/RoomNotificationSettingsBridgePresenter.swift @@ -0,0 +1,100 @@ +// +// Copyright 2021 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +import Foundation + +@objc protocol RoomNotificationSettingsCoordinatorBridgePresenterDelegate { + func roomNotificationSettingsCoordinatorBridgePresenterDelegateDidComplete(_ coordinatorBridgePresenter: RoomNotificationSettingsCoordinatorBridgePresenter) +} + +/// RoomNotificationSettingsCoordinatorBridgePresenter enables to start RoomNotificationSettingsCoordinator from a view controller. +/// This bridge is used while waiting for global usage of coordinator pattern. +/// It breaks the Coordinator abstraction and it has been introduced for Objective-C compatibility (mainly for integration in legacy view controllers). +/// Each bridge should be removed once the underlying Coordinator has been integrated by another Coordinator. +@objcMembers +final class RoomNotificationSettingsCoordinatorBridgePresenter: NSObject { + + // MARK: - Properties + + // MARK: Private + + private let room: MXRoom + private var coordinator: RoomNotificationSettingsCoordinator? + + // MARK: Public + + weak var delegate: RoomNotificationSettingsCoordinatorBridgePresenterDelegate? + + // MARK: - Setup + + init(room: MXRoom) { + self.room = room + super.init() + } + + // MARK: - Public + + // NOTE: Default value feature is not compatible with Objective-C. + // func present(from viewController: UIViewController, animated: Bool) { + // self.present(from: viewController, animated: animated) + // } + + func present(from viewController: UIViewController, animated: Bool) { + let roomNotificationSettingsCoordinator = RoomNotificationSettingsCoordinator(room: room) + roomNotificationSettingsCoordinator.delegate = self + let presentable = roomNotificationSettingsCoordinator.toPresentable() + let navigationController = RiotNavigationController(rootViewController: presentable) + navigationController.modalPresentationStyle = .formSheet + presentable.presentationController?.delegate = self + viewController.present(navigationController, animated: animated, completion: nil) + roomNotificationSettingsCoordinator.start() + + self.coordinator = roomNotificationSettingsCoordinator + } + + func dismiss(animated: Bool, completion: (() -> Void)?) { + guard let coordinator = self.coordinator else { + return + } + coordinator.toPresentable().dismiss(animated: animated) { + self.coordinator = nil + + if let completion = completion { + completion() + } + } + } +} + +// MARK: - RoomNotificationSettingsCoordinatorDelegate +extension RoomNotificationSettingsCoordinatorBridgePresenter: RoomNotificationSettingsCoordinatorDelegate { + func roomNotificationSettingsCoordinatorDidCancel(_ coordinator: RoomNotificationSettingsCoordinatorType) { + self.delegate?.roomNotificationSettingsCoordinatorBridgePresenterDelegateDidComplete(self) + } + + func roomNotificationSettingsCoordinatorDidComplete(_ coordinator: RoomNotificationSettingsCoordinatorType) { + self.delegate?.roomNotificationSettingsCoordinatorBridgePresenterDelegateDidComplete(self) + } +} + +// MARK: - UIAdaptivePresentationControllerDelegate + +extension RoomNotificationSettingsCoordinatorBridgePresenter: UIAdaptivePresentationControllerDelegate { + + func roomNotificationSettingsCoordinatorDidComplete(_ presentationController: UIPresentationController) { + self.delegate?.roomNotificationSettingsCoordinatorBridgePresenterDelegateDidComplete(self) + } + +} diff --git a/Riot/Modules/Room/NotificationSettings/RoomNotificationSettingsCellViewData.swift b/Riot/Modules/Room/NotificationSettings/RoomNotificationSettingsCellViewData.swift new file mode 100644 index 000000000..2b50835d1 --- /dev/null +++ b/Riot/Modules/Room/NotificationSettings/RoomNotificationSettingsCellViewData.swift @@ -0,0 +1,22 @@ +// +// Copyright 2021 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation + +struct RoomNotificationSettingsCellViewData { + let notificicationState: RoomNotificationState + let selected: Bool +} diff --git a/Riot/Modules/Room/NotificationSettings/RoomNotificationSettingsCoordinator.swift b/Riot/Modules/Room/NotificationSettings/RoomNotificationSettingsCoordinator.swift new file mode 100644 index 000000000..2d8e48cee --- /dev/null +++ b/Riot/Modules/Room/NotificationSettings/RoomNotificationSettingsCoordinator.swift @@ -0,0 +1,75 @@ +// File created from ScreenTemplate +// $ createScreen.sh Room/NotificationSettings RoomNotificationSettings +/* + Copyright 2021 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import Foundation +import UIKit + +final class RoomNotificationSettingsCoordinator: RoomNotificationSettingsCoordinatorType { + + // MARK: - Properties + + // MARK: Private + private var roomNotificationSettingsViewModel: RoomNotificationSettingsViewModelType + private let roomNotificationSettingsViewController: RoomNotificationSettingsViewController + + // MARK: Public + + // Must be used only internally + var childCoordinators: [Coordinator] = [] + + weak var delegate: RoomNotificationSettingsCoordinatorDelegate? + + // MARK: - Setup + + init(room: MXRoom, showAvatar: Bool = true) { + let repository = RoomNotificationSettingsService(room: room) + + let avatarData = showAvatar ? RoomAvatarViewData( + roomId: room.roomId, + displayName: room.summary.displayname, + avatarUrl: room.summary.avatar, + mediaManager: room.mxSession.mediaManager + ) : nil + let roomNotificationSettingsViewModel = RoomNotificationSettingsViewModel(roomNotificationService: repository, roomEncrypted: room.summary.isEncrypted, avatarViewData: avatarData) + let roomNotificationSettingsViewController = RoomNotificationSettingsViewController.instantiate(with: roomNotificationSettingsViewModel) + self.roomNotificationSettingsViewModel = roomNotificationSettingsViewModel + self.roomNotificationSettingsViewController = roomNotificationSettingsViewController + } + + // MARK: - Public methods + + func start() { + self.roomNotificationSettingsViewModel.coordinatorDelegate = self + } + + func toPresentable() -> UIViewController { + return self.roomNotificationSettingsViewController + } +} + +// MARK: - RoomNotificationSettingsViewModelCoordinatorDelegate +extension RoomNotificationSettingsCoordinator: RoomNotificationSettingsViewModelCoordinatorDelegate { + + func roomNotificationSettingsViewModelDidComplete(_ viewModel: RoomNotificationSettingsViewModelType) { + self.delegate?.roomNotificationSettingsCoordinatorDidComplete(self) + } + + func roomNotificationSettingsViewModelDidCancel(_ viewModel: RoomNotificationSettingsViewModelType) { + self.delegate?.roomNotificationSettingsCoordinatorDidCancel(self) + } +} diff --git a/Riot/Modules/Room/NotificationSettings/RoomNotificationSettingsCoordinatorType.swift b/Riot/Modules/Room/NotificationSettings/RoomNotificationSettingsCoordinatorType.swift new file mode 100644 index 000000000..ac0c6288b --- /dev/null +++ b/Riot/Modules/Room/NotificationSettings/RoomNotificationSettingsCoordinatorType.swift @@ -0,0 +1,29 @@ +// File created from ScreenTemplate +// $ createScreen.sh Room/NotificationSettings RoomNotificationSettings +/* + Copyright 2021 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import Foundation + +protocol RoomNotificationSettingsCoordinatorDelegate: AnyObject { + func roomNotificationSettingsCoordinatorDidComplete(_ coordinator: RoomNotificationSettingsCoordinatorType) + func roomNotificationSettingsCoordinatorDidCancel(_ coordinator: RoomNotificationSettingsCoordinatorType) +} + +/// `RoomNotificationSettingsCoordinatorType` is a protocol describing a Coordinator that handles changes to the room navigation settings navigation flow. +protocol RoomNotificationSettingsCoordinatorType: Coordinator, Presentable { + var delegate: RoomNotificationSettingsCoordinatorDelegate? { get } +} diff --git a/Riot/Modules/Room/NotificationSettings/RoomNotificationSettingsFooter.swift b/Riot/Modules/Room/NotificationSettings/RoomNotificationSettingsFooter.swift new file mode 100644 index 000000000..db5285192 --- /dev/null +++ b/Riot/Modules/Room/NotificationSettings/RoomNotificationSettingsFooter.swift @@ -0,0 +1,60 @@ +// +// Copyright 2021 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import UIKit +import Reusable + +class RoomNotificationSettingsFooter: UITableViewHeaderFooterView { + + struct State { + let showEncryptedNotice: Bool + let showAccountLink: Bool + } + + @IBOutlet weak var label: UILabel! + + func update(footerState: State) { + + // Don't include link until global settings in place +// let paragraphStyle = NSMutableParagraphStyle() +// paragraphStyle.lineHeightMultiple = 1.16 +// let paragraphAttributes: [NSAttributedString.Key: Any] = [ +// NSAttributedString.Key.kern: -0.08, +// NSAttributedString.Key.paragraphStyle: paragraphStyle, +// NSAttributedString.Key.font: UIFont.systemFont(ofSize: 13.0) +// ] +// let linkStr = VectorL10n.roomNotifsSettingsAccountSettings +// let formatStr = VectorL10n.roomNotifsSettingsManageNotifications(linkStr) +// +// let formattedStr = String(format: formatStr, arguments: [linkStr]) +// let footer0 = NSMutableAttributedString(string: formattedStr, attributes: paragraphAttributes) +// let linkRange = (footer0.string as NSString).range(of: linkStr) +// footer0.addAttribute(NSAttributedString.Key.link, value: Constants.linkToAccountSettings, range: linkRange) + + label.text = footerState.showEncryptedNotice ? VectorL10n.roomNotifsSettingsEncryptedRoomNotice : nil + + } +} + + +extension RoomNotificationSettingsFooter: NibReusable {} +extension RoomNotificationSettingsFooter: Themable { + + func update(theme: Theme) { + contentView.backgroundColor = theme.headerBackgroundColor + label.textColor = theme.headerTextSecondaryColor + } +} diff --git a/Riot/Modules/Room/NotificationSettings/RoomNotificationSettingsFooter.xib b/Riot/Modules/Room/NotificationSettings/RoomNotificationSettingsFooter.xib new file mode 100644 index 000000000..48895dca9 --- /dev/null +++ b/Riot/Modules/Room/NotificationSettings/RoomNotificationSettingsFooter.xib @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Riot/Modules/Room/NotificationSettings/RoomNotificationSettingsSerivceType.swift b/Riot/Modules/Room/NotificationSettings/RoomNotificationSettingsSerivceType.swift new file mode 100644 index 000000000..22961ad03 --- /dev/null +++ b/Riot/Modules/Room/NotificationSettings/RoomNotificationSettingsSerivceType.swift @@ -0,0 +1,27 @@ +// +// Copyright 2021 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation + +typealias UpdateRoomNotificationStateCompletion = () -> Void +typealias RoomNotificationStateCallback = (RoomNotificationState) -> Void + +protocol RoomNotificationSettingsServiceType { + + func observeNotificationState(listener: @escaping RoomNotificationStateCallback) + func update(state: RoomNotificationState, completion: @escaping UpdateRoomNotificationStateCompletion) + var notificationState: RoomNotificationState { get } +} diff --git a/Riot/Modules/Room/NotificationSettings/RoomNotificationSettingsViewAction.swift b/Riot/Modules/Room/NotificationSettings/RoomNotificationSettingsViewAction.swift new file mode 100644 index 000000000..5d42f64fa --- /dev/null +++ b/Riot/Modules/Room/NotificationSettings/RoomNotificationSettingsViewAction.swift @@ -0,0 +1,26 @@ +// File created from ScreenTemplate +// $ createScreen.sh Room/NotificationSettings RoomNotificationSettings +/* + Copyright 2021 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import Foundation +/// RoomNotificationSettingsViewController view actions exposed to view model +enum RoomNotificationSettingsViewAction { + case load + case selectNotificationState(RoomNotificationState) + case save + case cancel +} diff --git a/Riot/Modules/Room/NotificationSettings/RoomNotificationSettingsViewController.storyboard b/Riot/Modules/Room/NotificationSettings/RoomNotificationSettingsViewController.storyboard new file mode 100644 index 000000000..8d49ce878 --- /dev/null +++ b/Riot/Modules/Room/NotificationSettings/RoomNotificationSettingsViewController.storyboard @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Riot/Modules/Room/NotificationSettings/RoomNotificationSettingsViewController.swift b/Riot/Modules/Room/NotificationSettings/RoomNotificationSettingsViewController.swift new file mode 100644 index 000000000..3e2adf89e --- /dev/null +++ b/Riot/Modules/Room/NotificationSettings/RoomNotificationSettingsViewController.swift @@ -0,0 +1,233 @@ +// File created from ScreenTemplate +// $ createScreen.sh Room/NotificationSettings RoomNotificationSettings +/* + Copyright 2021 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import UIKit + +final class RoomNotificationSettingsViewController: UIViewController { + + // MARK: - Properties + + private enum Constants { + static let linkToAccountSettings = "linkToAccountSettings" + } + + // MARK: Outlets + + @IBOutlet private weak var mainTableView: UITableView! + + // MARK: Private + + private var viewModel: RoomNotificationSettingsViewModelType! + private var theme: Theme! + private var errorPresenter: MXKErrorPresentation! + private var activityPresenter: ActivityIndicatorPresenter! + private lazy var avatarView: RoomNotificationSettingsAvatarView = { + RoomNotificationSettingsAvatarView.loadFromNib() + }() + + private struct Row { + var cellViewData: RoomNotificationSettingsCellViewData + var action: (() -> Void)? + } + + private struct Section { + var title: String + var rows: [Row] + var footerState: RoomNotificationSettingsFooter.State + } + + private var sections: [Section] = [] { + didSet { + mainTableView.reloadData() + } + } + + private var viewState: RoomNotificationSettingsViewStateType! + + // MARK: - Setup + + class func instantiate(with viewModel: RoomNotificationSettingsViewModelType) -> RoomNotificationSettingsViewController { + let viewController = StoryboardScene.RoomNotificationSettingsViewController.initialScene.instantiate() + viewController.viewModel = viewModel + viewController.theme = ThemeService.shared().theme + return viewController + } + + // MARK: - Life cycle + + override func viewDidLoad() { + super.viewDidLoad() + + // Do any additional setup after loading the view. + + setupViews() + activityPresenter = ActivityIndicatorPresenter() + errorPresenter = MXKErrorAlertPresentation() + + registerThemeServiceDidChangeThemeNotification() + update(theme: theme) + + viewModel.viewDelegate = self + viewModel.process(viewAction: .load) + } + + override var preferredStatusBarStyle: UIStatusBarStyle { + return theme.statusBarStyle + } + + // MARK: - Private + + private func update(theme: Theme) { + self.theme = theme + + view.backgroundColor = theme.headerBackgroundColor + mainTableView.backgroundColor = theme.headerBackgroundColor + + if let navigationBar = navigationController?.navigationBar { + theme.applyStyle(onNavigationBar: navigationBar) + } + mainTableView.reloadData() + } + + private func registerThemeServiceDidChangeThemeNotification() { + NotificationCenter.default.addObserver(self, selector: #selector(themeDidChange), name: .themeServiceDidChangeTheme, object: nil) + } + + @objc private func themeDidChange() { + update(theme: ThemeService.shared().theme) + } + + private func setupViews() { + + self.title = VectorL10n.roomDetailsNotifs + let doneBarButtonItem = MXKBarButtonItem(title: VectorL10n.roomNotifsSettingsDoneAction, style: .plain) { [weak self] in + self?.viewModel.process(viewAction: .save) + } + + let cancelBarButtonItem = MXKBarButtonItem(title: VectorL10n.roomNotifsSettingsCancelAction, style: .plain) { [weak self] in + self?.viewModel.process(viewAction: .cancel) + } + + if navigationController?.navigationBar.backItem == nil { + navigationItem.leftBarButtonItem = cancelBarButtonItem + } + navigationItem.rightBarButtonItem = doneBarButtonItem + mainTableView.register(cellType: RoomNotificationSettingsCell.self) + mainTableView.register(headerFooterViewType: RoomNotificationSettingsFooter.self) + mainTableView.register(headerFooterViewType: TitleHeaderView.self) + mainTableView.sectionFooterHeight = UITableView.automaticDimension + mainTableView.sectionHeaderHeight = UITableView.automaticDimension + mainTableView.estimatedSectionFooterHeight = 50 + mainTableView.estimatedSectionHeaderHeight = 30 + } + + private func render(viewState: RoomNotificationSettingsViewStateType) { + + if viewState.saving { + activityPresenter.presentActivityIndicator(on: view, animated: true) + } else { + activityPresenter.removeCurrentActivityIndicator(animated: true) + } + self.viewState = viewState + if let avatarData = viewState.avatarData { + mainTableView.tableHeaderView = avatarView + avatarView.configure(viewData: avatarData) + avatarView.update(theme: theme) + } + updateSections() + } + + private func updateSections() { + let rows = viewState.notificationOptions.map({ (setting) -> Row in + let cellViewData = RoomNotificationSettingsCellViewData(notificicationState: setting, selected: viewState.notificationState == setting) + return Row(cellViewData: cellViewData, + action: { + self.viewModel.process(viewAction: .selectNotificationState(setting)) + }) + }) + let footerState = RoomNotificationSettingsFooter.State(showEncryptedNotice: viewState.roomEncrypted, showAccountLink: false) + let section0 = Section(title: VectorL10n.roomNotifsSettingsNotifyMeFor, rows: rows, footerState: footerState) + sections = [ + section0 + ] + } +} + +// MARK: - UITableViewDataSource +extension RoomNotificationSettingsViewController: UITableViewDataSource { + + func numberOfSections(in tableView: UITableView) -> Int { + return sections.count + } + + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return sections[section].rows.count + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let row = sections[indexPath.section].rows[indexPath.row] + let cell: RoomNotificationSettingsCell = tableView.dequeueReusableCell(for: indexPath) + cell.update(state: row.cellViewData) + cell.update(theme: theme) + return cell + } + + func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { + return UITableView.automaticDimension + } + + func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat { + return UITableView.automaticDimension + } + +} + +// MARK: - UITableViewDelegate +extension RoomNotificationSettingsViewController: UITableViewDelegate { + + func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { + guard let headerView: TitleHeaderView = tableView.dequeueReusableHeaderFooterView() else { return nil } + headerView.update(title: sections[section].title) + headerView.update(theme: theme) + return headerView + } + + func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? { + guard let footerView: RoomNotificationSettingsFooter = tableView.dequeueReusableHeaderFooterView() else { return nil } + let footerState = sections[section].footerState + footerView.update(footerState: footerState) + footerView.update(theme: theme) + return footerView + } + + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + tableView.deselectRow(at: indexPath, animated: true) + + let row = sections[indexPath.section].rows[indexPath.row] + row.action?() + } + +} + +// MARK: - RoomNotificationSettingsViewModelViewDelegate +extension RoomNotificationSettingsViewController: RoomNotificationSettingsViewModelViewDelegate { + + func roomNotificationSettingsViewModel(_ viewModel: RoomNotificationSettingsViewModelType, didUpdateViewState viewSate: RoomNotificationSettingsViewStateType) { + render(viewState: viewSate) + } +} diff --git a/Riot/Modules/Room/NotificationSettings/RoomNotificationSettingsViewModel.swift b/Riot/Modules/Room/NotificationSettings/RoomNotificationSettingsViewModel.swift new file mode 100644 index 000000000..e9f5729df --- /dev/null +++ b/Riot/Modules/Room/NotificationSettings/RoomNotificationSettingsViewModel.swift @@ -0,0 +1,89 @@ +// File created from ScreenTemplate +// $ createScreen.sh Room/NotificationSettings RoomNotificationSettings +/* + Copyright 2021 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import Foundation + +final class RoomNotificationSettingsViewModel: RoomNotificationSettingsViewModelType { + + // MARK: - Properties + + // MARK: Private + + private let roomNotificationService: RoomNotificationSettingsServiceType + private var state: RoomNotificationSettingsViewState { + willSet { + update(viewState: newValue) + } + } + // MARK: Public + + weak var viewDelegate: RoomNotificationSettingsViewModelViewDelegate? + + weak var coordinatorDelegate: RoomNotificationSettingsViewModelCoordinatorDelegate? + + // MARK: - Setup + + init(roomNotificationService: RoomNotificationSettingsServiceType, roomEncrypted: Bool, avatarViewData: AvatarViewDataProtocol?) { + self.roomNotificationService = roomNotificationService + + let notificationState = Self.mapNotificationStateOnRead(encrypted: roomEncrypted, state: roomNotificationService.notificationState) + self.state = RoomNotificationSettingsViewState(roomEncrypted: roomEncrypted, saving: false, notificationState: notificationState, avatarData: avatarViewData) + self.roomNotificationService.observeNotificationState { [weak self] state in + guard let self = self else { return } + + self.state.notificationState = Self.mapNotificationStateOnRead(encrypted: roomEncrypted, state: state) + } + } + + // MARK: - Public + + func process(viewAction: RoomNotificationSettingsViewAction) { + switch viewAction { + case .load: + update(viewState: self.state) + case .selectNotificationState(let state): + self.state.notificationState = state + case .save: + self.state.saving = true + roomNotificationService.update(state: state.notificationState) { [weak self] in + guard let self = self else { return } + self.state.saving = false + self.coordinatorDelegate?.roomNotificationSettingsViewModelDidComplete(self) + } + case .cancel: + coordinatorDelegate?.roomNotificationSettingsViewModelDidCancel(self) + } + } + + // MARK: - Private + + private static func mapNotificationStateOnRead(encrypted: Bool, state: RoomNotificationState) -> RoomNotificationState { + if encrypted, case .mentionsAndKeywordsOnly = state { + // Notifications not supported on encrypted rooms, map mentionsOnly to mute on read + return .mute + } else { + return state + } + } + + private func update(viewState: RoomNotificationSettingsViewStateType) { + self.viewDelegate?.roomNotificationSettingsViewModel(self, didUpdateViewState: viewState) + } + + +} diff --git a/Riot/Modules/Room/NotificationSettings/RoomNotificationSettingsViewModelType.swift b/Riot/Modules/Room/NotificationSettings/RoomNotificationSettingsViewModelType.swift new file mode 100644 index 000000000..e2fe135d4 --- /dev/null +++ b/Riot/Modules/Room/NotificationSettings/RoomNotificationSettingsViewModelType.swift @@ -0,0 +1,37 @@ +// File created from ScreenTemplate +// $ createScreen.sh Room/NotificationSettings RoomNotificationSettings +/* + Copyright 2021 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import Foundation + +protocol RoomNotificationSettingsViewModelViewDelegate: AnyObject { + func roomNotificationSettingsViewModel(_ viewModel: RoomNotificationSettingsViewModelType, didUpdateViewState viewState: RoomNotificationSettingsViewStateType) +} + +protocol RoomNotificationSettingsViewModelCoordinatorDelegate: AnyObject { + func roomNotificationSettingsViewModelDidComplete(_ viewModel: RoomNotificationSettingsViewModelType) + func roomNotificationSettingsViewModelDidCancel(_ viewModel: RoomNotificationSettingsViewModelType) +} + +/// Protocol describing the view model used by `RoomNotificationSettingsViewController` +protocol RoomNotificationSettingsViewModelType { + + var viewDelegate: RoomNotificationSettingsViewModelViewDelegate? { get set } + var coordinatorDelegate: RoomNotificationSettingsViewModelCoordinatorDelegate? { get set } + + func process(viewAction: RoomNotificationSettingsViewAction) +} diff --git a/Riot/Modules/Room/NotificationSettings/RoomNotificationSettingsViewStateType.swift b/Riot/Modules/Room/NotificationSettings/RoomNotificationSettingsViewStateType.swift new file mode 100644 index 000000000..56de53b13 --- /dev/null +++ b/Riot/Modules/Room/NotificationSettings/RoomNotificationSettingsViewStateType.swift @@ -0,0 +1,42 @@ +// File created from ScreenTemplate +// $ createScreen.sh Room/NotificationSettings RoomNotificationSettings +/* + Copyright 2021 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import Foundation + +/// RoomNotificationSettingsViewController view state +struct RoomNotificationSettingsViewState: RoomNotificationSettingsViewStateType { + let roomEncrypted: Bool + var saving: Bool + var notificationState: RoomNotificationState + var notificationOptions: [RoomNotificationState] { + if roomEncrypted { + return [.all, .mute] + } else { + return RoomNotificationState.allCases + } + } + let avatarData: AvatarViewDataProtocol? +} + +protocol RoomNotificationSettingsViewStateType { + var saving: Bool { get } + var roomEncrypted: Bool { get } + var notificationOptions: [RoomNotificationState] { get } + var notificationState: RoomNotificationState { get } + var avatarData: AvatarViewDataProtocol? { get } +} diff --git a/Riot/Modules/Room/NotificationSettings/RoomNotificationState.swift b/Riot/Modules/Room/NotificationSettings/RoomNotificationState.swift new file mode 100644 index 000000000..054993117 --- /dev/null +++ b/Riot/Modules/Room/NotificationSettings/RoomNotificationState.swift @@ -0,0 +1,23 @@ +// +// Copyright 2021 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation + +enum RoomNotificationState: CaseIterable { + case all + case mentionsAndKeywordsOnly + case mute +} diff --git a/Riot/Modules/Room/NotificationSettings/RoomNotificationsSettingsCell.swift b/Riot/Modules/Room/NotificationSettings/RoomNotificationsSettingsCell.swift new file mode 100644 index 000000000..343c7bba4 --- /dev/null +++ b/Riot/Modules/Room/NotificationSettings/RoomNotificationsSettingsCell.swift @@ -0,0 +1,56 @@ +// +// Copyright 2021 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +import UIKit +import Reusable + +class RoomNotificationSettingsCell: UITableViewCell { + + func update(state: RoomNotificationSettingsCellViewData) { + textLabel?.text = state.notificicationState.title + if state.selected { + accessoryView = UIImageView(image: Asset.Images.checkmark.image) + } else { + accessoryView = nil + } + } +} + +extension RoomNotificationSettingsCell: Reusable {} + +extension RoomNotificationSettingsCell: Themable { + func update(theme: Theme) { + textLabel?.font = theme.fonts.body + textLabel?.textColor = theme.textPrimaryColor + backgroundColor = theme.backgroundColor + contentView.backgroundColor = .clear + tintColor = theme.tintColor + selectedBackgroundView = UIView() + selectedBackgroundView?.backgroundColor = theme.selectedBackgroundColor + } +} + +fileprivate extension RoomNotificationState { + var title: String { + switch self { + case .all: + return VectorL10n.roomNotifsSettingsAllMessages + case .mentionsAndKeywordsOnly: + return VectorL10n.roomNotifsSettingsMentionsAndKeywords + case .mute: + return VectorL10n.roomNotifsSettingsNone + } + } +} diff --git a/Riot/Modules/Room/NotificationSettings/RoomNotificationsSettingsService.swift b/Riot/Modules/Room/NotificationSettings/RoomNotificationsSettingsService.swift new file mode 100644 index 000000000..b62c7ea60 --- /dev/null +++ b/Riot/Modules/Room/NotificationSettings/RoomNotificationsSettingsService.swift @@ -0,0 +1,331 @@ +// +// Copyright 2021 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation + +final class RoomNotificationSettingsService: RoomNotificationSettingsServiceType { + + typealias Completion = () -> Void + + // MARK: - Properties + + // MARK: Private + + private let room: MXRoom + + private var notificationCenterDidUpdateObserver: NSObjectProtocol? + private var notificationCenterDidFailObserver: NSObjectProtocol? + + private var observers: [ObjectIdentifier] = [] + + // MARK: Public + + var notificationState: RoomNotificationState { + room.notificationState + } + + // MARK: - Setup + + init(room: MXRoom) { + self.room = room + } + + deinit { + observers.forEach(NotificationCenter.default.removeObserver) + } + + // MARK: - Public + + func observeNotificationState(listener: @escaping RoomNotificationStateCallback) { + + let observer = NotificationCenter.default.addObserver( + forName: NSNotification.Name(rawValue: kMXNotificationCenterDidUpdateRules), + object: nil, + queue: OperationQueue.main) { [weak self] _ in + guard let self = self else { return } + listener(self.room.notificationState) + } + observers += [ObjectIdentifier(observer)] + } + + func update(state: RoomNotificationState, completion: @escaping Completion) { + switch state { + case .all: + allMessages(completion: completion) + case .mentionsAndKeywordsOnly: + mentionsOnly(completion: completion) + case .mute: + mute(completion: completion) + } + } + + // MARK: - Private + + private func mute(completion: @escaping Completion) { + guard !room.isMuted else { + completion() + return + } + + if let rule = room.roomPushRule { + removePushRule(rule: rule) { + self.mute(completion: completion) + } + return + } + + guard let rule = room.overridePushRule else { + self.addPushRuleToMute(completion: completion) + return + } + + guard notificationCenterDidUpdateObserver == nil else { + MXLog.debug("[RoomNotificationSettingsService] Request in progress: ignore push rule update") + completion() + return + } + + // if the user defined one, use it + if rule.actionsContains(actionType: MXPushRuleActionTypeDontNotify) { + enablePushRule(rule: rule, completion: completion) + } else { + removePushRule(rule: rule) { + self.addPushRuleToMute(completion: completion) + } + } + } + + private func mentionsOnly(completion: @escaping Completion) { + guard !room.isMentionsOnly else { + completion() + return + } + + if let rule = room.overridePushRule, room.isMuted { + removePushRule(rule: rule) { + self.mentionsOnly(completion: completion) + } + return + } + + guard let rule = room.roomPushRule else { + addPushRuleToMentionOnly(completion: completion) + return + } + + guard notificationCenterDidUpdateObserver == nil else { + MXLog.debug("[MXRoom+Riot] Request in progress: ignore push rule update") + completion() + return + } + + // if the user defined one, use it + if rule.actionsContains(actionType: MXPushRuleActionTypeDontNotify) { + enablePushRule(rule: rule, completion: completion) + } else { + removePushRule(rule: rule) { + self.addPushRuleToMentionOnly(completion: completion) + } + } + + } + + private func allMessages(completion: @escaping Completion) { + if !room.isMentionsOnly && !room.isMuted { + completion() + return + } + + if let rule = room.overridePushRule, room.isMuted { + removePushRule(rule: rule) { + self.allMessages(completion: completion) + } + return + } + + if let rule = room.roomPushRule, room.isMentionsOnly { + removePushRule(rule: rule, completion: completion) + } + } + + private func addPushRuleToMentionOnly(completion: @escaping Completion) { + handleUpdateCallback(completion) { [weak self] in + guard let self = self else { return true } + return self.room.roomPushRule != nil + } + handleFailureCallback(completion) + + room.mxSession.notificationCenter.addRoomRule( + room.roomId, + notify: false, + sound: false, + highlight: false) + } + + private func addPushRuleToMute(completion: @escaping Completion) { + guard let roomId = room.roomId else { + return + } + handleUpdateCallback(completion) { [weak self] in + guard let self = self else { return true } + return self.room.overridePushRule != nil + } + handleFailureCallback(completion) + + room.mxSession.notificationCenter.addOverrideRule( + withId: roomId, + conditions: [["kind": "event_match", "key": "room_id", "pattern": roomId]], + notify: false, + sound: false, + highlight: false + ) + } + + private func removePushRule(rule: MXPushRule, completion: @escaping Completion) { + handleUpdateCallback(completion) { [weak self] in + guard let self = self else { return true } + return self.room.mxSession.notificationCenter.rule(byId: rule.ruleId) == nil + } + handleFailureCallback(completion) + + room.mxSession.notificationCenter.removeRule(rule) + } + + private func enablePushRule(rule: MXPushRule, completion: @escaping Completion) { + handleUpdateCallback(completion) { + // No way to check whether this notification concerns the push rule. Consider the change is applied. + return true + } + handleFailureCallback(completion) + + room.mxSession.notificationCenter.enableRule(rule, isEnabled: true) + } + + private func handleUpdateCallback(_ completion: @escaping Completion, releaseCheck: @escaping () -> Bool) { + notificationCenterDidUpdateObserver = NotificationCenter.default.addObserver( + forName: NSNotification.Name(rawValue: kMXNotificationCenterDidUpdateRules), + object: nil, + queue: OperationQueue.main) { [weak self] _ in + guard let self = self else { return } + if releaseCheck() { + self.removeObservers() + completion() + } + } + } + + private func handleFailureCallback(_ completion: @escaping Completion) { + notificationCenterDidFailObserver = NotificationCenter.default.addObserver( + forName: NSNotification.Name(rawValue: kMXNotificationCenterDidFailRulesUpdate), + object: nil, + queue: OperationQueue.main) { [weak self] _ in + guard let self = self else { return } + self.removeObservers() + completion() + } + } + + func removeObservers() { + if let observer = self.notificationCenterDidUpdateObserver { + NotificationCenter.default.removeObserver(observer) + self.notificationCenterDidUpdateObserver = nil + } + + if let observer = self.notificationCenterDidFailObserver { + NotificationCenter.default.removeObserver(observer) + self.notificationCenterDidFailObserver = nil + } + } +} + +// We could move these to their own file and make available in global namespace or move to sdk but they are only used here at the moment +fileprivate extension MXRoom { + + typealias Completion = () -> Void + func getRoomRule(from rules: [Any]) -> MXPushRule? { + guard let pushRules = rules as? [MXPushRule] else { + return nil + } + + return pushRules.first(where: { self.roomId == $0.ruleId }) + } + + var overridePushRule: MXPushRule? { + guard let overrideRules = mxSession.notificationCenter.rules.global.override else { + return nil + } + return getRoomRule(from: overrideRules) + } + + var roomPushRule: MXPushRule? { + guard let roomRules = mxSession.notificationCenter.rules.global.room else { + return nil + } + return getRoomRule(from: roomRules) + } + + var notificationState: RoomNotificationState { + + if isMuted { + return .mute + } + if isMentionsOnly { + return .mentionsAndKeywordsOnly + } + return .all + } + + var isMuted: Bool { + // Check whether an override rule has been defined with the roomm id as rule id. + // This kind of rule is created to mute the room + guard let rule = self.overridePushRule, + rule.actionsContains(actionType: MXPushRuleActionTypeDontNotify), + rule.conditionIsEnabled(kind: .eventMatch, for: roomId) else { + return false + } + return rule.enabled + } + + var isMentionsOnly: Bool { + // Check push rules at room level + guard let rule = roomPushRule else { return false } + return rule.enabled && rule.actionsContains(actionType: MXPushRuleActionTypeDontNotify) + } + +} + +fileprivate extension MXPushRule { + func actionsContains(actionType: MXPushRuleActionType) -> Bool { + guard let actions = actions as? [MXPushRuleAction] else { + return false + } + return actions.contains(where: { $0.actionType == actionType }) + } + + func conditionIsEnabled(kind: MXPushRuleConditionType, for roomId: String) -> Bool { + guard let conditions = conditions as? [MXPushRuleCondition] else { + return false + } + let ruleContainsCondition = conditions.contains { condition in + guard case kind = MXPushRuleConditionType(identifier: condition.kind), + let key = condition.parameters["key"] as? String, + let pattern = condition.parameters["pattern"] as? String + else { return false } + return key == "room_id" && pattern == roomId + } + return ruleContainsCondition && enabled + } +} diff --git a/Riot/Modules/Room/ReactionHistory/ReactionHistoryCoordinatorType.swift b/Riot/Modules/Room/ReactionHistory/ReactionHistoryCoordinatorType.swift index 420dc1fe2..95a5bfbe8 100644 --- a/Riot/Modules/Room/ReactionHistory/ReactionHistoryCoordinatorType.swift +++ b/Riot/Modules/Room/ReactionHistory/ReactionHistoryCoordinatorType.swift @@ -18,7 +18,7 @@ import Foundation -protocol ReactionHistoryCoordinatorDelegate: class { +protocol ReactionHistoryCoordinatorDelegate: AnyObject { func reactionHistoryCoordinatorDidClose(_ coordinator: ReactionHistoryCoordinatorType) } diff --git a/Riot/Modules/Room/ReactionHistory/ReactionHistoryViewModelType.swift b/Riot/Modules/Room/ReactionHistory/ReactionHistoryViewModelType.swift index 92c300d46..735ee7b16 100644 --- a/Riot/Modules/Room/ReactionHistory/ReactionHistoryViewModelType.swift +++ b/Riot/Modules/Room/ReactionHistory/ReactionHistoryViewModelType.swift @@ -18,11 +18,11 @@ import Foundation -protocol ReactionHistoryViewModelViewDelegate: class { +protocol ReactionHistoryViewModelViewDelegate: AnyObject { func reactionHistoryViewModel(_ viewModel: ReactionHistoryViewModelType, didUpdateViewState viewSate: ReactionHistoryViewState) } -protocol ReactionHistoryViewModelCoordinatorDelegate: class { +protocol ReactionHistoryViewModelCoordinatorDelegate: AnyObject { func reactionHistoryViewModelDidClose(_ viewModel: ReactionHistoryViewModelType) } diff --git a/Riot/Modules/Room/RoomInfo/RoomInfoCoordinator.swift b/Riot/Modules/Room/RoomInfo/RoomInfoCoordinator.swift index f1efb7577..569803fbf 100644 --- a/Riot/Modules/Room/RoomInfo/RoomInfoCoordinator.swift +++ b/Riot/Modules/Room/RoomInfo/RoomInfoCoordinator.swift @@ -137,6 +137,12 @@ final class RoomInfoCoordinator: NSObject, RoomInfoCoordinatorType { return coordinator } + private func createRoomNotificationSettingsCoordinator() -> RoomNotificationSettingsCoordinator { + let coordinator = RoomNotificationSettingsCoordinator(room: room, showAvatar: false) + coordinator.delegate = self + return coordinator + } + private func showRoomDetails(with target: RoomInfoListTarget, animated: Bool) { switch target { case .integrations: @@ -152,8 +158,16 @@ final class RoomInfoCoordinator: NSObject, RoomInfoCoordinatorType { self.navigationRouter.push(search, animated: animated, popCompletion: nil) } }) + case .notifications: + let coordinator = createRoomNotificationSettingsCoordinator() + coordinator.start() + self.add(childCoordinator: coordinator) + self.navigationRouter.push(coordinator, animated: true, popCompletion: nil) default: - segmentedViewController.selectedIndex = target.tabIndex + guard let tabIndex = target.tabIndex else { + fatalError("No settings tab index for this target.") + } + segmentedViewController.selectedIndex = tabIndex if case .settings(let roomSettingsField) = target { roomSettingsViewController?.selectedRoomSettingsField = roomSettingsField @@ -180,6 +194,18 @@ extension RoomInfoCoordinator: RoomInfoListCoordinatorDelegate { extension RoomInfoCoordinator: RoomParticipantsViewControllerDelegate { func roomParticipantsViewController(_ roomParticipantsViewController: RoomParticipantsViewController!, mention member: MXRoomMember!) { + self.navigationRouter.popToRootModule(animated: true) + self.delegate?.roomInfoCoordinator(self, didRequestMentionForMember: member) + } + +} + +extension RoomInfoCoordinator: RoomNotificationSettingsCoordinatorDelegate { + func roomNotificationSettingsCoordinatorDidComplete(_ coordinator: RoomNotificationSettingsCoordinatorType) { + self.navigationRouter.popModule(animated: true) + } + + func roomNotificationSettingsCoordinatorDidCancel(_ coordinator: RoomNotificationSettingsCoordinatorType) { } diff --git a/Riot/Modules/Room/RoomInfo/RoomInfoCoordinatorBridgePresenter.swift b/Riot/Modules/Room/RoomInfo/RoomInfoCoordinatorBridgePresenter.swift index 44a46cbd0..48dc70d3e 100644 --- a/Riot/Modules/Room/RoomInfo/RoomInfoCoordinatorBridgePresenter.swift +++ b/Riot/Modules/Room/RoomInfo/RoomInfoCoordinatorBridgePresenter.swift @@ -20,6 +20,7 @@ import Foundation @objc protocol RoomInfoCoordinatorBridgePresenterDelegate { func roomInfoCoordinatorBridgePresenterDelegateDidComplete(_ coordinatorBridgePresenter: RoomInfoCoordinatorBridgePresenter) + func roomInfoCoordinatorBridgePresenter(_ coordinatorBridgePresenter: RoomInfoCoordinatorBridgePresenter, didRequestMentionForMember member: MXRoomMember) } /// RoomInfoCoordinatorBridgePresenter enables to start RoomInfoCoordinator from a view controller. @@ -115,6 +116,10 @@ extension RoomInfoCoordinatorBridgePresenter: RoomInfoCoordinatorDelegate { self.delegate?.roomInfoCoordinatorBridgePresenterDelegateDidComplete(self) } + func roomInfoCoordinator(_ coordinator: RoomInfoCoordinatorType, didRequestMentionForMember member: MXRoomMember) { + self.delegate?.roomInfoCoordinatorBridgePresenter(self, didRequestMentionForMember: member) + } + } // MARK: - UIAdaptivePresentationControllerDelegate diff --git a/Riot/Modules/Room/RoomInfo/RoomInfoCoordinatorType.swift b/Riot/Modules/Room/RoomInfo/RoomInfoCoordinatorType.swift index c98c7a751..092a3b874 100644 --- a/Riot/Modules/Room/RoomInfo/RoomInfoCoordinatorType.swift +++ b/Riot/Modules/Room/RoomInfo/RoomInfoCoordinatorType.swift @@ -18,8 +18,9 @@ import Foundation -protocol RoomInfoCoordinatorDelegate: class { +protocol RoomInfoCoordinatorDelegate: AnyObject { func roomInfoCoordinatorDidComplete(_ coordinator: RoomInfoCoordinatorType) + func roomInfoCoordinator(_ coordinator: RoomInfoCoordinatorType, didRequestMentionForMember member: MXRoomMember) } /// `RoomInfoCoordinatorType` is a protocol describing a Coordinator that handle keybackup setup navigation flow. diff --git a/Riot/Modules/Room/RoomInfo/RoomInfoList/RoomInfoListCoordinatorType.swift b/Riot/Modules/Room/RoomInfo/RoomInfoList/RoomInfoListCoordinatorType.swift index db953c40b..7979d1aa8 100644 --- a/Riot/Modules/Room/RoomInfo/RoomInfoList/RoomInfoListCoordinatorType.swift +++ b/Riot/Modules/Room/RoomInfo/RoomInfoList/RoomInfoListCoordinatorType.swift @@ -18,7 +18,7 @@ import Foundation -protocol RoomInfoListCoordinatorDelegate: class { +protocol RoomInfoListCoordinatorDelegate: AnyObject { func roomInfoListCoordinator(_ coordinator: RoomInfoListCoordinatorType, wantsToNavigateTo target: RoomInfoListTarget) func roomInfoListCoordinatorDidCancel(_ coordinator: RoomInfoListCoordinatorType) } diff --git a/Riot/Modules/Room/RoomInfo/RoomInfoList/RoomInfoListViewAction.swift b/Riot/Modules/Room/RoomInfo/RoomInfoList/RoomInfoListViewAction.swift index 945d2844a..c6e0c8887 100644 --- a/Riot/Modules/Room/RoomInfo/RoomInfoList/RoomInfoListViewAction.swift +++ b/Riot/Modules/Room/RoomInfo/RoomInfoList/RoomInfoListViewAction.swift @@ -24,25 +24,21 @@ enum RoomInfoListTarget: Equatable { case uploads case integrations case search - - var tabIndex: UInt { - let tabIndex: UInt - + case notifications + + var tabIndex: UInt? { switch self { case .members: - tabIndex = 0 + return 0 case .uploads: - tabIndex = 1 + return 1 case .settings: - tabIndex = 2 - case .integrations: - tabIndex = 3 - case .search: - tabIndex = 4 + return 2 + default: + return nil } - - return tabIndex } + } /// RoomInfoListViewController view actions exposed to view model diff --git a/Riot/Modules/Room/RoomInfo/RoomInfoList/RoomInfoListViewController.swift b/Riot/Modules/Room/RoomInfo/RoomInfoList/RoomInfoListViewController.swift index bfc3396bf..ddc14d605 100644 --- a/Riot/Modules/Room/RoomInfo/RoomInfoList/RoomInfoListViewController.swift +++ b/Riot/Modules/Room/RoomInfo/RoomInfoList/RoomInfoListViewController.swift @@ -150,6 +150,9 @@ final class RoomInfoListViewController: UIViewController { let rowSettings = Row(type: .default, icon: Asset.Images.settingsIcon.image, text: VectorL10n.roomDetailsSettings, accessoryType: .disclosureIndicator) { self.viewModel.process(viewAction: .navigate(target: .settings())) } + let roomNotifications = Row(type: .default, icon: Asset.Images.notifications.image, text: VectorL10n.roomDetailsNotifs, accessoryType: .disclosureIndicator) { + self.viewModel.process(viewAction: .navigate(target: .notifications)) + } let text = viewData.numberOfMembers == 1 ? VectorL10n.roomInfoListOneMember : VectorL10n.roomInfoListSeveralMembers(String(viewData.numberOfMembers)) let rowMembers = Row(type: .default, icon: Asset.Images.userIcon.image, text: text, accessoryType: .disclosureIndicator) { self.viewModel.process(viewAction: .navigate(target: .members)) @@ -165,8 +168,11 @@ final class RoomInfoListViewController: UIViewController { } var rows = [rowSettings] - if (RiotSettings.shared.roomInfoScreenShowIntegrations) - { + + if BuildSettings.roomSettingsScreenShowNotificationsV2 { + rows.append(roomNotifications) + } + if RiotSettings.shared.roomInfoScreenShowIntegrations { rows.append(rowIntegrations) } rows.append(rowMembers) diff --git a/Riot/Modules/Room/RoomInfo/RoomInfoList/RoomInfoListViewModelType.swift b/Riot/Modules/Room/RoomInfo/RoomInfoList/RoomInfoListViewModelType.swift index 439624dea..2610d89a2 100644 --- a/Riot/Modules/Room/RoomInfo/RoomInfoList/RoomInfoListViewModelType.swift +++ b/Riot/Modules/Room/RoomInfo/RoomInfoList/RoomInfoListViewModelType.swift @@ -18,11 +18,11 @@ import Foundation -protocol RoomInfoListViewModelViewDelegate: class { +protocol RoomInfoListViewModelViewDelegate: AnyObject { func roomInfoListViewModel(_ viewModel: RoomInfoListViewModelType, didUpdateViewState viewSate: RoomInfoListViewState) } -protocol RoomInfoListViewModelCoordinatorDelegate: class { +protocol RoomInfoListViewModelCoordinatorDelegate: AnyObject { func roomInfoListViewModelDidCancel(_ viewModel: RoomInfoListViewModelType) func roomInfoListViewModel(_ viewModel: RoomInfoListViewModelType, wantsToNavigateTo target: RoomInfoListTarget) } diff --git a/Riot/Modules/Room/RoomViewController.m b/Riot/Modules/Room/RoomViewController.m index fb4b7db8f..757f187e0 100644 --- a/Riot/Modules/Room/RoomViewController.m +++ b/Riot/Modules/Room/RoomViewController.m @@ -135,7 +135,7 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; @interface RoomViewController () + RoomDataSourceDelegate, RoomCreationModalCoordinatorBridgePresenterDelegate, RoomInfoCoordinatorBridgePresenterDelegate, DialpadViewControllerDelegate, RemoveJitsiWidgetViewDelegate, VoiceMessageControllerDelegate> { // The preview header @@ -240,6 +240,8 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; @property (nonatomic, getter=isActivitiesViewExpanded) BOOL activitiesViewExpanded; @property (nonatomic, getter=isScrollToBottomHidden) BOOL scrollToBottomHidden; +@property (nonatomic, strong) VoiceMessageController *voiceMessageController; + @end @implementation RoomViewController @@ -313,6 +315,9 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; // Show / hide actions button in document preview according BuildSettings self.allowActionsInDocumentPreview = BuildSettings.messageDetailsAllowShare; + + _voiceMessageController = [[VoiceMessageController alloc] initWithThemeService:ThemeService.shared mediaServiceProvider:VoiceMessageMediaServiceProvider.sharedProvider]; + self.voiceMessageController.delegate = self; } - (void)viewDidLoad @@ -386,6 +391,10 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; [self.bubblesTableView registerNib:RoomTypingBubbleCell.nib forCellReuseIdentifier:RoomTypingBubbleCell.defaultReuseIdentifier]; + [self.bubblesTableView registerClass:VoiceMessageBubbleCell.class forCellReuseIdentifier:VoiceMessageBubbleCell.defaultReuseIdentifier]; + [self.bubblesTableView registerClass:VoiceMessageWithoutSenderInfoBubbleCell.class forCellReuseIdentifier:VoiceMessageWithoutSenderInfoBubbleCell.defaultReuseIdentifier]; + [self.bubblesTableView registerClass:VoiceMessageWithPaginationTitleBubbleCell.class forCellReuseIdentifier:VoiceMessageWithPaginationTitleBubbleCell.defaultReuseIdentifier]; + [self vc_removeBackTitle]; [self setupRemoveJitsiWidgetRemoveView]; @@ -607,6 +616,8 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; self.roomDataSource.showReadMarker = YES; self.updateRoomReadMarker = NO; isAppeared = NO; + + [VoiceMessageMediaServiceProvider.sharedProvider stopAllServices]; } - (void)viewDidAppear:(BOOL)animated @@ -987,6 +998,8 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; } [self refreshRoomInputToolbar]; + + [VoiceMessageMediaServiceProvider.sharedProvider setCurrentRoomSummary:dataSource.room.summary]; } - (void)onRoomDataSourceReady @@ -1114,6 +1127,13 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; if (!self.inputToolbarView || ![self.inputToolbarView isMemberOfClass:roomInputToolbarViewClass]) { [super setRoomInputToolbarViewClass:roomInputToolbarViewClass]; + + // The voice message toolbar cannot be set on DisabledInputToolbarView. + if ([self.inputToolbarView isKindOfClass:RoomInputToolbarView.class]) + { + [(RoomInputToolbarView *)self.inputToolbarView setVoiceMessageToolbarView:self.voiceMessageController.voiceMessageToolbarView]; + } + [self updateInputToolBarViewHeight]; } } @@ -2359,6 +2379,16 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; { cellViewClass = RoomGroupCallStatusBubbleCell.class; } + else if (bubbleData.attachment.type == MXKAttachmentTypeVoiceMessage) + { + if (bubbleData.isPaginationFirstBubble) { + cellViewClass = VoiceMessageWithPaginationTitleBubbleCell.class; + } else if (bubbleData.shouldHideSenderInformation) { + cellViewClass = VoiceMessageWithoutSenderInfoBubbleCell.class; + } else { + cellViewClass = VoiceMessageBubbleCell.class; + } + } else if (bubbleData.isIncoming) { if (bubbleData.isAttachmentWithThumbnail) @@ -2718,12 +2748,11 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; [actionIdentifier isEqualToString:RoomGroupCallStatusBubbleCell.answerAction]) { MXWeakify(self); - NSString *appDisplayName = [[NSBundle mainBundle] infoDictionary][@"CFBundleDisplayName"]; // Check app permissions first [MXKTools checkAccessForCall:YES - manualChangeMessageForAudio:[NSString stringWithFormat:[NSBundle mxk_localizedStringForKey:@"microphone_access_not_granted_for_call"], appDisplayName] - manualChangeMessageForVideo:[NSString stringWithFormat:[NSBundle mxk_localizedStringForKey:@"camera_access_not_granted_for_call"], appDisplayName] + manualChangeMessageForAudio:[NSString stringWithFormat:[NSBundle mxk_localizedStringForKey:@"microphone_access_not_granted_for_call"], AppInfo.current.displayName] + manualChangeMessageForVideo:[NSString stringWithFormat:[NSBundle mxk_localizedStringForKey:@"camera_access_not_granted_for_call"], AppInfo.current.displayName] showPopUpInViewController:self completionHandler:^(BOOL granted) { MXStrongifyAndReturnIfNil(self); @@ -3715,12 +3744,10 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; { __weak __typeof(self) weakSelf = self; - NSString *appDisplayName = [[NSBundle mainBundle] infoDictionary][@"CFBundleDisplayName"]; - // Check app permissions first [MXKTools checkAccessForCall:video - manualChangeMessageForAudio:[NSString stringWithFormat:[NSBundle mxk_localizedStringForKey:@"microphone_access_not_granted_for_call"], appDisplayName] - manualChangeMessageForVideo:[NSString stringWithFormat:[NSBundle mxk_localizedStringForKey:@"camera_access_not_granted_for_call"], appDisplayName] + manualChangeMessageForAudio:[NSString stringWithFormat:[NSBundle mxk_localizedStringForKey:@"microphone_access_not_granted_for_call"], AppInfo.current.displayName] + manualChangeMessageForVideo:[NSString stringWithFormat:[NSBundle mxk_localizedStringForKey:@"camera_access_not_granted_for_call"], AppInfo.current.displayName] showPopUpInViewController:self completionHandler:^(BOOL granted) { if (weakSelf) @@ -3936,13 +3963,6 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; [self cancelEventSelection]; } -#pragma mark - RoomParticipantsViewControllerDelegate - -- (void)roomParticipantsViewController:(RoomParticipantsViewController *)roomParticipantsViewController mention:(MXRoomMember*)member -{ - [self mention:member]; -} - #pragma mark - MXKRoomMemberDetailsViewControllerDelegate - (void)roomMemberDetailsViewController:(MXKRoomMemberDetailsViewController *)roomMemberDetailsViewController startChatWithMemberId:(NSString *)matrixId completion:(void (^)(void))completion @@ -5810,7 +5830,7 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; MXWeakify(self); RoomContextualMenuItem *replyMenuItem = [[RoomContextualMenuItem alloc] initWithMenuAction:RoomContextualMenuActionReply]; - replyMenuItem.isEnabled = [self.roomDataSource canReplyToEventWithId:event.eventId]; + replyMenuItem.isEnabled = [self.roomDataSource canReplyToEventWithId:event.eventId] && !self.voiceMessageController.isRecordingAudio; replyMenuItem.action = ^{ MXStrongifyAndReturnIfNil(self); @@ -6056,7 +6076,8 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; RoomInputToolbarView *roomInputToolbarView = [self inputToolbarViewAsRoomInputToolbarView]; if (roomInputToolbarView) { - [roomInputToolbarView sendSelectedVideo:url isPhotoLibraryAsset:NO]; + AVURLAsset *selectedVideo = [AVURLAsset assetWithURL:url]; + [roomInputToolbarView sendSelectedVideoAsset:selectedVideo isPhotoLibraryAsset:NO]; } } @@ -6080,7 +6101,7 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; } } -- (void)mediaPickerCoordinatorBridgePresenter:(MediaPickerCoordinatorBridgePresenter *)coordinatorBridgePresenter didSelectVideoAt:(NSURL *)url +- (void)mediaPickerCoordinatorBridgePresenter:(MediaPickerCoordinatorBridgePresenter *)coordinatorBridgePresenter didSelectVideo:(AVAsset *)videoAsset { [coordinatorBridgePresenter dismissWithAnimated:YES completion:nil]; self.mediaPickerPresenter = nil; @@ -6088,7 +6109,7 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; RoomInputToolbarView *roomInputToolbarView = [self inputToolbarViewAsRoomInputToolbarView]; if (roomInputToolbarView) { - [roomInputToolbarView sendSelectedVideo:url isPhotoLibraryAsset:YES]; + [roomInputToolbarView sendSelectedVideoAsset:videoAsset isPhotoLibraryAsset:YES]; } } @@ -6120,6 +6141,11 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; self.roomInfoCoordinatorBridgePresenter = nil; } +- (void)roomInfoCoordinatorBridgePresenter:(RoomInfoCoordinatorBridgePresenter *)coordinatorBridgePresenter didRequestMentionForMember:(MXRoomMember *)member +{ + [self mention:member]; +} + #pragma mark - RemoveJitsiWidgetViewDelegate - (void)removeJitsiWidgetViewDidCompleteSliding:(RemoveJitsiWidgetView *)view @@ -6153,4 +6179,32 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; }]; } +#pragma mark - VoiceMessageControllerDelegate + +- (void)voiceMessageControllerDidRequestMicrophonePermission:(VoiceMessageController *)voiceMessageController +{ + NSString *message = [NSString stringWithFormat:[NSBundle mxk_localizedStringForKey:@"microphone_access_not_granted_for_voice_message"], AppInfo.current.displayName]; + + [MXKTools checkAccessForMediaType:AVMediaTypeAudio + manualChangeMessage: message + showPopUpInViewController:self completionHandler:^(BOOL granted) { + + }]; +} + +- (void)voiceMessageController:(VoiceMessageController *)voiceMessageController + didRequestSendForFileAtURL:(NSURL *)url + duration:(NSUInteger)duration + samples:(NSArray *)samples + completion:(void (^)(BOOL))completion +{ + [self.roomDataSource sendVoiceMessage:url mimeType:nil duration:duration samples:samples success:^(NSString *eventId) { + MXLogDebug(@"Success with event id %@", eventId); + completion(YES); + } failure:^(NSError *error) { + MXLogError(@"Failed sending voice message"); + completion(NO); + }]; +} + @end diff --git a/Riot/Modules/Room/Settings/RoomSettingsViewController.m b/Riot/Modules/Room/Settings/RoomSettingsViewController.m index 634468fe8..08d18f655 100644 --- a/Riot/Modules/Room/Settings/RoomSettingsViewController.m +++ b/Riot/Modules/Room/Settings/RoomSettingsViewController.m @@ -528,7 +528,10 @@ NSString *const kRoomSettingsAdvancedE2eEnabledCellViewIdentifier = @"kRoomSetti { [sectionMain addRowWithTag:ROOM_SETTINGS_MAIN_SECTION_ROW_DIRECT_CHAT]; } - [sectionMain addRowWithTag:ROOM_SETTINGS_MAIN_SECTION_ROW_MUTE_NOTIFICATIONS]; + if (!BuildSettings.roomSettingsScreenShowNotificationsV2) + { + [sectionMain addRowWithTag:ROOM_SETTINGS_MAIN_SECTION_ROW_MUTE_NOTIFICATIONS]; + } [sectionMain addRowWithTag:ROOM_SETTINGS_MAIN_SECTION_ROW_LEAVE]; [tmpSections addObject:sectionMain]; diff --git a/Riot/Modules/Room/Views/BubbleCells/Call/CallBubbleCellBaseContentView.swift b/Riot/Modules/Room/Views/BubbleCells/Call/CallBubbleCellBaseContentView.swift index cdec7a227..5fbf6e51d 100644 --- a/Riot/Modules/Room/Views/BubbleCells/Call/CallBubbleCellBaseContentView.swift +++ b/Riot/Modules/Room/Views/BubbleCells/Call/CallBubbleCellBaseContentView.swift @@ -32,8 +32,6 @@ class CallBubbleCellBaseContentView: UIView { @IBOutlet weak var avatarImageView: MXKImageView! @IBOutlet weak var callerNameLabel: UILabel! @IBOutlet weak var callIconView: UIImageView! - @IBOutlet weak var callTypeLabel: UILabel! - @IBOutlet weak var dotLabel: UILabel! @IBOutlet private weak var callStatusLabel: UILabel! @IBOutlet private weak var callSummaryHeightConstraint: NSLayoutConstraint! @@ -51,7 +49,6 @@ class CallBubbleCellBaseContentView: UIView { var statusText: String? { didSet { - dotLabel.isHidden = statusText == nil callStatusLabel.text = statusText } } @@ -105,9 +102,7 @@ extension CallBubbleCellBaseContentView: Themable { bgView.backgroundColor = theme.colors.tile callerNameLabel.textColor = theme.textPrimaryColor - callIconView.tintColor = theme.textTertiaryColor - callTypeLabel.textColor = theme.textSecondaryColor - dotLabel.textColor = theme.textSecondaryColor + callIconView.tintColor = theme.textSecondaryColor callStatusLabel.textColor = theme.textSecondaryColor if let bottomContainerView = bottomContainerView as? Themable { diff --git a/Riot/Modules/Room/Views/BubbleCells/Call/CallBubbleCellBaseContentView.xib b/Riot/Modules/Room/Views/BubbleCells/Call/CallBubbleCellBaseContentView.xib index 526f057fa..09df8b2e6 100644 --- a/Riot/Modules/Room/Views/BubbleCells/Call/CallBubbleCellBaseContentView.xib +++ b/Riot/Modules/Room/Views/BubbleCells/Call/CallBubbleCellBaseContentView.xib @@ -1,9 +1,9 @@ - + - + @@ -78,7 +78,7 @@ - + @@ -87,20 +87,8 @@ - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Riot/Modules/Room/VoiceMessages/VoiceMessageToolbarView.swift b/Riot/Modules/Room/VoiceMessages/VoiceMessageToolbarView.swift new file mode 100644 index 000000000..f0b27fd46 --- /dev/null +++ b/Riot/Modules/Room/VoiceMessages/VoiceMessageToolbarView.swift @@ -0,0 +1,402 @@ +// +// Copyright 2021 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import UIKit +import Reusable + +protocol VoiceMessageToolbarViewDelegate: AnyObject { + func voiceMessageToolbarViewDidRequestRecordingStart(_ toolbarView: VoiceMessageToolbarView) + func voiceMessageToolbarViewDidRequestRecordingCancel(_ toolbarView: VoiceMessageToolbarView) + func voiceMessageToolbarViewDidRequestRecordingFinish(_ toolbarView: VoiceMessageToolbarView) + func voiceMessageToolbarViewDidRequestLockedModeRecording(_ toolbarView: VoiceMessageToolbarView) + func voiceMessageToolbarViewDidRequestPlaybackToggle(_ toolbarView: VoiceMessageToolbarView) + func voiceMessageToolbarViewDidRequestSend(_ toolbarView: VoiceMessageToolbarView) +} + +enum VoiceMessageToolbarViewUIState { + case idle + case record + case lockedModeRecord + case lockedModePlayback +} + +struct VoiceMessageToolbarViewDetails { + var state: VoiceMessageToolbarViewUIState = .idle + var elapsedTime: String = "" + var audioSamples: [Float] = [] + var isPlaying: Bool = false + var progress: Double = 0.0 + var toastMessage: String? +} + +class VoiceMessageToolbarView: PassthroughView, NibLoadable, Themable, UIGestureRecognizerDelegate, VoiceMessagePlaybackViewDelegate { + + private enum Constants { + static let longPressMinimumDuration: TimeInterval = 0.0 + static let animationDuration: TimeInterval = 0.25 + static let lockModeTransitionAnimationDuration: TimeInterval = 0.5 + static let panDirectionChangeThreshold: CGFloat = 20.0 + static let toastContainerCornerRadii: CGFloat = 8.0 + static let toastDisplayTimeout: TimeInterval = 5.0 + } + + @IBOutlet private var backgroundView: UIView! + + @IBOutlet private var recordingContainerView: UIView! + + @IBOutlet private var recordButtonsContainerView: UIView! + @IBOutlet private var primaryRecordButton: UIButton! + @IBOutlet private var secondaryRecordButton: UIButton! + + @IBOutlet private var recordingChromeContainerView: UIView! + @IBOutlet private var recordingIndicatorView: UIView! + + @IBOutlet private var elapsedTimeLabel: UILabel! + + @IBOutlet private var slideToCancelContainerView: UIView! + @IBOutlet private var slideToCancelLabel: UILabel! + @IBOutlet private var slideToCancelChevron: UIImageView! + @IBOutlet private var slideToCancelGradient: UIImageView! + + @IBOutlet private var lockContainerView: UIView! + @IBOutlet private var lockContainerBackgroundView: UIView! + + @IBOutlet private var lockButtonsContainerView: UIView! + @IBOutlet private var primaryLockButton: UIButton! + @IBOutlet private var secondaryLockButton: UIButton! + @IBOutlet private var lockChevron: UIView! + + @IBOutlet private var lockedModeContainerView: UIView! + @IBOutlet private var deleteButton: UIButton! + @IBOutlet private var playbackViewContainerView: UIView! + @IBOutlet private var sendButton: UIButton! + + @IBOutlet private var toastNotificationContainerView: UIView! + @IBOutlet private var toastNotificationLabel: UILabel! + + private var playbackView: VoiceMessagePlaybackView! + + private var cancelLabelToRecordButtonDistance: CGFloat = 0.0 + private var lockChevronToRecordButtonDistance: CGFloat = 0.0 + private var lockChevronToLockButtonDistance: CGFloat = 0.0 + private var panDirection: UISwipeGestureRecognizer.Direction? + + private var details: VoiceMessageToolbarViewDetails? + + private var currentTheme: Theme? { + didSet { + updateUIWithDetails(details, animated: true) + } + } + + weak var delegate: VoiceMessageToolbarViewDelegate? + + override func awakeFromNib() { + super.awakeFromNib() + + lockContainerBackgroundView.layer.cornerRadius = lockContainerBackgroundView.bounds.width / 2.0 + lockButtonsContainerView.layer.cornerRadius = lockButtonsContainerView.bounds.width / 2.0 + toastNotificationContainerView.layer.cornerRadius = Constants.toastContainerCornerRadii + + let longPressGesture = UILongPressGestureRecognizer(target: self, action: #selector(handleLongPress)) + longPressGesture.delegate = self + longPressGesture.minimumPressDuration = Constants.longPressMinimumDuration + recordButtonsContainerView.addGestureRecognizer(longPressGesture) + + let panGesture = UIPanGestureRecognizer(target: self, action: #selector(handlePan)) + longPressGesture.delegate = self + recordButtonsContainerView.addGestureRecognizer(panGesture) + + playbackView = VoiceMessagePlaybackView.loadFromNib() + playbackView.delegate = self + playbackViewContainerView.vc_addSubViewMatchingParent(playbackView) + + let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleWaveformTap)) + playbackView.waveformView.addGestureRecognizer(tapGesture) + + updateUIWithDetails(VoiceMessageToolbarViewDetails(), animated: false) + } + + func configureWithDetails(_ details: VoiceMessageToolbarViewDetails) { + elapsedTimeLabel.text = details.elapsedTime + + self.updateToastNotificationsWithDetails(details) + self.updatePlaybackViewWithDetails(details) + + if self.details?.state != details.state { + switch details.state { + case .record: + var convertedFrame = self.convert(slideToCancelLabel.frame, from: slideToCancelContainerView) + cancelLabelToRecordButtonDistance = recordButtonsContainerView.frame.minX - convertedFrame.maxX + + convertedFrame = self.convert(lockChevron.frame, from: lockContainerView) + lockChevronToRecordButtonDistance = recordButtonsContainerView.frame.midY + convertedFrame.maxY + + lockChevronToLockButtonDistance = (lockChevron.frame.minY - lockButtonsContainerView.frame.midY) / 2 + + startAnimatingRecordingIndicator() + default: + cancelDrag() + } + + if details.state == .lockedModeRecord && self.details?.state == .record { + UIView.animate(withDuration: Constants.animationDuration) { + self.lockButtonsContainerView.transform = CGAffineTransform(scaleX: 0.1, y: 0.1) + } completion: { _ in + self.updateUIWithDetails(details, animated: true) + } + } else { + updateUIWithDetails(details, animated: true) + } + } + + self.details = details + } + + func getRequiredNumberOfSamples() -> Int { + return playbackView.getRequiredNumberOfSamples() + } + + // MARK: - Themable + + func update(theme: Theme) { + currentTheme = theme + playbackView.update(theme: theme) + } + + // MARK: - UIGestureRecognizerDelegate + + func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { + return true + } + + // MARK: - VoiceMessagePlaybackViewDelegate + + func voiceMessagePlaybackViewDidRequestPlaybackToggle() { + delegate?.voiceMessageToolbarViewDidRequestPlaybackToggle(self) + } + + func voiceMessagePlaybackViewDidChangeWidth() { + + } + + // MARK: - Private + + @objc private func handleLongPress(_ gestureRecognizer: UILongPressGestureRecognizer) { + switch gestureRecognizer.state { + case UIGestureRecognizer.State.began: + delegate?.voiceMessageToolbarViewDidRequestRecordingStart(self) + case UIGestureRecognizer.State.ended: + delegate?.voiceMessageToolbarViewDidRequestRecordingFinish(self) + default: + break + } + } + + @objc private func handlePan(_ gestureRecognizer: UIPanGestureRecognizer) { + guard details?.state == .record && gestureRecognizer.state == .changed else { + return + } + + let translation = gestureRecognizer.translation(in: self) + + if abs(translation.x) <= Constants.panDirectionChangeThreshold && abs(translation.y) <= Constants.panDirectionChangeThreshold { + panDirection = nil + } else if panDirection == nil { + if abs(translation.x) >= abs(translation.y) { + panDirection = .left + } else { + panDirection = .up + } + } + + if panDirection == .left { + secondaryRecordButton.transform = CGAffineTransform(translationX: min(translation.x, 0.0), y: 0.0) + slideToCancelContainerView.transform = CGAffineTransform(translationX: min(translation.x + cancelLabelToRecordButtonDistance, 0.0), y: 0.0) + + if abs(translation.x - recordButtonsContainerView.frame.width / 2.0) > self.bounds.width / 2.0 { + delegate?.voiceMessageToolbarViewDidRequestRecordingCancel(self) + } + } else if panDirection == .up { + secondaryRecordButton.transform = CGAffineTransform(translationX: 0.0, y: min(0.0, translation.y)) + + let yTranslation = min(max(translation.y + lockChevronToRecordButtonDistance, -lockChevronToLockButtonDistance), 0.0) + lockChevron.transform = CGAffineTransform(translationX: 0.0, y: yTranslation) + + let transitionPercentage = abs(yTranslation) / lockChevronToLockButtonDistance + + lockChevron.alpha = 1.0 - transitionPercentage + secondaryRecordButton.alpha = 1.0 - transitionPercentage + primaryLockButton.alpha = 1.0 - transitionPercentage + lockContainerBackgroundView.alpha = 1.0 - transitionPercentage + secondaryLockButton.alpha = transitionPercentage + + if transitionPercentage >= 1.0 { + self.delegate?.voiceMessageToolbarViewDidRequestLockedModeRecording(self) + } + + } else { + secondaryRecordButton.transform = CGAffineTransform(translationX: min(0.0, translation.x), y: min(0.0, translation.y)) + } + } + + private func cancelDrag() { + recordButtonsContainerView.gestureRecognizers?.forEach { gestureRecognizer in + gestureRecognizer.isEnabled = false + gestureRecognizer.isEnabled = true + } + } + + private func updateUIWithDetails(_ details: VoiceMessageToolbarViewDetails?, animated: Bool) { + guard let details = details else { + return + } + + UIView.animate(withDuration: (animated ? Constants.animationDuration : 0.0), delay: 0.0, options: .beginFromCurrentState) { + switch details.state { + case .record: + self.lockContainerBackgroundView.alpha = 1.0 + case .idle: + self.lockContainerBackgroundView.alpha = 1.0 + self.primaryLockButton.alpha = 1.0 + self.secondaryLockButton.alpha = 0.0 + self.lockChevron.alpha = 1.0 + default: + break + } + + self.backgroundView.alpha = (details.state == .idle ? 0.0 : 1.0) + self.primaryRecordButton.alpha = (details.state == .idle ? 1.0 : 0.0) + self.secondaryRecordButton.alpha = (details.state == .record ? 1.0 : 0.0) + self.recordingChromeContainerView.alpha = (details.state == .record ? 1.0 : 0.0) + self.lockContainerView.alpha = (details.state == .record ? 1.0 : 0.0) + self.lockedModeContainerView.alpha = (details.state == .lockedModePlayback || details.state == .lockedModeRecord ? 1.0 : 0.0) + self.recordingContainerView.alpha = (details.state == .idle || details.state == .record ? 1.0 : 0.0) + + guard let theme = self.currentTheme else { + return + } + + self.backgroundView.backgroundColor = theme.colors.background + self.slideToCancelGradient.tintColor = theme.colors.background + + self.primaryRecordButton.tintColor = theme.colors.tertiaryContent + self.slideToCancelLabel.textColor = theme.colors.secondaryContent + self.slideToCancelChevron.tintColor = theme.colors.secondaryContent + self.elapsedTimeLabel.textColor = theme.colors.secondaryContent + + self.lockContainerBackgroundView.backgroundColor = theme.colors.navigation + self.lockButtonsContainerView.backgroundColor = theme.colors.navigation + + } completion: { _ in + switch details.state { + case .idle: + self.secondaryRecordButton.transform = .identity + self.slideToCancelContainerView.transform = .identity + self.lockChevron.transform = .identity + self.lockButtonsContainerView.transform = .identity + default: + break + } + } + } + + private var toastIdleTimer: Timer? + private var lastUIState: VoiceMessageToolbarViewUIState = .idle + + private func updateToastNotificationsWithDetails(_ details: VoiceMessageToolbarViewDetails, animated: Bool = true) { + + guard self.toastNotificationLabel.text != details.toastMessage || lastUIState != details.state else { + return + } + + lastUIState = details.state + + let shouldShowNotification = details.state != .idle && details.toastMessage != nil + let requiredAlpha: CGFloat = shouldShowNotification ? 1.0 : 0.0 + + toastIdleTimer?.invalidate() + toastIdleTimer = nil + + if shouldShowNotification { + self.toastNotificationLabel.text = details.toastMessage + } + + UIView.animate(withDuration: (animated ? Constants.animationDuration : 0.0)) { + self.toastNotificationContainerView.alpha = requiredAlpha + } + + if shouldShowNotification { + toastIdleTimer = Timer.scheduledTimer(withTimeInterval: Constants.toastDisplayTimeout, repeats: false) { [weak self] timer in + guard let self = self else { + return + } + + self.toastIdleTimer?.invalidate() + self.toastIdleTimer = nil + + UIView.animate(withDuration: Constants.animationDuration) { + self.toastNotificationContainerView.alpha = 0 + } + } + } + } + + private func updatePlaybackViewWithDetails(_ details: VoiceMessageToolbarViewDetails, animated: Bool = true) { + UIView.animate(withDuration: (animated ? Constants.animationDuration : 0.0)) { + var playbackViewDetails = VoiceMessagePlaybackViewDetails() + playbackViewDetails.recording = (details.state == .record || details.state == .lockedModeRecord) + playbackViewDetails.playing = details.isPlaying + playbackViewDetails.progress = details.progress + playbackViewDetails.currentTime = details.elapsedTime + playbackViewDetails.samples = details.audioSamples + playbackViewDetails.playbackEnabled = true + self.playbackView.configureWithDetails(playbackViewDetails) + } + } + + private func startAnimatingRecordingIndicator() { + if self.details?.state != .record { + return + } + + UIView.animate(withDuration: Constants.lockModeTransitionAnimationDuration) { + if self.recordingIndicatorView.alpha > 0.0 { + self.recordingIndicatorView.alpha = 0.0 + } else { + self.recordingIndicatorView.alpha = 1.0 + } + } completion: { [weak self] _ in + self?.startAnimatingRecordingIndicator() + } + } + + @IBAction private func onTrashButtonTap(_ sender: UIBarItem) { + delegate?.voiceMessageToolbarViewDidRequestRecordingCancel(self) + } + + @IBAction private func onSendButtonTap(_ sender: UIBarItem) { + delegate?.voiceMessageToolbarViewDidRequestSend(self) + } + + @objc private func handleWaveformTap(_ gestureRecognizer: UITapGestureRecognizer) { + guard self.lastUIState == .lockedModeRecord else { + return + } + + delegate?.voiceMessageToolbarViewDidRequestRecordingFinish(self) + } +} diff --git a/Riot/Modules/Room/VoiceMessages/VoiceMessageToolbarView.xib b/Riot/Modules/Room/VoiceMessages/VoiceMessageToolbarView.xib new file mode 100644 index 000000000..52dcce870 --- /dev/null +++ b/Riot/Modules/Room/VoiceMessages/VoiceMessageToolbarView.xib @@ -0,0 +1,286 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Riot/Modules/Room/VoiceMessages/VoiceMessageWaveformView.swift b/Riot/Modules/Room/VoiceMessages/VoiceMessageWaveformView.swift new file mode 100644 index 000000000..8685109c4 --- /dev/null +++ b/Riot/Modules/Room/VoiceMessages/VoiceMessageWaveformView.swift @@ -0,0 +1,122 @@ +// +// Copyright 2021 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import UIKit + +class VoiceMessageWaveformView: UIView { + + private let lineWidth: CGFloat = 2.0 + private let linePadding: CGFloat = 2.0 + private let renderingQueue: DispatchQueue = DispatchQueue(label: "io.element.VoiceMessageWaveformView.queue", qos: .userInitiated) + + var samples: [Float] = [] { + didSet { + computeWaveForm() + } + } + + var primaryLineColor = UIColor.lightGray { + didSet { + backgroundLayer.strokeColor = primaryLineColor.cgColor + backgroundLayer.fillColor = primaryLineColor.cgColor + } + } + var secondaryLineColor = UIColor.darkGray { + didSet { + progressLayer.strokeColor = secondaryLineColor.cgColor + progressLayer.fillColor = secondaryLineColor.cgColor + } + } + + private let backgroundLayer = CAShapeLayer() + private let progressLayer = CAShapeLayer() + + var progress = 0.0 { + didSet { + CATransaction.begin() + CATransaction.setDisableActions(true) + progressLayer.frame = CGRect(origin: self.bounds.origin, size: CGSize(width: self.bounds.width * CGFloat(self.progress), height: self.bounds.height)) + CATransaction.commit() + } + } + + var requiredNumberOfSamples: Int { + return Int(self.bounds.size.width / (lineWidth + linePadding)) + } + + override init(frame: CGRect) { + super.init(frame: frame) + + setupAndAdd(backgroundLayer, with: primaryLineColor) + setupAndAdd(progressLayer, with: secondaryLineColor) + progressLayer.masksToBounds = true + + computeWaveForm() + } + + required init?(coder: NSCoder) { + fatalError() + } + + override func layoutSubviews() { + super.layoutSubviews() + + backgroundLayer.frame = self.bounds + progressLayer.frame = CGRect(origin: self.bounds.origin, size: CGSize(width: self.bounds.width * CGFloat(self.progress), height: self.bounds.height)) + computeWaveForm() + } + + // MARK: - Private + + private func computeWaveForm() { + renderingQueue.async { [samples] in // Capture the current samples as a way to provide atomicity + let path = UIBezierPath() + + let drawMappingFactor = self.bounds.size.height + let minimumGraphAmplitude: CGFloat = 1 + + var xOffset: CGFloat = self.lineWidth / 2 + var index = 0 + + while xOffset < self.bounds.width - self.lineWidth { + let sample = CGFloat(index >= samples.count ? 1 : samples[index]) + let invertedDbSample = 1 - sample // sample is in dB, linearly normalized to [0, 1] (1 -> -50 dB) + let drawingAmplitude = max(minimumGraphAmplitude, invertedDbSample * drawMappingFactor) + + path.move(to: CGPoint(x: xOffset, y: self.bounds.midY - drawingAmplitude / 2)) + path.addLine(to: CGPoint(x: xOffset, y: self.bounds.midY + drawingAmplitude / 2)) + + xOffset += self.lineWidth + self.linePadding + + index += 1 + } + + DispatchQueue.main.async { + self.backgroundLayer.path = path.cgPath + self.progressLayer.path = path.cgPath + } + } + } + + private func setupAndAdd(_ shapeLayer: CAShapeLayer, with color: UIColor) { + shapeLayer.frame = self.bounds + shapeLayer.strokeColor = color.cgColor + shapeLayer.fillColor = color.cgColor + shapeLayer.lineCap = .round + shapeLayer.lineWidth = lineWidth + self.layer.addSublayer(shapeLayer) + } +} diff --git a/Riot/Modules/Rooms/RoomsDirectoryCoordinatorType.swift b/Riot/Modules/Rooms/RoomsDirectoryCoordinatorType.swift index a33788a44..40e38c6d4 100644 --- a/Riot/Modules/Rooms/RoomsDirectoryCoordinatorType.swift +++ b/Riot/Modules/Rooms/RoomsDirectoryCoordinatorType.swift @@ -18,7 +18,7 @@ import Foundation -protocol RoomsDirectoryCoordinatorDelegate: class { +protocol RoomsDirectoryCoordinatorDelegate: AnyObject { func roomsDirectoryCoordinator(_ coordinator: RoomsDirectoryCoordinatorType, didSelectRoom room: MXPublicRoom) func roomsDirectoryCoordinatorDidTapCreateNewRoom(_ coordinator: RoomsDirectoryCoordinatorType) func roomsDirectoryCoordinatorDidComplete(_ coordinator: RoomsDirectoryCoordinatorType) diff --git a/Riot/Modules/Rooms/RoomsViewController.m b/Riot/Modules/Rooms/RoomsViewController.m index a9dc73b5d..979b30610 100644 --- a/Riot/Modules/Rooms/RoomsViewController.m +++ b/Riot/Modules/Rooms/RoomsViewController.m @@ -18,16 +18,11 @@ #import "RecentsDataSource.h" -#import "DirectoryServerPickerViewController.h" - #import "Riot-Swift.h" @interface RoomsViewController () { RecentsDataSource *recentsDataSource; - - // The animated view displayed at the table view bottom when paginating the room directory - UIView* footerSpinnerView; } @end @@ -63,8 +58,6 @@ plusButtonImageView = [self vc_addFABWithImage:[UIImage imageNamed:@"rooms_floating_action"] target:self action:@selector(onPlusButtonPressed)]; - - self.enableStickyHeaders = YES; } - (void)viewWillAppear:(BOOL)animated @@ -74,24 +67,12 @@ [AppDelegate theDelegate].masterTabBarController.navigationItem.title = NSLocalizedStringFromTable(@"title_rooms", @"Vector", nil); [AppDelegate theDelegate].masterTabBarController.tabBar.tintColor = ThemeService.shared.theme.tintColor; - // TODO: Notify RiotSettings.shared.showNSFWPublicRooms change for iPad as viewWillAppear may not be called - recentsDataSource.publicRoomsDirectoryDataSource.showNSFWRooms = RiotSettings.shared.showNSFWPublicRooms; - if ([self.dataSource isKindOfClass:RecentsDataSource.class]) { - BOOL isFirstTime = (recentsDataSource != self.dataSource); - // Take the lead on the shared data source. recentsDataSource = (RecentsDataSource*)self.dataSource; recentsDataSource.areSectionsShrinkable = NO; [recentsDataSource setDelegate:self andRecentsDataSourceMode:RecentsDataSourceModeRooms]; - - if (isFirstTime) - { - // The first time the screen is displayed, make publicRoomsDirectoryDataSource - // start loading data - [recentsDataSource.publicRoomsDirectoryDataSource paginate:nil failure:nil]; - } } } @@ -113,32 +94,19 @@ [super refreshCurrentSelectedCell:forceVisible]; } -- (UIView *)tableView:(UITableView *)tableView viewForStickyHeaderInSection:(NSInteger)section -{ - CGRect frame = [tableView rectForHeaderInSection:section]; - frame.size.height = self.stickyHeaderHeight; - - return [recentsDataSource viewForHeaderInSection:section withFrame:frame]; -} - -- (void)dataSource:(MXKDataSource *)dataSource didRecognizeAction:(NSString *)actionIdentifier inCell:(id)cell userInfo:(NSDictionary *)userInfo -{ - if ([actionIdentifier isEqualToString:kRecentsDataSourceTapOnDirectoryServerChange]) - { - // Show the directory server picker - [self performSegueWithIdentifier:@"presentDirectoryServerPicker" sender:self]; - } - else - { - [super dataSource:dataSource didRecognizeAction:actionIdentifier inCell:cell userInfo:userInfo]; - } -} - - (void)onPlusButtonPressed { [self showRoomDirectory]; } +#pragma mark - UITableView delegate + +- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section +{ + // Hide the header to merge Invites and Rooms into a single list. + return 0.0; +} + #pragma mark - - (void)scrollToNextRoomWithMissedNotifications @@ -150,181 +118,6 @@ } } -#pragma mark - Navigation - -- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender -{ - [super prepareForSegue:segue sender:sender]; - - UIViewController *pushedViewController = [segue destinationViewController]; - - if ([[segue identifier] isEqualToString:@"presentDirectoryServerPicker"]) - { - UINavigationController *pushedNavigationViewController = (UINavigationController*)pushedViewController; - DirectoryServerPickerViewController* directoryServerPickerViewController = (DirectoryServerPickerViewController*)pushedNavigationViewController.viewControllers.firstObject; - - MXKDirectoryServersDataSource *directoryServersDataSource = [[MXKDirectoryServersDataSource alloc] initWithMatrixSession:recentsDataSource.publicRoomsDirectoryDataSource.mxSession]; - [directoryServersDataSource finalizeInitialization]; - - // Add directory servers from the app settings - directoryServersDataSource.roomDirectoryServers = BuildSettings.publicRoomsDirectoryServers; - - __weak typeof(self) weakSelf = self; - - [directoryServerPickerViewController displayWithDataSource:directoryServersDataSource onComplete:^(id cellData) { - if (weakSelf && cellData) - { - typeof(self) self = weakSelf; - - // Use the selected directory server - if (cellData.thirdPartyProtocolInstance) - { - self->recentsDataSource.publicRoomsDirectoryDataSource.thirdpartyProtocolInstance = cellData.thirdPartyProtocolInstance; - } - else if (cellData.homeserver) - { - self->recentsDataSource.publicRoomsDirectoryDataSource.includeAllNetworks = cellData.includeAllNetworks; - self->recentsDataSource.publicRoomsDirectoryDataSource.homeserver = cellData.homeserver; - } - - // Refresh data - [self addSpinnerFooterView]; - - [self->recentsDataSource.publicRoomsDirectoryDataSource paginate:^(NSUInteger roomsAdded) { - - if (weakSelf) - { - typeof(self) self = weakSelf; - - // The table view is automatically filled - [self removeSpinnerFooterView]; - - // Make the directory section appear full-page - [self.recentsTableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:self->recentsDataSource.directorySection] atScrollPosition:UITableViewScrollPositionTop animated:YES]; - } - - } failure:^(NSError *error) { - - if (weakSelf) - { - typeof(self) self = weakSelf; - [self removeSpinnerFooterView]; - } - }]; - } - }]; - - // Hide back button title - pushedViewController.navigationController.navigationItem.backBarButtonItem =[[UIBarButtonItem alloc] initWithTitle:@"" style:UIBarButtonItemStylePlain target:nil action:nil]; - } -} - -#pragma mark - UITableView delegate - -- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section -{ - if (section == recentsDataSource.directorySection) - { - // Let the recents dataSource provide the height of this section header - return [recentsDataSource heightForHeaderInSection:section]; - } - - return [super tableView:tableView heightForHeaderInSection:section]; -} - -- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath -{ - if (indexPath.section == recentsDataSource.directorySection) - { - // Sanity check - MXPublicRoom *publicRoom = [recentsDataSource.publicRoomsDirectoryDataSource roomAtIndexPath:indexPath]; - if (publicRoom) - { - [self openPublicRoomAtIndexPath:indexPath]; - } - } - else - { - [super tableView:tableView didSelectRowAtIndexPath:indexPath]; - } -} - -- (void)scrollViewDidScroll:(UIScrollView *)scrollView -{ - // Trigger inconspicuous pagination on directy when user scrolls down - if ((scrollView.contentSize.height - scrollView.contentOffset.y - scrollView.frame.size.height) < 300) - { - [self triggerDirectoryPagination]; - } - - [super scrollViewDidScroll:scrollView]; -} - -#pragma mark - Private methods - -- (void)openPublicRoomAtIndexPath:(NSIndexPath *)indexPath -{ - MXPublicRoom *publicRoom = [recentsDataSource.publicRoomsDirectoryDataSource roomAtIndexPath:indexPath]; - - [self openPublicRoom:publicRoom]; -} - -- (void)triggerDirectoryPagination -{ - if (!recentsDataSource - || recentsDataSource.state == MXKDataSourceStateUnknown - || recentsDataSource.publicRoomsDirectoryDataSource.hasReachedPaginationEnd - || footerSpinnerView) - { - // We are not yet ready or being killed or we got all public rooms or we are already paginating - // Do nothing - return; - } - - [self addSpinnerFooterView]; - - [recentsDataSource.publicRoomsDirectoryDataSource paginate:^(NSUInteger roomsAdded) { - - // The table view is automatically filled - [self removeSpinnerFooterView]; - - } failure:^(NSError *error) { - - [self removeSpinnerFooterView]; - }]; -} - -- (void)addSpinnerFooterView -{ - if (!footerSpinnerView) - { - UIActivityIndicatorView* spinner = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge]; - spinner.transform = CGAffineTransformMakeScale(0.75f, 0.75f); - CGRect frame = spinner.frame; - frame.size.height = 80; // 80 * 0.75 = 60 - spinner.bounds = frame; - - spinner.color = [UIColor darkGrayColor]; - spinner.hidesWhenStopped = NO; - spinner.backgroundColor = [UIColor clearColor]; - [spinner startAnimating]; - - // No need to manage constraints here, iOS defines them - self.recentsTableView.tableFooterView = footerSpinnerView = spinner; - } -} - -- (void)removeSpinnerFooterView -{ - if (footerSpinnerView) - { - footerSpinnerView = nil; - - // Hide line separators of empty cells - self.recentsTableView.tableFooterView = [[UIView alloc] init];; - } -} - #pragma mark - Empty view management - (void)updateEmptyView @@ -362,7 +155,6 @@ - (NSUInteger)totalItemCounts { return recentsDataSource.conversationCellDataArray.count - + recentsDataSource.publicRoomsDirectoryDataSource.roomsCount + recentsDataSource.invitesCellDataArray.count; } diff --git a/Riot/Modules/Rooms/ShowDirectory/Cells/Room/DirectoryRoomTableViewCell.swift b/Riot/Modules/Rooms/ShowDirectory/Cells/Room/DirectoryRoomTableViewCell.swift index e072ff350..d6b67472b 100644 --- a/Riot/Modules/Rooms/ShowDirectory/Cells/Room/DirectoryRoomTableViewCell.swift +++ b/Riot/Modules/Rooms/ShowDirectory/Cells/Room/DirectoryRoomTableViewCell.swift @@ -17,7 +17,7 @@ import UIKit import Reusable -protocol DirectoryRoomTableViewCellDelegate: class { +protocol DirectoryRoomTableViewCellDelegate: AnyObject { func directoryRoomTableViewCellDidTapJoin(_ cell: DirectoryRoomTableViewCell) } diff --git a/Riot/Modules/Rooms/ShowDirectory/ShowDirectoryCoordinatorType.swift b/Riot/Modules/Rooms/ShowDirectory/ShowDirectoryCoordinatorType.swift index 2290edf48..b46b3ca4f 100644 --- a/Riot/Modules/Rooms/ShowDirectory/ShowDirectoryCoordinatorType.swift +++ b/Riot/Modules/Rooms/ShowDirectory/ShowDirectoryCoordinatorType.swift @@ -18,7 +18,7 @@ import Foundation -protocol ShowDirectoryCoordinatorDelegate: class { +protocol ShowDirectoryCoordinatorDelegate: AnyObject { func showDirectoryCoordinator(_ coordinator: ShowDirectoryCoordinatorType, didSelectRoom room: MXPublicRoom) func showDirectoryCoordinatorDidTapCreateNewRoom(_ coordinator: ShowDirectoryCoordinatorType) func showDirectoryCoordinatorDidCancel(_ coordinator: ShowDirectoryCoordinatorType) diff --git a/Riot/Modules/Rooms/ShowDirectory/ShowDirectoryViewModelType.swift b/Riot/Modules/Rooms/ShowDirectory/ShowDirectoryViewModelType.swift index 643cf4029..4c4291f99 100644 --- a/Riot/Modules/Rooms/ShowDirectory/ShowDirectoryViewModelType.swift +++ b/Riot/Modules/Rooms/ShowDirectory/ShowDirectoryViewModelType.swift @@ -18,11 +18,11 @@ import Foundation -protocol ShowDirectoryViewModelViewDelegate: class { +protocol ShowDirectoryViewModelViewDelegate: AnyObject { func showDirectoryViewModel(_ viewModel: ShowDirectoryViewModelType, didUpdateViewState viewSate: ShowDirectoryViewState) } -protocol ShowDirectoryViewModelCoordinatorDelegate: class { +protocol ShowDirectoryViewModelCoordinatorDelegate: AnyObject { func showDirectoryViewModelDidSelect(_ viewModel: ShowDirectoryViewModelType, room: MXPublicRoom) func showDirectoryViewModel(_ viewModel: ShowDirectoryViewModelType, didSelectRoomWithIdOrAlias roomIdOrAlias: String) func showDirectoryViewModelDidTapCreateNewRoom(_ viewModel: ShowDirectoryViewModelType) diff --git a/Riot/Modules/Secrets/Recover/RecoverWithKey/SecretsRecoveryWithKeyCoordinatorType.swift b/Riot/Modules/Secrets/Recover/RecoverWithKey/SecretsRecoveryWithKeyCoordinatorType.swift index f0a593f99..74b1b41a2 100644 --- a/Riot/Modules/Secrets/Recover/RecoverWithKey/SecretsRecoveryWithKeyCoordinatorType.swift +++ b/Riot/Modules/Secrets/Recover/RecoverWithKey/SecretsRecoveryWithKeyCoordinatorType.swift @@ -16,7 +16,7 @@ import Foundation -protocol SecretsRecoveryWithKeyCoordinatorDelegate: class { +protocol SecretsRecoveryWithKeyCoordinatorDelegate: AnyObject { func secretsRecoveryWithKeyCoordinatorDidRecover(_ coordinator: SecretsRecoveryWithKeyCoordinatorType) func secretsRecoveryWithKeyCoordinatorDidCancel(_ coordinator: SecretsRecoveryWithKeyCoordinatorType) func secretsRecoveryWithKeyCoordinatorWantsToResetSecrets(_ viewModel: SecretsRecoveryWithKeyCoordinatorType) diff --git a/Riot/Modules/Secrets/Recover/RecoverWithKey/SecretsRecoveryWithKeyViewController.swift b/Riot/Modules/Secrets/Recover/RecoverWithKey/SecretsRecoveryWithKeyViewController.swift index a0cc96d35..911f08302 100644 --- a/Riot/Modules/Secrets/Recover/RecoverWithKey/SecretsRecoveryWithKeyViewController.swift +++ b/Riot/Modules/Secrets/Recover/RecoverWithKey/SecretsRecoveryWithKeyViewController.swift @@ -123,6 +123,8 @@ final class SecretsRecoveryWithKeyViewController: UIViewController { self.updateRecoverButton() self.resetSecretsButton.vc_enableMultiLinesTitle() + + self.resetSecretsButton.isHidden = !RiotSettings.shared.secretsRecoveryAllowReset } private func update(theme: Theme) { diff --git a/Riot/Modules/Secrets/Recover/RecoverWithKey/SecretsRecoveryWithKeyViewModelType.swift b/Riot/Modules/Secrets/Recover/RecoverWithKey/SecretsRecoveryWithKeyViewModelType.swift index c747d67ba..b71ffac5d 100644 --- a/Riot/Modules/Secrets/Recover/RecoverWithKey/SecretsRecoveryWithKeyViewModelType.swift +++ b/Riot/Modules/Secrets/Recover/RecoverWithKey/SecretsRecoveryWithKeyViewModelType.swift @@ -16,11 +16,11 @@ import Foundation -protocol SecretsRecoveryWithKeyViewModelViewDelegate: class { +protocol SecretsRecoveryWithKeyViewModelViewDelegate: AnyObject { func secretsRecoveryWithKeyViewModel(_ viewModel: SecretsRecoveryWithKeyViewModelType, didUpdateViewState viewSate: SecretsRecoveryWithKeyViewState) } -protocol SecretsRecoveryWithKeyViewModelCoordinatorDelegate: class { +protocol SecretsRecoveryWithKeyViewModelCoordinatorDelegate: AnyObject { func secretsRecoveryWithKeyViewModelDidRecover(_ viewModel: SecretsRecoveryWithKeyViewModelType) func secretsRecoveryWithKeyViewModelDidCancel(_ viewModel: SecretsRecoveryWithKeyViewModelType) func secretsRecoveryWithKeyViewModelWantsToResetSecrets(_ viewModel: SecretsRecoveryWithKeyViewModelType) diff --git a/Riot/Modules/Secrets/Recover/RecoverWithPassphrase/SecretsRecoveryWithPassphraseCoordinatorType.swift b/Riot/Modules/Secrets/Recover/RecoverWithPassphrase/SecretsRecoveryWithPassphraseCoordinatorType.swift index f09c2eb98..3d6d17e4e 100644 --- a/Riot/Modules/Secrets/Recover/RecoverWithPassphrase/SecretsRecoveryWithPassphraseCoordinatorType.swift +++ b/Riot/Modules/Secrets/Recover/RecoverWithPassphrase/SecretsRecoveryWithPassphraseCoordinatorType.swift @@ -16,7 +16,7 @@ import Foundation -protocol SecretsRecoveryWithPassphraseCoordinatorDelegate: class { +protocol SecretsRecoveryWithPassphraseCoordinatorDelegate: AnyObject { func secretsRecoveryWithPassphraseCoordinatorDidRecover(_ coordinator: SecretsRecoveryWithPassphraseCoordinatorType) func secretsRecoveryWithPassphraseCoordinatorDoNotKnowPassphrase(_ coordinator: SecretsRecoveryWithPassphraseCoordinatorType) func secretsRecoveryWithPassphraseCoordinatorDidCancel(_ coordinator: SecretsRecoveryWithPassphraseCoordinatorType) diff --git a/Riot/Modules/Secrets/Recover/RecoverWithPassphrase/SecretsRecoveryWithPassphraseViewController.swift b/Riot/Modules/Secrets/Recover/RecoverWithPassphrase/SecretsRecoveryWithPassphraseViewController.swift index c1fcaaec4..5c64fd51b 100644 --- a/Riot/Modules/Secrets/Recover/RecoverWithPassphrase/SecretsRecoveryWithPassphraseViewController.swift +++ b/Riot/Modules/Secrets/Recover/RecoverWithPassphrase/SecretsRecoveryWithPassphraseViewController.swift @@ -125,6 +125,8 @@ final class SecretsRecoveryWithPassphraseViewController: UIViewController { self.updateRecoverButton() self.resetSecretsButton.vc_enableMultiLinesTitle() + + self.resetSecretsButton.isHidden = !RiotSettings.shared.secretsRecoveryAllowReset } private func update(theme: Theme) { diff --git a/Riot/Modules/Secrets/Recover/RecoverWithPassphrase/SecretsRecoveryWithPassphraseViewModelType.swift b/Riot/Modules/Secrets/Recover/RecoverWithPassphrase/SecretsRecoveryWithPassphraseViewModelType.swift index d54ae59f2..d1f1860a9 100644 --- a/Riot/Modules/Secrets/Recover/RecoverWithPassphrase/SecretsRecoveryWithPassphraseViewModelType.swift +++ b/Riot/Modules/Secrets/Recover/RecoverWithPassphrase/SecretsRecoveryWithPassphraseViewModelType.swift @@ -16,11 +16,11 @@ import Foundation -protocol SecretsRecoveryWithPassphraseViewModelViewDelegate: class { +protocol SecretsRecoveryWithPassphraseViewModelViewDelegate: AnyObject { func secretsRecoveryWithPassphraseViewModel(_ viewModel: SecretsRecoveryWithPassphraseViewModelType, didUpdateViewState viewSate: SecretsRecoveryWithPassphraseViewState) } -protocol SecretsRecoveryWithPassphraseViewModelCoordinatorDelegate: class { +protocol SecretsRecoveryWithPassphraseViewModelCoordinatorDelegate: AnyObject { func secretsRecoveryWithPassphraseViewModelDidRecover(_ viewModel: SecretsRecoveryWithPassphraseViewModelType) func secretsRecoveryWithPassphraseViewModelDidCancel(_ viewModel: SecretsRecoveryWithPassphraseViewModelType) func secretsRecoveryWithPassphraseViewModelWantsToRecoverByKey(_ viewModel: SecretsRecoveryWithPassphraseViewModelType) diff --git a/Riot/Modules/Secrets/Recover/SecretsRecoveryCoordinatorType.swift b/Riot/Modules/Secrets/Recover/SecretsRecoveryCoordinatorType.swift index 64b35f203..f7aea9dba 100644 --- a/Riot/Modules/Secrets/Recover/SecretsRecoveryCoordinatorType.swift +++ b/Riot/Modules/Secrets/Recover/SecretsRecoveryCoordinatorType.swift @@ -16,7 +16,7 @@ import Foundation -protocol SecretsRecoveryCoordinatorDelegate: class { +protocol SecretsRecoveryCoordinatorDelegate: AnyObject { func secretsRecoveryCoordinatorDidRecover(_ coordinator: SecretsRecoveryCoordinatorType) func secretsRecoveryCoordinatorDidCancel(_ coordinator: SecretsRecoveryCoordinatorType) } diff --git a/Riot/Modules/Secrets/Reset/SecretsResetCoordinatorType.swift b/Riot/Modules/Secrets/Reset/SecretsResetCoordinatorType.swift index 6b28912ce..cd33b058a 100644 --- a/Riot/Modules/Secrets/Reset/SecretsResetCoordinatorType.swift +++ b/Riot/Modules/Secrets/Reset/SecretsResetCoordinatorType.swift @@ -18,7 +18,7 @@ import Foundation -protocol SecretsResetCoordinatorDelegate: class { +protocol SecretsResetCoordinatorDelegate: AnyObject { func secretsResetCoordinatorDidResetSecrets(_ coordinator: SecretsResetCoordinatorType) func secretsResetCoordinatorDidCancel(_ coordinator: SecretsResetCoordinatorType) } diff --git a/Riot/Modules/Secrets/Reset/SecretsResetViewModelType.swift b/Riot/Modules/Secrets/Reset/SecretsResetViewModelType.swift index ce64f4c31..5a5c88077 100644 --- a/Riot/Modules/Secrets/Reset/SecretsResetViewModelType.swift +++ b/Riot/Modules/Secrets/Reset/SecretsResetViewModelType.swift @@ -18,11 +18,11 @@ import Foundation -protocol SecretsResetViewModelViewDelegate: class { +protocol SecretsResetViewModelViewDelegate: AnyObject { func secretsResetViewModel(_ viewModel: SecretsResetViewModelType, didUpdateViewState viewState: SecretsResetViewState) } -protocol SecretsResetViewModelCoordinatorDelegate: class { +protocol SecretsResetViewModelCoordinatorDelegate: AnyObject { func secretsResetViewModel(_ viewModel: SecretsResetViewModelType, needsToAuthenticateWith request: AuthenticatedEndpointRequest) func secretsResetViewModelDidResetSecrets(_ viewModel: SecretsResetViewModelType) func secretsResetViewModelDidCancel(_ viewModel: SecretsResetViewModelType) diff --git a/Riot/Modules/Secrets/Setup/RecoveryKey/SecretsSetupRecoveryKeyCoordinatorType.swift b/Riot/Modules/Secrets/Setup/RecoveryKey/SecretsSetupRecoveryKeyCoordinatorType.swift index 0c2002813..87c885fcd 100644 --- a/Riot/Modules/Secrets/Setup/RecoveryKey/SecretsSetupRecoveryKeyCoordinatorType.swift +++ b/Riot/Modules/Secrets/Setup/RecoveryKey/SecretsSetupRecoveryKeyCoordinatorType.swift @@ -18,7 +18,7 @@ import Foundation -protocol SecretsSetupRecoveryKeyCoordinatorDelegate: class { +protocol SecretsSetupRecoveryKeyCoordinatorDelegate: AnyObject { func secretsSetupRecoveryKeyCoordinatorDidComplete(_ coordinator: SecretsSetupRecoveryKeyCoordinatorType) func secretsSetupRecoveryKeyCoordinatorDidFailed(_ coordinator: SecretsSetupRecoveryKeyCoordinatorType) func secretsSetupRecoveryKeyCoordinatorDidCancel(_ coordinator: SecretsSetupRecoveryKeyCoordinatorType) diff --git a/Riot/Modules/Secrets/Setup/RecoveryKey/SecretsSetupRecoveryKeyViewModelType.swift b/Riot/Modules/Secrets/Setup/RecoveryKey/SecretsSetupRecoveryKeyViewModelType.swift index 3cf3843d2..0922c9347 100644 --- a/Riot/Modules/Secrets/Setup/RecoveryKey/SecretsSetupRecoveryKeyViewModelType.swift +++ b/Riot/Modules/Secrets/Setup/RecoveryKey/SecretsSetupRecoveryKeyViewModelType.swift @@ -18,11 +18,11 @@ import Foundation -protocol SecretsSetupRecoveryKeyViewModelViewDelegate: class { +protocol SecretsSetupRecoveryKeyViewModelViewDelegate: AnyObject { func secretsSetupRecoveryKeyViewModel(_ viewModel: SecretsSetupRecoveryKeyViewModelType, didUpdateViewState viewSate: SecretsSetupRecoveryKeyViewState) } -protocol SecretsSetupRecoveryKeyViewModelCoordinatorDelegate: class { +protocol SecretsSetupRecoveryKeyViewModelCoordinatorDelegate: AnyObject { func secretsSetupRecoveryKeyViewModelDidComplete(_ viewModel: SecretsSetupRecoveryKeyViewModelType) func secretsSetupRecoveryKeyViewModelDidFailed(_ viewModel: SecretsSetupRecoveryKeyViewModelType) func secretsSetupRecoveryKeyViewModelDidCancel(_ viewModel: SecretsSetupRecoveryKeyViewModelType) diff --git a/Riot/Modules/Secrets/Setup/RecoveryPassphrase/SecretsSetupRecoveryPassphraseCoordinatorType.swift b/Riot/Modules/Secrets/Setup/RecoveryPassphrase/SecretsSetupRecoveryPassphraseCoordinatorType.swift index 4318c4414..a3e3140d0 100644 --- a/Riot/Modules/Secrets/Setup/RecoveryPassphrase/SecretsSetupRecoveryPassphraseCoordinatorType.swift +++ b/Riot/Modules/Secrets/Setup/RecoveryPassphrase/SecretsSetupRecoveryPassphraseCoordinatorType.swift @@ -18,7 +18,7 @@ import Foundation -protocol SecretsSetupRecoveryPassphraseCoordinatorDelegate: class { +protocol SecretsSetupRecoveryPassphraseCoordinatorDelegate: AnyObject { func secretsSetupRecoveryPassphraseCoordinator(_ coordinator: SecretsSetupRecoveryPassphraseCoordinatorType, didEnterNewPassphrase passphrase: String) func secretsSetupRecoveryPassphraseCoordinator(_ coordinator: SecretsSetupRecoveryPassphraseCoordinatorType, didConfirmPassphrase passphrase: String) func secretsSetupRecoveryPassphraseCoordinatorDidCancel(_ coordinator: SecretsSetupRecoveryPassphraseCoordinatorType) diff --git a/Riot/Modules/Secrets/Setup/RecoveryPassphrase/SecretsSetupRecoveryPassphraseViewModelType.swift b/Riot/Modules/Secrets/Setup/RecoveryPassphrase/SecretsSetupRecoveryPassphraseViewModelType.swift index a7f27ad22..91a3eb50b 100644 --- a/Riot/Modules/Secrets/Setup/RecoveryPassphrase/SecretsSetupRecoveryPassphraseViewModelType.swift +++ b/Riot/Modules/Secrets/Setup/RecoveryPassphrase/SecretsSetupRecoveryPassphraseViewModelType.swift @@ -18,11 +18,11 @@ import Foundation -protocol SecretsSetupRecoveryPassphraseViewModelViewDelegate: class { +protocol SecretsSetupRecoveryPassphraseViewModelViewDelegate: AnyObject { func secretsSetupRecoveryPassphraseViewModel(_ viewModel: SecretsSetupRecoveryPassphraseViewModelType, didUpdateViewState viewSate: SecretsSetupRecoveryPassphraseViewState) } -protocol SecretsSetupRecoveryPassphraseViewModelCoordinatorDelegate: class { +protocol SecretsSetupRecoveryPassphraseViewModelCoordinatorDelegate: AnyObject { func secretsSetupRecoveryPassphraseViewModel(_ viewModel: SecretsSetupRecoveryPassphraseViewModelType, didEnterNewPassphrase passphrase: String) func secretsSetupRecoveryPassphraseViewModel(_ viewModel: SecretsSetupRecoveryPassphraseViewModelType, didConfirmPassphrase passphrase: String) func secretsSetupRecoveryPassphraseViewModelDidCancel(_ viewModel: SecretsSetupRecoveryPassphraseViewModelType) diff --git a/Riot/Modules/SecureBackup/Banners/SecureBackupBannerCell.swift b/Riot/Modules/SecureBackup/Banners/SecureBackupBannerCell.swift index a1ff99449..780689396 100644 --- a/Riot/Modules/SecureBackup/Banners/SecureBackupBannerCell.swift +++ b/Riot/Modules/SecureBackup/Banners/SecureBackupBannerCell.swift @@ -16,7 +16,7 @@ import UIKit -@objc protocol SecureBackupBannerCellDelegate: class { +@objc protocol SecureBackupBannerCellDelegate: AnyObject { func secureBackupBannerCellDidTapCloseAction(_ cell: SecureBackupBannerCell) } diff --git a/Riot/Modules/SecureBackup/Setup/Intro/SecureBackupSetupIntroViewController.swift b/Riot/Modules/SecureBackup/Setup/Intro/SecureBackupSetupIntroViewController.swift index 0bdd5123c..b95bf6a24 100644 --- a/Riot/Modules/SecureBackup/Setup/Intro/SecureBackupSetupIntroViewController.swift +++ b/Riot/Modules/SecureBackup/Setup/Intro/SecureBackupSetupIntroViewController.swift @@ -16,7 +16,7 @@ import UIKit -protocol SecureBackupSetupIntroViewControllerDelegate: class { +protocol SecureBackupSetupIntroViewControllerDelegate: AnyObject { func secureBackupSetupIntroViewControllerDidTapUseKey(_ secureBackupSetupIntroViewController: SecureBackupSetupIntroViewController) func secureBackupSetupIntroViewControllerDidTapUsePassphrase(_ secureBackupSetupIntroViewController: SecureBackupSetupIntroViewController) func secureBackupSetupIntroViewControllerDidCancel(_ secureBackupSetupIntroViewController: SecureBackupSetupIntroViewController, showSkipAlert: Bool) diff --git a/Riot/Modules/SecureBackup/Setup/SecureBackupSetupCoordinatorType.swift b/Riot/Modules/SecureBackup/Setup/SecureBackupSetupCoordinatorType.swift index b4392672f..0ab664999 100644 --- a/Riot/Modules/SecureBackup/Setup/SecureBackupSetupCoordinatorType.swift +++ b/Riot/Modules/SecureBackup/Setup/SecureBackupSetupCoordinatorType.swift @@ -18,7 +18,7 @@ import Foundation -protocol SecureBackupSetupCoordinatorDelegate: class { +protocol SecureBackupSetupCoordinatorDelegate: AnyObject { func secureBackupSetupCoordinatorDidComplete(_ coordinator: SecureBackupSetupCoordinatorType) func secureBackupSetupCoordinatorDidCancel(_ coordinator: SecureBackupSetupCoordinatorType) } diff --git a/Riot/Modules/ServiceTerms/Modal/Modal/ServiceTermsModalScreenCoordinatorType.swift b/Riot/Modules/ServiceTerms/Modal/Modal/ServiceTermsModalScreenCoordinatorType.swift index ebf6adc6e..909a0b33f 100644 --- a/Riot/Modules/ServiceTerms/Modal/Modal/ServiceTermsModalScreenCoordinatorType.swift +++ b/Riot/Modules/ServiceTerms/Modal/Modal/ServiceTermsModalScreenCoordinatorType.swift @@ -18,7 +18,7 @@ import Foundation -protocol ServiceTermsModalScreenCoordinatorDelegate: class { +protocol ServiceTermsModalScreenCoordinatorDelegate: AnyObject { func serviceTermsModalScreenCoordinatorDidAccept(_ coordinator: ServiceTermsModalScreenCoordinatorType) func serviceTermsModalScreenCoordinator(_ coordinator: ServiceTermsModalScreenCoordinatorType, displayPolicy policy: MXLoginPolicyData) func serviceTermsModalScreenCoordinatorDidDecline(_ coordinator: ServiceTermsModalScreenCoordinatorType) diff --git a/Riot/Modules/ServiceTerms/Modal/Modal/ServiceTermsModalScreenViewModelType.swift b/Riot/Modules/ServiceTerms/Modal/Modal/ServiceTermsModalScreenViewModelType.swift index 76fff52d3..4dd4b6317 100644 --- a/Riot/Modules/ServiceTerms/Modal/Modal/ServiceTermsModalScreenViewModelType.swift +++ b/Riot/Modules/ServiceTerms/Modal/Modal/ServiceTermsModalScreenViewModelType.swift @@ -18,11 +18,11 @@ import Foundation -protocol ServiceTermsModalScreenViewModelViewDelegate: class { +protocol ServiceTermsModalScreenViewModelViewDelegate: AnyObject { func serviceTermsModalScreenViewModel(_ viewModel: ServiceTermsModalScreenViewModelType, didUpdateViewState viewSate: ServiceTermsModalScreenViewState) } -protocol ServiceTermsModalScreenViewModelCoordinatorDelegate: class { +protocol ServiceTermsModalScreenViewModelCoordinatorDelegate: AnyObject { func serviceTermsModalScreenViewModel(_ coordinator: ServiceTermsModalScreenViewModelType, displayPolicy policy: MXLoginPolicyData) func serviceTermsModalScreenViewModelDidAccept(_ viewModel: ServiceTermsModalScreenViewModelType) func serviceTermsModalScreenViewModelDidDecline(_ viewModel: ServiceTermsModalScreenViewModelType) diff --git a/Riot/Modules/ServiceTerms/Modal/ServiceTermsModalCoordinatorType.swift b/Riot/Modules/ServiceTerms/Modal/ServiceTermsModalCoordinatorType.swift index 84f3386b9..34c09cb18 100644 --- a/Riot/Modules/ServiceTerms/Modal/ServiceTermsModalCoordinatorType.swift +++ b/Riot/Modules/ServiceTerms/Modal/ServiceTermsModalCoordinatorType.swift @@ -18,7 +18,7 @@ import Foundation -protocol ServiceTermsModalCoordinatorDelegate: class { +protocol ServiceTermsModalCoordinatorDelegate: AnyObject { func serviceTermsModalCoordinatorDidAccept(_ coordinator: ServiceTermsModalCoordinatorType) func serviceTermsModalCoordinatorDidDecline(_ coordinator: ServiceTermsModalCoordinatorType) func serviceTermsModalCoordinatorDidCancel(_ coordinator: ServiceTermsModalCoordinatorType) diff --git a/Riot/Modules/SetPinCode/EnterPinCode/EnterPinCodeCoordinatorType.swift b/Riot/Modules/SetPinCode/EnterPinCode/EnterPinCodeCoordinatorType.swift index 873a365bf..fb2a705bc 100644 --- a/Riot/Modules/SetPinCode/EnterPinCode/EnterPinCodeCoordinatorType.swift +++ b/Riot/Modules/SetPinCode/EnterPinCode/EnterPinCodeCoordinatorType.swift @@ -18,7 +18,7 @@ import Foundation -protocol EnterPinCodeCoordinatorDelegate: class { +protocol EnterPinCodeCoordinatorDelegate: AnyObject { func enterPinCodeCoordinatorDidComplete(_ coordinator: EnterPinCodeCoordinatorType) func enterPinCodeCoordinatorDidCompleteWithReset(_ coordinator: EnterPinCodeCoordinatorType, dueToTooManyErrors: Bool) func enterPinCodeCoordinator(_ coordinator: EnterPinCodeCoordinatorType, didCompleteWithPin pin: String) diff --git a/Riot/Modules/SetPinCode/EnterPinCode/EnterPinCodeViewModelType.swift b/Riot/Modules/SetPinCode/EnterPinCode/EnterPinCodeViewModelType.swift index eb66cdc8c..89d1bf8f1 100644 --- a/Riot/Modules/SetPinCode/EnterPinCode/EnterPinCodeViewModelType.swift +++ b/Riot/Modules/SetPinCode/EnterPinCode/EnterPinCodeViewModelType.swift @@ -18,13 +18,13 @@ import Foundation -protocol EnterPinCodeViewModelViewDelegate: class { +protocol EnterPinCodeViewModelViewDelegate: AnyObject { func enterPinCodeViewModel(_ viewModel: EnterPinCodeViewModelType, didUpdateViewState viewSate: EnterPinCodeViewState) func enterPinCodeViewModel(_ viewModel: EnterPinCodeViewModelType, didUpdatePlaceholdersCount count: Int) func enterPinCodeViewModel(_ viewModel: EnterPinCodeViewModelType, didUpdateCancelButtonHidden isHidden: Bool) } -protocol EnterPinCodeViewModelCoordinatorDelegate: class { +protocol EnterPinCodeViewModelCoordinatorDelegate: AnyObject { func enterPinCodeViewModelDidComplete(_ viewModel: EnterPinCodeViewModelType) func enterPinCodeViewModelDidCompleteWithReset(_ viewModel: EnterPinCodeViewModelType, dueToTooManyErrors: Bool) func enterPinCodeViewModel(_ viewModel: EnterPinCodeViewModelType, didCompleteWithPin pin: String) diff --git a/Riot/Modules/SetPinCode/SetPinCoordinatorType.swift b/Riot/Modules/SetPinCode/SetPinCoordinatorType.swift index 3649250b1..ae439b9ee 100644 --- a/Riot/Modules/SetPinCode/SetPinCoordinatorType.swift +++ b/Riot/Modules/SetPinCode/SetPinCoordinatorType.swift @@ -18,7 +18,7 @@ import Foundation -protocol SetPinCoordinatorDelegate: class { +protocol SetPinCoordinatorDelegate: AnyObject { func setPinCoordinatorDidComplete(_ coordinator: SetPinCoordinatorType) func setPinCoordinatorDidCompleteWithReset(_ coordinator: SetPinCoordinatorType, dueToTooManyErrors: Bool) func setPinCoordinatorDidCancel(_ coordinator: SetPinCoordinatorType) diff --git a/Riot/Modules/SetPinCode/SetupBiometrics/SetupBiometricsCoordinatorType.swift b/Riot/Modules/SetPinCode/SetupBiometrics/SetupBiometricsCoordinatorType.swift index 2cd9e9f83..f43d7759a 100644 --- a/Riot/Modules/SetPinCode/SetupBiometrics/SetupBiometricsCoordinatorType.swift +++ b/Riot/Modules/SetPinCode/SetupBiometrics/SetupBiometricsCoordinatorType.swift @@ -18,7 +18,7 @@ import Foundation -protocol SetupBiometricsCoordinatorDelegate: class { +protocol SetupBiometricsCoordinatorDelegate: AnyObject { func setupBiometricsCoordinatorDidComplete(_ coordinator: SetupBiometricsCoordinatorType) func setupBiometricsCoordinatorDidCompleteWithReset(_ coordinator: SetupBiometricsCoordinatorType, dueToTooManyErrors: Bool) func setupBiometricsCoordinatorDidCancel(_ coordinator: SetupBiometricsCoordinatorType) diff --git a/Riot/Modules/SetPinCode/SetupBiometrics/SetupBiometricsViewModelType.swift b/Riot/Modules/SetPinCode/SetupBiometrics/SetupBiometricsViewModelType.swift index 2b2708e1d..bd7f3124f 100644 --- a/Riot/Modules/SetPinCode/SetupBiometrics/SetupBiometricsViewModelType.swift +++ b/Riot/Modules/SetPinCode/SetupBiometrics/SetupBiometricsViewModelType.swift @@ -18,11 +18,11 @@ import Foundation -protocol SetupBiometricsViewModelViewDelegate: class { +protocol SetupBiometricsViewModelViewDelegate: AnyObject { func setupBiometricsViewModel(_ viewModel: SetupBiometricsViewModelType, didUpdateViewState viewSate: SetupBiometricsViewState) } -protocol SetupBiometricsViewModelCoordinatorDelegate: class { +protocol SetupBiometricsViewModelCoordinatorDelegate: AnyObject { func setupBiometricsViewModelDidComplete(_ viewModel: SetupBiometricsViewModelType) func setupBiometricsViewModelDidCompleteWithReset(_ viewModel: SetupBiometricsViewModelType, dueToTooManyErrors: Bool) func setupBiometricsViewModelDidCancel(_ viewModel: SetupBiometricsViewModelType) diff --git a/Riot/Modules/Settings/Discovery/SettingsDiscoveryTableViewSection.swift b/Riot/Modules/Settings/Discovery/SettingsDiscoveryTableViewSection.swift index 0c9116928..6bee0aebe 100644 --- a/Riot/Modules/Settings/Discovery/SettingsDiscoveryTableViewSection.swift +++ b/Riot/Modules/Settings/Discovery/SettingsDiscoveryTableViewSection.swift @@ -16,7 +16,7 @@ import Foundation -@objc protocol SettingsDiscoveryTableViewSectionDelegate: class { +@objc protocol SettingsDiscoveryTableViewSectionDelegate: AnyObject { func settingsDiscoveryTableViewSection(_ settingsDiscoveryTableViewSection: SettingsDiscoveryTableViewSection, tableViewCellClass: MXKTableViewCell.Type, forRow: Int) -> MXKTableViewCell func settingsDiscoveryTableViewSectionDidUpdate(_ settingsDiscoveryTableViewSection: SettingsDiscoveryTableViewSection) diff --git a/Riot/Modules/Settings/Discovery/SettingsDiscoveryViewModelType.swift b/Riot/Modules/Settings/Discovery/SettingsDiscoveryViewModelType.swift index c3ebf4dab..0435a5eb5 100644 --- a/Riot/Modules/Settings/Discovery/SettingsDiscoveryViewModelType.swift +++ b/Riot/Modules/Settings/Discovery/SettingsDiscoveryViewModelType.swift @@ -16,11 +16,11 @@ import Foundation -protocol SettingsDiscoveryViewModelViewDelegate: class { +protocol SettingsDiscoveryViewModelViewDelegate: AnyObject { func settingsDiscoveryViewModel(_ viewModel: SettingsDiscoveryViewModelType, didUpdateViewState viewState: SettingsDiscoveryViewState) } -@objc protocol SettingsDiscoveryViewModelCoordinatorDelegate: class { +@objc protocol SettingsDiscoveryViewModelCoordinatorDelegate: AnyObject { func settingsDiscoveryViewModel(_ viewModel: SettingsDiscoveryViewModel, didSelectThreePidWith medium: String, and address: String) func settingsDiscoveryViewModelDidTapUserSettingsLink(_ viewModel: SettingsDiscoveryViewModel) } diff --git a/Riot/Modules/Settings/Discovery/ThreePidDetails/SettingsDiscoveryThreePidDetailsViewModelType.swift b/Riot/Modules/Settings/Discovery/ThreePidDetails/SettingsDiscoveryThreePidDetailsViewModelType.swift index ce00918fd..df8ccf6f2 100644 --- a/Riot/Modules/Settings/Discovery/ThreePidDetails/SettingsDiscoveryThreePidDetailsViewModelType.swift +++ b/Riot/Modules/Settings/Discovery/ThreePidDetails/SettingsDiscoveryThreePidDetailsViewModelType.swift @@ -18,7 +18,7 @@ import Foundation -protocol SettingsDiscoveryThreePidDetailsViewModelViewDelegate: class { +protocol SettingsDiscoveryThreePidDetailsViewModelViewDelegate: AnyObject { func settingsDiscoveryThreePidDetailsViewModel(_ viewModel: SettingsDiscoveryThreePidDetailsViewModelType, didUpdateViewState viewSate: SettingsDiscoveryThreePidDetailsViewState) } diff --git a/Riot/Modules/Settings/IdentityServer/SettingsIdentityServerViewController.storyboard b/Riot/Modules/Settings/IdentityServer/SettingsIdentityServerViewController.storyboard index ab35e9993..b33325642 100644 --- a/Riot/Modules/Settings/IdentityServer/SettingsIdentityServerViewController.storyboard +++ b/Riot/Modules/Settings/IdentityServer/SettingsIdentityServerViewController.storyboard @@ -1,11 +1,9 @@ - - - - + + - + @@ -19,7 +17,7 @@ - + @@ -38,9 +36,8 @@ - - + @@ -63,7 +60,7 @@ -