diff --git a/CHANGES.rst b/CHANGES.rst index 6f580afbc..c08d93e5e 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,3 +1,38 @@ +Changes in 0.9.0 (2019-07-16) +=============================================== + +Improvements: + * Upgrade MatrixKit version ([v0.10.1](https://github.com/matrix-org/matrix-ios-kit/releases/tag/v0.10.1)). + * Upgrade MatrixKit version ([v0.10.0](https://github.com/matrix-org/matrix-ios-kit/releases/tag/v0.10.0)). + * RoomVC: When replying, use a "Reply" button instead of "Send". + * RoomVC: New message actions (#2394). + * Room upgrade: Autojoin the upgraded room when the user taps on the tombstone banner (#2486). + * Room upgrade: Use the `server_name` parameter when joining the new room (#2550). + * Join Room: Support via parameters to better handle federation (#2547). + * Reactions: Display existing reactions below the message (#2396). + * Menu actions: Display message time (#2463). + * Reactions Menu: Fix position (#2447). + * Context menu polish (#2466). + * Upgrade Piwik/MatomoTracker (v6.0.1) (#2159). + * Message Editing: Annotate edited messages in timeline (#2400). + * Message Editing: Editing in the timeline (#2404). + * Read receipts: They are now counted at the MatrixKit level. + * Migrate to Swift 5.0. + * Reactions: Update quick reactions (#2459). + * Message Editing: Handle reply edition (#2492). + * RoomVC: Add ability to upload a file that comes from outside the app’s sandbox (#2019). + * Share extension: Enable any file upload (max 5). + * Tools: Create filterCryptoLogs.sh to filter logs related to e2ee from Riot logs. + +Bug fix: + * Device Verification: Fix user display name and device id colors in dark theme + * Device Verification: Name for 🔒 is "Lock" (#2526). + * Device Verification: Name for ⏰ is "Clock. + * Registration with an email is broken (#2417). + * Reactions: Bad position (#2462). + * Reactions: It lets you react to join/leave events (#2476). + * Adjust size of the insert button in the People tab, thanks to @dcordero (PR #2473). + Changes in 0.8.6 (2019-05-06) =============================================== @@ -35,7 +70,7 @@ Changes in 0.8.4 (2019-03-21) Improvements: * Upgrade MatrixKit version ([v0.9.8](https://github.com/matrix-org/matrix-ios-kit/releases/tag/v0.9.8)). * Share extension: Remove image large size resizing choice if output dimension is too high to prevent memory limit exception (PR #2342). - + Bug fix: * Unable to open a file attachment of a room message (#2338). @@ -44,7 +79,7 @@ Changes in 0.8.3 (2019-03-13) Improvements: * Upgrade MatrixKit version ([v0.9.7](https://github.com/matrix-org/matrix-ios-kit/releases/tag/v0.9.7)). - + Bug fix: * Widgets: Attempt to re-register for a scalar token if ours is invalid (#2326). * Widgets: Pass scalar_token only when required. @@ -106,7 +141,7 @@ Improvements: * Key backup: Update key backup setup UI and UX (PR #2243). * Key backup: Logout warning (#2245). * Key backup: new recover method detected (#2230). - + Bug fix: * Use white scroll bar on dark themes (#2158). * Registration: fix tap gesture on checkboxes in the terms screen. diff --git a/Gemfile b/Gemfile index 49ffe69fb..1498a6710 100644 --- a/Gemfile +++ b/Gemfile @@ -2,7 +2,7 @@ source "https://rubygems.org" gem "xcode-install" gem "fastlane" -gem "cocoapods", '~>1.6.0' +gem "cocoapods", '~>1.7.2' plugins_path = File.join(File.dirname(__FILE__), 'fastlane', 'Pluginfile') eval_gemfile(plugins_path) if File.exist?(plugins_path) diff --git a/Gemfile.lock b/Gemfile.lock index 176b06f10..62c17a244 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -2,7 +2,7 @@ GEM remote: https://rubygems.org/ specs: CFPropertyList (3.0.0) - activesupport (4.2.11) + activesupport (4.2.11.1) i18n (~> 0.7) minitest (~> 5.1) thread_safe (~> 0.3, >= 0.3.4) @@ -12,11 +12,11 @@ GEM atomos (0.1.3) babosa (1.0.2) claide (1.0.2) - cocoapods (1.6.1) + cocoapods (1.7.2) activesupport (>= 4.0.2, < 5) claide (>= 1.0.2, < 2.0) - cocoapods-core (= 1.6.1) - cocoapods-deintegrate (>= 1.0.2, < 2.0) + cocoapods-core (= 1.7.2) + cocoapods-deintegrate (>= 1.0.3, < 2.0) cocoapods-downloader (>= 1.2.2, < 2.0) cocoapods-plugins (>= 1.0.0, < 2.0) cocoapods-search (>= 1.0.0, < 2.0) @@ -25,17 +25,17 @@ GEM cocoapods-try (>= 1.1.0, < 2.0) colored2 (~> 3.1) escape (~> 0.0.4) - fourflusher (>= 2.2.0, < 3.0) + fourflusher (>= 2.3.0, < 3.0) gh_inspector (~> 1.0) molinillo (~> 0.6.6) nap (~> 1.0) ruby-macho (~> 1.4) - xcodeproj (>= 1.8.1, < 2.0) - cocoapods-core (1.6.1) + xcodeproj (>= 1.10.0, < 2.0) + cocoapods-core (1.7.2) activesupport (>= 4.0.2, < 6) fuzzy_match (~> 2.0.4) nap (~> 1.0) - cocoapods-deintegrate (1.0.3) + cocoapods-deintegrate (1.0.4) cocoapods-downloader (1.2.2) cocoapods-plugins (1.0.0) nap @@ -49,16 +49,16 @@ GEM colored2 (3.1.2) commander-fastlane (4.4.6) highline (~> 1.7.2) - concurrent-ruby (1.1.4) + concurrent-ruby (1.1.5) declarative (0.0.10) declarative-option (0.1.0) digest-crc (0.4.1) domain_name (0.5.20180417) unf (>= 0.0.5, < 1.0.0) - dotenv (2.7.1) + dotenv (2.7.2) emoji_regex (1.0.1) escape (0.0.4) - excon (0.62.0) + excon (0.64.0) faraday (0.15.4) multipart-post (>= 1.2, < 3) faraday-cookie_jar (0.0.6) @@ -67,7 +67,7 @@ GEM faraday_middleware (0.13.1) faraday (>= 0.7.4, < 1.0) fastimage (2.1.5) - fastlane (2.116.1) + fastlane (2.125.2) CFPropertyList (>= 2.3, < 4.0.0) addressable (>= 2.3, < 3.0.0) babosa (>= 1.0.2, < 2.0.0) @@ -86,8 +86,8 @@ GEM google-cloud-storage (>= 1.15.0, < 2.0.0) highline (>= 1.7.2, < 2.0.0) json (< 3.0.0) + jwt (~> 2.1.0) mini_magick (~> 4.5.1) - multi_json multi_xml (~> 0.5) multipart-post (~> 2.0.0) plist (>= 3.1.0, < 4.0.0) @@ -96,16 +96,16 @@ GEM security (= 0.1.3) simctl (~> 1.6.3) slack-notifier (>= 2.0.0, < 3.0.0) - terminal-notifier (>= 1.6.2, < 2.0.0) + terminal-notifier (>= 2.0.0, < 3.0.0) terminal-table (>= 1.4.5, < 2.0.0) tty-screen (>= 0.6.3, < 1.0.0) tty-spinner (>= 0.8.0, < 1.0.0) word_wrap (~> 1.0.0) - xcodeproj (>= 1.6.0, < 2.0.0) + xcodeproj (>= 1.8.1, < 2.0.0) xcpretty (~> 0.3.0) xcpretty-travis-formatter (>= 0.0.3) fastlane-plugin-versioning (0.3.4) - fourflusher (2.2.0) + fourflusher (2.3.1) fuzzy_match (2.0.4) gh_inspector (1.1.3) google-api-client (0.23.9) @@ -118,7 +118,7 @@ GEM signet (~> 0.9) google-cloud-core (1.3.0) google-cloud-env (~> 1.0) - google-cloud-env (1.0.5) + google-cloud-env (1.1.0) faraday (~> 0.11) google-cloud-storage (1.16.0) digest-crc (~> 0.4) @@ -143,7 +143,7 @@ GEM memoist (0.16.0) mime-types (3.2.2) mime-types-data (~> 3.2015) - mime-types-data (3.2018.0812) + mime-types-data (3.2019.0331) mini_magick (4.5.1) minitest (5.11.3) molinillo (0.6.6) @@ -154,7 +154,7 @@ GEM nap (1.1.0) naturally (2.2.0) netrc (0.11.0) - os (1.0.0) + os (1.0.1) plist (3.5.0) public_suffix (2.0.5) representable (3.0.4) @@ -164,7 +164,7 @@ GEM retriable (3.1.2) rouge (2.0.7) ruby-macho (1.4.0) - rubyzip (1.2.2) + rubyzip (1.2.3) security (0.1.3) signet (0.11.0) addressable (~> 2.3) @@ -175,26 +175,26 @@ GEM CFPropertyList naturally slack-notifier (2.3.2) - terminal-notifier (1.8.0) + terminal-notifier (2.0.0) terminal-table (1.8.0) unicode-display_width (~> 1.1, >= 1.1.1) thread_safe (0.3.6) - tty-cursor (0.6.0) - tty-screen (0.6.5) - tty-spinner (0.9.0) - tty-cursor (~> 0.6.0) + tty-cursor (0.7.0) + tty-screen (0.7.0) + tty-spinner (0.9.1) + tty-cursor (~> 0.7) tzinfo (1.2.5) thread_safe (~> 0.1) uber (0.1.0) unf (0.1.4) unf_ext - unf_ext (0.0.7.5) - unicode-display_width (1.4.1) + unf_ext (0.0.7.6) + unicode-display_width (1.6.0) word_wrap (1.0.0) xcode-install (2.5.0) claide (>= 0.9.1, < 1.1.0) fastlane (>= 2.1.0, < 3.0.0) - xcodeproj (1.8.1) + xcodeproj (1.10.0) CFPropertyList (>= 2.3.3, < 4.0) atomos (~> 0.1.3) claide (>= 1.0.2, < 2.0) @@ -209,10 +209,10 @@ PLATFORMS ruby DEPENDENCIES - cocoapods (~> 1.6.0) + cocoapods (~> 1.7.2) fastlane fastlane-plugin-versioning xcode-install BUNDLED WITH - 1.17.3 + 2.0.2 diff --git a/Podfile b/Podfile index 280ff6c64..c75eeeea7 100644 --- a/Podfile +++ b/Podfile @@ -7,7 +7,7 @@ use_frameworks! # Different flavours of pods to MatrixKit # The current MatrixKit pod version -$matrixKitVersion = '0.9.9' +$matrixKitVersion = '0.10.1' # The develop branch version #$matrixKitVersion = 'develop' @@ -60,25 +60,25 @@ end abstract_target 'RiotPods' do pod 'GBDeviceInfo', '~> 5.2.0' - pod 'Reusable', '~> 4.0' + pod 'Reusable', '~> 4.1' + pod 'SwiftUTI', :git => 'https://github.com/speramusinc/SwiftUTI.git', :branch => 'master' # Piwik for analytics - # While https://github.com/matomo-org/matomo-sdk-ios/pull/223 is not released, use the PR branch - pod 'PiwikTracker', :git => 'https://github.com/manuroe/matomo-sdk-ios.git', :branch => 'feature/CustomVariables' - #pod 'PiwikTracker', '~> 4.4.2' + pod 'MatomoTracker', '~> 6.0.1' # Remove warnings from "bad" pods pod 'OLMKit', :inhibit_warnings => true pod 'cmark', :inhibit_warnings => true pod 'DTCoreText', :inhibit_warnings => true pod 'zxcvbn-ios' - + # Tools pod 'SwiftGen', '~> 6.1' - pod 'SwiftLint', '~> 0.30.1' + pod 'SwiftLint', '~> 0.33.0' target "Riot" do import_MatrixKit + pod 'DGCollectionViewLeftAlignFlowLayout', '~> 1.0.4' end target "RiotShareExtension" do @@ -101,12 +101,11 @@ post_install do |installer| target.build_configurations.each do |config| config.build_settings['ENABLE_BITCODE'] = 'NO' - # Required for PiwikTracker as `swift_version` is not defined in podspec. Should be removed - if target.name.include? 'PiwikTracker' - config.build_settings['SWIFT_VERSION'] = '4.0' + # Force SwiftUTI Swift version to 5.0 (as there is no code changes to perform for SwiftUTI fork using Swift 4.2) + if target.name.include? 'SwiftUTI' + config.build_settings['SWIFT_VERSION'] = '5.0' end end - end end diff --git a/Podfile.lock b/Podfile.lock index 952f5eb90..664d1aadf 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -15,6 +15,7 @@ PODS: - AFNetworking/UIKit (3.2.1): - AFNetworking/NSURLSession - cmark (0.24.1) + - DGCollectionViewLeftAlignFlowLayout (1.0.4) - DTCoreText (1.6.21): - DTCoreText/Core (= 1.6.21) - DTFoundation/Core (~> 1.7.5) @@ -44,79 +45,86 @@ PODS: - HPGrowingTextView (1.1) - JitsiMeetSDK (2.1.0) - libbase58 (0.1.4) - - libPhoneNumber-iOS (0.9.13) - - MatrixKit (0.9.9): + - libPhoneNumber-iOS (0.9.15) + - MatomoTracker (6.0.1): + - MatomoTracker/Core (= 6.0.1) + - MatomoTracker/Core (6.0.1) + - MatrixKit (0.10.1): - cmark (~> 0.24.1) - DTCoreText (~> 1.6.21) - HPGrowingTextView (~> 1.1) - libPhoneNumber-iOS (~> 0.9.13) - - MatrixKit/Core (= 0.9.9) - - MatrixSDK (= 0.12.5) - - MatrixKit/AppExtension (0.9.9): + - MatrixKit/Core (= 0.10.1) + - MatrixSDK (= 0.13.0) + - SwiftUTI (~> 1.0.6) + - MatrixKit/AppExtension (0.10.1): - cmark (~> 0.24.1) - DTCoreText (~> 1.6.21) - DTCoreText/Extension - HPGrowingTextView (~> 1.1) - libPhoneNumber-iOS (~> 0.9.13) - - MatrixSDK (= 0.12.5) - - MatrixKit/Core (0.9.9): + - MatrixSDK (= 0.13.0) + - SwiftUTI (~> 1.0.6) + - MatrixKit/Core (0.10.1): - cmark (~> 0.24.1) - DTCoreText (~> 1.6.21) - HPGrowingTextView (~> 1.1) - libPhoneNumber-iOS (~> 0.9.13) - - MatrixSDK (= 0.12.5) - - MatrixSDK (0.12.5): - - MatrixSDK/Core (= 0.12.5) - - MatrixSDK/Core (0.12.5): + - MatrixSDK (= 0.13.0) + - SwiftUTI (~> 1.0.6) + - MatrixSDK (0.13.0): + - MatrixSDK/Core (= 0.13.0) + - MatrixSDK/Core (0.13.0): - AFNetworking (~> 3.2.0) - GZIP (~> 1.2.2) - libbase58 (~> 0.1.4) - OLMKit (~> 3.1.0) - Realm (~> 3.13.1) - - MatrixSDK/JingleCallStack (0.12.5): + - MatrixSDK/JingleCallStack (0.13.0): - JitsiMeetSDK (~> 2.1.0) - MatrixSDK/Core - - MatrixSDK/SwiftSupport (0.12.5): + - MatrixSDK/SwiftSupport (0.13.0): - MatrixSDK/Core - OLMKit (3.1.0): - OLMKit/olmc (= 3.1.0) - OLMKit/olmcpp (= 3.1.0) - OLMKit/olmc (3.1.0) - OLMKit/olmcpp (3.1.0) - - PiwikTracker (4.4.2): - - PiwikTracker/Core (= 4.4.2) - - PiwikTracker/Core (4.4.2) - Realm (3.13.1): - Realm/Headers (= 3.13.1) - Realm/Headers (3.13.1) - - Reusable (4.0.5): - - Reusable/Storyboard (= 4.0.5) - - Reusable/View (= 4.0.5) - - Reusable/Storyboard (4.0.5) - - Reusable/View (4.0.5) + - Reusable (4.1.0): + - Reusable/Storyboard (= 4.1.0) + - Reusable/View (= 4.1.0) + - Reusable/Storyboard (4.1.0) + - Reusable/View (4.1.0) - SwiftGen (6.1.0) - - SwiftLint (0.30.1) + - SwiftLint (0.33.0) + - SwiftUTI (1.0.7) - zxcvbn-ios (1.0.4) DEPENDENCIES: - cmark + - DGCollectionViewLeftAlignFlowLayout (~> 1.0.4) - DTCoreText - GBDeviceInfo (~> 5.2.0) - - MatrixKit (= 0.9.9) - - MatrixKit/AppExtension (= 0.9.9) + - MatomoTracker (~> 6.0.1) + - MatrixKit (= 0.10.1) + - MatrixKit/AppExtension (= 0.10.1) - MatrixSDK/JingleCallStack - MatrixSDK/SwiftSupport - OLMKit - - PiwikTracker (from `https://github.com/manuroe/matomo-sdk-ios.git`, branch `feature/CustomVariables`) - - Reusable (~> 4.0) + - Reusable (~> 4.1) - SwiftGen (~> 6.1) - - SwiftLint (~> 0.30.1) + - SwiftLint (~> 0.33.0) + - SwiftUTI (from `https://github.com/speramusinc/SwiftUTI.git`, branch `master`) - zxcvbn-ios SPEC REPOS: https://github.com/cocoapods/specs.git: - AFNetworking - cmark + - DGCollectionViewLeftAlignFlowLayout - DTCoreText - DTFoundation - GBDeviceInfo @@ -125,6 +133,7 @@ SPEC REPOS: - JitsiMeetSDK - libbase58 - libPhoneNumber-iOS + - MatomoTracker - MatrixKit - MatrixSDK - OLMKit @@ -135,18 +144,19 @@ SPEC REPOS: - zxcvbn-ios EXTERNAL SOURCES: - PiwikTracker: - :branch: feature/CustomVariables - :git: https://github.com/manuroe/matomo-sdk-ios.git + SwiftUTI: + :branch: master + :git: https://github.com/speramusinc/SwiftUTI.git CHECKOUT OPTIONS: - PiwikTracker: - :commit: dfb048f25f4eefbe13ff7515c3c1c2cad5d94491 - :git: https://github.com/manuroe/matomo-sdk-ios.git + SwiftUTI: + :commit: c21237f13e9fb31a07f3fcd5243c5cf79d75901c + :git: https://github.com/speramusinc/SwiftUTI.git SPEC CHECKSUMS: AFNetworking: b6f891fdfaed196b46c7a83cf209e09697b94057 - cmark: ec0275215b504780287b6fca360224e384368af8 + cmark: 1d9ad0375e3b9fa281732e992467903606015520 + DGCollectionViewLeftAlignFlowLayout: a0fa58797373ded039cafba8133e79373d048399 DTCoreText: e5d688cffc9f6a61eddd1a4f94e2046851230de3 DTFoundation: f03be9fd786f11e505bb8fc44e2a3732bf0917df GBDeviceInfo: 2c65ceb9404f9079264d4c238f5b81916fdfc5e2 @@ -154,17 +164,18 @@ SPEC CHECKSUMS: HPGrowingTextView: 88a716d97fb853bcb08a4a08e4727da17efc9b19 JitsiMeetSDK: 3e66564af7f38a19142338955dd7f581801852b3 libbase58: 7c040313537b8c44b6e2d15586af8e21f7354efd - libPhoneNumber-iOS: e444379ac18bbfbdefad571da735b2cd7e096caa - MatrixKit: 6f553797e1ad42794b5336afb5cecb975ec69daa - MatrixSDK: ed0d0cee4877955052f19730bb3ee727e01ec948 + libPhoneNumber-iOS: 0a32a9525cf8744fe02c5206eb30d571e38f7d75 + MatomoTracker: 3ae4f65a1f5ace8043bda7244888fee28a734de5 + MatrixKit: f8224de32ca8b6e4c54a2654369cedec7744dc6d + MatrixSDK: 6886e7234c650408db5876b44a7f7608c865ce30 OLMKit: 4ee0159d63feeb86d836fdcfefe418e163511639 - PiwikTracker: 42862c7b13028065c3dfd36b4dc38db8a5765acf Realm: 50071da38fe079e0735e47c9f2eae738c68c5996 - Reusable: 188be1a54ac0691bc66e5bb24ec6eb91971b315b + Reusable: 82be188f29d96dc5eff0db7b2393bcc08d2cdd5b SwiftGen: f872ca75cbd17bf7103c17f13dcfa0d9a15667b0 - SwiftLint: a54bf1fe12b55c68560eb2a7689dfc81458508f7 + SwiftLint: fed9c66336e41fc74dc48a73678380718f0c8b0e + SwiftUTI: 917993c124f8eac25e88ced0202fc58d7eb50fa8 zxcvbn-ios: fef98b7c80f1512ff0eec47ac1fa399fc00f7e3c -PODFILE CHECKSUM: cfb6be050dfbb227d58b14434629e447ea54554b +PODFILE CHECKSUM: 6b3ff49b9c446763a5629e71bdde3fe8da7ba93f -COCOAPODS: 1.6.1 +COCOAPODS: 1.7.2 diff --git a/README.rst b/README.rst index d1c56e902..7e398492b 100644 --- a/README.rst +++ b/README.rst @@ -21,7 +21,8 @@ Before opening the Riot Xcode workspace, you need to build it with the CocoaPods command:: $ cd Riot - $ pod install + $ bundle install + $ bundle exec pod install This will load all dependencies for the Riot source code, including MatrixKit and MatrixSDK. You will need an recent and updated (``pod setup``) install of diff --git a/Riot.xcodeproj/project.pbxproj b/Riot.xcodeproj/project.pbxproj index 08f2bccf2..13306ca78 100644 --- a/Riot.xcodeproj/project.pbxproj +++ b/Riot.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 50; + objectVersion = 51; objects = { /* Begin PBXBuildFile section */ @@ -22,6 +22,7 @@ 32242F1721E8FBE500725742 /* Theme.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32242F0D21E8FBA900725742 /* Theme.swift */; }; 32242F1821E8FBF800725742 /* DefaultTheme.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32242F0F21E8FBA900725742 /* DefaultTheme.swift */; }; 32242F1921E8FBFB00725742 /* DarkTheme.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32242F1021E8FBA900725742 /* DarkTheme.swift */; }; + 322C110822BBC6F80043FEAC /* WidgetManagerConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 322C110722BBC6F80043FEAC /* WidgetManagerConfig.swift */; }; 3232AB1422564D9100AD6A5C /* swiftgen-config.yml in Resources */ = {isa = PBXBuildFile; fileRef = 3232AB0022564D9100AD6A5C /* swiftgen-config.yml */; }; 3232AB1522564D9100AD6A5C /* flat-swift4-vector.stencil in Resources */ = {isa = PBXBuildFile; fileRef = 3232AB0322564D9100AD6A5C /* flat-swift4-vector.stencil */; }; 3232AB2122564D9100AD6A5C /* README.md in Resources */ = {isa = PBXBuildFile; fileRef = 3232AB1322564D9100AD6A5C /* README.md */; }; @@ -74,7 +75,21 @@ 32891D712264DF7B00C82226 /* DeviceVerificationVerifiedViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32891D6F2264DF7B00C82226 /* DeviceVerificationVerifiedViewController.swift */; }; 32891D75226728EE00C82226 /* DeviceVerificationDataLoadingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32891D73226728EE00C82226 /* DeviceVerificationDataLoadingViewController.swift */; }; 32891D76226728EF00C82226 /* DeviceVerificationDataLoadingViewController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 32891D74226728EE00C82226 /* DeviceVerificationDataLoadingViewController.storyboard */; }; + 329E746622CD02EA006F9797 /* BubbleReactionActionViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 329E746422CD02EA006F9797 /* BubbleReactionActionViewCell.xib */; }; + 329E746722CD02EA006F9797 /* BubbleReactionActionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 329E746522CD02EA006F9797 /* BubbleReactionActionViewCell.swift */; }; + 32A6001622C661100042C1D9 /* EditHistoryViewState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32A6000D22C661100042C1D9 /* EditHistoryViewState.swift */; }; + 32A6001722C661100042C1D9 /* EditHistoryViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32A6000E22C661100042C1D9 /* EditHistoryViewController.swift */; }; + 32A6001822C661100042C1D9 /* EditHistoryViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32A6000F22C661100042C1D9 /* EditHistoryViewModel.swift */; }; + 32A6001922C661100042C1D9 /* EditHistoryViewModelType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32A6001022C661100042C1D9 /* EditHistoryViewModelType.swift */; }; + 32A6001A22C661100042C1D9 /* EditHistoryCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32A6001122C661100042C1D9 /* EditHistoryCoordinator.swift */; }; + 32A6001B22C661100042C1D9 /* EditHistoryViewAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32A6001222C661100042C1D9 /* EditHistoryViewAction.swift */; }; + 32A6001C22C661100042C1D9 /* EditHistoryViewController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 32A6001322C661100042C1D9 /* EditHistoryViewController.storyboard */; }; + 32A6001D22C661100042C1D9 /* EditHistoryCoordinatorType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32A6001422C661100042C1D9 /* EditHistoryCoordinatorType.swift */; }; + 32A6001E22C661100042C1D9 /* EditHistoryCoordinatorBridgePresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32A6001522C661100042C1D9 /* EditHistoryCoordinatorBridgePresenter.swift */; }; + 32A6002022C66FCF0042C1D9 /* EditHistoryMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32A6001F22C66FCF0042C1D9 /* EditHistoryMessage.swift */; }; 32B1FEDB21A46F2C00637127 /* TermsView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 32B1FEDA21A46F2C00637127 /* TermsView.xib */; }; + 32B94DF9228EC26400716A26 /* ReactionsMenuViewAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32B94DF2228EC26400716A26 /* ReactionsMenuViewAction.swift */; }; + 32B94DFA228EC26400716A26 /* ReactionsMenuButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32B94DF3228EC26400716A26 /* ReactionsMenuButton.swift */; }; 32BF994F21FA29A400698084 /* SettingsKeyBackupViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32BF994E21FA29A400698084 /* SettingsKeyBackupViewModel.swift */; }; 32BF995121FA29DC00698084 /* SettingsKeyBackupViewModelType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32BF995021FA29DC00698084 /* SettingsKeyBackupViewModelType.swift */; }; 32BF995321FA2A1300698084 /* SettingsKeyBackupViewState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32BF995221FA2A1300698084 /* SettingsKeyBackupViewState.swift */; }; @@ -138,6 +153,8 @@ B140B4A221F87F7100E3F5FE /* OperationQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = B140B4A121F87F7100E3F5FE /* OperationQueue.swift */; }; B140B4A621F89E7600E3F5FE /* KeyBackupSetupCoordinatorBridgePresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B140B4A521F89E7600E3F5FE /* KeyBackupSetupCoordinatorBridgePresenter.swift */; }; B140B4A821F8AB4600E3F5FE /* KeyBackupRecoverCoordinatorBridgePresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B140B4A721F8AB4600E3F5FE /* KeyBackupRecoverCoordinatorBridgePresenter.swift */; }; + B142317A22CCFA2000FFA96A /* EditHistoryCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B142317822CCFA2000FFA96A /* EditHistoryCell.swift */; }; + B142317B22CCFA2000FFA96A /* EditHistoryCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = B142317922CCFA2000FFA96A /* EditHistoryCell.xib */; }; B14F142E22144F6500FA0595 /* KeyBackupRecoverFromRecoveryKeyViewController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B14F142622144F6400FA0595 /* KeyBackupRecoverFromRecoveryKeyViewController.storyboard */; }; B14F142F22144F6500FA0595 /* KeyBackupRecoverFromRecoveryKeyViewModelType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B14F142722144F6400FA0595 /* KeyBackupRecoverFromRecoveryKeyViewModelType.swift */; }; B14F143022144F6500FA0595 /* KeyBackupRecoverFromRecoveryKeyCoordinatorType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B14F142822144F6500FA0595 /* KeyBackupRecoverFromRecoveryKeyCoordinatorType.swift */; }; @@ -187,8 +204,22 @@ B169331720F3CBE000746532 /* RecentCellData.m in Sources */ = {isa = PBXBuildFile; fileRef = B16932F920F3C51900746532 /* RecentCellData.m */; }; B17982FF2119FED2001FD722 /* GDPRConsentViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B17982FE2119FED2001FD722 /* GDPRConsentViewController.swift */; }; B1798302211B13B3001FD722 /* OnBoardingManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1798301211B13B3001FD722 /* OnBoardingManager.swift */; }; + B190F55922CE356800AEB493 /* EditHistoryHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B190F55822CE356800AEB493 /* EditHistoryHeaderView.swift */; }; + B190F55B22CE35FD00AEB493 /* EditHistoryHeaderView.xib in Resources */ = {isa = PBXBuildFile; fileRef = B190F55A22CE35FD00AEB493 /* EditHistoryHeaderView.xib */; }; + B190F55D22CE5A9700AEB493 /* EditHistorySection.swift in Sources */ = {isa = PBXBuildFile; fileRef = B190F55C22CE5A9600AEB493 /* EditHistorySection.swift */; }; + B1963B2B228F1C4900CBA17F /* BubbleReactionsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1963B25228F1C4800CBA17F /* BubbleReactionsView.swift */; }; + B1963B2C228F1C4900CBA17F /* BubbleReactionViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = B1963B26228F1C4800CBA17F /* BubbleReactionViewCell.xib */; }; + B1963B2D228F1C4900CBA17F /* BubbleReactionsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1963B27228F1C4800CBA17F /* BubbleReactionsViewModel.swift */; }; + B1963B2E228F1C4900CBA17F /* BubbleReactionViewData.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1963B28228F1C4800CBA17F /* BubbleReactionViewData.swift */; }; + B1963B2F228F1C4900CBA17F /* BubbleReactionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1963B29228F1C4800CBA17F /* BubbleReactionViewCell.swift */; }; + B1963B30228F1C4900CBA17F /* BubbleReactionsView.xib in Resources */ = {isa = PBXBuildFile; fileRef = B1963B2A228F1C4800CBA17F /* BubbleReactionsView.xib */; }; + B1963B32228F1C6B00CBA17F /* BubbleReactionsViewModelType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1963B31228F1C6B00CBA17F /* BubbleReactionsViewModelType.swift */; }; + B1963B3822933BC800CBA17F /* AutosizedCollectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1963B3722933BC800CBA17F /* AutosizedCollectionView.swift */; }; B19EFA3921F8BB2C00FC070E /* KeyBackupRecoverCoordinatorType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B19EFA3821F8BB2C00FC070E /* KeyBackupRecoverCoordinatorType.swift */; }; B19EFA3B21F8BB4100FC070E /* KeyBackupRecoverCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = B19EFA3A21F8BB4100FC070E /* KeyBackupRecoverCoordinator.swift */; }; + B1A5B33E227ADF2A004CBA85 /* UIImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1A5B33D227ADF2A004CBA85 /* UIImage.swift */; }; + B1A68593229E807A00D6C09A /* RoomBubbleCellLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1A68592229E807800D6C09A /* RoomBubbleCellLayout.swift */; }; + B1B12B2922942315002CB419 /* UITouch.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1B12B2822942315002CB419 /* UITouch.swift */; }; B1B5571820EE6C4D00210D55 /* CountryPickerViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = B1B5567A20EE6C4C00210D55 /* CountryPickerViewController.m */; }; B1B5571920EE6C4D00210D55 /* LanguagePickerViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = B1B5567C20EE6C4C00210D55 /* LanguagePickerViewController.m */; }; B1B5571A20EE6C4D00210D55 /* SettingsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = B1B5567E20EE6C4C00210D55 /* SettingsViewController.m */; }; @@ -426,10 +457,27 @@ B1B5599320EFC5E400210D55 /* DecryptionFailure.m in Sources */ = {isa = PBXBuildFile; fileRef = B1B5598D20EFC5E400210D55 /* DecryptionFailure.m */; }; B1B5599420EFC5E400210D55 /* DecryptionFailureTracker.m in Sources */ = {isa = PBXBuildFile; fileRef = B1B5599120EFC5E400210D55 /* DecryptionFailureTracker.m */; }; B1B9194C2118984300FE25B5 /* RoomPredecessorBubbleCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = B1B9194A2118984300FE25B5 /* RoomPredecessorBubbleCell.xib */; }; + B1C562CA2289C2690037F12A /* UIGestureRecognizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1C562C92289C2690037F12A /* UIGestureRecognizer.swift */; }; + B1C562CC228AB3510037F12A /* UIStackView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1C562CB228AB3510037F12A /* UIStackView.swift */; }; + B1C562D9228C0B760037F12A /* RoomContextualMenuItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1C562D8228C0B760037F12A /* RoomContextualMenuItem.swift */; }; + B1C562DB228C0BB00037F12A /* RoomContextualMenuAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1C562DA228C0BB00037F12A /* RoomContextualMenuAction.swift */; }; + B1C562E1228C7C8C0037F12A /* RoomContextualMenuToolbarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1C562DC228C7C890037F12A /* RoomContextualMenuToolbarView.swift */; }; + B1C562E2228C7C8D0037F12A /* RoomContextualMenuViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1C562DD228C7C8A0037F12A /* RoomContextualMenuViewController.swift */; }; + B1C562E3228C7C8D0037F12A /* RoomContextualMenuPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1C562DE228C7C8B0037F12A /* RoomContextualMenuPresenter.swift */; }; + B1C562E4228C7C8D0037F12A /* RoomContextualMenuToolbarView.xib in Resources */ = {isa = PBXBuildFile; fileRef = B1C562DF228C7C8C0037F12A /* RoomContextualMenuToolbarView.xib */; }; + B1C562E5228C7C8D0037F12A /* RoomContextualMenuViewController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B1C562E0228C7C8C0037F12A /* RoomContextualMenuViewController.storyboard */; }; + B1C562E8228C7CF20037F12A /* ContextualMenuItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1C562E6228C7CF10037F12A /* ContextualMenuItemView.swift */; }; + B1C562E9228C7CF20037F12A /* ContextualMenuItemView.xib in Resources */ = {isa = PBXBuildFile; fileRef = B1C562E7228C7CF20037F12A /* ContextualMenuItemView.xib */; }; B1CA3A2721EF6914000D1D89 /* UIViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1CA3A2621EF6913000D1D89 /* UIViewController.swift */; }; B1CA3A2921EF692B000D1D89 /* UIView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1CA3A2821EF692B000D1D89 /* UIView.swift */; }; B1CE9EFD22148703000FAE6A /* SignOutAlertPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1CE9EFC22148703000FAE6A /* SignOutAlertPresenter.swift */; }; B1CE9F062216FB09000FAE6A /* EncryptionKeysExportPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1CE9F052216FB09000FAE6A /* EncryptionKeysExportPresenter.swift */; }; + B1D1BDA622BBAFB500831367 /* ReactionsMenuView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1D1BDA522BBAFB500831367 /* ReactionsMenuView.swift */; }; + B1D1BDA822BBAFC900831367 /* ReactionsMenuView.xib in Resources */ = {isa = PBXBuildFile; fileRef = B1D1BDA722BBAFC900831367 /* ReactionsMenuView.xib */; }; + B1D211E222BD193C00D939BD /* ReactionsMenuViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1D211E122BD193C00D939BD /* ReactionsMenuViewModel.swift */; }; + B1D211E422C18E3800D939BD /* ReactionsMenuViewModelType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1D211E322C18E3800D939BD /* ReactionsMenuViewModelType.swift */; }; + B1D211E622C194A200D939BD /* ReactionsMenuViewState.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1D211E522C194A200D939BD /* ReactionsMenuViewState.swift */; }; + B1D211E822C195B400D939BD /* ReactionMenuItemViewData.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1D211E722C195B400D939BD /* ReactionMenuItemViewData.swift */; }; B1D250D82118AA0A000F4E93 /* RoomPredecessorBubbleCell.m in Sources */ = {isa = PBXBuildFile; fileRef = B1D250D72118AA0A000F4E93 /* RoomPredecessorBubbleCell.m */; }; B1D4752721EE4E630067973F /* KeyboardAvoider.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1D4752521EE4E620067973F /* KeyboardAvoider.swift */; }; B1D4752821EE4E630067973F /* KeyboardNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1D4752621EE4E620067973F /* KeyboardNotification.swift */; }; @@ -519,6 +567,7 @@ 32242F0F21E8FBA900725742 /* DefaultTheme.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DefaultTheme.swift; sourceTree = ""; }; 32242F1021E8FBA900725742 /* DarkTheme.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DarkTheme.swift; sourceTree = ""; }; 32242F1121E8FBA900725742 /* ThemeService.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ThemeService.h; sourceTree = ""; }; + 322C110722BBC6F80043FEAC /* WidgetManagerConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetManagerConfig.swift; sourceTree = ""; }; 3232AB0022564D9100AD6A5C /* swiftgen-config.yml */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "swiftgen-config.yml"; sourceTree = ""; }; 3232AB0322564D9100AD6A5C /* flat-swift4-vector.stencil */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "flat-swift4-vector.stencil"; sourceTree = ""; }; 3232AB1322564D9100AD6A5C /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; @@ -575,7 +624,21 @@ 32891D6F2264DF7B00C82226 /* DeviceVerificationVerifiedViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DeviceVerificationVerifiedViewController.swift; sourceTree = ""; }; 32891D73226728EE00C82226 /* DeviceVerificationDataLoadingViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DeviceVerificationDataLoadingViewController.swift; sourceTree = ""; }; 32891D74226728EE00C82226 /* DeviceVerificationDataLoadingViewController.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = DeviceVerificationDataLoadingViewController.storyboard; sourceTree = ""; }; + 329E746422CD02EA006F9797 /* BubbleReactionActionViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = BubbleReactionActionViewCell.xib; sourceTree = ""; }; + 329E746522CD02EA006F9797 /* BubbleReactionActionViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BubbleReactionActionViewCell.swift; sourceTree = ""; }; + 32A6000D22C661100042C1D9 /* EditHistoryViewState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EditHistoryViewState.swift; sourceTree = ""; }; + 32A6000E22C661100042C1D9 /* EditHistoryViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EditHistoryViewController.swift; sourceTree = ""; }; + 32A6000F22C661100042C1D9 /* EditHistoryViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EditHistoryViewModel.swift; sourceTree = ""; }; + 32A6001022C661100042C1D9 /* EditHistoryViewModelType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EditHistoryViewModelType.swift; sourceTree = ""; }; + 32A6001122C661100042C1D9 /* EditHistoryCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EditHistoryCoordinator.swift; sourceTree = ""; }; + 32A6001222C661100042C1D9 /* EditHistoryViewAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EditHistoryViewAction.swift; sourceTree = ""; }; + 32A6001322C661100042C1D9 /* EditHistoryViewController.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = EditHistoryViewController.storyboard; sourceTree = ""; }; + 32A6001422C661100042C1D9 /* EditHistoryCoordinatorType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EditHistoryCoordinatorType.swift; sourceTree = ""; }; + 32A6001522C661100042C1D9 /* EditHistoryCoordinatorBridgePresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EditHistoryCoordinatorBridgePresenter.swift; sourceTree = ""; }; + 32A6001F22C66FCF0042C1D9 /* EditHistoryMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditHistoryMessage.swift; sourceTree = ""; }; 32B1FEDA21A46F2C00637127 /* TermsView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = TermsView.xib; sourceTree = ""; }; + 32B94DF2228EC26400716A26 /* ReactionsMenuViewAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReactionsMenuViewAction.swift; sourceTree = ""; }; + 32B94DF3228EC26400716A26 /* ReactionsMenuButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReactionsMenuButton.swift; sourceTree = ""; }; 32BDC9A1211C2C870064AF51 /* zh_Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = zh_Hant; path = zh_Hant.lproj/InfoPlist.strings; sourceTree = ""; }; 32BDC9A2211C2C870064AF51 /* zh_Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = zh_Hant; path = zh_Hant.lproj/Localizable.strings; sourceTree = ""; }; 32BDC9A3211C2C870064AF51 /* zh_Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = zh_Hant; path = zh_Hant.lproj/Vector.strings; sourceTree = ""; }; @@ -661,6 +724,8 @@ B140B4A121F87F7100E3F5FE /* OperationQueue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OperationQueue.swift; sourceTree = ""; }; B140B4A521F89E7600E3F5FE /* KeyBackupSetupCoordinatorBridgePresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyBackupSetupCoordinatorBridgePresenter.swift; sourceTree = ""; }; B140B4A721F8AB4600E3F5FE /* KeyBackupRecoverCoordinatorBridgePresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyBackupRecoverCoordinatorBridgePresenter.swift; sourceTree = ""; }; + B142317822CCFA2000FFA96A /* EditHistoryCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditHistoryCell.swift; sourceTree = ""; }; + B142317922CCFA2000FFA96A /* EditHistoryCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = EditHistoryCell.xib; sourceTree = ""; }; B14F142622144F6400FA0595 /* KeyBackupRecoverFromRecoveryKeyViewController.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = KeyBackupRecoverFromRecoveryKeyViewController.storyboard; sourceTree = ""; }; B14F142722144F6400FA0595 /* KeyBackupRecoverFromRecoveryKeyViewModelType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyBackupRecoverFromRecoveryKeyViewModelType.swift; sourceTree = ""; }; B14F142822144F6500FA0595 /* KeyBackupRecoverFromRecoveryKeyCoordinatorType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyBackupRecoverFromRecoveryKeyCoordinatorType.swift; sourceTree = ""; }; @@ -758,8 +823,22 @@ B169331320F3CAFC00746532 /* PublicRoomsDirectoryDataSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PublicRoomsDirectoryDataSource.h; sourceTree = ""; }; B17982FE2119FED2001FD722 /* GDPRConsentViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GDPRConsentViewController.swift; sourceTree = ""; }; B1798301211B13B3001FD722 /* OnBoardingManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnBoardingManager.swift; sourceTree = ""; }; + B190F55822CE356800AEB493 /* EditHistoryHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditHistoryHeaderView.swift; sourceTree = ""; }; + B190F55A22CE35FD00AEB493 /* EditHistoryHeaderView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = EditHistoryHeaderView.xib; sourceTree = ""; }; + B190F55C22CE5A9600AEB493 /* EditHistorySection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditHistorySection.swift; sourceTree = ""; }; + B1963B25228F1C4800CBA17F /* BubbleReactionsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BubbleReactionsView.swift; sourceTree = ""; }; + B1963B26228F1C4800CBA17F /* BubbleReactionViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = BubbleReactionViewCell.xib; sourceTree = ""; }; + B1963B27228F1C4800CBA17F /* BubbleReactionsViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BubbleReactionsViewModel.swift; sourceTree = ""; }; + B1963B28228F1C4800CBA17F /* BubbleReactionViewData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BubbleReactionViewData.swift; sourceTree = ""; }; + B1963B29228F1C4800CBA17F /* BubbleReactionViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BubbleReactionViewCell.swift; sourceTree = ""; }; + B1963B2A228F1C4800CBA17F /* BubbleReactionsView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = BubbleReactionsView.xib; sourceTree = ""; }; + B1963B31228F1C6B00CBA17F /* BubbleReactionsViewModelType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BubbleReactionsViewModelType.swift; sourceTree = ""; }; + B1963B3722933BC800CBA17F /* AutosizedCollectionView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AutosizedCollectionView.swift; sourceTree = ""; }; B19EFA3821F8BB2C00FC070E /* KeyBackupRecoverCoordinatorType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyBackupRecoverCoordinatorType.swift; sourceTree = ""; }; B19EFA3A21F8BB4100FC070E /* KeyBackupRecoverCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyBackupRecoverCoordinator.swift; sourceTree = ""; }; + B1A5B33D227ADF2A004CBA85 /* UIImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIImage.swift; sourceTree = ""; }; + B1A68592229E807800D6C09A /* RoomBubbleCellLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomBubbleCellLayout.swift; sourceTree = ""; }; + B1B12B2822942315002CB419 /* UITouch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UITouch.swift; sourceTree = ""; }; B1B5567920EE6C4C00210D55 /* CountryPickerViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CountryPickerViewController.h; sourceTree = ""; }; B1B5567A20EE6C4C00210D55 /* CountryPickerViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CountryPickerViewController.m; sourceTree = ""; }; B1B5567C20EE6C4C00210D55 /* LanguagePickerViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LanguagePickerViewController.m; sourceTree = ""; }; @@ -1133,10 +1212,27 @@ B1B5599020EFC5E400210D55 /* Analytics.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Analytics.h; sourceTree = ""; }; B1B5599120EFC5E400210D55 /* DecryptionFailureTracker.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DecryptionFailureTracker.m; sourceTree = ""; }; B1B9194A2118984300FE25B5 /* RoomPredecessorBubbleCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = RoomPredecessorBubbleCell.xib; sourceTree = ""; }; + B1C562C92289C2690037F12A /* UIGestureRecognizer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIGestureRecognizer.swift; sourceTree = ""; }; + B1C562CB228AB3510037F12A /* UIStackView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIStackView.swift; sourceTree = ""; }; + B1C562D8228C0B760037F12A /* RoomContextualMenuItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomContextualMenuItem.swift; sourceTree = ""; }; + B1C562DA228C0BB00037F12A /* RoomContextualMenuAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomContextualMenuAction.swift; sourceTree = ""; }; + B1C562DC228C7C890037F12A /* RoomContextualMenuToolbarView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RoomContextualMenuToolbarView.swift; sourceTree = ""; }; + B1C562DD228C7C8A0037F12A /* RoomContextualMenuViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RoomContextualMenuViewController.swift; sourceTree = ""; }; + B1C562DE228C7C8B0037F12A /* RoomContextualMenuPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RoomContextualMenuPresenter.swift; sourceTree = ""; }; + B1C562DF228C7C8C0037F12A /* RoomContextualMenuToolbarView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = RoomContextualMenuToolbarView.xib; sourceTree = ""; }; + B1C562E0228C7C8C0037F12A /* RoomContextualMenuViewController.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = RoomContextualMenuViewController.storyboard; sourceTree = ""; }; + B1C562E6228C7CF10037F12A /* ContextualMenuItemView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContextualMenuItemView.swift; sourceTree = ""; }; + B1C562E7228C7CF20037F12A /* ContextualMenuItemView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = ContextualMenuItemView.xib; sourceTree = ""; }; B1CA3A2621EF6913000D1D89 /* UIViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIViewController.swift; sourceTree = ""; }; B1CA3A2821EF692B000D1D89 /* UIView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIView.swift; sourceTree = ""; }; B1CE9EFC22148703000FAE6A /* SignOutAlertPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignOutAlertPresenter.swift; sourceTree = ""; }; B1CE9F052216FB09000FAE6A /* EncryptionKeysExportPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EncryptionKeysExportPresenter.swift; sourceTree = ""; }; + B1D1BDA522BBAFB500831367 /* ReactionsMenuView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReactionsMenuView.swift; sourceTree = ""; }; + B1D1BDA722BBAFC900831367 /* ReactionsMenuView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ReactionsMenuView.xib; sourceTree = ""; }; + B1D211E122BD193C00D939BD /* ReactionsMenuViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReactionsMenuViewModel.swift; sourceTree = ""; }; + B1D211E322C18E3800D939BD /* ReactionsMenuViewModelType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReactionsMenuViewModelType.swift; sourceTree = ""; }; + B1D211E522C194A200D939BD /* ReactionsMenuViewState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReactionsMenuViewState.swift; sourceTree = ""; }; + B1D211E722C195B400D939BD /* ReactionMenuItemViewData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReactionMenuItemViewData.swift; sourceTree = ""; }; B1D250D62118AA0A000F4E93 /* RoomPredecessorBubbleCell.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RoomPredecessorBubbleCell.h; sourceTree = ""; }; B1D250D72118AA0A000F4E93 /* RoomPredecessorBubbleCell.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RoomPredecessorBubbleCell.m; sourceTree = ""; }; B1D4752521EE4E620067973F /* KeyboardAvoider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyboardAvoider.swift; sourceTree = ""; }; @@ -1447,6 +1543,43 @@ path = js; sourceTree = ""; }; + 32A6000C22C661100042C1D9 /* EditHistory */ = { + isa = PBXGroup; + children = ( + 32A6000D22C661100042C1D9 /* EditHistoryViewState.swift */, + 32A6000E22C661100042C1D9 /* EditHistoryViewController.swift */, + 32A6001322C661100042C1D9 /* EditHistoryViewController.storyboard */, + B190F55822CE356800AEB493 /* EditHistoryHeaderView.swift */, + B190F55A22CE35FD00AEB493 /* EditHistoryHeaderView.xib */, + B142317822CCFA2000FFA96A /* EditHistoryCell.swift */, + B142317922CCFA2000FFA96A /* EditHistoryCell.xib */, + 32A6000F22C661100042C1D9 /* EditHistoryViewModel.swift */, + 32A6001022C661100042C1D9 /* EditHistoryViewModelType.swift */, + 32A6001122C661100042C1D9 /* EditHistoryCoordinator.swift */, + 32A6001222C661100042C1D9 /* EditHistoryViewAction.swift */, + 32A6001422C661100042C1D9 /* EditHistoryCoordinatorType.swift */, + 32A6001522C661100042C1D9 /* EditHistoryCoordinatorBridgePresenter.swift */, + B190F55C22CE5A9600AEB493 /* EditHistorySection.swift */, + 32A6001F22C66FCF0042C1D9 /* EditHistoryMessage.swift */, + ); + path = EditHistory; + sourceTree = ""; + }; + 32B94DF0228EC26400716A26 /* ReactionsMenu */ = { + isa = PBXGroup; + children = ( + B1D211E322C18E3800D939BD /* ReactionsMenuViewModelType.swift */, + B1D211E122BD193C00D939BD /* ReactionsMenuViewModel.swift */, + B1D211E722C195B400D939BD /* ReactionMenuItemViewData.swift */, + B1D1BDA522BBAFB500831367 /* ReactionsMenuView.swift */, + B1D1BDA722BBAFC900831367 /* ReactionsMenuView.xib */, + B1D211E522C194A200D939BD /* ReactionsMenuViewState.swift */, + 32B94DF2228EC26400716A26 /* ReactionsMenuViewAction.swift */, + 32B94DF3228EC26400716A26 /* ReactionsMenuButton.swift */, + ); + path = ReactionsMenu; + sourceTree = ""; + }; 32BF994D21FA1C6300698084 /* KeyBackup */ = { isa = PBXGroup; children = ( @@ -1884,6 +2017,31 @@ path = OnBoarding; sourceTree = ""; }; + B1963B24228F1C4800CBA17F /* BubbleReactions */ = { + isa = PBXGroup; + children = ( + 329E746522CD02EA006F9797 /* BubbleReactionActionViewCell.swift */, + 329E746422CD02EA006F9797 /* BubbleReactionActionViewCell.xib */, + B1963B31228F1C6B00CBA17F /* BubbleReactionsViewModelType.swift */, + B1963B27228F1C4800CBA17F /* BubbleReactionsViewModel.swift */, + B1963B25228F1C4800CBA17F /* BubbleReactionsView.swift */, + B1963B2A228F1C4800CBA17F /* BubbleReactionsView.xib */, + B1963B28228F1C4800CBA17F /* BubbleReactionViewData.swift */, + B1963B29228F1C4800CBA17F /* BubbleReactionViewCell.swift */, + B1963B26228F1C4800CBA17F /* BubbleReactionViewCell.xib */, + ); + path = BubbleReactions; + sourceTree = ""; + }; + B1963B3622933B9500CBA17F /* CollectionView */ = { + isa = PBXGroup; + children = ( + B1963B3722933BC800CBA17F /* AutosizedCollectionView.swift */, + ); + name = CollectionView; + path = Riot/Modules/Common/CollectionView; + sourceTree = SOURCE_ROOT; + }; B1B5567620EE6C4C00210D55 /* Modules */ = { isa = PBXGroup; children = ( @@ -1985,6 +2143,7 @@ B1B5568E20EE6C4C00210D55 /* Room */ = { isa = PBXGroup; children = ( + 32A6000C22C661100042C1D9 /* EditHistory */, B1B5568F20EE6C4C00210D55 /* RoomViewController.h */, B1B556A020EE6C4C00210D55 /* RoomViewController.m */, B1B5569620EE6C4C00210D55 /* RoomViewController.xib */, @@ -1997,6 +2156,8 @@ B1B556A120EE6C4C00210D55 /* Files */, B1B556A420EE6C4C00210D55 /* Members */, B1B5569020EE6C4C00210D55 /* Settings */, + B1C562D7228C0B4C0037F12A /* ContextualMenu */, + B1963B24228F1C4800CBA17F /* BubbleReactions */, ); path = Room; sourceTree = ""; @@ -2178,6 +2339,7 @@ B1B556CD20EE6C4C00210D55 /* Common */ = { isa = PBXGroup; children = ( + B1963B3622933B9500CBA17F /* CollectionView */, B1B556CE20EE6C4C00210D55 /* WebViewController */, B1B556D120EE6C4C00210D55 /* NavigationController */, B1B556D420EE6C4C00210D55 /* SegmentedViewController */, @@ -2789,6 +2951,7 @@ B1B5584220EF768E00210D55 /* Encryption */ = { isa = PBXGroup; children = ( + B1A68592229E807800D6C09A /* RoomBubbleCellLayout.swift */, B1B5584320EF768E00210D55 /* RoomIncomingEncryptedTextMsgWithPaginationTitleBubbleCell.h */, B1B5584420EF768E00210D55 /* RoomOutgoingEncryptedTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.h */, B1B5584520EF768E00210D55 /* RoomOutgoingEncryptedAttachmentBubbleCell.h */, @@ -2943,6 +3106,7 @@ B1B5598420EFC3DF00210D55 /* WidgetManager.m */, B1B5598120EFC3DF00210D55 /* Widget.h */, B1B5598320EFC3DF00210D55 /* Widget.m */, + 322C110722BBC6F80043FEAC /* WidgetManagerConfig.swift */, ); path = Widgets; sourceTree = ""; @@ -2977,6 +3141,23 @@ path = Analytics; sourceTree = ""; }; + B1C562D7228C0B4C0037F12A /* ContextualMenu */ = { + isa = PBXGroup; + children = ( + 32B94DF0228EC26400716A26 /* ReactionsMenu */, + B1C562DA228C0BB00037F12A /* RoomContextualMenuAction.swift */, + B1C562D8228C0B760037F12A /* RoomContextualMenuItem.swift */, + B1C562DE228C7C8B0037F12A /* RoomContextualMenuPresenter.swift */, + B1C562DD228C7C8A0037F12A /* RoomContextualMenuViewController.swift */, + B1C562E0228C7C8C0037F12A /* RoomContextualMenuViewController.storyboard */, + B1C562E6228C7CF10037F12A /* ContextualMenuItemView.swift */, + B1C562E7228C7CF20037F12A /* ContextualMenuItemView.xib */, + B1C562DC228C7C890037F12A /* RoomContextualMenuToolbarView.swift */, + B1C562DF228C7C8C0037F12A /* RoomContextualMenuToolbarView.xib */, + ); + path = ContextualMenu; + sourceTree = ""; + }; B1CE9EFB22148681000FAE6A /* SignOut */ = { isa = PBXGroup; children = ( @@ -3131,6 +3312,10 @@ B109D6F0222D8C400061B6D9 /* UIApplication.swift */, B1DB4F05223015080065DBFA /* Character.swift */, B1DB4F0A223131600065DBFA /* String.swift */, + B1A5B33D227ADF2A004CBA85 /* UIImage.swift */, + B1C562C92289C2690037F12A /* UIGestureRecognizer.swift */, + B1C562CB228AB3510037F12A /* UIStackView.swift */, + B1B12B2822942315002CB419 /* UITouch.swift */, ); path = Categories; sourceTree = ""; @@ -3292,7 +3477,7 @@ 24CBEC4D1F0EAD310093EABB = { CreatedOnToolsVersion = 8.3.2; DevelopmentTeam = 7J4U792NQT; - LastSwiftMigration = 1010; + LastSwiftMigration = 1020; ProvisioningStyle = Automatic; SystemCapabilities = { com.apple.ApplicationGroups.iOS = { @@ -3313,7 +3498,7 @@ F094A9A11B78D8F000B1FBBF = { CreatedOnToolsVersion = 6.2; DevelopmentTeam = 7J4U792NQT; - LastSwiftMigration = 1010; + LastSwiftMigration = 1020; ProvisioningStyle = Automatic; SystemCapabilities = { com.apple.ApplicationGroups.iOS = { @@ -3426,13 +3611,16 @@ B1B558EA20EF768F00210D55 /* RoomOutgoingTextMsgWithPaginationTitleBubbleCell.xib in Resources */, B1B558CD20EF768F00210D55 /* RoomOutgoingEncryptedAttachmentWithPaginationTitleBubbleCell.xib in Resources */, B1B9194C2118984300FE25B5 /* RoomPredecessorBubbleCell.xib in Resources */, + B1C562E9228C7CF20037F12A /* ContextualMenuItemView.xib in Resources */, B1B5572120EE6C4D00210D55 /* ContactsTableViewController.xib in Resources */, B1B5593A20EF7BAC00210D55 /* TableViewCellWithLabelAndLargeTextView.xib in Resources */, B1B558D820EF768F00210D55 /* RoomIncomingEncryptedAttachmentWithPaginationTitleBubbleCell.xib in Resources */, B1B558DA20EF768F00210D55 /* RoomOutgoingEncryptedTextMsgWithoutSenderInfoBubbleCell.xib in Resources */, B1B558D620EF768F00210D55 /* RoomOutgoingEncryptedTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.xib in Resources */, B1B5593720EF7BAC00210D55 /* TableViewCellWithCheckBoxAndLabel.xib in Resources */, + B190F55B22CE35FD00AEB493 /* EditHistoryHeaderView.xib in Resources */, B1B5579020EF568D00210D55 /* GroupTableViewCell.xib in Resources */, + B142317B22CCFA2000FFA96A /* EditHistoryCell.xib in Resources */, B1B5581B20EF625800210D55 /* ExpandedRoomTitleView.xib in Resources */, B1B558C220EF768F00210D55 /* RoomIncomingEncryptedTextMsgBubbleCell.xib in Resources */, B1B558F620EF768F00210D55 /* RoomOutgoingTextMsgBubbleCell.xib in Resources */, @@ -3442,6 +3630,7 @@ B1B5574320EE6C4D00210D55 /* CallViewController.xib in Resources */, F083BDEA1E7009ED00A9B29C /* ringback.mp3 in Resources */, F083BDF21E7009ED00A9B29C /* GoogleService-Info.plist in Resources */, + 329E746622CD02EA006F9797 /* BubbleReactionActionViewCell.xib in Resources */, B1B558E320EF768F00210D55 /* RoomEmptyBubbleCell.xib in Resources */, B1E5368F21FB7258001F3AFF /* KeyBackupRecoverFromPassphraseViewController.storyboard in Resources */, B1B5590420EF768F00210D55 /* RoomOutgoingAttachmentBubbleCell.xib in Resources */, @@ -3462,6 +3651,7 @@ B1B5593920EF7BAC00210D55 /* TableViewCellWithCheckBoxes.xib in Resources */, B1B557C120EF5B4500210D55 /* DisabledRoomInputToolbarView.xib in Resources */, 32891D6C2264CBA300C82226 /* SimpleScreenTemplateViewController.storyboard in Resources */, + B1963B2C228F1C4900CBA17F /* BubbleReactionViewCell.xib in Resources */, B1664DA320F4F96200808783 /* Vector.strings in Resources */, B1B557C720EF5CD400210D55 /* DirectoryServerDetailTableViewCell.xib in Resources */, B1B5582620EF638A00210D55 /* RoomMemberTitleView.xib in Resources */, @@ -3475,6 +3665,7 @@ 3232AB1422564D9100AD6A5C /* swiftgen-config.yml in Resources */, 324A204F225FC571004FE8B0 /* DeviceVerificationIncomingViewController.storyboard in Resources */, 3232AB4B2256558300AD6A5C /* TemplateScreenViewController.storyboard in Resources */, + B1D1BDA822BBAFC900831367 /* ReactionsMenuView.xib in Resources */, 32B1FEDB21A46F2C00637127 /* TermsView.xib in Resources */, B1B5578E20EF568D00210D55 /* GroupInviteTableViewCell.xib in Resources */, B1B5582020EF625800210D55 /* SimpleRoomTitleView.xib in Resources */, @@ -3492,10 +3683,12 @@ B1B557C020EF5B4500210D55 /* RoomInputToolbarView.xib in Resources */, B1B5583D20EF6E7F00210D55 /* GroupRoomTableViewCell.xib in Resources */, B1B5572D20EE6C4D00210D55 /* RoomParticipantsViewController.xib in Resources */, + 32A6001C22C661100042C1D9 /* EditHistoryViewController.storyboard in Resources */, B1B5577220EE702800210D55 /* JitsiViewController.xib in Resources */, B1B557D720EF5EA900210D55 /* RoomActivitiesView.xib in Resources */, B1098BF821ECFE65000DDA48 /* KeyBackupSetupPassphraseViewController.storyboard in Resources */, F083BDF31E7009ED00A9B29C /* Images.xcassets in Resources */, + B1C562E4228C7C8D0037F12A /* RoomContextualMenuToolbarView.xib in Resources */, B1B5590720EF768F00210D55 /* RoomOutgoingTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.xib in Resources */, B169329920F39E6300746532 /* LaunchScreen.storyboard in Resources */, B1B5595320EF9A8700210D55 /* RecentTableViewCell.xib in Resources */, @@ -3512,6 +3705,7 @@ B1B5590F20EF782800210D55 /* TableViewCellWithPhoneNumberTextField.xib in Resources */, B1B5578520EF564900210D55 /* GroupTableViewCellWithSwitch.xib in Resources */, B1B557B320EF5AEF00210D55 /* EventDetailsView.xib in Resources */, + B1963B30228F1C4900CBA17F /* BubbleReactionsView.xib in Resources */, B1B557DD20EF5FBB00210D55 /* FilesSearchTableViewCell.xib in Resources */, B1B5590320EF768F00210D55 /* RoomSelectedStickerBubbleCell.xib in Resources */, 3232ABB62257BE6400AD6A5C /* DeviceVerificationVerifyViewController.storyboard in Resources */, @@ -3520,6 +3714,7 @@ B1B558C020EF768F00210D55 /* RoomIncomingEncryptedTextMsgWithoutSenderInfoBubbleCell.xib in Resources */, B1B5572420EE6C4D00210D55 /* RoomViewController.xib in Resources */, B169331520F3CAFC00746532 /* PublicRoomTableViewCell.xib in Resources */, + B1C562E5228C7C8D0037F12A /* RoomContextualMenuViewController.storyboard in Resources */, 3232ABA2225730E100AD6A5C /* DeviceVerificationStartViewController.storyboard in Resources */, 3284A35120A07C210044F922 /* postMessageAPI.js in Resources */, B1B557A220EF58AD00210D55 /* ContactTableViewCell.xib in Resources */, @@ -3553,52 +3748,11 @@ files = ( ); inputFileListPaths = ( - ); - inputPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-RiotPods-Riot/Pods-RiotPods-Riot-frameworks.sh", - "${BUILT_PRODUCTS_DIR}/AFNetworking/AFNetworking.framework", - "${BUILT_PRODUCTS_DIR}/DTCoreText/DTCoreText.framework", - "${BUILT_PRODUCTS_DIR}/DTFoundation/DTFoundation.framework", - "${BUILT_PRODUCTS_DIR}/GBDeviceInfo/GBDeviceInfo.framework", - "${BUILT_PRODUCTS_DIR}/GZIP/GZIP.framework", - "${BUILT_PRODUCTS_DIR}/HPGrowingTextView/HPGrowingTextView.framework", - "${PODS_ROOT}/JitsiMeetSDK/Frameworks/JitsiMeet.framework", - "${PODS_ROOT}/JitsiMeetSDK/Frameworks/WebRTC.framework", - "${BUILT_PRODUCTS_DIR}/MatrixKit/MatrixKit.framework", - "${BUILT_PRODUCTS_DIR}/MatrixSDK.common-JingleCallStack/MatrixSDK.framework", - "${BUILT_PRODUCTS_DIR}/OLMKit/OLMKit.framework", - "${BUILT_PRODUCTS_DIR}/PiwikTracker/PiwikTracker.framework", - "${BUILT_PRODUCTS_DIR}/Realm/Realm.framework", - "${BUILT_PRODUCTS_DIR}/Reusable/Reusable.framework", - "${BUILT_PRODUCTS_DIR}/cmark/cmark.framework", - "${BUILT_PRODUCTS_DIR}/libPhoneNumber-iOS/libPhoneNumber_iOS.framework", - "${BUILT_PRODUCTS_DIR}/libbase58/libbase58.framework", - "${BUILT_PRODUCTS_DIR}/zxcvbn-ios/zxcvbn_ios.framework", - "${BUILT_PRODUCTS_DIR}/DTCoreText.default-Extension/DTCoreText.framework", - "${BUILT_PRODUCTS_DIR}/MatrixKit-AppExtension/MatrixKit.framework", + "${PODS_ROOT}/Target Support Files/Pods-RiotPods-Riot/Pods-RiotPods-Riot-frameworks-${CONFIGURATION}-input-files.xcfilelist", ); name = "[CP] Embed Pods Frameworks"; outputFileListPaths = ( - ); - outputPaths = ( - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/AFNetworking.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/DTCoreText.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/DTFoundation.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GBDeviceInfo.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GZIP.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/HPGrowingTextView.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/JitsiMeet.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/WebRTC.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MatrixKit.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MatrixSDK.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/OLMKit.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/PiwikTracker.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Realm.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Reusable.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/cmark.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/libPhoneNumber_iOS.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/libbase58.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/zxcvbn_ios.framework", + "${PODS_ROOT}/Target Support Files/Pods-RiotPods-Riot/Pods-RiotPods-Riot-frameworks-${CONFIGURATION}-output-files.xcfilelist", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; @@ -3795,6 +3949,7 @@ B1B558E820EF768F00210D55 /* RoomIncomingAttachmentWithPaginationTitleBubbleCell.m in Sources */, B1B558F320EF768F00210D55 /* RoomOutgoingAttachmentWithoutSenderInfoBubbleCell.m in Sources */, B1B557BD20EF5B4500210D55 /* KeyboardGrowingTextView.m in Sources */, + B1A68593229E807A00D6C09A /* RoomBubbleCellLayout.swift in Sources */, B1B558F420EF768F00210D55 /* RoomOutgoingTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.m in Sources */, B1B5572320EE6C4D00210D55 /* AttachmentsViewController.m in Sources */, F083BDEE1E7009ED00A9B29C /* MXRoom+Riot.m in Sources */, @@ -3805,9 +3960,11 @@ B1098BDF21ECE09F000DDA48 /* Strings.swift in Sources */, B1B558C420EF768F00210D55 /* RoomIncomingEncryptedTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.m in Sources */, 3232ABC022594C0900AD6A5C /* VerifyEmojiCollectionViewCell.swift in Sources */, + B1963B2E228F1C4900CBA17F /* BubbleReactionViewData.swift in Sources */, B1B5572F20EE6C4D00210D55 /* ReadReceiptsViewController.m in Sources */, B1B558CB20EF768F00210D55 /* RoomIncomingEncryptedTextMsgWithoutSenderInfoBubbleCell.m in Sources */, B169330B20F3CA3A00746532 /* Contact.m in Sources */, + B1A5B33E227ADF2A004CBA85 /* UIImage.swift in Sources */, B1D4752A21EE52B10067973F /* KeyBackupSetupIntroViewController.swift in Sources */, B1B5599220EFC5E400210D55 /* Analytics.m in Sources */, B14F143422144F6500FA0595 /* KeyBackupRecoverFromRecoveryKeyViewAction.swift in Sources */, @@ -3815,9 +3972,11 @@ B1B558C320EF768F00210D55 /* RoomIncomingEncryptedTextMsgWithoutSenderNameBubbleCell.m in Sources */, B110872521F098F0003554A5 /* ActivityIndicatorPresenter.swift in Sources */, 32242F1521E8FBA900725742 /* DarkTheme.swift in Sources */, + B1D211E222BD193C00D939BD /* ReactionsMenuViewModel.swift in Sources */, B140B4A621F89E7600E3F5FE /* KeyBackupSetupCoordinatorBridgePresenter.swift in Sources */, B1B5577420EE702900210D55 /* WidgetViewController.m in Sources */, B139C21B21FE5B9200BB68EC /* KeyBackupRecoverFromPassphraseViewModel.swift in Sources */, + 32A6001E22C661100042C1D9 /* EditHistoryCoordinatorBridgePresenter.swift in Sources */, B1B5574A20EE6C4D00210D55 /* MediaPickerViewController.m in Sources */, B1B5598520EFC3E000210D55 /* RageShakeManager.m in Sources */, 3232ABA8225730E100AD6A5C /* DeviceVerificationStartViewState.swift in Sources */, @@ -3836,6 +3995,7 @@ B1B5593B20EF7BAC00210D55 /* TableViewCellWithCheckBoxAndLabel.m in Sources */, B1B5581A20EF625800210D55 /* ExpandedRoomTitleView.m in Sources */, B1107EC82200B0720038014B /* KeyBackupRecoverSuccessViewController.swift in Sources */, + B1963B2F228F1C4900CBA17F /* BubbleReactionViewCell.swift in Sources */, B1B558E920EF768F00210D55 /* RoomSelectedStickerBubbleCell.m in Sources */, B1B558DF20EF768F00210D55 /* RoomOutgoingTextMsgWithoutSenderInfoBubbleCell.m in Sources */, F083BE041E7009ED00A9B29C /* Tools.m in Sources */, @@ -3852,6 +4012,7 @@ 32891D712264DF7B00C82226 /* DeviceVerificationVerifiedViewController.swift in Sources */, F083BDEF1E7009ED00A9B29C /* UINavigationController+Riot.m in Sources */, B1B5581F20EF625800210D55 /* SimpleRoomTitleView.m in Sources */, + B1C562E2228C7C8D0037F12A /* RoomContextualMenuViewController.swift in Sources */, B169330020F3C97D00746532 /* RoomDataSource.m in Sources */, B1B558ED20EF768F00210D55 /* RoomIncomingTextMsgWithoutSenderNameBubbleCell.m in Sources */, B1B5571920EE6C4D00210D55 /* LanguagePickerViewController.m in Sources */, @@ -3862,19 +4023,23 @@ B1B558DD20EF768F00210D55 /* RoomIncomingEncryptedTextMsgBubbleCell.m in Sources */, B1098BE521ECE1FC000DDA48 /* Storyboards.swift in Sources */, 3232ABC2225B996200AD6A5C /* Themable.swift in Sources */, + 32A6001B22C661100042C1D9 /* EditHistoryViewAction.swift in Sources */, 3232ABA7225730E100AD6A5C /* DeviceVerificationStartCoordinator.swift in Sources */, B1D4752721EE4E630067973F /* KeyboardAvoider.swift in Sources */, B1D4752821EE4E630067973F /* KeyboardNotification.swift in Sources */, + B1D1BDA622BBAFB500831367 /* ReactionsMenuView.swift in Sources */, B1B5573C20EE6C4D00210D55 /* MasterTabBarController.m in Sources */, 32F6B96E2270623100BBA352 /* DeviceVerificationDataLoadingViewModelType.swift in Sources */, B1B5592C20EF7A5D00210D55 /* TableViewCellWithButton.m in Sources */, 32242F1421E8FBA900725742 /* DefaultTheme.swift in Sources */, + B1963B2D228F1C4900CBA17F /* BubbleReactionsViewModel.swift in Sources */, 32242F1321E8FBA900725742 /* Theme.swift in Sources */, B1B5582520EF638A00210D55 /* RoomMemberTitleView.m in Sources */, B1B5582C20EF666100210D55 /* DirectoryRecentTableViewCell.m in Sources */, B1B558E420EF768F00210D55 /* RoomMembershipWithPaginationTitleBubbleCell.m in Sources */, B1B5573620EE6C4D00210D55 /* GroupsViewController.m in Sources */, 3232ABB82257BE6500AD6A5C /* DeviceVerificationVerifyCoordinator.swift in Sources */, + B142317A22CCFA2000FFA96A /* EditHistoryCell.swift in Sources */, B1B5572A20EE6C4D00210D55 /* RoomMemberDetailsViewController.m in Sources */, B1B5590120EF768F00210D55 /* RoomMembershipExpandedWithPaginationTitleBubbleCell.m in Sources */, 32F6B96B2270623100BBA352 /* DeviceVerificationDataLoadingViewAction.swift in Sources */, @@ -3891,9 +4056,11 @@ 3232ABBA2257BE6500AD6A5C /* DeviceVerificationVerifyViewModel.swift in Sources */, B1098C1021ED07E4000DDA48 /* Presentable.swift in Sources */, B1B558E020EF768F00210D55 /* RoomOutgoingTextMsgBubbleCell.m in Sources */, + B1C562E3228C7C8D0037F12A /* RoomContextualMenuPresenter.swift in Sources */, B1B5593C20EF7BAC00210D55 /* TableViewCellWithCheckBoxes.m in Sources */, 32891D6B2264CBA300C82226 /* SimpleScreenTemplateViewController.swift in Sources */, B1CA3A2721EF6914000D1D89 /* UIViewController.swift in Sources */, + 322C110822BBC6F80043FEAC /* WidgetManagerConfig.swift in Sources */, F0D2ADA11F6AA5FD00A7097D /* MXRoomSummary+Riot.m in Sources */, B1B5596F20EFA85D00210D55 /* EncryptionInfoView.m in Sources */, B1B5573820EE6C4D00210D55 /* GroupParticipantsViewController.m in Sources */, @@ -3906,6 +4073,7 @@ B139C22121FE5D9D00BB68EC /* KeyBackupRecoverFromPassphraseViewState.swift in Sources */, B1B5579120EF568D00210D55 /* GroupInviteTableViewCell.m in Sources */, B1B5579A20EF575B00210D55 /* ForgotPasswordInputsView.m in Sources */, + B1B12B2922942315002CB419 /* UITouch.swift in Sources */, B1B558CC20EF768F00210D55 /* RoomOutgoingEncryptedAttachmentWithoutSenderInfoBubbleCell.m in Sources */, B1B5571D20EE6C4D00210D55 /* HomeViewController.m in Sources */, 3232ABA6225730E100AD6A5C /* DeviceVerificationStartViewController.swift in Sources */, @@ -3922,6 +4090,7 @@ B1B5572620EE6C4D00210D55 /* RoomFilesSearchViewController.m in Sources */, B1B5583120EF66BA00210D55 /* RoomIdOrAliasTableViewCell.m in Sources */, B1CA3A2921EF692B000D1D89 /* UIView.swift in Sources */, + 32A6001D22C661100042C1D9 /* EditHistoryCoordinatorType.swift in Sources */, F083BDFA1E7009ED00A9B29C /* RoomPreviewData.m in Sources */, B1B557B420EF5AEF00210D55 /* EventDetailsView.m in Sources */, B1B5577E20EE84BF00210D55 /* IncomingCallView.m in Sources */, @@ -3936,7 +4105,10 @@ B1B5572020EE6C4D00210D55 /* ContactsTableViewController.m in Sources */, B1B5581920EF625800210D55 /* RoomTitleView.m in Sources */, B1098BE321ECE09F000DDA48 /* RiotDefaults.swift in Sources */, + B1C562CA2289C2690037F12A /* UIGestureRecognizer.swift in Sources */, + B1C562CC228AB3510037F12A /* UIStackView.swift in Sources */, B1B557BE20EF5B4500210D55 /* RoomInputToolbarView.m in Sources */, + 32A6001922C661100042C1D9 /* EditHistoryViewModelType.swift in Sources */, B1B5573B20EE6C4D00210D55 /* FavouritesViewController.m in Sources */, B1B5579920EF575B00210D55 /* AuthInputsView.m in Sources */, B1B5597520EFB02A00210D55 /* InviteRecentTableViewCell.m in Sources */, @@ -3944,10 +4116,14 @@ B1798302211B13B3001FD722 /* OnBoardingManager.swift in Sources */, B1B5573520EE6C4D00210D55 /* GroupDetailsViewController.m in Sources */, B10B3B5B2201DD740072C76B /* KeyBackupBannerCell.swift in Sources */, + B1963B32228F1C6B00CBA17F /* BubbleReactionsViewModelType.swift in Sources */, + 32A6001722C661100042C1D9 /* EditHistoryViewController.swift in Sources */, B1098BFA21ECFE65000DDA48 /* KeyBackupSetupPassphraseViewModel.swift in Sources */, B1B5575220EE6C4D00210D55 /* RoomKeyRequestViewController.m in Sources */, + 32A6001A22C661100042C1D9 /* EditHistoryCoordinator.swift in Sources */, F083BD1E1E7009ED00A9B29C /* AppDelegate.m in Sources */, B1B558E620EF768F00210D55 /* RoomIncomingAttachmentWithoutSenderInfoBubbleCell.m in Sources */, + 329E746722CD02EA006F9797 /* BubbleReactionActionViewCell.swift in Sources */, B1098BFB21ECFE65000DDA48 /* KeyBackupSetupCoordinatorType.swift in Sources */, B1098BF721ECFE65000DDA48 /* PasswordStrength.swift in Sources */, 324A2052225FC571004FE8B0 /* DeviceVerificationIncomingViewAction.swift in Sources */, @@ -3955,11 +4131,13 @@ B1B557D820EF5EA900210D55 /* RoomActivitiesView.m in Sources */, B1B5596620EF9E9B00210D55 /* RoomTableViewCell.m in Sources */, B14F143322144F6500FA0595 /* KeyBackupRecoverFromRecoveryKeyViewModel.swift in Sources */, + 32A6001822C661100042C1D9 /* EditHistoryViewModel.swift in Sources */, B1B558D020EF768F00210D55 /* RoomOutgoingEncryptedTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.m in Sources */, B1B558CF20EF768F00210D55 /* RoomIncomingEncryptedTextMsgWithPaginationTitleBubbleCell.m in Sources */, B140B4A221F87F7100E3F5FE /* OperationQueue.swift in Sources */, B1B5575120EE6C4D00210D55 /* AuthenticationViewController.m in Sources */, B1B5571820EE6C4D00210D55 /* CountryPickerViewController.m in Sources */, + B1D211E622C194A200D939BD /* ReactionsMenuViewState.swift in Sources */, B17982FF2119FED2001FD722 /* GDPRConsentViewController.swift in Sources */, B1098BE121ECE09F000DDA48 /* Images.swift in Sources */, 3232ABA4225730E100AD6A5C /* DeviceVerificationStartViewAction.swift in Sources */, @@ -3977,11 +4155,14 @@ 324A2054225FC571004FE8B0 /* DeviceVerificationIncomingCoordinatorType.swift in Sources */, 3232ABB92257BE6500AD6A5C /* DeviceVerificationVerifyViewController.swift in Sources */, B139C21F21FE5D6600BB68EC /* KeyBackupRecoverFromPassphraseViewAction.swift in Sources */, + B1C562DB228C0BB00037F12A /* RoomContextualMenuAction.swift in Sources */, B1B5574720EE6C4D00210D55 /* UsersDevicesViewController.m in Sources */, B1098BFF21ECFE65000DDA48 /* PasswordStrengthView.swift in Sources */, B1B558D220EF768F00210D55 /* RoomEncryptedDataBubbleCell.m in Sources */, B1B558FA20EF768F00210D55 /* RoomMembershipBubbleCell.m in Sources */, 3232ABA1225730E100AD6A5C /* DeviceVerificationCoordinatorType.swift in Sources */, + B1C562D9228C0B760037F12A /* RoomContextualMenuItem.swift in Sources */, + B1C562E1228C7C8C0037F12A /* RoomContextualMenuToolbarView.swift in Sources */, B1B557BF20EF5B4500210D55 /* DisabledRoomInputToolbarView.m in Sources */, B1B5578620EF564900210D55 /* GroupTableViewCellWithSwitch.m in Sources */, B1098BE821ECFE52000DDA48 /* Coordinator.swift in Sources */, @@ -3994,8 +4175,10 @@ B110872621F098F0003554A5 /* ActivityIndicatorView.swift in Sources */, B19EFA3921F8BB2C00FC070E /* KeyBackupRecoverCoordinatorType.swift in Sources */, B1E5368D21FB7245001F3AFF /* KeyBackupRecoverFromPassphraseViewController.swift in Sources */, + B1963B3822933BC800CBA17F /* AutosizedCollectionView.swift in Sources */, B169330320F3C98900746532 /* RoomBubbleCellData.m in Sources */, B1B557CC20EF5D8000210D55 /* DirectoryServerTableViewCell.m in Sources */, + B1963B2B228F1C4900CBA17F /* BubbleReactionsView.swift in Sources */, B1B5575C20EE6C4D00210D55 /* DirectoryViewController.m in Sources */, B1B558BD20EF768F00210D55 /* RoomOutgoingEncryptedTextMsgWithoutSenderNameBubbleCell.m in Sources */, B1B5577020EE702800210D55 /* WidgetPickerViewController.m in Sources */, @@ -4006,18 +4189,24 @@ B16932EE20F3C3C900746532 /* FilesSearchCellData.m in Sources */, B1B558E520EF768F00210D55 /* RoomMembershipExpandedBubbleCell.m in Sources */, 32BF995121FA29DC00698084 /* SettingsKeyBackupViewModelType.swift in Sources */, + B190F55922CE356800AEB493 /* EditHistoryHeaderView.swift in Sources */, 32F6B96A2270623100BBA352 /* DeviceVerificationDataLoadingViewState.swift in Sources */, 32BF995321FA2A1300698084 /* SettingsKeyBackupViewState.swift in Sources */, + 32B94DF9228EC26400716A26 /* ReactionsMenuViewAction.swift in Sources */, B1B5599420EFC5E400210D55 /* DecryptionFailureTracker.m in Sources */, F083BDF01E7009ED00A9B29C /* UIViewController+RiotSearch.m in Sources */, F083BDF91E7009ED00A9B29C /* RoomEmailInvitation.m in Sources */, + B1D211E422C18E3800D939BD /* ReactionsMenuViewModelType.swift in Sources */, 324A2055225FC571004FE8B0 /* DeviceVerificationIncomingViewModelType.swift in Sources */, B1B5572C20EE6C4D00210D55 /* RoomParticipantsViewController.m in Sources */, B1B558EE20EF768F00210D55 /* RoomOutgoingAttachmentBubbleCell.m in Sources */, 3232ABB52257BE6400AD6A5C /* DeviceVerificationVerifyCoordinatorType.swift in Sources */, 32BF994F21FA29A400698084 /* SettingsKeyBackupViewModel.swift in Sources */, + B190F55D22CE5A9700AEB493 /* EditHistorySection.swift in Sources */, + 32A6002022C66FCF0042C1D9 /* EditHistoryMessage.swift in Sources */, B1B5574920EE6C4D00210D55 /* RiotSplitViewController.m in Sources */, B1B5574E20EE6C4D00210D55 /* DirectoryServerPickerViewController.m in Sources */, + B1D211E822C195B400D939BD /* ReactionMenuItemViewData.swift in Sources */, B1DB4F0B223131600065DBFA /* String.swift in Sources */, 3232AB522256558300AD6A5C /* TemplateScreenViewModel.swift in Sources */, B1B5575B20EE6C4D00210D55 /* HomeFilesSearchViewController.m in Sources */, @@ -4036,8 +4225,11 @@ B1098C0021ECFE65000DDA48 /* KeyBackupSetupPassphraseViewController.swift in Sources */, B1B5591020EF782800210D55 /* TableViewCellWithPhoneNumberTextField.m in Sources */, B1DB4F06223015080065DBFA /* Character.swift in Sources */, + 32B94DFA228EC26400716A26 /* ReactionsMenuButton.swift in Sources */, + B1C562E8228C7CF20037F12A /* ContextualMenuItemView.swift in Sources */, B14F143022144F6500FA0595 /* KeyBackupRecoverFromRecoveryKeyCoordinatorType.swift in Sources */, B1E5368921FB1E20001F3AFF /* UIButton.swift in Sources */, + 32A6001622C661100042C1D9 /* EditHistoryViewState.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -4190,7 +4382,7 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; SWIFT_OBJC_BRIDGING_HEADER = "$(SRCROOT)/$(PRODUCT_NAME)/SupportingFiles/RiotShareExtension-Bridging-Header.h"; - SWIFT_VERSION = 4.2; + SWIFT_VERSION = 5.0; USER_HEADER_SEARCH_PATHS = "$(inherited)"; }; name = Debug; @@ -4230,7 +4422,7 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; SWIFT_OBJC_BRIDGING_HEADER = "$(SRCROOT)/$(PRODUCT_NAME)/SupportingFiles/RiotShareExtension-Bridging-Header.h"; - SWIFT_VERSION = 4.2; + SWIFT_VERSION = 5.0; USER_HEADER_SEARCH_PATHS = "$(inherited)"; }; name = Release; @@ -4433,7 +4625,7 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "$(SRCROOT)/$(PRODUCT_NAME)/SupportingFiles/Riot-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 4.2; + SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; USER_HEADER_SEARCH_PATHS = "$(inherited)"; }; @@ -4464,7 +4656,7 @@ PRODUCT_BUNDLE_IDENTIFIER = im.vector.app; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "$(SRCROOT)/$(PRODUCT_NAME)/SupportingFiles/Riot-Bridging-Header.h"; - SWIFT_VERSION = 4.2; + SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; USER_HEADER_SEARCH_PATHS = "$(inherited)"; }; diff --git a/Riot/AppDelegate.m b/Riot/AppDelegate.m index 65b7ffdd3..db380b2ec 100644 --- a/Riot/AppDelegate.m +++ b/Riot/AppDelegate.m @@ -2324,6 +2324,7 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN [homeViewController stopActivityIndicator]; roomPreviewData = [[RoomPreviewData alloc] initWithRoomId:roomIdOrAlias emailInvitationParams:queryParams andSession:account.mxSession]; + roomPreviewData.viaServers = queryParams[@"via"]; [self showRoomPreview:roomPreviewData]; } else @@ -2522,8 +2523,22 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN { value = [value stringByReplacingOccurrencesOfString:@"+" withString:@" "]; value = [value stringByRemovingPercentEncoding]; - - queryParams[key] = value; + + if ([key isEqualToString:@"via"]) + { + // Special case the via parameter + // As we can have several of them, store each value into an array + if (!queryParams[key]) + { + queryParams[key] = [NSMutableArray array]; + } + + [queryParams[key] addObject:value]; + } + else + { + queryParams[key] = value; + } } } } @@ -2555,8 +2570,8 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN // Get modular widget events in rooms histories [[MXKAppSettings standardAppSettings] addSupportedEventTypes:@[kWidgetMatrixEventTypeString, kWidgetModularEventTypeString]]; - // Disable long press on event in bubble cells - [MXKRoomBubbleTableViewCell disableLongPressGestureOnEvent:YES]; + // Enable long press on event in bubble cells + [MXKRoomBubbleTableViewCell disableLongPressGestureOnEvent:NO]; // Set first RoomDataSource class used in Vector [MXKRoomDataSourceManager registerRoomDataSourceClass:RoomDataSource.class]; @@ -2727,7 +2742,7 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN if (isPushRegistered) { // Enable push notifications by default on new added account - account.enablePushKitNotifications = YES; + [account enablePushKitNotifications:YES success:nil failure:nil]; } else { diff --git a/Riot/Assets/Images.xcassets/People/create_direct_chat.imageset/create_direct_chat.png b/Riot/Assets/Images.xcassets/People/create_direct_chat.imageset/create_direct_chat.png index 388b3a1e8..de289fc08 100644 Binary files a/Riot/Assets/Images.xcassets/People/create_direct_chat.imageset/create_direct_chat.png and b/Riot/Assets/Images.xcassets/People/create_direct_chat.imageset/create_direct_chat.png differ diff --git a/Riot/Assets/Images.xcassets/People/create_direct_chat.imageset/create_direct_chat@2x.png b/Riot/Assets/Images.xcassets/People/create_direct_chat.imageset/create_direct_chat@2x.png index 7bade5e8e..d9b709ae8 100644 Binary files a/Riot/Assets/Images.xcassets/People/create_direct_chat.imageset/create_direct_chat@2x.png and b/Riot/Assets/Images.xcassets/People/create_direct_chat.imageset/create_direct_chat@2x.png differ diff --git a/Riot/Assets/Images.xcassets/People/create_direct_chat.imageset/create_direct_chat@3x.png b/Riot/Assets/Images.xcassets/People/create_direct_chat.imageset/create_direct_chat@3x.png index 2a466e5d0..f12d709ca 100644 Binary files a/Riot/Assets/Images.xcassets/People/create_direct_chat.imageset/create_direct_chat@3x.png and b/Riot/Assets/Images.xcassets/People/create_direct_chat.imageset/create_direct_chat@3x.png differ diff --git a/Riot/Assets/Images.xcassets/Room/ContextMenu/Contents.json b/Riot/Assets/Images.xcassets/Room/ContextMenu/Contents.json new file mode 100644 index 000000000..da4a164c9 --- /dev/null +++ b/Riot/Assets/Images.xcassets/Room/ContextMenu/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Riot/Assets/Images.xcassets/Room/ContextMenu/room_context_menu_copy.imageset/Contents.json b/Riot/Assets/Images.xcassets/Room/ContextMenu/room_context_menu_copy.imageset/Contents.json new file mode 100644 index 000000000..a5230d6fe --- /dev/null +++ b/Riot/Assets/Images.xcassets/Room/ContextMenu/room_context_menu_copy.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "room_context_menu_copy.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "room_context_menu_copy@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "room_context_menu_copy@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Riot/Assets/Images.xcassets/Room/ContextMenu/room_context_menu_copy.imageset/room_context_menu_copy.png b/Riot/Assets/Images.xcassets/Room/ContextMenu/room_context_menu_copy.imageset/room_context_menu_copy.png new file mode 100644 index 000000000..7bf0c6e82 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/ContextMenu/room_context_menu_copy.imageset/room_context_menu_copy.png differ diff --git a/Riot/Assets/Images.xcassets/Room/ContextMenu/room_context_menu_copy.imageset/room_context_menu_copy@2x.png b/Riot/Assets/Images.xcassets/Room/ContextMenu/room_context_menu_copy.imageset/room_context_menu_copy@2x.png new file mode 100644 index 000000000..853e03326 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/ContextMenu/room_context_menu_copy.imageset/room_context_menu_copy@2x.png differ diff --git a/Riot/Assets/Images.xcassets/Room/ContextMenu/room_context_menu_copy.imageset/room_context_menu_copy@3x.png b/Riot/Assets/Images.xcassets/Room/ContextMenu/room_context_menu_copy.imageset/room_context_menu_copy@3x.png new file mode 100644 index 000000000..15363a0b0 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/ContextMenu/room_context_menu_copy.imageset/room_context_menu_copy@3x.png differ diff --git a/Riot/Assets/Images.xcassets/Room/ContextMenu/room_context_menu_edit.imageset/Contents.json b/Riot/Assets/Images.xcassets/Room/ContextMenu/room_context_menu_edit.imageset/Contents.json new file mode 100644 index 000000000..1290bc2c4 --- /dev/null +++ b/Riot/Assets/Images.xcassets/Room/ContextMenu/room_context_menu_edit.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "room_context_menu_edit.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "room_context_menu_edit@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "room_context_menu_edit@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Riot/Assets/Images.xcassets/Room/ContextMenu/room_context_menu_edit.imageset/room_context_menu_edit.png b/Riot/Assets/Images.xcassets/Room/ContextMenu/room_context_menu_edit.imageset/room_context_menu_edit.png new file mode 100644 index 000000000..208378c76 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/ContextMenu/room_context_menu_edit.imageset/room_context_menu_edit.png differ diff --git a/Riot/Assets/Images.xcassets/Room/ContextMenu/room_context_menu_edit.imageset/room_context_menu_edit@2x.png b/Riot/Assets/Images.xcassets/Room/ContextMenu/room_context_menu_edit.imageset/room_context_menu_edit@2x.png new file mode 100644 index 000000000..4b9f38f01 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/ContextMenu/room_context_menu_edit.imageset/room_context_menu_edit@2x.png differ diff --git a/Riot/Assets/Images.xcassets/Room/ContextMenu/room_context_menu_edit.imageset/room_context_menu_edit@3x.png b/Riot/Assets/Images.xcassets/Room/ContextMenu/room_context_menu_edit.imageset/room_context_menu_edit@3x.png new file mode 100644 index 000000000..45034e075 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/ContextMenu/room_context_menu_edit.imageset/room_context_menu_edit@3x.png differ diff --git a/Riot/Assets/Images.xcassets/Room/ContextMenu/room_context_menu_more.imageset/Contents.json b/Riot/Assets/Images.xcassets/Room/ContextMenu/room_context_menu_more.imageset/Contents.json new file mode 100644 index 000000000..aba738f89 --- /dev/null +++ b/Riot/Assets/Images.xcassets/Room/ContextMenu/room_context_menu_more.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "room_context_menu_more.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "room_context_menu_more@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "room_context_menu_more@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Riot/Assets/Images.xcassets/Room/ContextMenu/room_context_menu_more.imageset/room_context_menu_more.png b/Riot/Assets/Images.xcassets/Room/ContextMenu/room_context_menu_more.imageset/room_context_menu_more.png new file mode 100644 index 000000000..bb4a70d8a Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/ContextMenu/room_context_menu_more.imageset/room_context_menu_more.png differ diff --git a/Riot/Assets/Images.xcassets/Room/ContextMenu/room_context_menu_more.imageset/room_context_menu_more@2x.png b/Riot/Assets/Images.xcassets/Room/ContextMenu/room_context_menu_more.imageset/room_context_menu_more@2x.png new file mode 100644 index 000000000..bedd0c99c Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/ContextMenu/room_context_menu_more.imageset/room_context_menu_more@2x.png differ diff --git a/Riot/Assets/Images.xcassets/Room/ContextMenu/room_context_menu_more.imageset/room_context_menu_more@3x.png b/Riot/Assets/Images.xcassets/Room/ContextMenu/room_context_menu_more.imageset/room_context_menu_more@3x.png new file mode 100644 index 000000000..b7eb52e56 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/ContextMenu/room_context_menu_more.imageset/room_context_menu_more@3x.png differ diff --git a/Riot/Assets/Images.xcassets/Room/ContextMenu/room_context_menu_reply.imageset/Contents.json b/Riot/Assets/Images.xcassets/Room/ContextMenu/room_context_menu_reply.imageset/Contents.json new file mode 100644 index 000000000..caedf0179 --- /dev/null +++ b/Riot/Assets/Images.xcassets/Room/ContextMenu/room_context_menu_reply.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "room_context_menu_reply.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "room_context_menu_reply@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "room_context_menu_reply@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Riot/Assets/Images.xcassets/Room/ContextMenu/room_context_menu_reply.imageset/room_context_menu_reply.png b/Riot/Assets/Images.xcassets/Room/ContextMenu/room_context_menu_reply.imageset/room_context_menu_reply.png new file mode 100644 index 000000000..da8c8d608 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/ContextMenu/room_context_menu_reply.imageset/room_context_menu_reply.png differ diff --git a/Riot/Assets/Images.xcassets/Room/ContextMenu/room_context_menu_reply.imageset/room_context_menu_reply@2x.png b/Riot/Assets/Images.xcassets/Room/ContextMenu/room_context_menu_reply.imageset/room_context_menu_reply@2x.png new file mode 100644 index 000000000..3767d9b0c Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/ContextMenu/room_context_menu_reply.imageset/room_context_menu_reply@2x.png differ diff --git a/Riot/Assets/Images.xcassets/Room/ContextMenu/room_context_menu_reply.imageset/room_context_menu_reply@3x.png b/Riot/Assets/Images.xcassets/Room/ContextMenu/room_context_menu_reply.imageset/room_context_menu_reply@3x.png new file mode 100644 index 000000000..a7f162b4a Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/ContextMenu/room_context_menu_reply.imageset/room_context_menu_reply@3x.png differ diff --git a/Riot/Assets/bg.lproj/InfoPlist.strings b/Riot/Assets/bg.lproj/InfoPlist.strings index 00e7f9498..675f3d391 100644 --- a/Riot/Assets/bg.lproj/InfoPlist.strings +++ b/Riot/Assets/bg.lproj/InfoPlist.strings @@ -3,3 +3,4 @@ "NSPhotoLibraryUsageDescription" = "Галерията се използва, за да се изпращат снимки и видеа."; "NSMicrophoneUsageDescription" = "Микрофонът се използва, за да се правят видеа и да се водят разговори."; "NSContactsUsageDescription" = "За да покажем кои от контактите Ви използват Riot или Matrix, можем да изпратим имейл адресите и телефонните номера от телефонния указател към Matrix сървъра за самоличност. Компания New Vector не складира тези данни и не ги използва за никаква друга цел. За повече информация, вижте политиката за поверителност в настройките."; +"NSCalendarsUsageDescription" = "Вижте насрочените срещи в приложението."; diff --git a/Riot/Assets/bg.lproj/Localizable.strings b/Riot/Assets/bg.lproj/Localizable.strings index 49c93d17c..bd8e2cfd6 100644 --- a/Riot/Assets/bg.lproj/Localizable.strings +++ b/Riot/Assets/bg.lproj/Localizable.strings @@ -1,5 +1,5 @@ /* New message from a specific person, not referencing a room */ -"MSG_FROM_USER" = "Съобщение от %@"; +"MSG_FROM_USER" = "%@ изпрати съобщение"; /* New message from a specific person in a named room */ "MSG_FROM_USER_IN_ROOM" = "%@ публикува в %@"; /* New message from a specific person, not referencing a room. Content included. */ @@ -11,7 +11,7 @@ /* New action message from a specific person in a named room. */ "ACTION_FROM_USER_IN_ROOM" = "%@: * %@ %@"; /* New action message from a specific person, not referencing a room. */ -"IMAGE_FROM_USER" = "%@ Ви изпрати снимка %@"; +"IMAGE_FROM_USER" = "%@ изпрати снимка %@"; /* New action message from a specific person in a named room. */ "IMAGE_FROM_USER_IN_ROOM" = "%@ публикува снимка %@ в %@"; /* A single unread message in a room */ @@ -50,3 +50,7 @@ "VOICE_CONF_NAMED_FROM_USER" = "Групово повикване от %@: '%@'"; /* Incoming named video conference invite from a specific person */ "VIDEO_CONF_NAMED_FROM_USER" = "Групово видео повикване от %@: '%@'"; +/* Message title for a specific person in a named room */ +"MSG_FROM_USER_IN_ROOM_TITLE" = "%@ в %@"; +/* Sticker from a specific person, not referencing a room. */ +"STICKER_FROM_USER" = "%@ изпрати стикер"; diff --git a/Riot/Assets/bg.lproj/Vector.strings b/Riot/Assets/bg.lproj/Vector.strings index 562310eb8..aa681d10b 100644 --- a/Riot/Assets/bg.lproj/Vector.strings +++ b/Riot/Assets/bg.lproj/Vector.strings @@ -76,14 +76,14 @@ "auth_msisdn_validation_title" = "Очакване на потвърждение"; "auth_msisdn_validation_message" = "Изпратихме Ви SMS с код за активиране. Моля, въведете този код по-долу."; "auth_msisdn_validation_error" = "Неуспешно потвърждение на телефонен номер."; -"auth_recaptcha_message" = "Този Home сървър би искал да се увери, че не сте робот"; +"auth_recaptcha_message" = "Този сървър би искал да се увери, че не сте робот"; "auth_reset_password_message" = "За да възстановите Вашата парола, въведете имейл адреса, свързан с профила Ви:"; "auth_reset_password_missing_email" = "Имейл адресът, свързан с профила Ви, трябва да бъде въведен."; "auth_reset_password_missing_password" = "Трябва да бъде въведена нова парола."; "auth_reset_password_email_validation_message" = "Имейл беше изпратен на %@. След като проследите връзката, която съдържа, натиснете по-долу."; "auth_reset_password_next_step_button" = "Потвърдих имейл адреса си"; "auth_reset_password_error_unauthorized" = "Неуспешно потвърждаване на имейл адреса: уверете се, че сте кликнали върху връзката в имейла"; -"auth_reset_password_error_not_found" = "Изглежда вашият имейл адрес не може да се асоциира с Matrix ID на този Home сървър."; +"auth_reset_password_error_not_found" = "Изглежда имейл адресът Ви не принадлежи на Matrix потребител от този сървър."; "auth_reset_password_success_message" = "Вашата парола беше възстановена.\n\nВие сте излязли от профила си от всички устройства и вече няма да получавате известия. За да включите известията отново, влезте в профила си от всички устройства."; "auth_add_email_and_phone_warning" = "Регистрация с имейл и телефонен номер наведнъж не се поддържа в момента. Само телефонен номер се взима под внимание. Можете да добавите имейл към профила си в настройките."; // Chat creation @@ -562,7 +562,7 @@ "settings_key_backup_info_version" = "Версия на резервното копие на ключовете: %@"; "settings_key_backup_info_algorithm" = "Алгоритъм: %@"; "settings_key_backup_info_valid" = "Това устройство прави резервно копие на ключовете Ви."; -"settings_key_backup_info_not_valid" = "Това устройство не прави резервно копие на ключовете Ви."; +"settings_key_backup_info_not_valid" = "Това устройство не прави резервно копие на ключовете Ви, но имате съществуващо резервно копие, от което да възстановявате или допълвате в бъдеще."; "settings_key_backup_info_progress" = "Правене на резервно копие на %@ ключа..."; "settings_key_backup_info_progress_done" = "Има резервно копие на всички ключове"; "settings_key_backup_info_not_trusted_from_verifiable_device_fix_action" = "За да използвате Възстановяване на Защитени Съобщения на това устройство, потвърдете %@ сега."; @@ -624,7 +624,7 @@ "key_backup_recover_banner_title_part1" = "Стартирайте Възстановяване на Защитени Съобщения"; "key_backup_recover_banner_title_part2" = " за да четете шифрованата история на съобщенията на това устройство"; "settings_key_backup_info" = "Шифрованите съобщения са защитени с шифроване от край до край. Само Вие и получателят (получателите) имате ключове за прочитането им."; -"settings_key_backup_info_signout_warning" = "Направете копие на ключовете преди да излезете от профила, за да не ги загубите."; +"settings_key_backup_info_signout_warning" = "Свържете това устройство с резервно копие за ключове преди да излезете от профила, за да не изгубите ключовете намиращи се само на устройството."; "settings_key_backup_button_use" = "Използвай резервно копие на ключовете"; // Key backup wrong version "e2e_key_backup_wrong_version_title" = "Ново резервно копие на ключовете"; @@ -673,3 +673,118 @@ "store_full_description" = "Комуникирай по свой начин.\n\nЧат приложение, което е изцяло гъвкаво и под Ваш контрол. Riot позволява да комуникирате по начина, по който искате. Направено за [matrix] - стандарт за отворена и децентрализирана комуникация.\n\nИзползвайте безплатен matrix.org акаунт, собствен сървър от https://modular.im или друг Matrix сървър.\n\nЗащо да изберете Riot.im?\n\n• ПЪЛНА КОМУНИКАЦИЯ: Създавайте каквито пожелаете чат стаи - свързани с работни екипи, приятели или друг вид общности! Чатете, споделяйте файлове, добавяйте приспособления и водете аудио и видео разговори - всичко това, изцяло безплатно.\n\n• МОЩНИ ИНТЕГРАЦИИ: Използвайте Riot.im с досега-познатите Ви инструменти. Посредством Riot.im дори можете да чатите с потребители и групи използващи други чат приложения.\n\n• ЛИЧНО И ЗАЩИТЕНО: Пазете комуникацията си в тайна. Съвременна технология за шифроване от край до край гарантира, че Вашата комуникацията наистина остава Ваша.\n\n• ОТВОРЕНО, А НЕ ЗАТВОРЕНО: Приложение с отворен код, изградено върху Matrix. Дръжте данните си под Ваш контрол, като използвате Ваш собствен сървър или като изберете сървър, на който вярвате.\n\n• КЪДЕТО И ДА СТЕ: Поддържайте връзка където и да сте, с напълно синхронизирана чат история на всички Ваши устройства или онлайн на https://riot.im."; "auth_autodiscover_invalid_response" = "Невалиден отговор при опит за откриване на адреса на сървъра"; "room_message_unable_open_link_error_message" = "Неуспешно отваряне на връзката."; +"room_event_action_reply" = "Отговори"; +"room_event_action_edit" = "Редактирай"; +"auth_login_single_sign_on" = "Влез със SSO"; +"room_event_action_reaction_agree" = "Съгласи се с %@"; +"room_event_action_reaction_disagree" = "Противоречи на %@"; +"room_event_action_reaction_like" = "Харесай %@"; +"room_event_action_reaction_dislike" = "Не харесай %@"; +"room_action_reply" = "Отговори"; +"settings_labs_message_reaction" = "Реагирай на съобщения с емоджи"; +"settings_key_backup_button_connect" = "Свържи устройството към резервно копие на ключове"; +"key_backup_setup_intro_setup_connect_action_with_existing_backup" = "Свържи устройството към резервно копие на ключове"; +"key_backup_recover_connent_banner_subtitle" = "Свържи устройството към резервно копие на ключове"; +// MARK: - Device Verification +"device_verification_title" = "Потвърждение на устройство"; +"device_verification_security_advice" = "За максимална сигурност, препоръчваме да правите това на живо или използвайки друг защитен начин за комуникация"; +"device_verification_cancelled" = "Отсрещната страна отказа потвърждението."; +"device_verification_cancelled_by_me" = "Потвърждението беше отказано. Причина: %@"; +"device_verification_error_cannot_load_device" = "Неуспешно зареждане на информация за устройството."; +// Mark: Incoming +"device_verification_incoming_title" = "Входяща заявка за потвърждение"; +"device_verification_incoming_description_1" = "Потвърдете това устройство за да го маркирате като доверено. Доверяването на устройства на партньори Ви дава допълнително спокойствие когато използвате шифроване от-край-до-край."; +"device_verification_incoming_description_2" = "Потвърждаването на устройството ще го маркира като доверено и ще маркира Вашето като доверено при партньора."; +// MARK: Start +"device_verification_start_title" = "Потвърждение чрез сравняване на кратък текст"; +"device_verification_start_wait_partner" = "Изчакване на партньора да приеме..."; +"device_verification_start_use_legacy" = "Не се показва нищо? Засега не всички клиенти поддържат интерактивно потвърждение. Използвайте стария метод за потвърждение."; +"device_verification_start_verify_button" = "Започни потвърждение"; +"device_verification_start_use_legacy_action" = "Потвърди по стария метод"; +// MARK: Verify +"device_verification_verify_title_emoji" = "Потвърдете това устройство като се уверите че следните емоджита се повяват на екрана на партньора"; +"device_verification_verify_title_number" = "Потвърдете това устройство като се уверите че следните числа се повяват на екрана на партньора"; +"device_verification_verify_wait_partner" = "Изчакване на потвърждение от партньора..."; +// MARK: Verified +"device_verification_verified_title" = "Потвърдено!"; +"device_verification_verified_description_1" = "Успешно потвърдихте това устройство."; +"device_verification_verified_description_2" = "Защитените съобщения с този потребител са шифровани от край до край и не могат да бъдат прочетени от трети лица."; +"device_verification_verified_got_it_button" = "Разбрах"; +// MARK: Emoji +"device_verification_emoji_dog" = "Куче"; +"device_verification_emoji_cat" = "Котка"; +"device_verification_emoji_lion" = "Лъв"; +"device_verification_emoji_horse" = "Кон"; +"device_verification_emoji_unicorn" = "Еднорог"; +"device_verification_emoji_pig" = "Прасее"; +"device_verification_emoji_elephant" = "Слон"; +"device_verification_emoji_rabbit" = "Заек"; +"device_verification_emoji_panda" = "Панда"; +"device_verification_emoji_rooster" = "Петел"; +"device_verification_emoji_penguin" = "Пингвин"; +"device_verification_emoji_turtle" = "Костенурка"; +"device_verification_emoji_fish" = "Риба"; +"device_verification_emoji_octopus" = "Октопод"; +"device_verification_emoji_butterfly" = "Пеперуда"; +"device_verification_emoji_flower" = "Цвете"; +"device_verification_emoji_tree" = "Дърво"; +"device_verification_emoji_cactus" = "Кактус"; +"device_verification_emoji_mushroom" = "Гъба"; +"device_verification_emoji_globe" = "Глобус"; +"device_verification_emoji_moon" = "Луна"; +"device_verification_emoji_cloud" = "Облак"; +"device_verification_emoji_fire" = "Огън"; +"device_verification_emoji_banana" = "Банан"; +"device_verification_emoji_apple" = "Ябълка"; +"device_verification_emoji_strawberry" = "Ягода"; +"device_verification_emoji_corn" = "Царевица"; +"device_verification_emoji_pizza" = "Пица"; +"device_verification_emoji_cake" = "Торта"; +"device_verification_emoji_heart" = "Сърце"; +"device_verification_emoji_smiley" = "Усмивка"; +"device_verification_emoji_robot" = "Робот"; +"device_verification_emoji_hat" = "Шапка"; +"device_verification_emoji_glasses" = "Очила"; +"device_verification_emoji_spanner" = "Гаечен ключ"; +"device_verification_emoji_santa" = "Дядо Коледа"; +"device_verification_emoji_thumbs up" = "Палец нагоре"; +"device_verification_emoji_umbrella" = "Чадър"; +"device_verification_emoji_hourglass" = "Пясъчен часовник"; +"device_verification_emoji_clock" = "Часовник"; +"device_verification_emoji_gift" = "Подарък"; +"device_verification_emoji_light bulb" = "Лампа"; +"device_verification_emoji_book" = "Книга"; +"device_verification_emoji_pencil" = "Молив"; +"device_verification_emoji_paperclip" = "Кламер"; +"device_verification_emoji_scissors" = "Ножици"; +"device_verification_emoji_padlock" = "Катинар"; +"device_verification_emoji_key" = "Ключ"; +"device_verification_emoji_hammer" = "Чук"; +"device_verification_emoji_telephone" = "Телефон"; +"device_verification_emoji_flag" = "Знаме"; +"device_verification_emoji_train" = "Влак"; +"device_verification_emoji_bicycle" = "Колело"; +"device_verification_emoji_aeroplane" = "Самолет"; +"device_verification_emoji_rocket" = "Ракета"; +"device_verification_emoji_trophy" = "Трофей"; +"device_verification_emoji_ball" = "Топка"; +"device_verification_emoji_guitar" = "Китара"; +"device_verification_emoji_trumpet" = "Тромпет"; +"device_verification_emoji_bell" = "Звънец"; +"device_verification_emoji_anchor" = "Котва"; +"device_verification_emoji_headphones" = "Слушалки"; +"device_verification_emoji_folder" = "Папка"; +"device_verification_emoji_pin" = "Карфица"; +"event_formatter_message_edited_mention" = "(редактирано)"; +// Widget +"widget_no_integrations_server_configured" = "Не е конфигуриран сървър за интеграции"; +"widget_integrations_server_failed_to_connect" = "Неуспешна връзка със сървъра за интеграции"; +"device_verification_emoji_lock" = "Катинар"; +"close" = "Затвори"; +"room_event_action_reaction_show_all" = "Покажи всички"; +"room_event_action_reaction_show_less" = "Покажи по-малко"; +"room_action_send_file" = "Изпрати файл"; +"room_message_edits_history_title" = "Редакции на съобщението"; +// MARK: File upload +"file_upload_error_title" = "Качване на файл"; +"file_upload_error_unsupported_file_type_message" = "Типът файл не се поддържа."; diff --git a/Riot/Assets/ca.lproj/Vector.strings b/Riot/Assets/ca.lproj/Vector.strings index 9bb795aac..5d99aa97c 100644 --- a/Riot/Assets/ca.lproj/Vector.strings +++ b/Riot/Assets/ca.lproj/Vector.strings @@ -216,7 +216,7 @@ "room_delete_unsent_messages" = "Esborrar missatges no enviats"; "room_event_action_copy" = "Copiar"; "room_event_action_quote" = "Cita"; -"room_event_action_redact" = "Redactar"; +"room_event_action_redact" = "Eliminar"; "room_event_action_more" = "Més"; "room_event_action_share" = "Comparteix"; "room_event_action_permalink" = "Permalink"; @@ -250,7 +250,7 @@ // Room Preview "room_preview_invitation_format" = "L'usuari %@ t'ha convidat a unir-te a aquesta sala"; "room_preview_subtitle" = "Aquesta es una previsualització d'aquesta sala. Les interaccions amb la sala estan deshabilitades."; -"room_preview_unlinked_email_warning" = "L'invitació s'ha enviat a %@, però no està associat amb aquest compte. Potser vols iniciar la sessió amb un compte diferent o afegir aquesta adreça de correu al teu compte actual."; +"room_preview_unlinked_email_warning" = "La invitació s'ha enviat a %@, però no està associat amb aquest compte. Potser voleu iniciar la sessió amb un compte diferent o afegir aquesta adreça de correu al vostre compte actual."; "room_preview_try_join_an_unknown_room" = "Estàs provant de accedir a %@. T'agradaria unir-te per a poder participar en el debat?"; "room_preview_try_join_an_unknown_room_default" = "una sala"; // Settings @@ -520,7 +520,7 @@ "room_replacement_information" = "Aquesta sala s'ha substituït i ja no està activa."; "room_replacement_link" = "La conversa continua aquí."; "room_predecessor_information" = "Aquesta sala és una continuació d'una altra conversa."; -"room_predecessor_link" = "Fes clic aquí per veure missatges antics."; +"room_predecessor_link" = "Premeu aquí per veure missatges més antics."; "room_resource_limit_exceeded_message_contact_1" = " Si us plau "; "room_resource_limit_exceeded_message_contact_2_link" = "Posa't en contacte amb l'administrador del servei"; "room_resource_limit_exceeded_message_contact_3" = " per a continuar usant aquest servei."; diff --git a/Riot/Assets/de.lproj/InfoPlist.strings b/Riot/Assets/de.lproj/InfoPlist.strings index 501141eff..73cdf84ef 100644 --- a/Riot/Assets/de.lproj/InfoPlist.strings +++ b/Riot/Assets/de.lproj/InfoPlist.strings @@ -3,3 +3,4 @@ "NSPhotoLibraryUsageDescription" = "Die Foto-Bibliothek wird verwendet um Fotos und Videos zu senden."; "NSMicrophoneUsageDescription" = "Das Mikrofon wird verwendet um Videos aufzunehmen sowie Gespräche zu führen."; "NSContactsUsageDescription" = "Um dir zu zeigen, welche deiner Kontakte bereits Riot oder Matrix benutzen, können wir die E-Mail-Adressen und Telefonnummern deines Adressbuches an deinen Matrix-Identitätsserver senden. New Vector speichert diese Daten nicht und nutzt sie auch für keine anderen Zwecke. Für mehr Informationen sieh dir bitte die Datenschutz-Seite in den App-Einstellungen an."; +"NSCalendarsUsageDescription" = "Sieh dir deine geplanten Meetings in der App an."; diff --git a/Riot/Assets/de.lproj/Localizable.strings b/Riot/Assets/de.lproj/Localizable.strings index 54b32d9a0..bcadaeb5f 100644 --- a/Riot/Assets/de.lproj/Localizable.strings +++ b/Riot/Assets/de.lproj/Localizable.strings @@ -11,7 +11,7 @@ /* New action message from a specific person in a named room. */ "ACTION_FROM_USER_IN_ROOM" = "%@: * %@ %@"; /* New action message from a specific person, not referencing a room. */ -"IMAGE_FROM_USER" = "%@ sendet dir ein Bild %@"; +"IMAGE_FROM_USER" = "%@ hat ein Bild gesendet %@"; /* New action message from a specific person in a named room. */ "IMAGE_FROM_USER_IN_ROOM" = "%@ sendet ein Bild %@ in %@"; /* Multiple unread messages in a room */ @@ -50,3 +50,7 @@ "SINGLE_UNREAD_IN_ROOM" = "Du hast eine Nachricht in %@ bekommen"; /* A single unread message */ "SINGLE_UNREAD" = "Du hast eine Nachricht bekommen"; +/* Message title for a specific person in a named room */ +"MSG_FROM_USER_IN_ROOM_TITLE" = "%@ in %@"; +/* Sticker from a specific person, not referencing a room. */ +"STICKER_FROM_USER" = "%@ hat einen Sticker gesendet"; diff --git a/Riot/Assets/de.lproj/Vector.strings b/Riot/Assets/de.lproj/Vector.strings index 249422b2d..c59aa01c5 100644 --- a/Riot/Assets/de.lproj/Vector.strings +++ b/Riot/Assets/de.lproj/Vector.strings @@ -276,7 +276,7 @@ "title_home" = "Start"; "next" = "Weiter"; "auth_invalid_login_param" = "Falscher Benutzername oder Passwort"; -"auth_add_email_message" = "Füge eine E-Mail-Adresse hinzu, damit dich andere Benutzer finden können, oder um das Passwort zurücksetzen zu können."; +"auth_add_email_message" = "Füge eine E-Mail-Adresse hinzu, damit dich andere Benutzer finden können und um dein Passwort zurückzusetzen."; "auth_add_phone_message" = "Füge eine Telefonnummer hinzu, damit dich andere Benutzer finden können."; "auth_add_email_phone_message" = "Füge eine E-Mail-Adresse und/oder Telefonnummer hinzu, damit dich andere Benutzer finden können. Über die E-Mail-Adresse kannst du das Passwort zurücksetzen."; "auth_add_email_and_phone_message" = "Füge eine E-Mail-Adresse und eine Telefonnummer hinzu, damit dich andere Benutzer finden können. Über die E-Mail-Adresse kannst du das Passwort zurücksetzen."; @@ -654,3 +654,85 @@ "e2e_key_backup_wrong_version_button_wasme" = "Das war ich"; "key_backup_setup_intro_manual_export_info" = "(Erweitert)"; "key_backup_setup_intro_manual_export_action" = "Manueller Schlüssel Export"; +// String for App Store +"store_short_description" = "Sicherer, dezentralisierter Chat/VoIP"; +"store_full_description" = "Kommuniziere - auf deine Art.\n\nEine Chat-App - unter deiner Kontrolle und komplett flexibel. Riot lässt dich so kommunizieren wie du willst. Gebaut für [matrix] - dem Standard für offene, dezentrale Kommunikation.\n\nHol dir ein kostenloses Konto auf matrix.org, deinen eigenen Server auf https://modular.im oder nutze einen anderen Matrix-Server.\n\nWarum Riot.im nutzen?\n\nLÜCKENLOSE KOMMUNIKATION: Baue Räume um deine Teams, deine Freunde, deine Community - ganz nach deinen Vorstellungen! Chatte, teile Dateien, füge Widgets hinzu und führe Sprach- sowie Videogespräche - alles kostenlos.\n\nMÄCHTIGE INTEGRATIONEN: Nutze Riot.im mit den Werkzeugen die du kennst und liebst. Mit Riot.im kannst du sogar mit Nutzern und Gruppen anderer Chat-Apps in Kontakt bleiben.\n\nPRIVAT UND SICHER: Halte deine Gespräche geheim. Modernste Verschlüsselung sogt dafür, dass private Kommunikation auch privat bleibt.\n\nOFFEN, NICHT VERSCHLOSSEN: Open Source - basierend auf [matrix]. Behalte die Hoheit über deine Daten indem du deinen eigenen Server betreibst - oder einen wählst, dem du vertraust.\n\nWO AUCH IMMER DU BIST: Halte den Kontakt - mit einem Nachrichtenverlauf der vollständig synchronisiert wird - auf all deinen Geräten oder Online unter https://riot.im"; +"auth_login_single_sign_on" = "Anmelden mit Single Sign-On"; +"auth_autodiscover_invalid_response" = "Ungültige Antwort beim Entdecken des Heimservers"; +"room_message_unable_open_link_error_message" = "Konnte Link nicht öffnen."; +"room_event_action_reply" = "Antworten"; +"room_event_action_edit" = "Bearbeiten"; +"room_event_action_reaction_agree" = "%@ zustimmen"; +"room_event_action_reaction_disagree" = "%@ nicht zustimmen"; +"room_event_action_reaction_like" = "%@ zustimmen"; +"room_event_action_reaction_dislike" = "%@ nicht zustimmen"; +"room_action_reply" = "Antworten"; +"settings_labs_message_reaction" = "Mit einem Emoji reagieren"; +"settings_key_backup_button_connect" = "Schlüssel dieses Geräts sichern"; +"event_formatter_message_edited_mention" = "(geändert)"; +"key_backup_setup_intro_setup_connect_action_with_existing_backup" = "Schlüssel dieses Geräts sichern"; +"key_backup_recover_connent_banner_subtitle" = "Schlüssel dieses Geräts sichern"; +// MARK: - Device Verification +"device_verification_title" = "Gerät verifizieren"; +"device_verification_cancelled" = "Die Gegenseite hat die Verifikation abgebrochen"; +"device_verification_cancelled_by_me" = "Die Verifikation wurde abgebrochen. Grund: %@"; +"device_verification_error_cannot_load_device" = "Konnte Geräteinformationen nicht laden."; +// Mark: Incoming +"device_verification_incoming_title" = "Eingehende Verifikationsanfrage"; +"device_verification_start_verify_button" = "Beginne Verifikation"; +"device_verification_verified_got_it_button" = "Verstanden"; +// MARK: Emoji +"device_verification_emoji_dog" = "Hund"; +"device_verification_emoji_cat" = "Katze"; +"device_verification_emoji_lion" = "Löwe"; +"device_verification_emoji_horse" = "Pferd"; +"device_verification_emoji_unicorn" = "Einhorn"; +"device_verification_emoji_pig" = "Schwein"; +"device_verification_emoji_elephant" = "Elefant"; +"device_verification_emoji_rabbit" = "Kaninchen"; +"device_verification_emoji_panda" = "Panda"; +"device_verification_emoji_penguin" = "Pinguin"; +"device_verification_emoji_turtle" = "Schildkröte"; +"device_verification_emoji_fish" = "Fisch"; +"device_verification_emoji_octopus" = "Oktopus"; +"device_verification_emoji_butterfly" = "Schmetterling"; +"device_verification_emoji_flower" = "Blume"; +"device_verification_emoji_tree" = "Baum"; +"device_verification_emoji_cactus" = "Kaktus"; +"device_verification_emoji_mushroom" = "Pilz"; +"device_verification_emoji_moon" = "Mond"; +"device_verification_emoji_cloud" = "Wolke"; +"device_verification_emoji_fire" = "Feuer"; +"device_verification_emoji_banana" = "Banane"; +"device_verification_emoji_apple" = "Apfel"; +"device_verification_emoji_strawberry" = "Erdbeere"; +"device_verification_emoji_corn" = "Mais"; +"device_verification_emoji_pizza" = "Pizza"; +"device_verification_emoji_cake" = "Kuchen"; +"device_verification_emoji_heart" = "Herz"; +"device_verification_emoji_robot" = "Roboter"; +"device_verification_emoji_hat" = "Hut"; +"device_verification_emoji_glasses" = "Brille"; +"device_verification_emoji_santa" = "Nikolaus"; +"device_verification_emoji_umbrella" = "Regenschirm"; +"device_verification_emoji_gift" = "Geschenk"; +"device_verification_emoji_light bulb" = "Glühbirne"; +"device_verification_emoji_book" = "Buch"; +"device_verification_emoji_paperclip" = "Büroklammer"; +"device_verification_emoji_scissors" = "Schere"; +"device_verification_emoji_padlock" = "Schloss"; +"device_verification_emoji_key" = "Schlüssel"; +"device_verification_emoji_hammer" = "Hammer"; +"device_verification_emoji_telephone" = "Telefon"; +"device_verification_emoji_flag" = "Flagge"; +"device_verification_emoji_train" = "Zug"; +"device_verification_emoji_bicycle" = "Fahrrad"; +"device_verification_emoji_aeroplane" = "Flugzeug"; +"device_verification_emoji_rocket" = "Rakete"; +"device_verification_emoji_trophy" = "Pokal"; +"device_verification_emoji_ball" = "Ball"; +"device_verification_emoji_guitar" = "Gitarre"; +"device_verification_emoji_trumpet" = "Trompete"; +"device_verification_emoji_bell" = "Glocke"; +"device_verification_emoji_anchor" = "Anker"; +"device_verification_emoji_headphones" = "Kopfhörer"; diff --git a/Riot/Assets/en.lproj/Vector.strings b/Riot/Assets/en.lproj/Vector.strings index 55e323610..f13c54309 100644 --- a/Riot/Assets/en.lproj/Vector.strings +++ b/Riot/Assets/en.lproj/Vector.strings @@ -56,6 +56,7 @@ "collapse" = "collapse"; "send_to" = "Send to %@"; "sending" = "Sending"; +"close" = "Close"; // Authentication "auth_login" = "Log in"; @@ -278,10 +279,16 @@ "room_event_action_cancel_send" = "Cancel Send"; "room_event_action_cancel_download" = "Cancel Download"; "room_event_action_view_encryption" = "Encryption Information"; +"room_event_action_reply" = "Reply"; +"room_event_action_edit" = "Edit"; +"room_event_action_reaction_show_all" = "Show all"; +"room_event_action_reaction_show_less" = "Show less"; "room_warning_about_encryption" = "End-to-end encryption is in beta and may not be reliable.\n\nYou should not yet trust it to secure data.\n\nDevices will not yet be able to decrypt history from before they joined the room.\n\nEncrypted messages will not be visible on clients that do not yet implement encryption."; "room_event_failed_to_send" = "Failed to send"; "room_action_send_photo_or_video" = "Send photo or video"; "room_action_send_sticker" = "Send sticker"; +"room_action_send_file" = "Send file"; +"room_action_reply" = "Reply"; "room_replacement_information" = "This room has been replaced and is no longer active."; "room_replacement_link" = "The conversation continues here."; "room_predecessor_information" = "This room is a continuation of another conversation."; @@ -293,6 +300,7 @@ "room_resource_usage_limit_reached_message_1_monthly_active_user" = "This homeserver has hit its Monthly Active User limit so "; "room_resource_usage_limit_reached_message_2" = "some users will not be able to log in."; "room_resource_usage_limit_reached_message_contact_3" = " to get this limit increased."; +"room_message_edits_history_title" = "Message edits"; // Unknown devices "unknown_devices_alert_title" = "Room contains unknown devices"; @@ -399,6 +407,7 @@ "settings_labs_room_members_lazy_loading" = "Lazy load rooms members"; "settings_labs_room_members_lazy_loading_error_message" = "Your homeserver does not support lazy loading of room members yet. Try later."; "settings_labs_create_conference_with_jitsi" = "Create conference calls with jitsi"; +"settings_labs_message_reaction" = "React to messages with emoji"; "settings_version" = "Version %@"; "settings_olm_version" = "Olm Version %@"; @@ -571,6 +580,7 @@ "event_formatter_jitsi_widget_removed" = "VoIP conference removed by %@"; "event_formatter_rerequest_keys_part1_link" = "Re-request encryption keys"; "event_formatter_rerequest_keys_part2" = " from your other devices."; +"event_formatter_message_edited_mention" = "(edited)"; // Others "or" = "or"; @@ -625,6 +635,8 @@ "bug_report_send" = "Send"; // Widget +"widget_no_integrations_server_configured" = "No integrations server configured"; +"widget_integrations_server_failed_to_connect" = "Failed to connect to integrations server"; "widget_no_power_to_manage" = "You need permission to manage widgets in this room"; "widget_creation_failure" = "Widget creation has failed"; "widget_sticker_picker_no_stickerpacks_alert" = "You don't currently have any stickerpacks enabled."; @@ -863,14 +875,14 @@ "device_verification_emoji_thumbs up" = "Thumbs up"; "device_verification_emoji_umbrella" = "Umbrella"; "device_verification_emoji_hourglass" = "Hourglass"; -"device_verification_emoji_clock" = "Class"; +"device_verification_emoji_clock" = "Clock"; "device_verification_emoji_gift" = "Gift"; "device_verification_emoji_light bulb" = "Light Bulb"; "device_verification_emoji_book" = "Book"; "device_verification_emoji_pencil" = "Pencil"; "device_verification_emoji_paperclip" = "Paperclip"; "device_verification_emoji_scissors" = "Scissors"; -"device_verification_emoji_padlock" = "Padlock"; +"device_verification_emoji_lock" = "Lock"; "device_verification_emoji_key" = "Key"; "device_verification_emoji_hammer" = "Hammer"; "device_verification_emoji_telephone" = "Telephone"; @@ -888,3 +900,7 @@ "device_verification_emoji_headphones" = "Headphones"; "device_verification_emoji_folder" = "Folder"; "device_verification_emoji_pin" = "Pin"; + +// MARK: File upload +"file_upload_error_title" = "File upload"; +"file_upload_error_unsupported_file_type_message" = "File type not supported."; diff --git a/Riot/Assets/eo.lproj/Vector.strings b/Riot/Assets/eo.lproj/Vector.strings index 72b237636..238f9724e 100644 --- a/Riot/Assets/eo.lproj/Vector.strings +++ b/Riot/Assets/eo.lproj/Vector.strings @@ -2,3 +2,18 @@ "encrypted_room_message_reply_to_placeholder" = "Sendi ĉifritan respondon…"; "room_message_short_placeholder" = "Sendi mesaĝon…"; "room_message_reply_to_short_placeholder" = "Sendi respondon…"; +"title_people" = "Homoj"; +"title_rooms" = "Babilejoj"; +"title_groups" = "Komunumoj"; +"room_recents_people_section" = "HOMOJ"; +"room_recents_conversations_section" = "BABILEJOJ"; +"room_recents_no_conversation" = "Neniu babilejo"; +"search_people" = "Homoj"; +"search_files" = "Dosieroj"; +"search_default_placeholder" = "Serĉi"; +// Chat +"room_jump_to_first_unread" = "Salti al unua nelegita mesaĝo"; +"group_details_people" = "Homoj"; +"group_details_rooms" = "Babilejoj"; +// MARK: - Device Verification +"device_verification_title" = "Kontroli aparaton"; diff --git a/Riot/Assets/es.lproj/Localizable.strings b/Riot/Assets/es.lproj/Localizable.strings index a931b34de..2a6c23591 100644 --- a/Riot/Assets/es.lproj/Localizable.strings +++ b/Riot/Assets/es.lproj/Localizable.strings @@ -1,5 +1,5 @@ /* New message from a specific person, not referencing a room */ -"MSG_FROM_USER" = "Mensaje de %@"; +"MSG_FROM_USER" = "%@ envió un mensaje"; /* New message from a specific person in a named room */ "MSG_FROM_USER_IN_ROOM" = "%@ publicó en %@"; /* New message from a specific person, not referencing a room. Content included. */ @@ -50,3 +50,7 @@ "VOICE_CONF_NAMED_FROM_USER" = "Llamada en grupo de %@: '%@'"; /* Incoming named video conference invite from a specific person */ "VIDEO_CONF_NAMED_FROM_USER" = "Llamada de vídeo en grupo de %@: '%@'"; +/* Message title for a specific person in a named room */ +"MSG_FROM_USER_IN_ROOM_TITLE" = "%@ en %@"; +/* Sticker from a specific person, not referencing a room. */ +"STICKER_FROM_USER" = "%@ envió una pegatina"; diff --git a/Riot/Assets/es.lproj/Vector.strings b/Riot/Assets/es.lproj/Vector.strings index c6af54045..33891e55c 100644 --- a/Riot/Assets/es.lproj/Vector.strings +++ b/Riot/Assets/es.lproj/Vector.strings @@ -551,3 +551,5 @@ "deactivate_account_informations_part5" = "Si quieres que olvidemos tus mensajes, por favor marca la casilla a continuación\n\nLa visibilidad de mensajes en Matrix es similar a la del correo electrónico. Que olvidemos tus mensajes implica que los mensajes que hayas enviado no se compartirán con ningún usuario nuevo o no registrado, pero aquellos usuarios registrados que ya tengan acceso a estos mensajes seguirán teniendo acceso a su copia."; "deactivate_account_forget_messages_information_part1" = "Por favor, olvida todos los mensajes enviados al desactivar mi cuenta ("; "deactivate_account_forget_messages_information_part3" = ": esto provocará que los usuarios futuros vean conversaciones incompletas)"; +// String for App Store +"store_short_description" = "Chat/VoIP descentralizado y seguro"; diff --git a/Riot/Assets/eu.lproj/InfoPlist.strings b/Riot/Assets/eu.lproj/InfoPlist.strings index f6610a7cc..028fbdef0 100644 --- a/Riot/Assets/eu.lproj/InfoPlist.strings +++ b/Riot/Assets/eu.lproj/InfoPlist.strings @@ -3,3 +3,4 @@ "NSPhotoLibraryUsageDescription" = "Argazkien liburutegia argazkiak eta bideoak bidaltzeko erabiltzen da."; "NSMicrophoneUsageDescription" = "Mikrofonoa bideoak atera eta deiak egiteko erabiltzen da."; "NSContactsUsageDescription" = "Zure kontaktuetatik Riot edo Matrix jada darabiltenak erakusteko, zure kontaktu liburuko e-mail helbideak eta telefono zenbakiak bidali ditzakegu identitate zerbitzarira. New Vector enpresak ez ditu datu hauek gordetzen edo beste ezertarako erabiltzen. Informazio gehiagorako ikusi pribatutasun politikari buruzko orria aplikazioaren ezarpenetan."; +"NSCalendarsUsageDescription" = "Ikusi zure programatutako batzarrak aplikazioan."; diff --git a/Riot/Assets/eu.lproj/Localizable.strings b/Riot/Assets/eu.lproj/Localizable.strings index 6786baf50..a17db7e88 100644 --- a/Riot/Assets/eu.lproj/Localizable.strings +++ b/Riot/Assets/eu.lproj/Localizable.strings @@ -1,5 +1,5 @@ /* New message from a specific person, not referencing a room */ -"MSG_FROM_USER" = "%@ erabiltzailearen mezua"; +"MSG_FROM_USER" = "%@ erabiltzaileak mezu bat bidali du"; /* New message from a specific person in a named room */ "MSG_FROM_USER_IN_ROOM" = "%@erabiltzaileak %@ gelan idatzi du"; /* New message from a specific person, not referencing a room. Content included. */ @@ -11,7 +11,7 @@ /* New action message from a specific person in a named room. */ "ACTION_FROM_USER_IN_ROOM" = "%@: * %@ %@"; /* New action message from a specific person, not referencing a room. */ -"IMAGE_FROM_USER" = "%@ erabiltzaileak irudi bat %@ bidali dizu"; +"IMAGE_FROM_USER" = "%@ erabiltzaileak irudi bat bidali du %@"; /* New action message from a specific person in a named room. */ "IMAGE_FROM_USER_IN_ROOM" = "%@ erabiltzaileak irudi bat %@ bidali du %@ gelara"; /* Multiple unread messages in a room */ @@ -50,3 +50,7 @@ "SINGLE_UNREAD_IN_ROOM" = "Mezu bat jaso duzu %@ gelan"; /* A single unread message */ "SINGLE_UNREAD" = "Mezu bat jaso duzu"; +/* Message title for a specific person in a named room */ +"MSG_FROM_USER_IN_ROOM_TITLE" = "%@ erabiltzailea %@ gelan"; +/* Sticker from a specific person, not referencing a room. */ +"STICKER_FROM_USER" = "%@ erabiltzaileak eranskailu bat bidali du"; diff --git a/Riot/Assets/eu.lproj/Vector.strings b/Riot/Assets/eu.lproj/Vector.strings index c8a433c96..a605cebfb 100644 --- a/Riot/Assets/eu.lproj/Vector.strings +++ b/Riot/Assets/eu.lproj/Vector.strings @@ -39,7 +39,7 @@ "bug_report_description" = "Azaldu akatsa. Zer egin duzu? Zer uste zenuen gertatuko zela? Zer gertatu da benetan?"; "bug_report_logs_description" = "Arazoak aztertzeari begira, bezero honen egunkariak arazte-txosten honekin batera bidaliko dira. Goiko testua besterik ez baduzu bidali nahi, desmarkatu:"; "rage_shake_prompt" = "Telefonoa amorruz astintzen zabiltzala dirudi. Akats baten berri eman nahi duzu?"; -"bug_report_prompt" = "Azken aldian aplikazioa kraskatu da. Kraskatze-txostena bidali nahi duzu?"; +"bug_report_prompt" = "Aurrekoan aplikazioa kraskatu da. Kraskatze-txostena bidali nahi duzu?"; "auth_register" = "Erregistratu"; // Authentication "auth_login" = "Hasi saioa"; @@ -52,7 +52,7 @@ "auth_password_placeholder" = "Pasahitza"; "auth_new_password_placeholder" = "Pasahitz berria"; "auth_user_name_placeholder" = "Erabiltzaile-izena"; -"auth_add_email_message" = "Gehitu e-mail helbide bat zure kontura erabiltzaileek zu aurkitzea baimentzeko, eta zuk pasahitza berrezarri ahal izateko."; +"auth_add_email_message" = "Gehitu e-mail helbide bat zure kontura erabiltzaileek zu aurkitzea baimentzeko, eta zure pasahitza berrezartzeko."; "auth_add_phone_message" = "Gehitu telefono zenbaki bat zure kontura beste erabiltzaileek zu aurkitzea ahalbidetzeko."; "auth_optional_email_placeholder" = "E-mail helbidea (aukerakoa)"; "auth_optional_phone_placeholder" = "Telefono zenbakia (aukerakoa)"; @@ -71,7 +71,7 @@ "auth_forgot_password" = "Pasahitza ahaztu duzu?"; "auth_use_server_options" = "Erabili zerbitzari pertsonalizatuaren ezarpenak (aurreratua)"; "auth_email_validation_message" = "Egiaztatu zure e-mail helbidea erregistroarekin jarraitzeko"; -"auth_recaptcha_message" = "Hasiera zerbitzari honek robot bat ez zarela egiaztatu nahi du"; +"auth_recaptcha_message" = "Hasiera-zerbitzari honek robot bat ez zarela egiaztatu nahi du"; "auth_username_in_use" = "Erabilitako erabiltzaile-izena"; "auth_reset_password_next_step_button" = "Nire e-mail helbidea baieztatu dut"; "auth_reset_password_message" = "Zure pasahitza berrezartzeko, idatzi zure kontura gehitutako e-mail helbidea:"; @@ -110,8 +110,8 @@ "room_participants_action_set_default_power_level" = "Berrezarri erabiltzaile arrunt gisa"; "room_participants_action_set_moderator" = "Bihurtu moderatzaile"; "room_participants_action_set_admin" = "Bihurtu kudeatzaile"; -"room_participants_action_ignore" = "Ezkutatu kide honen mezu guztiak"; -"room_participants_action_unignore" = "Erakutsi kide honen mezu guztiak"; +"room_participants_action_ignore" = "Ezkutatu erabiltzaile honen mezu guztiak"; +"room_participants_action_unignore" = "Erakutsi erabiltzaile honen mezu guztiak"; "room_participants_action_mention" = "Aipamena"; "contacts_address_book_matrix_users_toggle" = "Matrix erabiltzaileak besterik ez"; "encrypted_room_message_placeholder" = "Bidali zifratutako mezua…"; @@ -222,7 +222,7 @@ "auth_untrusted_id_server" = "Identitate zerbitzaria ez da fidagarria"; "auth_msisdn_validation_error" = "Ezin izan da telefono zenbakia egiaztatu."; "auth_reset_password_email_validation_message" = "E-mail bat bidali da %@ helbidera. Honek dakarren esteka jarraitu eta gero egin klik azpian."; -"auth_reset_password_error_not_found" = "Zure e-mail helbidea ez dago antza hasiera zerbitzari honetako Matrix ID batekin lotuta."; +"auth_reset_password_error_not_found" = "Zure e-mail helbidea ez dago antza hasiera-zerbitzari honetako Matrix ID batekin lotuta."; "room_creation_account" = "Kontua"; "room_creation_appearance" = "Itxura"; "room_creation_appearance_picture" = "Txateko irudia (aukerakoa)"; @@ -304,7 +304,7 @@ "room_title_members" = "%@ kide"; // Room Preview "room_preview_invitation_format" = "%@ erabiltzaileak gela honetara elkartzera gonbidatu zaitu"; -"room_preview_unlinked_email_warning" = "Zure gonbidapena bidali da %@ helbidera, hau ez dago kontu honetara lotuta.\nBeste kontu batekin hasi dezakezu saioa, edo e-mail hau kontu honetara gehitu."; +"room_preview_unlinked_email_warning" = "Zure gonbidapena %@ helbidera bidali da, hau ez dago kontu honetara lotuta.\nBeste kontu batekin hasi dezakezu saioa, edo e-mail hau zure kontura gehitu."; "room_preview_try_join_an_unknown_room" = "%@ gelan sartzen saiatzen ari zara. Elkartu nahi duzu elkarrizketan parte hartzeko?"; // Settings "settings_title" = "Ezarpenak"; @@ -312,7 +312,7 @@ "settings_config_no_build_info" = "Konpilazio daturik ez"; "settings_mark_all_as_read" = "Markatu mezu guztiak irakurrita gisa"; "settings_report_bug" = "Arazte-txostena"; -"settings_config_home_server" = "Hasiera zerbitzaria %@ da"; +"settings_config_home_server" = "Hasiera-zerbitzaria %@ da"; "settings_config_identity_server" = "Identitate zerbitzaria %@ da"; "settings_config_user_id" = "%@ gisa hasi duzu saioa"; "settings_user_settings" = "ERABILTZAILE EZARPENAK"; @@ -477,7 +477,7 @@ // Groups tab "group_invite_section" = "GONBIDAPENAK"; "group_section" = "KOMUNITATEAK"; -"settings_flair" = "Erakutsi ikurra non baimenduta"; +"settings_flair" = "Erakutsi ikurra baimenduta dagoen tokietan"; "room_details_flair_section" = "Erakutsi ikurra komunitateentzat"; "room_details_new_flair_placeholder" = "Gehitu komunitate ID berria (adib. +foo%@)"; "room_details_flair_invalid_id_prompt_title" = "Baliogabeko formatua"; @@ -571,7 +571,7 @@ "settings_key_backup_info_progress" = "%@ gakoen babes-kopia egiten..."; "settings_key_backup_info_progress_done" = "Gako guztien babes-kopia egin da"; "settings_key_backup_info_valid" = "Gailu honek zure gakoen babes-kopia egiten du."; -"settings_key_backup_info_not_valid" = "Gailu honek ez du zure gakoen babes kopia egiten."; +"settings_key_backup_info_not_valid" = "Gailu honek ez du zure gakoen babes kopia egiten, baina badago berreskuratu dezakezun babes-kopia bat jarraitu ahal izateko."; "settings_key_backup_button_create" = "Hasi 'Gakoen babes-kopia' erabiltzen"; "settings_key_backup_button_use" = "Erabili gakoen babes-kopia"; "key_backup_setup_passphrase_title" = "Babestu zure babeskopia pasaesaldi batekin"; @@ -633,7 +633,7 @@ "sign_out_key_backup_in_progress_alert_discard_key_backup_action" = "Ez ditut nire zifratutako mezuak behar"; "sign_out_key_backup_in_progress_alert_cancel_action" = "Itxaron egingo dut"; "settings_key_backup_info" = "Zifratutako mezuak muturretik muturrerako zifratzearen bidez babestuak daude. Zuk eta hartzaileak edo hartzaileek irakurri ditzakezue mezu horiek, beste inork ez."; -"settings_key_backup_info_signout_warning" = "Egin zure gakoen babes-kopia saioa amaitu aurretik galdu nahi ez badituzu."; +"settings_key_backup_info_signout_warning" = "Konektatu gailu hau gakoen babes-kopiara saioa amaitu aurretik, gailu honetan bakarrik egon daitezkeen gakoak galdu nahi ez badituzu."; "settings_key_backup_info_trust_signature_unknown" = "Babes-kopiak gailu honen sinadura du, ID: %@"; "settings_key_backup_info_trust_signature_valid" = "Babes-kopiak gailu honen baliozko sinadura bat du"; "settings_key_backup_info_trust_signature_valid_device_verified" = "Gailuak %@ gailuaren baliozko sinadura bat du"; @@ -657,3 +657,123 @@ "key_backup_setup_intro_setup_action_with_existing_backup" = "Erabili gakoen-babes-kopia"; "key_backup_setup_intro_manual_export_info" = "(Aurreratua)"; "key_backup_setup_intro_manual_export_action" = "Esportatu gakoak eskuz"; +// String for App Store +"store_short_description" = "Deszentralizatutako txat/VoIP segurua"; +"store_full_description" = "Komunikatu, zure erara.\n\nTxat aplikazio bat, zure kontrolpean eta erabat malgua. Riot-ek zuk nahi eran komunikatzea ahalbidetzen dizu. Komunikaziorako estandar ireki eta deszentralizatua den [matrix] protokolorako egina.\n\nEskuratu matrix.org kontu bat, ezarri zure zerbitzaria https://modular.im erabiliz, edo erabili beste Matrix zerbitzari bat.\n\nZertgatik aukeratu Riot.im?\n\nERABATEKO KOMUNIKAZIOA: Sortu zure taldeentzako gelak, zure lagunentzko, zure komunitatearentzat, zuk nahi eran! Txateatu, partekatu fitxategiak, gehitu trepetak eta egin ahots eta bideo-deiak, dena doan.\n\nINTEGRAZIO AHALTSUAK: Erabili Riot.im jada ezagutu eta maite dituzun tresnekin. Riot.im erabilita beste txat aplikazioak erabiltzen dituzten erabiltzaile eta taldeekin ere aritu zaitezke.\n\nPRIBATUA ETA SEGURUA: Mantendu zure elkarrizketak sekretupean. Gaur egungo muturretik muturrerako zifratzeak pribatua dena pribatu mantentzen laguntzen du.\n\nIREKIA, EZ ITXIA: Kode irekikoa, eta Matrix protokoloan eraikia. Izan zure datuen jabe zure zerbitzaria osatatauz, edo fidagarritzat duzun bat aukeratuz.\n\nZUREKIN NONAHI: Mantendu kontaktua zauden tokian zaudela gailuetan zehar erabat sinkronizatutako mezuen historialarekin, eta web bidez https://riot.im helbidean."; +"auth_login_single_sign_on" = "Hasi saioa urrats batean"; +"auth_autodiscover_invalid_response" = "Erantzun baliogabea hasiera-zerbitzarien bilaketari"; +"room_message_unable_open_link_error_message" = "Ezin izan da esteka ireki."; +"room_event_action_reply" = "Erantzun"; +"room_event_action_edit" = "Editatu"; +"room_event_action_reaction_agree" = "Ados %@"; +"room_event_action_reaction_disagree" = "Ez ados %@"; +"room_event_action_reaction_like" = "Gogokoa %@"; +"room_event_action_reaction_dislike" = "Ez gogokoa %@"; +"room_action_reply" = "Erantzun"; +"settings_key_backup_button_connect" = "Konektatu gailu hau gakoen babes-kopiara"; +"key_backup_setup_intro_setup_connect_action_with_existing_backup" = "Konektatu gailu hau gakoen babes-kopiara"; +"key_backup_recover_connent_banner_subtitle" = "Konektatu gailu hau gakoen babes-kopiara"; +// MARK: - Device Verification +"device_verification_title" = "Egiaztatu gailua"; +"device_verification_error_cannot_load_device" = "Ezin izan da gailuaren informazioa kargatu."; +// Mark: Incoming +"device_verification_incoming_title" = "Jasotako egiaztaketa eskaria"; +"device_verification_incoming_description_1" = "Egiaztatu gailu hau fidagarri gisa markatzeko. Gailuak fidagarritzat jotzeak lasaitasuna ematen dizu muturretik muturrera zifratutako mezuak erabiltzean."; +"device_verification_incoming_description_2" = "Gailu hau egiaztatzean fidagarri gisa markatuko da, eta zure gailua fidagarri gisa markatuko zaio ere zure kideari."; +// MARK: Start +"device_verification_start_title" = "Egiaztatu testu kate labur bat alderatuz"; +"device_verification_start_wait_partner" = "Kideak onartu bitartean zain..."; +"device_verification_start_use_legacy" = "Ez da ezer agertzen? Bezero guztiek ez dute onartzen egiaztaketa interaktiboa oraindik. Erabili egiaztaketa metodo zaharra."; +"device_verification_start_verify_button" = "Hasi egiaztaketa"; +"device_verification_start_use_legacy_action" = "Erabili egiaztaketa metodo zaharra"; +// MARK: Verify +"device_verification_verify_title_emoji" = "Egiaztatu gailu hau honako emojia kidearen pantailan agertu dela baieztatuz"; +"device_verification_verify_title_number" = "Egiaztatu gailu hau honako zenbaki hauek kidearen pantailan agertu direla baieztatuz"; +"device_verification_verify_wait_partner" = "Kideak baieztatzearen zain..."; +// MARK: Verified +"device_verification_verified_title" = "Egiaztatuta!"; +"device_verification_verified_description_1" = "Ongi egiaztatu duzu gailu hau."; +"device_verification_verified_description_2" = "Kide honekin partekatutako mezu seguruak muturretik muturrera zifratuta daude eta ezin ditu beste inork irakurri."; +"device_verification_verified_got_it_button" = "Ulertuta"; +// MARK: Emoji +"device_verification_emoji_dog" = "Txakurra"; +"device_verification_emoji_cat" = "Katua"; +"device_verification_emoji_lion" = "Lehoia"; +"device_verification_emoji_horse" = "Zaldia"; +"device_verification_emoji_unicorn" = "Unikornioa"; +"device_verification_emoji_pig" = "Zerria"; +"device_verification_emoji_elephant" = "Elefantea"; +"device_verification_emoji_rabbit" = "Untxia"; +"device_verification_emoji_panda" = "Panda hartza"; +"device_verification_emoji_rooster" = "Oilarra"; +"device_verification_emoji_penguin" = "Pinguinoa"; +"device_verification_emoji_turtle" = "Dortoka"; +"device_verification_emoji_fish" = "Arraina"; +"device_verification_emoji_octopus" = "Olagarroa"; +"device_verification_emoji_butterfly" = "Tximeleta"; +"device_verification_emoji_flower" = "Lorea"; +"device_verification_emoji_tree" = "Zuhaitza"; +"device_verification_emoji_cactus" = "Kaktusa"; +"device_verification_emoji_mushroom" = "Perretxikoa"; +"device_verification_emoji_globe" = "Lurra"; +"device_verification_emoji_moon" = "Ilargia"; +"device_verification_emoji_cloud" = "Hodeia"; +"device_verification_emoji_fire" = "Sua"; +"device_verification_emoji_banana" = "Banana"; +"device_verification_emoji_apple" = "Sagarra"; +"device_verification_emoji_strawberry" = "Marrubia"; +"device_verification_emoji_corn" = "Artoa"; +"device_verification_emoji_pizza" = "Pizza"; +"device_verification_emoji_cake" = "Pastela"; +"device_verification_emoji_heart" = "Bihotza"; +"device_verification_emoji_smiley" = "Irrifartxoa"; +"device_verification_emoji_robot" = "Robota"; +"device_verification_emoji_hat" = "Txanoa"; +"device_verification_emoji_glasses" = "Betaurrekoak"; +"device_verification_emoji_spanner" = "Giltza"; +"device_verification_emoji_santa" = "Santa"; +"device_verification_emoji_thumbs up" = "Ederto"; +"device_verification_emoji_umbrella" = "Aterkia"; +"settings_labs_message_reaction" = "Erreakzionatu mezuei emojiekin"; +"event_formatter_message_edited_mention" = "(editatua)"; +"device_verification_security_advice" = "Segurtasun gehiagorako, hau aurrez aurre edo bestelako komunikazio bide fidagarriak erabiliz egitea aholkatzen dizugu"; +"device_verification_cancelled" = "Beste aldeak egiaztaketa ezeztatu du."; +"device_verification_cancelled_by_me" = "Egiaztaketa ezeztatu da. Arrazoia: %@"; +"device_verification_emoji_hourglass" = "Harea-erlojua"; +"device_verification_emoji_clock" = "Klasea"; +"device_verification_emoji_gift" = "Oparia"; +"device_verification_emoji_light bulb" = "Bonbilla"; +"device_verification_emoji_book" = "Liburua"; +"device_verification_emoji_pencil" = "Arkatza"; +"device_verification_emoji_paperclip" = "Klipa"; +"device_verification_emoji_scissors" = "Artaziak"; +"device_verification_emoji_padlock" = "Giltzarrapoa"; +"device_verification_emoji_key" = "Giltza"; +"device_verification_emoji_hammer" = "Mailua"; +"device_verification_emoji_telephone" = "Telefonoa"; +"device_verification_emoji_flag" = "Bandera"; +"device_verification_emoji_train" = "Trena"; +"device_verification_emoji_bicycle" = "Bizikleta"; +"device_verification_emoji_aeroplane" = "Hegazkina"; +"device_verification_emoji_rocket" = "Kohetea"; +"device_verification_emoji_trophy" = "Saria"; +"device_verification_emoji_ball" = "Baloia"; +"device_verification_emoji_guitar" = "Gitarra"; +"device_verification_emoji_trumpet" = "Tronpeta"; +"device_verification_emoji_bell" = "Kanpaia"; +"device_verification_emoji_anchor" = "Aingura"; +"device_verification_emoji_headphones" = "Aurikularrak"; +"device_verification_emoji_folder" = "Karpeta"; +"device_verification_emoji_pin" = "Txintxeta"; +// Widget +"widget_no_integrations_server_configured" = "Ez da integrazio zerbitzaririk konfiguratu"; +"widget_integrations_server_failed_to_connect" = "Huts egin du integrazioen zerbitzarira konektatzean"; +"device_verification_emoji_lock" = "Giltzarrapoa"; +"close" = "Itxi"; +"room_event_action_reaction_show_all" = "Erakutsi denak"; +"room_event_action_reaction_show_less" = "Erakutsi gutxiago"; +"room_message_edits_history_title" = "Mezuaren edizioak"; +"room_action_send_file" = "Bidali fitxategia"; +// MARK: File upload +"file_upload_error_title" = "Fitxategi igoera"; +"file_upload_error_unsupported_file_type_message" = "Fitxategi mota ez onartua."; diff --git a/Riot/Assets/fi.lproj/Vector.strings b/Riot/Assets/fi.lproj/Vector.strings index 6ae6798fd..d3b8ffd71 100644 --- a/Riot/Assets/fi.lproj/Vector.strings +++ b/Riot/Assets/fi.lproj/Vector.strings @@ -29,3 +29,20 @@ "video" = "Video"; // Room Details "room_details_title" = "Huoneen tiedot"; +"auth_msisdn_validation_error" = "Puhelinnumeron vahvistus epäonnistui."; +"auth_reset_password_error_unauthorized" = "Sähköpostin vahvistus epäonnistui: varmistathan, että seurasit lähettämässämme viestissä olevaa linkkiä"; +// Room recents +"room_recents_directory_section" = "HUONELUETTELO"; +// Directory +"directory_cell_title" = "Selaa luetteloa"; +"directory_search_results_title" = "Selaa luettelon tuloksia"; +"directory_searching_title" = "Etsitään luettelosta…"; +"contacts_user_directory_section" = "KÄYTTÄJÄLUETTELO"; +"contacts_user_directory_offline_section" = "KÄYTTÄJÄLUETTELO (ei yhteydessä)"; +"unknown_devices_verify" = "Vahvista…"; +"room_details_access_section_directory_toggle" = "Listaa tämä huone huoneluettelossa"; +"room_details_fail_to_update_room_directory_visibility" = "Huoneen huoneluettelossa näkymisen muuttaminen epäonnistui"; +"room_details_fail_to_update_history_visibility" = "Huoneen keskusteluhistorian näkyvyyden muuttaminen epäonnistui"; +// Directory +"directory_title" = "Luettelo"; +"directory_server_picker_title" = "Valitse luettelo"; diff --git a/Riot/Assets/fr.lproj/InfoPlist.strings b/Riot/Assets/fr.lproj/InfoPlist.strings index 21e8f42c2..aa5c6d53c 100644 --- a/Riot/Assets/fr.lproj/InfoPlist.strings +++ b/Riot/Assets/fr.lproj/InfoPlist.strings @@ -3,3 +3,4 @@ "NSPhotoLibraryUsageDescription" = "La photothèque est utilisée pour envoyer des photos et des vidéos."; "NSMicrophoneUsageDescription" = "Le microphone est utilisé pour prendre des vidéos et passer des appels."; "NSContactsUsageDescription" = "Afin d’afficher qui parmis vos contacts utilise déjà Riot ou Matrix, nous pouvons envoyer les adresses e-mails et les numéros de téléphone de votre carnet d'adresse à votre Serveur d'Identité Matrix. New Vector ne stocke pas ces données et ne les utilise pas à d'autres fins. Pour plus d'informations, veuillez consulter la page de politique de confidentialité dans les paramètres de l'application."; +"NSCalendarsUsageDescription" = "Voir vos rendez-vous prévus dans l’application."; diff --git a/Riot/Assets/fr.lproj/Localizable.strings b/Riot/Assets/fr.lproj/Localizable.strings index 8b51db94b..7ad385fcb 100644 --- a/Riot/Assets/fr.lproj/Localizable.strings +++ b/Riot/Assets/fr.lproj/Localizable.strings @@ -1,5 +1,5 @@ /* New message from a specific person, not referencing a room */ -"MSG_FROM_USER" = "Message de %@"; +"MSG_FROM_USER" = "%@ a envoyé un message"; /* New message from a specific person in a named room */ "MSG_FROM_USER_IN_ROOM" = "%@ a posté dans %@"; /* New message from a specific person, not referencing a room. Content included. */ @@ -11,7 +11,7 @@ /* New action message from a specific person in a named room. */ "ACTION_FROM_USER_IN_ROOM" = "%@ : * %@ %@"; /* New action message from a specific person, not referencing a room. */ -"IMAGE_FROM_USER" = "%@ vous a envoyé une image %@"; +"IMAGE_FROM_USER" = "%@ a envoyé une image %@"; /* New action message from a specific person in a named room. */ "IMAGE_FROM_USER_IN_ROOM" = "%@ a posté une image %@ dans %@"; /* A single unread message in a room */ @@ -50,3 +50,7 @@ "VOICE_CONF_NAMED_FROM_USER" = "Téléconférence vocale de %@ : '%@'"; /* Incoming named video conference invite from a specific person */ "VIDEO_CONF_NAMED_FROM_USER" = "Téléconférence vidéo de %@ : '%@'"; +/* Message title for a specific person in a named room */ +"MSG_FROM_USER_IN_ROOM_TITLE" = "%@ dans %@"; +/* Sticker from a specific person, not referencing a room. */ +"STICKER_FROM_USER" = "%@ a envoyé un sticker"; diff --git a/Riot/Assets/fr.lproj/Vector.strings b/Riot/Assets/fr.lproj/Vector.strings index d9c0dfb36..ec1d923b5 100644 --- a/Riot/Assets/fr.lproj/Vector.strings +++ b/Riot/Assets/fr.lproj/Vector.strings @@ -54,7 +54,7 @@ "auth_invalid_email" = "L'adresse e-mail ne semble pas valide"; "auth_invalid_phone" = "Le numéro de téléphone ne semble pas valide"; "auth_missing_password" = "Mot de passe manquant"; -"auth_add_email_message" = "Ajouter une adresse e-mail au compte pour que les utilisateurs puissent vous retrouver, et pour pouvoir réinitialiser votre mot de passe."; +"auth_add_email_message" = "Ajouter une adresse e-mail au compte pour que les utilisateurs puissent vous retrouver et pour réinitialiser votre mot de passe."; "auth_add_phone_message" = "Ajouter un numéro de téléphone au compte pour que les utilisateurs puissent vous retrouver."; "auth_add_email_phone_message" = "Ajouter une adresse e-mail et/ou un numéro de téléphone à votre compte pour que les utilisateurs puissent vous retrouver. L'adresse e-mail vous permettra également de réinitialiser votre mot de passe."; "auth_add_email_and_phone_message" = "Ajouter une adresse e-mail et un numéro de téléphone à votre compte pour que les utilisateurs puissent vous retrouver. L'adresse e-mail vous permettra également de réinitialiser votre mot de passe."; @@ -69,14 +69,14 @@ "auth_msisdn_validation_title" = "Vérification en attente"; "auth_msisdn_validation_message" = "Nous vous avons envoyé un SMS avec un code d'activation. Merci de le recopier ci-dessous."; "auth_msisdn_validation_error" = "Impossible de vérifier votre numéro de téléphone."; -"auth_recaptcha_message" = "Ce serveur d'accueil voudrait s'assurer que vous n'êtes pas un robot"; +"auth_recaptcha_message" = "Ce serveur d’accueil voudrait s’assurer que vous n’êtes pas un robot"; "auth_reset_password_message" = "Pour réinitialiser votre mot de passe, saisissez l'adresse e-mail liée à votre compte :"; "auth_reset_password_missing_email" = "Vous devez saisir l'adresse e-mail liée à votre compte."; "auth_reset_password_missing_password" = "Vous devez spécifier un nouveau mot de passe."; "auth_reset_password_email_validation_message" = "Un e-mail a été envoyé à %@. Cliquez d'abord sur le lien dans l'e-mail, puis ci-dessous."; "auth_reset_password_next_step_button" = "J'ai vérifié mon adresse e-mail"; "auth_reset_password_error_unauthorized" = "Impossible de vérifier l'adresse e-mail : assurez-vous de cliquer sur le lien dans l'e-mail"; -"auth_reset_password_error_not_found" = "Votre adresse e-mail ne semble pas associée à un identifiant Matrix sur ce serveur d'accueil."; +"auth_reset_password_error_not_found" = "Votre adresse e-mail ne semble pas associée à un identifiant Matrix sur ce serveur d’accueil."; "auth_reset_password_success_message" = "Votre mot de passe a été réinitialisé.\n\nVous avez été déconnecté de tous vos appareils et ne recevrez plus les notifications. Pour réactiver les notifications, reconnectez-vous sur chaque appareil."; "auth_add_email_and_phone_warning" = "L'inscription avec un e-mail et un numéro de téléphone à la fois n'est pas supporté tant que l'API n'existe pas. Seul votre numéro de téléphone sera pris en compte. Vous pourrez ajouter l'adresse e-mail dans vos options de profil."; // Chat creation @@ -246,7 +246,7 @@ "settings_config_no_build_info" = "Aucune information sur la version"; "settings_mark_all_as_read" = "Marquer tous les messages comme lus"; "settings_report_bug" = "Signaler une erreur"; -"settings_config_home_server" = "Le serveur d'accueil est %@"; +"settings_config_home_server" = "Le serveur d’accueil est %@"; "settings_config_identity_server" = "Le serveur d'identité est %@"; "settings_config_user_id" = "Identifié en tant que %@"; "settings_user_settings" = "PRÉFÉRENCES UTILISATEUR"; @@ -563,7 +563,7 @@ "settings_key_backup_info_version" = "Version de sauvegarde de clé : %@"; "settings_key_backup_info_algorithm" = "Algorithme : %@"; "settings_key_backup_info_valid" = "Cet appareil sauvegarde vos clés."; -"settings_key_backup_info_not_valid" = "Cet appareil ne sauvegarde pas vos clés."; +"settings_key_backup_info_not_valid" = "Cet appareil ne sauvegarde pas vos clés, mais vous avez une sauvegarde que vous pouvez restaurer et joindre."; "settings_key_backup_info_progress" = "Sauvegarde de %@ clés…"; "settings_key_backup_info_progress_done" = "Toutes les clés ont été sauvegardées"; "settings_key_backup_info_not_trusted_from_verifiable_device_fix_action" = "Pour utiliser la récupération de messages sécurisée sur cet appareil, vérifiez %@ maintenant."; @@ -625,7 +625,7 @@ "key_backup_recover_banner_title_part1" = "Lancer la récupération de messages sécurisée"; "key_backup_recover_banner_title_part2" = " pour lire l'historique des messages chiffrés sur cet appareil"; "settings_key_backup_info" = "Les messages chiffrés sont sécurisés avec un chiffrement de bout en bout. Seuls vous et le(s) destinataire(s) avez les clés pour lire ces messages."; -"settings_key_backup_info_signout_warning" = "Sauvegardez vos clés avant de vous déconnecter pour éviter de les perdre."; +"settings_key_backup_info_signout_warning" = "Connectez cet appareil à la sauvegarde de clés avant de vous déconnecter pour éviter de perdre les clés qui ne seraient que sur cet appareil."; "settings_key_backup_button_use" = "Utiliser la sauvegarde de clés"; "key_backup_setup_intro_setup_action_without_existing_backup" = "Commencer à utiliser la sauvegarde de clés"; "key_backup_setup_intro_setup_action_with_existing_backup" = "Utiliser la sauvegarde de clés"; @@ -675,3 +675,117 @@ "auth_login_single_sign_on" = "Se connecter avec l'authentification unique"; "room_message_unable_open_link_error_message" = "Impossible d’ouvrir le lien."; "auth_autodiscover_invalid_response" = "Réponse de découverte du serveur d’accueil non valide"; +"room_event_action_reply" = "Répondre"; +"room_event_action_edit" = "Éditer"; +"room_event_action_reaction_agree" = "D’accord %@"; +"room_event_action_reaction_disagree" = "Pas d’accord %@"; +"room_event_action_reaction_like" = "J’aime %@"; +"room_event_action_reaction_dislike" = "J’aime pas %@"; +"room_action_reply" = "Répondre"; +"settings_labs_message_reaction" = "Réagir aux messages avec des émojis"; +"settings_key_backup_button_connect" = "Connecter cet appareil à la sauvegarde de clés"; +"key_backup_setup_intro_setup_connect_action_with_existing_backup" = "Connecter cet appareil à la sauvegarde de clés"; +"key_backup_recover_connent_banner_subtitle" = "Connecter cet appareil à la sauvegarde de clés"; +// MARK: - Device Verification +"device_verification_title" = "Vérifier l’appareil"; +"device_verification_security_advice" = "Pour une sécurité maximale, nous vous recommandons de faire cela en personne ou d’utiliser un autre moyen sûr de communication"; +"device_verification_cancelled" = "L’autre personne a annulé la vérification."; +"device_verification_cancelled_by_me" = "La vérification a été annulée. Motif : %@"; +"device_verification_error_cannot_load_device" = "Impossible de charger les informations de l’appareil."; +// Mark: Incoming +"device_verification_incoming_title" = "Demande de vérification entrante"; +"device_verification_incoming_description_1" = "Vérifier cet appareil pour que ce soit un appareil de confiance. Faire confiance aux appareils de vos partenaires vous apporte une tranquillité d’esprit quand vous utilisez des messages chiffrés de bout en bout."; +"device_verification_incoming_description_2" = "En vérifiant cet appareil, il sera marqué comme appareil de confiance, et le vôtre sera aussi marqué comme appareil de confiance pour votre partenaire."; +// MARK: Start +"device_verification_start_title" = "Vérifier en comparant une chaîne de caractères courte"; +"device_verification_start_wait_partner" = "Nous attendons que le partenaire accepte…"; +"device_verification_start_use_legacy" = "Rien n'apparaît ? Certains clients ne prennent pas encore en charge la vérification interactive. Utilisez la vérification traditionnelle."; +"device_verification_start_verify_button" = "Commencer la vérification"; +"device_verification_start_use_legacy_action" = "Utiliser la vérification traditionnelle"; +// MARK: Verify +"device_verification_verify_title_emoji" = "Vérifier cet appareil en confirmant que les émojis suivant apparaissent sur l’écran de votre partenaire"; +"device_verification_verify_title_number" = "Vérifier cet utilisateur en confirmant que les chiffres suivant apparaissent sur l’écran de votre partenaire"; +"device_verification_verify_wait_partner" = "Nous attendons la confirmation de votre partenaire…"; +// MARK: Verified +"device_verification_verified_title" = "Vérifié !"; +"device_verification_verified_description_1" = "Vous avez bien vérifié cet appareil."; +"device_verification_verified_description_2" = "Les messages sécurisés avec cet utilisateur sont chiffrés de bout en bout et ne peuvent être lus par d’autres personnes."; +"device_verification_verified_got_it_button" = "Compris"; +// MARK: Emoji +"device_verification_emoji_dog" = "Chien"; +"device_verification_emoji_cat" = "Chat"; +"device_verification_emoji_lion" = "Lion"; +"device_verification_emoji_horse" = "Cheval"; +"device_verification_emoji_unicorn" = "Licorne"; +"device_verification_emoji_pig" = "Cochon"; +"device_verification_emoji_elephant" = "Éléphant"; +"device_verification_emoji_rabbit" = "Lapin"; +"device_verification_emoji_panda" = "Panda"; +"device_verification_emoji_rooster" = "Coq"; +"device_verification_emoji_penguin" = "Manchot"; +"device_verification_emoji_turtle" = "Tortue"; +"device_verification_emoji_fish" = "Poisson"; +"device_verification_emoji_octopus" = "Pieuvre"; +"device_verification_emoji_butterfly" = "Papillon"; +"device_verification_emoji_flower" = "Fleur"; +"device_verification_emoji_tree" = "Arbre"; +"device_verification_emoji_cactus" = "Cactus"; +"device_verification_emoji_mushroom" = "Champignon"; +"device_verification_emoji_globe" = "Terre"; +"device_verification_emoji_moon" = "Lune"; +"device_verification_emoji_cloud" = "Nuage"; +"device_verification_emoji_fire" = "Feu"; +"device_verification_emoji_banana" = "Banane"; +"device_verification_emoji_apple" = "Pomme"; +"device_verification_emoji_strawberry" = "Fraise"; +"device_verification_emoji_corn" = "Maïs"; +"device_verification_emoji_pizza" = "Pizza"; +"device_verification_emoji_cake" = "Gâteau"; +"device_verification_emoji_heart" = "Cœur"; +"device_verification_emoji_smiley" = "Smiley"; +"device_verification_emoji_robot" = "Robot"; +"device_verification_emoji_hat" = "Chapeau"; +"device_verification_emoji_glasses" = "Lunettes"; +"device_verification_emoji_spanner" = "Clé plate"; +"device_verification_emoji_santa" = "Père Noël"; +"device_verification_emoji_thumbs up" = "Pouce levé"; +"device_verification_emoji_umbrella" = "Parapluie"; +"device_verification_emoji_hourglass" = "Sablier"; +"device_verification_emoji_clock" = "Horloge"; +"device_verification_emoji_gift" = "Cadeau"; +"device_verification_emoji_light bulb" = "Ampoule"; +"device_verification_emoji_book" = "Livre"; +"device_verification_emoji_pencil" = "Crayon"; +"device_verification_emoji_paperclip" = "Trombone"; +"device_verification_emoji_scissors" = "Ciseaux"; +"device_verification_emoji_padlock" = "Cadenas"; +"device_verification_emoji_key" = "Clé"; +"device_verification_emoji_hammer" = "Marteau"; +"device_verification_emoji_telephone" = "Téléphone"; +"device_verification_emoji_flag" = "Drapeau"; +"device_verification_emoji_train" = "Train"; +"device_verification_emoji_bicycle" = "Vélo"; +"device_verification_emoji_aeroplane" = "Avion"; +"device_verification_emoji_rocket" = "Fusée"; +"device_verification_emoji_trophy" = "Trophée"; +"device_verification_emoji_ball" = "Balle"; +"device_verification_emoji_guitar" = "Guitare"; +"device_verification_emoji_trumpet" = "Trompette"; +"device_verification_emoji_bell" = "Cloche"; +"device_verification_emoji_anchor" = "Ancre"; +"device_verification_emoji_headphones" = "Écouteurs"; +"device_verification_emoji_folder" = "Dossier"; +"device_verification_emoji_pin" = "Épingle"; +"event_formatter_message_edited_mention" = "(édité)"; +// Widget +"widget_no_integrations_server_configured" = "Aucun serveur d’intégrations n’est configuré"; +"widget_integrations_server_failed_to_connect" = "Échec de connexion au serveur d’intégrations"; +"device_verification_emoji_lock" = "Cadenas"; +"room_event_action_reaction_show_all" = "Tout afficher"; +"room_event_action_reaction_show_less" = "Réduire"; +"close" = "Fermer"; +"room_message_edits_history_title" = "Éditions de message"; +"room_action_send_file" = "Envoyer un fichier"; +// MARK: File upload +"file_upload_error_title" = "Envoi de fichier"; +"file_upload_error_unsupported_file_type_message" = "Type de fichier non pris en charge."; diff --git a/Riot/Assets/hu.lproj/InfoPlist.strings b/Riot/Assets/hu.lproj/InfoPlist.strings index 9413c4955..fcca04acb 100644 --- a/Riot/Assets/hu.lproj/InfoPlist.strings +++ b/Riot/Assets/hu.lproj/InfoPlist.strings @@ -3,3 +3,4 @@ "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."; "NSContactsUsageDescription" = "Ahhoz, hogy meg tudjuk mutatni melyik ismerősöd használja már a Riot-ot vagy Matrix-ot, el tudjuk küldeni az e-mail címeket és telefonszámokat a címjegyzékedből a Matrix Azonosítási Szerverének. „New Vector” nem tárolja és semmilyen más célra nem használja ezeket az információkat. További információkért olvasd el az adatkezelési oldalt az alkalmazás beállításaiban."; +"NSCalendarsUsageDescription" = "Nézd meg a találkozóidat az alkalmazásban."; diff --git a/Riot/Assets/hu.lproj/Localizable.strings b/Riot/Assets/hu.lproj/Localizable.strings index 08b69bc1a..1b800160b 100644 --- a/Riot/Assets/hu.lproj/Localizable.strings +++ b/Riot/Assets/hu.lproj/Localizable.strings @@ -1,5 +1,5 @@ /* New message from a specific person, not referencing a room */ -"MSG_FROM_USER" = "Üzenet tőle: %@"; +"MSG_FROM_USER" = "%@ üzenet elküldve"; /* New message from a specific person in a named room */ "MSG_FROM_USER_IN_ROOM" = "%@ küldte ebbe a szobába: %@"; /* New message from a specific person, not referencing a room. Content included. */ @@ -50,3 +50,7 @@ "VOICE_CONF_NAMED_FROM_USER" = "%@ csoportos hívása: '%@'"; /* Incoming named video conference invite from a specific person */ "VIDEO_CONF_NAMED_FROM_USER" = "%@ csoportos videóhívása: '%@'"; +/* Message title for a specific person in a named room */ +"MSG_FROM_USER_IN_ROOM_TITLE" = "%@ in %@ -ban/ben"; +/* Sticker from a specific person, not referencing a room. */ +"STICKER_FROM_USER" = "%@ matricát küldött"; diff --git a/Riot/Assets/hu.lproj/Vector.strings b/Riot/Assets/hu.lproj/Vector.strings index b4ea620de..668b332b2 100644 --- a/Riot/Assets/hu.lproj/Vector.strings +++ b/Riot/Assets/hu.lproj/Vector.strings @@ -568,7 +568,7 @@ "settings_key_backup_info_version" = "Kulcs mentés verzió: %@"; "settings_key_backup_info_algorithm" = "Algoritmus: %@"; "settings_key_backup_info_valid" = "Ez az eszköz elmenti a kulcsaidat."; -"settings_key_backup_info_not_valid" = "Ez az eszköz nem menti el a kulcsaidat."; +"settings_key_backup_info_not_valid" = "Ez az eszköz nem menti el a kulcsaidat, de van létező mentésed ahonnan vissza tudsz állni és továbbléphetsz."; "settings_key_backup_info_progress" = "%@ kulcsok mentése..."; "settings_key_backup_info_progress_done" = "Minden kulcs elmentve"; "settings_key_backup_info_not_trusted_from_verifiable_device_fix_action" = "A Biztonságos Üzenet Visszaállítás használatához ellenőrizd ezt: %@."; @@ -630,7 +630,7 @@ "key_backup_recover_banner_title_part1" = "Biztonságos Üzenet Visszaállítás futtatása"; "key_backup_recover_banner_title_part2" = " , hogy elolvashasd a titkosított üzeneteidet ezen az eszközön"; "settings_key_backup_info" = "Titkosított üzenetek végponttól-végpontig vannak titkosítva. Csak te és a címzettek rendelkeznek a visszafejtéshez szükséges kulcsokkal."; -"settings_key_backup_info_signout_warning" = "Mentsd el a kulcsaidat a kilépés előtt, hogy ne veszítsd el őket."; +"settings_key_backup_info_signout_warning" = "Állítsd be ezen az eszközön a Kulcs Mentést kijelentkezés előtt, hogy ne veszíts el olyan kulcsot ami csak ezen az eszközön van meg."; "settings_key_backup_button_use" = "Kulcs mentés használata"; "key_backup_setup_intro_setup_action_without_existing_backup" = "Kulcs mentés használata"; "key_backup_setup_intro_setup_action_with_existing_backup" = "Használd a Kulcs mentést"; @@ -680,3 +680,117 @@ "room_message_unable_open_link_error_message" = "A linket nem lehet megnyitni."; "store_full_description" = "Beszélgess, ahogy tetszik.\n\nA csevegő alkalmazás ami személyre szabható és az irányításod alatt marad. Riot megteremti a lehetőséget, hogy úgy beszélgess ahogy szeretnél. A [matrix] hálózathoz tervezve - ami egy nyílt és elosztott hálózat.\n\nKészíts egy ingyenes matrix.org fiókot vagy igényelj egy saját szervert a https://modular.im -től, de üzemeltethetsz is saját Matrix szervert.\n\nMiért válaszd a Riot.im-et?\n\nTELJES KOMMUNIKÁCIÓ: Nyiss szobákat a csoportod, barátaid, közösséged vagy bárkiknek akiknek szeretnél! Beszélgess, ossz meg fájlokat, adj hozzá kisalkalmazásokat és indíts hang és videóhívásokat - teljesen ingyen.\n\nERŐS KAPCSOLATOK: Használd a Riot-ot a kedvenc eszközeiddel. A Riottal még másik rendszerekben lévő emberekkel és csoportokkal is képes lehetsz beszélgetni.\n\nSZEMÉLYES ÉS BIZTONSÁGOS: Tartsd a beszélgetéseidet titokban. A végponttól-végpontig titkosítás biztosítja, hogy a személyes beszélgetések személyesek maradnak.\n\nNYÍLT ÉS NEM ZÁRT: Nyílt forrású és a Matrix-hoz készült. Az adataid maradjanak a birtokodban a saját szerver üzemeltetésével vagy válassz olyan szervert amiben megbízol.\n\nMINDENHOL AMERRE JÁRSZ: Maradj kapcsolatban a többiekkel mindenhol az eszközeid közötti teljesen szinkronizált üzenetváltásokkal. Akár a https://riot.im -en."; "auth_autodiscover_invalid_response" = "Matrix szerver felderítésénél érvénytelen válasz érkezett"; +"room_event_action_reply" = "Válasz"; +"room_event_action_edit" = "Szerkeszt"; +"room_event_action_reaction_agree" = "Egyetért %@"; +"room_event_action_reaction_disagree" = "Ellentmond %@"; +"room_event_action_reaction_like" = "Kedveli %@"; +"room_event_action_reaction_dislike" = "Nem kedveli %@"; +"room_action_reply" = "Válasz"; +"settings_labs_message_reaction" = "Emoji reakció az üzenetre"; +"settings_key_backup_button_connect" = "Eszköz csatlakoztatása a Kulcs Mentéshez"; +"key_backup_setup_intro_setup_connect_action_with_existing_backup" = "Eszköz csatlakoztatása a Kulcs Mentéshez"; +"key_backup_recover_connent_banner_subtitle" = "Eszköz csatlakoztatása a Kulcs Mentéshez"; +// MARK: - Device Verification +"device_verification_title" = "Eszköz ellenőrzése"; +"device_verification_security_advice" = "A legnagyobb biztonság érdekében javasoljuk, hogy ezt személyesen vagy egy másik biztonságos kommunikációs csatornán tedd meg"; +"device_verification_cancelled" = "A másik fél megszakította az ellenőrzést."; +"device_verification_cancelled_by_me" = "Az ellenőrzés megszakadt. Ok: %@"; +"device_verification_error_cannot_load_device" = "Az eszköz információk nem tölthetők be."; +// Mark: Incoming +"device_verification_incoming_title" = "Beérkező Ellenőrzési Kérés"; +"device_verification_incoming_description_1" = "Eszköz ellenőrzése és beállítás megbízhatónak. A partnerek eszközeiben való megbízás megnyugtató lehet, ha végponttól végpontig titkosítást használsz."; +"device_verification_incoming_description_2" = "Az eszköz ellenőrzése megbízhatónak fogja jelezni az eszközt és a partnernél a te eszközödet szintén megbízhatónak fogja jelezni."; +// MARK: Start +"device_verification_start_title" = "Rövid szöveggel ellenőriz"; +"device_verification_start_wait_partner" = "Várakozás a partner általi elfogadásra..."; +"device_verification_start_use_legacy" = "Nem jelenik meg semmi? Nem minden kliens támogatja az interaktív ellenőrzést. Használd a hagyományos ellenőrzést."; +"device_verification_start_verify_button" = "Ellenőrzés megkezdése"; +"device_verification_start_use_legacy_action" = "Használd a hagyományos ellenőrzést"; +// MARK: Verify +"device_verification_verify_title_emoji" = "Eszköz ellenőrzése az alábbi emojik a partner képernyőjén való megjelenésének megerősítésével történik"; +"device_verification_verify_title_number" = "Eszköz ellenőrzése az alábbi számok a partner képernyőjén való megjelenésének megerősítésével történik"; +"device_verification_verify_wait_partner" = "Várakozás a partner megerősítésére..."; +// MARK: Verified +"device_verification_verified_title" = "Ellenőrizve!"; +"device_verification_verified_description_1" = "Ezt az eszközt sikeresen ellenőrizted."; +"device_verification_verified_description_2" = "Az biztonságos üzentküldés ezzel a felhasználóval végponttól végpontig titkosított és harmadik fél nem tudja elolvasni."; +"device_verification_verified_got_it_button" = "Értem"; +// MARK: Emoji +"device_verification_emoji_dog" = "Kutya"; +"device_verification_emoji_cat" = "Macska"; +"device_verification_emoji_lion" = "Oroszlán"; +"device_verification_emoji_horse" = "Ló"; +"device_verification_emoji_unicorn" = "Egyszarvú"; +"device_verification_emoji_pig" = "Disznó"; +"device_verification_emoji_elephant" = "Elefánt"; +"device_verification_emoji_rabbit" = "Nyúl"; +"device_verification_emoji_panda" = "Panda"; +"device_verification_emoji_rooster" = "Kakas"; +"device_verification_emoji_penguin" = "Pingvin"; +"device_verification_emoji_turtle" = "Teknős"; +"device_verification_emoji_fish" = "Hal"; +"device_verification_emoji_octopus" = "Polip"; +"device_verification_emoji_butterfly" = "Pillangó"; +"device_verification_emoji_flower" = "Virág"; +"device_verification_emoji_tree" = "Fa"; +"device_verification_emoji_cactus" = "Kaktusz"; +"device_verification_emoji_mushroom" = "Gomba"; +"device_verification_emoji_globe" = "Földgömb"; +"device_verification_emoji_moon" = "Hold"; +"device_verification_emoji_cloud" = "Felhő"; +"device_verification_emoji_fire" = "Tűz"; +"device_verification_emoji_banana" = "Banán"; +"device_verification_emoji_apple" = "Alma"; +"device_verification_emoji_strawberry" = "Eper"; +"device_verification_emoji_corn" = "Kukorica"; +"device_verification_emoji_pizza" = "Pizza"; +"device_verification_emoji_cake" = "Süti"; +"device_verification_emoji_heart" = "Szív"; +"device_verification_emoji_smiley" = "Mosoly"; +"device_verification_emoji_robot" = "Robot"; +"device_verification_emoji_hat" = "Kalap"; +"device_verification_emoji_glasses" = "Szemüveg"; +"device_verification_emoji_spanner" = "Csavarkulcs"; +"device_verification_emoji_santa" = "Télapó"; +"device_verification_emoji_thumbs up" = "Hüvelykujj fel"; +"device_verification_emoji_umbrella" = "Esernyő"; +"device_verification_emoji_hourglass" = "Homokóra"; +"device_verification_emoji_clock" = "Osztály"; +"device_verification_emoji_gift" = "Ajándék"; +"device_verification_emoji_light bulb" = "Égő"; +"device_verification_emoji_book" = "Könyv"; +"device_verification_emoji_pencil" = "Toll"; +"device_verification_emoji_paperclip" = "Gémkapocs"; +"device_verification_emoji_scissors" = "Olló"; +"device_verification_emoji_padlock" = "Lakat"; +"device_verification_emoji_key" = "Kulcs"; +"device_verification_emoji_hammer" = "Kalapács"; +"device_verification_emoji_telephone" = "Telefon"; +"device_verification_emoji_flag" = "Zászló"; +"device_verification_emoji_train" = "Vonat"; +"device_verification_emoji_bicycle" = "Kerékpár"; +"device_verification_emoji_aeroplane" = "Repülő"; +"device_verification_emoji_rocket" = "Rakáta"; +"device_verification_emoji_trophy" = "Kupa"; +"device_verification_emoji_ball" = "Labda"; +"device_verification_emoji_guitar" = "Gitár"; +"device_verification_emoji_trumpet" = "Trombita"; +"device_verification_emoji_bell" = "Harang"; +"device_verification_emoji_anchor" = "Vasmacska"; +"device_verification_emoji_headphones" = "Fejhallgató"; +"device_verification_emoji_folder" = "Mappa"; +"device_verification_emoji_pin" = "Rajszeg"; +"event_formatter_message_edited_mention" = "(szerkesztve)"; +// Widget +"widget_no_integrations_server_configured" = "Integrációs szerver nincs beállítva"; +"widget_integrations_server_failed_to_connect" = "Az integrációs szerverhez nem lehet csatlakozni"; +"device_verification_emoji_lock" = "Zár"; +"close" = "Bezár"; +"room_event_action_reaction_show_all" = "Mindet mutat"; +"room_event_action_reaction_show_less" = "Kevesebbet mutat"; +"room_message_edits_history_title" = "Üzenet szerkesztések"; +"room_action_send_file" = "Fájl küldés"; +// MARK: File upload +"file_upload_error_title" = "Fájl feltöltés"; +"file_upload_error_unsupported_file_type_message" = "A fájl típusa nem támogatott."; diff --git a/Riot/Assets/it.lproj/InfoPlist.strings b/Riot/Assets/it.lproj/InfoPlist.strings index 62fa60059..046971b31 100644 --- a/Riot/Assets/it.lproj/InfoPlist.strings +++ b/Riot/Assets/it.lproj/InfoPlist.strings @@ -2,4 +2,4 @@ "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."; -"NSContactsUsageDescription" = "La rubrica viene utilizzata per ricercare utenti tramite email o numero di telefono su Riot."; +"NSContactsUsageDescription" = "Per mostrare i tuoi contatti che stanno già usando Riot o Matrix, possiamo inviare gli indirizzi email e i numeri di telefono della tua rubrica al tuo server identità. New Vector non memorizza e non usa per altri scopi questi dati. Per maggiori informazioni guarda l'informativa sulla privacy presente nelle impostazioni di questa applicazione."; diff --git a/Riot/Assets/it.lproj/Localizable.strings b/Riot/Assets/it.lproj/Localizable.strings index 3ca46524d..daa2aefdd 100644 --- a/Riot/Assets/it.lproj/Localizable.strings +++ b/Riot/Assets/it.lproj/Localizable.strings @@ -28,3 +28,25 @@ "MSGS_FROM_THREE_USERS" = "%@ nuovi messaggi da %@, %@ e %@"; /* Multiple unread messages from two plus people (ie. for 4+ people: 'others' replaces the third person) */ "MSGS_FROM_TWO_PLUS_USERS" = "%@ nuovi messaggi da %@, %@ e altri"; +/* Multiple messages in two rooms */ +"MSGS_IN_TWO_ROOMS" = "%@ nuovi messaggi in %@ e %@"; +/* Look, stuff's happened, alright? Just open the app. */ +"MSGS_IN_TWO_PLUS_ROOMS" = "%@ nuovi messaggi in %@, %@ e altre"; +/* A user has invited you to a chat */ +"USER_INVITE_TO_CHAT" = "%@ ti ha invitato a chattare"; +/* A user has invited you to an (unamed) group chat */ +"USER_INVITE_TO_CHAT_GROUP_CHAT" = "%@ ti ha invitato in una chat di gruppo"; +/* A user has invited you to a named room */ +"USER_INVITE_TO_NAMED_ROOM" = "%@ ti ha invitato in %@"; +/* Incoming one-to-one voice call */ +"VOICE_CALL_FROM_USER" = "Chiamata da %@"; +/* Incoming one-to-one video call */ +"VIDEO_CALL_FROM_USER" = "Video chiamata da %@"; +/* Incoming unnamed voice conference invite from a specific person */ +"VOICE_CONF_FROM_USER" = "Chiamata di gruppo da %@"; +/* Incoming unnamed video conference invite from a specific person */ +"VIDEO_CONF_FROM_USER" = "Video chiamata di gruppo da %@"; +/* Incoming named voice conference invite from a specific person */ +"VOICE_CONF_NAMED_FROM_USER" = "Chiamata di gruppo da %@: '%@'"; +/* Incoming named video conference invite from a specific person */ +"VIDEO_CONF_NAMED_FROM_USER" = "Video chiamata di gruppo da %@: '%@'"; diff --git a/Riot/Assets/it.lproj/Vector.strings b/Riot/Assets/it.lproj/Vector.strings index e23b35b4b..c5c5d48e9 100644 --- a/Riot/Assets/it.lproj/Vector.strings +++ b/Riot/Assets/it.lproj/Vector.strings @@ -3,10 +3,10 @@ "title_favourites" = "Preferiti"; "title_people" = "Persone"; "title_rooms" = "Stanze"; -"store_full_description" = "Riot.im - Comunica, a modo tuo.\n\nUn'app di chat, sotto il tuo controllo e interamente flessibile. Riot ti permette di comunicare a modo tuo. Creata per [matrix] - lo standard per le comunicazioni aperte, decentralizzate.\n\nOttieni un account matrix.org gratuito, ottieni il tuo server su https://modular.im, o usa un altro server Matrix.\n\nPerchè scegliere Riot.im?\n\n• COMUNICAZIONE COMPLETA: crea stanze per i tuoi team, i tuoi amici, la tua comunità - come preferisci! Chatta, condividi file, aggiungi widget e fai videochiamate vocali - tutto gratuito.\n\n• GRANDI INTEGRAZIONI: usa Riot.im con gli strumenti che conosci ed ami. Con Riot.im puoi addirittura chattare con utenti e gruppi su altre app di chat.\n\n• PRIVATO E SICURO: tieni segrete le tue conversazioni. Una crittografia end-to-end allo stato dell'arte assicura che le comunicazioni private restino tali.\n\n• OPEN, NON CHIUSO: open source e costruito su Matrix. Possiedi i tuoi dati ospitando il tuo server personale, o scegliendone uno di cui ti fidi.\n\n• OVUNQUE TU SIA: resta in contatto ovunque tu sia con la cronologia dei messaggi totalmente sincronizzata tra i tuoi dispositivi ed online su https://riot.im."; +"store_full_description" = "Comunica, a modo tuo.\n\nUn'app di messaggistica, sotto il tuo controllo e interamente flessibile. Riot ti permette di comunicare a modo tuo. Creata per [matrix] - lo standard per le comunicazioni aperte, decentralizzate.\n\nOttieni un account matrix.org gratuito, ottieni il tuo server su https://modular.im, o usa un altro server Matrix.\n\nPerché scegliere Riot.im?\n\n• COMUNICAZIONE COMPLETA: crea stanze per i tuoi team, i tuoi amici, la tua comunità - come preferisci! Chatta, condividi file, aggiungi widget e fai videochiamate vocali - tutto gratuito.\n\n• GRANDI INTEGRAZIONI: usa Riot.im con gli strumenti che conosci ed ami. Con Riot.im puoi addirittura chattare con utenti e gruppi su altre applicazioni di messaggistica.\n\n• PRIVATO E SICURO: tieni segrete le tue conversazioni. Una crittografia end-to-end allo stato dell'arte assicura che le comunicazioni private restino tali.\n\n• APERTO, NON CHIUSO: open source e costruito su Matrix. Possiedi i tuoi dati ospitando il tuo server personale, o scegliendone uno di cui ti fidi.\n\n• OVUNQUE TU SIA: resta in contatto ovunque tu sia con la cronologia dei messaggi totalmente sincronizzata tra i tuoi dispositivi ed online su https://riot.im."; "title_groups" = "Comunità"; "warning" = "Attenzione"; -"next" = ""; +"next" = "Prossimo"; "leave" = "Lascia"; "remove" = "Rimuovi"; "invite" = "Invita"; @@ -58,3 +58,600 @@ "auth_msisdn_validation_message" = "Un SMS è stato spedito con il codice di attivazione. Per favore inserisci il codice qui sotto."; "auth_msisdn_validation_error" = "Impossibile verificare il numero di telefono."; "auth_reset_password_message" = "Per ripristinare la password, inserisci l'indirizzo email associato al tuo account:"; +// String for App Store +"store_short_description" = "Conversazioni sicure e decentralizzate"; +// Actions +"view" = "Vedi"; +"back" = "Indietro"; +"continue" = "Continua"; +"create" = "Crea"; +"start" = "Inizia"; +"retry" = "Riprova"; +"on" = "Attivo"; +"off" = "Disattivo"; +"camera" = "Fotocamera"; +"voice" = "Voce"; +"video" = "Video"; +"active_call" = "Chiamata attiva"; +"active_call_details" = "Chiamata attiva (%@)"; +"later" = "Più avanti"; +"sending" = "Inviando"; +"auth_submit" = "Invia"; +"auth_login_single_sign_on" = "Accedi con single sign-on"; +"auth_untrusted_id_server" = "Il server identità non è affidabile"; +"auth_password_dont_match" = "Le password non corrispondono"; +"auth_username_in_use" = "Nome utente in uso"; +"auth_email_not_found" = "Invio dell'email fallito: Questo indirizzo email non è stato trovato"; +"auth_use_server_options" = "Usa opzioni server personalizzate (avanzate)"; +"auth_recaptcha_message" = "Questo server home vorrebbe assicurarsi che tu non sia un robot"; +"auth_reset_password_missing_email" = "L'indirizzo email associato al tuo account deve essere inserito."; +"auth_reset_password_missing_password" = "Una nuova password deve essere inserita."; +"auth_reset_password_email_validation_message" = "Un'email è stata inviata a %s. Appena avrai seguito il link lì contenuto, clicca qui sotto."; +"auth_reset_password_next_step_button" = "Ho verificato il mio indirizzo email"; +"auth_reset_password_error_unauthorized" = "Verifica indirizzo email fallita: assicurati di aver cliccato sul link contenuto nell'email"; +"auth_reset_password_error_not_found" = "Il tuo indirizzo email non sembra associato a nessun ID Matrix su questo server home."; +"auth_reset_password_success_message" = "La tua password è stata ripristinata.\n\n Sei stato disconnesso da tutti i dispositivi e non riceverai più alcuna notifica. Per riabilitare le notifiche, riconnettiti su ciascun dispositivo."; +"auth_add_email_and_phone_warning" = "La registrazione con email e numero di telefono in una volta sola non è ancora supportata (relative API in sviluppo). Sarà utilizzato solo il numero di telefono. Puoi aggiungere la tua email al tuo profilo dalle impostazioni."; +"auth_accept_policies" = "Per favore, rivedere e accettare le politiche di questo server home:"; +"auth_autodiscover_invalid_response" = "Risposta alla scoperta di un homeserver non valida"; +// Chat creation +"room_creation_title" = "Nuova chat"; +"room_creation_account" = "Account"; +"room_creation_appearance" = "Aspetto"; +"room_creation_appearance_name" = "Nome"; +"room_creation_appearance_picture" = "Immagine chat (opzionale)"; +"room_creation_privacy" = "Privacy"; +"room_creation_private_room" = "Questa chat è privata"; +"room_creation_public_room" = "Questa chat è pubblica"; +"room_creation_make_public" = "Rendi pubblica"; +"room_creation_make_public_prompt_title" = "Rendere pubblica questa chat?"; +"room_creation_make_public_prompt_msg" = "Sei sicuro di voler rendere pubblica questa chat? Chiunque potrà leggere i tuoi messaggi e unirsi alla chat."; +"room_creation_keep_private" = "Mantieni privata"; +"room_creation_make_private" = "Rendi privata"; +"room_creation_wait_for_creation" = "Una stanza sta già venendo creata. Per favore attendi."; +"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" = "PERSONE"; +"room_recents_conversations_section" = "STANZE"; +"room_recents_no_conversation" = "Nessuna stanza"; +"room_recents_low_priority_section" = "BASSA PRIORITÀ"; +"room_recents_server_notice_section" = "AVVISI DI SISTEMA"; +"room_recents_invites_section" = "INVITI"; +"room_recents_start_chat_with" = "Avvia chat"; +"room_recents_create_empty_room" = "Crea stanza"; +"room_recents_join_room" = "Entra nella stanza"; +"room_recents_join_room_title" = "Entra in una stanza"; +"room_recents_join_room_prompt" = "Digita ID o soprannome (alias)"; +// People tab +"people_invites_section" = "INVITI"; +"people_conversation_section" = "CONVERSAZIONI"; +"people_no_conversation" = "Nessuna conversazione"; +// Rooms tab +"room_directory_no_public_room" = "Nessuna stanza pubblica disponibile"; +// Groups tab +"group_invite_section" = "INVITI"; +"group_section" = "COMUNITÀ"; +// Search +"search_rooms" = "Stanze"; +"search_messages" = "Messaggi"; +"search_people" = "Persone"; +"search_files" = "File"; +"search_default_placeholder" = "Cerca"; +"search_people_placeholder" = "Cerca per ID utente, nome o email"; +"search_no_result" = "Nessun risultato"; +"search_in_progress" = "Ricerca…"; +// Directory +"directory_cell_title" = "Esplora elenco"; +"directory_cell_description" = "%tu stanze"; +"directory_search_results_title" = "Esplora risultati elenco"; +"directory_search_results" = "%tu risultati trovati per %@"; +"directory_search_results_more_than" = ">%tu risultati trovati per %@"; +"directory_searching_title" = "Ricerca negli elenchi…"; +"directory_search_fail" = "Fallita la ricerca di informazioni"; +// Contacts +"contacts_address_book_section" = "CONTATTI LOCALI"; +"contacts_address_book_matrix_users_toggle" = "Solo utenti Matrix"; +"contacts_address_book_no_contact" = "Nessun contatto locale"; +"contacts_address_book_permission_required" = "Permesso richiesto per accedere ai contatti"; +"contacts_address_book_permission_denied" = "Non hai permesso a Riot di accedere ai tuoi contatti"; +"contacts_user_directory_section" = "ELENCO UTENTI"; +"contacts_user_directory_offline_section" = "ELENCO UTENTI (offline)"; +// Chat participants +"room_participants_title" = "Membri"; +"room_participants_add_participant" = "Aggiungi membro"; +"room_participants_one_participant" = "1 membro"; +"room_participants_multi_participants" = "%d membri"; +"room_participants_leave_prompt_title" = "Lascia stanza"; +"room_participants_leave_prompt_msg" = "Sei sicuro di voler uscire dalla stanza?"; +"room_participants_remove_prompt_title" = "Conferma"; +"room_participants_remove_prompt_msg" = "Sei sicuro di voler rimuovere %@ da questa chat?"; +"room_participants_remove_third_party_invite_msg" = "Finché non esistono le API, la rimozione di inviti di terze parti non è possibile"; +"room_participants_invite_prompt_title" = "Conferma"; +"room_participants_invite_prompt_msg" = "Sei sicuro di voler invitare %@ in questa chat?"; +"room_participants_filter_room_members" = "Filtra i membri della stanza"; +"room_participants_invite_another_user" = "Cerca / invita per ID utente, nome o email"; +"room_participants_invite_malformed_id_title" = "Errore durante l'invito"; +"room_participants_invite_malformed_id" = "ID malformato. Dovrebbe essere un indirizzo email o un ID Matrix come '@localpart:domain'"; +"room_participants_invited_section" = "INVITATI"; +"room_participants_online" = "Online"; +"room_participants_offline" = "Offline"; +"room_participants_unknown" = "Sconosciuto"; +"room_participants_idle" = "Inattivo"; +"room_participants_now" = "adesso"; +"room_participants_ago" = "fa"; +"room_participants_action_section_admin_tools" = "Strumenti admin"; +"room_participants_action_section_direct_chats" = "Chat dirette"; +"room_participants_action_section_devices" = "Dispositivi"; +"room_participants_action_section_other" = "Altro"; +"room_participants_action_invite" = "Invita"; +"room_participants_action_leave" = "Lascia questa stanza"; +"room_participants_action_remove" = "Rimuovi da questa stanza"; +"room_participants_action_ban" = "Bandisci da questa stanza"; +"room_participants_action_unban" = "Togli il bando"; +"room_participants_action_ignore" = "Nascondi tutti i messaggi di questo utente"; +"room_participants_action_unignore" = "Mostra tutti i messaggi di questo utente"; +"room_participants_action_set_default_power_level" = "Ripristina ad utente normale"; +"room_participants_action_set_moderator" = "Rendi moderatore"; +"room_participants_action_set_admin" = "Rendi amministratore"; +"room_participants_action_start_new_chat" = "Inizia nuova chat"; +"room_participants_action_start_voice_call" = "Avvia chiamata vocale"; +"room_participants_action_start_video_call" = "Avvia videochiamata"; +"room_participants_action_mention" = "Citazione"; +// Chat +"room_jump_to_first_unread" = "Primo messaggio non letto"; +"room_new_message_notification" = "%d nuovo messaggio"; +"room_new_messages_notification" = "%d nuovi messaggi"; +"room_one_user_is_typing" = "%@ sta scrivendo…"; +"room_two_users_are_typing" = "%@ e %@ stanno scrivendo…"; +"room_many_users_are_typing" = "%@, %@ e altri stanno scrivendo…"; +"room_message_placeholder" = "Invia un messaggio (non criptato)…"; +"room_message_reply_to_placeholder" = "Invia una risposta (non criptata)…"; +"room_message_unable_open_link_error_message" = "Impossibile aprire il link."; +"room_do_not_have_permission_to_post" = "Non hai il permesso di pubblicare in questa stanza"; +"encrypted_room_message_placeholder" = "Invia un messaggio criptato…"; +"encrypted_room_message_reply_to_placeholder" = "Invia una risposta criptata…"; +"room_message_short_placeholder" = "Invia un messaggio…"; +"room_message_reply_to_short_placeholder" = "Invia una risposta…"; +"room_offline_notification" = "La connessione al server è stata persa."; +"room_unsent_messages_notification" = "Messaggi non inviati. %1$s o %2$s ora?"; +"room_unsent_messages_unknown_devices_notification" = "Messaggi non inviati a causa di dispositivi sconosciuti qui presenti. %1$s o %2$s ora?"; +"room_ongoing_conference_call" = "Chiamata di conferenza in corso. Unisciti come %@ o %@."; +"room_ongoing_conference_call_with_close" = "Chiamata di conferenza in corso. Unisciti come %@ o %@. %@."; +"room_ongoing_conference_call_close" = "Chiudi"; +"room_conference_call_no_power" = "Hai bisogno i permessi per gestire le chiamate di gruppo in questa stanza"; +"room_prompt_resend" = "Rinvia tutto"; +"room_prompt_cancel" = "annulla tutto"; +"room_resend_unsent_messages" = "Rinvia i messaggi non spediti"; +"room_delete_unsent_messages" = "Elimina messaggi non spediti"; +"room_event_action_copy" = "Copia"; +"room_event_action_quote" = "Cita"; +"room_event_action_redact" = "Rimuovi"; +"room_event_action_more" = "Altro"; +"room_event_action_share" = "Condividi"; +"room_event_action_permalink" = "Collegamento permanente"; +"room_event_action_view_source" = "Vedi sorgente"; +"room_event_action_view_decrypted_source" = "Vedi sorgente decriptato"; +"room_event_action_report" = "Segnala contenuto"; +"room_event_action_report_prompt_reason" = "Motivo per segnalare questo contenuto"; +"room_event_action_kick_prompt_reason" = "Motivo per buttare fuori questo utente"; +"room_event_action_ban_prompt_reason" = "Motivo per bandire questo utente"; +"room_event_action_report_prompt_ignore_user" = "Vuoi nascondere tutti i messaggi da questo utente?"; +"room_event_action_save" = "Salva"; +"room_event_action_resend" = "Rinvia"; +"room_event_action_delete" = "Elimina"; +"room_event_action_cancel_send" = "Annulla invio"; +"room_event_action_cancel_download" = "Annulla download"; +"room_event_action_view_encryption" = "Informazioni crittografia"; +"room_warning_about_encryption" = "La crittografia da-utente-a-utente è in fase sperimentale e potrebbe non essere affidabile.\n\nNon dovesti ancora fidarti di questo per proteggere i tuoi dati.\n\nI dispositivi non potranno decifrare la cronologia dei messaggi precedenti all'entrata nella stanza.\n\nI messaggi cifrati non saranno visibili dalle applicazioni che non hanno ancora implementato la crittografia."; +"room_event_failed_to_send" = "Invio fallito"; +"room_action_send_photo_or_video" = "Invia foto o video"; +"room_action_send_sticker" = "Invia sticker"; +"room_replacement_information" = "Questa stanza è stata sostituita e non è più attiva."; +"room_replacement_link" = "La conversazione continua qui."; +"room_predecessor_information" = "Questa stanza è la continuazione di un'altra conversazione."; +"room_predecessor_link" = "Clicca per vedere messaggi più vecchi."; +"room_resource_limit_exceeded_message_contact_1" = " Per favore "; +"room_resource_limit_exceeded_message_contact_2_link" = "contatta l'amministratore del servizio"; +"room_resource_limit_exceeded_message_contact_3" = " per continuare ad usare questo servizio."; +"room_resource_usage_limit_reached_message_1_default" = "Questo server home ha superato uno dei limiti delle risorse, pertanto "; +"room_resource_usage_limit_reached_message_1_monthly_active_user" = "Questo server home ha raggiunto il limite mensile di utenti attivi, pertanto "; +"room_resource_usage_limit_reached_message_2" = "alcuni utenti non potranno accedere."; +"room_resource_usage_limit_reached_message_contact_3" = " per aumentare questo limite."; +// Unknown devices +"unknown_devices_alert_title" = "La stanza contiene dispositivi sconosciuti"; +"unknown_devices_alert" = "Questa stanza contiene dispositivi sconosciuti che non sono stati verificati.\nCiò significa che non esiste alcuna garanzia che i dispositivi appartengano agli utenti che ne rivendicano il possesso.\nSi consiglia di passare attraverso il processo di verifica per ogni dispositivo prima di continuare, ma è possibile inviare ugualmente il messaggio senza fare la verifica se preferisci."; +"unknown_devices_send_anyway" = "Invia comunque"; +"unknown_devices_call_anyway" = "Chiama comunque"; +"unknown_devices_answer_anyway" = "Rispondi comunque"; +"unknown_devices_verify" = "Verifica…"; +"unknown_devices_title" = "Dispositivi sconosciuti"; +// Room Title +"room_title_new_room" = "Nuova stanza"; +"room_title_multiple_active_members" = "%@/%@ membri attivi"; +"room_title_one_active_member" = "%@/%@ membro attivo"; +"room_title_invite_members" = "Invita membri"; +"room_title_members" = "%@ membri"; +"room_title_one_member" = "1 membro"; +// Room Preview +"room_preview_invitation_format" = "Sei stato invitato ad unirti in questa stanza da %@"; +"room_preview_subtitle" = "Questa è l'anteprima della stanza. Le interazioni con la stanza sono state disabilitate."; +"room_preview_unlinked_email_warning" = "Questo invito è stato spedito da %@, che non è associato a questo account. È possibile che tu voglia connetterti con un altro account, o aggiungere l'email al tuo account."; +"room_preview_try_join_an_unknown_room" = "Stai provando ad accedere a %@. Desideri entrare per partecipare alla discussione?"; +"room_preview_try_join_an_unknown_room_default" = "una stanza"; +// Settings +"settings_title" = "Impostazioni"; +"account_logout_all" = "Sconnetti tutti gli account"; +"settings_config_no_build_info" = "Nessuna informazione di compilazione"; +"settings_mark_all_as_read" = "Segna tutti i messaggi come letti"; +"settings_report_bug" = "Segnala errore"; +"settings_clear_cache" = "Elimina cache"; +"settings_config_home_server" = "Il server home è %@"; +"settings_config_identity_server" = "Il server identità è %@"; +"settings_config_user_id" = "Connesso come %@"; +"settings_user_settings" = "IMPOSTAZIONI UTENTE"; +"settings_notifications_settings" = "IMPOSTAZIONI NOTIFICHE"; +"settings_calls_settings" = "CHIAMATE"; +"settings_user_interface" = "INTERFACCIA UTENTE"; +"settings_ignored_users" = "UTENTI IGNORATI"; +"settings_contacts" = "CONTATTI LOCALI"; +"settings_advanced" = "AVANZATE"; +"settings_other" = "ALTRO"; +"settings_labs" = "LABORATORIO"; +"settings_flair" = "Mostra predisposizione se permesso"; +"settings_devices" = "DISPOSITIVI"; +"settings_cryptography" = "CRITTOGRAFIA"; +"settings_key_backup" = "BACKUP CHIAVE"; +"settings_deactivate_account" = "DISATTIVA ACCOUNT"; +"settings_sign_out" = "Disconnetti"; +"settings_sign_out_confirmation" = "Sei sicuro?"; +"settings_sign_out_e2e_warn" = "Perderai le tue chiavi di crittografia da-utente-a-utente. Questo significa che non potrai più leggere i vecchi messaggi nelle stanze cifrate su questo dispositivo."; +"settings_profile_picture" = "Immagine profilo"; +"settings_display_name" = "Nome visualizzato"; +"settings_first_name" = "Nome"; +"settings_surname" = "Cognome"; +"settings_remove_prompt_title" = "Conferma"; +"settings_remove_email_prompt_msg" = "Sei sicuro di voler rimuovere l'indirizzo email %@?"; +"settings_remove_phone_prompt_msg" = "Sei sicuro di voler rimuovere il numero di telefono %@?"; +"settings_email_address" = "Email"; +"settings_email_address_placeholder" = "Inserisci il tuo indirizzo email"; +"settings_add_email_address" = "Aggiungi indirizzo email"; +"settings_phone_number" = "Telefono"; +"settings_add_phone_number" = "Aggiungi numero di telefono"; +"settings_change_password" = "Cambia password"; +"settings_night_mode" = "Modalità notte"; +"settings_fail_to_update_profile" = "Errore nell'aggiornamento del profilo"; +"settings_enable_push_notif" = "Notifiche per questo dispositivo"; +"settings_show_decrypted_content" = "Mostra contenuto decriptato"; +"settings_global_settings_info" = "Impostazioni globali di notifica 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 nel blocca schermo. Mostra le chiamate Riot nella cronologia di chiamate del dispositivo. Se iCloud è attivo, questa cronologia sarà condivisa con Apple."; +"settings_ui_language" = "Lingua"; +"settings_ui_theme" = "Tema"; +"settings_ui_theme_auto" = "Automatico"; +"settings_ui_theme_light" = "Chiaro"; +"settings_ui_theme_dark" = "Scuro"; +"settings_ui_theme_black" = "Nero"; +"settings_ui_theme_picker_title" = "Seleziona un tema"; +"settings_ui_theme_picker_message" = "\"Automatico\" usa l'impostazione \"Inverti Colori\" del tuo dispositivo"; +"settings_unignore_user" = "Mostrare tutti i messaggi da %@?"; +"settings_contacts_discover_matrix_users" = "Usa email e numeri di telefono per trovare utenti"; +"settings_contacts_phonebook_country" = "Nazione rubrica telefonica"; +"settings_labs_e2e_encryption" = "Crittografia da-utente-a-utente"; +"settings_labs_e2e_encryption_prompt_message" = "Per finire la configurazione della crittografia devi rieseguire l'accesso."; +"settings_labs_room_members_lazy_loading" = "Caricamento posticipato dei membri della stanza"; +"settings_labs_room_members_lazy_loading_error_message" = "Il tuo server home non supporta ancora il caricamento posticipato dei membri delle stanze. Prova in seguito."; +"settings_labs_create_conference_with_jitsi" = "Crea una videoconferenza con jitsi"; +"settings_version" = "Versione %@"; +"settings_olm_version" = "Versione olm %@"; +"settings_copyright" = "Copyright"; +"settings_copyright_url" = "https://riot.im/copyright"; +"settings_term_conditions" = "Termini e condizioni"; +"settings_term_conditions_url" = "https://riot.im/tac_apple"; +"settings_privacy_policy" = "Politica sulla privacy"; +"settings_privacy_policy_url" = "https://riot.im/privacy"; +"settings_third_party_notices" = "Avvisi di terze parti"; +"settings_send_crash_report" = "Invia dati di utilizzo anonimi"; +"settings_enable_rageshake" = "Agita con rabbia per segnalare un errore"; +"settings_old_password" = "vecchia password"; +"settings_new_password" = "nuova password"; +"settings_confirm_password" = "conferma password"; +"settings_fail_to_update_password" = "Fallito l'aggiornamento password"; +"settings_password_updated" = "La tua password è stata aggiornata"; +"settings_crypto_device_name" = "Nome dispositivo: "; +"settings_crypto_device_id" = "\nID Dispositivo: "; +"settings_crypto_device_key" = "\nChiave dispositivo: "; +"settings_crypto_export" = "Esporta chiavi"; +"settings_crypto_blacklist_unverified_devices" = "Cripta solo per i dispositivi verificati"; +"settings_deactivate_my_account" = "Disattiva il mio account"; +"settings_key_backup_info" = "I messaggi criptati sono protetti con la cifratura end-to-end. Solo tu e il/i destinatario/i avete le chiavi per leggere questi messaggi."; +"settings_key_backup_info_checking" = "Verifica..."; +"settings_key_backup_info_none" = "Nessun backup programmato per le chiavi da questo dispositivo."; +"settings_key_backup_info_signout_warning" = "Fai un backup sicuro delle tue chiavi prima di uscire per evitare di perderle."; +"settings_key_backup_info_version" = "Versione backup chiave: %@"; +"settings_key_backup_info_algorithm" = "Algoritmo: %@"; +"settings_key_backup_info_valid" = "Questo dispositivo sta eseguendo il backup delle tue chiavi."; +"settings_key_backup_info_not_valid" = "Questo dispositivo non sta eseguendo il backup delle tue chiavi."; +"settings_key_backup_info_progress" = "Backup di %@ chiavi…"; +"settings_key_backup_info_progress_done" = "Backup di tutte le chiavi completato"; +"settings_key_backup_info_trust_signature_unknown" = "Il backup ha una firma dal dispositivo con ID: %@"; +"settings_key_backup_info_trust_signature_valid" = "Il backup ha una firma valida da questo dispositivo"; +"settings_key_backup_info_trust_signature_valid_device_verified" = "Il backup ha una firma valida da %@"; +"settings_key_backup_info_trust_signature_valid_device_unverified" = "Il backup ha una firma da %@"; +"settings_key_backup_info_trust_signature_invalid_device_verified" = "Il backup ha una firma non valida da %@"; +"settings_key_backup_info_trust_signature_invalid_device_unverified" = "Il backup ha una firma non valida da %@"; +"settings_key_backup_button_create" = "Inizia ad usare il backup chiavi"; +"settings_key_backup_button_restore" = "Ripristina da backup"; +"settings_key_backup_button_delete" = "Elimina backup"; +"settings_key_backup_delete_confirmation_prompt_title" = "Elimina backup"; +"settings_key_backup_delete_confirmation_prompt_msg" = "Sei sicuro? Perderai i tuoi messaggi cifrati se le tue chiavi non sono salvate correttamente."; +// Room Details +"room_details_title" = "Dettagli stanza"; +"room_details_people" = "Membri"; +"room_details_files" = "File"; +"room_details_settings" = "Impostazioni"; +"room_details_photo" = "Immagine stanza"; +"room_details_room_name" = "Nome stanza"; +"room_details_topic" = "Argomento"; +"room_details_favourite_tag" = "Preferito"; +"room_details_low_priority_tag" = "Bassa priorità"; +"room_details_mute_notifs" = "Silenzia notifiche"; +"room_details_direct_chat" = "Chat diretta"; +"room_details_access_section" = "Chi può entrare in questa stanza?"; +"room_details_access_section_invited_only" = "Solo le persone che sono state invitate"; +"room_details_access_section_anyone_apart_from_guest" = "Chiunque conosca il link della stanza, eccetto gli ospiti"; +"room_details_access_section_anyone" = "Chiunque conosca il link della stanza, compresi gli ospiti"; +"room_details_access_section_no_address_warning" = "Per indirizzare ad una stanza tramite link, essa deve avere un indirizzo"; +"room_details_access_section_directory_toggle" = "Mostra questa stanza nell'elenco delle stanze"; +"room_details_history_section" = "Chi può leggere la cronologia?"; +"room_details_history_section_anyone" = "Chiunque"; +"room_details_history_section_members_only" = "Solo i membri (dal momento in cui questa opzione è stata selezionata)"; +"room_details_history_section_members_only_since_invited" = "Solo i membri (dal momento in cui vengono invitati)"; +"room_details_history_section_members_only_since_joined" = "Solo i membri (dal momento in cui entrano nella stanza)"; +"room_details_history_section_prompt_title" = "Avvertimento sulla privacy"; +"room_details_history_section_prompt_msg" = "La leggibilità della cronologia sarà modificata solo per i futuri messaggi di questa stanza. La visibilità dei messaggi esistenti rimarrà invariata."; +"room_details_addresses_section" = "Indirizzi"; +"room_details_no_local_addresses" = "Questa stanza non ha indirizzi locali"; +"room_details_new_address" = "Aggiungi nuovo indirizzo"; +"room_details_new_address_placeholder" = "Aggiungi nuovo indirizzo (es. #foo%@)"; +"room_details_addresses_invalid_address_prompt_title" = "Formato soprannome non valido"; +"room_details_addresses_invalid_address_prompt_msg" = "%@ non è un formato valido per un soprannome"; +"room_details_addresses_disable_main_address_prompt_title" = "Avviso per l'indirizzo principale"; +"room_details_addresses_disable_main_address_prompt_msg" = "Non avrai nessun indirizzo principale specificato. L'indirizzo principale per questa stanza sarà selezionato casualmente"; +"room_details_flair_section" = "Mostra insegna per le comunità"; +"room_details_new_flair_placeholder" = "Aggiungi nuovo ID della comunità (es. +foo%@)"; +"room_details_flair_invalid_id_prompt_title" = "Formato non valido"; +"room_details_flair_invalid_id_prompt_msg" = "%@ non è un identificatore valido per una comunità"; +"room_details_banned_users_section" = "Utenti banditi"; +"room_details_advanced_section" = "Avanzate"; +"room_details_advanced_room_id" = "ID stanza:"; +"room_details_advanced_enable_e2e_encryption" = "Abilita crittografia (attenzione: non può più essere disabilitata!)"; +"room_details_advanced_e2e_encryption_enabled" = "Crittografia abilitata in questa stanza"; +"room_details_advanced_e2e_encryption_disabled" = "Crittografia non abilitata in questa stanza."; +"room_details_advanced_e2e_encryption_blacklist_unverified_devices" = "Cripta solo per i dispositivi verificati"; +"room_details_fail_to_update_avatar" = "Fallita modifica dell'immagine stanza"; +"room_details_fail_to_update_room_name" = "Fallita modifica del nome stanza"; +"room_details_fail_to_update_topic" = "Fallita modifica dell'argomento"; +"room_details_fail_to_update_room_guest_access" = "Fallita modifica dell'accesso agli ospiti alla stanza"; +"room_details_fail_to_update_room_join_rule" = "Fallita modifica delle regole di accesso"; +"room_details_fail_to_update_room_directory_visibility" = "Fallita modifica della visibilità nell'elenco stanze"; +"room_details_fail_to_update_history_visibility" = "Fallita modifica della visibilità della cronologia"; +"room_details_fail_to_add_room_aliases" = "Fallita aggiunta dei nuovi indirizzi stanza"; +"room_details_fail_to_remove_room_aliases" = "Fallita rimozione degli indirizzi stanza"; +"room_details_fail_to_update_room_canonical_alias" = "Fallita modifica dell'indirizzo principale"; +"room_details_fail_to_update_room_communities" = "Fallita modifica delle comunità collegate"; +"room_details_fail_to_update_room_direct" = "Fallita modifica nel rendere diretta questa stanza"; +"room_details_fail_to_enable_encryption" = "Fallita attivazione della cifratura in questa stanza"; +"room_details_save_changes_prompt" = "Vuoi salvare le modifiche?"; +"room_details_set_main_address" = "Imposta come indirizzo principale"; +"room_details_unset_main_address" = "Non più come indirizzo principale"; +"room_details_copy_room_id" = "Copia ID stanza"; +"room_details_copy_room_address" = "Copia indirizzo stanza"; +"room_details_copy_room_url" = "Copia URL stanza"; +// Group Details +"group_details_title" = "Dettagli comunità"; +"group_details_home" = "Home"; +"group_details_people" = "Persone"; +"group_details_rooms" = "Stanze"; +// Group Home +"group_home_one_member_format" = "1 membro"; +"group_home_multi_members_format" = "%tu membri"; +"group_home_one_room_format" = "1 stanza"; +"group_home_multi_rooms_format" = "%tu stanze"; +"group_invitation_format" = "%@ ti ha invitato ad unirti a questa comunità"; +// Group participants +"group_participants_add_participant" = "Aggiungi membri"; +"group_participants_leave_prompt_title" = "Lascia stanza"; +"group_participants_leave_prompt_msg" = "Sei sicuro di voler uscire dalla stanza?"; +"group_participants_remove_prompt_title" = "Conferma"; +"group_participants_remove_prompt_msg" = "Sei sicuro di voler rimuovere %@ da questa chat?"; +"group_participants_invite_prompt_title" = "Conferma"; +"group_participants_invite_prompt_msg" = "Sei sicuro di voler invitare %@ in questa stanza?"; +"group_participants_filter_members" = "Filtra membri comunità"; +"group_participants_invite_another_user" = "Cerca / invita per ID utente o nome"; +"group_participants_invite_malformed_id_title" = "Invito fallito"; +"group_participants_invite_malformed_id" = "ID malformato. Dovrebbe essere ID Matrix come '@localpart:domain'"; +"group_participants_invited_section" = "INVITATI"; +// Group rooms +"group_rooms_filter_rooms" = "Filtra stanze comunità"; +// Read Receipts +"read_receipts_list" = "Elenco ricevute di lettura"; +"receipt_status_read" = "Letto: "; +// Media picker +"media_picker_library" = "Galleria"; +"media_picker_select" = "Seleziona"; +// Directory +"directory_title" = "Elenco"; +"directory_server_picker_title" = "Seleziona un elenco"; +"directory_server_all_rooms" = "Tutte le stanze sul server %@"; +"directory_server_all_native_rooms" = "Tutte le stanze Matrix native"; +"directory_server_type_homeserver" = "Digita un server home per elencare le sue stanze pubbliche"; +"directory_server_placeholder" = "matrix.org"; +// Events formatter +"event_formatter_member_updates" = "%tu cambi d'appartenenza"; +"event_formatter_widget_added" = "%@ widget aggiunto da %@"; +"event_formatter_widget_removed" = "%@ widget rimosso da %@"; +"event_formatter_jitsi_widget_added" = "Conferenza VoIP aggiunta da %@"; +"event_formatter_jitsi_widget_removed" = "Conferenza VoIP rimossa da %@"; +"event_formatter_rerequest_keys_part1_link" = "Richiedi di nuovo le chiavi di cifratura"; +"event_formatter_rerequest_keys_part2" = " dai tuoi altri dispositivi."; +// Others +"or" = "o"; +"you" = "Tu"; +"today" = "Oggi"; +"yesterday" = "Ieri"; +"network_offline_prompt" = "La connessione a internet sembra essere disconnessa."; +"homeserver_connection_lost" = "Non riesco a connettermi al server home."; +"public_room_section_title" = "Stanze pubbliche (in %@):"; +"bug_report_prompt" = "L'applicazione è andata in crash l'ultima volta. Desideri aprire la schermata di segnalazione del crash?"; +"rage_shake_prompt" = "Sembra che tu stia scuotendo il dispositivo con frustrazione. Desideri aprire la schermata di segnalazione errore?"; +"do_not_ask_again" = "Non chiedere più"; +"camera_access_not_granted" = "%@ non ha il permesso per usare la fotocamera, modifica le impostazioni privacy"; +"large_badge_value_k_format" = "%.1fK"; +"room_does_not_exist" = "%@ non esiste"; +// Call +"call_incoming_voice_prompt" = "Chiamata vocale in arrivo da %@"; +"call_incoming_video_prompt" = "Videochiamata in arrivo da %@"; +"call_incoming_voice" = "Chiamata in arrivo..."; +"call_incoming_video" = "Videochiamata in arrivo..."; +"call_already_displayed" = "Già presente una chiamata in corso."; +"call_jitsi_error" = "Fallito ingresso alla chiamata di conferenza."; +// No VoIP support +"no_voip_title" = "Chiamata in arrivo"; +"no_voip" = "%@ ti sta chiamando, ma %@ non supporta ancora le chiamate.\nPuoi ignorare questa notifica e rispondere alla chiamata da un altro dispositivo o puoi rifiutare la chiamata."; +// Crash report +"google_analytics_use_prompt" = "Vuoi aiutare a migliorare %@ inviando automaticamente in modo anonimo i dati di utilizzo e le segnalazioni di crash?"; +// Crypto +"e2e_enabling_on_app_update" = "Riot ora supporta la crittografia da-utente-a-utente ma devi rieseguire l'accesso per abilitarla.\n\nPuoi farlo adesso oppure dopo dalle impostazioni dell'applicazione."; +"e2e_need_log_in_again" = "È necessario eseguire nuovamente l'accesso per generare le chiavi di crittografia da-utente-a-utente per questo dispositivo ed inviare la chiave pubblica al server home.\nCiò può capitare solo una volta; ci scusiamo per l'inconveniente."; +// Key backup wrong version +"e2e_key_backup_wrong_version_title" = "Nuovo backup chiave"; +"e2e_key_backup_wrong_version" = "È stato rilevato un nuovo backup chiavi per messaggi sicuri. \n\nSe non sei stato tu, imposta una nuova passphrase dalle impostazioni."; +"e2e_key_backup_wrong_version_button_settings" = "Impostazioni"; +"e2e_key_backup_wrong_version_button_wasme" = "Sono stato io"; +// Bug report +"bug_report_title" = "Rapporto errori"; +"bug_report_description" = "Per favore descrivi l'errore. Cosa hai fatto? Cosa ti aspettavi dovesse accadere? Cosa è effettivamente successo?"; +"bug_crash_report_title" = "Segnalazione del crash"; +"bug_crash_report_description" = "Per favore descrivi cosa hai fatto prima del crash:"; +"bug_report_logs_description" = "Al fine di diagnosticare i problemi, i registri Riot di questo dispositivo saranno inviati con il rapporto dell'errore. Se preferisci inviare solo il testo soprastante, deseleziona:"; +"bug_report_send_logs" = "Invia registri"; +"bug_report_send_screenshot" = "Invia screenshot"; +"bug_report_progress_zipping" = "Ottenimento registri"; +"bug_report_progress_uploading" = "Invio rapporto"; +"bug_report_send" = "Invia"; +// Widget +"widget_no_power_to_manage" = "Hai bisogno dei permessi per gestire i widget in questa stanza"; +"widget_creation_failure" = "Creazione del widget fallita"; +"widget_sticker_picker_no_stickerpacks_alert" = "Non hai ancora alcun pacco di adesivi attivo."; +"widget_sticker_picker_no_stickerpacks_alert_add_now" = "Aggiungerne qualcuno ora?"; +// Widget Integration Manager +"widget_integration_need_to_be_able_to_invite" = "Devi poter inviare utenti per compiere questa azione."; +"widget_integration_unable_to_create" = "Impossibile creare il widget."; +"widget_integration_failed_to_send_request" = "Invio della richiesta fallito."; +"widget_integration_room_not_recognised" = "Questa stanza non è riconosciuta."; +"widget_integration_positive_power_level" = "Il livello di potere deve essere un intero positivo."; +"widget_integration_must_be_in_room" = "Non sei in questa stanza."; +"widget_integration_no_permission_in_room" = "Non hai i permessi per eseguire l'azione in questa stanza."; +"widget_integration_missing_room_id" = "room_id mancante nella richiesta."; +"widget_integration_missing_user_id" = "user_id mancante nella richiesta."; +"widget_integration_room_not_visible" = "La stanza %@ non è visibile."; +// Share extension +"share_extension_auth_prompt" = "Esegui l'accesso nell'app principale per condividere il contenuto"; +"share_extension_failed_to_encrypt" = "Invio fallito. Controlla le impostazioni di crittografia per questa stanza"; +// Room key request dialog +"e2e_room_key_request_title" = "Richiesta chiave di crittografia"; +"e2e_room_key_request_message_new_device" = "Hai aggiunto un nuovo dispositivo '%@', che sta richiedendo le chiavi crittografiche."; +"e2e_room_key_request_message" = "Il tuo dispositivo non verificato '%@' sta richiedendo le chiavi crittografiche."; +"e2e_room_key_request_start_verification" = "Inizia la verifica..."; +"e2e_room_key_request_share_without_verifying" = "Condividi senza verificare"; +"e2e_room_key_request_ignore_request" = "Ignora richiesta"; +// GDPR +"gdpr_consent_not_given_alert_message" = "Per continuare ad usare l'homeserver %@ devi leggere e accettare i termini e le condizioni di utilizzo."; +"gdpr_consent_not_given_alert_review_now_action" = "Leggi adesso"; +"deactivate_account_title" = "Disattiva account"; +"deactivate_account_informations_part1" = "Questo renderà il tuo account permanentemente inutilizzabile. Non ci potrai più accedere e nessuno potrà ri-registrare lo stesso ID utente. Il tuo account abbandonerà tutte le stanze a cui partecipa e i dettagli del tuo account saranno rimossi dal server identità. "; +"deactivate_account_informations_part2_emphasize" = "Questa azione è irreversibile."; +"deactivate_account_informations_part3" = "\n\nDisattivare il tuo account "; +"deactivate_account_informations_part4_emphasize" = "non eliminerà in modo automatico i messaggi che hai inviato. "; +"deactivate_account_informations_part5" = "Se vuoi che siano dimenticati i tuoi messaggi, seleziona la casella qua sotto\n\nLa visibilità dei messaggi in Matrix è simile alle email. \"Dimenticare i tuoi messaggi\" significa che quelli che hai inviato non verranno condivisi con alcun utente nuovo o non registrato, ma gli utenti registrati che hanno avuto accesso ai tuoi messaggi avranno ancora accesso alla loro copia."; +"deactivate_account_forget_messages_information_part1" = "Per favore dimenticate tutti i messaggi che ho inviato al momento della disattivazione del mio account ("; +"deactivate_account_forget_messages_information_part2_emphasize" = "Attenzione"; +"deactivate_account_forget_messages_information_part3" = ": gli utenti futuri vedranno un elenco incompleto di conversazioni)"; +"deactivate_account_validate_action" = "Disattiva account"; +"deactivate_account_password_alert_title" = "Disattiva account"; +"deactivate_account_password_alert_message" = "Per proseguire, inserisci la tua password"; +// Re-request confirmation dialog +"rerequest_keys_alert_title" = "Richiesta inviata"; +"rerequest_keys_alert_message" = "Avvia Riot su un altro dispositivo che possa decifrare il messaggio, in modo da inviare le chiavi a questo dispositivo."; +"key_backup_setup_title" = "Backup chiave"; +"key_backup_setup_skip_alert_title" = "Sei sicuro?"; +"key_backup_setup_skip_alert_message" = "Potresti perdere i tuoi messaggi cifrati se ti disconnetti o perdi il dispositivo."; +"key_backup_setup_skip_alert_skip_action" = "Salta"; +"key_backup_setup_intro_title" = "Non perdere mai i messaggi cifrati"; +"key_backup_setup_intro_info" = "I messaggi nelle stanze cifrate sono protetti con la cifratura end-to-end. Solo tu e il/i destinatario/i avete le chiavi per leggere questi messaggi. \n \nFai un backup sicuro delle tue chiavi per evitare di perderle."; +"key_backup_setup_intro_setup_action_without_existing_backup" = "Inizia ad usare il backup chiavi"; +"key_backup_setup_intro_manual_export_info" = "(Avanzato)"; +"key_backup_setup_intro_manual_export_action" = "Esporta manualmente le chiavi"; +"key_backup_setup_passphrase_title" = "Proteggi il tuo backup con una frase d'accesso"; +"key_backup_setup_passphrase_info" = "Salveremo una copia cifrata delle tue chiavi nel tuo homeserver. Proteggi il tuo backup con una frase d'accesso (passphrase) per tenerlo sicuro. \n \nPer una massima sicurezza, dovrebbe essere diversa dalla password del tuo account."; +"key_backup_setup_passphrase_passphrase_title" = "Inserisci"; +"key_backup_setup_passphrase_passphrase_placeholder" = "Inserisci passphrase"; +"key_backup_setup_passphrase_passphrase_valid" = "Bene!"; +"key_backup_setup_passphrase_passphrase_invalid" = "Prova ad aggiungere una parola"; +"key_backup_setup_passphrase_confirm_passphrase_title" = "Conferma"; +"key_backup_setup_passphrase_confirm_passphrase_placeholder" = "Conferma passphrase"; +"key_backup_setup_passphrase_confirm_passphrase_valid" = "Bene!"; +"key_backup_setup_passphrase_confirm_passphrase_invalid" = "Le passphrase devono corrispondere"; +"key_backup_setup_passphrase_set_passphrase_action" = "Imposta passphrase"; +"key_backup_setup_passphrase_setup_recovery_key_info" = "Oppure, proteggi il tuo backup con una chiave di ripristino, salvandola in un luogo sicuro."; +"key_backup_setup_passphrase_setup_recovery_key_action" = "(Avanzato) Imposta con chiave di ripristino"; +"key_backup_setup_success_title" = "Completato!"; +// Success from passphrase +"key_backup_setup_success_from_passphrase_info" = "Backup delle tue chiavi in corso.\n\nLa tua chiave di ripristino è una rete di sicurezza - puoi usarla per recuperare l'accesso ai tuoi messaggi cifrati se dimentichi la tua passphrase. \n\nTieni la tua chiave di ripristino in un luogo sicuro, come un password manager (o una cassaforte)."; +"key_backup_setup_success_from_passphrase_save_recovery_key_action" = "Salva chiave di ripristino"; +"key_backup_setup_success_from_passphrase_done_action" = "Fatto"; +// Success from recovery key +"key_backup_setup_success_from_recovery_key_info" = "Backup delle tue chiavi in corso.\n\nFai una copia di questa chiave di ripristino e tienila al sicuro."; +"key_backup_setup_success_from_recovery_key_recovery_key_title" = "Chiave di ripristino"; +"key_backup_setup_success_from_recovery_key_make_copy_action" = "Fai una copia"; +"key_backup_setup_success_from_recovery_key_made_copy_action" = "Ho fatto una copia"; +"key_backup_recover_title" = "Messaggi sicuri"; +"key_backup_recover_invalid_passphrase_title" = "Passphrase di ripristino non corretta"; +"key_backup_recover_invalid_passphrase" = "Impossibile decifrare il backup con questa passphrase: verifica di avere inserito la passphrase di ripristino corretta."; +"key_backup_recover_invalid_recovery_key_title" = "La chiave di ripristino non corrisponde"; +"key_backup_recover_invalid_recovery_key" = "Impossibile decifrare il backup con questa chiave di ripristino: verifica di avere inserito la chiave di ripristino corretta."; +"key_backup_recover_from_passphrase_info" = "Usa la tua chiave di ripristino per sbloccare la cronologia dei messaggi cifrati"; +"key_backup_recover_from_passphrase_passphrase_title" = "Inserisci"; +"key_backup_recover_from_passphrase_passphrase_placeholder" = "Inserisci passphrase"; +"key_backup_recover_from_passphrase_recover_action" = "Sblocca cronologia"; +"key_backup_recover_from_passphrase_lost_passphrase_action_part1" = "Non conosci la tua passphrase di ripristino? Puoi "; +"key_backup_recover_from_passphrase_lost_passphrase_action_part2" = "usa la tua chiave di ripristino"; +"key_backup_recover_from_passphrase_lost_passphrase_action_part3" = "."; +"key_backup_recover_from_recovery_key_info" = "Usa la tua chiave di ripristino per sbloccare la cronologia dei tuoi messaggi sicuri"; +"key_backup_recover_from_recovery_key_recovery_key_title" = "Inserisci"; +"key_backup_recover_from_recovery_key_recovery_key_placeholder" = "Inserisci chiave di ripristino"; +"key_backup_recover_from_recovery_key_recover_action" = "Sblocca cronologia"; +"key_backup_recover_from_recovery_key_lost_recovery_key_action" = "Hai perso la chiave di ripristino? Puoi crearne una nuova nelle impostazioni."; +"key_backup_recover_success_info" = "Backup ripristinato!"; +"key_backup_recover_done_action" = "Fatto"; +"key_backup_setup_banner_title" = "Non perdere mai i messaggi cifrati"; +"key_backup_setup_banner_subtitle" = "Inizia ad usare il backup chiavi"; +"key_backup_recover_banner_title" = "Non perdere mai i messaggi cifrati"; +"sign_out_existing_key_backup_alert_title" = "Sei sicuro di volerti disconnettere?"; +"sign_out_existing_key_backup_alert_sign_out_action" = "Disconnetti"; +"sign_out_non_existing_key_backup_alert_title" = "Se ti disconnetti ora, perderai l'accesso ai tuoi messaggi cifrati"; +"sign_out_non_existing_key_backup_alert_setup_key_backup_action" = "Inizia ad usare il backup chiavi"; +"sign_out_non_existing_key_backup_alert_discard_key_backup_action" = "Non voglio i miei messaggi cifrati"; +"sign_out_non_existing_key_backup_sign_out_confirmation_alert_title" = "Perderai i tuoi messaggi cifrati"; +"sign_out_non_existing_key_backup_sign_out_confirmation_alert_message" = "Perderai l'accesso ai tuoi messaggi cifrati a meno che non fai il backup delle tue chiavi prima di disconnetterti."; +"sign_out_non_existing_key_backup_sign_out_confirmation_alert_sign_out_action" = "Disconnetti"; +"sign_out_non_existing_key_backup_sign_out_confirmation_alert_backup_action" = "Backup"; +"sign_out_key_backup_in_progress_alert_title" = "Backup chiave in corso. Se ti disconnetti ora, perderai l'accesso ai tuoi messaggi cifrati."; +"sign_out_key_backup_in_progress_alert_discard_key_backup_action" = "Non voglio i miei messaggi cifrati"; +"sign_out_key_backup_in_progress_alert_cancel_action" = "Aspetto"; diff --git a/Riot/Assets/ko.lproj/InfoPlist.strings b/Riot/Assets/ko.lproj/InfoPlist.strings index d253e9c23..5ce0ebe82 100644 --- a/Riot/Assets/ko.lproj/InfoPlist.strings +++ b/Riot/Assets/ko.lproj/InfoPlist.strings @@ -3,3 +3,4 @@ "NSPhotoLibraryUsageDescription" = "포토 라이브러리는 사진과 영상을 보내는데 쓰입니다."; "NSMicrophoneUsageDescription" = "마이크는 영상 촬영, 통화에 쓰입니다."; "NSContactsUsageDescription" = "연락처에 있는 상대가 이미 Riot이나 Matrix를 이용하고 있다는 걸 보여주기 위해 연락처의 이메일 주소와 전화번호를 Matrix 아이덴티티 서버로 보낼 수 있습니다. 새 Vector는 이 자료를 저장하거나 다른 용도로 사용하지 않습니다. 자세한 내용은 애플리케이션 설정에 있는 개인정보 보호정책을 읽어주세요."; +"NSCalendarsUsageDescription" = "앱에서 예정된 회의를 봅니다."; diff --git a/Riot/Assets/ko.lproj/Localizable.strings b/Riot/Assets/ko.lproj/Localizable.strings index 09d338371..779227e7f 100644 --- a/Riot/Assets/ko.lproj/Localizable.strings +++ b/Riot/Assets/ko.lproj/Localizable.strings @@ -13,13 +13,13 @@ /* New action message from a specific person, not referencing a room. */ "IMAGE_FROM_USER" = "%@ 보낸 사진 %@"; /* A single unread message in a room */ -"SINGLE_UNREAD_IN_ROOM" = "%@에서 메시지를 받았습니다."; +"SINGLE_UNREAD_IN_ROOM" = "%@에서 메시지를 받았습니다"; /* A single unread message */ -"SINGLE_UNREAD" = "메시지를 받았습니다."; +"SINGLE_UNREAD" = "메시지를 받았습니다"; /* A user has invited you to a chat */ -"USER_INVITE_TO_CHAT" = "%@가 대화에 당신을 초대했습니다."; +"USER_INVITE_TO_CHAT" = "%@가 대화에 당신을 초대했습니다"; /* A user has invited you to an (unamed) group chat */ -"USER_INVITE_TO_CHAT_GROUP_CHAT" = "%@가 그룹 대화에 당신을 초대했습니다."; +"USER_INVITE_TO_CHAT_GROUP_CHAT" = "%@가 그룹 대화에 당신을 초대했습니다"; /* Incoming one-to-one voice call */ "VOICE_CALL_FROM_USER" = "%@이 건 통화"; /* Incoming one-to-one video call */ @@ -28,3 +28,7 @@ "VOICE_CONF_FROM_USER" = "%@이 건 그룹통화"; /* Incoming unnamed video conference invite from a specific person */ "VIDEO_CONF_FROM_USER" = "%@이 건 영상그룹통화"; +/* Sticker from a specific person, not referencing a room. */ +"STICKER_FROM_USER" = "%@가 스티커를 보냈습니다"; +/* A user has invited you to a named room */ +"USER_INVITE_TO_NAMED_ROOM" = "%@가 %@로 당신을 초대했습니다"; diff --git a/Riot/Assets/nl.lproj/InfoPlist.strings b/Riot/Assets/nl.lproj/InfoPlist.strings index d81fc329d..2f756c814 100644 --- a/Riot/Assets/nl.lproj/InfoPlist.strings +++ b/Riot/Assets/nl.lproj/InfoPlist.strings @@ -15,7 +15,7 @@ */ // Permissions usage explanations -"NSCameraUsageDescription" = "De camera wordt gebruikt om foto's te schieten, video's te maken en voor videogesprekken."; -"NSPhotoLibraryUsageDescription" = "De fotogalerij wordt gebruikt om foto's en video's te versturen."; -"NSMicrophoneUsageDescription" = "De microffon wordt gebruikt om video's te maken en voor spraakoproepen."; -"NSContactsUsageDescription" = "De contactenlijst wordt gebruikt om naar gebruikers te zoeken door middel van een e-mailadres of telefoonnummer op Riot."; +"NSCameraUsageDescription" = "De camera wordt gebruikt om foto’s en video’s te maken, en voor videogesprekken."; +"NSPhotoLibraryUsageDescription" = "De fotogalerij wordt gebruikt om foto’s en video’s te versturen."; +"NSMicrophoneUsageDescription" = "De microfoon wordt gebruikt om video’s te maken, en voor spraakoproepen."; +"NSContactsUsageDescription" = "Om u te kunnen tonen welke van uw contacten reeds Riot of Matrix gebruiken, kunnen we de e-mailadressen en telefoonnummers in uw adresboek naar uw Matrix-identiteitsserver sturen. New Vector bewaart deze gegevens niet en gebruikt ze niet voor andere doeleinden. Bekijk voor meer informatie de privacybeleidspagina in de instellingen van de app."; diff --git a/Riot/Assets/nl.lproj/Localizable.strings b/Riot/Assets/nl.lproj/Localizable.strings index 10883cd3c..ecca3ab6c 100644 --- a/Riot/Assets/nl.lproj/Localizable.strings +++ b/Riot/Assets/nl.lproj/Localizable.strings @@ -33,9 +33,9 @@ /** Image Messages **/ /* New action message from a specific person, not referencing a room. */ -"IMAGE_FROM_USER" = "%@ heeft je een afbeelding %@ gestuurd"; +"IMAGE_FROM_USER" = "%@ heeft u een afbeelding %@ gestuurd"; /* New action message from a specific person in a named room. */ -"IMAGE_FROM_USER_IN_ROOM" = "%@ plaatste een afbeelding %@ in %@"; +"IMAGE_FROM_USER_IN_ROOM" = "%@ heeft een afbeelding %@ in %@ geplaatst"; /** Coalesced messages **/ /* Multiple unread messages in a room */ @@ -55,11 +55,11 @@ /** Invites **/ /* A user has invited you to a chat */ -"USER_INVITE_TO_CHAT" = "%@ heeft je voor een gesprek uitgenodigd"; +"USER_INVITE_TO_CHAT" = "%@ heeft u voor een gesprek uitgenodigd"; /* A user has invited you to an (unamed) group chat */ -"USER_INVITE_TO_CHAT_GROUP_CHAT" = "%@ heeft je voor een groepsgesprek uitgenodigd"; +"USER_INVITE_TO_CHAT_GROUP_CHAT" = "%@ heeft u in een groepsgesprek uitgenodigd"; /* A user has invited you to a named room */ -"USER_INVITE_TO_NAMED_ROOM" = "%@ heeft je in %@ uitgenodigd"; +"USER_INVITE_TO_NAMED_ROOM" = "%@ heeft u in %@ uitgenodigd"; /** Calls **/ /* Incoming one-to-one voice call */ @@ -69,12 +69,12 @@ /* Incoming unnamed voice conference invite from a specific person */ "VOICE_CONF_FROM_USER" = "Groepsoproep van %@"; /* Incoming unnamed video conference invite from a specific person */ -"VIDEO_CONF_FROM_USER" = "Video groepsoproep van %@"; +"VIDEO_CONF_FROM_USER" = "Video-groepsoproep van %@"; /* Incoming named voice conference invite from a specific person */ -"VOICE_CONF_NAMED_FROM_USER" = "Groepsoproep van %@: '%@'"; +"VOICE_CONF_NAMED_FROM_USER" = "Groepsoproep van %@: ‘%@’"; /* Incoming named video conference invite from a specific person */ -"VIDEO_CONF_NAMED_FROM_USER" = "Video groepsoproep van %@: '%@'"; +"VIDEO_CONF_NAMED_FROM_USER" = "Video-groepsoproep van %@: ‘%@’"; /* A single unread message in a room */ -"SINGLE_UNREAD_IN_ROOM" = "Je hebt een bericht ontvangen in %@"; +"SINGLE_UNREAD_IN_ROOM" = "U heeft een bericht ontvangen in %@"; /* A single unread message */ -"SINGLE_UNREAD" = "Je hebt een bericht ontvangen"; +"SINGLE_UNREAD" = "U heeft een bericht ontvangen"; diff --git a/Riot/Assets/nl.lproj/Vector.strings b/Riot/Assets/nl.lproj/Vector.strings index aed22a6bf..4038b6d7e 100644 --- a/Riot/Assets/nl.lproj/Vector.strings +++ b/Riot/Assets/nl.lproj/Vector.strings @@ -15,17 +15,17 @@ */ // Titles -"title_home" = "Home"; +"title_home" = "Thuis"; "title_favourites" = "Favorieten"; "title_people" = "Personen"; -"title_rooms" = "Ruimtes"; +"title_rooms" = "Gesprekken"; "warning" = "Waarschuwing"; // Actions "view" = "Weergeven"; "next" = "Volgende"; "back" = "Terug"; "continue" = "Verder gaan"; -"create" = "Maak"; +"create" = "Aanmaken"; "start" = "Starten"; "leave" = "Verlaten"; "remove" = "Verwijderen"; @@ -37,23 +37,23 @@ "save" = "Opslaan"; "join" = "Toetreden"; "decline" = "Afwijzen"; -"accept" = "Accepteren"; +"accept" = "Aanvaarden"; "preview" = "Voorvertonen"; "camera" = "Camera"; "voice" = "Spraak"; "video" = "Video"; -"active_call" = "Actief Gesprek"; -"active_call_details" = "Actief gesprek (%@)"; +"active_call" = "Actieve oproep"; +"active_call_details" = "Actieve oproep (%@)"; "later" = "Later"; -"rename" = "Herbenoemen"; +"rename" = "Hernoemen"; // Authentication -"auth_login" = "Inloggen"; +"auth_login" = "Aanmelden"; "auth_register" = "Registreren"; "auth_submit" = "Versturen"; "auth_skip" = "Overslaan"; -"auth_send_reset_email" = "Stuur Herstel E-mail"; -"auth_return_to_login" = "Ga terug naar het herstel scherm"; -"auth_user_id_placeholder" = "E-mail of gebruikersnaam"; +"auth_send_reset_email" = "Herstel-e-mail versturen"; +"auth_return_to_login" = "Terug naar het aanmeldingsscherm"; +"auth_user_id_placeholder" = "E-mailadres of gebruikersnaam"; "auth_password_placeholder" = "Wachtwoord"; "auth_new_password_placeholder" = "Nieuw wachtwoord"; "auth_user_name_placeholder" = "Gebruikersnaam"; @@ -62,112 +62,112 @@ "auth_optional_phone_placeholder" = "Telefoonnummer (optioneel)"; "auth_phone_placeholder" = "Telefoonnummer"; "auth_repeat_password_placeholder" = "Wachtwoord herhalen"; -"auth_repeat_new_password_placeholder" = "Bevestig je nieuwe wachtwoord"; -"auth_invalid_login_param" = "Incorrecte gebruikersnaam en/of wachtwoord"; -"auth_invalid_user_name" = "Gebruikersnamen mogen alleen letters, cijfers, punten, afbreekstreepjes en lage streepjes bevatten"; +"auth_repeat_new_password_placeholder" = "Bevestig uw nieuwe wachtwoord"; +"auth_invalid_login_param" = "Onjuiste gebruikersnaam en/of wachtwoord"; +"auth_invalid_user_name" = "Gebruikersnamen mogen alleen letters, cijfers, punten, afbreek- en lage streepjes bevatten"; "auth_invalid_password" = "Het wachtwoord is te kort (min 6)"; "auth_invalid_email" = "Dit ziet er niet uit als een geldig e-mailadres"; "auth_invalid_phone" = "Dit ziet er niet uit als een geldig telefoonnummer"; -"auth_missing_password" = "Missend wachtwoord"; -"auth_add_email_message" = "Voeg een e-mailadres aan je account toe zodat gebruikers je kunnen vinden en je het wachtwoord kunt herstellen."; -"auth_add_phone_message" = "Voeg een telefoonnummer aan je account toe zodat gebruikers je kunnen vinden."; -"auth_add_email_phone_message" = "Voeg een e-mailadres en/of een telefoonnummer aan je account toe zodat gebruikers je kunnen vinden. Je e-mailadres maakt het ook mogelijk om je wachtwoord te herstellen."; -"auth_add_email_and_phone_message" = "Voeg een e-mailadres en een telefoonnummer aan je account toe zodat gebruikers je kunnen vinden. Je e-mailadres maakt het ook mogelijk om je wachtwoord te herstellen."; -"auth_missing_email" = "Het e-mailadres mist"; -"auth_missing_phone" = "Het telefoonnummer mist"; -"auth_missing_email_or_phone" = "Het e-mailadres of telefoonnummer mist"; +"auth_missing_password" = "Wachtwoord ontbreekt"; +"auth_add_email_message" = "Voeg een e-mailadres aan uw account toe zodat gebruikers u kunnen vinden en u uw wachtwoord kunt herstellen."; +"auth_add_phone_message" = "Voeg een telefoonnummer aan uw account toe zodat gebruikers uw kunnen vinden."; +"auth_add_email_phone_message" = "Voeg een e-mailadres en/of telefoonnummer aan uw account toe zodat gebruikers u kunnen vinden. Uw e-mailadres maakt het ook mogelijk om uw wachtwoord te herstellen."; +"auth_add_email_and_phone_message" = "Voeg een e-mailadres en een telefoonnummer aan uw account toe zodat gebruikers u kunnen vinden. Uw e-mailadres maakt het ook mogelijk om uw wachtwoord te herstellen."; +"auth_missing_email" = "E-mailadres ontbreekt"; +"auth_missing_phone" = "Telefoonnummer ontbreekt"; +"auth_missing_email_or_phone" = "E-mailadres of telefoonnummer ontbreekt"; "auth_password_dont_match" = "De wachtwoorden komen niet overeen"; "auth_username_in_use" = "De gebruikersnaam is al in gebruik"; "auth_forgot_password" = "Wachtwoord vergeten?"; -"auth_use_server_options" = "Gebruik alternatieve serverinstellingen (geavanceerd)"; -"auth_email_validation_message" = "Bekijk je e-mail om verder te gaan met de registratie"; -"auth_msisdn_validation_title" = "Verificatie Aanhangig"; -"auth_msisdn_validation_message" = "We hebben een SMS met een activatie code gestuurd. Vul deze code hieronder in."; -"auth_msisdn_validation_error" = "Niet mogelijk om het telefoonnummer te verifiëren."; -"auth_recaptcha_message" = "Deze Thuisserver wil er graag zeker van zijn dat je geen robot bent"; -"auth_reset_password_message" = "Om je wachtwoord te herstellen, voer het e-mailadres dat met je account verbonden is in:"; -"auth_reset_password_missing_email" = "Het e-mailadres dat met je account verbonden is moet ingevoerd worden."; -"auth_reset_password_missing_password" = "Een nieuw wachtwoord moet ingevoerd worden."; -"auth_reset_password_email_validation_message" = "Een e-mail is naar %@ gestuurd. Zodra je de link die het bevat hebt gevolgd, klik hieronder."; +"auth_use_server_options" = "Aangepaste serverinstellingen gebruiken (geavanceerd)"; +"auth_email_validation_message" = "Bekijk uw e-mail om verder te gaan met de registratie"; +"auth_msisdn_validation_title" = "Verificatie in afwachting"; +"auth_msisdn_validation_message" = "We hebben een sms met een activatiecode gestuurd. Voer deze code hieronder in."; +"auth_msisdn_validation_error" = "Kan het telefoonnummer niet verifiëren."; +"auth_recaptcha_message" = "Deze thuisserver wil er graag zeker van zijn dat u geen robot bent"; +"auth_reset_password_message" = "Voer het e-mailadres dat met uw account verbonden is in om uw wachtwoord opnieuw in te stellen:"; +"auth_reset_password_missing_email" = "Het e-mailadres dat met uw account verbonden is moet ingevoerd worden."; +"auth_reset_password_missing_password" = "Er moet een nieuw wachtwoord ingevoerd worden."; +"auth_reset_password_email_validation_message" = "Er is een e-mail naar %@ gestuurd. Klik hieronder zodra u de koppeling erin hebt gevolgd."; "auth_reset_password_next_step_button" = "Ik heb mijn e-mailadres geverifieerd"; -"auth_reset_password_error_unauthorized" = "Het is niet gelukt om het e-mailadres te verifiëren: wees er zeker van dat je op de link in de e-mail hebt geklikt"; -"auth_reset_password_error_not_found" = "Het ziet er niet naar uit dat het e-mailadres met het Matrix ID op deze homeserver is verbonden."; -"auth_reset_password_success_message" = "Je wachtwoord is opnieuw ingesteld.\n\nJe bent op alle apparaten uitgelogd en je zal geen notificaties meer ontvangen. Om notificaties weer in te schakelen, log op elk apparaat opnieuw in."; -"auth_add_email_and_phone_warning" = "Registratie met e-mail en telefoonnummer tegelijkertijd wordt nog niet ondersteund totdat de api bestaat. Alleen het telefoonnummer zal worden gebruikt. Je kan je e-mailadres later aan je profiel in de instellingen toevoegen."; +"auth_reset_password_error_unauthorized" = "Verifiëren van e-mailadres is mislukt: wees er zeker van dat u op de koppeling in de e-mail hebt getikt"; +"auth_reset_password_error_not_found" = "Het ziet er niet naar uit dat uw e-mailadres met een Matrix-ID op deze thuisserver is verbonden."; +"auth_reset_password_success_message" = "Uw wachtwoord is opnieuw ingesteld.\n\nU bent op alle apparaten afgemeld en u zult geen pushmeldingen meer ontvangen. Om meldingen weer in te schakelen, meldt u zich op elk apparaat opnieuw aan."; +"auth_add_email_and_phone_warning" = "Registratie met e-mailadres en telefoonnummer tegelijkertijd wordt, totdat de API bestaat, nog niet ondersteund. Alleen het telefoonnummer zal worden gebruikt. U kunt uw e-mailadres later aan uw profiel in de instellingen toevoegen."; // Chat creation -"room_creation_title" = "Niew gesprek"; +"room_creation_title" = "Nieuw gesprek"; "room_creation_account" = "Account"; "room_creation_appearance" = "Uiterlijk"; "room_creation_appearance_name" = "Naam"; "room_creation_appearance_picture" = "Gespreksafbeelding (optioneel)"; "room_creation_privacy" = "Privacy"; "room_creation_private_room" = "Dit gesprek is privé"; -"room_creation_public_room" = "Dit gesprek ik publiek"; -"room_creation_make_public" = "Maak publiek"; +"room_creation_public_room" = "Dit gesprek is publiek"; +"room_creation_make_public" = "Publiek maken"; "room_creation_make_public_prompt_title" = "Dit gesprek publiek maken?"; -"room_creation_make_public_prompt_msg" = "Weet je zeker dat je dit gesprek publiek wilt maken? Iedereen kan je berichten lezen en aan het gesprek deelnemen."; +"room_creation_make_public_prompt_msg" = "Weet u zeker dat u dit gesprek publiek wilt maken? Iedereen kan uw berichten lezen en aan het gesprek deelnemen."; "room_creation_keep_private" = "Privé houden"; "room_creation_make_private" = "Privé maken"; -"room_creation_wait_for_creation" = "Er wordt al een ruimte aangemaakt. Even geduld alstublieft."; -"room_creation_invite_another_user" = "Zoek / nodig uit bij Gebruikers ID, Naam of e-mail"; +"room_creation_wait_for_creation" = "Er wordt al een gesprek aangemaakt. Even geduld."; +"room_creation_invite_another_user" = "Zoeken/uitnodigen met gebruikers-ID, naam of e-mailadres"; // Room recents -"room_recents_directory_section" = "RUIMTE ADRESBOEK"; +"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" = "RUIMTES"; -"room_recents_no_conversation" = "Geen ruimtes"; +"room_recents_conversations_section" = "GESPREKKEN"; +"room_recents_no_conversation" = "Geen gesprekken"; "room_recents_low_priority_section" = "LAGE PRIORITEIT"; "room_recents_invites_section" = "UITNODIGINGEN"; -"room_recents_start_chat_with" = "Start gesprek"; -"room_recents_create_empty_room" = "Creeër ruimte"; -"room_recents_join_room" = "Aan de ruimte deelnemen"; -"room_recents_join_room_title" = "Neem aan een ruimte deel"; -"room_recents_join_room_prompt" = "Typ een ruimte id of naam in"; +"room_recents_start_chat_with" = "Gesprek beginnen"; +"room_recents_create_empty_room" = "Gesprek aanmaken"; +"room_recents_join_room" = "Gesprek toetreden"; +"room_recents_join_room_title" = "Neem aan een gesprek deel"; +"room_recents_join_room_prompt" = "Voer een gespreks(bij)naam in"; // People tab "people_invites_section" = "UITNODIGINGEN"; "people_conversation_section" = "GESPREKKEN"; "people_no_conversation" = "Geen gesprekken"; // Rooms tab -"room_directory_no_public_room" = "Geen publieke ruimtes beschikbaar"; +"room_directory_no_public_room" = "Geen publieke gesprekken beschikbaar"; // Search -"search_rooms" = "Ruimtes"; +"search_rooms" = "Gesprekken"; "search_messages" = "Berichten"; "search_people" = "Personen"; "search_files" = "Bestanden"; "search_default_placeholder" = "Zoeken"; -"search_people_placeholder" = "Zoek bij Gebruikers ID, Naam of e-mail"; +"search_people_placeholder" = "Zoeken op gebruikers-ID, naam of e-mailadres"; "search_no_result" = "Geen resultaten"; // Directory -"directory_cell_title" = "Blader door de catalogus"; -"directory_cell_description" = "%tu ruimtes"; -"directory_search_results_title" = "Blader door catalogus resultaten"; +"directory_cell_title" = "Bladeren door de catalogus"; +"directory_cell_description" = "%tu gesprekken"; +"directory_search_results_title" = "Bladeren door catalogusresultaten"; "directory_search_results" = "%tu resultaten gevonden voor %@"; "directory_search_results_more_than" = ">%tu resultaten gevonden voor %@"; -"directory_searching_title" = "Catalogus afzoeken…"; -"directory_search_fail" = "Niet gelukt om data op te halen"; +"directory_searching_title" = "Catalogus wordt doorzocht…"; +"directory_search_fail" = "Ophalen van gegevens is mislukt"; // Contacts "contacts_address_book_section" = "LOKALE CONTACTEN"; -"contacts_address_book_matrix_users_toggle" = "Alleen Matrix gebruikers"; +"contacts_address_book_matrix_users_toggle" = "Alleen Matrix-gebruikers"; "contacts_address_book_no_contact" = "Geen lokale contacten"; -"contacts_address_book_permission_required" = "Permissie vereist voor toegang tot de lokale contacten"; -"contacts_address_book_permission_denied" = "Je hebt Riot geen toegang tot je lokale contacten toegestaan"; +"contacts_address_book_permission_required" = "Toestemming vereist voor toegang tot de lokale contacten"; +"contacts_address_book_permission_denied" = "U heeft Riot geen toegang tot uw lokale contacten verleend"; // Chat participants "room_participants_title" = "Deelnemers"; "room_participants_add_participant" = "Deelnemer toevoegen"; "room_participants_one_participant" = "1 deelnemer"; "room_participants_multi_participants" = "%d deelnemers"; -"room_participants_leave_prompt_title" = "Ruimte verlaten"; -"room_participants_leave_prompt_msg" = "Weet je zeker dat je de ruimte wilt verlaten?"; +"room_participants_leave_prompt_title" = "Gesprek verlaten"; +"room_participants_leave_prompt_msg" = "Weet u zeker dat u het gesprek wilt verlaten?"; "room_participants_remove_prompt_title" = "Bevestiging"; -"room_participants_remove_prompt_msg" = "Weet je zeker dat je %@ van dit gesprek wilt verwijderen?"; -"room_participants_remove_third_party_invite_msg" = "Verwijderen derde-partij uitnodiging is nog niet ondersteund tot de api bestaat"; +"room_participants_remove_prompt_msg" = "Weet u zeker dat u %@ uit dit gesprek wilt verwijderen?"; +"room_participants_remove_third_party_invite_msg" = "Verwijderen van uitnodigingen door derde partijen wordt, totdat de API bestaat, nog niet ondersteund"; "room_participants_invite_prompt_title" = "Bevestiging"; -"room_participants_invite_prompt_msg" = "Weet je zeker dat je %@ voor dit gesprek wilt uitnodigen?"; -"room_participants_filter_room_members" = "Filter ruimteleden"; -"room_participants_invite_another_user" = "Zoek / nodig uit bij Gebruikers ID, Naam of e-mail"; +"room_participants_invite_prompt_msg" = "Weet u zeker dat u %@ in dit gesprek wilt uitnodigen?"; +"room_participants_filter_room_members" = "Filter gespreksleden"; +"room_participants_invite_another_user" = "Zoeken/uitnodigen met gebruikers-ID, naam of e-mailadres"; "room_participants_invite_malformed_id_title" = "Uitnodigingsfout"; -"room_participants_invite_malformed_id" = "Ongeldig ID. Het hoort een e-mailadres of een Matrix ID zoals '@localpart:domain' te zijn"; +"room_participants_invite_malformed_id" = "Ongeldige ID. Dit zou een e-mailadres of een Matrix-ID zoals ‘@gebruikersnaam:domein’ moeten zijn"; "room_participants_invited_section" = "UITGENODIGD"; "room_participants_online" = "Online"; "room_participants_offline" = "Offline"; @@ -175,122 +175,122 @@ "room_participants_idle" = "Afwezig"; "room_participants_now" = "nu"; "room_participants_ago" = "geleden"; -"room_participants_action_section_admin_tools" = "Beheerder gereedschappen"; -"room_participants_action_section_direct_chats" = "Directe gesprekken"; +"room_participants_action_section_admin_tools" = "Beheerdersgereedschap"; +"room_participants_action_section_direct_chats" = "Tweegesprekken"; "room_participants_action_section_devices" = "Apparaten"; -"room_participants_action_section_other" = "Anders"; -"room_participants_action_invite" = "Nodig uit"; -"room_participants_action_leave" = "Deze ruimte verlaten"; -"room_participants_action_remove" = "Verwijder van deze ruimte"; -"room_participants_action_ban" = "Verban uit deze ruimte"; -"room_participants_action_unban" = "Ontban"; -"room_participants_action_ignore" = "Verberg alle berichten van deze gebruiker"; -"room_participants_action_unignore" = "Toon alle berichten van deze gebruiker"; +"room_participants_action_section_other" = "Overige"; +"room_participants_action_invite" = "Uitnodigen"; +"room_participants_action_leave" = "Dit gesprek verlaten"; +"room_participants_action_remove" = "Verwijderen uit dit gesprek"; +"room_participants_action_ban" = "Verbannen uit dit gesprek"; +"room_participants_action_unban" = "Ontbannen"; +"room_participants_action_ignore" = "Alle berichten van deze gebruiker verbergen"; +"room_participants_action_unignore" = "Alle berichten van deze gebruiker tonen"; "room_participants_action_set_default_power_level" = "Terugzetten naar normale gebruiker"; -"room_participants_action_set_moderator" = "Maak moderator"; -"room_participants_action_set_admin" = "Maak admin"; -"room_participants_action_start_new_chat" = "Start nieuw gesprek"; -"room_participants_action_start_voice_call" = "Start spraakoproep"; -"room_participants_action_start_video_call" = "Start video-oproep"; +"room_participants_action_set_moderator" = "Benoemen tot moderator"; +"room_participants_action_set_admin" = "Benoemen tot beheerder"; +"room_participants_action_start_new_chat" = "Nieuw gesprek beginnen"; +"room_participants_action_start_voice_call" = "Spraakoproep beginnen"; +"room_participants_action_start_video_call" = "Video-oproep beginnen"; "room_participants_action_mention" = "Vermelden"; // Chat "room_jump_to_first_unread" = "Spring naar het eerste ongelezen bericht"; "room_new_message_notification" = "%d nieuw bericht"; "room_new_messages_notification" = "%d nieuwe berichten"; "room_one_user_is_typing" = "%@ is aan het typen…"; -"room_two_users_are_typing" = "%@ & %@ zijn aan het typen…"; -"room_many_users_are_typing" = "%@, %@ & anderen zijn aan het typen…"; +"room_two_users_are_typing" = "%@ en %@ zijn aan het typen…"; +"room_many_users_are_typing" = "%@, %@ en anderen zijn aan het typen…"; "room_message_placeholder" = "Stuur een bericht (onversleuteld)…"; "encrypted_room_message_placeholder" = "Stuur een versleuteld bericht…"; "room_message_short_placeholder" = "Stuur een bericht…"; -"room_offline_notification" = "De verbinding met de server is verloren."; -"room_unsent_messages_notification" = "Berichten niet verstuurd. %@ of %@ nu?"; -"room_unsent_messages_unknown_devices_notification" = "Bericht is niet verstuurd doordat onbekende apparaten aanwezig zijn. %@ of %@ nu?"; -"room_ongoing_conference_call" = "Er is een vergadergesprek gaande. Deelnemen als %@ of %@."; -"room_prompt_resend" = "Alles opnieuw versturen"; -"room_prompt_cancel" = "Alles annuleren"; -"room_resend_unsent_messages" = "Niet verstuurde berichten opnieuw versturen"; -"room_delete_unsent_messages" = "Niet verstuurde berichten verwijderen"; +"room_offline_notification" = "De verbinding met de server is verbroken."; +"room_unsent_messages_notification" = "Berichten niet verstuurd. Nu %@ of %@?"; +"room_unsent_messages_unknown_devices_notification" = "Bericht is niet verstuurd doordat er onbekende apparaten aanwezig zijn. Nu %@ of %@?"; +"room_ongoing_conference_call" = "Er is een vergadergesprek gaande. Neem deel met %@ of %@."; +"room_prompt_resend" = "alles opnieuw versturen"; +"room_prompt_cancel" = "alles annuleren"; +"room_resend_unsent_messages" = "Onverstuurde berichten opnieuw versturen"; +"room_delete_unsent_messages" = "Onverstuurde berichten verwijderen"; "room_event_action_copy" = "Kopiëren"; -"room_event_action_quote" = "Citeer"; +"room_event_action_quote" = "Citeren"; "room_event_action_redact" = "Verwijderen"; "room_event_action_more" = "Meer"; "room_event_action_share" = "Delen"; "room_event_action_permalink" = "Permalink"; -"room_event_action_view_source" = "Bekijk bron"; +"room_event_action_view_source" = "Bron weergeven"; "room_event_action_report" = "Inhoud melden"; -"room_event_action_report_prompt_reason" = "Reden voor het aangeven van deze content"; -"room_event_action_report_prompt_ignore_user" = "Wil je alle berichten van deze gebruiker verbergen?"; +"room_event_action_report_prompt_reason" = "Reden voor het melden van deze inhoud"; +"room_event_action_report_prompt_ignore_user" = "Wilt u alle berichten van deze gebruiker verbergen?"; "room_event_action_save" = "Opslaan"; "room_event_action_resend" = "Opnieuw versturen"; "room_event_action_delete" = "Verwijderen"; "room_event_action_cancel_upload" = "Uploaden annuleren"; "room_event_action_cancel_download" = "Downloaden annuleren"; "room_event_action_view_encryption" = "Versleutelingsinformatie"; -"room_warning_about_encryption" = "End-to-endbeveiliging is in bèta en kan onbetrouwbaar zijn.\n\nHet is beter om het nog niet met gevoelige gegevens te vertrouwen.\n\nApparaten kunnen de geschiedenis van voordat ze de ruimte betraden nog niet ontsleutelen.\n\nVersleutelde berichten zullen nog niet zichtbaar zijn op programma's die geen versleuteling ondersteunen."; +"room_warning_about_encryption" = "Eind-tot-eindversleuteling is in bèta en kan onbetrouwbaar zijn.\n\nHet is beter om het nog niet met gevoelige gegevens te vertrouwen.\n\nApparaten kunnen de geschiedenis van vóór ze het gesprek betraden nog niet ontsleutelen.\n\nVersleutelde berichten zullen niet zichtbaar zijn op cliënten die nog geen versleuteling ondersteunen."; // Unknown devices -"unknown_devices_alert_title" = "Ruimte bevat onbekende apparaten"; -"unknown_devices_alert" = "Deze ruimte bevat onbekende apparaten die niet geen geverifieerd.\nDit betekent dat er geen garantie is dat de apparaten bij de gebruikers horen waar het beweert dat het bij hoort.\nWe raden je aan om bij elk apparaat door het verificatieprocces heen te gaan voordat je doorgaat, maar je kan het bericht opnieuw versturen zonder te verifiëren als je dat prefereert."; +"unknown_devices_alert_title" = "Gesprek bevat onbekende apparaten"; +"unknown_devices_alert" = "Dit gesprek bevat onbekende apparaten die niet geverifieerd zijn.\nDit betekent dat er geen garantie is dat de apparaten bij de gebruikers horen waarbij ze beweren te horen.\nWe raden u aan om bij elk apparaat door het verificatieproces heen te gaan voordat u verdergaat, maar u kunt het bericht ook zonder verificatie opnieuw versturen."; "unknown_devices_send_anyway" = "Alsnog versturen"; "unknown_devices_call_anyway" = "Alsnog bellen"; "unknown_devices_answer_anyway" = "Alsnog beantwoorden"; "unknown_devices_verify" = "Verifiëren…"; "unknown_devices_title" = "Onbekende apparaten"; // Room Title -"room_title_new_room" = "Nieuwe ruimte"; +"room_title_new_room" = "Nieuw gesprek"; "room_title_multiple_active_members" = "%@/%@ actieve leden"; "room_title_one_active_member" = "%@/%@ actief lid"; "room_title_invite_members" = "Leden uitnodigen"; "room_title_members" = "%@ leden"; "room_title_one_member" = "1 lid"; // Room Preview -"room_preview_invitation_format" = "Je bent uitgenodigd om aan deze ruimte deel te nemen bij %@"; -"room_preview_subtitle" = "Dit is een voorvertoning van deze ruimte. Ruimte interacties zijn uitgeschakeld."; -"room_preview_unlinked_email_warning" = "Deze uitnodiging is naar %@ verstuurd. Maar diegene is niet geassocieerd met dit account.\nMisschien wil je met een ander account inloggen of deze e-mail aan dit account toevoegen."; -"room_preview_try_join_an_unknown_room" = "Je probeert aan %@ deel te nemen. Zou je willen toetreden om aan de discussie deel te nemen?"; -"room_preview_try_join_an_unknown_room_default" = "een ruimte"; +"room_preview_invitation_format" = "U bent door %@ uitgenodigd om dit gesprek toe te treden"; +"room_preview_subtitle" = "Dit is een voorvertoning van dit gesprek. Gespreksinteracties zijn uitgeschakeld."; +"room_preview_unlinked_email_warning" = "Deze uitnodiging is naar %@ verstuurd, maar dat is niet geassocieerd met deze account. U kunt zich met een andere account aanmelden, of dit e-mailadres aan deze account toevoegen."; +"room_preview_try_join_an_unknown_room" = "U probeert toegang te verkrijgen tot %@. Zou u willen toetreden om aan het gesprek deel te nemen?"; +"room_preview_try_join_an_unknown_room_default" = "een gesprek"; // Settings "settings_title" = "Instellingen"; -"account_logout_all" = "Op alle accounts uitloggen"; -"settings_config_no_build_info" = "Geen bouw informatie"; +"account_logout_all" = "Alle accounts afmelden"; +"settings_config_no_build_info" = "Geen versie-informatie"; "settings_mark_all_as_read" = "Alle berichten als gelezen markeren"; "settings_report_bug" = "Fout rapporteren"; "settings_clear_cache" = "Cache verwijderen"; "settings_config_home_server" = "Thuisserver is %@"; "settings_config_identity_server" = "Identiteitsserver is %@"; -"settings_config_user_id" = "Ingelogd als %@"; -"settings_user_settings" = "GEBRUIKER INSTELLINGEN"; -"settings_notifications_settings" = "NOTIFICATIE INSTELLINGEN"; +"settings_config_user_id" = "Aangemeld als %@"; +"settings_user_settings" = "GEBRUIKERSINSTELLINGEN"; +"settings_notifications_settings" = "MELDINGSINSTELLINGEN"; "settings_ignored_users" = "GENEGEERDE GEBRUIKERS"; "settings_contacts" = "LOKALE CONTACTEN"; "settings_advanced" = "GEAVANCEERD"; -"settings_other" = "ANDERS"; -"settings_labs" = "LABS"; +"settings_other" = "OVERIGE"; +"settings_labs" = "EXPERIMENTEEL"; "settings_devices" = "APPARATEN"; "settings_cryptography" = "CRYPTOGRAFIE"; -"settings_sign_out" = "Uitloggen"; -"settings_sign_out_confirmation" = "Weet je het zeker?"; -"settings_sign_out_e2e_warn" = "Je zal je eind-tot-eind encryptie sleutels kwijtraken. Dat betekent dat je op dit apparaat geen oude berichten meer kan lezen in een versleutelde ruimtes."; +"settings_sign_out" = "Afmelden"; +"settings_sign_out_confirmation" = "Weet u het zeker?"; +"settings_sign_out_e2e_warn" = "U zult uw sleutels voor eind-tot-eind-versleuteling kwijtraken. Dat betekent dat u op dit apparaat geen oude berichten in versleutelde gesprekken meer zult kunnen lezen."; "settings_profile_picture" = "Profielfoto"; -"settings_display_name" = "Naam"; +"settings_display_name" = "Weergavenaam"; "settings_first_name" = "Voornaam"; "settings_surname" = "Achternaam"; "settings_remove_prompt_title" = "Bevestiging"; -"settings_remove_email_prompt_msg" = "Weet je zeker dat je het e-mailadres %@ wilt verwijderen?"; -"settings_remove_phone_prompt_msg" = "Weet je zeker dat je het telefoonnummer %@ wilt verwijderen?"; -"settings_email_address" = "E-mail"; -"settings_email_address_placeholder" = "Voer je e-mailadres in"; +"settings_remove_email_prompt_msg" = "Weet u zeker dat u het e-mailadres %@ wilt verwijderen?"; +"settings_remove_phone_prompt_msg" = "Weet u zeker dat u het telefoonnummer %@ wilt verwijderen?"; +"settings_email_address" = "E-mailadres"; +"settings_email_address_placeholder" = "Voer uw e-mailadres in"; "settings_add_email_address" = "E-mailadres toevoegen"; "settings_phone_number" = "Telefoon"; "settings_add_phone_number" = "Telefoonnummer toevoegen"; "settings_change_password" = "Wachtwoord veranderen"; "settings_night_mode" = "Nachtmodus"; -"settings_fail_to_update_profile" = "Het vernieuwen van het profiel is mislukt"; -"settings_enable_push_notif" = "Notificaties op dit apparaat"; -"settings_global_settings_info" = "Globale notificatie instellingen zijn beschikbaar op je %@ web client"; -"settings_pin_rooms_with_missed_notif" = "Pin ruimtes met gemiste notificaties"; -"settings_pin_rooms_with_unread" = "Pin ruimtes met ongelezen berichten"; -"settings_on_denied_notification" = "Notificaties worden ontkent voor %@, sta ze toe in je apparaat instellingen"; +"settings_fail_to_update_profile" = "Bijwerken van profiel is mislukt"; +"settings_enable_push_notif" = "Meldingen op dit apparaat"; +"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"; @@ -301,269 +301,491 @@ "settings_unignore_user" = "Alle berichten van %@ laten zien?"; "settings_contacts_discover_matrix_users" = "Gebruik e-mailadressen en telefoonnummers om gebruikers te vinden"; -"settings_contacts_phonebook_country" = "Telefoonboek land"; -"settings_labs_e2e_encryption" = "End-to-endbeveiliging"; -"settings_labs_e2e_encryption_prompt_message" = "Om het opzetten van de versleuteling af te ronden moet je opnieuw inloggen."; +"settings_contacts_phonebook_country" = "Land voor telefoonboek"; +"settings_labs_e2e_encryption" = "Eind-tot-eind-versleuteling"; +"settings_labs_e2e_encryption_prompt_message" = "Om het opzetten van de versleuteling af te ronden moet u zich opnieuw aanmelden."; "settings_version" = "Versie %@"; -"settings_olm_version" = "Olm Versie %@"; +"settings_olm_version" = "Olm-versie %@"; "settings_copyright" = "Copyright"; "settings_copyright_url" = "https://riot.im/copyright"; "settings_term_conditions" = "Algemene voorwaarden"; "settings_term_conditions_url" = "https://riot.im/tac_apple"; "settings_privacy_policy" = "Privacybeleid"; "settings_privacy_policy_url" = "https://riot.im/privacy"; -"settings_third_party_notices" = "Derde partij meldingen"; -"settings_send_crash_report" = "Stuur anon crash & gebruiksdata"; +"settings_third_party_notices" = "Derdepartijmeldingen"; +"settings_send_crash_report" = "Anonieme crash- en gebruiksgegevens versturen"; "settings_clear_cache" = "Cache verwijderen"; "settings_change_password" = "Wachtwoord veranderen"; "settings_old_password" = "oud wachtwoord"; "settings_new_password" = "nieuw wachtwoord"; "settings_confirm_password" = "wachtwoord bevestigen"; -"settings_fail_to_update_password" = "Wachtwoord updaten mislukt"; -"settings_password_updated" = "Je wachtwoord is geüpdatet"; +"settings_fail_to_update_password" = "Bijwerken van wachtwoord is mislukt"; +"settings_password_updated" = "Uw wachtwoord is bijgewerkt"; "settings_crypto_device_name" = "Apparaatnaam: "; -"settings_crypto_device_id" = "\nApparaat-ID: "; -"settings_crypto_device_key" = "\nApparaatsleutel: "; +"settings_crypto_device_id" = "\nApparaats-ID: "; +"settings_crypto_device_key" = "\nApparaatssleutel: "; "settings_crypto_export" = "Sleutels exporteren"; "settings_crypto_blacklist_unverified_devices" = "Alleen naar geverifieerde apparaten versleutelen"; // Room Details -"room_details_title" = "Ruimte Details"; +"room_details_title" = "Gespreksdetails"; "room_details_people" = "Leden"; "room_details_files" = "Bestanden"; "room_details_settings" = "Instellingen"; -"room_details_photo" = "Ruimte foto"; -"room_details_room_name" = "Ruimtenaam"; +"room_details_photo" = "Gespreksfoto"; +"room_details_room_name" = "Gespreksnaam"; "room_details_topic" = "Onderwerp"; "room_details_favourite_tag" = "Favoriet"; "room_details_low_priority_tag" = "Lage prioriteit"; -"room_details_mute_notifs" = "Notificaties dempen"; -"room_details_access_section" = "Wie kan tot deze ruimte toetreden?"; +"room_details_mute_notifs" = "Meldingen dempen"; +"room_details_access_section" = "Wie kan er tot dit gesprek toetreden?"; "room_details_access_section_invited_only" = "Alleen personen die zijn uitgenodigd"; -"room_details_access_section_anyone_apart_from_guest" = "Iedereen die de ruimte's link kent, behalve gasten"; -"room_details_access_section_anyone" = "Iedereen die de ruimte's link kent, inclusief gasten"; -"room_details_access_section_no_address_warning" = "De ruimte moet een adres hebben om er naar te linken"; -"room_details_access_section_directory_toggle" = "Zet deze ruimte in de ruimte catalogus"; -"room_details_history_section" = "Wie kan de geschiedenis lezen?"; +"room_details_access_section_anyone_apart_from_guest" = "Iedereen die de koppeling van het gesprek kent, behalve gasten"; +"room_details_access_section_anyone" = "Iedereen die de koppeling van het gesprek kent, inclusief gasten"; +"room_details_access_section_no_address_warning" = "Het gesprek moet een adres hebben om ernaar te verwijzen"; +"room_details_access_section_directory_toggle" = "Gesprek tonen in gesprekscatalogus"; +"room_details_history_section" = "Wie kan er de geschiedenis lezen?"; "room_details_history_section_anyone" = "Iedereen"; -"room_details_history_section_members_only" = "Alleen leden (vanaf de tijd dat deze optie geselecteerd wordt)"; +"room_details_history_section_members_only" = "Alleen leden (vanaf het moment dat deze optie geselecteerd wordt)"; "room_details_history_section_members_only_since_invited" = "Alleen leden (vanaf het moment dat ze uitgenodigd zijn)"; "room_details_history_section_members_only_since_joined" = "Alleen leden (vanaf het moment dat ze toetreden)"; -"room_details_history_section_prompt_title" = "Privacy waarschuwing"; -"room_details_history_section_prompt_msg" = "Veranderingen van wie de geschiedenis kan lezen zal alleen gelden voor toekomstige berichten in deze ruimte. De zichtbaarheid van bestaande geschiedenis zal ondervanderd blijven."; +"room_details_history_section_prompt_title" = "Privacywaarschuwing"; +"room_details_history_section_prompt_msg" = "Veranderingen aan wie de geschiedenis kan lezen zullen alleen gelden voor toekomstige berichten in dit gesprek. De zichtbaarheid van de bestaande geschiedenis zal onveranderd blijven."; "room_details_addresses_section" = "Adressen"; -"room_details_no_local_addresses" = "Deze ruimte heeft geen lokale adressen"; +"room_details_no_local_addresses" = "Dit gesprek heeft geen lokale adressen"; "room_details_new_address" = "Nieuw adres toevoegen"; -"room_details_new_address_placeholder" = "Nieuw adres toevoegen (bijv. #foo%@)"; -"room_details_addresses_invalid_address_prompt_title" = "Ongeldige naam formaat"; -"room_details_addresses_invalid_address_prompt_msg" = "%@ is geen geldig formaat voor een naam"; -"room_details_addresses_disable_main_address_prompt_title" = "Hoofdadres waarschuwing"; -"room_details_addresses_disable_main_address_prompt_msg" = "Je hebt geen hoofdadres gespecificeerd. Het standaard hoofdadres voor deze ruimte zal willekeurig gekozen worden"; +"room_details_new_address_placeholder" = "Nieuw adres toevoegen (bv. #foo%@)"; +"room_details_addresses_invalid_address_prompt_title" = "Ongeldig bijnaamformaat"; +"room_details_addresses_invalid_address_prompt_msg" = "%@ is geen geldig formaat voor een bijnaam"; +"room_details_addresses_disable_main_address_prompt_title" = "Hoofdadreswaarschuwing"; +"room_details_addresses_disable_main_address_prompt_msg" = "U heeft geen hoofdadres opgegeven. Het standaardhoofdadres voor dit gesprek zal willekeurig gekozen worden"; "room_details_banned_users_section" = "Verbannen gebruikers"; "room_details_advanced_section" = "Geavanceerd"; -"room_details_advanced_room_id" = "Ruimte ID:"; -"room_details_advanced_enable_e2e_encryption" = "Versleuteling aanzetten (waarschuwing: dit kan niet meer worden uitgezet!)"; -"room_details_advanced_e2e_encryption_enabled" = "Versleuteling staat aan in deze ruimte"; -"room_details_advanced_e2e_encryption_disabled" = "Versleuteling staat niet aan in deze ruimte."; +"room_details_advanced_room_id" = "Gespreks-ID:"; +"room_details_advanced_enable_e2e_encryption" = "Versleuteling inschakelen (let op: dit kan niet meer worden uitgeschakeld!)"; +"room_details_advanced_e2e_encryption_enabled" = "Versleuteling is ingeschakeld in dit gesprek"; +"room_details_advanced_e2e_encryption_disabled" = "Versleuteling is niet ingeschakeld in dit gesprek."; "room_details_advanced_e2e_encryption_blacklist_unverified_devices" = "Alleen naar geverifieerde apparaten versleutelen"; "room_details_advanced_e2e_encryption_prompt_message" = "End-to-endbeveiliging is experimenteel en kan onbetrouwbaar zijn.\n\nHet is beter om het nog niet met gevoelige gegevens te vertrouwen.\n\nApparaten kunnen de geschiedenis van voordat ze de ruimte betraden nog niet ontsleutelen.\n\nZodra de versleuteling aan staat kan het (voorlopig) niet worden uitgezet.\n\nVersleutelde berichten zullen nog niet zichtbaar zijn op programma's die geen versleuteling ondersteunen."; -"room_details_fail_to_update_avatar" = "Ruimte-foto vernieuwen mislukt"; -"room_details_fail_to_update_room_name" = "Ruimtenaam vernieuwen mislukt"; -"room_details_fail_to_update_topic" = "Ruimteonderwerp vernieuwen mislukt"; -"room_details_fail_to_update_room_guest_access" = "Ruimte's gast toegang vernieuwen mislukt"; -"room_details_fail_to_update_room_join_rule" = "Toetredingsregel vernieuwen mislukt"; -"room_details_fail_to_update_room_directory_visibility" = "Ruimte catalogus vernieuwen mislukt"; -"room_details_fail_to_update_history_visibility" = "Geschiedenis zichtbaarheid vernieuwen mislukt"; -"room_details_fail_to_add_room_aliases" = "Nieuwe ruimte adressen toevoegen mislukt"; -"room_details_fail_to_remove_room_aliases" = "Ruimte adressen verwijderen mislukt"; -"room_details_fail_to_update_room_canonical_alias" = "Hoofdadres vernieuwen mislukt"; -"room_details_fail_to_enable_encryption" = "Versleuteling in deze ruimte aanzetten mislukt"; -"room_details_save_changes_prompt" = "Wil je de veranderingen opslaan?"; +"room_details_fail_to_update_avatar" = "Bijwerken van gespreksfoto is mislukt"; +"room_details_fail_to_update_room_name" = "Bijwerken van gespreksnaam is mislukt"; +"room_details_fail_to_update_topic" = "Bijwerken van gespreksonderwerp is mislukt"; +"room_details_fail_to_update_room_guest_access" = "Bijwerken van gasttoegang tot gesprek is mislukt"; +"room_details_fail_to_update_room_join_rule" = "Bijwerken van toetredingsregel is mislukt"; +"room_details_fail_to_update_room_directory_visibility" = "Bijwerken van zichtbaarheid in de gesprekscatalogus is mislukt"; +"room_details_fail_to_update_history_visibility" = "Bijwerken van zichtbaarheid van geschiedenis is mislukt"; +"room_details_fail_to_add_room_aliases" = "Toevoegen van nieuwe gespreksadressen is mislukt"; +"room_details_fail_to_remove_room_aliases" = "Verwijderen van gespreksadressen is mislukt"; +"room_details_fail_to_update_room_canonical_alias" = "Bijwerken van hoofdadres is mislukt"; +"room_details_fail_to_enable_encryption" = "Inschakelen van versleuteling in dit gesprek is mislukt"; +"room_details_save_changes_prompt" = "Wilt u de wijzigingen opslaan?"; "room_details_set_main_address" = "Instellen als hoofdadres"; -"room_details_unset_main_address" = "Instellen als niet hoofdadres"; -"room_details_copy_room_id" = "Kopieer Ruimte ID"; -"room_details_copy_room_address" = "Kopieer Ruimte Adres"; -"room_details_copy_room_url" = "Kopieer Ruimte URL"; +"room_details_unset_main_address" = "Niet instellen als hoofdadres"; +"room_details_copy_room_id" = "Gespreks-ID kopiëren"; +"room_details_copy_room_address" = "Gespreksadres kopiëren"; +"room_details_copy_room_url" = "Gespreks-URL kopiëren"; // Media picker "media_picker_library" = "Bibliotheek"; "media_picker_select" = "Selecteren"; // Directory "directory_title" = "Catalogus"; "directory_server_picker_title" = "Selecteer een catalogus"; -"directory_server_all_rooms" = "Alle ruimtes op %@ server"; -"directory_server_all_native_rooms" = "Alle lokale Matrix ruimtes"; -"directory_server_type_homeserver" = "Voer een thuisserver in om de publieke ruimtes ervan te weergeven"; +"directory_server_all_rooms" = "Alle gesprekken op server %@"; +"directory_server_all_native_rooms" = "Alle lokale Matrix-gesprekken"; +"directory_server_type_homeserver" = "Voer een thuisserver in om de publieke gesprekken ervan weer te geven"; "directory_server_placeholder" = "matrix.org"; // Others "or" = "of"; -"you" = "Jij"; +"you" = "U"; "today" = "Vandaag"; "yesterday" = "Gisteren"; -"network_offline_prompt" = "Het ziet er naar uit dat de internet verbinding offline is."; -"public_room_section_title" = "Publieke ruimtes (op %@):"; -"bug_report_prompt" = "De applicatie is de vorige keer gecrasht. Wil je een crash rapport indienen?"; -"rage_shake_prompt" = "Het ziet er naar uit dat je de telefoon in frustratie schudt. Wil je een foutmelding indienen?"; -"camera_access_not_granted" = "%@ heeft geen permission om de Camera te gebruiken, verander de privacy instellingen"; +"network_offline_prompt" = "Het ziet er naar uit dat de internetverbinding offline is."; +"public_room_section_title" = "Publieke gesprekken (op %@):"; +"bug_report_prompt" = "De app is de vorige keer gecrasht. Wilt u een crashrapport indienen?"; +"rage_shake_prompt" = "Het ziet er naar uit dat u de telefoon in frustratie schudt. Wilt u een foutmelding indienen?"; +"camera_access_not_granted" = "%@ heeft geen toestemming om de camera te gebruiken, pas de privacy-instellingen aan"; "large_badge_value_k_format" = "%.1fK"; // Call "call_incoming_voice_prompt" = "Inkomende spraakoproep van %@"; -"call_incoming_video_prompt" = "Inkomende video oproep van %@"; +"call_incoming_video_prompt" = "Inkomende video-oproep van %@"; // No VoIP support "no_voip_title" = "Inkomende oproep"; -"no_voip" = "%@ belt jou %@ maar ondersteunt nog geen oproepen.\nJe kan deze notificatie negeren en vanaf een ander apparaat opnemen of de oproep afwijzen."; +"no_voip" = "%@ belt u %@ maar ondersteunt nog geen oproepen.\nU kunt deze melding negeren en vanaf een ander apparaat opnemen, of de oproep afwijzen."; // Crash report -"google_analytics_use_prompt" = "Wil je helpen met het verbeteren van %@ bij het automatisch rapporteren van crash rapporten en data van gebruik?"; +"google_analytics_use_prompt" = "Wilt u helpen met het verbeteren van %@ door anonieme crashrapporten en gebruiksstatistieken te versturen?"; // Crypto -"e2e_enabling_on_app_update" = "Riot ondersteunt nu eind-tot-eind sleuteling maar je moet opnieuw inloggen om het aan te zetten.\n\nJe kan het nu of later doen vanaf de applicatie instellingen."; -"e2e_need_log_in_again" = "Je moet opnieuw inloggen om end-to-endbeveligingssleutels te genereren voor dit apparaat en om de publieke sleutel naar de thuisserver te sturen.\nDit is eenmalig; excuses voor het ongemak."; +"e2e_enabling_on_app_update" = "Riot ondersteunt nu eind-tot-eind-versleuteling, maar u moet zich opnieuw aanmelden om het in te schakelen.\n\nU kunt dit nu of later doen vanuit de app-instellingen."; +"e2e_need_log_in_again" = "U moet zich opnieuw aanmelden om sleutels voor eind-tot-eind-versleuteling te genereren voor dit apparaat, en om de publieke sleutel naar uw thuisserver te sturen.\nDit is eenmalig; excuses voor het ongemak."; // Bug report "bug_report_title" = "Foutmelding"; -"bug_report_description" = "Beschrijf de foutmelding. Wat heb je gedaan? Wat verwachte je dat er zou gebeuren? Wat gebeurde er werkelijk?"; -"bug_crash_report_title" = "Crash Rapport"; -"bug_crash_report_description" = "Beschrijf wat je deed voor de crash:"; -"bug_report_logs_description" = "Om problemen te diagnoseren worden rapporten van deze applicatie met de foutmelding meegestuurd. Als je liever alleen bovenstaande tekst stuurt, haal dan het vinkje weg:"; -"bug_report_send_logs" = "Rapporten versturen"; +"bug_report_description" = "Beschrijf de foutmelding. Wat heeft u gedaan? Wat verwachtte u dat er zou gebeuren? Wat is er echt gebeurd?"; +"bug_crash_report_title" = "Crashrapport"; +"bug_crash_report_description" = "Beschrijf wat u deed vóór de crash:"; +"bug_report_logs_description" = "Om problemen te onderzoeken worden logboeken van deze app met de foutmelding meegestuurd. Als u liever alleen bovenstaande tekst stuurt, haal dan het vinkje weg:"; +"bug_report_send_logs" = "Logboeken versturen"; "bug_report_send_screenshot" = "Schermafbeelding versturen"; -"bug_report_progress_zipping" = "Rapporten verzamelen"; -"bug_report_progress_uploading" = "Rapport uploaden"; -"collapse" = "inkorten"; +"bug_report_progress_zipping" = "Logboeken worden verzameld"; +"bug_report_progress_uploading" = "Rapport wordt geüpload"; +"collapse" = "invouwen"; "auth_email_in_use" = "Dit e-mailadres is al in gebruik"; "auth_phone_in_use" = "Dit telefoonnummer is al in gebruik"; "auth_untrusted_id_server" = "De identiteitsserver is niet vertrouwd"; "auth_email_not_found" = "E-mail versturen mislukt: dit e-mailadres werd niet gevonden"; -"contacts_user_directory_section" = "GEBRUIKERSADRESBOEK"; -"contacts_user_directory_offline_section" = "GEBRUIKERSADRESBOEK (offline)"; +"contacts_user_directory_section" = "GEBRUIKERSCATALOGUS"; +"contacts_user_directory_offline_section" = "GEBRUIKERSCATALOGUS (offline)"; "settings_user_interface" = "GEBRUIKERSINTERFACE"; "settings_ui_language" = "Taal"; // Read Receipts -"read_receipts_list" = "Leesbewijzen Lijst"; -"receipt_status_read" = "Lees: "; +"read_receipts_list" = "Leesbevestigingslijst"; +"receipt_status_read" = "Gelezen: "; // Events formatter -"event_formatter_member_updates" = "%tu lidmaatschap aanpassingen"; -"bug_report_send" = "Stuur"; +"event_formatter_member_updates" = "%tu lidmaatschapsaanpassingen"; +"bug_report_send" = "Versturen"; "auth_home_server_placeholder" = "URL (bv. https://matrix.org)"; "auth_identity_server_placeholder" = "URL (bv. https://matrix.org)"; -"room_ongoing_conference_call_with_close" = "Lopend vergadergesprek. Neem deel als %@ of %@. %@ het."; +"room_ongoing_conference_call_with_close" = "Er is een vergadergesprek gaande. Neem deel met %@ of %@. %@ het."; "room_ongoing_conference_call_close" = "Sluiten"; -"room_conference_call_no_power" = "Je hebt permissie nodig om het vergadergesprek in deze ruimte te beheren"; +"room_conference_call_no_power" = "U heeft toestemming nodig om vergadergesprekken in dit groepsgesprek te beheren"; "settings_labs_create_conference_with_jitsi" = "Maak vergadergesprekken met jitsi"; -"call_already_displayed" = "Er is al een gesprek aan de gang."; -"call_jitsi_error" = "Het is niet gelukt om aan het vergadergesprek deel te nemen."; +"call_already_displayed" = "Er is al een oproep aan de gang."; +"call_jitsi_error" = "Deelnemen aan het vergadergesprek is mislukt."; // Widget -"widget_no_power_to_manage" = "Je hebt permissie nodig om widgets in deze ruimte te beheren"; -"widget_creation_failure" = "Het creëren van de widget is fout gegaan"; +"widget_no_power_to_manage" = "U heeft toestemming nodig om widgets in dit gesprek te beheren"; +"widget_creation_failure" = "Aanmaken van widget is mislukt"; "send_to" = "Stuur naar %@"; -"sending" = "Aan het sturen"; +"sending" = "Wordt verstuurd"; "search_in_progress" = "Aan het zoeken…"; -"room_event_action_cancel_send" = "Annuleer verzending"; -"room_event_failed_to_send" = "Verzenden is gefaald"; +"room_event_action_cancel_send" = "Verzending annuleren"; +"room_event_failed_to_send" = "Verzenden is mislukt"; "settings_calls_settings" = "OPROEPEN"; -"settings_show_decrypted_content" = "Geef ontsleutelde content weer"; +"settings_show_decrypted_content" = "Ontsleutelde inhoud tonen"; "settings_enable_callkit" = "Geïntegreerde oproepen"; -"settings_callkit_info" = "Ontvang inkomende oproepen op je toegangsscherm. Zie je Riot oproepen in de gesprekkengeschiedenis van het systeem. Als iCloud ingeschakeld is zal deze geschiedenis met Apple gedeeld worden."; +"settings_callkit_info" = "Ontvang inkomende oproepen op uw toegangsscherm. Geef uw Riot-oproepen weer in de gespreksgeschiedenis van het systeem. Als iCloud ingeschakeld is zal deze geschiedenis met Apple gedeeld worden."; "settings_ui_theme" = "Thema"; "settings_ui_theme_auto" = "Automatisch"; "settings_ui_theme_light" = "Licht"; "settings_ui_theme_dark" = "Donker"; "settings_ui_theme_black" = "Zwart"; "settings_ui_theme_picker_title" = "Selecteer een thema"; -"settings_ui_theme_picker_message" = "\"Automatisch\" gebruikt de \"Kleurweergave omkeren\" instelling van je apparaat"; +"settings_ui_theme_picker_message" = "‘Automatisch’ gebruikt de instelling ‘Kleurweergave omkeren’ van uw apparaat"; "settings_enable_rageshake" = "Schud de telefoon om een fout te rapporteren"; -"room_details_direct_chat" = "Privégesprek"; -"room_details_fail_to_update_room_direct" = "Het is niet gelukt om de direct (privé) vlag in deze ruimte te updaten"; +"room_details_direct_chat" = "Tweegesprek"; +"room_details_fail_to_update_room_direct" = "Bijwerken van de tweegespreksvlag in dit gesprek is mislukt"; "event_formatter_widget_added" = "%@ widget toegevoegd door %@"; "event_formatter_widget_removed" = "%@ widget verwijderd door %@"; -"event_formatter_jitsi_widget_added" = "VoIP vergadergesprek toegevoegd door %@"; -"event_formatter_jitsi_widget_removed" = "VoIP vergadergesprek verwijderd door %@"; +"event_formatter_jitsi_widget_added" = "VoIP-vergadergesprek toegevoegd door %@"; +"event_formatter_jitsi_widget_removed" = "VoIP-vergadergesprek verwijderd door %@"; "do_not_ask_again" = "Niet opnieuw vragen"; -"call_incoming_voice" = "Inkomende oproep..."; -"call_incoming_video" = "Inkomend videogesprek..."; +"call_incoming_voice" = "Inkomende oproep…"; +"call_incoming_video" = "Inkomende video-oproep…"; // Widget Integration Manager -"widget_integration_need_to_be_able_to_invite" = "Je moet het machtsniveau waarmee je gebruikers kan uitnodigen hebben om dat te kunnen doen."; -"widget_integration_unable_to_create" = "Niet in staat om een widget aan te maken."; -"widget_integration_failed_to_send_request" = "Het is niet gelukt om een verzoek te versturen."; -"widget_integration_room_not_recognised" = "Deze ruimte wordt niet herkend."; +"widget_integration_need_to_be_able_to_invite" = "U moet gebruikers kunnen uitnodigen om dat te kunnen doen."; +"widget_integration_unable_to_create" = "Kan widget niet aanmaken."; +"widget_integration_failed_to_send_request" = "Versturen van verzoek is mislukt."; +"widget_integration_room_not_recognised" = "Dit gesprek wordt niet herkend."; "widget_integration_positive_power_level" = "Het machtsniveau moet een positief geheel getal zijn."; -"widget_integration_must_be_in_room" = "Je zit niet in deze ruimte."; -"widget_integration_no_permission_in_room" = "Je hebt niet de permissie om dat in deze ruimte te doen."; -"widget_integration_missing_room_id" = "Het room_id mist in het verzoek."; -"widget_integration_missing_user_id" = "Het user_id mist in het verzoek."; -"widget_integration_room_not_visible" = "Room %@ is niet zichtbaar."; +"widget_integration_must_be_in_room" = "U zit niet in dit gesprek."; +"widget_integration_no_permission_in_room" = "U heeft geen toestemming om dat in dit gesprek te doen."; +"widget_integration_missing_room_id" = "room_id ontbreekt in het verzoek."; +"widget_integration_missing_user_id" = "user_id ontbreekt in het verzoek."; +"widget_integration_room_not_visible" = "Gesprek %@ is niet zichtbaar."; // Share extension -"share_extension_auth_prompt" = "Log in op de hoofdapplicatie om de content te delen"; -"share_extension_failed_to_encrypt" = "Niet gelukt om te versturen. Controleer in de hoofdapplicatie de versleutelingsinstellingen van deze ruimte"; +"share_extension_auth_prompt" = "Meld u aan in de hoofdapp om inhoud te delen"; +"share_extension_failed_to_encrypt" = "Versturen is mislukt. Controleer in de hoofdapp de versleutelingsinstellingen van dit gesprek"; // Room key request dialog -"e2e_room_key_request_title" = "Versleutelingssleutel verzoek"; -"e2e_room_key_request_message" = "Je niet geverifieerde apparaat '%@' vraagt naar versleutelingssleutels."; -"e2e_room_key_request_start_verification" = "Start verificatie..."; +"e2e_room_key_request_title" = "Versleutelingssleutelverzoek"; +"e2e_room_key_request_message" = "Uw ongeverifieerde apparaat ‘%@’ vraagt naar versleutelingssleutels."; +"e2e_room_key_request_start_verification" = "Verificatie beginnen…"; "e2e_room_key_request_share_without_verifying" = "Delen zonder te verifiëren"; -"e2e_room_key_request_ignore_request" = "Negeer verzoek"; +"e2e_room_key_request_ignore_request" = "Verzoek negeren"; "title_groups" = "Gemeenschappen"; // Groups tab "group_invite_section" = "UITNODIGINGEN"; "group_section" = "GEMEENSCHAPPEN"; -"room_do_not_have_permission_to_post" = "Je hebt geen toestemming om in deze ruimte te posten"; +"room_do_not_have_permission_to_post" = "U heeft geen toestemming om in dit gesprek te posten"; "settings_flair" = "Badge weergeven waar toegestaan"; "room_details_flair_section" = "Badge voor gemeenschappen weergeven"; -"room_details_new_flair_placeholder" = "Voeg nieuw gemeenschaps-ID toe (bv. +foo%@)"; +"room_details_new_flair_placeholder" = "Nieuwe gemeenschaps-ID toevoegen (bv. +foo%@)"; "room_details_flair_invalid_id_prompt_title" = "Ongeldig formaat"; -"room_details_flair_invalid_id_prompt_msg" = "%@ is niet een geldige identificatie voor een gemeenschap"; -"room_details_fail_to_update_room_communities" = "Niet gelukt om de gerelateerde gemeenschappen te updaten"; +"room_details_flair_invalid_id_prompt_msg" = "%@ is geen geldige identificatie voor een gemeenschap"; +"room_details_fail_to_update_room_communities" = "Bijwerken van gerelateerde gemeenschappen is mislukt"; // Group Details "group_details_title" = "Gemeenschapsdetails"; -"group_details_home" = "Home"; +"group_details_home" = "Thuis"; "group_details_people" = "Personen"; -"group_details_rooms" = "Ruimtes"; +"group_details_rooms" = "Gesprekken"; // Group Home "group_home_one_member_format" = "1 lid"; "group_home_multi_members_format" = "%tu leden"; -"group_home_one_room_format" = "1 ruimte"; -"group_home_multi_rooms_format" = "%tu ruimtes"; -"group_invitation_format" = "%@ heeft je uitgenodigd om tot deze gemeenschap toe te treden"; +"group_home_one_room_format" = "1 gesprek"; +"group_home_multi_rooms_format" = "%tu gesprekken"; +"group_invitation_format" = "%@ heeft u uitgenodigd om tot deze gemeenschap toe te treden"; // Group participants "group_participants_add_participant" = "Deelnemer toevoegen"; "group_participants_leave_prompt_title" = "Groep verlaten"; -"group_participants_leave_prompt_msg" = "Weet je zeker dat je de groep wilt verlaten?"; -"group_participants_remove_prompt_title" = "Bevestigen"; -"group_participants_remove_prompt_msg" = "Weet je zeker dat je %@ van deze groep wilt verwijderen?"; +"group_participants_leave_prompt_msg" = "Weet u zeker dat u de groep wilt verlaten?"; +"group_participants_remove_prompt_title" = "Bevestiging"; +"group_participants_remove_prompt_msg" = "Weet u zeker dat u %@ uit deze groep wilt verwijderen?"; "group_participants_invite_prompt_title" = "Bevestiging"; -"group_participants_invite_prompt_msg" = "Weet je zeker dat je %@ in deze groep wilt uitnodigen?"; +"group_participants_invite_prompt_msg" = "Weet u zeker dat u %@ in deze groep wilt uitnodigen?"; "group_participants_filter_members" = "Gemeenschapsleden filteren"; -"group_participants_invite_another_user" = "Zoeken / uitnodigen bij Gebruikers-ID of Naam"; +"group_participants_invite_another_user" = "Zoeken/uitnodigen met gebruikers-ID of naam"; "group_participants_invite_malformed_id_title" = "Uitnodigingsfout"; -"group_participants_invite_malformed_id" = "Misvormd ID. Het moet een Matrix ID zijn, zoals '@localpart:domain'"; +"group_participants_invite_malformed_id" = "Misvormde ID. Dit moet een Matrix-ID zijn, zoals ‘@gebruikersnaam:domein’"; "group_participants_invited_section" = "UITGENODIGD"; // Group rooms -"group_rooms_filter_rooms" = "Gemeenschapsruimtes filteren"; -"e2e_room_key_request_message_new_device" = "Je hebt een nieuwe apparaat '%@' toegevoegd, die een verzoek doet naar versleutelingssleutels."; +"group_rooms_filter_rooms" = "Gemeenschapsgesprekken filteren"; +"e2e_room_key_request_message_new_device" = "U heeft een nieuw apparaat ‘%@’ toegevoegd, dat vraagt naar versleutelingssleutels."; "room_event_action_kick_prompt_reason" = "Reden voor het verwijderen van deze gebruiker"; "room_event_action_ban_prompt_reason" = "Reden voor het verbannen van deze gebruiker"; // GDPR -"gdpr_consent_not_given_alert_message" = "Om de %@ thuisserver te blijven gebruiken moet je de algemeen voorwaarden lezen en daarmee akkoord gaan."; -"gdpr_consent_not_given_alert_review_now_action" = "Nu doorlezen"; +"gdpr_consent_not_given_alert_message" = "Om de %@-thuisserver te blijven gebruiken moet u de algemene voorwaarden lezen en er mee akkoord gaan."; +"gdpr_consent_not_given_alert_review_now_action" = "Nu lezen"; "room_action_send_photo_or_video" = "Foto of video versturen"; "room_action_send_sticker" = "Sticker versturen"; -"settings_deactivate_account" = "DEACTIVEER ACCOUNT"; +"settings_deactivate_account" = "ACCOUNT DEACTIVEREN"; "deactivate_account_forget_messages_information_part2_emphasize" = "Waarschuwing"; // Re-request confirmation dialog "rerequest_keys_alert_title" = "Aanvraag verstuurd"; -"rerequest_keys_alert_message" = "Start Riot alstublieft op een ander apparaat dat het bericht kan ontsleutelen zodat deze de sleutels kan sturen naar dit apparaat."; -"settings_deactivate_my_account" = "Account deactiveren"; -"event_formatter_rerequest_keys_part1_link" = "Vraag beveiligingssleutels opnieuw aan"; -"event_formatter_rerequest_keys_part2" = " van je andere apparaten."; -"widget_sticker_picker_no_stickerpacks_alert" = "Je hebt momenteel geen stickerpakketten aan staan."; -"widget_sticker_picker_no_stickerpacks_alert_add_now" = "Wil je er nu een paar toevoegen?"; +"rerequest_keys_alert_message" = "Start Riot op een ander apparaat dat het bericht kan ontsleutelen, zodat het de sleutels kan sturen naar dit apparaat."; +"settings_deactivate_my_account" = "Mijn account deactiveren"; +"event_formatter_rerequest_keys_part1_link" = "Versleutelingssleutels opnieuw aanvragen"; +"event_formatter_rerequest_keys_part2" = " van uw andere apparaten."; +"widget_sticker_picker_no_stickerpacks_alert" = "U heeft momenteel geen stickerpakketten ingeschakeld."; +"widget_sticker_picker_no_stickerpacks_alert_add_now" = "Wilt u er nu een paar toevoegen?"; "deactivate_account_title" = "Account deactiveren"; -"deactivate_account_informations_part1" = "Dit zal je account voorgoed onbruikbaar maken. Je zal niet meer in kunnen loggen en niemand anders zal met dezelfde gebruikers ID kunnen registreren. Dit zal er voor zorgen dat je account alle ruimtes verlaat waar het momenteel onderdeel van is en het verwijderd de accountgegevens van de identiteitsserver. "; +"deactivate_account_informations_part1" = "Dit zal uw account voorgoed onbruikbaar maken. U zult zich niet meer kunnen aanmelden, en niemand anders zal zich met dezelfde gebruikers-ID kunnen registreren. Dit zal er voor zorgen dat uw account alle gesprekken verlaat waar deze momenteel lid van is, en het verwijdert uw accountgegevens van de identiteitsserver. "; "deactivate_account_informations_part2_emphasize" = "Deze actie is onomkeerbaar."; -"deactivate_account_informations_part3" = "\n\nHet deactiveren van je account "; -"deactivate_account_informations_part4_emphasize" = "zal er niet standaard voor zorgen dat de berichten die je verzonden hebt vergeten worden. "; -"deactivate_account_informations_part5" = "Als je wilt dat wij de berichten vergeten, klikt alsjeblieft op het vakje hieronder.\n\nDe zichtbaarheid van berichten in Matrix is hetzelfde als in e-mail. Het vergeten van je berichten betekent dat berichten die je hebt verstuurd niet meer gedeeld worden met nieuwe of ongeregistreerde gebruikers, maar geregistreerde gebruikers die al toegang hebben tot deze berichten zullen alsnog toegang hebben tot hun eigen kopie van het bericht."; +"deactivate_account_informations_part3" = "\n\nHet deactiveren van uw account "; +"deactivate_account_informations_part4_emphasize" = "zal er niet standaard voor zorgen dat de berichten die u heeft verzonden worden vergeten. "; +"deactivate_account_informations_part5" = "Als u wilt dat wij de berichten vergeten, vinkt u het vakje hieronder aan.\n\nDe zichtbaarheid van berichten in Matrix is gelijkaardig aan e-mails. Het vergeten van uw berichten betekent dat berichten die u heeft verstuurd niet meer gedeeld worden met nieuwe of ongeregistreerde gebruikers, maar geregistreerde gebruikers die al toegang hebben tot deze berichten zullen alsnog toegang hebben tot hun eigen kopie ervan."; "deactivate_account_forget_messages_information_part1" = "Vergeet alle berichten die ik heb verstuurd wanneer mijn account gedeactiveerd is ("; -"deactivate_account_forget_messages_information_part3" = ": dit zal er voor zorgen dat toekomstige gebruikers een incompleet beeld krijgen van gesprekken)"; +"deactivate_account_forget_messages_information_part3" = ": dit zal er voor zorgen dat toekomstige gebruikers een onvolledig beeld krijgen van gesprekken)"; "deactivate_account_validate_action" = "Account deactiveren"; "deactivate_account_password_alert_title" = "Account deactiveren"; -"deactivate_account_password_alert_message" = "Voer je wachtwoord in om verder te gaan"; -"room_event_action_view_decrypted_source" = "Bekijk ontsleutelde bron"; -"room_message_reply_to_placeholder" = "Verstuur een antwoord (onversleuteld)…"; -"encrypted_room_message_reply_to_placeholder" = "Verstuur een versleuteld antwoord…"; -"room_message_reply_to_short_placeholder" = "Verstuur een antwoord…"; +"deactivate_account_password_alert_message" = "Voer uw wachtwoord in om verder te gaan"; +"room_event_action_view_decrypted_source" = "Ontsleutelde bron weergeven"; +"room_message_reply_to_placeholder" = "Stuur een antwoord (onversleuteld)…"; +"encrypted_room_message_reply_to_placeholder" = "Stuur een versleuteld antwoord…"; +"room_message_reply_to_short_placeholder" = "Stuur een antwoord…"; +// String for App Store +"store_short_description" = "Veilig en gedecentraliseerd chatten en bellen"; +"store_full_description" = "Communiceer op uw manier.\n\nEen chat-app, onder uw controle en heel flexibel. Riot laat u communiceren zoals u dat wilt. Gemaakt voor [matrix] - de standaard voor open, gedecentraliseerde communicatie.\n\nMaak een gratis account aan op matrix.org, verkrijg uw eigen server op https://modular.im, of gebruik een andere Matrix-server.\n\nWaarom zou ik voor Riot.im kiezen?\n\n• VOLLEDIGE COMMUNICATIE: maak gesprekken aan rond uw teams, uw vrienden, uw gemeenschap - hoe u maar wilt! Chat, deel bestanden, voeg widgets toe en maak stem- en video-oproepen - allemaal volledig gratis.\n\n• KRACHTIGE INTEGRATIE: gebruik Riot.im met de hulpmiddelen waarmee u vertrouwd bent. Met Riot.im kunt u zelfs chatten met gebruikers en groepen op andere chat-apps.\n\n• PRIVÉ EN VEILIG: houd uw gesprekken geheim. Eind-tot-eind-versleuteling van de bovenste plank zorgt ervoor dat uw privécommunicatie ook privé blijft.\n\n• OPEN, NIET GESLOTEN: vrije software, gebouwd op Matrix. Wees baas over uw eigen gegevens door uw eigen server te gebruiken, of te kiezen voor een andere server die u vertrouwt.\n\n• WAAR U OOK BENT: houd contact waar u ook bent met volledig gesynchroniseerde berichtgeschiedenis op al uw apparaten, en online op https://riot.im."; +"auth_login_single_sign_on" = "Aanmelden met enkele aanmelding"; +"auth_accept_policies" = "Gelieve het beleid van deze thuisserver te lezen en aanvaarden:"; +"auth_autodiscover_invalid_response" = "Ongeldig thuisserverontdekkingsantwoord"; +"room_recents_server_notice_section" = "SYSTEEMMELDINGEN"; +"room_message_unable_open_link_error_message" = "Kan de koppeling niet openen."; +"room_replacement_information" = "Dit gesprek is vervangen en is niet langer actief."; +"room_replacement_link" = "Het gesprek gaat hier verder."; +"room_predecessor_information" = "Dit gesprek is een voortzetting van een ander gesprek."; +"room_predecessor_link" = "Tik hier om oudere berichten te zien."; +"room_resource_limit_exceeded_message_contact_1" = " Gelieve "; +"room_resource_limit_exceeded_message_contact_2_link" = "contact op te nemen met uw dienstbeheerder"; +"room_resource_limit_exceeded_message_contact_3" = " om deze dienst te blijven gebruiken."; +"room_resource_usage_limit_reached_message_1_default" = "Deze thuisserver heeft één van zijn bronlimieten overschreden, dus "; +"room_resource_usage_limit_reached_message_1_monthly_active_user" = "Deze thuisserver heeft zijn limiet voor maandelijks actieve gebruikers overschreden, dus "; +"room_resource_usage_limit_reached_message_2" = "sommige gebruikers zullen zich niet kunnen aanmelden."; +"room_resource_usage_limit_reached_message_contact_3" = " om deze limiet te verhogen."; +"settings_key_backup" = "SLEUTELBACK-UP"; +"settings_labs_room_members_lazy_loading" = "Gespreksleden lui laden"; +"settings_labs_room_members_lazy_loading_error_message" = "Uw thuisserver ondersteunt het lui laden van gespreksleden nog niet. Probeer het later opnieuw."; +"settings_key_backup_info" = "Versleutelde berichten worden beveiligd met eind-tot-eind-versleuteling. Enkel de ontvanger(s) en u hebben de sleutels om deze berichten te lezen."; +"settings_key_backup_info_checking" = "Bezig met controleren…"; +"settings_key_backup_info_none" = "Uw sleutels worden niet geback-upt vanaf dit apparaat."; +"settings_key_backup_info_signout_warning" = "Verbind dit apparaat met de sleutelback-up vooraleer u zich afmeldt om sleutels die enkel op dit apparaat staat niet te verliezen."; +"settings_key_backup_info_version" = "Sleutelback-upversie: %@"; +"settings_key_backup_info_algorithm" = "Algoritme: %@"; +"settings_key_backup_info_valid" = "Dit apparaat maakt een back-up van uw sleutels."; +"settings_key_backup_info_not_valid" = "Dit apparaat maakt geen back-up van uw sleutels, maar u heeft wel een bestaande back-up waarvan u kunt herstellen, en u vanaf dan nieuwe sleutels aan kunt toevoegen."; +"settings_key_backup_info_progress" = "Back-up van %@ sleutels wordt gemaakt…"; +"settings_key_backup_info_progress_done" = "Alle sleutels zijn geback-upt"; +"settings_key_backup_info_trust_signature_unknown" = "De back-up heeft een ondertekening van apparaat met ID: %@"; +"settings_key_backup_info_trust_signature_valid" = "De back-up heeft een geldige ondertekening van dit apparaat"; +"settings_key_backup_info_trust_signature_valid_device_verified" = "De back-up heeft een geldige ondertekening van %@"; +"settings_key_backup_info_trust_signature_valid_device_unverified" = "De back-up heeft een ondertekening van %@"; +"settings_key_backup_info_trust_signature_invalid_device_verified" = "De back-up heeft een ongeldige ondertekening van %@"; +"settings_key_backup_info_trust_signature_invalid_device_unverified" = "De back-up heeft een ongeldige ondertekening van %@"; +"settings_key_backup_button_create" = "Begin sleutelback-up te gebruiken"; +"settings_key_backup_button_restore" = "Herstellen uit back-up"; +"settings_key_backup_button_delete" = "Back-up verwijderen"; +"settings_key_backup_delete_confirmation_prompt_title" = "Back-up verwijderen"; +"settings_key_backup_delete_confirmation_prompt_msg" = "Weet u het zeker? Als uw sleutels niet correct geback-upt zijn, zult u uw versleutelde berichten verliezen."; +"homeserver_connection_lost" = "Kon geen verbinding maken met de thuisserver."; +"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_button_settings" = "Instellingen"; +"e2e_key_backup_wrong_version_button_wasme" = "Ik was het"; +"key_backup_setup_title" = "Sleutelback-up"; +"key_backup_setup_skip_alert_title" = "Weet u het zeker?"; +"key_backup_setup_skip_alert_message" = "U verliest mogelijk uw versleutelde berichten als u zich afmeldt of uw apparaat verliest."; +"key_backup_setup_skip_alert_skip_action" = "Overslaan"; +"key_backup_setup_intro_title" = "Verlies nooit uw versleutelde berichten"; +"key_backup_setup_intro_info" = "Berichten in versleutelde gesprekken worden versleuteld met eind-tot-eind-beveiliging. Enkel de ontvanger(s) en u hebben de sleutels om deze berichten te lezen.\n\nMaak een veilige back-up van uw sleutels om deze niet te verliezen."; +"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_passphrase_title" = "Invoeren"; +"key_backup_setup_passphrase_passphrase_placeholder" = "Voer wachtwoord in"; +"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"; +"key_backup_setup_passphrase_confirm_passphrase_placeholder" = "Wachtwoord bevestigen"; +"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_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_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_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_from_passphrase_passphrase_title" = "Invoeren"; +"key_backup_recover_from_passphrase_passphrase_placeholder" = "Voer wachtwoord in"; +"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_part3" = "."; +"key_backup_recover_from_recovery_key_info" = "Gebruik uw herstelsleutel om uw versleutelde berichtgeschiedenis 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_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_success_info" = "Back-up hersteld!"; +"key_backup_recover_done_action" = "Klaar"; +"key_backup_setup_banner_title" = "Verlies nooit uw versleutelde berichten"; +"key_backup_setup_banner_subtitle" = "Begin sleutelback-up te gebruiken"; +"key_backup_recover_banner_title" = "Verlies nooit uw versleutelde berichten"; +"sign_out_existing_key_backup_alert_title" = "Weet u zeker dat u zich wilt afmelden?"; +"sign_out_existing_key_backup_alert_sign_out_action" = "Afmelden"; +"sign_out_non_existing_key_backup_alert_title" = "Als u zich nu afmeldt, zult u de toegang tot uw versleutelde berichten verliezen"; +"sign_out_non_existing_key_backup_alert_setup_key_backup_action" = "Begin sleutelback-up te gebruiken"; +"sign_out_non_existing_key_backup_alert_discard_key_backup_action" = "Ik wil mijn versleutelde berichten niet"; +"sign_out_non_existing_key_backup_sign_out_confirmation_alert_title" = "U zult uw versleutelde berichten verliezen"; +"sign_out_non_existing_key_backup_sign_out_confirmation_alert_message" = "U zult de toegang tot uw versleutelde berichten verliezen, tenzij u een back-up van uw sleutels maakt vooraleer u zich afmeldt."; +"sign_out_non_existing_key_backup_sign_out_confirmation_alert_sign_out_action" = "Afmelden"; +"sign_out_non_existing_key_backup_sign_out_confirmation_alert_backup_action" = "Back-up"; +"sign_out_key_backup_in_progress_alert_title" = "Sleutelback-up is bezig. Als u zich nu afmeldt zult u de toegang tot uw versleutelde berichten verliezen."; +"sign_out_key_backup_in_progress_alert_discard_key_backup_action" = "Ik wil mijn versleutelde berichten niet"; +"sign_out_key_backup_in_progress_alert_cancel_action" = "Ik wacht wel"; +"room_event_action_reply" = "Beantwoorden"; +"room_event_action_edit" = "Bewerken"; +"room_event_action_reaction_agree" = "Akkoord met %@"; +"room_event_action_reaction_disagree" = "Niet akkoord met %@"; +"room_event_action_reaction_like" = "Duim omhoog voor %@"; +"room_event_action_reaction_dislike" = "Duim omlaag voor %@"; +"room_action_reply" = "Beantwoorden"; +"settings_labs_message_reaction" = "Beantwoord berichten met emoticons"; +"settings_key_backup_button_connect" = "Dit apparaat verbinden met sleutelback-up"; +"key_backup_setup_intro_setup_connect_action_with_existing_backup" = "Dit apparaat verbinden met sleutelback-up"; +"key_backup_recover_connent_banner_subtitle" = "Dit apparaat verbinden met sleutelback-up"; +// MARK: - Device Verification +"device_verification_title" = "Apparaat verifiëren"; +"device_verification_security_advice" = "Voor een maximale beveiliging raden we aan om dit onder vier ogen te doen, of via een ander vertrouwd communicatiekanaal"; +"device_verification_cancelled" = "De andere partij heeft de verificatie geannuleerd."; +"device_verification_cancelled_by_me" = "De verificatie is geannuleerd. Reden: %@"; +"device_verification_error_cannot_load_device" = "Kan apparaatsinformatie niet laden."; +// Mark: Incoming +"device_verification_incoming_title" = "Inkomend verificatieverzoek"; +"device_verification_incoming_description_1" = "Verifieer dit apparaat om het als vertrouwd te markeren. Door de apparaten van uw partners te vertrouwen hoeft u zich nog minder zorgen te maken over het gebruik van eind-tot-eind-versleutelde berichten."; +"device_verification_incoming_description_2" = "Dit apparaat verifiëren zal het als vertrouwd markeren, en het ook aan uw gesprekspartner als vertrouwd markeren."; +// MARK: Start +"device_verification_start_title" = "Verifieer door een korte tekenreeks te vergelijken"; +"device_verification_start_wait_partner" = "Wachten op partner om te aanvaarden…"; +"device_verification_start_use_legacy" = "Verschijnt er niets? Nog niet alle cliënten bieden ondersteuning voor interactieve verificatie. Gebruik de traditionele verificatiemethode."; +"device_verification_start_verify_button" = "Verificatie beginnen"; +"device_verification_start_use_legacy_action" = "Traditionele verificatie gebruiken"; +// MARK: Verify +"device_verification_verify_title_emoji" = "Verifieer dit apparaat door te bevestigen dat de volgende emoticons op het scherm van uw gesprekspartner verschijnen"; +"device_verification_verify_title_number" = "Verifieer dit apparaat door te bevestigen dat de volgende cijfers op het scherm van uw gesprekspartner verschijnen"; +"device_verification_verify_wait_partner" = "Wachten op partner voor bevestiging…"; +// MARK: Verified +"device_verification_verified_title" = "Geverifieerd!"; +"device_verification_verified_description_1" = "U heeft dit apparaat geverifieerd."; +"device_verification_verified_description_2" = "Beveiligde berichten met deze gebruiker zijn eind-tot-eind-versleuteld en kunnen niet door derde partijen gelezen worden."; +"device_verification_verified_got_it_button" = "Ik snap het"; +// MARK: Emoji +"device_verification_emoji_dog" = "Hond"; +"device_verification_emoji_cat" = "Kat"; +"device_verification_emoji_lion" = "Leeuw"; +"device_verification_emoji_horse" = "Paard"; +"device_verification_emoji_unicorn" = "Eenhoorn"; +"device_verification_emoji_pig" = "Varken"; +"device_verification_emoji_elephant" = "Olifant"; +"device_verification_emoji_rabbit" = "Konijn"; +"device_verification_emoji_panda" = "Panda"; +"device_verification_emoji_rooster" = "Haan"; +"device_verification_emoji_penguin" = "Pinguïn"; +"device_verification_emoji_turtle" = "Schildpad"; +"device_verification_emoji_fish" = "Vis"; +"device_verification_emoji_octopus" = "Octopus"; +"device_verification_emoji_butterfly" = "Vlinder"; +"device_verification_emoji_flower" = "Bloem"; +"device_verification_emoji_tree" = "Boom"; +"device_verification_emoji_cactus" = "Cactus"; +"device_verification_emoji_mushroom" = "Paddenstoel"; +"device_verification_emoji_globe" = "Wereldbol"; +"device_verification_emoji_moon" = "Maan"; +"device_verification_emoji_cloud" = "Wolk"; +"device_verification_emoji_fire" = "Vuur"; +"device_verification_emoji_banana" = "Banaan"; +"device_verification_emoji_apple" = "Appel"; +"device_verification_emoji_strawberry" = "Aardbei"; +"device_verification_emoji_corn" = "Maïs"; +"device_verification_emoji_pizza" = "Pizza"; +"device_verification_emoji_cake" = "Taart"; +"device_verification_emoji_heart" = "Hart"; +"device_verification_emoji_smiley" = "Smiley"; +"device_verification_emoji_robot" = "Robot"; +"device_verification_emoji_hat" = "Hoed"; +"device_verification_emoji_glasses" = "Bril"; +"device_verification_emoji_spanner" = "Moersleutel"; +"device_verification_emoji_santa" = "Kerstman"; +"device_verification_emoji_thumbs up" = "Duim omhoog"; +"device_verification_emoji_umbrella" = "Paraplu"; +"device_verification_emoji_hourglass" = "Zandloper"; +"device_verification_emoji_clock" = "Klok"; +"device_verification_emoji_gift" = "Cadeau"; +"device_verification_emoji_light bulb" = "Gloeilamp"; +"device_verification_emoji_book" = "Boek"; +"device_verification_emoji_pencil" = "Potlood"; +"device_verification_emoji_paperclip" = "Paperclip"; +"device_verification_emoji_scissors" = "Schaar"; +"device_verification_emoji_padlock" = "Hangslot"; +"device_verification_emoji_key" = "Sleutel"; +"device_verification_emoji_hammer" = "Hamer"; +"device_verification_emoji_telephone" = "Telefoon"; +"device_verification_emoji_flag" = "Vlag"; +"device_verification_emoji_train" = "Trein"; +"device_verification_emoji_bicycle" = "Fiets"; +"device_verification_emoji_aeroplane" = "Vliegtuig"; +"device_verification_emoji_rocket" = "Raket"; +"device_verification_emoji_trophy" = "Trofee"; +"device_verification_emoji_ball" = "Bal"; +"device_verification_emoji_guitar" = "Gitaar"; +"device_verification_emoji_trumpet" = "Trompet"; +"device_verification_emoji_bell" = "Bel"; +"device_verification_emoji_anchor" = "Anker"; +"device_verification_emoji_headphones" = "Koptelefoon"; +"device_verification_emoji_folder" = "Map"; +"device_verification_emoji_pin" = "Speld"; +"event_formatter_message_edited_mention" = "(Bewerkt)"; +// Widget +"widget_no_integrations_server_configured" = "Geen integratieserver geconfigureerd"; +"widget_integrations_server_failed_to_connect" = "Verbinden met integratieserver mislukt"; +"device_verification_emoji_lock" = "Slot"; diff --git a/Riot/Assets/pl.lproj/Vector.strings b/Riot/Assets/pl.lproj/Vector.strings index 86071d940..4df9c322f 100644 --- a/Riot/Assets/pl.lproj/Vector.strings +++ b/Riot/Assets/pl.lproj/Vector.strings @@ -207,7 +207,7 @@ // Room Preview "room_preview_invitation_format" = "Zostałeś(-aś) zaproszony(-a) do tego pokoju przez %@"; "room_preview_subtitle" = "To jest podgląd pokoju. Interakcje zostały zablokowane."; -"room_preview_unlinked_email_warning" = "Zaproszenie zostało wysłane do %@, który nie jest powiązany z zalogowanym kontem. Możesz zalogować się z wykorzystaniem innego konta, albo dodać ten adres e-mail do swoich kontaktów."; +"room_preview_unlinked_email_warning" = "Zaproszenie zostało wysłane do %@, który nie jest powiązany z tym kontem. Możesz zalogować się z wykorzystaniem innego konta, albo dodać ten adres email do swojego konta."; "room_preview_try_join_an_unknown_room_default" = "pokój"; // Settings "settings_title" = "Ustawienia"; @@ -314,7 +314,7 @@ "group_participants_remove_prompt_title" = "Potwierdzenie"; "group_participants_invite_prompt_title" = "Potwierdzenie"; "group_participants_filter_members" = "Filtruj członków społeczności"; -"group_participants_invite_malformed_id" = "Uszkodzony ID. Czy to powinien być Matrix ID podobny do '@localpart:domain'"; +"group_participants_invite_malformed_id" = "Uszkodzony ID. Matrix ID powinien być podobny do '@localpart:domain'"; // Group rooms "group_rooms_filter_rooms" = "Filtruj pokoje społeczności"; // Read Receipts @@ -488,3 +488,25 @@ "media_picker_library" = "Biblioteka"; "large_badge_value_k_format" = "%.1fK"; "bug_crash_report_title" = "Raport o awarii"; +// String for App Store +"store_short_description" = "Bezpieczny, zdecentralizowany czat/VoIP"; +"store_full_description" = "Komunikuj się, po swojemu.\n\nAplikacja do czatowania, pod Twoją kontrolą i całkowicie elastyczna. Riot pozwala Ci komunikować się tak, jak chcesz. Stworzona dla [matrixa] - otwartego standardu, zdecentralizowanej komunikacji.\n\nZałóż darmowe konto na matrix.org, załatw swój własny serwer na https://modular.im lub skorzystaj z innego serwera Matrix.\n\nDlaczego warto wybrać Riot.im?\n\nPEŁNA KOMUNIKACJA: Zbuduj pokoje wokół swoich zespołów, przyjaciół, społeczności - jak chcesz! Czat, udostępnianie plików, dodawanie widgetów i wykonywanie połączeń głosowych i wideo - wszystko to za darmo.\n\n\nPOTĘŻNA INTEGRACJA: Użyj Riot.im z narzędziami, które znasz i kochasz. Dzięki Riot.im możesz nawet rozmawiać z użytkownikami i grupami z innymi aplikacjami do czatowania.\n\nPRYWATNY I BEZPIECZNY: Trzymaj swoje rozmowy w tajemnicy. Najnowocześniejsze szyfrowanie typu end-to-end zapewnia prywatną komunikację.\n\nOTWARTY, NIE ZAMKNIĘTY: Open source i zbudowany na Matrixie. Miej swoje dane pod kontrolą poprzez hosting własnego serwera lub wybranie serwera, któremu ufasz.\n\nGDZIEKOLWIEK JESTEŚ: Bądź w kontakcie gdziekolwiek jesteś, dzięki w pełni zsynchronizowanej historii wiadomości na wszystkich Twoich urządzeniach i online na https://riot.im."; +"room_creation_make_public_prompt_msg" = "Jesteś pewien, że chcesz zrobić ten czat publiczny? Każdy może czytać Twoje wiadomości i dołączyć do czatu."; +"directory_search_fail" = "Nie udało się pobrać danych"; +"contacts_address_book_permission_required" = "Uprawnienie jest wymagane żeby uzyskać dostęp do kontaktów lokalnych"; +"room_participants_invite_malformed_id_title" = "Błąd Zaproszenia"; +"room_participants_action_ban" = "Zbanuj z tego pokoju"; +"room_unsent_messages_unknown_devices_notification" = "Wiadomość nie została wysłana z powodu obecności nieznanych urządzeń. %@ lub %@@ teraz?"; +"room_event_action_ban_prompt_reason" = "Powód, dla którego zbanowano tego użytkownika"; +"room_event_action_reply" = "Odpowiedz"; +"room_event_action_edit" = "Edytuj"; +"room_action_reply" = "Odpowiedz"; +"room_preview_try_join_an_unknown_room" = "Próbujesz uzyskać dostęp do %@. Czy chciałbyś się przyłączyć, aby wziąć udział w dyskusji?"; +"account_logout_all" = "Wyloguj wszystkie konta"; +"settings_flair" = "Pokazuj wyznacznik społeczności gdzie jest to zezwolone"; +"settings_key_backup" = "KOPIA ZAPASOWA KLUCZY"; +"settings_enable_push_notif" = "Powiadomienia na tym urządzeniu"; +"settings_global_settings_info" = "Globalne ustawienia powiadomień są dostępne na Twoim kliencie internetowym %@"; +"settings_on_denied_notification" = "Powiadomienia są odrzucane w %@, proszę zezwól na nie w ustawieniach urządzenia"; +"settings_callkit_info" = "Odbieraj połączenia przychodzące na ekranie blokady. Zobacz swoje połęczenia Riot 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\" urządzenia"; diff --git a/Riot/Assets/pt_BR.lproj/Localizable.strings b/Riot/Assets/pt_BR.lproj/Localizable.strings index 767bb6cc3..fdfd667eb 100644 --- a/Riot/Assets/pt_BR.lproj/Localizable.strings +++ b/Riot/Assets/pt_BR.lproj/Localizable.strings @@ -1,5 +1,5 @@ /* New message from a specific person, not referencing a room */ -"MSG_FROM_USER" = "Mensagem de %@"; +"MSG_FROM_USER" = "%@ enviou uma mensagem"; /* New message from a specific person in a named room */ "MSG_FROM_USER_IN_ROOM" = "%@ enviou mensagem em %@"; /* New message from a specific person, not referencing a room. Content included. */ @@ -11,7 +11,7 @@ /* New action message from a specific person in a named room. */ "ACTION_FROM_USER_IN_ROOM" = "%@ : * %@ %@"; /* New action message from a specific person, not referencing a room. */ -"IMAGE_FROM_USER" = "%@ enviou a você uma imagem %@"; +"IMAGE_FROM_USER" = "%@ enviou uma imagem %@"; /* New action message from a specific person in a named room. */ "IMAGE_FROM_USER_IN_ROOM" = "%@ enviou uma imagem %@ na sala %@"; /* A single unread message in a room */ @@ -50,3 +50,7 @@ "VOICE_CONF_NAMED_FROM_USER" = "Vídeo chamada coletiva de %@: '%@'"; /* Incoming named video conference invite from a specific person */ "VIDEO_CONF_NAMED_FROM_USER" = "Vídeo-chamada coletiva de %@: '%@'"; +/* Message title for a specific person in a named room */ +"MSG_FROM_USER_IN_ROOM_TITLE" = "%@ em %@"; +/* Sticker from a specific person, not referencing a room. */ +"STICKER_FROM_USER" = "%@ enviou uma figurinha"; diff --git a/Riot/Assets/ro.lproj/Vector.strings b/Riot/Assets/ro.lproj/Vector.strings new file mode 100644 index 000000000..8d9ff4221 --- /dev/null +++ b/Riot/Assets/ro.lproj/Vector.strings @@ -0,0 +1,11 @@ +// String for App Store +"store_short_description" = "// String pe App Store"; +"title_favourites" = "Favorite"; +"title_people" = "Oamei"; +"title_rooms" = "Camere"; +"title_groups" = "Comunități"; +"warning" = "Greșeală"; +// Actions +"view" = "Văzut"; +"next" = "Următorul"; +"back" = "Înapoi"; diff --git a/Riot/Assets/ru.lproj/InfoPlist.strings b/Riot/Assets/ru.lproj/InfoPlist.strings index 6f2335ecf..7922a0459 100644 --- a/Riot/Assets/ru.lproj/InfoPlist.strings +++ b/Riot/Assets/ru.lproj/InfoPlist.strings @@ -3,3 +3,4 @@ "NSPhotoLibraryUsageDescription" = "Галерея используется для отправки фотографий и видео."; "NSMicrophoneUsageDescription" = "Микрофон используется при съемке видео и выполнении звонков."; "NSContactsUsageDescription" = "Для отображения контактов, использующих Riot или Matrix, мы можем отправить адреса email и номера телефонов из вашей адресной книги на ваш сервер идентификации Matrix. Новый Vector не хранит эти данные и не использует их для каких-либо других целей. Для получения дополнительной информации, пожалуйста, ознакомьтесь с Политикой конфиденциальности в настройках приложения."; +"NSCalendarsUsageDescription" = "Ознакомьтесь со своими запланированными встречами в приложении."; diff --git a/Riot/Assets/ru.lproj/Localizable.strings b/Riot/Assets/ru.lproj/Localizable.strings index 7e194a1e8..040de742b 100644 --- a/Riot/Assets/ru.lproj/Localizable.strings +++ b/Riot/Assets/ru.lproj/Localizable.strings @@ -11,7 +11,7 @@ /* New action message from a specific person in a named room. */ "ACTION_FROM_USER_IN_ROOM" = "%@: * %@ %@"; /* New action message from a specific person, not referencing a room. */ -"IMAGE_FROM_USER" = "%@ отправил(а) вам фото %@"; +"IMAGE_FROM_USER" = "%@ отправил(а) фото %@"; /* New action message from a specific person in a named room. */ "IMAGE_FROM_USER_IN_ROOM" = "%@ отправил(а) фото %@ в %@"; /* Multiple unread messages in a room */ @@ -50,3 +50,7 @@ "SINGLE_UNREAD_IN_ROOM" = "Вы получили сообщение в %@"; /* A single unread message */ "SINGLE_UNREAD" = "Вы получили сообщение"; +/* Message title for a specific person in a named room */ +"MSG_FROM_USER_IN_ROOM_TITLE" = "%@ в %@"; +/* Sticker from a specific person, not referencing a room. */ +"STICKER_FROM_USER" = "%@ отправил(а) стикер"; diff --git a/Riot/Assets/ru.lproj/Vector.strings b/Riot/Assets/ru.lproj/Vector.strings index 2bf3f0c7f..2bfa610c9 100644 --- a/Riot/Assets/ru.lproj/Vector.strings +++ b/Riot/Assets/ru.lproj/Vector.strings @@ -53,7 +53,7 @@ "auth_invalid_email" = "Это не похоже на допустимый адрес электронной почты"; "auth_invalid_phone" = "Это не похоже на действительный номер телефона"; "auth_missing_password" = "Пароль отсутствует"; -"auth_add_email_message" = "Добавьте адрес электронной почты в свою учетную запись, чтобы другим пользователям было проще вас найти. Также это поможет вам при необходимости восстановить пароль."; +"auth_add_email_message" = "Добавьте адрес электронной почты в свою учетную запись, чтобы другим пользователям было проще вас найти. Также это поможет вам при восстановлении паролья."; "auth_add_phone_message" = "Добавьте номер телефона в свою учетную запись, чтобы другим пользователям было проще вас найти."; "auth_add_email_phone_message" = "Добавьте адрес электронной почты и/или номер телефона в свою учетную запись, чтобы другим пользователям было проще вас найти. Адрес электронной почты также позволит вам сбросить пароль."; "auth_add_email_and_phone_message" = "Добавьте адрес электронной почты и номер телефона в свою учетную запись, чтобы другим пользователям было проще вас найти. Адрес электронной почты также позволит вам сбросить пароль."; @@ -675,3 +675,13 @@ "store_full_description" = "Приложение для чата, под вашим контролем и полностью гибкое. Райот позволяет вам общаться так, как вы хотите. Сделано на [matrix] — стандарт для открытого, децентрализованного общения.\n\nПолучите бесплатную учетную запись на matrix.org, собственный сервер по адресу https://modular.im или используйте другой сервер Matrix.\n\nПочему стоит выбрать Riot.im?\n\n• ПОЛНАЯ СВЯЗЬ: создавайте комнаты для команд, друзей, сообщест — как хотите! Общайтесь, обменивайтесь файлами, добавляйте виджеты и совершайте голосовые и видеозвонки — и все это бесплатно.\n\n• МОЩНЫЕ ИНТЕГРАЦИИ: Используйте Riot.im с инструментами, которые знаете и любите. С Riot.im вы можете даже общаться с пользователями и группами других приложений.\n\n• ЧАСТНЫЕ И БЕЗОПАСНЫЕ: держите ваши разговоры в тайне. Современное сквозное шифрование гарантирует, что частное общение остается частным.\n\n• ОТКРЫТО, НЕ ЗАКРЫТО: Исходный код открыт, построено на Matrix. Владейте данными, используя собственный сервер или выбирайте тот, которому доверяете.\n\n• Везде, где вы находитесь: оставайтесь на связи, где бы вы ни находились, с полностью синхронизированной историей сообщений на всех ваших устройствах и в Интернете по адресу https://riot.im."; "auth_login_single_sign_on" = "Вход с SSO"; "room_message_unable_open_link_error_message" = "Невозможно открыть ссылку."; +"auth_autodiscover_invalid_response" = "Неверный ответ обнаружения сервера"; +"room_event_action_reply" = "Ответ"; +"room_event_action_edit" = "Редактировать"; +"room_event_action_reaction_agree" = "%@ согласен"; +"room_event_action_reaction_disagree" = "%@ несогласен"; +"room_event_action_reaction_like" = "%@ согласен"; +"room_event_action_reaction_dislike" = "%@ не согласны"; +"room_action_reply" = "Ответ"; +"settings_labs_message_reaction" = "Реагировать на сообщения с Emoji"; +"settings_key_backup_button_connect" = "Подключите это устройство к ключу резервного копирования"; diff --git a/Riot/Assets/sq.lproj/InfoPlist.strings b/Riot/Assets/sq.lproj/InfoPlist.strings index ede69c765..680c8399d 100644 --- a/Riot/Assets/sq.lproj/InfoPlist.strings +++ b/Riot/Assets/sq.lproj/InfoPlist.strings @@ -2,4 +2,4 @@ "NSCameraUsageDescription" = "Kamera përdoret për të bërë foto dhe regjistruar video, dhe për të bërë thirrje video."; "NSPhotoLibraryUsageDescription" = "Fototeka përdoret për të dërguar foto dhe video."; "NSMicrophoneUsageDescription" = "Mikrofoni përdoret për të regjistruar video, dhe për të bërë thirrje."; -"NSContactsUsageDescription" = "Që të mund t’ju shfaqim se cilët prej kontakteve tuaj përdorin tashmë Riot ose Matrix, mund të dërgojmë adresat email dhe numrat e telefonave nga libri juaj i adresave te Shërbyesi Matrix i Identiteteve. Vektori i ri nuk i depoziton këto të dhëna apo t’i përdorë për ndonjë qëllim tjetër. Për më tepër të dhëna, shihni faqen e rregullave të privatësisë, te rregullimet e aplikacionit."; +"NSContactsUsageDescription" = "Që të mund t’ju shfaqim se cilët prej kontakteve tuaj përdorin tashmë Riot ose Matrix, mund të dërgojmë adresat email dhe numrat e telefonave nga libri juaj i adresave te Shërbyesi Matrix i Identiteteve. Vektori i ri nuk i depoziton këto të dhëna, as i përdor për ndonjë qëllim tjetër. Për më tepër të dhëna, shihni faqen e rregullave të privatësisë, te rregullimet e aplikacionit."; diff --git a/Riot/Assets/sq.lproj/Localizable.strings b/Riot/Assets/sq.lproj/Localizable.strings index 6587a8ce3..d060cfcd8 100644 --- a/Riot/Assets/sq.lproj/Localizable.strings +++ b/Riot/Assets/sq.lproj/Localizable.strings @@ -1,5 +1,5 @@ /* New message from a specific person, not referencing a room */ -"MSG_FROM_USER" = "Mesazh prej %@"; +"MSG_FROM_USER" = "%@ dërgoi një mesazh"; /* New message from a specific person in a named room */ "MSG_FROM_USER_IN_ROOM" = "%@ postuar te %@"; /* New message from a specific person, not referencing a room. Content included. */ @@ -11,7 +11,7 @@ /* New action message from a specific person in a named room. */ "ACTION_FROM_USER_IN_ROOM" = "%@: * %@ %@"; /* New action message from a specific person, not referencing a room. */ -"IMAGE_FROM_USER" = "%@ ju dërgoi një foto %@"; +"IMAGE_FROM_USER" = "%@ dërgoi një foto %@"; /* New action message from a specific person in a named room. */ "IMAGE_FROM_USER_IN_ROOM" = "%@ postoi një foto %@ në %@"; /* A single unread message in a room */ @@ -50,3 +50,7 @@ "VOICE_CONF_NAMED_FROM_USER" = "Thirrje grupi nga %@: '%@'"; /* Incoming named video conference invite from a specific person */ "VIDEO_CONF_NAMED_FROM_USER" = "Thirrje video në grup nga %@: '%@'"; +/* Message title for a specific person in a named room */ +"MSG_FROM_USER_IN_ROOM_TITLE" = "%@ në %@"; +/* Sticker from a specific person, not referencing a room. */ +"STICKER_FROM_USER" = "%@ dërgoi një ngjitës"; diff --git a/Riot/Assets/sq.lproj/Vector.strings b/Riot/Assets/sq.lproj/Vector.strings index 04ea808f3..f1ba0864e 100644 --- a/Riot/Assets/sq.lproj/Vector.strings +++ b/Riot/Assets/sq.lproj/Vector.strings @@ -47,8 +47,8 @@ "auth_phone_placeholder" = "Numër telefoni"; "auth_repeat_password_placeholder" = "Rijepeni fjalëkalimin"; "auth_repeat_new_password_placeholder" = "Ripohoni fjalëkalimin tuaj të ri"; -"auth_home_server_placeholder" = "URL (p.sh. https://matrix.org)"; -"auth_identity_server_placeholder" = "URL (p.sh. https://matrix.org)"; +"auth_home_server_placeholder" = "URL (p.sh., https://matrix.org)"; +"auth_identity_server_placeholder" = "URL (p.sh., https://matrix.org)"; "auth_invalid_login_param" = "Emër përdoruesi dhe/ose fjalëkalim i pasaktë"; "auth_invalid_password" = "Fjalëkalim shumë i shkurtër (min 6)"; "auth_invalid_email" = "Kjo s’duket si adresë email e vlefshme"; @@ -83,7 +83,7 @@ "room_creation_keep_private" = "Mbaje private"; "room_creation_make_private" = "Bëje private"; // Room recents -"room_recents_directory_section" = "DREJTORI DHOME"; +"room_recents_directory_section" = "DREJTORI DHOMASH"; "room_recents_directory_section_network" = "Rrjet"; "room_recents_people_section" = "PERSONA"; "room_recents_conversations_section" = "DHOMA"; @@ -116,16 +116,16 @@ "directory_cell_title" = "Shfletoni në drejtori"; "directory_cell_description" = "Dhoma %tu"; "directory_search_results_title" = "Shfletoni përfundime drejtorie"; -"directory_search_results" = "u gjetën %tu përfundime për %@"; -"directory_search_results_more_than" = "u gjetën >%tu përfundime për %@"; +"directory_search_results" = "U gjetën %tu përfundime për %@"; +"directory_search_results_more_than" = ">%tu përfundime të gjetura për %@"; "directory_searching_title" = "Po kërkohet në drejtori…"; "directory_search_fail" = "S’u arrit të sillen të dhëna"; // Contacts "contacts_address_book_section" = "KONTAKTE VENDORE"; "contacts_address_book_matrix_users_toggle" = "Vetëm përdorues të Matrix-it"; "contacts_address_book_no_contact" = "S’ka kontakte vendore"; -"contacts_user_directory_section" = "DREJTORI PËRDORUESI"; -"contacts_user_directory_offline_section" = "DREJTORI PËRDORUESI (jashtë linje)"; +"contacts_user_directory_section" = "DREJTORI PËRDORUESISH"; +"contacts_user_directory_offline_section" = "DREJTORI PËRDORUESISH (jashtë linje)"; // Chat participants "room_participants_title" = "Pjesëmarrës"; "room_participants_add_participant" = "Shtoni pjesmarrës"; @@ -138,7 +138,7 @@ "room_participants_invite_prompt_title" = "Ripohim"; "room_participants_invite_prompt_msg" = "Jeni i sigurt se doni të ftohet %@ te kjo fjalosje?"; "room_participants_filter_room_members" = "Filtroni anëtarë dhome"; -"room_participants_invite_malformed_id_title" = "Gabim Ftim"; +"room_participants_invite_malformed_id_title" = "Gabim Ftimi"; "room_participants_invited_section" = "TË FTUAR"; "room_participants_online" = "Në linjë"; "room_participants_offline" = "Jo në linjë"; @@ -213,7 +213,7 @@ "settings_config_no_build_info" = "S’ka të dhëna montimi"; "settings_mark_all_as_read" = "Vëru shenjë krejt mesazheve si të lexuar"; "settings_report_bug" = "Njoftoni një të metë"; -"settings_config_home_server" = "Shërbyesi home është %@"; +"settings_config_home_server" = "Shërbyesi Home është %@"; "settings_config_identity_server" = "Shërbyes identitetesh është %@"; "settings_config_user_id" = "I futur si %@"; "settings_user_settings" = "RREGULLIME PËRDORUESI"; @@ -319,7 +319,7 @@ // Group Home "group_home_one_member_format" = "1 anëtar"; "group_home_one_room_format" = "1 dhomë"; -"group_invitation_format" = "%s ju ftoi të bëheni pjesë e kësaj bashkësie"; +"group_invitation_format" = "%@ ju ftoi të bëheni pjesë e kësaj bashkësie"; // Group participants "group_participants_add_participant" = "Shtoni pjesmarrës"; "group_participants_leave_prompt_title" = "Braktiseni grupin"; @@ -351,7 +351,7 @@ "you" = "Ju"; "today" = "Sot"; "yesterday" = "Dje"; -"network_offline_prompt" = "Lidhja Internet duket se s’funksionin."; +"network_offline_prompt" = "Lidhja Internet duket se s’funksionon."; "public_room_section_title" = "Dhoma Publike (at %@):"; "do_not_ask_again" = "Mos pyet sërish"; // Call @@ -390,7 +390,7 @@ "on" = "On"; "auth_add_phone_message" = "Shtoni te llogaria juaj një numër telefoni, për t’u dhënë mundësinë përdoruesve t’ju zbulojnë."; "auth_msisdn_validation_message" = "Kemi dërguar një SMS me një kod aktivizimi. Ju lutemi, jepeni këtë kod më poshtë."; -"auth_recaptcha_message" = "Ky Shërbyes Home do të donte të sigurohej se s’jeni robot"; +"auth_recaptcha_message" = "Ky shërbyes Home do të donte të sigurohej se s’jeni robot"; "auth_reset_password_error_unauthorized" = "Verifikimi i adresës email dështoi: sigurohuni se keni klikuar lidhjen te email-i"; "auth_reset_password_error_not_found" = "Adresa juaj email s’duket të jetë e përshoqëruar me ID Matrix në këtë shërbyes Home."; "title_favourites" = "Të parapëlqyer"; @@ -460,7 +460,7 @@ "call_jitsi_error" = "S'u arrit të hyhej në thirrjen për konferencë."; "widget_creation_failure" = "Krijimi i widget-it dështoi"; "widget_sticker_picker_no_stickerpacks_alert_add_now" = "Të shtohen ca tani?"; -"widget_integration_room_not_visible" = "Dhoma %s s’është e dukshme."; +"widget_integration_room_not_visible" = "Dhoma %@ s’është e dukshme."; "gdpr_consent_not_given_alert_review_now_action" = "Shqyrtojini tani"; "deactivate_account_title" = "Çaktivizoni Llogarinë"; "deactivate_account_informations_part2_emphasize" = "Ky veprim është i paprapakthyeshëm."; @@ -473,7 +473,7 @@ "rerequest_keys_alert_title" = "Kërkesa u Dërgua"; "auth_reset_password_success_message" = "Fjalëkalimi juaj u ri caktua.\n\nËshtë bërë dalja juaj nga llogaria në krejt pajisjet dhe s’do të merrni më njoftime push. Për riaktivizim të njoftimeve, ribëni hyrjen në çdo pajisje."; "room_creation_make_public_prompt_msg" = "Jeni i sigurt se doni ta bëni publike këtë fjalosje? Në një të tillë, mesazhet tuaj mund t’i lexojë cilido dhe mund të hyjë në bisedë."; -"room_creation_wait_for_creation" = "Po krijohet tashmë një dhomë, Ju lutemi, prisni."; +"room_creation_wait_for_creation" = "Po krijohet tashmë një dhomë. Ju lutemi, prisni."; "contacts_address_book_permission_required" = "Lypset leje për hyrje në kontaktet vendore"; "contacts_address_book_permission_denied" = "S’e lejuat Riot-i të hyjë në kontaktet tuaja vendore"; "room_participants_remove_third_party_invite_msg" = "Heqja e ftesave nga palë të treta nuk mbulohet ende, derisa të kihet API"; @@ -492,7 +492,7 @@ "room_resource_usage_limit_reached_message_1_monthly_active_user" = "Ky shërbyes home ka tejkaluar kufirin Përdorues Aktivë Mujorë, ndaj "; "unknown_devices_alert" = "Kjo dhomë përmban pajisje të panjohura që s’janë verifikuar.\nKjo do të thotë se nuk ka garanci se pajisjet u përkasin përdoruesve që pretendojnë se u përkasin.\nPërpara se të vazhdoni, këshillojmë që të kaloni në proces verifikimi çdo pajisje, por mund të ridërgoni mesazhin pa verifikuar gjë, nëse parapëlqeni kështu."; "room_preview_subtitle" = "Kjo është një paraparje e kësaj dhome. Ndërveprimet në dhomë janë çaktivizuar."; -"room_preview_unlinked_email_warning" = "Kjo ftesë i qe dërguar %@, që s’është i përshoqëruar me këtë llogari. Mund të doni të hyni me një llogari tjetër, ose ta shtoni këtë email te kjo llogari."; +"room_preview_unlinked_email_warning" = "Kjo ftesë i qe dërguar %@, që s’është i përshoqëruar me këtë llogari. Mund të doni të hyni me një llogari tjetër, ose ta shtoni këtë email te llogaria juaj."; "room_preview_try_join_an_unknown_room" = "Po përpiqeni të hyni në %@. Do të donit të bëheni pjesë, që të mundni të merrni pjesë te diskutimi?"; "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"; @@ -500,7 +500,7 @@ "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 Riot 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"; +"settings_ui_theme_picker_message" = "\"Auto\" përdor rregullimet \"Përmbysi Ngjyrat\" të pajisjes tuaj"; "settings_contacts_discover_matrix_users" = "Përdorni email-e dhe numra telefoni për të gjetur përdorues"; "settings_labs_e2e_encryption_prompt_message" = "Që të përfundohet rregullimi i fshehtëzimit duhet të ribëni hyrjen në llogari."; "settings_labs_room_members_lazy_loading" = "Lazy-load anëtarët e dhomave"; @@ -541,14 +541,14 @@ "e2e_room_key_request_message" = "Pajisja juaj e paverifikuar '%@' po kërkon kyçe fshehtëzimi."; // GDPR "gdpr_consent_not_given_alert_message" = "Që të vazhdohet të përdoret shërbyesi home %@, duhet të shqyrtoni dhe pajtoheni me termat dhe kushtet."; -"deactivate_account_informations_part1" = "Kjo do ta bëjë përgjithnjë të papërdorshëm llogarinë tuaj. Nuk do të jeni në gjendje të bëni hyrjen në të, dhe askush s’do të jetë në gjendje të rrregjistrohet me të njëjtën ID përdoruesi. Kjo do të sjellë daljen e llogarisë tuaj nga krejt dhomat në të cilat po merr pjesë, dhe do të heqë nga shërbyesi i identitetit tuaj hollësitë e llogarisë tuaj. "; +"deactivate_account_informations_part1" = "Kjo do ta bëjë përgjithnjë të papërdorshëm llogarinë tuaj. Nuk do të jeni në gjendje të bëni hyrjen në të, dhe askush s’do të jetë në gjendje të regjistrohet me të njëjtën ID përdoruesi. Kjo do të sjellë daljen e llogarisë tuaj nga krejt dhomat në të cilat po merr pjesë, dhe do të heqë nga shërbyesi i identitetit tuaj hollësitë e llogarisë tuaj. "; "deactivate_account_informations_part4_emphasize" = "si parazgjedhje, nuk na bën të harrojmë mesazhet që keni dërguar. "; "deactivate_account_informations_part5" = "Nëse do të donit që të harrohen mesazhet tuaj, ju lutemi, i vini shenjë kutizës më poshtë\n\nDukshmëria e mesazheve në Matrix është e ngjashme me atë në email. Harrimi i mesazheve nga ana jonë do të thotë që mesazhet që keni dërguar nuk do të ndahen me çfarëdo përdoruesi të ri apo të paregjistruar, por përdoruesit e regjistruar, që kanë tashmë hyrje në këto mesazhe, do të kenë prapëseprapë hyrje te kopja e tyre."; "deactivate_account_forget_messages_information_part1" = "Të lutem, harro krejt mesazhet që kamë dërguar, kur të çaktivizohet llogaria ime ("; "deactivate_account_forget_messages_information_part3" = ": kjo do të bëjë që përdorues të ardhshëm të shohin një pamje jo të plotë të bisedave)"; "rerequest_keys_alert_message" = "Ju lutemi, niseni Riot-in në një tjetër pajisje që mundet të shfshehtëzojë mesazhin, që kështu të mund të dërgojë kyçet te kjo pajisje."; "room_event_action_redact" = "Hiqe"; -"e2e_need_log_in_again" = "Që të prodhohen kyçe fshehtëzimi skaj-më-skaj për këtë pajisje, lypset të ribëni hyrjen dhe të parashtroni kyçin publik te shërbyesi juaj homë.\nKjo duhet vetëm një herë; na ndjeni për belanë."; +"e2e_need_log_in_again" = "Që të prodhohen kyçe fshehtëzimi skaj-më-skaj për këtë pajisje, lypset të ribëni hyrjen dhe të parashtroni kyçin publik te shërbyesi juaj Home.\nKjo duhet vetëm një herë; na ndjeni për belanë."; "room_action_send_sticker" = "Dërgoni ngjitës"; "settings_flair" = "Shfaq simbole, kur lejohet"; "room_details_flair_section" = "Shfaq simbole për bashkësi"; @@ -559,7 +559,7 @@ "settings_key_backup_info_version" = "Version Kopjeruajtjeje Kyçesh: %@"; "settings_key_backup_info_algorithm" = "Algoritëm: %@"; "settings_key_backup_info_valid" = "Kjo pajisje po bën kopjeruajtje të kyçeve tuaja."; -"settings_key_backup_info_not_valid" = "Kjo pajisje nuk po bën kopjeruajtje të kyçeve tuaja."; +"settings_key_backup_info_not_valid" = "Kjo pajisje nuk po bën kopjeruajtje të kyçeve tuaja, por keni një kopjeruajtje ekzistuese që mund ta përdorni për rimarrje dhe ta shtoni më tej."; "settings_key_backup_info_progress" = "Po kopjeruhen kyçet për %@…"; "settings_key_backup_info_progress_done" = "U kopjeruajtën krejt kyçet"; "settings_key_backup_info_not_trusted_from_verifiable_device_fix_action" = "Për të përdorur Rikthim Mesazhesh të Sigurt në këtë pajisje, verifikoni tani %@."; @@ -621,7 +621,7 @@ "key_backup_recover_banner_title_part1" = "Xhironi Rikthim Mesazhesh të Sigurt"; "key_backup_recover_banner_title_part2" = " që të lexoni historik mesazhesh të fshehtëzuar në këtë pajisje"; "settings_key_backup_info" = "Mesazhet e fshehtëzuar sigurohen me fshehtëzim skaj-më-skaj. Vetëm ju dhe marrësi(t) kanë kyçet për të lexuar këto mesazhe."; -"settings_key_backup_info_signout_warning" = "Kopjeruajini kyçet tuaj, përpara se të dilni, që të shmangni humbjen e tyre."; +"settings_key_backup_info_signout_warning" = "Lidheni këtë pajisje me kopjeruajtje kyçesh, përpara se të dilni, që të shmangni humbje të çfarëdo kyçi që mund të gjendet vetëm në këtë pajisje."; "settings_key_backup_button_use" = "Përdor kopjeruajtje kyçesh"; "key_backup_setup_intro_setup_action_without_existing_backup" = "Fillo të përdorësh Kopjeruajtje Kyçesh"; "key_backup_setup_intro_setup_action_with_existing_backup" = "Përdor Kopjeruajtje Kyçesh"; @@ -667,6 +667,114 @@ "key_backup_setup_intro_manual_export_action" = "Eksportoni kyçe dorazi"; // String for App Store "store_short_description" = "Fjalosje /VoIP e siguruar, e decentralizuar"; -"store_full_description" = "Komunikoni sipas rrugës tuaj.\n\nNjë aplikacion fjalosjesh, nën kontrollin tuaj dhe krejtësisht i zhdërvjellët. Riot-i ju lejon të komunikoni si doni. Krijuar për [matrix] - standardi për për komunikim të hapur, të decentralizuar.\n\nMerrni një llogari matrix.org falas, merrni shërbyesin tuaj te https://modular.im, ose përdorni një tjetër shërbyes Matrix.\n\nPse të zgjidhet Riot.im?\n\nKOMUNIKIM I PLOTË: Krijoni dhoma rreth ekipeve tuaja, shokëve tuaj, bashkësisë tuaj - rreth kujtdo që doni! Bisedoni, shkëmbeni kartela, shtoni widget-e dhe bëni thirrje audio dhe video - gjithçka falas.\n\nINTEGRIME TË FUQISHME: Përdoreni Riot.im me mjetet që njihni dhe doni. Me Riot.im mundeni madje të bisedoni me përdorues dhe grupe nën aplikacione të tjera fjalosjesh.\n\nPRIVAT DHE I SIGURUAR: Mbajini bisedat tuaja të fshehta. Fshehtëzimi skaj-më-skaj i fjalës së fundit garanton që komunikimi privat mbetet privat.\n\nI HAPUR, JO I MBYLLUR: Me burim të hapur dhe i ngritur mbi Matrix. Jini zot i të dhënave tuaja, duke strehuar shërbyesin tuaj, ose duke përzgjedhur një të cilit i besoni.\n\nKUDO QOFSHI: Mbani lidhjet, kudo qofshi, me historik plotësisht të njëkohësuar mesazhesh në krejt pajisjet tuaja dhe në internet, te https://riot.im."; +"store_full_description" = "Komunikoni sipas rrugës tuaj.\n\nNjë aplikacion fjalosjesh, nën kontrollin tuaj dhe krejtësisht i zhdërvjellët. Riot-i ju lejon të komunikoni si doni. Krijuar për [matrix] - standardi për komunikim të hapur, të decentralizuar.\n\nMerrni një llogari matrix.org falas, merrni shërbyesin tuaj te https://modular.im, ose përdorni një tjetër shërbyes Matrix.\n\nPse të zgjidhet Riot.im?\n\nKOMUNIKIM I PLOTË: Krijoni dhoma rreth ekipeve tuaja, shokëve tuaj, bashkësisë tuaj - rreth kujtdo që doni! Bisedoni, shkëmbeni kartela, shtoni widget-e dhe bëni thirrje audio dhe video - gjithçka falas.\n\nINTEGRIME TË FUQISHME: Përdoreni Riot.im me mjetet që njihni dhe doni. Me Riot.im mundeni madje të bisedoni me përdorues dhe grupe nën aplikacione të tjera fjalosjesh.\n\nPRIVAT DHE I SIGURUAR: Mbajini bisedat tuaja të fshehta. Fshehtëzimi skaj-më-skaj i fjalës së fundit garanton që komunikimi privat mbetet privat.\n\nI HAPUR, JO I MBYLLUR: Me burim të hapur dhe i ngritur mbi Matrix. Jini zot i të dhënave tuaja, duke strehuar shërbyesin tuaj, ose duke përzgjedhur një të cilit i besoni.\n\nKUDO QOFSHI: Mbani lidhjet, kudo qofshi, me historik plotësisht të njëkohësuar mesazhesh në krejt pajisjet tuaja dhe në internet, te https://riot.im."; "auth_login_single_sign_on" = "Hyni përmes një hyrjeje njëshe"; "room_message_unable_open_link_error_message" = "S’arrihet të hapet lidhja."; +"auth_autodiscover_invalid_response" = "Përgjigje e pavlefshme pikasjeje shërbyesi Home"; +"room_details_fail_to_update_room_direct" = "S’arrihet të përditësohet shenja si e drejtpërdrejtë e kësaj dhome"; +"room_event_action_reply" = "Përgjigjuni"; +"room_event_action_edit" = "Përpunoni"; +"room_event_action_reaction_agree" = "Pajtohem me %@"; +"room_event_action_reaction_disagree" = "S’pajtohem me %@"; +"room_event_action_reaction_like" = "Pëlqejeni %@"; +"room_event_action_reaction_dislike" = "Shpëlqejeni %@"; +"room_action_reply" = "Përgjigjuni"; +"settings_labs_message_reaction" = "Reagoni ndaj mesazhesh me emoji"; +"settings_key_backup_button_connect" = "Lidhe këtë pajisje me Kopjeruajtje Kyçesh"; +"key_backup_setup_intro_setup_connect_action_with_existing_backup" = "Lidhe këtë pajisje me Kopjeruajtje Kyçesh"; +"key_backup_recover_connent_banner_subtitle" = "Lidhe këtë pajisje me Kopjeruajtje Kyçesh"; +// MARK: - Device Verification +"device_verification_title" = "Verifikoni pajisje"; +"device_verification_security_advice" = "Për siguri maksimale, këshillojmë ta bëni këtë në prani të vetë personit, ose të përdorni një tjetër kanal të besuar komunikimesh"; +"device_verification_cancelled" = "Pala tjetër e anuloi verifikimin."; +"device_verification_cancelled_by_me" = "Verifikimi u anulua. Arsye: %@"; +"device_verification_error_cannot_load_device" = "S’ngarkohen dot të dhëna pajisje."; +// Mark: Incoming +"device_verification_incoming_title" = "Kërkesë Verifikimi e Ardhur"; +"device_verification_incoming_description_1" = "Që t’i vihet shenjë si e besuar, verifikojeni këtë pajisje. Besimi i pajisjeve të partnerëve ju jep ca qetësi më tepër kur përdoren mesazhe të fshehtëzuar skaj-më-skaj."; +"device_verification_incoming_description_2" = "Verifikimi i kësaj pajisje do ta shënojë atë të besuar, dhe tuajën si të besuar te partneri."; +// MARK: Start +"device_verification_start_title" = "Verifikoje duke krahasuar një varg të shkurtër teksti"; +"device_verification_start_wait_partner" = "Po pritet pranimi nga partneri…"; +"device_verification_start_use_legacy" = "S’duket gjë? Jo të tërë klientët mbulojnë verifikim ndërveprues ende. Përdorni verifikimin në stil të vjetër."; +"device_verification_start_verify_button" = "Po Verifikohet"; +"device_verification_start_use_legacy_action" = "Përdor Verifikim të Dikurshëm"; +// MARK: Verify +"device_verification_verify_title_emoji" = "Verifikojeni këtë pajisje duke ripohuar shfaqjen e emoji-t vijues në skenën e partnerit"; +"device_verification_verify_title_number" = "Verifikojeni këtë pajisje duke ripohuar se numri vijues shfaqet në ekranin e partnerit"; +"device_verification_verify_wait_partner" = "Po pritet ripohimi nga partneri…"; +// MARK: Verified +"device_verification_verified_title" = "U verifikua!"; +"device_verification_verified_description_1" = "E verifikuat me sukses këtë pajisje."; +"device_verification_verified_description_2" = "Mesazhet e sigurt me këtë përdorues fshehtëzohen skaj-më-skaj dhe janë të palexueshëm nga palë të treta."; +"device_verification_verified_got_it_button" = "E mora vesh"; +// MARK: Emoji +"device_verification_emoji_dog" = "Qen"; +"device_verification_emoji_cat" = "Mace"; +"device_verification_emoji_lion" = "Luan"; +"device_verification_emoji_horse" = "Kalë"; +"device_verification_emoji_unicorn" = "Njëbrirësh"; +"device_verification_emoji_pig" = "Derr"; +"device_verification_emoji_elephant" = "Elefant"; +"device_verification_emoji_rabbit" = "Lepur"; +"device_verification_emoji_panda" = "Panda"; +"device_verification_emoji_rooster" = "Këndes"; +"device_verification_emoji_penguin" = "Pinguin"; +"device_verification_emoji_turtle" = "Breshkë"; +"device_verification_emoji_fish" = "Peshk"; +"device_verification_emoji_octopus" = "Oktapod"; +"device_verification_emoji_butterfly" = "Flutur"; +"device_verification_emoji_flower" = "Lule"; +"device_verification_emoji_tree" = "Pemë"; +"device_verification_emoji_cactus" = "Kaktus"; +"device_verification_emoji_mushroom" = "Kërpudhë"; +"device_verification_emoji_globe" = "Rruzull"; +"device_verification_emoji_moon" = "Hëna"; +"device_verification_emoji_cloud" = "Re"; +"device_verification_emoji_fire" = "Zjarr"; +"device_verification_emoji_banana" = "Banane"; +"device_verification_emoji_apple" = "Mollë"; +"device_verification_emoji_strawberry" = "Luleshtrydhe"; +"device_verification_emoji_corn" = "Misër"; +"device_verification_emoji_pizza" = "Picë"; +"device_verification_emoji_cake" = "Tortë"; +"device_verification_emoji_heart" = "Zemër"; +"device_verification_emoji_smiley" = "Emotikon"; +"device_verification_emoji_robot" = "Robot"; +"device_verification_emoji_hat" = "Kapë"; +"device_verification_emoji_glasses" = "Syze"; +"device_verification_emoji_santa" = "Babagjyshi i Vitit të Ri"; +"device_verification_emoji_umbrella" = "Ombrellë"; +"device_verification_emoji_hourglass" = "Klepsidër"; +"device_verification_emoji_clock" = "Klasë"; +"device_verification_emoji_gift" = "Dhuratë"; +"device_verification_emoji_light bulb" = "Llambë"; +"device_verification_emoji_book" = "Libër"; +"device_verification_emoji_pencil" = "Laps"; +"device_verification_emoji_paperclip" = "Kapëse"; +"device_verification_emoji_scissors" = "Gërshërë"; +"device_verification_emoji_padlock" = "Dry"; +"device_verification_emoji_key" = "Kyç"; +"device_verification_emoji_hammer" = "Çekiç"; +"device_verification_emoji_telephone" = "Telefon"; +"device_verification_emoji_flag" = "Flamur"; +"device_verification_emoji_train" = "Tren"; +"device_verification_emoji_bicycle" = "Biçikletë"; +"device_verification_emoji_aeroplane" = "Avion"; +"device_verification_emoji_rocket" = "Raketë"; +"device_verification_emoji_trophy" = "Trofe"; +"device_verification_emoji_ball" = "Top"; +"device_verification_emoji_guitar" = "Kitarë"; +"device_verification_emoji_trumpet" = "Trombë"; +"device_verification_emoji_bell" = "Kambanë"; +"device_verification_emoji_anchor" = "Spirancë"; +"device_verification_emoji_headphones" = "Kufje"; +"device_verification_emoji_folder" = "Dosje"; +"event_formatter_message_edited_mention" = "(U përpunua)"; +// Widget +"widget_no_integrations_server_configured" = "S’ka të formësuar shërbyes integrimesh"; +"widget_integrations_server_failed_to_connect" = "S’u arrit të lidhej me shërbyes integrimesh"; +"close" = "Mbylle"; +"room_event_action_reaction_show_all" = "Shfaqi krejt"; +"room_event_action_reaction_show_less" = "Shfaq më pak"; +"room_message_edits_history_title" = "Përpunime mesazhi"; diff --git a/Riot/Assets/third_party_licenses.html b/Riot/Assets/third_party_licenses.html index b1f0e155b..5f067b63d 100644 --- a/Riot/Assets/third_party_licenses.html +++ b/Riot/Assets/third_party_licenses.html @@ -21,6 +21,9 @@ href="https://github.com/matrix-org/matrix-ios-kit.git">https://github.com/matrix-org/matrix-ios-kit.git)

The Matrix reusable UI library for iOS based on MatrixSDK.

Copyright (c) 2014-2016 OpenMarket Ltd. +
Copyright (c) 2016-2017 Vector Creations Ltd +
Copyright (c) 2018-2019 New Vector Ltd +
Copyright (c) 2019 The Matrix.org Foundation C.I.C

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this work except in compliance with the License. You may obtain a copy of the License at:

https://www.apache.org/licenses/LICENSE-2.0 @@ -33,6 +36,9 @@

The iOS SDK to build apps compatible with Matrix (https://www.matrix.org).

Copyright (c) 2014-2016 OpenMarket Ltd. +
Copyright (c) 2016-2017 Vector Creations Ltd +
Copyright (c) 2018-2019 New Vector Ltd +
Copyright (c) 2019 The Matrix.org Foundation C.I.C

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this work except in compliance with the License. You may obtain a copy of the License at:

https://www.apache.org/licenses/LICENSE-2.0 @@ -131,951 +137,965 @@

  • - WebRTC-iOS (WebRTC iOS framework) -

    webrtc

    -
    -            Copyright (c) 2011, The WebRTC project authors. All rights reserved.
    -
    -            Redistribution and use in source and binary forms, with or without
    -            modification, are permitted provided that the following conditions are
    -            met:
    -
    -            * Redistributions of source code must retain the above copyright
    -            notice, this list of conditions and the following disclaimer.
    -
    -            * Redistributions in binary form must reproduce the above copyright
    -            notice, this list of conditions and the following disclaimer in
    -            the documentation and/or other materials provided with the
    -            distribution.
    -
    -            * Neither the name of Google nor the names of its contributors may
    -            be used to endorse or promote products derived from this software
    -            without specific prior written permission.
    -
    -            THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
    -            "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
    -            LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
    -            A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
    -            HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
    -            SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
    -            LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
    -            DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
    -            THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
    -            (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
    -            OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    -
    -            This source tree contains third party source code which is governed by third
    -            party licenses. Paths to the files and associated licenses are collected here.
    -
    -            Files governed by third party licenses:
    -            common_audio/fft4g.c
    -            common_audio/signal_processing/spl_sqrt_floor.c
    -            common_audio/signal_processing/spl_sqrt_floor_arm.S
    -            modules/audio_coding/codecs/g711/main/source/g711.c
    -            modules/audio_coding/codecs/g711/main/source/g711.h
    -            modules/audio_coding/codecs/g722/main/source/g722_decode.c
    -            modules/audio_coding/codecs/g722/main/source/g722_enc_dec.h
    -            modules/audio_coding/codecs/g722/main/source/g722_encode.c
    -            modules/audio_coding/codecs/isac/main/source/fft.c
    -            modules/audio_device/mac/portaudio/pa_memorybarrier.h
    -            modules/audio_device/mac/portaudio/pa_ringbuffer.c
    -            modules/audio_device/mac/portaudio/pa_ringbuffer.h
    -            modules/audio_processing/aec/aec_rdft.c
    -            system_wrappers/source/condition_variable_event_win.cc
    -            system_wrappers/source/set_thread_name_win.h
    -            system_wrappers/source/spreadsortlib/constants.hpp
    -            system_wrappers/source/spreadsortlib/spreadsort.hpp
    -
    -            Individual licenses for each file:
    -            -------------------------------------------------------------------------------
    -            Files:
    -            common_audio/signal_processing/spl_sqrt_floor.c
    -            common_audio/signal_processing/spl_sqrt_floor_arm.S
    -
    -            License:
    -            /*
    -            * Written by Wilco Dijkstra, 1996. The following email exchange establishes the
    -            * license.
    -            *
    -            * From: Wilco Dijkstra <Wilco.Dijkstra@ntlworld.com>
    -            * Date: Fri, Jun 24, 2011 at 3:20 AM
    -            * Subject: Re: sqrt routine
    -            * To: Kevin Ma <kma@google.com>
    -            * Hi Kevin,
    -            * Thanks for asking. Those routines are public domain (originally posted to
    -            * comp.sys.arm a long time ago), so you can use them freely for any purpose.
    -            * Cheers,
    -            * Wilco
    -            *
    -            * ----- Original Message -----
    -            * From: "Kevin Ma" <kma@google.com>
    -            * To: <Wilco.Dijkstra@ntlworld.com>
    -            * Sent: Thursday, June 23, 2011 11:44 PM
    -            * Subject: Fwd: sqrt routine
    -            * Hi Wilco,
    -            * I saw your sqrt routine from several web sites, including
    -            * http://www.finesse.demon.co.uk/steven/sqrt.html.
    -            * Just wonder if there's any copyright information with your Successive
    -            * approximation routines, or if I can freely use it for any purpose.
    -            * Thanks.
    -            * Kevin
    -            */
    -            -------------------------------------------------------------------------------
    -            Files:
    -            modules/audio_coding/codecs/g711/main/source/g711.c
    -            modules/audio_coding/codecs/g711/main/source/g711.h
    -
    -            License:
    -            /*
    -            * SpanDSP - a series of DSP components for telephony
    -            *
    -            * g711.h - In line A-law and u-law conversion routines
    -            *
    -            * Written by Steve Underwood <steveu@coppice.org>
    -            *
    -            * Copyright (C) 2001 Steve Underwood
    -            *
    -            *  Despite my general liking of the GPL, I place this code in the
    -            *  public domain for the benefit of all mankind - even the slimy
    -            *  ones who might try to proprietize my work and use it to my
    -            *  detriment.
    -            */
    -            -------------------------------------------------------------------------------
    -            Files:
    -            modules/audio_coding/codecs/g722/main/source/g722_decode.c
    -            modules/audio_coding/codecs/g722/main/source/g722_enc_dec.h
    -            modules/audio_coding/codecs/g722/main/source/g722_encode.c
    -
    -            License:
    -            /*
    -            * SpanDSP - a series of DSP components for telephony
    -            *
    -            * g722_decode.c - The ITU G.722 codec, decode part.
    -            *
    -            * Written by Steve Underwood <steveu@coppice.org>
    -            *
    -            * Copyright (C) 2005 Steve Underwood
    -            *
    -            *  Despite my general liking of the GPL, I place my own contributions
    -            *  to this code in the public domain for the benefit of all mankind -
    -            *  even the slimy ones who might try to proprietize my work and use it
    -            *  to my detriment.
    -            *
    -            * Based in part on a single channel G.722 codec which is:
    -            *
    -            * Copyright (c) CMU 1993
    -            * Computer Science, Speech Group
    -            * Chengxiang Lu and Alex Hauptmann
    -            */
    -            -------------------------------------------------------------------------------
    -            Files:
    -            modules/audio_coding/codecs/isac/main/source/fft.c
    -
    -            License:
    -            /*
    -            * Copyright(c)1995,97 Mark Olesen <olesen@me.QueensU.CA>
    -            *    Queen's Univ at Kingston (Canada)
    -            *
    -            * Permission to use, copy, modify, and distribute this software for
    -            * any purpose without fee is hereby granted, provided that this
    -            * entire notice is included in all copies of any software which is
    -            * or includes a copy or modification of this software and in all
    -            * copies of the supporting documentation for such software.
    -            *
    -            * THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR
    -            * IMPLIED WARRANTY.  IN PARTICULAR, NEITHER THE AUTHOR NOR QUEEN'S
    -            * UNIVERSITY AT KINGSTON MAKES ANY REPRESENTATION OR WARRANTY OF ANY
    -            * KIND CONCERNING THE MERCHANTABILITY OF THIS SOFTWARE OR ITS
    -            * FITNESS FOR ANY PARTICULAR PURPOSE.
    -            *
    -            * All of which is to say that you can do what you like with this
    -            * source code provided you don't try to sell it as your own and you
    -            * include an unaltered copy of this message (including the
    -            * copyright).
    -            *
    -            * It is also implicitly understood that bug fixes and improvements
    -            * should make their way back to the general Internet community so
    -            * that everyone benefits.
    -            */
    -            -------------------------------------------------------------------------------
    -            Files:
    -            modules/audio_device/mac/portaudio/pa_memorybarrier.h
    -            modules/audio_device/mac/portaudio/pa_ringbuffer.c
    -            modules/audio_device/mac/portaudio/pa_ringbuffer.h
    -
    -            License:
    -            /*
    -            * $Id: pa_memorybarrier.h 1240 2007-07-17 13:05:07Z bjornroche $
    -            * Portable Audio I/O Library
    -            * Memory barrier utilities
    -            *
    -            * Author: Bjorn Roche, XO Audio, LLC
    -            *
    -            * This program uses the PortAudio Portable Audio Library.
    -            * For more information see: http://www.portaudio.com
    -            * Copyright (c) 1999-2000 Ross Bencina and Phil Burk
    -            *
    -            * 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.
    -            */
    -
    -            /*
    -            * The text above constitutes the entire PortAudio license; however,
    -            * the PortAudio community also makes the following non-binding requests:
    -            *
    -            * Any person wishing to distribute modifications to the Software is
    -            * requested to send the modifications to the original developer so that
    -            * they can be incorporated into the canonical version. It is also
    -            * requested that these non-binding requests be included along with the
    -            * license above.
    -            */
    -
    -            /*
    -            * $Id: pa_ringbuffer.c 1421 2009-11-18 16:09:05Z bjornroche $
    -            * Portable Audio I/O Library
    -            * Ring Buffer utility.
    -            *
    -            * Author: Phil Burk, http://www.softsynth.com
    -            * modified for SMP safety on Mac OS X by Bjorn Roche
    -            * modified for SMP safety on Linux by Leland Lucius
    -            * also, allowed for const where possible
    -            * modified for multiple-byte-sized data elements by Sven Fischer
    -            *
    -            * Note that this is safe only for a single-thread reader and a
    -            * single-thread writer.
    -            *
    -            * This program uses the PortAudio Portable Audio Library.
    -            * For more information see: http://www.portaudio.com
    -            * Copyright (c) 1999-2000 Ross Bencina and Phil Burk
    -            *
    -            * 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.
    -            */
    -
    -            /*
    -            * The text above constitutes the entire PortAudio license; however,
    -            * the PortAudio community also makes the following non-binding requests:
    -            *
    -            * Any person wishing to distribute modifications to the Software is
    -            * requested to send the modifications to the original developer so that
    -            * they can be incorporated into the canonical version. It is also
    -            * requested that these non-binding requests be included along with the
    -            * license above.
    -            */
    -            -------------------------------------------------------------------------------
    -            Files:
    -            common_audio/fft4g.c
    -            modules/audio_processing/aec/aec_rdft.c
    -
    -            License:
    -            /*
    -            * http://www.kurims.kyoto-u.ac.jp/~ooura/fft.html
    -            * Copyright Takuya OOURA, 1996-2001
    -            *
    -            * You may use, copy, modify and distribute this code for any purpose (include
    -            * commercial use) and without fee. Please refer to this package when you modify
    -            * this code.
    -            */
    -            -------------------------------------------------------------------------------
    -            Files:
    -            system_wrappers/source/condition_variable_event_win.cc
    -
    -            Source:
    -            http://www1.cse.wustl.edu/~schmidt/ACE-copying.html
    -
    -            License:
    -            Copyright and Licensing Information for ACE(TM), TAO(TM), CIAO(TM), DAnCE(TM),
    -            and CoSMIC(TM)
    -
    -            ACE(TM), TAO(TM), CIAO(TM), DAnCE>(TM), and CoSMIC(TM) (henceforth referred to
    -            as "DOC software") are copyrighted by Douglas C. Schmidt and his research
    -            group at Washington University, University of California, Irvine, and
    -            Vanderbilt University, Copyright (c) 1993-2009, all rights reserved. Since DOC
    -            software is open-source, freely available software, you are free to use,
    -            modify, copy, and distribute--perpetually and irrevocably--the DOC software
    -            source code and object code produced from the source, as well as copy and
    -            distribute modified versions of this software. You must, however, include this
    -            copyright statement along with any code built using DOC software that you
    -            release. No copyright statement needs to be provided if you just ship binary
    -            executables of your software products.
    -            You can use DOC software in commercial and/or binary software releases and are
    -            under no obligation to redistribute any of your source code that is built
    -            using DOC software. Note, however, that you may not misappropriate the DOC
    -            software code, such as copyrighting it yourself or claiming authorship of the
    -            DOC software code, in a way that will prevent DOC software from being
    -            distributed freely using an open-source development model. You needn't inform
    -            anyone that you're using DOC software in your software, though we encourage
    -            you to let us know so we can promote your project in the DOC software success
    -            stories.
    -
    -            The ACE, TAO, CIAO, DAnCE, and CoSMIC web sites are maintained by the DOC
    -            Group at the Institute for Software Integrated Systems (ISIS) and the Center
    -            for Distributed Object Computing of Washington University, St. Louis for the
    -            development of open-source software as part of the open-source software
    -            community. Submissions are provided by the submitter ``as is'' with no
    -            warranties whatsoever, including any warranty of merchantability,
    -            noninfringement of third party intellectual property, or fitness for any
    -            particular purpose. In no event shall the submitter be liable for any direct,
    -            indirect, special, exemplary, punitive, or consequential damages, including
    -            without limitation, lost profits, even if advised of the possibility of such
    -            damages. Likewise, DOC software is provided as is with no warranties of any
    -            kind, including the warranties of design, merchantability, and fitness for a
    -            particular purpose, noninfringement, or arising from a course of dealing,
    -            usage or trade practice. Washington University, UC Irvine, Vanderbilt
    -            University, their employees, and students shall have no liability with respect
    -            to the infringement of copyrights, trade secrets or any patents by DOC
    -            software or any part thereof. Moreover, in no event will Washington
    -            University, UC Irvine, or Vanderbilt University, their employees, or students
    -            be liable for any lost revenue or profits or other special, indirect and
    -            consequential damages.
    -
    -            DOC software is provided with no support and without any obligation on the
    -            part of Washington University, UC Irvine, Vanderbilt University, their
    -            employees, or students to assist in its use, correction, modification, or
    -            enhancement. A number of companies around the world provide commercial support
    -            for DOC software, however. DOC software is Y2K-compliant, as long as the
    -            underlying OS platform is Y2K-compliant. Likewise, DOC software is compliant
    -            with the new US daylight savings rule passed by Congress as "The Energy Policy
    -            Act of 2005," which established new daylight savings times (DST) rules for the
    -            United States that expand DST as of March 2007. Since DOC software obtains
    -            time/date and calendaring information from operating systems users will not be
    -            affected by the new DST rules as long as they upgrade their operating systems
    -            accordingly.
    -
    -            The names ACE(TM), TAO(TM), CIAO(TM), DAnCE(TM), CoSMIC(TM), Washington
    -            University, UC Irvine, and Vanderbilt University, may not be used to endorse
    -            or promote products or services derived from this source without express
    -            written permission from Washington University, UC Irvine, or Vanderbilt
    -            University. This license grants no permission to call products or services
    -            derived from this source ACE(TM), TAO(TM), CIAO(TM), DAnCE(TM), or CoSMIC(TM),
    -            nor does it grant permission for the name Washington University, UC Irvine, or
    -            Vanderbilt University to appear in their names.
    -            -------------------------------------------------------------------------------
    -            Files:
    -            system_wrappers/source/set_thread_name_win.h
    -
    -            Source:
    -            http://msdn.microsoft.com/en-us/cc300389.aspx#P
    -
    -            License:
    -            This license governs use of code marked as “sample” or “example” available on
    -            this web site without a license agreement, as provided under the section above
    -            titled “NOTICE SPECIFIC TO SOFTWARE AVAILABLE ON THIS WEB SITE.” If you use
    -            such code (the “software”), you accept this license. If you do not accept the
    -            license, do not use the software.
    -
    -            1. Definitions
    -
    -            The terms “reproduce,” “reproduction,” “derivative works,” and “distribution”
    -            have the same meaning here as under U.S. copyright law.
    -
    -            A “contribution” is the original software, or any additions or changes to the
    -            software.
    -
    -            A “contributor” is any person that distributes its contribution under this
    -            license.
    -
    -            “Licensed patents” are a contributor’s patent claims that read directly on its
    -            contribution.
    -
    -            2. Grant of Rights
    -
    -            (A) Copyright Grant - Subject to the terms of this license, including the
    -            license conditions and limitations in section 3, each contributor grants you a
    -            non-exclusive, worldwide, royalty-free copyright license to reproduce its
    -            contribution, prepare derivative works of its contribution, and distribute its
    -            contribution or any derivative works that you create.
    -
    -            (B) Patent Grant - Subject to the terms of this license, including the license
    -            conditions and limitations in section 3, each contributor grants you a
    -            non-exclusive, worldwide, royalty-free license under its licensed patents to
    -            make, have made, use, sell, offer for sale, import, and/or otherwise dispose
    -            of its contribution in the software or derivative works of the contribution in
    -            the software.
    -
    -            3. Conditions and Limitations
    -
    -            (A) No Trademark License- This license does not grant you rights to use any
    -            contributors’ name, logo, or trademarks.
    -
    -            (B) If you bring a patent claim against any contributor over patents that you
    -            claim are infringed by the software, your patent license from such contributor
    -            to the software ends automatically.
    -
    -            (C) If you distribute any portion of the software, you must retain all
    -            copyright, patent, trademark, and attribution notices that are present in the
    -            software.
    -
    -            (D) If you distribute any portion of the software in source code form, you may
    -            do so only under this license by including a complete copy of this license
    -            with your distribution. If you distribute any portion of the software in
    -            compiled or object code form, you may only do so under a license that complies
    -            with this license.
    -
    -            (E) The software is licensed “as-is.” You bear the risk of using it. The
    -            contributors give no express warranties, guarantees or conditions. You may
    -            have additional consumer rights under your local laws which this license
    -            cannot change. To the extent permitted under your local laws, the contributors
    -            exclude the implied warranties of merchantability, fitness for a particular
    -            purpose and non-infringement.
    -
    -            (F) Platform Limitation - The licenses granted in sections 2(A) and 2(B)
    -            extend only to the software or derivative works that you create that run on a
    -            Microsoft Windows operating system product.
    -            -------------------------------------------------------------------------------
    -            Files:
    -            system_wrappers/source/spreadsortlib/constants.hpp
    -            system_wrappers/source/spreadsortlib/spreadsort.hpp
    -
    -            License:
    -            /*Boost Software License - Version 1.0 - August 17th, 2003
    -
    -            Permission is hereby granted, free of charge, to any person or organization
    -            obtaining a copy of the software and accompanying documentation covered by
    -            this license (the "Software") to use, reproduce, display, distribute,
    -            execute, and transmit the Software, and to prepare derivative works of the
    -            Software, and to permit third-parties to whom the Software is furnished to
    -            do so, all subject to the following:
    -
    -            The copyright notices in the Software and this entire statement, including
    -            the above license grant, this restriction and the following disclaimer,
    -            must be included in all copies of the Software, in whole or in part, and
    -            all derivative works of the Software, unless such copies or derivative
    -            works are solely in the form of machine-executable object code generated by
    -            a source language processor.
    -
    -            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, TITLE AND NON-INFRINGEMENT. IN NO EVENT
    -            SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
    -            FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
    -            ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    -            DEALINGS IN THE SOFTWARE.*/
    -
    -        
    -

    boringssl

    -
    -            BoringSSL is a fork of OpenSSL. As such, large parts of it fall under OpenSSL
    -            licensing. Files that are completely new have a Google copyright and an ISC
    -            license. This license is reproduced at the bottom of this file.
    -
    -            Contributors to BoringSSL are required to follow the CLA rules for Chromium:
    -            https://cla.developers.google.com/clas
    -
    -            Some files from Intel are under yet another license, which is also included
    -            underneath.
    -
    -            The OpenSSL toolkit stays under a dual license, i.e. both the conditions of the
    -            OpenSSL License and the original SSLeay license apply to the toolkit. See below
    -            for the actual license texts. Actually both licenses are BSD-style Open Source
    -            licenses. In case of any license issues related to OpenSSL please contact
    -            openssl-core@openssl.org.
    -
    -            The following are Google-internal bug numbers where explicit permission from
    -            some authors is recorded for use of their work. (This is purely for our own
    -            record keeping.)
    -            27287199
    -            27287880
    -            27287883
    -
    -            OpenSSL License
    -            ---------------
    -
    -            /* ====================================================================
    -            * Copyright (c) 1998-2011 The OpenSSL Project.  All rights reserved.
    -            *
    -            * Redistribution and use in source and binary forms, with or without
    -            * modification, are permitted provided that the following conditions
    -            * are met:
    -            *
    -            * 1. Redistributions of source code must retain the above copyright
    -            *    notice, this list of conditions and the following disclaimer.
    -            *
    -            * 2. Redistributions in binary form must reproduce the above copyright
    -            *    notice, this list of conditions and the following disclaimer in
    -            *    the documentation and/or other materials provided with the
    -            *    distribution.
    -            *
    -            * 3. All advertising materials mentioning features or use of this
    -            *    software must display the following acknowledgment:
    -            *    "This product includes software developed by the OpenSSL Project
    -            *    for use in the OpenSSL Toolkit. (http://www.openssl.org/)"
    -            *
    -            * 4. The names "OpenSSL Toolkit" and "OpenSSL Project" must not be used to
    -            *    endorse or promote products derived from this software without
    -            *    prior written permission. For written permission, please contact
    -            *    openssl-core@openssl.org.
    -            *
    -            * 5. Products derived from this software may not be called "OpenSSL"
    -            *    nor may "OpenSSL" appear in their names without prior written
    -            *    permission of the OpenSSL Project.
    -            *
    -            * 6. Redistributions of any form whatsoever must retain the following
    -            *    acknowledgment:
    -            *    "This product includes software developed by the OpenSSL Project
    -            *    for use in the OpenSSL Toolkit (http://www.openssl.org/)"
    -            *
    -            * THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY
    -            * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
    -            * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
    -            * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE OpenSSL PROJECT OR
    -            * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
    -            * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
    -            * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
    -            * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
    -            * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
    -            * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
    -            * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
    -            * OF THE POSSIBILITY OF SUCH DAMAGE.
    -            * ====================================================================
    -            *
    -            * This product includes cryptographic software written by Eric Young
    -            * (eay@cryptsoft.com).  This product includes software written by Tim
    -            * Hudson (tjh@cryptsoft.com).
    -            *
    -            */
    -
    -            Original SSLeay License
    -            -----------------------
    -
    -            /* Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com)
    -            * All rights reserved.
    -            *
    -            * This package is an SSL implementation written
    -            * by Eric Young (eay@cryptsoft.com).
    -            * The implementation was written so as to conform with Netscapes SSL.
    -            *
    -            * This library is free for commercial and non-commercial use as long as
    -            * the following conditions are aheared to.  The following conditions
    -            * apply to all code found in this distribution, be it the RC4, RSA,
    -            * lhash, DES, etc., code; not just the SSL code.  The SSL documentation
    -            * included with this distribution is covered by the same copyright terms
    -            * except that the holder is Tim Hudson (tjh@cryptsoft.com).
    -            *
    -            * Copyright remains Eric Young's, and as such any Copyright notices in
    -            * the code are not to be removed.
    -            * If this package is used in a product, Eric Young should be given attribution
    -            * as the author of the parts of the library used.
    -            * This can be in the form of a textual message at program startup or
    -            * in documentation (online or textual) provided with the package.
    -            *
    -            * Redistribution and use in source and binary forms, with or without
    -            * modification, are permitted provided that the following conditions
    -            * are met:
    -            * 1. Redistributions of source code must retain the copyright
    -            *    notice, this list of conditions and the following disclaimer.
    -            * 2. Redistributions in binary form must reproduce the above copyright
    -            *    notice, this list of conditions and the following disclaimer in the
    -            *    documentation and/or other materials provided with the distribution.
    -            * 3. All advertising materials mentioning features or use of this software
    -            *    must display the following acknowledgement:
    -            *    "This product includes cryptographic software written by
    -            *     Eric Young (eay@cryptsoft.com)"
    -            *    The word 'cryptographic' can be left out if the rouines from the library
    -            *    being used are not cryptographic related :-).
    -            * 4. If you include any Windows specific code (or a derivative thereof) from
    -            *    the apps directory (application code) you must include an acknowledgement:
    -            *    "This product includes software written by Tim Hudson (tjh@cryptsoft.com)"
    -            *
    -            * THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``AS IS'' AND
    -            * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
    -            * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
    -            * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
    -            * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
    -            * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
    -            * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
    -            * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
    -            * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
    -            * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
    -            * SUCH DAMAGE.
    -            *
    -            * The licence and distribution terms for any publically available version or
    -            * derivative of this code cannot be changed.  i.e. this code cannot simply be
    -            * copied and put under another distribution licence
    -            * [including the GNU Public Licence.]
    -            */
    -
    -
    -            ISC license used for completely new code in BoringSSL:
    -
    -            /* Copyright (c) 2015, Google Inc.
    -            *
    -            * Permission to use, copy, modify, and/or distribute this software for any
    -            * purpose with or without fee is hereby granted, provided that the above
    -            * copyright notice and this permission notice appear in all copies.
    -            *
    -            * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
    -            * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
    -            * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
    -            * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
    -            * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
    -            * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
    -            * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */
    -
    -
    -            Some files from Intel carry the following license:
    -
    -            # Copyright (c) 2012, Intel Corporation
    -            #
    -            # All rights reserved.
    -            #
    -            # Redistribution and use in source and binary forms, with or without
    -            # modification, are permitted provided that the following conditions are
    -            # met:
    -            #
    -            # *  Redistributions of source code must retain the above copyright
    -            #    notice, this list of conditions and the following disclaimer.
    -            #
    -            # *  Redistributions in binary form must reproduce the above copyright
    -            #    notice, this list of conditions and the following disclaimer in the
    -            #    documentation and/or other materials provided with the
    -            #    distribution.
    -            #
    -            # *  Neither the name of the Intel Corporation nor the names of its
    -            #    contributors may be used to endorse or promote products derived from
    -            #    this software without specific prior written permission.
    -            #
    -            #
    -            # THIS SOFTWARE IS PROVIDED BY INTEL CORPORATION ""AS IS"" AND ANY
    -            # EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
    -            # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
    -            # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL INTEL CORPORATION OR
    -            # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
    -            # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
    -            # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
    -            # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
    -            # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
    -            # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
    -            # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    -
    -        
    -

    jsoncpp

    -
    -            The JsonCpp library's source code, including accompanying documentation,
    -            tests and demonstration applications, are licensed under the following
    -            conditions...
    -
    -            The author (Baptiste Lepilleur) explicitly disclaims copyright in all
    -            jurisdictions which recognize such a disclaimer. In such jurisdictions,
    -            this software is released into the Public Domain.
    -
    -            In jurisdictions which do not recognize Public Domain property (e.g. Germany as of
    -            2010), this software is Copyright (c) 2007-2010 by Baptiste Lepilleur, and is
    -            released under the terms of the MIT License (see below).
    -
    -            In jurisdictions which recognize Public Domain property, the user of this
    -            software may choose to accept it either as 1) Public Domain, 2) under the
    -            conditions of the MIT License (see below), or 3) under the terms of dual
    -            Public Domain/MIT License conditions described here, as they choose.
    -
    -            The MIT License is about as close to Public Domain as a license can get, and is
    -            described in clear, concise terms at:
    -
    -            http://en.wikipedia.org/wiki/MIT_License
    -
    -            The full text of the MIT License follows:
    -
    -            ========================================================================
    -            Copyright (c) 2007-2010 Baptiste Lepilleur
    -
    -            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.
    -            ========================================================================
    -            (END LICENSE TEXT)
    -
    -            The MIT license is compatible with both the GPL and commercial
    -            software, affording one all of the rights of Public Domain with the
    -            minor nuisance of being required to keep the above copyright notice
    -            and license text in the source code. Note also that by accepting the
    -            Public Domain "license" you can re-license your copy using whatever
    -            license you like.
    -
    -        
    -

    opus

    -
    -            Copyright 2001-2011 Xiph.Org, Skype Limited, Octasic,
    -            Jean-Marc Valin, Timothy B. Terriberry,
    -            CSIRO, Gregory Maxwell, Mark Borgerding,
    -            Erik de Castro Lopo
    -
    -            Redistribution and use in source and binary forms, with or without
    -            modification, are permitted provided that the following conditions
    -            are met:
    -
    -            - Redistributions of source code must retain the above copyright
    -            notice, this list of conditions and the following disclaimer.
    -
    -            - Redistributions in binary form must reproduce the above copyright
    -            notice, this list of conditions and the following disclaimer in the
    -            documentation and/or other materials provided with the distribution.
    -
    -            - Neither the name of Internet Society, IETF or IETF Trust, nor the
    -            names of specific contributors, may be used to endorse or promote
    -            products derived from this software without specific prior written
    -            permission.
    -
    -            THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
    -            ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
    -            LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
    -            A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
    -            OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
    -            EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
    -            PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
    -            PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
    -            LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
    -            NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
    -            SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    -
    -            Opus is subject to the royalty-free patent licenses which are
    -            specified at:
    -
    -            Xiph.Org Foundation:
    -            https://datatracker.ietf.org/ipr/1524/
    -
    -            Microsoft Corporation:
    -            https://datatracker.ietf.org/ipr/1914/
    -
    -            Broadcom Corporation:
    -            https://datatracker.ietf.org/ipr/1526/
    -
    -        
    -

    protobuf_lite

    -
    -            This license applies to all parts of Protocol Buffers except the following:
    -
    -            - Atomicops support for generic gcc, located in
    -            src/google/protobuf/stubs/atomicops_internals_generic_gcc.h.
    -            This file is copyrighted by Red Hat Inc.
    -
    -            - Atomicops support for AIX/POWER, located in
    -            src/google/protobuf/stubs/atomicops_internals_power.h.
    -            This file is copyrighted by Bloomberg Finance LP.
    -
    -            Copyright 2014, Google Inc.  All rights reserved.
    -
    -            Redistribution and use in source and binary forms, with or without
    -            modification, are permitted provided that the following conditions are
    -            met:
    -
    -            * Redistributions of source code must retain the above copyright
    -            notice, this list of conditions and the following disclaimer.
    -            * Redistributions in binary form must reproduce the above
    -            copyright notice, this list of conditions and the following disclaimer
    -            in the documentation and/or other materials provided with the
    -            distribution.
    -            * Neither the name of Google Inc. nor the names of its
    -            contributors may be used to endorse or promote products derived from
    -            this software without specific prior written permission.
    -
    -            THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
    -            "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
    -            LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
    -            A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
    -            OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
    -            SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
    -            LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
    -            DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
    -            THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
    -            (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
    -            OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    -
    -            Code generated by the Protocol Buffer compiler is owned by the owner
    -            of the input file used when generating it.  This code is not
    -            standalone and requires a support library to be linked with it.  This
    -            support library is itself covered by the above license.
    -
    -        
    -

    srtp

    -
    -            /*
    -            *
    -            * Copyright (c) 2001-2006 Cisco Systems, Inc.
    -            * All rights reserved.
    -            *
    -            * Redistribution and use in source and binary forms, with or without
    -            * modification, are permitted provided that the following conditions
    -            * are met:
    -            *
    -            *   Redistributions of source code must retain the above copyright
    -            *   notice, this list of conditions and the following disclaimer.
    -            *
    -            *   Redistributions in binary form must reproduce the above
    -            *   copyright notice, this list of conditions and the following
    -            *   disclaimer in the documentation and/or other materials provided
    -            *   with the distribution.
    -            *
    -            *   Neither the name of the Cisco Systems, Inc. nor the names of its
    -            *   contributors may be used to endorse or promote products derived
    -            *   from this software without specific prior written permission.
    -            *
    -            * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
    -            * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
    -            * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
    -            * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
    -            * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
    -            * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
    -            * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
    -            * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
    -            * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
    -            * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
    -            * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
    -            * OF THE POSSIBILITY OF SUCH DAMAGE.
    -            *
    -            */
    -
    -        
    -

    usrsctplib

    -
    -            (Copied from the COPYRIGHT file of
    -            https://code.google.com/p/sctp-refimpl/source/browse/trunk/COPYRIGHT)
    -            --------------------------------------------------------------------------------
    -
    -            Copyright (c) 2001, 2002 Cisco Systems, Inc.
    -            Copyright (c) 2002-12 Randall R. Stewart
    -            Copyright (c) 2002-12 Michael Tuexen
    -            All rights reserved.
    -
    -            Redistribution and use in source and binary forms, with or without
    -            modification, are permitted provided that the following conditions
    -            are met:
    -
    -            1. Redistributions of source code must retain the above copyright
    -            notice, this list of conditions and the following disclaimer.
    -            2. Redistributions in binary form must reproduce the above copyright
    -            notice, this list of conditions and the following disclaimer in the
    -            documentation and/or other materials provided with the distribution.
    -
    -            THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
    -            ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
    -            IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
    -            ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
    -            FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
    -            DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
    -            OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
    -            HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
    -            LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
    -            OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
    -            SUCH DAMAGE.
    -
    -        
    -

    vpx

    -
    -            Copyright (c) 2010, The WebM Project authors. All rights reserved.
    -
    -            Redistribution and use in source and binary forms, with or without
    -            modification, are permitted provided that the following conditions are
    -            met:
    -            
    -            * Redistributions of source code must retain the above copyright
    -            notice, this list of conditions and the following disclaimer.
    -            
    -            * Redistributions in binary form must reproduce the above copyright
    -            notice, this list of conditions and the following disclaimer in
    -            the documentation and/or other materials provided with the
    -            distribution.
    -            
    -            * Neither the name of Google, nor the WebM Project, nor the names
    -            of its contributors may be used to endorse or promote products
    -            derived from this software without specific prior written
    -            permission.
    -            
    -            THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
    -            "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
    -            LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
    -            A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
    -            HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
    -            SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
    -            LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
    -            DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
    -            THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
    -            (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
    -            OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    -            
    -            
    -        
    -

    yuv

    -
    -            Copyright 2011 The LibYuv Project Authors. All rights reserved.
    -            
    -            Redistribution and use in source and binary forms, with or without
    -            modification, are permitted provided that the following conditions are
    -            met:
    -            
    -            * Redistributions of source code must retain the above copyright
    -            notice, this list of conditions and the following disclaimer.
    -            
    -            * Redistributions in binary form must reproduce the above copyright
    -            notice, this list of conditions and the following disclaimer in
    -            the documentation and/or other materials provided with the
    -            distribution.
    -            
    -            * Neither the name of Google nor the names of its contributors may
    -            be used to endorse or promote products derived from this software
    -            without specific prior written permission.
    -            
    -            THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
    -            "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
    -            LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
    -            A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
    -            HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
    -            SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
    -            LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
    -            DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
    -            THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
    -            (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
    -            OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    -        
    -

    + Jitsi Meet iOS SDK (Jitsi Meet iOS SDK binaries) +

    It is composed of 2 frameworks:

    +
      +
    • + JitsiMeet.framework +

      Copyright 2018-present 8x8, Inc. +

      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: +

      https://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. +

      +
    • +
    • + WebRTC.framework +

      webrtc

      +
      +                    Copyright (c) 2011, The WebRTC project authors. All rights reserved.
      +                    
      +                    Redistribution and use in source and binary forms, with or without
      +                    modification, are permitted provided that the following conditions are
      +                    met:
      +                    
      +                    * Redistributions of source code must retain the above copyright
      +                    notice, this list of conditions and the following disclaimer.
      +                    
      +                    * Redistributions in binary form must reproduce the above copyright
      +                    notice, this list of conditions and the following disclaimer in
      +                    the documentation and/or other materials provided with the
      +                    distribution.
      +                    
      +                    * Neither the name of Google nor the names of its contributors may
      +                    be used to endorse or promote products derived from this software
      +                    without specific prior written permission.
      +                    
      +                    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
      +                    "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
      +                    LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
      +                    A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
      +                    HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
      +                    SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
      +                    LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
      +                    DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
      +                    THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
      +                    (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
      +                    OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
      +                    
      +                    This source tree contains third party source code which is governed by third
      +                    party licenses. Paths to the files and associated licenses are collected here.
      +                    
      +                    Files governed by third party licenses:
      +                    common_audio/fft4g.c
      +                    common_audio/signal_processing/spl_sqrt_floor.c
      +                    common_audio/signal_processing/spl_sqrt_floor_arm.S
      +                    modules/audio_coding/codecs/g711/main/source/g711.c
      +                    modules/audio_coding/codecs/g711/main/source/g711.h
      +                    modules/audio_coding/codecs/g722/main/source/g722_decode.c
      +                    modules/audio_coding/codecs/g722/main/source/g722_enc_dec.h
      +                    modules/audio_coding/codecs/g722/main/source/g722_encode.c
      +                    modules/audio_coding/codecs/isac/main/source/fft.c
      +                    modules/audio_device/mac/portaudio/pa_memorybarrier.h
      +                    modules/audio_device/mac/portaudio/pa_ringbuffer.c
      +                    modules/audio_device/mac/portaudio/pa_ringbuffer.h
      +                    modules/audio_processing/aec/aec_rdft.c
      +                    system_wrappers/source/condition_variable_event_win.cc
      +                    system_wrappers/source/set_thread_name_win.h
      +                    system_wrappers/source/spreadsortlib/constants.hpp
      +                    system_wrappers/source/spreadsortlib/spreadsort.hpp
      +                    
      +                    Individual licenses for each file:
      +                    -------------------------------------------------------------------------------
      +                    Files:
      +                    common_audio/signal_processing/spl_sqrt_floor.c
      +                    common_audio/signal_processing/spl_sqrt_floor_arm.S
      +                    
      +                    License:
      +                    /*
      +                    * Written by Wilco Dijkstra, 1996. The following email exchange establishes the
      +                    * license.
      +                    *
      +                    * From: Wilco Dijkstra <Wilco.Dijkstra@ntlworld.com>
      +                    * Date: Fri, Jun 24, 2011 at 3:20 AM
      +                    * Subject: Re: sqrt routine
      +                    * To: Kevin Ma <kma@google.com>
      +                    * Hi Kevin,
      +                    * Thanks for asking. Those routines are public domain (originally posted to
      +                    * comp.sys.arm a long time ago), so you can use them freely for any purpose.
      +                    * Cheers,
      +                    * Wilco
      +                    *
      +                    * ----- Original Message -----
      +                    * From: "Kevin Ma" <kma@google.com>
      +                    * To: <Wilco.Dijkstra@ntlworld.com>
      +                    * Sent: Thursday, June 23, 2011 11:44 PM
      +                    * Subject: Fwd: sqrt routine
      +                    * Hi Wilco,
      +                    * I saw your sqrt routine from several web sites, including
      +                    * http://www.finesse.demon.co.uk/steven/sqrt.html.
      +                    * Just wonder if there's any copyright information with your Successive
      +                    * approximation routines, or if I can freely use it for any purpose.
      +                    * Thanks.
      +                    * Kevin
      +                    */
      +                    -------------------------------------------------------------------------------
      +                    Files:
      +                    modules/audio_coding/codecs/g711/main/source/g711.c
      +                    modules/audio_coding/codecs/g711/main/source/g711.h
      +                    
      +                    License:
      +                    /*
      +                    * SpanDSP - a series of DSP components for telephony
      +                    *
      +                    * g711.h - In line A-law and u-law conversion routines
      +                    *
      +                    * Written by Steve Underwood <steveu@coppice.org>
      +                    *
      +                    * Copyright (C) 2001 Steve Underwood
      +                    *
      +                    *  Despite my general liking of the GPL, I place this code in the
      +                    *  public domain for the benefit of all mankind - even the slimy
      +                    *  ones who might try to proprietize my work and use it to my
      +                    *  detriment.
      +                    */
      +                    -------------------------------------------------------------------------------
      +                    Files:
      +                    modules/audio_coding/codecs/g722/main/source/g722_decode.c
      +                    modules/audio_coding/codecs/g722/main/source/g722_enc_dec.h
      +                    modules/audio_coding/codecs/g722/main/source/g722_encode.c
      +                    
      +                    License:
      +                    /*
      +                    * SpanDSP - a series of DSP components for telephony
      +                    *
      +                    * g722_decode.c - The ITU G.722 codec, decode part.
      +                    *
      +                    * Written by Steve Underwood <steveu@coppice.org>
      +                    *
      +                    * Copyright (C) 2005 Steve Underwood
      +                    *
      +                    *  Despite my general liking of the GPL, I place my own contributions
      +                    *  to this code in the public domain for the benefit of all mankind -
      +                    *  even the slimy ones who might try to proprietize my work and use it
      +                    *  to my detriment.
      +                    *
      +                    * Based in part on a single channel G.722 codec which is:
      +                    *
      +                    * Copyright (c) CMU 1993
      +                    * Computer Science, Speech Group
      +                    * Chengxiang Lu and Alex Hauptmann
      +                    */
      +                    -------------------------------------------------------------------------------
      +                    Files:
      +                    modules/audio_coding/codecs/isac/main/source/fft.c
      +                    
      +                    License:
      +                    /*
      +                    * Copyright(c)1995,97 Mark Olesen <olesen@me.QueensU.CA>
      +                    *    Queen's Univ at Kingston (Canada)
      +                    *
      +                    * Permission to use, copy, modify, and distribute this software for
      +                    * any purpose without fee is hereby granted, provided that this
      +                    * entire notice is included in all copies of any software which is
      +                    * or includes a copy or modification of this software and in all
      +                    * copies of the supporting documentation for such software.
      +                    *
      +                    * THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR
      +                    * IMPLIED WARRANTY.  IN PARTICULAR, NEITHER THE AUTHOR NOR QUEEN'S
      +                    * UNIVERSITY AT KINGSTON MAKES ANY REPRESENTATION OR WARRANTY OF ANY
      +                    * KIND CONCERNING THE MERCHANTABILITY OF THIS SOFTWARE OR ITS
      +                    * FITNESS FOR ANY PARTICULAR PURPOSE.
      +                    *
      +                    * All of which is to say that you can do what you like with this
      +                    * source code provided you don't try to sell it as your own and you
      +                    * include an unaltered copy of this message (including the
      +                    * copyright).
      +                    *
      +                    * It is also implicitly understood that bug fixes and improvements
      +                    * should make their way back to the general Internet community so
      +                    * that everyone benefits.
      +                    */
      +                    -------------------------------------------------------------------------------
      +                    Files:
      +                    modules/audio_device/mac/portaudio/pa_memorybarrier.h
      +                    modules/audio_device/mac/portaudio/pa_ringbuffer.c
      +                    modules/audio_device/mac/portaudio/pa_ringbuffer.h
      +                    
      +                    License:
      +                    /*
      +                    * $Id: pa_memorybarrier.h 1240 2007-07-17 13:05:07Z bjornroche $
      +                    * Portable Audio I/O Library
      +                    * Memory barrier utilities
      +                    *
      +                    * Author: Bjorn Roche, XO Audio, LLC
      +                    *
      +                    * This program uses the PortAudio Portable Audio Library.
      +                    * For more information see: http://www.portaudio.com
      +                    * Copyright (c) 1999-2000 Ross Bencina and Phil Burk
      +                    *
      +                    * 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.
      +                    */
      +                    
      +                    /*
      +                    * The text above constitutes the entire PortAudio license; however,
      +                    * the PortAudio community also makes the following non-binding requests:
      +                    *
      +                    * Any person wishing to distribute modifications to the Software is
      +                    * requested to send the modifications to the original developer so that
      +                    * they can be incorporated into the canonical version. It is also
      +                    * requested that these non-binding requests be included along with the
      +                    * license above.
      +                    */
      +                    
      +                    /*
      +                    * $Id: pa_ringbuffer.c 1421 2009-11-18 16:09:05Z bjornroche $
      +                    * Portable Audio I/O Library
      +                    * Ring Buffer utility.
      +                    *
      +                    * Author: Phil Burk, http://www.softsynth.com
      +                    * modified for SMP safety on Mac OS X by Bjorn Roche
      +                    * modified for SMP safety on Linux by Leland Lucius
      +                    * also, allowed for const where possible
      +                    * modified for multiple-byte-sized data elements by Sven Fischer
      +                    *
      +                    * Note that this is safe only for a single-thread reader and a
      +                    * single-thread writer.
      +                    *
      +                    * This program uses the PortAudio Portable Audio Library.
      +                    * For more information see: http://www.portaudio.com
      +                    * Copyright (c) 1999-2000 Ross Bencina and Phil Burk
      +                    *
      +                    * 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.
      +                    */
      +                    
      +                    /*
      +                    * The text above constitutes the entire PortAudio license; however,
      +                    * the PortAudio community also makes the following non-binding requests:
      +                    *
      +                    * Any person wishing to distribute modifications to the Software is
      +                    * requested to send the modifications to the original developer so that
      +                    * they can be incorporated into the canonical version. It is also
      +                    * requested that these non-binding requests be included along with the
      +                    * license above.
      +                    */
      +                    -------------------------------------------------------------------------------
      +                    Files:
      +                    common_audio/fft4g.c
      +                    modules/audio_processing/aec/aec_rdft.c
      +                    
      +                    License:
      +                    /*
      +                    * http://www.kurims.kyoto-u.ac.jp/~ooura/fft.html
      +                    * Copyright Takuya OOURA, 1996-2001
      +                    *
      +                    * You may use, copy, modify and distribute this code for any purpose (include
      +                    * commercial use) and without fee. Please refer to this package when you modify
      +                    * this code.
      +                    */
      +                    -------------------------------------------------------------------------------
      +                    Files:
      +                    system_wrappers/source/condition_variable_event_win.cc
      +                    
      +                    Source:
      +                    http://www1.cse.wustl.edu/~schmidt/ACE-copying.html
      +                    
      +                    License:
      +                    Copyright and Licensing Information for ACE(TM), TAO(TM), CIAO(TM), DAnCE(TM),
      +                    and CoSMIC(TM)
      +                    
      +                    ACE(TM), TAO(TM), CIAO(TM), DAnCE>(TM), and CoSMIC(TM) (henceforth referred to
      +                    as "DOC software") are copyrighted by Douglas C. Schmidt and his research
      +                    group at Washington University, University of California, Irvine, and
      +                    Vanderbilt University, Copyright (c) 1993-2009, all rights reserved. Since DOC
      +                    software is open-source, freely available software, you are free to use,
      +                    modify, copy, and distribute--perpetually and irrevocably--the DOC software
      +                    source code and object code produced from the source, as well as copy and
      +                    distribute modified versions of this software. You must, however, include this
      +                    copyright statement along with any code built using DOC software that you
      +                    release. No copyright statement needs to be provided if you just ship binary
      +                    executables of your software products.
      +                    You can use DOC software in commercial and/or binary software releases and are
      +                    under no obligation to redistribute any of your source code that is built
      +                    using DOC software. Note, however, that you may not misappropriate the DOC
      +                    software code, such as copyrighting it yourself or claiming authorship of the
      +                    DOC software code, in a way that will prevent DOC software from being
      +                    distributed freely using an open-source development model. You needn't inform
      +                    anyone that you're using DOC software in your software, though we encourage
      +                    you to let us know so we can promote your project in the DOC software success
      +                    stories.
      +                    
      +                    The ACE, TAO, CIAO, DAnCE, and CoSMIC web sites are maintained by the DOC
      +                    Group at the Institute for Software Integrated Systems (ISIS) and the Center
      +                    for Distributed Object Computing of Washington University, St. Louis for the
      +                    development of open-source software as part of the open-source software
      +                    community. Submissions are provided by the submitter ``as is'' with no
      +                    warranties whatsoever, including any warranty of merchantability,
      +                    noninfringement of third party intellectual property, or fitness for any
      +                    particular purpose. In no event shall the submitter be liable for any direct,
      +                    indirect, special, exemplary, punitive, or consequential damages, including
      +                    without limitation, lost profits, even if advised of the possibility of such
      +                    damages. Likewise, DOC software is provided as is with no warranties of any
      +                    kind, including the warranties of design, merchantability, and fitness for a
      +                    particular purpose, noninfringement, or arising from a course of dealing,
      +                    usage or trade practice. Washington University, UC Irvine, Vanderbilt
      +                    University, their employees, and students shall have no liability with respect
      +                    to the infringement of copyrights, trade secrets or any patents by DOC
      +                    software or any part thereof. Moreover, in no event will Washington
      +                    University, UC Irvine, or Vanderbilt University, their employees, or students
      +                    be liable for any lost revenue or profits or other special, indirect and
      +                    consequential damages.
      +                    
      +                    DOC software is provided with no support and without any obligation on the
      +                    part of Washington University, UC Irvine, Vanderbilt University, their
      +                    employees, or students to assist in its use, correction, modification, or
      +                    enhancement. A number of companies around the world provide commercial support
      +                    for DOC software, however. DOC software is Y2K-compliant, as long as the
      +                    underlying OS platform is Y2K-compliant. Likewise, DOC software is compliant
      +                    with the new US daylight savings rule passed by Congress as "The Energy Policy
      +                    Act of 2005," which established new daylight savings times (DST) rules for the
      +                    United States that expand DST as of March 2007. Since DOC software obtains
      +                    time/date and calendaring information from operating systems users will not be
      +                    affected by the new DST rules as long as they upgrade their operating systems
      +                    accordingly.
      +                    
      +                    The names ACE(TM), TAO(TM), CIAO(TM), DAnCE(TM), CoSMIC(TM), Washington
      +                    University, UC Irvine, and Vanderbilt University, may not be used to endorse
      +                    or promote products or services derived from this source without express
      +                    written permission from Washington University, UC Irvine, or Vanderbilt
      +                    University. This license grants no permission to call products or services
      +                    derived from this source ACE(TM), TAO(TM), CIAO(TM), DAnCE(TM), or CoSMIC(TM),
      +                    nor does it grant permission for the name Washington University, UC Irvine, or
      +                    Vanderbilt University to appear in their names.
      +                    -------------------------------------------------------------------------------
      +                    Files:
      +                    system_wrappers/source/set_thread_name_win.h
      +                    
      +                    Source:
      +                    http://msdn.microsoft.com/en-us/cc300389.aspx#P
      +                    
      +                    License:
      +                    This license governs use of code marked as “sample” or “example” available on
      +                    this web site without a license agreement, as provided under the section above
      +                    titled “NOTICE SPECIFIC TO SOFTWARE AVAILABLE ON THIS WEB SITE.” If you use
      +                    such code (the “software”), you accept this license. If you do not accept the
      +                    license, do not use the software.
      +                    
      +                    1. Definitions
      +                    
      +                    The terms “reproduce,” “reproduction,” “derivative works,” and “distribution”
      +                    have the same meaning here as under U.S. copyright law.
      +                    
      +                    A “contribution” is the original software, or any additions or changes to the
      +                    software.
      +                    
      +                    A “contributor” is any person that distributes its contribution under this
      +                    license.
      +                    
      +                    “Licensed patents” are a contributor’s patent claims that read directly on its
      +                    contribution.
      +                    
      +                    2. Grant of Rights
      +                    
      +                    (A) Copyright Grant - Subject to the terms of this license, including the
      +                    license conditions and limitations in section 3, each contributor grants you a
      +                    non-exclusive, worldwide, royalty-free copyright license to reproduce its
      +                    contribution, prepare derivative works of its contribution, and distribute its
      +                    contribution or any derivative works that you create.
      +                    
      +                    (B) Patent Grant - Subject to the terms of this license, including the license
      +                    conditions and limitations in section 3, each contributor grants you a
      +                    non-exclusive, worldwide, royalty-free license under its licensed patents to
      +                    make, have made, use, sell, offer for sale, import, and/or otherwise dispose
      +                    of its contribution in the software or derivative works of the contribution in
      +                    the software.
      +                    
      +                    3. Conditions and Limitations
      +                    
      +                    (A) No Trademark License- This license does not grant you rights to use any
      +                    contributors’ name, logo, or trademarks.
      +                    
      +                    (B) If you bring a patent claim against any contributor over patents that you
      +                    claim are infringed by the software, your patent license from such contributor
      +                    to the software ends automatically.
      +                    
      +                    (C) If you distribute any portion of the software, you must retain all
      +                    copyright, patent, trademark, and attribution notices that are present in the
      +                    software.
      +                    
      +                    (D) If you distribute any portion of the software in source code form, you may
      +                    do so only under this license by including a complete copy of this license
      +                    with your distribution. If you distribute any portion of the software in
      +                    compiled or object code form, you may only do so under a license that complies
      +                    with this license.
      +                    
      +                    (E) The software is licensed “as-is.” You bear the risk of using it. The
      +                    contributors give no express warranties, guarantees or conditions. You may
      +                    have additional consumer rights under your local laws which this license
      +                    cannot change. To the extent permitted under your local laws, the contributors
      +                    exclude the implied warranties of merchantability, fitness for a particular
      +                    purpose and non-infringement.
      +                    
      +                    (F) Platform Limitation - The licenses granted in sections 2(A) and 2(B)
      +                    extend only to the software or derivative works that you create that run on a
      +                    Microsoft Windows operating system product.
      +                    -------------------------------------------------------------------------------
      +                    Files:
      +                    system_wrappers/source/spreadsortlib/constants.hpp
      +                    system_wrappers/source/spreadsortlib/spreadsort.hpp
      +                    
      +                    License:
      +                    /*Boost Software License - Version 1.0 - August 17th, 2003
      +                    
      +                    Permission is hereby granted, free of charge, to any person or organization
      +                    obtaining a copy of the software and accompanying documentation covered by
      +                    this license (the "Software") to use, reproduce, display, distribute,
      +                    execute, and transmit the Software, and to prepare derivative works of the
      +                    Software, and to permit third-parties to whom the Software is furnished to
      +                    do so, all subject to the following:
      +                    
      +                    The copyright notices in the Software and this entire statement, including
      +                    the above license grant, this restriction and the following disclaimer,
      +                    must be included in all copies of the Software, in whole or in part, and
      +                    all derivative works of the Software, unless such copies or derivative
      +                    works are solely in the form of machine-executable object code generated by
      +                    a source language processor.
      +                    
      +                    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, TITLE AND NON-INFRINGEMENT. IN NO EVENT
      +                    SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
      +                    FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
      +                    ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
      +                    DEALINGS IN THE SOFTWARE.*/
      +                    
      +                
      +

      boringssl

      +
      +                    BoringSSL is a fork of OpenSSL. As such, large parts of it fall under OpenSSL
      +                    licensing. Files that are completely new have a Google copyright and an ISC
      +                    license. This license is reproduced at the bottom of this file.
      +                    
      +                    Contributors to BoringSSL are required to follow the CLA rules for Chromium:
      +                    https://cla.developers.google.com/clas
      +                    
      +                    Some files from Intel are under yet another license, which is also included
      +                    underneath.
      +                    
      +                    The OpenSSL toolkit stays under a dual license, i.e. both the conditions of the
      +                    OpenSSL License and the original SSLeay license apply to the toolkit. See below
      +                    for the actual license texts. Actually both licenses are BSD-style Open Source
      +                    licenses. In case of any license issues related to OpenSSL please contact
      +                    openssl-core@openssl.org.
      +                    
      +                    The following are Google-internal bug numbers where explicit permission from
      +                    some authors is recorded for use of their work. (This is purely for our own
      +                    record keeping.)
      +                    27287199
      +                    27287880
      +                    27287883
      +                    
      +                    OpenSSL License
      +                    ---------------
      +                    
      +                    /* ====================================================================
      +                    * Copyright (c) 1998-2011 The OpenSSL Project.  All rights reserved.
      +                    *
      +                    * Redistribution and use in source and binary forms, with or without
      +                    * modification, are permitted provided that the following conditions
      +                    * are met:
      +                    *
      +                    * 1. Redistributions of source code must retain the above copyright
      +                    *    notice, this list of conditions and the following disclaimer.
      +                    *
      +                    * 2. Redistributions in binary form must reproduce the above copyright
      +                    *    notice, this list of conditions and the following disclaimer in
      +                    *    the documentation and/or other materials provided with the
      +                    *    distribution.
      +                    *
      +                    * 3. All advertising materials mentioning features or use of this
      +                    *    software must display the following acknowledgment:
      +                    *    "This product includes software developed by the OpenSSL Project
      +                    *    for use in the OpenSSL Toolkit. (http://www.openssl.org/)"
      +                    *
      +                    * 4. The names "OpenSSL Toolkit" and "OpenSSL Project" must not be used to
      +                    *    endorse or promote products derived from this software without
      +                    *    prior written permission. For written permission, please contact
      +                    *    openssl-core@openssl.org.
      +                    *
      +                    * 5. Products derived from this software may not be called "OpenSSL"
      +                    *    nor may "OpenSSL" appear in their names without prior written
      +                    *    permission of the OpenSSL Project.
      +                    *
      +                    * 6. Redistributions of any form whatsoever must retain the following
      +                    *    acknowledgment:
      +                    *    "This product includes software developed by the OpenSSL Project
      +                    *    for use in the OpenSSL Toolkit (http://www.openssl.org/)"
      +                    *
      +                    * THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY
      +                    * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
      +                    * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
      +                    * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE OpenSSL PROJECT OR
      +                    * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
      +                    * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
      +                    * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
      +                    * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
      +                    * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
      +                    * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
      +                    * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
      +                    * OF THE POSSIBILITY OF SUCH DAMAGE.
      +                    * ====================================================================
      +                    *
      +                    * This product includes cryptographic software written by Eric Young
      +                    * (eay@cryptsoft.com).  This product includes software written by Tim
      +                    * Hudson (tjh@cryptsoft.com).
      +                    *
      +                    */
      +                    
      +                    Original SSLeay License
      +                    -----------------------
      +                    
      +                    /* Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com)
      +                    * All rights reserved.
      +                    *
      +                    * This package is an SSL implementation written
      +                    * by Eric Young (eay@cryptsoft.com).
      +                    * The implementation was written so as to conform with Netscapes SSL.
      +                    *
      +                    * This library is free for commercial and non-commercial use as long as
      +                    * the following conditions are aheared to.  The following conditions
      +                    * apply to all code found in this distribution, be it the RC4, RSA,
      +                    * lhash, DES, etc., code; not just the SSL code.  The SSL documentation
      +                    * included with this distribution is covered by the same copyright terms
      +                    * except that the holder is Tim Hudson (tjh@cryptsoft.com).
      +                    *
      +                    * Copyright remains Eric Young's, and as such any Copyright notices in
      +                    * the code are not to be removed.
      +                    * If this package is used in a product, Eric Young should be given attribution
      +                    * as the author of the parts of the library used.
      +                    * This can be in the form of a textual message at program startup or
      +                    * in documentation (online or textual) provided with the package.
      +                    *
      +                    * Redistribution and use in source and binary forms, with or without
      +                    * modification, are permitted provided that the following conditions
      +                    * are met:
      +                    * 1. Redistributions of source code must retain the copyright
      +                    *    notice, this list of conditions and the following disclaimer.
      +                    * 2. Redistributions in binary form must reproduce the above copyright
      +                    *    notice, this list of conditions and the following disclaimer in the
      +                    *    documentation and/or other materials provided with the distribution.
      +                    * 3. All advertising materials mentioning features or use of this software
      +                    *    must display the following acknowledgement:
      +                    *    "This product includes cryptographic software written by
      +                    *     Eric Young (eay@cryptsoft.com)"
      +                    *    The word 'cryptographic' can be left out if the rouines from the library
      +                    *    being used are not cryptographic related :-).
      +                    * 4. If you include any Windows specific code (or a derivative thereof) from
      +                    *    the apps directory (application code) you must include an acknowledgement:
      +                    *    "This product includes software written by Tim Hudson (tjh@cryptsoft.com)"
      +                    *
      +                    * THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``AS IS'' AND
      +                    * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
      +                    * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
      +                    * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
      +                    * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
      +                    * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
      +                    * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
      +                    * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
      +                    * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
      +                    * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
      +                    * SUCH DAMAGE.
      +                    *
      +                    * The licence and distribution terms for any publically available version or
      +                    * derivative of this code cannot be changed.  i.e. this code cannot simply be
      +                    * copied and put under another distribution licence
      +                    * [including the GNU Public Licence.]
      +                    */
      +                    
      +                    
      +                    ISC license used for completely new code in BoringSSL:
      +                    
      +                    /* Copyright (c) 2015, Google Inc.
      +                    *
      +                    * Permission to use, copy, modify, and/or distribute this software for any
      +                    * purpose with or without fee is hereby granted, provided that the above
      +                    * copyright notice and this permission notice appear in all copies.
      +                    *
      +                    * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
      +                    * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
      +                    * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
      +                    * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
      +                    * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
      +                    * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
      +                    * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */
      +                    
      +                    
      +                    Some files from Intel carry the following license:
      +                    
      +                    # Copyright (c) 2012, Intel Corporation
      +                    #
      +                    # All rights reserved.
      +                    #
      +                    # Redistribution and use in source and binary forms, with or without
      +                    # modification, are permitted provided that the following conditions are
      +                    # met:
      +                    #
      +                    # *  Redistributions of source code must retain the above copyright
      +                    #    notice, this list of conditions and the following disclaimer.
      +                    #
      +                    # *  Redistributions in binary form must reproduce the above copyright
      +                    #    notice, this list of conditions and the following disclaimer in the
      +                    #    documentation and/or other materials provided with the
      +                    #    distribution.
      +                    #
      +                    # *  Neither the name of the Intel Corporation nor the names of its
      +                    #    contributors may be used to endorse or promote products derived from
      +                    #    this software without specific prior written permission.
      +                    #
      +                    #
      +                    # THIS SOFTWARE IS PROVIDED BY INTEL CORPORATION ""AS IS"" AND ANY
      +                    # EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
      +                    # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
      +                    # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL INTEL CORPORATION OR
      +                    # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
      +                    # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
      +                    # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
      +                    # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
      +                    # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
      +                    # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
      +                    # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
      +                    
      +                
      +

      jsoncpp

      +
      +                    The JsonCpp library's source code, including accompanying documentation,
      +                    tests and demonstration applications, are licensed under the following
      +                    conditions...
      +                    
      +                    The author (Baptiste Lepilleur) explicitly disclaims copyright in all
      +                    jurisdictions which recognize such a disclaimer. In such jurisdictions,
      +                    this software is released into the Public Domain.
      +                    
      +                    In jurisdictions which do not recognize Public Domain property (e.g. Germany as of
      +                    2010), this software is Copyright (c) 2007-2010 by Baptiste Lepilleur, and is
      +                    released under the terms of the MIT License (see below).
      +                    
      +                    In jurisdictions which recognize Public Domain property, the user of this
      +                    software may choose to accept it either as 1) Public Domain, 2) under the
      +                    conditions of the MIT License (see below), or 3) under the terms of dual
      +                    Public Domain/MIT License conditions described here, as they choose.
      +                    
      +                    The MIT License is about as close to Public Domain as a license can get, and is
      +                    described in clear, concise terms at:
      +                    
      +                    http://en.wikipedia.org/wiki/MIT_License
      +                    
      +                    The full text of the MIT License follows:
      +                    
      +                    ========================================================================
      +                    Copyright (c) 2007-2010 Baptiste Lepilleur
      +                    
      +                    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.
      +                    ========================================================================
      +                    (END LICENSE TEXT)
      +                    
      +                    The MIT license is compatible with both the GPL and commercial
      +                    software, affording one all of the rights of Public Domain with the
      +                    minor nuisance of being required to keep the above copyright notice
      +                    and license text in the source code. Note also that by accepting the
      +                    Public Domain "license" you can re-license your copy using whatever
      +                    license you like.
      +                    
      +                
      +

      opus

      +
      +                    Copyright 2001-2011 Xiph.Org, Skype Limited, Octasic,
      +                    Jean-Marc Valin, Timothy B. Terriberry,
      +                    CSIRO, Gregory Maxwell, Mark Borgerding,
      +                    Erik de Castro Lopo
      +                    
      +                    Redistribution and use in source and binary forms, with or without
      +                    modification, are permitted provided that the following conditions
      +                    are met:
      +                    
      +                    - Redistributions of source code must retain the above copyright
      +                    notice, this list of conditions and the following disclaimer.
      +                    
      +                    - Redistributions in binary form must reproduce the above copyright
      +                    notice, this list of conditions and the following disclaimer in the
      +                    documentation and/or other materials provided with the distribution.
      +                    
      +                    - Neither the name of Internet Society, IETF or IETF Trust, nor the
      +                    names of specific contributors, may be used to endorse or promote
      +                    products derived from this software without specific prior written
      +                    permission.
      +                    
      +                    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
      +                    ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
      +                    LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
      +                    A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
      +                    OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
      +                    EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
      +                    PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
      +                    PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
      +                    LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
      +                    NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
      +                    SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
      +                    
      +                    Opus is subject to the royalty-free patent licenses which are
      +                    specified at:
      +                    
      +                    Xiph.Org Foundation:
      +                    https://datatracker.ietf.org/ipr/1524/
      +                    
      +                    Microsoft Corporation:
      +                    https://datatracker.ietf.org/ipr/1914/
      +                    
      +                    Broadcom Corporation:
      +                    https://datatracker.ietf.org/ipr/1526/
      +                    
      +                
      +

      protobuf_lite

      +
      +                    This license applies to all parts of Protocol Buffers except the following:
      +                    
      +                    - Atomicops support for generic gcc, located in
      +                    src/google/protobuf/stubs/atomicops_internals_generic_gcc.h.
      +                    This file is copyrighted by Red Hat Inc.
      +                    
      +                    - Atomicops support for AIX/POWER, located in
      +                    src/google/protobuf/stubs/atomicops_internals_power.h.
      +                    This file is copyrighted by Bloomberg Finance LP.
      +                    
      +                    Copyright 2014, Google Inc.  All rights reserved.
      +                    
      +                    Redistribution and use in source and binary forms, with or without
      +                    modification, are permitted provided that the following conditions are
      +                    met:
      +                    
      +                    * Redistributions of source code must retain the above copyright
      +                    notice, this list of conditions and the following disclaimer.
      +                    * Redistributions in binary form must reproduce the above
      +                    copyright notice, this list of conditions and the following disclaimer
      +                    in the documentation and/or other materials provided with the
      +                    distribution.
      +                    * Neither the name of Google Inc. nor the names of its
      +                    contributors may be used to endorse or promote products derived from
      +                    this software without specific prior written permission.
      +                    
      +                    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
      +                    "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
      +                    LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
      +                    A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
      +                    OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
      +                    SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
      +                    LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
      +                    DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
      +                    THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
      +                    (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
      +                    OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
      +                    
      +                    Code generated by the Protocol Buffer compiler is owned by the owner
      +                    of the input file used when generating it.  This code is not
      +                    standalone and requires a support library to be linked with it.  This
      +                    support library is itself covered by the above license.
      +                    
      +                
      +

      srtp

      +
      +                    /*
      +                    *
      +                    * Copyright (c) 2001-2006 Cisco Systems, Inc.
      +                    * All rights reserved.
      +                    *
      +                    * Redistribution and use in source and binary forms, with or without
      +                    * modification, are permitted provided that the following conditions
      +                    * are met:
      +                    *
      +                    *   Redistributions of source code must retain the above copyright
      +                    *   notice, this list of conditions and the following disclaimer.
      +                    *
      +                    *   Redistributions in binary form must reproduce the above
      +                    *   copyright notice, this list of conditions and the following
      +                    *   disclaimer in the documentation and/or other materials provided
      +                    *   with the distribution.
      +                    *
      +                    *   Neither the name of the Cisco Systems, Inc. nor the names of its
      +                    *   contributors may be used to endorse or promote products derived
      +                    *   from this software without specific prior written permission.
      +                    *
      +                    * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
      +                    * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
      +                    * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
      +                    * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
      +                    * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
      +                    * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
      +                    * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
      +                    * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
      +                    * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
      +                    * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
      +                    * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
      +                    * OF THE POSSIBILITY OF SUCH DAMAGE.
      +                    *
      +                    */
      +                    
      +                
      +

      usrsctplib

      +
      +                    (Copied from the COPYRIGHT file of
      +                    https://code.google.com/p/sctp-refimpl/source/browse/trunk/COPYRIGHT)
      +                    --------------------------------------------------------------------------------
      +                    
      +                    Copyright (c) 2001, 2002 Cisco Systems, Inc.
      +                    Copyright (c) 2002-12 Randall R. Stewart
      +                    Copyright (c) 2002-12 Michael Tuexen
      +                    All rights reserved.
      +                    
      +                    Redistribution and use in source and binary forms, with or without
      +                    modification, are permitted provided that the following conditions
      +                    are met:
      +                    
      +                    1. Redistributions of source code must retain the above copyright
      +                    notice, this list of conditions and the following disclaimer.
      +                    2. Redistributions in binary form must reproduce the above copyright
      +                    notice, this list of conditions and the following disclaimer in the
      +                    documentation and/or other materials provided with the distribution.
      +                    
      +                    THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
      +                    ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
      +                    IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
      +                    ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
      +                    FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
      +                    DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
      +                    OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
      +                    HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
      +                    LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
      +                    OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
      +                    SUCH DAMAGE.
      +                    
      +                
      +

      vpx

      +
      +                    Copyright (c) 2010, The WebM Project authors. All rights reserved.
      +                    
      +                    Redistribution and use in source and binary forms, with or without
      +                    modification, are permitted provided that the following conditions are
      +                    met:
      +                    
      +                    * Redistributions of source code must retain the above copyright
      +                    notice, this list of conditions and the following disclaimer.
      +                    
      +                    * Redistributions in binary form must reproduce the above copyright
      +                    notice, this list of conditions and the following disclaimer in
      +                    the documentation and/or other materials provided with the
      +                    distribution.
      +                    
      +                    * Neither the name of Google, nor the WebM Project, nor the names
      +                    of its contributors may be used to endorse or promote products
      +                    derived from this software without specific prior written
      +                    permission.
      +                    
      +                    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
      +                    "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
      +                    LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
      +                    A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
      +                    HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
      +                    SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
      +                    LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
      +                    DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
      +                    THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
      +                    (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
      +                    OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
      +                    
      +                    
      +                
      +

      yuv

      +
      +                    Copyright 2011 The LibYuv Project Authors. All rights reserved.
      +                    
      +                    Redistribution and use in source and binary forms, with or without
      +                    modification, are permitted provided that the following conditions are
      +                    met:
      +                    
      +                    * Redistributions of source code must retain the above copyright
      +                    notice, this list of conditions and the following disclaimer.
      +                    
      +                    * Redistributions in binary form must reproduce the above copyright
      +                    notice, this list of conditions and the following disclaimer in
      +                    the documentation and/or other materials provided with the
      +                    distribution.
      +                    
      +                    * Neither the name of Google nor the names of its contributors may
      +                    be used to endorse or promote products derived from this software
      +                    without specific prior written permission.
      +                    
      +                    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
      +                    "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
      +                    LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
      +                    A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
      +                    HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
      +                    SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
      +                    LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
      +                    DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
      +                    THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
      +                    (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
      +                    OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
      +                
      +
    • +
  • Realm ( -#define VECTOR_ROOMBUBBLETABLEVIEWCELL_TIMELABEL_WIDTH 39 - #define VECTOR_ROOMBUBBLETABLEVIEWCELL_MARK_X 48 #define VECTOR_ROOMBUBBLETABLEVIEWCELL_MARK_WIDTH 4 @@ -36,112 +34,166 @@ NSString *const kMXKRoomBubbleCellTapOnReceiptsContainer = @"kMXKRoomBubbleCellT - (void)addTimestampLabelForComponent:(NSUInteger)componentIndex { - self.bubbleInfoContainer.hidden = NO; - MXKRoomBubbleComponent *component; NSArray *bubbleComponents = bubbleData.bubbleComponents; if (componentIndex < bubbleComponents.count) { - component = bubbleComponents[componentIndex]; + component = bubbleComponents[componentIndex]; } if (component && component.date) { - // Check whether this is the first displayed component. BOOL isFirstDisplayedComponent = (componentIndex == 0); + BOOL isLastMessageMostRecentComponent = NO; + + RoomBubbleCellData *roomBubbleCellData; + if ([bubbleData isKindOfClass:RoomBubbleCellData.class]) { - isFirstDisplayedComponent = (componentIndex == ((RoomBubbleCellData*)bubbleData).oldestComponentIndex); + roomBubbleCellData = (RoomBubbleCellData*)bubbleData; + isFirstDisplayedComponent = (componentIndex == roomBubbleCellData.oldestComponentIndex); + isLastMessageMostRecentComponent = roomBubbleCellData.containsLastMessage && (componentIndex == roomBubbleCellData.mostRecentComponentIndex); } - CGFloat timeLabelPosX = self.bubbleInfoContainer.frame.size.width - VECTOR_ROOMBUBBLETABLEVIEWCELL_TIMELABEL_WIDTH; - CGFloat timeLabelPosY = isFirstDisplayedComponent ? 0 : component.position.y + self.msgTextViewTopConstraint.constant - self.bubbleInfoContainerTopConstraint.constant; - UILabel *timeLabel = [[UILabel alloc] initWithFrame:CGRectMake(timeLabelPosX, timeLabelPosY, VECTOR_ROOMBUBBLETABLEVIEWCELL_TIMELABEL_WIDTH , 18)]; + // Display timestamp on the left for selected component when it cannot overlap other UI elements like user's avatar + BOOL displayLabelOnLeft = roomBubbleCellData.displayTimestampForSelectedComponentOnLeftWhenPossible + && !isLastMessageMostRecentComponent + && ( !isFirstDisplayedComponent || roomBubbleCellData.shouldHideSenderInformation); - timeLabel.text = [bubbleData.eventFormatter timeStringFromDate:component.date]; - timeLabel.textAlignment = NSTextAlignmentRight; - timeLabel.textColor = ThemeService.shared.theme.textSecondaryColor; - if ([UIFont respondsToSelector:@selector(systemFontOfSize:weight:)]) + [self addTimestampLabelForComponentIndex:componentIndex + isFirstDisplayedComponent:isFirstDisplayedComponent + viewTag:componentIndex + displayOnLeft:displayLabelOnLeft]; + } +} + +- (void)addTimestampLabelForComponentIndex:(NSInteger)componentIndex + isFirstDisplayedComponent:(BOOL)isFirstDisplayedComponent + viewTag:(NSInteger)viewTag + displayOnLeft:(BOOL)displayOnLeft +{ + NSArray *bubbleComponents = bubbleData.bubbleComponents; + MXKRoomBubbleComponent *component = bubbleComponents[componentIndex]; + + self.bubbleInfoContainer.hidden = NO; + + CGFloat timeLabelPosX; + CGFloat timeLabelPosY; + CGFloat timeLabelHeight = RoomBubbleCellLayout.timestampLabelHeight; + CGFloat timeLabelWidth; + NSTextAlignment timeLabelTextAlignment; + + CGRect componentFrame = [self componentFrameInContentViewForIndex:componentIndex]; + + if (displayOnLeft) + { + CGFloat leftMargin = 10.0; + CGFloat rightMargin = (self.contentView.frame.size.width - (self.bubbleInfoContainer.frame.origin.x + self.bubbleInfoContainer.frame.size.width)); + + timeLabelPosX = 0; + + if (CGRectEqualToRect(componentFrame, CGRectNull) == false) { - timeLabel.font = [UIFont systemFontOfSize:12 weight:UIFontWeightLight]; + timeLabelPosY = componentFrame.origin.y - self.bubbleInfoContainerTopConstraint.constant; } else { - timeLabel.font = [UIFont systemFontOfSize:12]; + timeLabelPosY = component.position.y + self.msgTextViewTopConstraint.constant - self.bubbleInfoContainerTopConstraint.constant; } - timeLabel.adjustsFontSizeToFitWidth = YES; - - timeLabel.tag = componentIndex; - [timeLabel setTranslatesAutoresizingMaskIntoConstraints:NO]; - timeLabel.accessibilityIdentifier = @"timestampLabel"; - [self.bubbleInfoContainer addSubview:timeLabel]; - - // Define timeLabel constraints (to handle auto-layout in case of screen rotation) - NSLayoutConstraint *rightConstraint = [NSLayoutConstraint constraintWithItem:timeLabel - attribute:NSLayoutAttributeTrailing - relatedBy:NSLayoutRelationEqual - toItem:self.bubbleInfoContainer - attribute:NSLayoutAttributeTrailing - multiplier:1.0 - constant:0]; - NSLayoutConstraint *topConstraint = [NSLayoutConstraint constraintWithItem:timeLabel - attribute:NSLayoutAttributeTop - relatedBy:NSLayoutRelationEqual - toItem:self.bubbleInfoContainer - attribute:NSLayoutAttributeTop - multiplier:1.0 - constant:timeLabelPosY]; - NSLayoutConstraint *widthConstraint = [NSLayoutConstraint constraintWithItem:timeLabel - attribute:NSLayoutAttributeWidth - relatedBy:NSLayoutRelationEqual - toItem:nil - attribute:NSLayoutAttributeNotAnAttribute - multiplier:1.0 - constant:VECTOR_ROOMBUBBLETABLEVIEWCELL_TIMELABEL_WIDTH]; - NSLayoutConstraint *heightConstraint = [NSLayoutConstraint constraintWithItem:timeLabel - attribute:NSLayoutAttributeHeight - relatedBy:NSLayoutRelationEqual - toItem:nil - attribute:NSLayoutAttributeNotAnAttribute - multiplier:1.0 - constant:18]; - - // Available on iOS 8 and later - [NSLayoutConstraint activateConstraints:@[rightConstraint, topConstraint, widthConstraint, heightConstraint]]; - - // Check whether a vertical whitespace was applied to display correctly the timestamp. - if (!isFirstDisplayedComponent || bubbleData.shouldHideSenderInformation || bubbleData.shouldHideSenderName) - { - // Adjust the position of the potential encryption icon in this case. - if (self.encryptionStatusContainerView) - { - NSArray* subviews = self.encryptionStatusContainerView.subviews; - for (UIView *view in subviews) - { - // Note: The encryption icon has been tagged with the component index. - if (view.tag == componentIndex) - { - CGRect frame = view.frame; - frame.origin.y += 15; - view.frame = frame; - - break; - } - } - } - } + timeLabelWidth = self.contentView.frame.size.width - leftMargin - rightMargin; + timeLabelTextAlignment = NSTextAlignmentLeft; } + else + { + timeLabelPosX = self.bubbleInfoContainer.frame.size.width - RoomBubbleCellLayout.timestampLabelWidth; + + if (isFirstDisplayedComponent) + { + timeLabelPosY = 0; + } + else if (CGRectEqualToRect(componentFrame, CGRectNull) == false) + { + timeLabelPosY = componentFrame.origin.y - self.bubbleInfoContainerTopConstraint.constant - timeLabelHeight; + } + else + { + timeLabelPosY = component.position.y + self.msgTextViewTopConstraint.constant - timeLabelHeight - self.bubbleInfoContainerTopConstraint.constant; + } + + timeLabelWidth = RoomBubbleCellLayout.timestampLabelWidth; + timeLabelTextAlignment = NSTextAlignmentRight; + } + + timeLabelPosY = MAX(0.0, timeLabelPosY); + + UILabel *timeLabel = [[UILabel alloc] initWithFrame:CGRectMake(timeLabelPosX, timeLabelPosY, timeLabelWidth, timeLabelHeight)]; + + timeLabel.text = [bubbleData.eventFormatter timeStringFromDate:component.date]; + timeLabel.textAlignment = timeLabelTextAlignment; + timeLabel.textColor = ThemeService.shared.theme.textSecondaryColor; + timeLabel.font = [UIFont systemFontOfSize:12 weight:UIFontWeightLight]; + timeLabel.adjustsFontSizeToFitWidth = YES; + + timeLabel.tag = viewTag; + + [timeLabel setTranslatesAutoresizingMaskIntoConstraints:NO]; + timeLabel.accessibilityIdentifier = @"timestampLabel"; + [self.bubbleInfoContainer addSubview:timeLabel]; + + // Define timeLabel constraints (to handle auto-layout in case of screen rotation) + NSLayoutConstraint *rightConstraint = [NSLayoutConstraint constraintWithItem:timeLabel + attribute:NSLayoutAttributeTrailing + relatedBy:NSLayoutRelationEqual + toItem:self.bubbleInfoContainer + attribute:NSLayoutAttributeTrailing + multiplier:1.0 + constant:0]; + NSLayoutConstraint *topConstraint = [NSLayoutConstraint constraintWithItem:timeLabel + attribute:NSLayoutAttributeTop + relatedBy:NSLayoutRelationEqual + toItem:self.bubbleInfoContainer + attribute:NSLayoutAttributeTop + multiplier:1.0 + constant:timeLabelPosY]; + + NSLayoutConstraint *widthConstraint = [NSLayoutConstraint constraintWithItem:timeLabel + attribute:NSLayoutAttributeWidth + relatedBy:NSLayoutRelationEqual + toItem:nil + attribute:NSLayoutAttributeNotAnAttribute + multiplier:1.0 + constant:timeLabelWidth]; + + + NSLayoutConstraint *heightConstraint = [NSLayoutConstraint constraintWithItem:timeLabel + attribute:NSLayoutAttributeHeight + relatedBy:NSLayoutRelationEqual + toItem:nil + attribute:NSLayoutAttributeNotAnAttribute + multiplier:1.0 + constant:timeLabelHeight]; + + // Available on iOS 8 and later + [NSLayoutConstraint activateConstraints:@[rightConstraint, topConstraint, widthConstraint, heightConstraint]]; } - (void)selectComponent:(NSUInteger)componentIndex +{ + [self selectComponent:componentIndex showEditButton:NO showTimestamp:YES]; +} + +- (void)selectComponent:(NSUInteger)componentIndex showEditButton:(BOOL)showEditButton showTimestamp:(BOOL)showTimestamp { if (componentIndex < bubbleData.bubbleComponents.count) { - // Add time label - [self addTimestampLabelForComponent:componentIndex]; + if (showTimestamp) + { + // Add time label + [self addTimestampLabelForComponent:componentIndex]; + } // Blur timestamp labels which are not related to the selected component (if any) for (UIView* view in self.bubbleInfoContainer.subviews) @@ -164,8 +216,11 @@ NSString *const kMXKRoomBubbleCellTapOnReceiptsContainer = @"kMXKRoomBubbleCellT } } - // Add the edit button - [self addEditButtonForComponent:componentIndex completion:nil]; + if (showEditButton) + { + // Add the edit button + [self addEditButtonForComponent:componentIndex completion:nil]; + } } } @@ -260,7 +315,7 @@ NSString *const kMXKRoomBubbleCellTapOnReceiptsContainer = @"kMXKRoomBubbleCellT NSDate *date = bubbleData.date; if (date) { - UILabel *timeLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, self.bubbleInfoContainer.frame.size.width , 18)]; + UILabel *timeLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, self.bubbleInfoContainer.frame.size.width, RoomBubbleCellLayout.timestampLabelHeight)]; timeLabel.text = [bubbleData.eventFormatter dateStringFromDate:date withTime:NO]; timeLabel.textAlignment = NSTextAlignmentRight; @@ -307,7 +362,7 @@ NSString *const kMXKRoomBubbleCellTapOnReceiptsContainer = @"kMXKRoomBubbleCellT toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 - constant:18]; + constant:RoomBubbleCellLayout.timestampLabelHeight]; // Available on iOS 8 and later [NSLayoutConstraint activateConstraints:@[rightConstraint, topConstraint, widthConstraint, heightConstraint]]; @@ -412,6 +467,187 @@ NSString *const kMXKRoomBubbleCellTapOnReceiptsContainer = @"kMXKRoomBubbleCellT } } +- (CGRect)componentFrameInTableViewForIndex:(NSInteger)componentIndex +{ + CGRect componentFrameInContentView = [self componentFrameInContentViewForIndex:componentIndex]; + return [self.contentView convertRect:componentFrameInContentView toView:self.superview]; +} + +- (CGRect)surroundingFrameInTableViewForComponentIndex:(NSInteger)componentIndex +{ + CGRect surroundingFrame; + + CGRect componentFrameInContentView = [self componentFrameInContentViewForIndex:componentIndex]; + MXKRoomBubbleTableViewCell *roomBubbleTableViewCell = self; + MXKRoomBubbleCellData *bubbleCellData = roomBubbleTableViewCell.bubbleData; + + NSInteger firstVisibleComponentIndex = NSNotFound; + NSInteger lastMostRecentComponentIndex = NSNotFound; + + if ([bubbleCellData isKindOfClass:[RoomBubbleCellData class]]) + { + RoomBubbleCellData *roomBubbleCellData = (RoomBubbleCellData*)bubbleCellData; + firstVisibleComponentIndex = [roomBubbleCellData firstVisibleComponentIndex]; + + if (roomBubbleCellData.containsLastMessage + && roomBubbleCellData.mostRecentComponentIndex != NSNotFound + && roomBubbleCellData.firstVisibleComponentIndex != roomBubbleCellData.mostRecentComponentIndex + && componentIndex == roomBubbleCellData.mostRecentComponentIndex) + { + lastMostRecentComponentIndex = roomBubbleCellData.mostRecentComponentIndex; + } + } + + // Do not overlap timestamp for last message + if (lastMostRecentComponentIndex != NSNotFound) + { + CGFloat componentBottomY = componentFrameInContentView.origin.y + componentFrameInContentView.size.height; + + CGFloat x = 0; + CGFloat y = componentFrameInContentView.origin.y - RoomBubbleCellLayout.timestampLabelHeight; + CGFloat width = roomBubbleTableViewCell.contentView.frame.size.width; + CGFloat height = componentBottomY - y; + + surroundingFrame = CGRectMake(x, y, width, height); + } // Do not overlap user name label for first visible component + else if (!CGRectEqualToRect(componentFrameInContentView, CGRectNull) + && firstVisibleComponentIndex != NSNotFound + && componentIndex <= firstVisibleComponentIndex + && roomBubbleTableViewCell.userNameLabel + && roomBubbleTableViewCell.userNameLabel.isHidden == NO) + { + CGFloat componentBottomY = componentFrameInContentView.origin.y + componentFrameInContentView.size.height; + + CGFloat x = 0; + CGFloat y = roomBubbleTableViewCell.userNameLabel.frame.origin.y; + CGFloat width = roomBubbleTableViewCell.contentView.frame.size.width; + CGFloat height = componentBottomY - y; + + surroundingFrame = CGRectMake(x, y, width, height); + } + else + { + surroundingFrame = componentFrameInContentView; + } + + return [self.contentView convertRect:surroundingFrame toView:self.superview]; +} + +- (CGRect)componentFrameInContentViewForIndex:(NSInteger)componentIndex +{ + MXKRoomBubbleTableViewCell *roomBubbleTableViewCell = self; + MXKRoomBubbleCellData *bubbleCellData = roomBubbleTableViewCell.bubbleData; + MXKRoomBubbleComponent *selectedComponent; + + if (bubbleCellData.bubbleComponents.count > componentIndex) + { + selectedComponent = bubbleCellData.bubbleComponents[componentIndex]; + } + + if (!selectedComponent) + { + return CGRectNull; + } + + CGFloat selectedComponenContentViewYOffset = 0; + CGFloat selectedComponentPositionY = 0; + CGFloat selectedComponentHeight = 0; + + CGRect componentFrame = CGRectNull; + + if (roomBubbleTableViewCell.attachmentView) + { + CGRect attachamentViewFrame = roomBubbleTableViewCell.attachmentView.frame; + + selectedComponenContentViewYOffset = attachamentViewFrame.origin.y; + selectedComponentHeight = attachamentViewFrame.size.height; + } + else if (roomBubbleTableViewCell.messageTextView) + { + CGFloat textMessageHeight = 0; + + if ([bubbleCellData isKindOfClass:[RoomBubbleCellData class]]) + { + RoomBubbleCellData *roomBubbleCellData = (RoomBubbleCellData*)bubbleCellData; + + if (!roomBubbleCellData.attachment && selectedComponent.attributedTextMessage) + { + textMessageHeight = [roomBubbleCellData rawTextHeight:selectedComponent.attributedTextMessage]; + } + } + + selectedComponentPositionY = selectedComponent.position.y; + + if (textMessageHeight > 0) + { + selectedComponentHeight = textMessageHeight; + } + else + { + selectedComponentHeight = roomBubbleTableViewCell.frame.size.height - selectedComponentPositionY; + } + + selectedComponenContentViewYOffset = roomBubbleTableViewCell.messageTextView.frame.origin.y; + } + + if (roomBubbleTableViewCell.attachmentView || roomBubbleTableViewCell.messageTextView) + { + CGFloat x = 0; + CGFloat y = selectedComponenContentViewYOffset + selectedComponentPositionY; + CGFloat width = roomBubbleTableViewCell.contentView.frame.size.width; + + componentFrame = CGRectMake(x, y, width, selectedComponentHeight); + } + else + { + componentFrame = roomBubbleTableViewCell.bounds; + } + + return componentFrame; +} + ++ (CGFloat)attachmentBubbleCellHeightForCellData:(MXKCellData *)cellData withMaximumWidth:(CGFloat)maxWidth +{ + MXKRoomBubbleTableViewCell* cell = [self cellWithOriginalXib]; + CGFloat rowHeight = 0; + + RoomBubbleCellData *bubbleData; + + if ([cellData isKindOfClass:[RoomBubbleCellData class]]) + { + bubbleData = (RoomBubbleCellData*)cellData; + } + + if (bubbleData && cell.attachmentView && bubbleData.isAttachmentWithThumbnail) + { + // retrieve the suggested image view height + rowHeight = bubbleData.contentSize.height; + + // Check here the minimum height defined in cell view for text message + if (cell.attachViewMinHeightConstraint && rowHeight < cell.attachViewMinHeightConstraint.constant) + { + rowHeight = cell.attachViewMinHeightConstraint.constant; + } + + // Finalize the row height by adding the vertical constraints. + + rowHeight += cell.attachViewTopConstraint.constant; + + CGFloat additionalHeight = bubbleData.additionalContentHeight; + + if (additionalHeight) + { + rowHeight += additionalHeight; + } + else + { + rowHeight += cell.attachViewBottomConstraint.constant; + } + } + + return rowHeight; +} + #pragma mark - User actions - (IBAction)onEditButtonPressed:(id)sender @@ -460,7 +696,7 @@ NSString *const kMXKRoomBubbleCellTapOnReceiptsContainer = @"kMXKRoomBubbleCellT // Define 'Edit' button frame UIImage *editIcon = [UIImage imageNamed:@"edit_icon"]; - CGFloat editBtnPosX = self.bubbleInfoContainer.frame.size.width - VECTOR_ROOMBUBBLETABLEVIEWCELL_TIMELABEL_WIDTH - 22 - editIcon.size.width / 2; + CGFloat editBtnPosX = self.bubbleInfoContainer.frame.size.width - RoomBubbleCellLayout.timestampLabelWidth - 22 - editIcon.size.width / 2; CGFloat editBtnPosY = isFirstDisplayedComponent ? -13 : component.position.y + self.msgTextViewTopConstraint.constant - self.bubbleInfoContainerTopConstraint.constant - 13; UIButton *editButton = [[UIButton alloc] initWithFrame:CGRectMake(editBtnPosX, editBtnPosY, 44, 44)]; diff --git a/Riot/Categories/UIGestureRecognizer.swift b/Riot/Categories/UIGestureRecognizer.swift new file mode 100644 index 000000000..26dd05082 --- /dev/null +++ b/Riot/Categories/UIGestureRecognizer.swift @@ -0,0 +1,28 @@ +/* + Copyright 2019 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 + +extension UIGestureRecognizer { + + func vc_isTouchingInside(view: UIView? = nil) -> Bool { + guard let view = view ?? self.view else { + return false + } + let touchedLocation = self.location(in: view) + return view.bounds.contains(touchedLocation) + } +} diff --git a/Riot/Categories/UIImage.swift b/Riot/Categories/UIImage.swift new file mode 100644 index 000000000..9c6847645 --- /dev/null +++ b/Riot/Categories/UIImage.swift @@ -0,0 +1,51 @@ +/* + Copyright 2019 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 + +extension UIImage { + + class func vc_image(from color: UIColor, size: CGSize = CGSize(width: 1, height: 1)) -> UIImage? { + let rect = CGRect(x: 0, y: 0, width: size.width, height: size.height) + UIGraphicsBeginImageContext(rect.size) + let context = UIGraphicsGetCurrentContext() + + context?.setFillColor(color.cgColor) + context?.fill(rect) + + var image = UIGraphicsGetImageFromCurrentImageContext() + UIGraphicsEndImageContext() + + UIGraphicsBeginImageContext(size) + image?.draw(in: rect) + image = UIGraphicsGetImageFromCurrentImageContext() + UIGraphicsEndImageContext() + + return image + } + + func vc_tintedImage(usingColor tintColor: UIColor) -> UIImage? { + UIGraphicsBeginImageContextWithOptions(self.size, false, self.scale) + let drawRect = CGRect(x: 0, y: 0, width: self.size.width, height: self.size.height) + + self.draw(in: drawRect) + tintColor.set() + UIRectFillUsingBlendMode(drawRect, .sourceAtop) + let tintedImage: UIImage? = UIGraphicsGetImageFromCurrentImageContext() + UIGraphicsEndImageContext() + return tintedImage + } +} diff --git a/Riot/Categories/UIStackView.swift b/Riot/Categories/UIStackView.swift new file mode 100644 index 000000000..e1ebb800b --- /dev/null +++ b/Riot/Categories/UIStackView.swift @@ -0,0 +1,28 @@ +/* + Copyright 2019 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 + +extension UIStackView { + + func vc_removeAllSubviews() { + let subviews = self.arrangedSubviews + for subview in subviews { + self.removeArrangedSubview(subview) + subview.removeFromSuperview() + } + } +} diff --git a/Riot/Categories/UITouch.swift b/Riot/Categories/UITouch.swift new file mode 100644 index 000000000..be7e8307e --- /dev/null +++ b/Riot/Categories/UITouch.swift @@ -0,0 +1,28 @@ +/* + Copyright 2019 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 + +extension UITouch { + + func vc_isInside(view: UIView? = nil) -> Bool { + guard let view = view ?? self.view else { + return false + } + let touchedLocation = self.location(in: view) + return view.bounds.contains(touchedLocation) + } +} diff --git a/Riot/Generated/Images.swift b/Riot/Generated/Images.swift index 486035dc0..5007a2609 100644 --- a/Riot/Generated/Images.swift +++ b/Riot/Generated/Images.swift @@ -75,6 +75,10 @@ internal enum Asset { internal static let scrolldown = ImageAsset(name: "scrolldown") internal static let scrollup = ImageAsset(name: "scrollup") internal static let typing = ImageAsset(name: "typing") + internal static let roomContextMenuCopy = ImageAsset(name: "room_context_menu_copy") + internal static let roomContextMenuEdit = ImageAsset(name: "room_context_menu_edit") + internal static let roomContextMenuMore = ImageAsset(name: "room_context_menu_more") + internal static let roomContextMenuReply = ImageAsset(name: "room_context_menu_reply") internal static let uploadIcon = ImageAsset(name: "upload_icon") internal static let voiceCallIcon = ImageAsset(name: "voice_call_icon") internal static let addParticipant = ImageAsset(name: "add_participant") diff --git a/Riot/Generated/Storyboards.swift b/Riot/Generated/Storyboards.swift index 5db46ea18..b8647542a 100644 --- a/Riot/Generated/Storyboards.swift +++ b/Riot/Generated/Storyboards.swift @@ -37,6 +37,11 @@ internal enum StoryboardScene { internal static let initialScene = InitialSceneType(storyboard: DeviceVerificationVerifyViewController.self) } + internal enum EditHistoryViewController: StoryboardType { + internal static let storyboardName = "EditHistoryViewController" + + internal static let initialScene = InitialSceneType(storyboard: EditHistoryViewController.self) + } internal enum KeyBackupRecoverFromPassphraseViewController: StoryboardType { internal static let storyboardName = "KeyBackupRecoverFromPassphraseViewController" @@ -72,6 +77,11 @@ internal enum StoryboardScene { internal static let initialScene = InitialSceneType(storyboard: KeyBackupSetupSuccessFromRecoveryKeyViewController.self) } + internal enum RoomContextualMenuViewController: StoryboardType { + internal static let storyboardName = "RoomContextualMenuViewController" + + internal static let initialScene = InitialSceneType(storyboard: RoomContextualMenuViewController.self) + } internal enum SimpleScreenTemplateViewController: StoryboardType { internal static let storyboardName = "SimpleScreenTemplateViewController" diff --git a/Riot/Generated/Strings.swift b/Riot/Generated/Strings.swift index 87287b665..0738f3685 100644 --- a/Riot/Generated/Strings.swift +++ b/Riot/Generated/Strings.swift @@ -334,6 +334,10 @@ internal enum VectorL10n { internal static var cancel: String { return VectorL10n.tr("Vector", "cancel") } + /// Close + internal static var close: String { + return VectorL10n.tr("Vector", "close") + } /// collapse internal static var collapse: String { return VectorL10n.tr("Vector", "collapse") @@ -482,7 +486,7 @@ internal enum VectorL10n { internal static var deviceVerificationEmojiCat: String { return VectorL10n.tr("Vector", "device_verification_emoji_cat") } - /// Class + /// Clock internal static var deviceVerificationEmojiClock: String { return VectorL10n.tr("Vector", "device_verification_emoji_clock") } @@ -574,6 +578,10 @@ internal enum VectorL10n { internal static var deviceVerificationEmojiLion: String { return VectorL10n.tr("Vector", "device_verification_emoji_lion") } + /// Lock + internal static var deviceVerificationEmojiLock: String { + return VectorL10n.tr("Vector", "device_verification_emoji_lock") + } /// Moon internal static var deviceVerificationEmojiMoon: String { return VectorL10n.tr("Vector", "device_verification_emoji_moon") @@ -586,10 +594,6 @@ internal enum VectorL10n { internal static var deviceVerificationEmojiOctopus: String { return VectorL10n.tr("Vector", "device_verification_emoji_octopus") } - /// Padlock - internal static var deviceVerificationEmojiPadlock: String { - return VectorL10n.tr("Vector", "device_verification_emoji_padlock") - } /// Panda internal static var deviceVerificationEmojiPanda: String { return VectorL10n.tr("Vector", "device_verification_emoji_panda") @@ -886,6 +890,10 @@ internal enum VectorL10n { internal static func eventFormatterMemberUpdates(_ p1: Int) -> String { return VectorL10n.tr("Vector", "event_formatter_member_updates", p1) } + /// (edited) + internal static var eventFormatterMessageEditedMention: String { + return VectorL10n.tr("Vector", "event_formatter_message_edited_mention") + } /// Re-request encryption keys internal static var eventFormatterRerequestKeysPart1Link: String { return VectorL10n.tr("Vector", "event_formatter_rerequest_keys_part1_link") @@ -902,6 +910,14 @@ internal enum VectorL10n { internal static func eventFormatterWidgetRemoved(_ p1: String, _ p2: String) -> String { return VectorL10n.tr("Vector", "event_formatter_widget_removed", p1, p2) } + /// File upload + internal static var fileUploadErrorTitle: String { + return VectorL10n.tr("Vector", "file_upload_error_title") + } + /// File type not supported. + internal static var fileUploadErrorUnsupportedFileTypeMessage: String { + return VectorL10n.tr("Vector", "file_upload_error_unsupported_file_type_message") + } /// To continue using the %@ homeserver you must review and agree to the terms and conditions. internal static func gdprConsentNotGivenAlertMessage(_ p1: String) -> String { return VectorL10n.tr("Vector", "gdpr_consent_not_given_alert_message", p1) @@ -1338,6 +1354,14 @@ internal enum VectorL10n { internal static var retry: String { return VectorL10n.tr("Vector", "retry") } + /// Reply + internal static var roomActionReply: String { + return VectorL10n.tr("Vector", "room_action_reply") + } + /// Send file + internal static var roomActionSendFile: String { + return VectorL10n.tr("Vector", "room_action_send_file") + } /// Send photo or video internal static var roomActionSendPhotoOrVideo: String { return VectorL10n.tr("Vector", "room_action_send_photo_or_video") @@ -1694,6 +1718,10 @@ internal enum VectorL10n { internal static var roomEventActionDelete: String { return VectorL10n.tr("Vector", "room_event_action_delete") } + /// Edit + internal static var roomEventActionEdit: String { + return VectorL10n.tr("Vector", "room_event_action_edit") + } /// Reason for kicking this user internal static var roomEventActionKickPromptReason: String { return VectorL10n.tr("Vector", "room_event_action_kick_prompt_reason") @@ -1710,10 +1738,22 @@ internal enum VectorL10n { internal static var roomEventActionQuote: String { return VectorL10n.tr("Vector", "room_event_action_quote") } + /// Show all + internal static var roomEventActionReactionShowAll: String { + return VectorL10n.tr("Vector", "room_event_action_reaction_show_all") + } + /// Show less + internal static var roomEventActionReactionShowLess: String { + return VectorL10n.tr("Vector", "room_event_action_reaction_show_less") + } /// Remove internal static var roomEventActionRedact: String { return VectorL10n.tr("Vector", "room_event_action_redact") } + /// Reply + internal static var roomEventActionReply: String { + return VectorL10n.tr("Vector", "room_event_action_reply") + } /// Report content internal static var roomEventActionReport: String { return VectorL10n.tr("Vector", "room_event_action_report") @@ -1762,6 +1802,10 @@ internal enum VectorL10n { internal static func roomManyUsersAreTyping(_ p1: String, _ p2: String) -> String { return VectorL10n.tr("Vector", "room_many_users_are_typing", p1, p2) } + /// Message edits + internal static var roomMessageEditsHistoryTitle: String { + return VectorL10n.tr("Vector", "room_message_edits_history_title") + } /// Send a message (unencrypted)… internal static var roomMessagePlaceholder: String { return VectorL10n.tr("Vector", "room_message_placeholder") @@ -2446,6 +2490,10 @@ internal enum VectorL10n { internal static var settingsLabsE2eEncryptionPromptMessage: String { return VectorL10n.tr("Vector", "settings_labs_e2e_encryption_prompt_message") } + /// React to messages with emoji + internal static var settingsLabsMessageReaction: String { + return VectorL10n.tr("Vector", "settings_labs_message_reaction") + } /// Lazy load rooms members internal static var settingsLabsRoomMembersLazyLoading: String { return VectorL10n.tr("Vector", "settings_labs_room_members_lazy_loading") @@ -2798,6 +2846,14 @@ internal enum VectorL10n { internal static var widgetIntegrationUnableToCreate: String { return VectorL10n.tr("Vector", "widget_integration_unable_to_create") } + /// Failed to connect to integrations server + internal static var widgetIntegrationsServerFailedToConnect: String { + return VectorL10n.tr("Vector", "widget_integrations_server_failed_to_connect") + } + /// No integrations server configured + internal static var widgetNoIntegrationsServerConfigured: String { + return VectorL10n.tr("Vector", "widget_no_integrations_server_configured") + } /// You need permission to manage widgets in this room internal static var widgetNoPowerToManage: String { return VectorL10n.tr("Vector", "widget_no_power_to_manage") diff --git a/Riot/Managers/Analytics/Analytics.m b/Riot/Managers/Analytics/Analytics.m index 552e9e0ab..4491c4158 100644 --- a/Riot/Managers/Analytics/Analytics.m +++ b/Riot/Managers/Analytics/Analytics.m @@ -31,7 +31,30 @@ NSString *const kAnalyticsE2eCategory = @"E2E"; NSString *const kAnalyticsE2eDecryptionFailureAction = @"Decryption failure"; -@import PiwikTracker; +@import MatomoTracker; + +@interface MatomoTracker (MatomoTrackerMigration) ++ (MatomoTracker *)shared; + +- (void)migrateFromFourPointFourSharedInstance; +@end + +@implementation MatomoTracker (MatomoTrackerMigration) ++ (MatomoTracker *)shared +{ + NSDictionary *piwikConfig = [[NSUserDefaults standardUserDefaults] objectForKey:@"piwik"]; + MatomoTracker *matomoTracker = [[MatomoTracker alloc] initWithSiteId:piwikConfig[@"siteId"] baseURL:[NSURL URLWithString:piwikConfig[@"url"]] userAgent:@"iOSMatomoTracker"]; + [matomoTracker migrateFromFourPointFourSharedInstance]; + return matomoTracker; +} + +- (void)migrateFromFourPointFourSharedInstance +{ + if ([[NSUserDefaults standardUserDefaults] boolForKey:@"migratedFromFourPointFourSharedInstance"]) return; + [self copyFromOldSharedInstance]; + [[NSUserDefaults standardUserDefaults] setBool:true forKey:@"migratedFromFourPointFourSharedInstance"]; +} +@end @implementation Analytics @@ -49,29 +72,24 @@ NSString *const kAnalyticsE2eDecryptionFailureAction = @"Decryption failure"; - (void)start { - NSDictionary *piwikConfig = [[NSUserDefaults standardUserDefaults] objectForKey:@"piwik"]; - [PiwikTracker configureSharedInstanceWithSiteID:piwikConfig[@"siteId"] - baseURL:[NSURL URLWithString:piwikConfig[@"url"]] - userAgent:@"iOSPiwikTracker"]; - // Check whether the user has enabled the sending of crash reports. if (RiotSettings.shared.enableCrashReport) { - [PiwikTracker shared].isOptedOut = NO; + [MatomoTracker shared].isOptedOut = NO; - [[PiwikTracker shared] setCustomVariableWithIndex:1 name:@"App Platform" value:@"iOS Platform"]; - [[PiwikTracker shared] setCustomVariableWithIndex:2 name:@"App Version" value:[AppDelegate theDelegate].appVersion]; + [[MatomoTracker shared] setCustomVariableWithIndex:1 name:@"App Platform" value:@"iOS Platform"]; + [[MatomoTracker shared] setCustomVariableWithIndex:2 name:@"App Version" value:[AppDelegate theDelegate].appVersion]; // The language is either the one selected by the user within the app // or, else, the one configured by the OS NSString *language = [NSBundle mxk_language] ? [NSBundle mxk_language] : [[NSBundle mainBundle] preferredLocalizations][0]; - [[PiwikTracker shared] setCustomVariableWithIndex:4 name:@"Chosen Language" value:language]; + [[MatomoTracker shared] setCustomVariableWithIndex:4 name:@"Chosen Language" value:language]; MXKAccount* account = [MXKAccountManager sharedManager].activeAccounts.firstObject; if (account) { - [[PiwikTracker shared] setCustomVariableWithIndex:7 name:@"Homeserver URL" value:account.mxCredentials.homeServer]; - [[PiwikTracker shared] setCustomVariableWithIndex:8 name:@"Identity Server URL" value:account.identityServerURL]; + [[MatomoTracker shared] setCustomVariableWithIndex:7 name:@"Homeserver URL" value:account.mxCredentials.homeServer]; + [[MatomoTracker shared] setCustomVariableWithIndex:8 name:@"Identity Server URL" value:account.identityServerURL]; } // TODO: We should also track device and os version @@ -83,20 +101,20 @@ NSString *const kAnalyticsE2eDecryptionFailureAction = @"Decryption failure"; #ifdef DEBUG // Disable analytics in debug as it pollutes stats - [PiwikTracker shared].isOptedOut = YES; + [MatomoTracker shared].isOptedOut = YES; #endif } else { NSLog(@"[AppDelegate] The user decided to not send analytics"); - [PiwikTracker shared].isOptedOut = YES; + [MatomoTracker shared].isOptedOut = YES; [MXLogger logCrashes:NO]; } } - (void)stop { - [PiwikTracker shared].isOptedOut = YES; + [MatomoTracker shared].isOptedOut = YES; [MXLogger logCrashes:NO]; } @@ -106,20 +124,20 @@ NSString *const kAnalyticsE2eDecryptionFailureAction = @"Decryption failure"; NSString *appName = [[NSBundle mainBundle] infoDictionary][@"CFBundleDisplayName"]; NSString *appVersion = [AppDelegate theDelegate].appVersion; - [[PiwikTracker shared] trackWithView:@[@"ios", appName, appVersion, screenName] + [[MatomoTracker shared] trackWithView:@[@"ios", appName, appVersion, screenName] url:nil]; } - (void)dispatch { - [[PiwikTracker shared] dispatch]; + [[MatomoTracker shared] dispatch]; } - (void)trackLaunchScreenDisplayDuration:(NSTimeInterval)seconds { NSString *action = [NSString stringWithFormat:kAnalyticsMetricsActionPattern, kMXAnalyticsStartupCategory]; - [[PiwikTracker shared] trackWithEventWithCategory:kAnalyticsMetricsCategory + [[MatomoTracker shared] trackWithEventWithCategory:kAnalyticsMetricsCategory action:action name:kMXAnalyticsStartupLaunchScreen number:@(seconds * 1000) @@ -132,7 +150,7 @@ NSString *const kAnalyticsE2eDecryptionFailureAction = @"Decryption failure"; { NSString *action = [NSString stringWithFormat:kAnalyticsMetricsActionPattern, kMXAnalyticsStartupCategory]; - [[PiwikTracker shared] trackWithEventWithCategory:kAnalyticsMetricsCategory + [[MatomoTracker shared] trackWithEventWithCategory:kAnalyticsMetricsCategory action:action name:kMXAnalyticsStartupStorePreload number:@(seconds * 1000) @@ -143,7 +161,7 @@ NSString *const kAnalyticsE2eDecryptionFailureAction = @"Decryption failure"; { NSString *action = [NSString stringWithFormat:kAnalyticsMetricsActionPattern, kMXAnalyticsStartupCategory]; - [[PiwikTracker shared] trackWithEventWithCategory:kAnalyticsMetricsCategory + [[MatomoTracker shared] trackWithEventWithCategory:kAnalyticsMetricsCategory action:action name:kMXAnalyticsStartupMountData number:@(seconds * 1000) @@ -154,7 +172,7 @@ NSString *const kAnalyticsE2eDecryptionFailureAction = @"Decryption failure"; { NSString *action = [NSString stringWithFormat:kAnalyticsMetricsActionPattern, kMXAnalyticsStartupCategory]; - [[PiwikTracker shared] trackWithEventWithCategory:kAnalyticsMetricsCategory + [[MatomoTracker shared] trackWithEventWithCategory:kAnalyticsMetricsCategory action:action name:isInitial ? kMXAnalyticsStartupInititialSync : kMXAnalyticsStartupIncrementalSync number:@(seconds * 1000) @@ -165,7 +183,7 @@ NSString *const kAnalyticsE2eDecryptionFailureAction = @"Decryption failure"; { NSString *action = [NSString stringWithFormat:kAnalyticsMetricsActionPattern, kMXAnalyticsStatsCategory]; - [[PiwikTracker shared] trackWithEventWithCategory:kAnalyticsMetricsCategory + [[MatomoTracker shared] trackWithEventWithCategory:kAnalyticsMetricsCategory action:action name:kMXAnalyticsStatsRooms number:@(roomCount) @@ -178,7 +196,7 @@ NSString *const kAnalyticsE2eDecryptionFailureAction = @"Decryption failure"; { for (NSString *reason in failuresCounts) { - [[PiwikTracker shared] trackWithEventWithCategory:kAnalyticsE2eCategory + [[MatomoTracker shared] trackWithEventWithCategory:kAnalyticsE2eCategory action:kAnalyticsE2eDecryptionFailureAction name:reason number:failuresCounts[reason] diff --git a/Riot/Managers/Theme/Themable.swift b/Riot/Managers/Theme/Themable.swift index 3764b24b7..f4f59f1e9 100644 --- a/Riot/Managers/Theme/Themable.swift +++ b/Riot/Managers/Theme/Themable.swift @@ -16,6 +16,6 @@ import Foundation -protocol Themable: class { +@objc protocol Themable: class { func update(theme: Theme) } diff --git a/Riot/Managers/Theme/Theme.swift b/Riot/Managers/Theme/Theme.swift index 375dd3913..c8a10674a 100644 --- a/Riot/Managers/Theme/Theme.swift +++ b/Riot/Managers/Theme/Theme.swift @@ -38,6 +38,7 @@ import UIKit var textSecondaryColor: UIColor { get } var tintColor: UIColor { get } + var tintBackgroundColor: UIColor { get } var unreadRoomIndentColor: UIColor { get } diff --git a/Riot/Managers/Theme/Themes/DarkTheme.swift b/Riot/Managers/Theme/Themes/DarkTheme.swift index 1b3651a6e..c98b27a24 100644 --- a/Riot/Managers/Theme/Themes/DarkTheme.swift +++ b/Riot/Managers/Theme/Themes/DarkTheme.swift @@ -30,8 +30,8 @@ class DarkTheme: NSObject, Theme { var searchBackgroundColor: UIColor = UIColor(rgb: 0x181B21) var searchPlaceholderColor: UIColor = UIColor(rgb: 0x61708B) - var headerBackgroundColor: UIColor = UIColor(rgb: 0x15171B) - var headerBorderColor: UIColor = UIColor(rgb: 0x22262E) + var headerBackgroundColor: UIColor = UIColor(rgb: 0x22262E) + var headerBorderColor: UIColor = UIColor(rgb: 0x181B21) var headerTextPrimaryColor: UIColor = UIColor(rgb: 0xA1B2D1) var headerTextSecondaryColor: UIColor = UIColor(rgb: 0xC8C8CD) @@ -39,6 +39,7 @@ class DarkTheme: NSObject, Theme { var textSecondaryColor: UIColor = UIColor(rgb: 0xA1B2D1) var tintColor: UIColor = UIColor(rgb: 0x03B381) + var tintBackgroundColor: UIColor = UIColor(rgb: 0x1F6954) var unreadRoomIndentColor: UIColor = UIColor(rgb: 0x2E3648) var lineBreakColor: UIColor = UIColor(rgb: 0x61708B) diff --git a/Riot/Managers/Theme/Themes/DefaultTheme.swift b/Riot/Managers/Theme/Themes/DefaultTheme.swift index d1bcaf9f8..7f154f232 100644 --- a/Riot/Managers/Theme/Themes/DefaultTheme.swift +++ b/Riot/Managers/Theme/Themes/DefaultTheme.swift @@ -30,7 +30,7 @@ class DefaultTheme: NSObject, Theme { var searchBackgroundColor: UIColor = UIColor(rgb: 0xFFFFFF) var searchPlaceholderColor: UIColor = UIColor(rgb: 0x61708B) - var headerBackgroundColor: UIColor = UIColor(rgb: 0xF2F5F8) + var headerBackgroundColor: UIColor = UIColor(rgb: 0xF3F8FD) var headerBorderColor: UIColor = UIColor(rgb: 0xE9EDF1) var headerTextPrimaryColor: UIColor = UIColor(rgb: 0x61708B) var headerTextSecondaryColor: UIColor = UIColor(rgb: 0xC8C8CD) @@ -39,6 +39,7 @@ class DefaultTheme: NSObject, Theme { var textSecondaryColor: UIColor = UIColor(rgb: 0x9E9E9E) var tintColor: UIColor = UIColor(rgb: 0x03B381) + var tintBackgroundColor: UIColor = UIColor(rgb: 0xe9fff9) var unreadRoomIndentColor: UIColor = UIColor(rgb: 0x2E3648) var lineBreakColor: UIColor = UIColor(rgb: 0xEEEFEF) diff --git a/Riot/Managers/Widgets/Widget.m b/Riot/Managers/Widgets/Widget.m index 482f54e9c..1438701b3 100644 --- a/Riot/Managers/Widgets/Widget.m +++ b/Riot/Managers/Widgets/Widget.m @@ -1,5 +1,6 @@ /* Copyright 2017 Vector Creations Ltd + Copyright 2019 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. @@ -100,7 +101,7 @@ _widgetId]]; // Check if their scalar token must added - if ([WidgetManager isScalarUrl:widgetUrl]) + if ([[WidgetManager sharedManager] isScalarUrl:widgetUrl forUser:userId]) { return [[WidgetManager sharedManager] getScalarTokenForMXSession:_mxSession validate:NO success:^(NSString *scalarToken) { // Add the user scalar token diff --git a/Riot/Managers/Widgets/WidgetManager.h b/Riot/Managers/Widgets/WidgetManager.h index 83b41ac2f..a15928789 100644 --- a/Riot/Managers/Widgets/WidgetManager.h +++ b/Riot/Managers/Widgets/WidgetManager.h @@ -1,5 +1,6 @@ /* Copyright 2017 Vector Creations Ltd + Copyright 2019 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. @@ -20,6 +21,8 @@ #import "Widget.h" +@class WidgetManagerConfig; + /** The type of matrix event used for matrix widgets. */ @@ -51,7 +54,9 @@ FOUNDATION_EXPORT NSString *const WidgetManagerErrorDomain; typedef enum : NSUInteger { WidgetManagerErrorCodeNotEnoughPower, - WidgetManagerErrorCodeCreationFailed + WidgetManagerErrorCodeCreationFailed, + WidgetManagerErrorCodeNoIntegrationsServerConfigured, + WidgetManagerErrorCodeFailedToConnectToIntegrationsServer } WidgetManagerErrorCode; @@ -180,6 +185,30 @@ WidgetManagerErrorCode; #pragma mark - Modular interface +/** + Get the integration manager configuration for a user. + + @param userId the user id. + @return the integration manager configuration. + */ +- (WidgetManagerConfig*)configForUser:(NSString*)userId; + +/** + Store the integration manager configuration for a user. + + @param the integration manager configuration. + @param userId the user id. + */ +- (void)setConfig:(WidgetManagerConfig*)config forUser:(NSString*)userId; + +/** + Check if the user has URLs for an integration manager configured. + + @param userId the user id. + @return YES if they have URLs for an integration manager. + */ +- (BOOL)hasIntegrationManagerForUser:(NSString*)userId; + /** Make sure there is a scalar token for the given Matrix session. @@ -201,8 +230,9 @@ WidgetManagerErrorCode; Returns true if specified url is a scalar URL, typically https://scalar.vector.im/api @param urlString the URL to check. + @param userId the user id. @return YES if specified URL is a scalar URL. */ -+ (BOOL)isScalarUrl:(NSString*)urlString; +- (BOOL)isScalarUrl:(NSString*)urlString forUser:(NSString*)userId; @end diff --git a/Riot/Managers/Widgets/WidgetManager.m b/Riot/Managers/Widgets/WidgetManager.m index 8f761bf08..a6d2bdec1 100644 --- a/Riot/Managers/Widgets/WidgetManager.m +++ b/Riot/Managers/Widgets/WidgetManager.m @@ -1,5 +1,6 @@ /* Copyright 2017 Vector Creations Ltd + Copyright 2019 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. @@ -16,6 +17,8 @@ #import "WidgetManager.h" +#import "Riot-Swift.h" + #import #pragma mark - Contants @@ -46,7 +49,7 @@ NSString *const WidgetManagerErrorDomain = @"WidgetManagerErrorDomain"; NSMutableDictionary*> *failureBlockForWidgetCreation; // User id -> scalar token - NSMutableDictionary *scalarTokens; + NSMutableDictionary *configs; } @end @@ -74,12 +77,7 @@ NSString *const WidgetManagerErrorDomain = @"WidgetManagerErrorDomain"; successBlockForWidgetCreation = [NSMutableDictionary dictionary]; failureBlockForWidgetCreation = [NSMutableDictionary dictionary]; - [self load]; - - if (!scalarTokens) - { - scalarTokens = [NSMutableDictionary dictionary]; - } + [self loadConfigs]; } return self; } @@ -261,6 +259,15 @@ NSString *const WidgetManagerErrorDomain = @"WidgetManagerErrorDomain"; success:(void (^)(Widget *jitsiWidget))success failure:(void (^)(NSError *error))failure { + NSString *userId = room.mxSession.myUser.userId; + WidgetManagerConfig *config = [self configForUser:userId]; + if (!config.hasUrls) + { + NSLog(@"[WidgetManager] createJitsiWidgetInRoom: Error: no Integrations Manager API URL for user %@", userId); + failure(self.errorForNonConfiguredIntegrationManager); + return nil; + } + // Build data for a jitsi widget NSString *widgetId = [NSString stringWithFormat:@"%@_%@_%@", kWidgetTypeJitsi, room.mxSession.myUser.userId, @((uint64_t)([[NSDate date] timeIntervalSince1970] * 1000))]; @@ -274,8 +281,7 @@ NSString *const WidgetManagerErrorDomain = @"WidgetManagerErrorDomain"; // TODO: This url should come from modular API // Note: this url can be used as is inside a web container (like iframe for Riot-web) // Riot-iOS does not directly use it but extracts params from it (see `[JitsiViewController openWidget:withVideo:]`) - NSString *modularRestUrl = [[NSUserDefaults standardUserDefaults] objectForKey:@"integrationsRestUrl"]; - NSString *url = [NSString stringWithFormat:@"%@/widgets/jitsi.html?confId=%@&isAudioConf=%@&displayName=$matrix_display_name&avatarUrl=$matrix_avatar_url&email=$matrix_user_id@", modularRestUrl, confId, video ? @"false" : @"true"]; + NSString *url = [NSString stringWithFormat:@"%@/widgets/jitsi.html?confId=%@&isAudioConf=%@&displayName=$matrix_display_name&avatarUrl=$matrix_avatar_url&email=$matrix_user_id@", config.apiUrl, confId, video ? @"false" : @"true"]; return [self createWidget:widgetId withContent:@{ @@ -435,12 +441,30 @@ NSString *const WidgetManagerErrorDomain = @"WidgetManagerErrorDomain"; - (void)deleteDataForUser:(NSString *)userId { - [scalarTokens removeObjectForKey:userId]; - [self save]; + [configs removeObjectForKey:userId]; + [self saveConfigs]; } #pragma mark - Modular interface +- (WidgetManagerConfig*)configForUser:(NSString*)userId +{ + // Return a default config by default + return configs[userId] ? configs[userId] : [WidgetManagerConfig new]; +} + +- (BOOL)hasIntegrationManagerForUser:(NSString*)userId +{ + return [self configForUser:userId].hasUrls; +} + +- (void)setConfig:(WidgetManagerConfig*)config forUser:(NSString*)userId +{ + configs[userId] = config; + [self saveConfigs]; +} + + - (MXHTTPOperation *)getScalarTokenForMXSession:(MXSession*)mxSession validate:(BOOL)validate success:(void (^)(NSString *scalarToken))success @@ -487,15 +511,22 @@ NSString *const WidgetManagerErrorDomain = @"WidgetManagerErrorDomain"; failure:(void (^)(NSError *error))failure { MXHTTPOperation *operation; + NSString *userId = mxSession.myUser.userId; + + WidgetManagerConfig *config = [self configForUser:userId]; + if (!config.hasUrls) + { + NSLog(@"[WidgetManager] registerForScalarToken: Error: no Integrations Manager API URL for user %@", mxSession.myUser.userId); + failure(self.errorForNonConfiguredIntegrationManager); + return nil; + } MXWeakify(self); operation = [mxSession.matrixRestClient openIdToken:^(MXOpenIdToken *tokenObject) { MXStrongifyAndReturnIfNil(self); // Exchange the token for a scalar token - NSString *modularRestUrl = [[NSUserDefaults standardUserDefaults] objectForKey:@"integrationsRestUrl"]; - - MXHTTPClient *httpClient = [[MXHTTPClient alloc] initWithBaseURL:modularRestUrl andOnUnrecognizedCertificateBlock:nil]; + MXHTTPClient *httpClient = [[MXHTTPClient alloc] initWithBaseURL:config.apiUrl andOnUnrecognizedCertificateBlock:nil]; MXHTTPOperation *operation2 = [httpClient requestWithMethod:@"POST" @@ -506,9 +537,10 @@ NSString *const WidgetManagerErrorDomain = @"WidgetManagerErrorDomain"; NSString *scalarToken; MXJSONModelSetString(scalarToken, JSONResponse[@"scalar_token"]) - self->scalarTokens[mxSession.myUser.userId] = scalarToken; + config.scalarToken = scalarToken; - [self save]; + self->configs[userId] = config; + [self saveConfigs]; if (success) { @@ -516,10 +548,17 @@ NSString *const WidgetManagerErrorDomain = @"WidgetManagerErrorDomain"; } } failure:^(NSError *error) { - NSLog(@"[WidgetManager] registerForScalarToken. Error in modular/register request"); + NSLog(@"[WidgetManager] registerForScalarToken: Failed to register. Error: %@", error); if (failure) { + // Specialise the error + NSError *error = [NSError errorWithDomain:WidgetManagerErrorDomain + code:WidgetManagerErrorCodeFailedToConnectToIntegrationsServer + userInfo:@{ + NSLocalizedDescriptionKey: NSLocalizedStringFromTable(@"widget_integrations_server_failed_to_connect", @"Vector", nil) + }]; + failure(error); } }]; @@ -542,8 +581,17 @@ NSString *const WidgetManagerErrorDomain = @"WidgetManagerErrorDomain"; complete:(void (^)(BOOL valid))complete failure:(void (^)(NSError *error))failure { - NSString *modularRestUrl = [[NSUserDefaults standardUserDefaults] objectForKey:@"integrationsRestUrl"]; - MXHTTPClient *httpClient = [[MXHTTPClient alloc] initWithBaseURL:modularRestUrl andOnUnrecognizedCertificateBlock:nil]; + NSString *userId = mxSession.myUser.userId; + + WidgetManagerConfig *config = [self configForUser:userId]; + if (!config.hasUrls) + { + NSLog(@"[WidgetManager] validateScalarToken: Error: no Integrations Manager API URL for user %@", mxSession.myUser.userId); + failure(self.errorForNonConfiguredIntegrationManager); + return nil; + } + + MXHTTPClient *httpClient = [[MXHTTPClient alloc] initWithBaseURL:config.apiUrl andOnUnrecognizedCertificateBlock:nil]; return [httpClient requestWithMethod:@"GET" path:[NSString stringWithFormat:@"account?v=1.1&scalar_token=%@", scalarToken] @@ -579,14 +627,19 @@ NSString *const WidgetManagerErrorDomain = @"WidgetManagerErrorDomain"; }]; } -+ (BOOL)isScalarUrl:(NSString *)urlString +- (BOOL)isScalarUrl:(NSString *)urlString forUser:(NSString*)userId { BOOL isScalarUrl = NO; + // TODO: Do we need to add `integrationsWidgetsUrls` to `WidgetManagerConfig`? NSArray *scalarUrlStrings = [[NSUserDefaults standardUserDefaults] objectForKey:@"integrationsWidgetsUrls"]; if (scalarUrlStrings.count == 0) { - scalarUrlStrings = @[[[NSUserDefaults standardUserDefaults] objectForKey:@"integrationsRestUrl"]]; + NSString *apiUrl = [self configForUser:userId].apiUrl; + if (apiUrl) + { + scalarUrlStrings = @[apiUrl]; + } } for (NSString *scalarUrlString in scalarUrlStrings) @@ -605,20 +658,63 @@ NSString *const WidgetManagerErrorDomain = @"WidgetManagerErrorDomain"; - (NSString *)scalarTokenForMXSession:(MXSession *)mxSession { - return scalarTokens[mxSession.myUser.userId]; + return configs[mxSession.myUser.userId].scalarToken; } -- (void)load -{ - NSUserDefaults *userDefaults = [MXKAppSettings standardAppSettings].sharedUserDefaults; - scalarTokens = [NSMutableDictionary dictionaryWithDictionary:[userDefaults objectForKey:@"scalarTokens"]]; -} - -- (void)save +- (void)loadConfigs { NSUserDefaults *userDefaults = [MXKAppSettings standardAppSettings].sharedUserDefaults; - [userDefaults setObject:scalarTokens forKey:@"scalarTokens"]; + NSDictionary *scalarTokens = [userDefaults objectForKey:@"scalarTokens"]; + if (scalarTokens) + { + // Manage migration to WidgetManagerConfig + configs = [NSMutableDictionary dictionary]; + for (NSString *userId in scalarTokens) + { + NSString *scalarToken = scalarTokens[userId]; + + NSLog(@"[WidgetManager] migrate scalarTokens to integrationManagerConfigs for %@", userId); + + WidgetManagerConfig *config = [WidgetManagerConfig new]; + config.scalarToken = scalarToken; + + configs[userId] = config; + } + + [self saveConfigs]; + [userDefaults removeObjectForKey:@"scalarTokens"]; + } + else + { + NSData *configsData = [userDefaults objectForKey:@"integrationManagerConfigs"]; + if (configsData) + { + configs = [NSMutableDictionary dictionaryWithDictionary:[NSKeyedUnarchiver unarchiveObjectWithData:configsData]]; + } + + if (!configs) + { + configs = [NSMutableDictionary dictionary]; + } + } +} + +- (void)saveConfigs +{ + NSUserDefaults *userDefaults = [MXKAppSettings standardAppSettings].sharedUserDefaults; + [userDefaults setObject:[NSKeyedArchiver archivedDataWithRootObject:configs] + forKey:@"integrationManagerConfigs"]; +} + + +#pragma mark - Errors + +- (NSError*)errorForNonConfiguredIntegrationManager +{ + return [NSError errorWithDomain:WidgetManagerErrorDomain + code:WidgetManagerErrorCodeNoIntegrationsServerConfigured + userInfo:@{NSLocalizedDescriptionKey: NSLocalizedStringFromTable(@"widget_no_integrations_server_configured", @"Vector", nil)}]; } @end diff --git a/Riot/Managers/Widgets/WidgetManagerConfig.swift b/Riot/Managers/Widgets/WidgetManagerConfig.swift new file mode 100644 index 000000000..ba6c3ac36 --- /dev/null +++ b/Riot/Managers/Widgets/WidgetManagerConfig.swift @@ -0,0 +1,77 @@ +/* + Copyright 2019 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 + +/// Configuration for an integration manager. +/// By default, it uses URLs defined in the app settings but they can be overidden. +@objcMembers +class WidgetManagerConfig: NSObject, NSCoding { + + /// The URL for the REST api + let apiUrl: NSString? + /// The URL of the integration manager interface + let uiUrl: NSString? + /// The token if the user has been authenticated + var scalarToken: NSString? + + var hasUrls: Bool { + if apiUrl != nil && uiUrl != nil { + return true + } else { + return false + } + } + + init(apiUrl: NSString?, uiUrl: NSString?) { + self.apiUrl = apiUrl + self.uiUrl = uiUrl + + super.init() + } + + override convenience init () { + // Use app settings as default + let apiUrl = UserDefaults.standard.object(forKey: "integrationsRestUrl") as? NSString + let uiUrl = UserDefaults.standard.object(forKey: "integrationsUiUrl") as? NSString + + self.init(apiUrl: apiUrl, uiUrl: uiUrl) + } + + + /// MARK: - NSCoding + + enum CodingKeys: String { + case apiUrl = "apiUrl" + case uiUrl = "uiUrl" + case scalarToken = "scalarToken" + } + + func encode(with aCoder: NSCoder) { + aCoder.encode(self.apiUrl, forKey: CodingKeys.apiUrl.rawValue) + aCoder.encode(self.uiUrl, forKey: CodingKeys.uiUrl.rawValue) + aCoder.encode(self.scalarToken, forKey: CodingKeys.scalarToken.rawValue) + } + + convenience required init?(coder aDecoder: NSCoder) { + let apiUrl = aDecoder.decodeObject(forKey: CodingKeys.apiUrl.rawValue) as? NSString + let uiUrl = aDecoder.decodeObject(forKey: CodingKeys.uiUrl.rawValue) as? NSString + let scalarToken = aDecoder.decodeObject(forKey: CodingKeys.scalarToken.rawValue) as? NSString + + self.init(apiUrl: apiUrl, uiUrl: uiUrl) + self.scalarToken = scalarToken + } +} diff --git a/Riot/Model/Room/RoomPreviewData.h b/Riot/Model/Room/RoomPreviewData.h index 333912c8f..47797327c 100644 --- a/Riot/Model/Room/RoomPreviewData.h +++ b/Riot/Model/Room/RoomPreviewData.h @@ -50,6 +50,12 @@ */ @property (nonatomic) NSString *eventId; +/** + In case of preview, the server names to try and join through in addition to those + that are automatically chosen. + */ +@property (nonatomic) NSArray *viaServers; + /** Preview information. */ diff --git a/Riot/Modules/Authentication/Views/AuthInputsView.m b/Riot/Modules/Authentication/Views/AuthInputsView.m index 79280d25d..5685a9f43 100644 --- a/Riot/Modules/Authentication/Views/AuthInputsView.m +++ b/Riot/Modules/Authentication/Views/AuthInputsView.m @@ -814,8 +814,24 @@ else if ([self isFlowSupported:kMXLoginFlowTypeTerms] && ![self isFlowCompleted:kMXLoginFlowTypeTerms]) { NSLog(@"[AuthInputsView] Prepare a new terms stage"); - - [self prepareParameters:callback]; + + if (externalRegistrationParameters) + { + [self displayTermsView:^{ + + NSDictionary *parameters = @{ + @"auth": @{ + @"session":self->currentSession.session, + @"type": kMXLoginFlowTypeTerms + } + }; + callback(parameters, nil); + }]; + } + else + { + [self prepareParameters:callback]; + } return; } diff --git a/Riot/Modules/Common/CollectionView/AutosizedCollectionView.swift b/Riot/Modules/Common/CollectionView/AutosizedCollectionView.swift new file mode 100644 index 000000000..10cdbf75c --- /dev/null +++ b/Riot/Modules/Common/CollectionView/AutosizedCollectionView.swift @@ -0,0 +1,31 @@ +/* + Copyright 2019 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 + +/// AutosizedCollectionView is a convenient UICollectionView that makes dynamic sizing easier when using Auto Layout +class AutosizedCollectionView: UICollectionView { + + override var contentSize: CGSize { + didSet { + self.invalidateIntrinsicContentSize() + } + } + + override var intrinsicContentSize: CGSize { + return self.contentSize + } +} diff --git a/Riot/Modules/Common/Recents/RecentsViewController.m b/Riot/Modules/Common/Recents/RecentsViewController.m index 03e582aeb..30a754803 100644 --- a/Riot/Modules/Common/Recents/RecentsViewController.m +++ b/Riot/Modules/Common/Recents/RecentsViewController.m @@ -1815,8 +1815,9 @@ self->currentAlert = nil; [self.activityIndicator startAnimating]; - - self->currentRequest = [self.mainSession joinRoom:roomAliasOrId success:^(MXRoom *room) { + + // TODO + self->currentRequest = [self.mainSession joinRoom:roomAliasOrId viaServers:nil success:^(MXRoom *room) { self->currentRequest = nil; [self.activityIndicator stopAnimating]; diff --git a/Riot/Modules/DeviceVerification/Incoming/DeviceVerificationIncomingViewController.swift b/Riot/Modules/DeviceVerification/Incoming/DeviceVerificationIncomingViewController.swift index 7013886db..99874dc1e 100644 --- a/Riot/Modules/DeviceVerification/Incoming/DeviceVerificationIncomingViewController.swift +++ b/Riot/Modules/DeviceVerification/Incoming/DeviceVerificationIncomingViewController.swift @@ -75,22 +75,6 @@ final class DeviceVerificationIncomingViewController: UIViewController { self.viewModel.viewDelegate = self } - - override func viewWillAppear(_ animated: Bool) { - super.viewWillAppear(animated) - } - - override func viewDidAppear(_ animated: Bool) { - super.viewDidAppear(animated) - } - - override func viewDidDisappear(_ animated: Bool) { - super.viewDidDisappear(animated) - } - - override func viewDidLayoutSubviews() { - super.viewDidLayoutSubviews() - } override var preferredStatusBarStyle: UIStatusBarStyle { return self.theme.statusBarStyle @@ -110,7 +94,9 @@ final class DeviceVerificationIncomingViewController: UIViewController { self.titleLabel.textColor = theme.textPrimaryColor self.description1Label.textColor = theme.textPrimaryColor self.description2Label.textColor = theme.textPrimaryColor - + self.userDisplaynameLabel.textColor = theme.textPrimaryColor + self.deviceIdLabel.textColor = theme.textPrimaryColor + self.continueButtonBackgroundView.backgroundColor = theme.backgroundColor theme.applyStyle(onButton: self.continueButton) } diff --git a/Riot/Modules/DeviceVerification/Start/DeviceVerificationStartViewController.swift b/Riot/Modules/DeviceVerification/Start/DeviceVerificationStartViewController.swift index 9b6d39451..958965cc6 100644 --- a/Riot/Modules/DeviceVerification/Start/DeviceVerificationStartViewController.swift +++ b/Riot/Modules/DeviceVerification/Start/DeviceVerificationStartViewController.swift @@ -71,22 +71,6 @@ final class DeviceVerificationStartViewController: UIViewController { self.viewModel.viewDelegate = self } - - override func viewWillAppear(_ animated: Bool) { - super.viewWillAppear(animated) - } - - override func viewDidAppear(_ animated: Bool) { - super.viewDidAppear(animated) - } - - override func viewDidDisappear(_ animated: Bool) { - super.viewDidDisappear(animated) - } - - override func viewDidLayoutSubviews() { - super.viewDidLayoutSubviews() - } override var preferredStatusBarStyle: UIStatusBarStyle { return self.theme.statusBarStyle diff --git a/Riot/Modules/DeviceVerification/Verify/DeviceVerificationVerifyViewController.swift b/Riot/Modules/DeviceVerification/Verify/DeviceVerificationVerifyViewController.swift index 80e7136bf..61b4e2fd4 100644 --- a/Riot/Modules/DeviceVerification/Verify/DeviceVerificationVerifyViewController.swift +++ b/Riot/Modules/DeviceVerification/Verify/DeviceVerificationVerifyViewController.swift @@ -78,18 +78,6 @@ final class DeviceVerificationVerifyViewController: UIViewController { // Hide back button self.navigationItem.setHidesBackButton(true, animated: animated) } - - override func viewDidAppear(_ animated: Bool) { - super.viewDidAppear(animated) - } - - override func viewDidDisappear(_ animated: Bool) { - super.viewDidDisappear(animated) - } - - override func viewDidLayoutSubviews() { - super.viewDidLayoutSubviews() - } override var preferredStatusBarStyle: UIStatusBarStyle { return self.theme.statusBarStyle diff --git a/Riot/Modules/Integrations/IntegrationManagerViewController.m b/Riot/Modules/Integrations/IntegrationManagerViewController.m index 37bab65fa..ff9df08fe 100644 --- a/Riot/Modules/Integrations/IntegrationManagerViewController.m +++ b/Riot/Modules/Integrations/IntegrationManagerViewController.m @@ -1,5 +1,6 @@ /* Copyright 2017 Vector Creations Ltd + Copyright 2019 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. @@ -18,6 +19,9 @@ #import "WidgetManager.h" +#import "AppDelegate.h" +#import "Riot-Swift.h" + NSString *const kIntegrationManagerMainScreen = nil; NSString *const kIntegrationManagerAddIntegrationScreen = @"add_integ"; @@ -85,8 +89,14 @@ NSString *const kIntegrationManagerAddIntegrationScreen = @"add_integ"; } failure:^(NSError *error) { MXStrongifyAndReturnIfNil(self); + NSLog(@"[IntegraionManagerVS] Cannot open due to missing scalar token. Error: %@", error); + self->operation = nil; [self stopActivityIndicator]; + + [self withdrawViewControllerAnimated:YES completion:^{ + [[AppDelegate theDelegate] showErrorAsAlert:error]; + }]; }]; } } @@ -100,10 +110,12 @@ NSString *const kIntegrationManagerAddIntegrationScreen = @"add_integ"; { NSMutableString *url; + NSString *integrationsUiUrl = [[WidgetManager sharedManager] configForUser:mxSession.myUser.userId].uiUrl; + if (scalarToken) { url = [NSMutableString stringWithFormat:@"%@?scalar_token=%@&room_id=%@", - [[NSUserDefaults standardUserDefaults] objectForKey:@"integrationsUiUrl"], + integrationsUiUrl, [MXTools encodeURIComponent:scalarToken], [MXTools encodeURIComponent:roomId] ]; diff --git a/Riot/Modules/Integrations/Widgets/WidgetViewController.m b/Riot/Modules/Integrations/Widgets/WidgetViewController.m index f67456292..f7ad5c0bd 100644 --- a/Riot/Modules/Integrations/Widgets/WidgetViewController.m +++ b/Riot/Modules/Integrations/Widgets/WidgetViewController.m @@ -190,7 +190,7 @@ NSString *const kJavascriptSendResponseToPostMessageAPI = @"riotIOS.sendResponse NSLog(@"[WidgetVC] decidePolicyForNavigationResponse: statusCode: %@", @(response.statusCode)); } - if (response.statusCode == 403 && [WidgetManager isScalarUrl:self.URL]) + if (response.statusCode == 403 && [[WidgetManager sharedManager] isScalarUrl:self.URL forUser:self.widget.mxSession.myUser.userId]) { [self fixScalarToken]; } diff --git a/Riot/Modules/Room/BubbleReactions/BubbleReactionActionViewCell.swift b/Riot/Modules/Room/BubbleReactions/BubbleReactionActionViewCell.swift new file mode 100644 index 000000000..5fbe5de8a --- /dev/null +++ b/Riot/Modules/Room/BubbleReactions/BubbleReactionActionViewCell.swift @@ -0,0 +1,68 @@ +/* + Copyright 2019 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 + +final class BubbleReactionActionViewCell: UICollectionViewCell, NibReusable, Themable { + + // MARK: - Constants + + // MARK: - Properties + + // MARK: Outlets + + @IBOutlet private weak var actionLabel: UILabel! + + // MARK: Private + + private var theme: Theme? + + // MARK: Public + + // MARK: - Life cycle + + override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes { + if #available(iOS 12.0, *) { + /* + On iOS 12, there are issues with self-sizing cells as described in Apple release notes (https://developer.apple.com/documentation/ios_release_notes/ios_12_release_notes) : + "You might encounter issues with systemLayoutSizeFitting(_:) when using a UICollectionViewCell subclass that requires updateConstraints(). + (42138227) — Workaround: Don't call the cell's setNeedsUpdateConstraints() method unless you need to support live constraint changes. + If you need to support live constraint changes, call updateConstraintsIfNeeded() before calling systemLayoutSizeFitting(_:)." + */ + self.updateConstraintsIfNeeded() + } + return super.preferredLayoutAttributesFitting(layoutAttributes) + } + + // MARK: - Public + + func fill(actionString: String) { + self.actionLabel.text = actionString + self.updateViews() + } + + func update(theme: Theme) { + self.theme = theme + self.updateViews() + } + + // MARK: - Private + + private func updateViews() { + self.actionLabel.textColor = self.theme?.tintColor + } +} diff --git a/Riot/Modules/Room/BubbleReactions/BubbleReactionActionViewCell.xib b/Riot/Modules/Room/BubbleReactions/BubbleReactionActionViewCell.xib new file mode 100644 index 000000000..fa3e5bdb7 --- /dev/null +++ b/Riot/Modules/Room/BubbleReactions/BubbleReactionActionViewCell.xib @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Riot/Modules/Room/BubbleReactions/BubbleReactionViewCell.swift b/Riot/Modules/Room/BubbleReactions/BubbleReactionViewCell.swift new file mode 100644 index 000000000..c6e2af626 --- /dev/null +++ b/Riot/Modules/Room/BubbleReactions/BubbleReactionViewCell.swift @@ -0,0 +1,108 @@ +/* + Copyright 2019 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 + +final class BubbleReactionViewCell: UICollectionViewCell, NibReusable, Themable { + + // MARK: - Constants + + private enum Constants { + static let selectedBorderWidth: CGFloat = 1.0 + } + + // MARK: - Properties + + // MARK: Outlets + + @IBOutlet private weak var reactionBackgroundView: UIView! + @IBOutlet private weak var emojiLabel: UILabel! + @IBOutlet private weak var countLabel: UILabel! + + // MARK: Private + + private var theme: Theme? + + // MARK: Public + + private var isReactionSelected: Bool = false + + // MARK: - Life cycle + + override func awakeFromNib() { + super.awakeFromNib() + // Initialization code + + self.reactionBackgroundView.layer.masksToBounds = true + } + + override func layoutSubviews() { + super.layoutSubviews() + + self.reactionBackgroundView.layer.cornerRadius = self.reactionBackgroundView.frame.size.height/2.0 + } + + override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes { + if #available(iOS 12.0, *) { + /* + On iOS 12, there are issues with self-sizing cells as described in Apple release notes (https://developer.apple.com/documentation/ios_release_notes/ios_12_release_notes) : + "You might encounter issues with systemLayoutSizeFitting(_:) when using a UICollectionViewCell subclass that requires updateConstraints(). + (42138227) — Workaround: Don't call the cell's setNeedsUpdateConstraints() method unless you need to support live constraint changes. + If you need to support live constraint changes, call updateConstraintsIfNeeded() before calling systemLayoutSizeFitting(_:)." + */ + self.updateConstraintsIfNeeded() + } + return super.preferredLayoutAttributesFitting(layoutAttributes) + } + + // MARK: - Public + + func fill(viewData: BubbleReactionViewData) { + self.emojiLabel.text = viewData.emoji + self.countLabel.text = viewData.countString + self.isReactionSelected = viewData.isCurrentUserReacted + + self.updateViews() + } + + func update(theme: Theme) { + self.theme = theme + self.reactionBackgroundView.layer.borderColor = theme.tintColor.cgColor + self.emojiLabel.textColor = theme.textPrimaryColor + self.countLabel.textColor = theme.textPrimaryColor + self.updateViews() + } + + // MARK: - Private + + private func updateViews() { + + let reactionBackgroundColor: UIColor? + let reactionBackgroundBorderWidth: CGFloat + + if self.isReactionSelected { + reactionBackgroundColor = self.theme?.tintBackgroundColor + reactionBackgroundBorderWidth = Constants.selectedBorderWidth + } else { + reactionBackgroundColor = self.theme?.headerBackgroundColor + reactionBackgroundBorderWidth = 0.0 + } + + self.reactionBackgroundView.layer.borderWidth = reactionBackgroundBorderWidth + self.reactionBackgroundView.backgroundColor = reactionBackgroundColor + } +} diff --git a/Riot/Modules/Room/BubbleReactions/BubbleReactionViewCell.xib b/Riot/Modules/Room/BubbleReactions/BubbleReactionViewCell.xib new file mode 100644 index 000000000..17552af69 --- /dev/null +++ b/Riot/Modules/Room/BubbleReactions/BubbleReactionViewCell.xib @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Riot/Modules/Room/BubbleReactions/BubbleReactionViewData.swift b/Riot/Modules/Room/BubbleReactions/BubbleReactionViewData.swift new file mode 100644 index 000000000..316f9c34b --- /dev/null +++ b/Riot/Modules/Room/BubbleReactions/BubbleReactionViewData.swift @@ -0,0 +1,23 @@ +/* + Copyright 2019 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 BubbleReactionViewData { + let emoji: String + let countString: String + let isCurrentUserReacted: Bool +} diff --git a/Riot/Modules/Room/BubbleReactions/BubbleReactionsView.swift b/Riot/Modules/Room/BubbleReactions/BubbleReactionsView.swift new file mode 100644 index 000000000..4ac8923e4 --- /dev/null +++ b/Riot/Modules/Room/BubbleReactions/BubbleReactionsView.swift @@ -0,0 +1,185 @@ +/* + Copyright 2019 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 MatrixSDK +import Reusable +import DGCollectionViewLeftAlignFlowLayout + +@objcMembers +final class BubbleReactionsView: UIView, NibOwnerLoadable { + + // MARK: - Constants + + private enum Constants { + static let minimumInteritemSpacing: CGFloat = 6.0 + static let minimumLineSpacing: CGFloat = 2.0 + } + + // MARK: - Properties + + // MARK: Outlets + + @IBOutlet private weak var collectionView: UICollectionView! + + // MARK: Private + + private var reactionsViewData: [BubbleReactionViewData] = [] + private var showAllButtonState: BubbleReactionsViewState.ShowAllButtonState = .none + private var theme: Theme? + + // MARK: Public + + // Do not use `BubbleReactionsViewModelType` here due to Objective-C incompatibily + var viewModel: BubbleReactionsViewModel? { + didSet { + self.viewModel?.viewDelegate = self + self.viewModel?.process(viewAction: .loadData) + } + } + + // MARK: - Setup + + private func commonInit() { + self.collectionView.isScrollEnabled = false + self.collectionView.delegate = self + self.collectionView.dataSource = self + self.collectionView.collectionViewLayout = DGCollectionViewLeftAlignFlowLayout() + + if let collectionViewFlowLayout = self.collectionView.collectionViewLayout as? UICollectionViewFlowLayout { + collectionViewFlowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize + collectionViewFlowLayout.minimumInteritemSpacing = Constants.minimumInteritemSpacing + collectionViewFlowLayout.minimumLineSpacing = Constants.minimumLineSpacing + } + + self.collectionView.register(cellType: BubbleReactionViewCell.self) + self.collectionView.register(cellType: BubbleReactionActionViewCell.self) + self.collectionView.reloadData() + } + + convenience init() { + self.init(frame: CGRect.zero) + } + + required init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + self.loadNibContent() + self.commonInit() + } + + override init(frame: CGRect) { + super.init(frame: frame) + self.loadNibContent() + self.commonInit() + } + + // MARK: - Public + + func update(theme: Theme) { + self.theme = theme + self.collectionView.reloadData() + } + + // MARK: - Private + + private func fill(reactionsViewData: [BubbleReactionViewData], showAllButtonState: BubbleReactionsViewState.ShowAllButtonState) { + self.reactionsViewData = reactionsViewData + self.showAllButtonState = showAllButtonState + self.collectionView.reloadData() + self.collectionView.collectionViewLayout.invalidateLayout() + } + + private func actionButtonString() -> String { + let actionString: String + switch self.showAllButtonState { + case .showAll: + actionString = VectorL10n.roomEventActionReactionShowAll + case .showLess: + actionString = VectorL10n.roomEventActionReactionShowLess + case .none: + actionString = "" + } + + return actionString + } +} + +// MARK: - UICollectionViewDataSource +extension BubbleReactionsView: UICollectionViewDataSource { + + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + // "Show all" or "Show less" is a cell in the same section as reactions cells + let additionalItems = self.showAllButtonState == .none ? 0 : 1 + + return self.reactionsViewData.count + additionalItems + } + + func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + if indexPath.row < self.reactionsViewData.count { + let cell: BubbleReactionViewCell = collectionView.dequeueReusableCell(for: indexPath) + + if let theme = self.theme { + cell.update(theme: theme) + } + + let viewData = self.reactionsViewData[indexPath.row] + cell.fill(viewData: viewData) + + return cell + } else { + let cell: BubbleReactionActionViewCell = collectionView.dequeueReusableCell(for: indexPath) + + if let theme = self.theme { + cell.update(theme: theme) + } + + let actionString = self.actionButtonString() + cell.fill(actionString: actionString) + + return cell + } + } +} + +// MARK: - UICollectionViewDelegate +extension BubbleReactionsView: UICollectionViewDelegate { + + func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { + if indexPath.row < self.reactionsViewData.count { + self.viewModel?.process(viewAction: .tapReaction(index: indexPath.row)) + } else { + switch self.showAllButtonState { + case .showAll: + self.viewModel?.process(viewAction: .tapShowAction(action: .showAll)) + case .showLess: + self.viewModel?.process(viewAction: .tapShowAction(action: .showLess)) + case .none: + break + } + } + } +} + +// MARK: - BubbleReactionsViewModelViewDelegate +extension BubbleReactionsView: BubbleReactionsViewModelViewDelegate { + + func bubbleReactionsViewModel(_ viewModel: BubbleReactionsViewModel, didUpdateViewState viewState: BubbleReactionsViewState) { + switch viewState { + case .loaded(reactionsViewData: let reactionsViewData, showAllButtonState: let showAllButtonState): + self.fill(reactionsViewData: reactionsViewData, showAllButtonState: showAllButtonState) + } + } +} diff --git a/Riot/Modules/Room/BubbleReactions/BubbleReactionsView.xib b/Riot/Modules/Room/BubbleReactions/BubbleReactionsView.xib new file mode 100644 index 000000000..7c5cedb2a --- /dev/null +++ b/Riot/Modules/Room/BubbleReactions/BubbleReactionsView.xib @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Riot/Modules/Room/BubbleReactions/BubbleReactionsViewModel.swift b/Riot/Modules/Room/BubbleReactions/BubbleReactionsViewModel.swift new file mode 100644 index 000000000..1a79b6edb --- /dev/null +++ b/Riot/Modules/Room/BubbleReactions/BubbleReactionsViewModel.swift @@ -0,0 +1,95 @@ +/* + Copyright 2019 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 final class BubbleReactionsViewModel: NSObject, BubbleReactionsViewModelType { + + // MARK: - Constants + + private enum Constants { + static let maxItemsWhenLimited: Int = 8 + } + + // MARK: - Properties + + // MARK: Private + + private let aggregatedReactions: MXAggregatedReactions + private let eventId: String + private let showAll: Bool + + // MARK: Public + + @objc weak var viewModelDelegate: BubbleReactionsViewModelDelegate? + weak var viewDelegate: BubbleReactionsViewModelViewDelegate? + + // MARK: - Setup + + @objc init(aggregatedReactions: MXAggregatedReactions, + eventId: String, + showAll: Bool) { + self.aggregatedReactions = aggregatedReactions + self.eventId = eventId + self.showAll = showAll + } + + // MARK: - Public + + func process(viewAction: BubbleReactionsViewAction) { + switch viewAction { + case .loadData: + self.loadData() + case .tapReaction(let index): + guard index < self.aggregatedReactions.reactions.count else { + return + } + let reactionCount = self.aggregatedReactions.reactions[index] + if reactionCount.myUserHasReacted { + self.viewModelDelegate?.bubbleReactionsViewModel(self, didRemoveReaction: reactionCount, forEventId: self.eventId) + } else { + self.viewModelDelegate?.bubbleReactionsViewModel(self, didAddReaction: reactionCount, forEventId: self.eventId) + } + case .addNewReaction: + break + case .tapShowAction(.showAll): + self.viewModelDelegate?.bubbleReactionsViewModel(self, didShowAllTappedForEventId: self.eventId) + case .tapShowAction(.showLess): + self.viewModelDelegate?.bubbleReactionsViewModel(self, didShowLessTappedForEventId: self.eventId) + } + } + + func loadData() { + var reactions = self.aggregatedReactions.reactions + var showAllButtonState: BubbleReactionsViewState.ShowAllButtonState = .none + + // Limit displayed reactions if required + if reactions.count > Constants.maxItemsWhenLimited { + if self.showAll == true { + showAllButtonState = .showLess + } else { + reactions = Array(reactions[0.. BubbleReactionViewData in + return BubbleReactionViewData(emoji: reactionCount.reaction, countString: "\(reactionCount.count)", isCurrentUserReacted: reactionCount.myUserHasReacted) + } + + self.viewDelegate?.bubbleReactionsViewModel(self, didUpdateViewState: .loaded(reactionsViewData: reactionsViewData, showAllButtonState: showAllButtonState)) + } +} diff --git a/Riot/Modules/Room/BubbleReactions/BubbleReactionsViewModelType.swift b/Riot/Modules/Room/BubbleReactions/BubbleReactionsViewModelType.swift new file mode 100644 index 000000000..3496854df --- /dev/null +++ b/Riot/Modules/Room/BubbleReactions/BubbleReactionsViewModelType.swift @@ -0,0 +1,57 @@ +/* + Copyright 2019 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 BubbleReactionsViewAction { + case loadData + case tapReaction(index: Int) + case addNewReaction + case tapShowAction(action: ShowAction) + + enum ShowAction { + case showAll + case showLess + } +} + +enum BubbleReactionsViewState { + case loaded(reactionsViewData: [BubbleReactionViewData], showAllButtonState: ShowAllButtonState) + + enum ShowAllButtonState { + case none + case showAll + case showLess + } +} + +@objc protocol BubbleReactionsViewModelDelegate: class { + 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) + func bubbleReactionsViewModel(_ viewModel: BubbleReactionsViewModel, didShowLessTappedForEventId eventId: String) +} + +protocol BubbleReactionsViewModelViewDelegate: class { + func bubbleReactionsViewModel(_ viewModel: BubbleReactionsViewModel, didUpdateViewState viewState: BubbleReactionsViewState) +} + +protocol BubbleReactionsViewModelType { + var viewModelDelegate: BubbleReactionsViewModelDelegate? { get set } + var viewDelegate: BubbleReactionsViewModelViewDelegate? { get set } + + func process(viewAction: BubbleReactionsViewAction) +} diff --git a/Riot/Modules/Room/CellData/RoomBubbleCellData.h b/Riot/Modules/Room/CellData/RoomBubbleCellData.h index dc59bf3a6..777063a59 100644 --- a/Riot/Modules/Room/CellData/RoomBubbleCellData.h +++ b/Riot/Modules/Room/CellData/RoomBubbleCellData.h @@ -35,6 +35,15 @@ typedef NS_ENUM(NSInteger, RoomBubbleCellDataTag) */ @property(nonatomic) BOOL containsLastMessage; +/** + Indicate true to display the timestamp of the selected component. + */ +@property(nonatomic) BOOL showTimestampForSelectedComponent; + +/** + Indicate true to display the timestamp of the selected component on the left if possible (YES by default). + */ +@property(nonatomic) BOOL displayTimestampForSelectedComponentOnLeftWhenPossible; /** The event id of the current selected event inside the bubble. Default is nil. @@ -56,4 +65,29 @@ typedef NS_ENUM(NSInteger, RoomBubbleCellDataTag) */ @property(nonatomic, readonly) NSInteger selectedComponentIndex; +/** + Return additional content height (read receipts, reactions). + */ +@property(nonatomic, readonly) CGFloat additionalContentHeight; + +/** + Indicate to update additional content height. + */ +- (void)setNeedsUpdateAdditionalContentHeight; + +/** + Update additional content height if needed. + */ +- (void)updateAdditionalContentHeightIfNeeded; + +/** + The index of the first visible component. NSNotFound by default. + */ +- (NSInteger)firstVisibleComponentIndex; + +#pragma mark - Show all reactions + +- (BOOL)showAllReactionsForEvent:(NSString*)eventId; +- (void)setShowAllReactions:(BOOL)showAllReactions forEvent:(NSString*)eventId; + @end diff --git a/Riot/Modules/Room/CellData/RoomBubbleCellData.m b/Riot/Modules/Room/CellData/RoomBubbleCellData.m index 81cbef55f..5fda69f74 100644 --- a/Riot/Modules/Room/CellData/RoomBubbleCellData.m +++ b/Riot/Modules/Room/CellData/RoomBubbleCellData.m @@ -1,5 +1,6 @@ /* Copyright 2015 OpenMarket Ltd + Copyright 2019 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. @@ -21,13 +22,40 @@ #import "AvatarGenerator.h" #import "Tools.h" +#import "Riot-Swift.h" + static NSAttributedString *timestampVerticalWhitespace = nil; -static NSAttributedString *readReceiptVerticalWhitespace = nil; + +@interface RoomBubbleCellData() + +@property(nonatomic, readonly) BOOL addVerticalWhitespaceForSelectedComponentTimestamp; +@property(nonatomic, readwrite) CGFloat additionalContentHeight; +@property(nonatomic) BOOL shouldUpdateAdditionalContentHeight; + +// Flags to "Show All" reactions for an event +@property(nonatomic) NSMutableSet *eventsToShowAllReactions; + +@end @implementation RoomBubbleCellData +- (BOOL)addVerticalWhitespaceForSelectedComponentTimestamp +{ + return self.showTimestampForSelectedComponent && !self.displayTimestampForSelectedComponentOnLeftWhenPossible; +} + #pragma mark - Override MXKRoomBubbleCellData +- (instancetype)init +{ + self = [super init]; + if (self) + { + _eventsToShowAllReactions = [NSMutableSet set]; + } + return self; +} + - (instancetype)initWithEvent:(MXEvent *)event andRoomState:(MXRoomState *)roomState andRoomDataSource:(MXKRoomDataSource *)roomDataSource2 { self = [super initWithEvent:event andRoomState:roomState andRoomDataSource:roomDataSource2]; @@ -58,13 +86,11 @@ static NSAttributedString *readReceiptVerticalWhitespace = nil; // Increase maximum number of components self.maxComponentCount = 20; - - // Initialize read receipts - self.readReceipts = [NSMutableDictionary dictionary]; - self.readReceipts[event.eventId] = [roomDataSource.room getEventReceipts:event.eventId sorted:YES]; // Reset attributedTextMessage to force reset MXKRoomCellData parameters self.attributedTextMessage = nil; + + self.displayTimestampForSelectedComponentOnLeftWhenPossible = YES; } return self; @@ -91,6 +117,8 @@ static NSAttributedString *readReceiptVerticalWhitespace = nil; shouldUpdateComponentsPosition = NO; } + + [self updateAdditionalContentHeightIfNeeded]; } - (NSAttributedString*)attributedTextMessage @@ -199,7 +227,7 @@ static NSAttributedString *readReceiptVerticalWhitespace = nil; } // Check whether the timestamp is displayed for this component, and check whether a vertical whitespace is required - if ((selectedComponentIndex == index || lastMessageIndex == index) && (self.shouldHideSenderInformation || self.shouldHideSenderName)) + if (((selectedComponentIndex == index && self.addVerticalWhitespaceForSelectedComponentTimestamp) || lastMessageIndex == index) && (self.shouldHideSenderInformation || self.shouldHideSenderName)) { currentAttributedTextMsg = [[NSMutableAttributedString alloc] initWithAttributedString:[RoomBubbleCellData timestampVerticalWhitespace]]; [currentAttributedTextMsg appendAttributedString:componentString]; @@ -210,11 +238,7 @@ static NSAttributedString *readReceiptVerticalWhitespace = nil; currentAttributedTextMsg = [[NSMutableAttributedString alloc] initWithAttributedString:componentString]; } - if (self.readReceipts[component.event.eventId].count) - { - // Add vertical whitespace in case of read receipts - [currentAttributedTextMsg appendAttributedString:[RoomBubbleCellData readReceiptVerticalWhitespace]]; - } + [self addVerticalWhitespaceToString:currentAttributedTextMsg forEvent:component.event.eventId]; // The first non empty component has been handled. break; @@ -238,7 +262,7 @@ static NSAttributedString *readReceiptVerticalWhitespace = nil; } // Check whether the timestamp is displayed - if (selectedComponentIndex == index || lastMessageIndex == index) + if ((selectedComponentIndex == index && self.addVerticalWhitespaceForSelectedComponentTimestamp) || lastMessageIndex == index) { [currentAttributedTextMsg appendAttributedString:[RoomBubbleCellData timestampVerticalWhitespace]]; } @@ -246,33 +270,56 @@ static NSAttributedString *readReceiptVerticalWhitespace = nil; // Append attributed text [currentAttributedTextMsg appendAttributedString:componentString]; - if (self.readReceipts[component.event.eventId].count) - { - // Add vertical whitespace in case of read receipts - [currentAttributedTextMsg appendAttributedString:[RoomBubbleCellData readReceiptVerticalWhitespace]]; - } + [self addVerticalWhitespaceToString:currentAttributedTextMsg forEvent:component.event.eventId]; } } return currentAttributedTextMsg; } +- (NSInteger)firstVisibleComponentIndex +{ + __block NSInteger firstVisibleComponentIndex = NSNotFound; + + if (self.attachment && self.bubbleComponents.count) + { + firstVisibleComponentIndex = 0; + } + else + { + [self.bubbleComponents enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { + + MXKRoomBubbleComponent *component = (MXKRoomBubbleComponent*)obj; + + if (component.attributedTextMessage) + { + firstVisibleComponentIndex = idx; + *stop = YES; + } + }]; + } + + return firstVisibleComponentIndex; +} + - (void)refreshBubbleComponentsPosition { // CAUTION: This method must be called on the main thread. @synchronized(bubbleComponents) { + NSInteger bubbleComponentsCount = bubbleComponents.count; + // Check whether there is at least one component. - if (bubbleComponents.count) + if (bubbleComponentsCount) { - BOOL hasReadReceipts = NO; - // Set position of the first component CGFloat positionY = (self.attachment == nil || self.attachment.type == MXKAttachmentTypeFile || self.attachment.type == MXKAttachmentTypeAudio) ? MXKROOMBUBBLECELLDATA_TEXTVIEW_DEFAULT_VERTICAL_INSET : 0; MXKRoomBubbleComponent *component; NSUInteger index = 0; - for (; index < bubbleComponents.count; index++) + + // Use same position for first components without render (redacted) + for (; index < bubbleComponentsCount; index++) { // Compute the vertical position for next component component = bubbleComponents[index]; @@ -281,39 +328,19 @@ static NSAttributedString *readReceiptVerticalWhitespace = nil; if (component.attributedTextMessage) { - hasReadReceipts = (self.readReceipts[component.event.eventId].count > 0); break; } } // Check whether the position of other components need to be refreshed - if (!self.attachment && index < bubbleComponents.count) + if (!self.attachment && index < bubbleComponentsCount) { - NSMutableAttributedString *attributedString; + NSMutableAttributedString *attributedString = [NSMutableAttributedString new]; NSInteger selectedComponentIndex = self.selectedComponentIndex; NSInteger lastMessageIndex = self.containsLastMessage ? self.mostRecentComponentIndex : NSNotFound; - - // Check whether the timestamp is displayed for this first component, and check whether a vertical whitespace is required - if ((selectedComponentIndex == index || lastMessageIndex == index) && (self.shouldHideSenderInformation || self.shouldHideSenderName)) - { - attributedString = [[NSMutableAttributedString alloc] initWithAttributedString:[RoomBubbleCellData timestampVerticalWhitespace]]; - [attributedString appendAttributedString:component.attributedTextMessage]; - } - else - { - // Init attributed string with the first text component - attributedString = [[NSMutableAttributedString alloc] initWithAttributedString:component.attributedTextMessage]; - } - - // Vertical whitespace is added in case of read receipts - if (hasReadReceipts) - { - [attributedString appendAttributedString:[RoomBubbleCellData readReceiptVerticalWhitespace]]; - } - - [attributedString appendAttributedString:[MXKRoomBubbleCellDataWithAppendingMode messageSeparator]]; - - for (index++; index < bubbleComponents.count; index++) + NSInteger visibleMessageIndex = 0; + + for (; index < bubbleComponentsCount; index++) { // Compute the vertical position for next component component = bubbleComponents[index]; @@ -321,17 +348,14 @@ static NSAttributedString *readReceiptVerticalWhitespace = nil; if (component.attributedTextMessage) { // Prepare its attributed string by considering potential vertical margin required to display timestamp. - NSAttributedString *componentString; - if (selectedComponentIndex == index || lastMessageIndex == index) + NSAttributedString *componentString = component.attributedTextMessage; + + // Check whether the timestamp is displayed for this component, and check whether a vertical whitespace is required + + if (((selectedComponentIndex == index && self.addVerticalWhitespaceForSelectedComponentTimestamp) || lastMessageIndex == index) + && !(visibleMessageIndex == 0 && !(self.shouldHideSenderInformation || self.shouldHideSenderName))) { - NSMutableAttributedString *componentAttributedString = [[NSMutableAttributedString alloc] initWithAttributedString:[RoomBubbleCellData timestampVerticalWhitespace]]; - [componentAttributedString appendAttributedString:component.attributedTextMessage]; - - componentString = componentAttributedString; - } - else - { - componentString = component.attributedTextMessage; + [attributedString appendAttributedString:[RoomBubbleCellData timestampVerticalWhitespace]]; } // Append this attributed string. @@ -345,13 +369,12 @@ static NSAttributedString *readReceiptVerticalWhitespace = nil; component.position = CGPointMake(0, positionY); - // Add vertical whitespace in case of read receipts. - if (self.readReceipts[component.event.eventId].count) - { - [attributedString appendAttributedString:[RoomBubbleCellData readReceiptVerticalWhitespace]]; - } + // Vertical whitespace is added in case of read receipts or reactions + [self addVerticalWhitespaceToString:attributedString forEvent:component.event.eventId]; [attributedString appendAttributedString:[MXKRoomBubbleCellDataWithAppendingMode messageSeparator]]; + + visibleMessageIndex++; } else { @@ -363,6 +386,142 @@ static NSAttributedString *readReceiptVerticalWhitespace = nil; } } +- (void)addVerticalWhitespaceToString:(NSMutableAttributedString *)attributedString forEvent:(NSString *)eventId +{ + // Add vertical whitespace in case of read receipts. + NSUInteger reactionCount = self.reactions[eventId].reactions.count; + + MXAggregatedReactions *aggregatedReactions = self.reactions[eventId]; + + if (reactionCount) + { + CGFloat bubbleReactionsViewWidth = self.maxTextViewWidth - 4; + + CGSize fittingSize = UILayoutFittingCompressedSize; + fittingSize.width = bubbleReactionsViewWidth; + + static BubbleReactionsView *bubbleReactionsView; + + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + bubbleReactionsView = [BubbleReactionsView new]; + }); + + BOOL showAllReactions = [self.eventsToShowAllReactions containsObject:eventId]; + + bubbleReactionsView.frame = CGRectMake(0, 0, bubbleReactionsViewWidth, 1.0); + BubbleReactionsViewModel *viemModel = [[BubbleReactionsViewModel alloc] initWithAggregatedReactions:aggregatedReactions eventId:eventId showAll:showAllReactions]; + bubbleReactionsView.viewModel = viemModel; + [bubbleReactionsView setNeedsLayout]; + [bubbleReactionsView layoutIfNeeded]; + + CGFloat height = [bubbleReactionsView systemLayoutSizeFittingSize:fittingSize].height + RoomBubbleCellLayout.reactionsViewTopMargin; + + [attributedString appendAttributedString:[RoomBubbleCellData verticalWhitespaceForHeight: height]]; + } + + // Add vertical whitespace in case of read receipts. + if (self.readReceipts[eventId].count) + { + [attributedString appendAttributedString:[RoomBubbleCellData verticalWhitespaceForHeight:RoomBubbleCellLayout.readReceiptsViewHeight + RoomBubbleCellLayout.readReceiptsViewTopMargin]]; + } +} + +- (CGFloat)computeAdditionalHeight +{ + CGFloat height = 0; + + for (MXKRoomBubbleComponent *bubbleComponent in self.bubbleComponents) + { + NSString *eventId = bubbleComponent.event.eventId; + + height+= [self reactionHeightForEventId:eventId]; + height+= [self readReceiptHeightForEventId:eventId]; + } + + return height; +} + +- (void)updateAdditionalContentHeightIfNeeded; +{ + if (self.shouldUpdateAdditionalContentHeight) + { + void(^updateAdditionalHeight)(void) = ^() { + self.additionalContentHeight = [self computeAdditionalHeight]; + }; + + // The additional height depends on the room read receipts and reactions view which must be calculated on the main thread. + // Check here the current thread, this is just a sanity check because this method is called during the rendering step + // which takes place on the main thread. + if ([NSThread currentThread] != [NSThread mainThread]) + { + NSLog(@"[RoomBubbleCellData] prepareBubbleComponentsPosition called on wrong thread"); + dispatch_sync(dispatch_get_main_queue(), ^{ + updateAdditionalHeight(); + }); + } + else + { + updateAdditionalHeight(); + } + + self.shouldUpdateAdditionalContentHeight = NO; + } +} + +- (void)setNeedsUpdateAdditionalContentHeight +{ + self.shouldUpdateAdditionalContentHeight = YES; +} + +- (CGFloat)reactionHeightForEventId:(NSString*)eventId +{ + CGFloat height = 0; + + NSUInteger reactionCount = self.reactions[eventId].reactions.count; + + MXAggregatedReactions *aggregatedReactions = self.reactions[eventId]; + + if (reactionCount) + { + CGFloat bubbleReactionsViewWidth = self.maxTextViewWidth - 4; + + CGSize fittingSize = UILayoutFittingCompressedSize; + fittingSize.width = bubbleReactionsViewWidth; + + static BubbleReactionsView *bubbleReactionsView; + + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + bubbleReactionsView = [BubbleReactionsView new]; + }); + + BOOL showAllReactions = [self.eventsToShowAllReactions containsObject:eventId]; + + bubbleReactionsView.frame = CGRectMake(0, 0, bubbleReactionsViewWidth, 1.0); + BubbleReactionsViewModel *viemModel = [[BubbleReactionsViewModel alloc] initWithAggregatedReactions:aggregatedReactions eventId:eventId showAll:showAllReactions]; + bubbleReactionsView.viewModel = viemModel; + [bubbleReactionsView setNeedsLayout]; + [bubbleReactionsView layoutIfNeeded]; + + height = [bubbleReactionsView systemLayoutSizeFittingSize:fittingSize].height + RoomBubbleCellLayout.reactionsViewTopMargin; + } + + return height; +} + +- (CGFloat)readReceiptHeightForEventId:(NSString*)eventId +{ + CGFloat height = 0; + + if (self.readReceipts[eventId].count) + { + height = RoomBubbleCellLayout.readReceiptsViewHeight + RoomBubbleCellLayout.readReceiptsViewTopMargin; + } + + return height; +} + - (void)setContainsLastMessage:(BOOL)containsLastMessage { // Check whether there is something to do @@ -468,18 +627,21 @@ static NSAttributedString *readReceiptVerticalWhitespace = nil; return timestampVerticalWhitespace; } - -+ (NSAttributedString *)readReceiptVerticalWhitespace ++ (NSAttributedString *)verticalWhitespaceForHeight:(CGFloat)height { - @synchronized(self) + UIFont *sizingFont = [UIFont systemFontOfSize:2]; + CGFloat returnHeight = sizingFont.lineHeight; + + NSUInteger returns = (NSUInteger)round(height/returnHeight); + NSMutableString *returnString = [NSMutableString string]; + + for (NSUInteger i = 0; i < returns; i++) { - if (readReceiptVerticalWhitespace == nil) - { - readReceiptVerticalWhitespace = [[NSAttributedString alloc] initWithString:@"\n\n" attributes:@{NSForegroundColorAttributeName : [UIColor blackColor], - NSFontAttributeName: [UIFont systemFontOfSize:4]}]; - } + [returnString appendString:@"\n"]; } - return readReceiptVerticalWhitespace; + + return [[NSAttributedString alloc] initWithString:returnString attributes:@{NSForegroundColorAttributeName : [UIColor blackColor], + NSFontAttributeName: sizingFont}]; } - (BOOL)hasSameSenderAsBubbleCellData:(id)bubbleCellData @@ -513,10 +675,27 @@ static NSAttributedString *readReceiptVerticalWhitespace = nil; return NO; } - // Update read receipts for this bubble - self.readReceipts[event.eventId] = [roomDataSource.room getEventReceipts:event.eventId sorted:YES]; - return [super addEvent:event andRoomState:roomState]; } + +#pragma mark - Show all reactions + +- (BOOL)showAllReactionsForEvent:(NSString*)eventId +{ + return [self.eventsToShowAllReactions containsObject:eventId]; +} + +- (void)setShowAllReactions:(BOOL)showAllReactions forEvent:(NSString*)eventId +{ + if (showAllReactions) + { + [self.eventsToShowAllReactions addObject:eventId]; + } + else + { + [self.eventsToShowAllReactions removeObject:eventId]; + } +} + @end diff --git a/Riot/Modules/Room/ContextualMenu/ContextualMenuItemView.swift b/Riot/Modules/Room/ContextualMenu/ContextualMenuItemView.swift new file mode 100644 index 000000000..8a0147e5f --- /dev/null +++ b/Riot/Modules/Room/ContextualMenu/ContextualMenuItemView.swift @@ -0,0 +1,172 @@ +/* + Copyright 2019 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 + +final class ContextualMenuItemView: UIView, NibOwnerLoadable { + + // MARK: - Constants + + private enum ColorAlpha { + static let normal: CGFloat = 1.0 + static let highlighted: CGFloat = 0.3 + } + + private enum ViewAlpha { + static let normal: CGFloat = 1.0 + static let disabled: CGFloat = 0.3 + } + + // MARK: - Properties + + // MARK: Outlets + + @IBOutlet private weak var imageView: UIImageView! + @IBOutlet private weak var titleLabel: UILabel! + + // MARK: Private + + private var originalImage: UIImage? + + private var isHighlighted: Bool = false { + didSet { + self.updateView() + } + } + + // MARK: Public + + var titleColor: UIColor = .black { + didSet { + self.updateView() + } + } + + var imageColor: UIColor = .black { + didSet { + self.updateView() + } + } + + var isEnabled: Bool = true { + didSet { + self.updateView() + } + } + + var action: (() -> Void)? + + // MARK: Setup + + private func commonInit() { + self.setupGestureRecognizer() + } + + convenience init() { + self.init(frame: CGRect.zero) + } + + required init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + self.loadNibContent() + self.commonInit() + } + + override init(frame: CGRect) { + super.init(frame: frame) + self.loadNibContent() + self.commonInit() + } + + // MARK: - Public + + func fill(title: String, image: UIImage?) { + self.originalImage = image?.withRenderingMode(.alwaysTemplate) + self.titleLabel.text = title + self.updateView() + } + + func fill(menuItem: RoomContextualMenuItem) { + self.fill(title: menuItem.title, image: menuItem.image) + self.action = menuItem.action + self.isEnabled = menuItem.isEnabled + } + + // MARK: - Private + + private func setupGestureRecognizer() { + let gestureRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(buttonAction(_:))) + gestureRecognizer.minimumPressDuration = 0 + self.addGestureRecognizer(gestureRecognizer) + } + + private func updateView() { + + let viewAlpha = self.isEnabled ? ViewAlpha.normal : ViewAlpha.disabled + let colorAlpha = self.isHighlighted ? ColorAlpha.highlighted : ColorAlpha.normal + + self.updateTitleAndImageAlpha(viewAlpha) + self.imageView.tintColor = self.imageColor + self.updateTitleAndImageColorAlpha(colorAlpha) + } + + private func updateTitleAndImageAlpha(_ alpha: CGFloat) { + self.imageView.alpha = alpha + self.titleLabel.alpha = alpha + } + + private func updateTitleAndImageColorAlpha(_ alpha: CGFloat) { + let titleColor: UIColor + let image: UIImage? + + if alpha < 1.0 { + titleColor = self.titleColor.withAlphaComponent(alpha) + image = self.originalImage?.vc_tintedImage(usingColor: self.imageColor.withAlphaComponent(alpha)) + } else { + titleColor = self.titleColor + image = self.originalImage + } + + self.titleLabel.textColor = titleColor + self.imageView.image = image + } + + // MARK: - Actions + + @objc private func buttonAction(_ sender: UILongPressGestureRecognizer) { + guard self.isEnabled else { + return + } + + let isBackgroundViewTouched = sender.vc_isTouchingInside() + + switch sender.state { + case .began, .changed: + self.isHighlighted = isBackgroundViewTouched + case .ended: + self.isHighlighted = false + + if isBackgroundViewTouched { + self.action?() + } + case .cancelled: + self.isHighlighted = false + default: + break + } + } +} diff --git a/Riot/Modules/Room/ContextualMenu/ContextualMenuItemView.xib b/Riot/Modules/Room/ContextualMenu/ContextualMenuItemView.xib new file mode 100644 index 000000000..0032099d7 --- /dev/null +++ b/Riot/Modules/Room/ContextualMenu/ContextualMenuItemView.xib @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Riot/Modules/Room/ContextualMenu/ReactionsMenu/ReactionMenuItemViewData.swift b/Riot/Modules/Room/ContextualMenu/ReactionsMenu/ReactionMenuItemViewData.swift new file mode 100644 index 000000000..df9f4ed27 --- /dev/null +++ b/Riot/Modules/Room/ContextualMenu/ReactionsMenu/ReactionMenuItemViewData.swift @@ -0,0 +1,22 @@ +/* + Copyright 2019 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 ReactionMenuItemViewData { + let emoji: String + let isSelected: Bool +} diff --git a/Riot/Modules/Room/ContextualMenu/ReactionsMenu/ReactionsMenuButton.swift b/Riot/Modules/Room/ContextualMenu/ReactionsMenu/ReactionsMenuButton.swift new file mode 100644 index 000000000..6da7eb19b --- /dev/null +++ b/Riot/Modules/Room/ContextualMenu/ReactionsMenu/ReactionsMenuButton.swift @@ -0,0 +1,85 @@ +/* + Copyright 2019 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 ReactionsMenuButton: UIButton, Themable { + + // MARK: - Constants + + private enum Constants { + static let borderWidthSelected: CGFloat = 1/UIScreen.main.scale + static let borderColorAlpha: CGFloat = 0.15 + } + + // MARK: - Properties + + private var theme: Theme! + + // MARK: - Setup + + required init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + self.commonInit() + } + + override init(frame: CGRect) { + super.init(frame: frame) + self.commonInit() + } + + // MARK: - Life cycle + + override func layoutSubviews() { + super.layoutSubviews() + self.layer.cornerRadius = self.frame.size.height / 3 + self.layer.borderWidth = self.isSelected ? Constants.borderWidthSelected : 0 + } + + // MARK: - Private + + private func commonInit() { + self.layer.masksToBounds = true + + self.update(theme: ThemeService.shared().theme) + + customizeViewRendering() + updateView() + } + + private func customizeViewRendering() { + self.tintColor = UIColor.clear + } + + func update(theme: Theme) { + self.theme = theme + + self.setTitleColor(self.theme.textPrimaryColor, for: .normal) + self.setTitleColor(self.theme.textPrimaryColor, for: .selected) + + self.layer.borderColor = self.theme.tintColor.withAlphaComponent(Constants.borderColorAlpha).cgColor + } + + private func updateView() { + backgroundColor = isSelected ? self.theme.tintBackgroundColor : UIColor.clear + } + + override open var isSelected: Bool { + didSet { + self.updateView() + } + } +} diff --git a/Riot/Modules/Room/ContextualMenu/ReactionsMenu/ReactionsMenuView.swift b/Riot/Modules/Room/ContextualMenu/ReactionsMenu/ReactionsMenuView.swift new file mode 100644 index 000000000..5ceb40d9b --- /dev/null +++ b/Riot/Modules/Room/ContextualMenu/ReactionsMenu/ReactionsMenuView.swift @@ -0,0 +1,146 @@ +/* + Copyright 2019 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 + +final class ReactionsMenuView: UIView, Themable, NibLoadable { + + // MARK: - Constants + + private enum Constants { + static let selectedReactionAnimationScale: CGFloat = 1.2 + } + + // MARK: - Properties + + // MARK: Outlets + + @IBOutlet private weak var reactionsBackgroundView: UIView! + @IBOutlet private weak var reactionsStackView: UIStackView! + + // MARK: Private + + private var reactionViewDatas: [ReactionMenuItemViewData] = [] + private var reactionButtons: [ReactionsMenuButton] = [] + private var tappedReactionButton: ReactionsMenuButton? + + // MARK: Public + + var viewModel: ReactionsMenuViewModelType? { + didSet { + self.viewModel?.viewDelegate = self + self.viewModel?.process(viewAction: .loadData) + } + } + + var reactionHasBeenTapped: Bool { + return self.tappedReactionButton != nil + } + + // MARK: - Life cycle + + override func awakeFromNib() { + super.awakeFromNib() + + self.reactionsBackgroundView.layer.masksToBounds = true + self.update(theme: ThemeService.shared().theme) + } + + override func layoutSubviews() { + super.layoutSubviews() + + self.reactionsBackgroundView.layer.cornerRadius = self.reactionsBackgroundView.frame.size.height/2 + } + + // MARK: - Public + + func update(theme: Theme) { + self.reactionsBackgroundView.backgroundColor = theme.headerBackgroundColor + } + + func selectionAnimationInstructionPart1() { + guard let tappedButton = self.tappedReactionButton else { + return + } + let scale = Constants.selectedReactionAnimationScale + tappedButton.superview?.bringSubviewToFront(tappedButton) + tappedButton.transform = CGAffineTransform(scaleX: scale, y: scale) + } + + func selectionAnimationInstructionPart2() { + guard let tappedButton = self.tappedReactionButton else { + return + } + tappedButton.transform = CGAffineTransform.identity + tappedButton.isSelected.toggle() + } + + // MARK: - Private + + private func fill(reactionsMenuViewDatas: [ReactionMenuItemViewData]) { + self.reactionViewDatas = reactionsMenuViewDatas + + self.reactionsStackView.vc_removeAllSubviews() + + let reactionsStackViewCount = self.reactionsStackView.arrangedSubviews.count + + // Remove all menu buttons if reactions count has changed + if reactionsStackViewCount != self.reactionViewDatas.count { + self.reactionsStackView.vc_removeAllSubviews() + } + + var index = 0 + + for reactionViewData in self.reactionViewDatas { + + let reactionsMenuButton: ReactionsMenuButton + + if index < reactionsStackViewCount, let foundReactionsMenuButton = self.reactionsStackView.arrangedSubviews[index] as? ReactionsMenuButton { + reactionsMenuButton = foundReactionsMenuButton + } else { + reactionsMenuButton = ReactionsMenuButton() + reactionsMenuButton.addTarget(self, action: #selector(reactionButtonAction), for: .touchUpInside) + self.reactionsStackView.addArrangedSubview(reactionsMenuButton) + self.reactionButtons.append(reactionsMenuButton) + } + + reactionsMenuButton.setTitle(reactionViewData.emoji, for: .normal) + reactionsMenuButton.isSelected = reactionViewData.isSelected + + index+=1 + } + } + + @objc private func reactionButtonAction(_ sender: ReactionsMenuButton) { + guard let tappedReaction = sender.titleLabel?.text else { + return + } + self.tappedReactionButton = sender + self.viewModel?.process(viewAction: .tap(reaction: tappedReaction)) + } +} + +// MARK: - ReactionsMenuViewModelViewDelegate +extension ReactionsMenuView: ReactionsMenuViewModelViewDelegate { + + func reactionsMenuViewModel(_ viewModel: ReactionsMenuViewModel, didUpdateViewState viewState: ReactionsMenuViewState) { + switch viewState { + case .loaded(reactionsViewData: let reactionsViewData): + self.fill(reactionsMenuViewDatas: reactionsViewData) + } + } +} diff --git a/Riot/Modules/Room/ContextualMenu/ReactionsMenu/ReactionsMenuView.xib b/Riot/Modules/Room/ContextualMenu/ReactionsMenu/ReactionsMenuView.xib new file mode 100644 index 000000000..f9c6c0113 --- /dev/null +++ b/Riot/Modules/Room/ContextualMenu/ReactionsMenu/ReactionsMenuView.xib @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Riot/Modules/Room/ContextualMenu/ReactionsMenu/ReactionsMenuViewAction.swift b/Riot/Modules/Room/ContextualMenu/ReactionsMenu/ReactionsMenuViewAction.swift new file mode 100644 index 000000000..3608a9187 --- /dev/null +++ b/Riot/Modules/Room/ContextualMenu/ReactionsMenu/ReactionsMenuViewAction.swift @@ -0,0 +1,23 @@ +/* + Copyright 2019 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 + +/// Action chosen by the user +enum ReactionsMenuViewAction { + case loadData + case tap(reaction: String) +} diff --git a/Riot/Modules/Room/ContextualMenu/ReactionsMenu/ReactionsMenuViewModel.swift b/Riot/Modules/Room/ContextualMenu/ReactionsMenu/ReactionsMenuViewModel.swift new file mode 100644 index 000000000..c35678cdb --- /dev/null +++ b/Riot/Modules/Room/ContextualMenu/ReactionsMenu/ReactionsMenuViewModel.swift @@ -0,0 +1,84 @@ +/* + Copyright 2019 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 final class ReactionsMenuViewModel: NSObject, ReactionsMenuViewModelType { + + // MARK: - Properties + + private let reactions = ["👍", "👎", "😄", "🎉", "😕", "❤️", "🚀", "👀"] + private var currentViewDatas: [ReactionMenuItemViewData] = [] + + // MARK: Private + + private let aggregatedReactions: MXAggregatedReactions? + private let reactionsViewData: [ReactionMenuItemViewData] = [] + private let eventId: String + + // MARK: Public + + @objc weak var coordinatorDelegate: ReactionsMenuViewModelCoordinatorDelegate? + weak var viewDelegate: ReactionsMenuViewModelViewDelegate? + + // MARK: - Setup + + @objc init(aggregatedReactions: MXAggregatedReactions?, + eventId: String) { + self.aggregatedReactions = aggregatedReactions + self.eventId = eventId + } + + // MARK: - Public + + func process(viewAction: ReactionsMenuViewAction) { + switch viewAction { + case .loadData: + self.loadData() + case .tap(let reaction): + if let viewData = self.currentViewDatas.first(where: { $0.emoji == reaction }) { + if viewData.isSelected { + self.coordinatorDelegate?.reactionsMenuViewModel(self, didRemoveReaction: reaction, forEventId: self.eventId) + } else { + self.coordinatorDelegate?.reactionsMenuViewModel(self, didAddReaction: reaction, forEventId: self.eventId) + } + } + } + } + + // MARK: - Private + + private func loadData() { + let reactionCounts = self.aggregatedReactions?.withNonZeroCount()?.reactions ?? [] + + var quickReactionsWithUserReactedFlag: [String: Bool] = Dictionary(uniqueKeysWithValues: self.reactions.map { ($0, false) }) + + reactionCounts.forEach { (reactionCount) in + if let hasUserReacted = quickReactionsWithUserReactedFlag[reactionCount.reaction], hasUserReacted == false { + quickReactionsWithUserReactedFlag[reactionCount.reaction] = reactionCount.myUserHasReacted + } + } + + let reactionMenuItemViewDatas: [ReactionMenuItemViewData] = self.reactions.map { reaction -> ReactionMenuItemViewData in + let isSelected = quickReactionsWithUserReactedFlag[reaction] ?? false + return ReactionMenuItemViewData(emoji: reaction, isSelected: isSelected) + } + + self.currentViewDatas = reactionMenuItemViewDatas + + self.viewDelegate?.reactionsMenuViewModel(self, didUpdateViewState: ReactionsMenuViewState.loaded(reactionsViewData: reactionMenuItemViewDatas)) + } +} diff --git a/Riot/Modules/Room/ContextualMenu/ReactionsMenu/ReactionsMenuViewModelType.swift b/Riot/Modules/Room/ContextualMenu/ReactionsMenu/ReactionsMenuViewModelType.swift new file mode 100644 index 000000000..0e162f6c1 --- /dev/null +++ b/Riot/Modules/Room/ContextualMenu/ReactionsMenu/ReactionsMenuViewModelType.swift @@ -0,0 +1,34 @@ +/* + Copyright 2019 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 ReactionsMenuViewModelViewDelegate: class { + func reactionsMenuViewModel(_ viewModel: ReactionsMenuViewModel, didUpdateViewState viewState: ReactionsMenuViewState) +} + +@objc protocol ReactionsMenuViewModelCoordinatorDelegate: class { + func reactionsMenuViewModel(_ viewModel: ReactionsMenuViewModel, didAddReaction reaction: String, forEventId eventId: String) + func reactionsMenuViewModel(_ viewModel: ReactionsMenuViewModel, didRemoveReaction reaction: String, forEventId eventId: String) +} + +protocol ReactionsMenuViewModelType { + + var coordinatorDelegate: ReactionsMenuViewModelCoordinatorDelegate? { get set } + var viewDelegate: ReactionsMenuViewModelViewDelegate? { get set } + + func process(viewAction: ReactionsMenuViewAction) +} diff --git a/Riot/Modules/Room/ContextualMenu/ReactionsMenu/ReactionsMenuViewState.swift b/Riot/Modules/Room/ContextualMenu/ReactionsMenu/ReactionsMenuViewState.swift new file mode 100644 index 000000000..e74a77ef7 --- /dev/null +++ b/Riot/Modules/Room/ContextualMenu/ReactionsMenu/ReactionsMenuViewState.swift @@ -0,0 +1,22 @@ +/* + Copyright 2019 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 + +/// ReactionsMenuView view state +enum ReactionsMenuViewState { + case loaded(reactionsViewData: [ReactionMenuItemViewData]) +} diff --git a/Riot/Modules/Room/ContextualMenu/RoomContextualMenuAction.swift b/Riot/Modules/Room/ContextualMenu/RoomContextualMenuAction.swift new file mode 100644 index 000000000..f4898fca0 --- /dev/null +++ b/Riot/Modules/Room/ContextualMenu/RoomContextualMenuAction.swift @@ -0,0 +1,62 @@ +/* + Copyright 2019 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 enum RoomContextualMenuAction: Int { + case copy + case reply + case edit + case more + + // MARK: - Properties + + var title: String { + let title: String + + switch self { + case .copy: + title = VectorL10n.roomEventActionCopy + case .reply: + title = VectorL10n.roomEventActionReply + case .edit: + title = VectorL10n.roomEventActionEdit + case .more: + title = VectorL10n.roomEventActionMore + } + + return title + } + + var image: UIImage? { + let image: UIImage? + + switch self { + case .copy: + image = Asset.Images.roomContextMenuCopy.image + case .reply: + image = Asset.Images.roomContextMenuReply.image + case .edit: + image = Asset.Images.roomContextMenuEdit.image + case .more: + image = Asset.Images.roomContextMenuMore.image + default: + image = nil + } + + return image + } +} diff --git a/Riot/Modules/Room/ContextualMenu/RoomContextualMenuItem.swift b/Riot/Modules/Room/ContextualMenu/RoomContextualMenuItem.swift new file mode 100644 index 000000000..ba32691f3 --- /dev/null +++ b/Riot/Modules/Room/ContextualMenu/RoomContextualMenuItem.swift @@ -0,0 +1,37 @@ +/* + Copyright 2019 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 + +@objcMembers +final class RoomContextualMenuItem: NSObject { + + // MARK: - Properties + + let title: String + let image: UIImage? + + var isEnabled: Bool = true + var action: (() -> Void)? + + // MARK: - Setup + + init(menuAction: RoomContextualMenuAction) { + self.title = menuAction.title + self.image = menuAction.image + super.init() + } +} diff --git a/Riot/Modules/Room/ContextualMenu/RoomContextualMenuPresenter.swift b/Riot/Modules/Room/ContextualMenu/RoomContextualMenuPresenter.swift new file mode 100644 index 000000000..04d87ad92 --- /dev/null +++ b/Riot/Modules/Room/ContextualMenu/RoomContextualMenuPresenter.swift @@ -0,0 +1,130 @@ +/* + Copyright 2019 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 + +@objcMembers +final class RoomContextualMenuPresenter: NSObject { + + // MARK: - Constants + + private enum AnimationDurations { + static let showMenu: TimeInterval = 0.15 + static let showMenuFromSingleTap: TimeInterval = 0.1 + static let hideMenu: TimeInterval = 0.2 + static let selectedReaction: TimeInterval = 0.15 + } + + // MARK: - Properties + + // MARK: Private + + private weak var roomContextualMenuViewController: RoomContextualMenuViewController? + + // MARK: Public + + var isPresenting: Bool { + return self.roomContextualMenuViewController?.parent != nil + } + + // MARK: - Public + + func present(roomContextualMenuViewController: RoomContextualMenuViewController, + from viewController: UIViewController, + on view: UIView, + contentToReactFrame: CGRect, // Not nullable for compatibility with Obj-C + fromSingleTapGesture usedSingleTapGesture: Bool, + animated: Bool, + completion: (() -> Void)?) { + guard self.isPresenting == false else { + return + } + + viewController.vc_addChildViewController(viewController: roomContextualMenuViewController, onView: view) + + self.roomContextualMenuViewController = roomContextualMenuViewController + + roomContextualMenuViewController.contentToReactFrame = contentToReactFrame + + roomContextualMenuViewController.hideMenuToolbar() + roomContextualMenuViewController.prepareReactionsMenuAnimations() + roomContextualMenuViewController.hideReactionsMenu() + + roomContextualMenuViewController.view.layoutIfNeeded() + + let animationInstructions: (() -> Void) = { + roomContextualMenuViewController.showMenuToolbar() + roomContextualMenuViewController.showReactionsMenu() + roomContextualMenuViewController.view.layoutIfNeeded() + } + + if animated { + let animationDuration = usedSingleTapGesture ? AnimationDurations.showMenuFromSingleTap : AnimationDurations.showMenu + + UIView.animate(withDuration: animationDuration, animations: { + animationInstructions() + }, completion: { completed in + completion?() + }) + } else { + animationInstructions() + completion?() + } + } + + func hideContextualMenu(animated: Bool, completion: (() -> Void)?) { + guard let roomContextualMenuViewController = self.roomContextualMenuViewController, self.isPresenting else { + completion?() + return + } + + let animationInstructions: (() -> Void) = { + roomContextualMenuViewController.hideMenuToolbar() + roomContextualMenuViewController.hideReactionsMenu() + roomContextualMenuViewController.view.layoutIfNeeded() + } + + let animationCompletionInstructions: (() -> Void) = { + roomContextualMenuViewController.vc_removeFromParent() + self.roomContextualMenuViewController = nil + completion?() + } + + if animated { + if roomContextualMenuViewController.shouldPerformTappedReactionAnimation { + UIView.animate(withDuration: AnimationDurations.selectedReaction, animations: { + roomContextualMenuViewController.selectedReactionAnimationsIntructionsPart1() + }, completion: { _ in + UIView.animate(withDuration: AnimationDurations.hideMenu, animations: { + roomContextualMenuViewController.selectedReactionAnimationsIntructionsPart2() + animationInstructions() + }, completion: { completed in + animationCompletionInstructions() + }) + }) + } else { + UIView.animate(withDuration: AnimationDurations.hideMenu, animations: { + animationInstructions() + }, completion: { completed in + animationCompletionInstructions() + }) + } + } else { + animationInstructions() + animationCompletionInstructions() + } + } +} diff --git a/Riot/Modules/Room/ContextualMenu/RoomContextualMenuToolbarView.swift b/Riot/Modules/Room/ContextualMenu/RoomContextualMenuToolbarView.swift new file mode 100644 index 000000000..8d695c945 --- /dev/null +++ b/Riot/Modules/Room/ContextualMenu/RoomContextualMenuToolbarView.swift @@ -0,0 +1,141 @@ +/* + Copyright 2019 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 + +final class RoomContextualMenuToolbarView: MXKRoomInputToolbarView, NibOwnerLoadable, Themable { + + // MARK: - Constants + + private enum Constants { + static let menuItemMinWidth: CGFloat = 50.0 + static let menuItemMaxWidth: CGFloat = 80.0 + } + + // MARK: - Properties + + // MARK: Outlets + + @IBOutlet private weak var menuItemsStackView: UIStackView! + @IBOutlet private weak var separatorView: UIView! + + // MARK: Private + + private var theme: Theme? + private var menuItemViews: [ContextualMenuItemView] = [] + + // MARK: - Public + + @objc func update(theme: Theme) { + self.theme = theme + self.backgroundColor = theme.backgroundColor + self.tintColor = theme.tintColor + self.separatorView.backgroundColor = theme.lineBreakColor + + for menuItemView in self.menuItemViews { + menuItemView.titleColor = theme.tintColor + menuItemView.imageColor = theme.tintColor + } + } + + @objc func fill(contextualMenuItems: [RoomContextualMenuItem]) { + self.menuItemsStackView.vc_removeAllSubviews() + self.menuItemViews.removeAll() + + for menuItem in contextualMenuItems { + let menuItemView = ContextualMenuItemView() + menuItemView.fill(menuItem: menuItem) + + if let theme = theme { + menuItemView.titleColor = theme.textPrimaryColor + menuItemView.imageColor = theme.tintColor + } + + self.add(menuItemView: menuItemView) + } + + self.layoutIfNeeded() + } + + // MARK: - Setup + + private func commonInit() { + } + + convenience init() { + self.init(frame: CGRect.zero) + self.loadNibContent() + commonInit() + } + + required init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + self.loadNibContent() + commonInit() + } + + override init(frame: CGRect) { + super.init(frame: frame) + self.loadNibContent() + commonInit() + } + + // MARK: - Life cycle + + override func awakeFromNib() { + super.awakeFromNib() + } + + // MARK: - Private + + private func add(menuItemView: ContextualMenuItemView) { + let menuItemContentView = UIView() + menuItemContentView.backgroundColor = .clear + + self.add(menuItemView: menuItemView, on: menuItemContentView) + + self.menuItemsStackView.addArrangedSubview(menuItemContentView) + + let widthConstraint = menuItemContentView.widthAnchor.constraint(equalTo: self.menuItemsStackView.widthAnchor) + widthConstraint.priority = .defaultLow + widthConstraint.isActive = true + + self.menuItemViews.append(menuItemView) + } + + private func add(menuItemView: ContextualMenuItemView, on contentView: UIView) { + contentView.translatesAutoresizingMaskIntoConstraints = false + menuItemView.translatesAutoresizingMaskIntoConstraints = false + + contentView.addSubview(menuItemView) + + menuItemView.centerXAnchor.constraint(equalTo: contentView.centerXAnchor).isActive = true + menuItemView.centerYAnchor.constraint(equalTo: contentView.centerYAnchor).isActive = true + + let widthConstraint = menuItemView.widthAnchor.constraint(equalToConstant: 0.0) + widthConstraint.priority = .defaultLow + widthConstraint.isActive = true + + let minWidthConstraint = menuItemView.widthAnchor.constraint(greaterThanOrEqualToConstant: Constants.menuItemMinWidth) + minWidthConstraint.priority = .required + minWidthConstraint.isActive = true + + let maxWidthConstraint = menuItemView.widthAnchor.constraint(lessThanOrEqualToConstant: Constants.menuItemMaxWidth) + maxWidthConstraint.priority = .required + maxWidthConstraint.isActive = true + } +} diff --git a/Riot/Modules/Room/ContextualMenu/RoomContextualMenuToolbarView.xib b/Riot/Modules/Room/ContextualMenu/RoomContextualMenuToolbarView.xib new file mode 100644 index 000000000..42b2b9ab3 --- /dev/null +++ b/Riot/Modules/Room/ContextualMenu/RoomContextualMenuToolbarView.xib @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Riot/Modules/Room/ContextualMenu/RoomContextualMenuViewController.storyboard b/Riot/Modules/Room/ContextualMenu/RoomContextualMenuViewController.storyboard new file mode 100644 index 000000000..f47344996 --- /dev/null +++ b/Riot/Modules/Room/ContextualMenu/RoomContextualMenuViewController.storyboard @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Riot/Modules/Room/ContextualMenu/RoomContextualMenuViewController.swift b/Riot/Modules/Room/ContextualMenu/RoomContextualMenuViewController.swift new file mode 100644 index 000000000..baba7f279 --- /dev/null +++ b/Riot/Modules/Room/ContextualMenu/RoomContextualMenuViewController.swift @@ -0,0 +1,267 @@ +/* + Copyright 2019 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 + +@objc protocol RoomContextualMenuViewControllerDelegate: class { + func roomContextualMenuViewControllerDidTapBackgroundOverlay(_ viewController: RoomContextualMenuViewController) +} + +@objcMembers +final class RoomContextualMenuViewController: UIViewController, Themable { + + // MARK: - Constants + + private enum Constants { + static let reactionsMenuViewVerticalMargin: CGFloat = 10.0 + static let reactionsMenuViewHiddenScale: CGFloat = 0.97 + } + + // MARK: - Properties + + // MARK: Outlets + + @IBOutlet private weak var backgroundOverlayView: UIView! + @IBOutlet private weak var menuToolbarView: RoomContextualMenuToolbarView! + + @IBOutlet private weak var menuToolbarViewHeightConstraint: NSLayoutConstraint! + @IBOutlet private weak var menuToolbarViewBottomConstraint: NSLayoutConstraint! + + @IBOutlet private weak var reactionsMenuContainerView: UIView! + @IBOutlet private weak var reactionsMenuViewHeightConstraint: NSLayoutConstraint! + @IBOutlet private weak var reactionsMenuViewBottomConstraint: NSLayoutConstraint! + + // MARK: Private + + private var theme: Theme! + private var contextualMenuItems: [RoomContextualMenuItem] = [] + private var reactionsMenuViewModel: ReactionsMenuViewModel? + + private weak var reactionsMenuView: ReactionsMenuView? + + private var reactionsMenuViewBottomStartConstraintConstant: CGFloat? + private var reactionsMenuViewBottomEndConstraintConstant: CGFloat? + + private var hiddenToolbarViewBottomConstant: CGFloat { + let bottomSafeAreaHeight: CGFloat + + if #available(iOS 11.0, *) { + bottomSafeAreaHeight = self.view.safeAreaInsets.bottom + } else { + bottomSafeAreaHeight = self.bottomLayoutGuide.length + } + + return -(self.menuToolbarViewHeightConstraint.constant + bottomSafeAreaHeight) + } + + private var shouldPresentReactionsMenu: Bool { + return self.reactionsMenuContainerView.isHidden == false + } + + // MARK: Public + + var contentToReactFrame: CGRect? + var shouldPerformTappedReactionAnimation: Bool { + return self.reactionsMenuView?.reactionHasBeenTapped ?? false + } + + weak var delegate: RoomContextualMenuViewControllerDelegate? + + // MARK: - Setup + + class func instantiate() -> RoomContextualMenuViewController { + let viewController = StoryboardScene.RoomContextualMenuViewController.initialScene.instantiate() + viewController.theme = ThemeService.shared().theme + return viewController + } + + // MARK: - Life cycle + + override func viewDidLoad() { + super.viewDidLoad() + + // Do any additional setup after loading the view. + + self.backgroundOverlayView.isUserInteractionEnabled = true + self.setupBackgroundOverlayGestureRecognizers() + + self.updateViews() + + self.registerThemeServiceDidChangeThemeNotification() + self.update(theme: self.theme) + } + + // MARK: - Public + + func update(contextualMenuItems: [RoomContextualMenuItem], reactionsMenuViewModel: ReactionsMenuViewModel?) { + self.contextualMenuItems = contextualMenuItems + self.reactionsMenuViewModel = reactionsMenuViewModel + if self.isViewLoaded { + self.updateViews() + } + } + + func showMenuToolbar() { + self.menuToolbarViewBottomConstraint.constant = 0 + self.menuToolbarView.alpha = 1 + } + + func hideMenuToolbar() { + self.menuToolbarViewBottomConstraint.constant = self.hiddenToolbarViewBottomConstant + self.menuToolbarView.alpha = 0 + } + + func prepareReactionsMenuAnimations() { + guard let frame = self.contentToReactFrame, frame.equalTo(CGRect.null) == false else { + return + } + + let menuHeight = self.reactionsMenuViewHeightConstraint.constant + let verticalMargin = Constants.reactionsMenuViewVerticalMargin + + let reactionsMenuViewBottomStartConstraintConstant: CGFloat? + let reactionsMenuViewBottomEndConstraintConstant: CGFloat? + + // Try to display the menu at the top of the message first + // Then, try at the bottom + // Else, keep the position defined in the storyboard + if frame.origin.y - verticalMargin >= menuHeight { + let menuViewBottomY = frame.origin.y - verticalMargin + reactionsMenuViewBottomStartConstraintConstant = menuViewBottomY + menuHeight/2 + reactionsMenuViewBottomEndConstraintConstant = menuViewBottomY + } else { + let frameBottomY = frame.origin.y + frame.size.height + verticalMargin + let visibleViewHeight = self.view.frame.size.height - self.menuToolbarView.frame.size.height + + if frameBottomY + menuHeight < visibleViewHeight { + let menuViewBottomY = frameBottomY + menuHeight + + reactionsMenuViewBottomEndConstraintConstant = menuViewBottomY + reactionsMenuViewBottomStartConstraintConstant = menuViewBottomY - menuHeight/2 + } else { + reactionsMenuViewBottomEndConstraintConstant = nil + reactionsMenuViewBottomStartConstraintConstant = nil + } + } + + self.reactionsMenuViewBottomStartConstraintConstant = reactionsMenuViewBottomStartConstraintConstant + self.reactionsMenuViewBottomEndConstraintConstant = reactionsMenuViewBottomEndConstraintConstant + + self.reactionsMenuContainerView.isHidden = false + } + + func showReactionsMenu() { + guard self.shouldPresentReactionsMenu, let reactionsMenuView = self.reactionsMenuView else { + return + } + + if let reactionsMenuViewBottomEndConstraintConstant = self.reactionsMenuViewBottomEndConstraintConstant { + self.reactionsMenuViewBottomConstraint.constant = reactionsMenuViewBottomEndConstraintConstant + } + + reactionsMenuView.alpha = 1 + reactionsMenuContainerView.transform = CGAffineTransform.identity + } + + func hideReactionsMenu() { + guard self.shouldPresentReactionsMenu, let reactionsMenuView = self.reactionsMenuView else { + return + } + + if let reactionsMenuViewBottomStartConstraintConstant = self.reactionsMenuViewBottomStartConstraintConstant { + self.reactionsMenuViewBottomConstraint.constant = reactionsMenuViewBottomStartConstraintConstant + } + + reactionsMenuView.alpha = 0 + + let transformScale = Constants.reactionsMenuViewHiddenScale + self.reactionsMenuContainerView.transform = CGAffineTransform(scaleX: transformScale, y: transformScale) + } + + func selectedReactionAnimationsIntructionsPart1() { + self.reactionsMenuView?.selectionAnimationInstructionPart1() + } + + func selectedReactionAnimationsIntructionsPart2() { + self.reactionsMenuView?.selectionAnimationInstructionPart2() + } + + func update(theme: Theme) { + self.menuToolbarView.update(theme: theme) + } + + // MARK: - Private + + private func updateViews() { + self.menuToolbarView.fill(contextualMenuItems: self.contextualMenuItems) + + let hideReactionMenu: Bool + + if let reactionsMenuViewModel = self.reactionsMenuViewModel { + hideReactionMenu = false + self.updateReactionsMenu(with: reactionsMenuViewModel) + } else { + hideReactionMenu = true + } + + self.reactionsMenuContainerView.isHidden = hideReactionMenu + } + + private func updateReactionsMenu(with viewModel: ReactionsMenuViewModel) { + + if self.reactionsMenuContainerView.subviews.isEmpty { + let reactionsMenuView = ReactionsMenuView.loadFromNib() + self.reactionsMenuContainerView.vc_addSubViewMatchingParent(reactionsMenuView) + self.reactionsMenuView = reactionsMenuView + } + + self.reactionsMenuView?.viewModel = viewModel + } + + private func setupBackgroundOverlayGestureRecognizers() { + + let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handle(gestureRecognizer:))) + tapGestureRecognizer.delegate = self + + let swipeGestureRecognizer = UISwipeGestureRecognizer(target: self, action: #selector(handle(gestureRecognizer:))) + swipeGestureRecognizer.direction = [.down, .up] + swipeGestureRecognizer.delegate = self + + self.backgroundOverlayView.addGestureRecognizer(tapGestureRecognizer) + self.backgroundOverlayView.addGestureRecognizer(swipeGestureRecognizer) + } + + @objc private func handle(gestureRecognizer: UIGestureRecognizer) { + self.delegate?.roomContextualMenuViewControllerDidTapBackgroundOverlay(self) + } + + private func registerThemeServiceDidChangeThemeNotification() { + NotificationCenter.default.addObserver(self, selector: #selector(themeDidChange), name: .themeServiceDidChangeTheme, object: nil) + } + + @objc private func themeDidChange() { + self.update(theme: ThemeService.shared().theme) + } +} + +// MARK: - UIGestureRecognizerDelegate +extension RoomContextualMenuViewController: UIGestureRecognizerDelegate { + + // Avoid triggering background overlay gesture recognizers when touching reactions menu + func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool { + return touch.vc_isInside(view: self.reactionsMenuContainerView) == false + } +} diff --git a/Riot/Modules/Room/DataSources/RoomDataSource.h b/Riot/Modules/Room/DataSources/RoomDataSource.h index dc695ba7c..1c7dfda5a 100644 --- a/Riot/Modules/Room/DataSources/RoomDataSource.h +++ b/Riot/Modules/Room/DataSources/RoomDataSource.h @@ -34,6 +34,11 @@ */ @property(nonatomic) BOOL markTimelineInitialEvent; +/** + Tell whether timestamp should be displayed on event selection. Default is YES. + */ +@property(nonatomic) BOOL showBubbleDateTimeOnSelection; + /** Check if there is an active jitsi widget in the room and return it. @@ -41,4 +46,20 @@ */ - (Widget *)jitsiWidget; +/** + Send a video to the room. + Note: Move this method to MatrixKit when MatrixKit project will handle Swift module. + + While sending, a fake event will be echoed in the messages list. + Once complete, this local echo will be replaced by the event saved by the homeserver. + + @param videoLocalURL the local filesystem path of the video to send. + @param success A block object called when the operation succeeds. It returns + the event id of the event generated on the homeserver + @param failure A block object called when the operation fails. + */ +- (void)sendVideo:(NSURL*)videoLocalURL + success:(void (^)(NSString *eventId))success + failure:(void (^)(NSError *error))failure; + @end diff --git a/Riot/Modules/Room/DataSources/RoomDataSource.m b/Riot/Modules/Room/DataSources/RoomDataSource.m index 49d189283..fa7034b45 100644 --- a/Riot/Modules/Room/DataSources/RoomDataSource.m +++ b/Riot/Modules/Room/DataSources/RoomDataSource.m @@ -27,7 +27,9 @@ #import "MXRoom+Riot.h" -@interface RoomDataSource() + + +@interface RoomDataSource() { // Observe kThemeServiceDidChangeThemeNotification to handle user interface theme change. id kThemeServiceDidChangeThemeNotificationObserver; @@ -58,6 +60,9 @@ self.markTimelineInitialEvent = NO; + self.showBubbleDateTimeOnSelection = YES; + self.showReactions = YES; + // Observe user interface theme change. kThemeServiceDidChangeThemeNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kThemeServiceDidChangeThemeNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) { @@ -115,87 +120,29 @@ [super destroy]; } -- (void)didReceiveReceiptEvent:(MXEvent *)receiptEvent roomState:(MXRoomState *)roomState +- (void)updateCellDataReactions:(id)cellData forEventId:(NSString*)eventId { - // Do the processing on the same processing queue as MXKRoomDataSource - dispatch_async(MXKRoomDataSource.processingQueue, ^{ + [super updateCellDataReactions:cellData forEventId:eventId]; - // Remove the previous displayed read receipt for each user who sent a - // new read receipt. - // To implement it, we need to find the sender id of each new read receipt - // among the read receipts array of all events in all bubbles. - NSArray *readReceiptSenders = receiptEvent.readReceiptSenders; + [self setNeedsUpdateAdditionalContentHeightForCellData:cellData]; +} - @synchronized(bubbles) - { - for (RoomBubbleCellData *cellData in bubbles) - { - NSMutableDictionary *> *updatedCellDataReadReceipts = [NSMutableDictionary dictionary]; +- (void)updateCellData:(MXKRoomBubbleCellData*)cellData withReadReceipts:(NSArray*)readReceipts forEventId:(NSString*)eventId +{ + [super updateCellData:cellData withReadReceipts:readReceipts forEventId:eventId]; + + [self setNeedsUpdateAdditionalContentHeightForCellData:cellData]; +} - for (NSString *eventId in cellData.readReceipts) - { - for (MXReceiptData *receiptData in cellData.readReceipts[eventId]) - { - for (NSString *senderId in readReceiptSenders) - { - if ([receiptData.userId isEqualToString:senderId]) - { - if (!updatedCellDataReadReceipts[eventId]) - { - updatedCellDataReadReceipts[eventId] = cellData.readReceipts[eventId]; - } - - NSPredicate *predicate = [NSPredicate predicateWithFormat:@"userId!=%@", receiptData.userId]; - updatedCellDataReadReceipts[eventId] = [updatedCellDataReadReceipts[eventId] filteredArrayUsingPredicate:predicate]; - break; - } - } - - } - } - - // Flush found changed to the cell data - for (NSString *eventId in updatedCellDataReadReceipts) - { - if (updatedCellDataReadReceipts[eventId].count) - { - cellData.readReceipts[eventId] = updatedCellDataReadReceipts[eventId]; - } - else - { - cellData.readReceipts[eventId] = nil; - } - } - } - } - - // Update cell data we have received a read receipt for - NSArray *readEventIds = receiptEvent.readReceiptEventIds; - for (NSString* eventId in readEventIds) - { - RoomBubbleCellData *cellData = [self cellDataOfEventWithEventId:eventId]; - if (cellData) - { - @synchronized(bubbles) - { - if (!cellData.hasNoDisplay) - { - cellData.readReceipts[eventId] = [self.room getEventReceipts:eventId sorted:YES]; - } - else - { - // Ignore the read receipts on the events without an actual display. - cellData.readReceipts[eventId] = nil; - } - } - } - } - - dispatch_async(dispatch_get_main_queue(), ^{ - // TODO: Be smarter and update only updated cells - [super didReceiveReceiptEvent:receiptEvent roomState:roomState]; - }); - }); +- (void)setNeedsUpdateAdditionalContentHeightForCellData:(id)cellData +{ + RoomBubbleCellData *roomBubbleCellData; + + if ([cellData isKindOfClass:[RoomBubbleCellData class]]) + { + roomBubbleCellData = (RoomBubbleCellData*)cellData; + [roomBubbleCellData setNeedsUpdateAdditionalContentHeight]; + } } #pragma mark - @@ -260,21 +207,83 @@ [bubbleCell addTimestampLabelForComponent:cellData.mostRecentComponentIndex]; } + NSMutableArray *temporaryViews = [NSMutableArray new]; + // Handle read receipts and read marker display. // Ignore the read receipts on the bubble without actual display. // Ignore the read receipts on collapsed bubbles - if ((self.showBubbleReceipts && cellData.readReceipts.count && !isCollapsableCellCollapsed) || self.showReadMarker) + if ((((self.showBubbleReceipts && cellData.readReceipts.count) || cellData.reactions.count) && !isCollapsableCellCollapsed) || self.showReadMarker) { // Read receipts container are inserted here on the right side into the content view. // Some vertical whitespaces are added in message text view (see RoomBubbleCellData class) to insert correctly multiple receipts. - NSInteger index = bubbleComponents.count; - CGFloat bottomPositionY = bubbleCell.frame.size.height; - while (index--) + + NSInteger index = 0; + + for (MXKRoomBubbleComponent *component in bubbleComponents) { - MXKRoomBubbleComponent *component = bubbleComponents[index]; + NSString *componentEventId = component.event.eventId; if (component.event.sentState != MXEventSentStateFailed) { + CGFloat bottomPositionY; + + CGRect bubbleComponentFrame = [bubbleCell componentFrameInContentViewForIndex:index]; + + if (CGRectEqualToRect(bubbleComponentFrame, CGRectNull) == NO) + { + bottomPositionY = bubbleComponentFrame.origin.y + bubbleComponentFrame.size.height; + } + else + { + continue; + } + + MXAggregatedReactions* reactions = cellData.reactions[componentEventId]; + + BubbleReactionsView *reactionsView; + + if (reactions && !isCollapsableCellCollapsed) + { + BOOL showAllReactions = [cellData showAllReactionsForEvent:componentEventId]; + BubbleReactionsViewModel *bubbleReactionsViewModel = [[BubbleReactionsViewModel alloc] initWithAggregatedReactions:reactions + eventId:componentEventId + showAll:showAllReactions]; + + reactionsView = [BubbleReactionsView new]; + reactionsView.viewModel = bubbleReactionsViewModel; + [reactionsView updateWithTheme:ThemeService.shared.theme]; + + [temporaryViews addObject:reactionsView]; + + bubbleReactionsViewModel.viewModelDelegate = self; + + reactionsView.translatesAutoresizingMaskIntoConstraints = NO; + [bubbleCell.contentView addSubview:reactionsView]; + + if (!bubbleCell.tmpSubviews) + { + bubbleCell.tmpSubviews = [NSMutableArray array]; + } + [bubbleCell.tmpSubviews addObject:reactionsView]; + + CGFloat leftMargin = RoomBubbleCellLayout.reactionsViewLeftMargin; + + if (self.room.summary.isEncrypted) + { + leftMargin+= RoomBubbleCellLayout.encryptedContentLeftMargin; + } + + // Force receipts container size + [NSLayoutConstraint activateConstraints: + @[ + [reactionsView.leadingAnchor constraintEqualToAnchor:reactionsView.superview.leadingAnchor constant:leftMargin], + [reactionsView.trailingAnchor constraintEqualToAnchor:reactionsView.superview.trailingAnchor constant:-RoomBubbleCellLayout.reactionsViewRightMargin], + [reactionsView.topAnchor constraintEqualToAnchor:reactionsView.superview.topAnchor constant:bottomPositionY + RoomBubbleCellLayout.reactionsViewTopMargin] + ]]; + } + + MXKReceiptSendersContainer* avatarsContainer; + // Handle read receipts (if any) if (self.showBubbleReceipts && cellData.readReceipts.count && !isCollapsableCellCollapsed) { @@ -305,7 +314,7 @@ if (roomMembers.count) { // Define the read receipts container, positioned on the right border of the bubble cell (Note the right margin 6 pts). - MXKReceiptSendersContainer* avatarsContainer = [[MXKReceiptSendersContainer alloc] initWithFrame:CGRectMake(bubbleCell.frame.size.width - 156, bottomPositionY - 13, 150, 12) andMediaManager:self.mxSession.mediaManager]; + avatarsContainer = [[MXKReceiptSendersContainer alloc] initWithFrame:CGRectMake(bubbleCell.frame.size.width - RoomBubbleCellLayout.readReceiptsViewWidth + RoomBubbleCellLayout.readReceiptsViewRightMargin, bottomPositionY + RoomBubbleCellLayout.readReceiptsViewTopMargin, RoomBubbleCellLayout.readReceiptsViewWidth, RoomBubbleCellLayout.readReceiptsViewHeight) andMediaManager:self.mxSession.mediaManager]; // Custom avatar display avatarsContainer.maxDisplayedAvatars = 5; @@ -327,6 +336,8 @@ avatarsContainer.translatesAutoresizingMaskIntoConstraints = NO; avatarsContainer.accessibilityIdentifier = @"readReceiptsContainer"; + [temporaryViews addObject:avatarsContainer]; + // Add this read receipts container in the content view if (!bubbleCell.tmpSubviews) { @@ -345,14 +356,14 @@ toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 - constant:150]; + constant:RoomBubbleCellLayout.readReceiptsViewWidth]; NSLayoutConstraint *heightConstraint = [NSLayoutConstraint constraintWithItem:avatarsContainer attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 - constant:12]; + constant:RoomBubbleCellLayout.readReceiptsViewHeight]; // Force receipts container position NSLayoutConstraint *trailingConstraint = [NSLayoutConstraint constraintWithItem:avatarsContainer @@ -361,14 +372,19 @@ toItem:avatarsContainer.superview attribute:NSLayoutAttributeTrailing multiplier:1.0 - constant:-6]; - NSLayoutConstraint *topConstraint = [NSLayoutConstraint constraintWithItem:avatarsContainer - attribute:NSLayoutAttributeTop - relatedBy:NSLayoutRelationEqual - toItem:avatarsContainer.superview - attribute:NSLayoutAttributeTop - multiplier:1.0 - constant:bottomPositionY - 13]; + constant:-RoomBubbleCellLayout.readReceiptsViewRightMargin]; + + // At the bottom, we have reactions or nothing + NSLayoutConstraint *topConstraint; + if (reactionsView) + { + topConstraint = [avatarsContainer.topAnchor constraintEqualToAnchor:reactionsView.bottomAnchor constant:RoomBubbleCellLayout.readReceiptsViewTopMargin]; + } + else + { + topConstraint = [avatarsContainer.topAnchor constraintEqualToAnchor:avatarsContainer.superview.topAnchor constant:bottomPositionY + RoomBubbleCellLayout.readReceiptsViewTopMargin]; + } + // Available on iOS 8 and later [NSLayoutConstraint activateConstraints:@[widthConstraint, heightConstraint, topConstraint, trailingConstraint]]; @@ -385,9 +401,9 @@ bubbleCell.bubbleOverlayContainer.userInteractionEnabled = NO; bubbleCell.bubbleOverlayContainer.hidden = NO; - if ([component.event.eventId isEqualToString:self.room.accountData.readMarkerEventId]) + if ([componentEventId isEqualToString:self.room.accountData.readMarkerEventId]) { - bubbleCell.readMarkerView = [[UIView alloc] initWithFrame:CGRectMake(0, bottomPositionY - 2, bubbleCell.bubbleOverlayContainer.frame.size.width, 2)]; + bubbleCell.readMarkerView = [[UIView alloc] initWithFrame:CGRectMake(0, bottomPositionY - RoomBubbleCellLayout.readMarkerViewHeight, bubbleCell.bubbleOverlayContainer.frame.size.width, RoomBubbleCellLayout.readMarkerViewHeight)]; bubbleCell.readMarkerView.backgroundColor = ThemeService.shared.theme.tintColor; // Hide by default the marker, it will be shown and animated when the cell will be rendered. bubbleCell.readMarkerView.hidden = YES; @@ -399,45 +415,58 @@ // Force read marker constraints bubbleCell.readMarkerViewTopConstraint = [NSLayoutConstraint constraintWithItem:bubbleCell.readMarkerView - attribute:NSLayoutAttributeTop - relatedBy:NSLayoutRelationEqual - toItem:bubbleCell.bubbleOverlayContainer - attribute:NSLayoutAttributeTop - multiplier:1.0 - constant:bottomPositionY - 2]; - bubbleCell.readMarkerViewLeadingConstraint = [NSLayoutConstraint constraintWithItem:bubbleCell.readMarkerView - attribute:NSLayoutAttributeLeading - relatedBy:NSLayoutRelationEqual - toItem:bubbleCell.bubbleOverlayContainer - attribute:NSLayoutAttributeLeading - multiplier:1.0 - constant:0]; - bubbleCell.readMarkerViewTrailingConstraint = [NSLayoutConstraint constraintWithItem:bubbleCell.bubbleOverlayContainer - attribute:NSLayoutAttributeTrailing + attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual - toItem:bubbleCell.readMarkerView - attribute:NSLayoutAttributeTrailing + toItem:bubbleCell.bubbleOverlayContainer + attribute:NSLayoutAttributeTop multiplier:1.0 - constant:0]; + constant:bottomPositionY - RoomBubbleCellLayout.readMarkerViewHeight]; + bubbleCell.readMarkerViewLeadingConstraint = [NSLayoutConstraint constraintWithItem:bubbleCell.readMarkerView + attribute:NSLayoutAttributeLeading + relatedBy:NSLayoutRelationEqual + toItem:bubbleCell.bubbleOverlayContainer + attribute:NSLayoutAttributeLeading + multiplier:1.0 + constant:0]; + bubbleCell.readMarkerViewTrailingConstraint = [NSLayoutConstraint constraintWithItem:bubbleCell.bubbleOverlayContainer + attribute:NSLayoutAttributeTrailing + relatedBy:NSLayoutRelationEqual + toItem:bubbleCell.readMarkerView + attribute:NSLayoutAttributeTrailing + multiplier:1.0 + constant:0]; bubbleCell.readMarkerViewHeightConstraint = [NSLayoutConstraint constraintWithItem:bubbleCell.readMarkerView - attribute:NSLayoutAttributeHeight - relatedBy:NSLayoutRelationEqual - toItem:nil - attribute:NSLayoutAttributeNotAnAttribute - multiplier:1.0 - constant:2]; + attribute:NSLayoutAttributeHeight + relatedBy:NSLayoutRelationEqual + toItem:nil + attribute:NSLayoutAttributeNotAnAttribute + multiplier:1.0 + constant:RoomBubbleCellLayout.readMarkerViewHeight]; [NSLayoutConstraint activateConstraints:@[bubbleCell.readMarkerViewTopConstraint, bubbleCell.readMarkerViewLeadingConstraint, bubbleCell.readMarkerViewTrailingConstraint, bubbleCell.readMarkerViewHeightConstraint]]; } } } - // Prepare the bottom position for the next read receipt container (if any) - bottomPositionY = bubbleCell.msgTextViewTopConstraint.constant + component.position.y; + index++; } } + // Update attachmentView bottom constraint to display reactions and read receipts if needed + + UIView *attachmentView = bubbleCell.attachmentView; + NSLayoutConstraint *attachmentViewBottomConstraint = bubbleCell.attachViewBottomConstraint; + + if (attachmentView && temporaryViews.count) + { + attachmentViewBottomConstraint.constant = roomBubbleCellData.additionalContentHeight; + } + else if (attachmentView) + { + [bubbleCell resetAttachmentViewBottomConstraintConstant]; + } + // Check whether an event is currently selected: the other messages are then blurred if (_selectedEventId) { @@ -445,7 +474,7 @@ NSInteger selectedComponentIndex = cellData.selectedComponentIndex; if (selectedComponentIndex != NSNotFound) { - [bubbleCell selectComponent:cellData.selectedComponentIndex]; + [bubbleCell selectComponent:cellData.selectedComponentIndex showEditButton:NO showTimestamp:cellData.showTimestampForSelectedComponent]; } else { @@ -492,11 +521,14 @@ { RoomBubbleCellData *cellData = [self cellDataOfEventWithEventId:_selectedEventId]; cellData.selectedEventId = nil; + cellData.showTimestampForSelectedComponent = NO; } if (selectedEventId.length) { RoomBubbleCellData *cellData = [self cellDataOfEventWithEventId:selectedEventId]; + + cellData.showTimestampForSelectedComponent = self.showBubbleDateTimeOnSelection; if (cellData.collapsed && cellData.nextCollapsableCellData) { @@ -523,4 +555,56 @@ return jitsiWidget; } +- (void)sendVideo:(NSURL*)videoLocalURL + success:(void (^)(NSString *eventId))success + failure:(void (^)(NSError *error))failure +{ + UIImage *videoThumbnail = [MXKVideoThumbnailGenerator.shared generateThumbnailFrom:videoLocalURL]; + [self sendVideo:videoLocalURL withThumbnail:videoThumbnail success:success failure:failure]; +} + +#pragma mark - BubbleReactionsViewModelDelegate + +- (void)bubbleReactionsViewModel:(BubbleReactionsViewModel *)viewModel didAddReaction:(MXReactionCount *)reactionCount forEventId:(NSString *)eventId +{ + [self addReaction:reactionCount.reaction forEventId:eventId success:^{ + + } failure:^(NSError *error) { + + }]; +} + +- (void)bubbleReactionsViewModel:(BubbleReactionsViewModel *)viewModel didRemoveReaction:(MXReactionCount * _Nonnull)reactionCount forEventId:(NSString * _Nonnull)eventId +{ + [self removeReaction:reactionCount.reaction forEventId:eventId success:^{ + + } failure:^(NSError *error) { + + }]; +} + +- (void)bubbleReactionsViewModel:(BubbleReactionsViewModel *)viewModel didShowAllTappedForEventId:(NSString * _Nonnull)eventId +{ + [self setShowAllReactions:YES forEvent:eventId]; +} + +- (void)bubbleReactionsViewModel:(BubbleReactionsViewModel *)viewModel didShowLessTappedForEventId:(NSString * _Nonnull)eventId +{ + [self setShowAllReactions:NO forEvent:eventId]; +} + +- (void)setShowAllReactions:(BOOL)showAllReactions forEvent:(NSString*)eventId +{ + id cellData = [self cellDataOfEventWithEventId:eventId]; + if ([cellData isKindOfClass:[RoomBubbleCellData class]]) + { + RoomBubbleCellData *roomBubbleCellData = (RoomBubbleCellData*)cellData; + + [roomBubbleCellData setShowAllReactions:showAllReactions forEvent:eventId]; + [self updateCellDataReactions:roomBubbleCellData forEventId:eventId]; + + [self.delegate dataSource:self didCellChange:nil]; + } +} + @end diff --git a/Riot/Modules/Room/EditHistory/EditHistoryCell.swift b/Riot/Modules/Room/EditHistory/EditHistoryCell.swift new file mode 100644 index 000000000..0d05ef6c2 --- /dev/null +++ b/Riot/Modules/Room/EditHistory/EditHistoryCell.swift @@ -0,0 +1,38 @@ +/* + Copyright 2019 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 + +final class EditHistoryCell: UITableViewCell, NibReusable, Themable { + + // MARK: - Properties + + @IBOutlet private weak var timestampLabel: UILabel! + @IBOutlet private weak var messageLabel: UILabel! + + // MARK: - Public + + func fill(with timeString: String, and attributedMessage: NSAttributedString) { + self.timestampLabel.text = timeString + self.messageLabel.attributedText = attributedMessage + } + + func update(theme: Theme) { + self.backgroundColor = theme.backgroundColor + self.timestampLabel.textColor = theme.textSecondaryColor + } +} diff --git a/Riot/Modules/Room/EditHistory/EditHistoryCell.xib b/Riot/Modules/Room/EditHistory/EditHistoryCell.xib new file mode 100644 index 000000000..7780255db --- /dev/null +++ b/Riot/Modules/Room/EditHistory/EditHistoryCell.xib @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Riot/Modules/Room/EditHistory/EditHistoryCoordinator.swift b/Riot/Modules/Room/EditHistory/EditHistoryCoordinator.swift new file mode 100644 index 000000000..7cabf0a73 --- /dev/null +++ b/Riot/Modules/Room/EditHistory/EditHistoryCoordinator.swift @@ -0,0 +1,67 @@ +// File created from ScreenTemplate +// $ createScreen.sh Room/EditHistory EditHistory +/* + Copyright 2019 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 EditHistoryCoordinator: EditHistoryCoordinatorType { + + // MARK: - Properties + + // MARK: Private + + private var editHistoryViewModel: EditHistoryViewModelType + private let editHistoryViewController: EditHistoryViewController + + // MARK: Public + + // Must be used only internally + var childCoordinators: [Coordinator] = [] + + weak var delegate: EditHistoryCoordinatorDelegate? + + // MARK: - Setup + + init(session: MXSession, + formatter: MXKEventFormatter, + event: MXEvent) { + + let editHistoryViewModel = EditHistoryViewModel(session: session, formatter: formatter, event: event) + let editHistoryViewController = EditHistoryViewController.instantiate(with: editHistoryViewModel) + self.editHistoryViewModel = editHistoryViewModel + self.editHistoryViewController = editHistoryViewController + } + + // MARK: - Public methods + + func start() { + self.editHistoryViewModel.coordinatorDelegate = self + } + + func toPresentable() -> UIViewController { + return self.editHistoryViewController + } +} + +// MARK: - EditHistoryViewModelCoordinatorDelegate +extension EditHistoryCoordinator: EditHistoryViewModelCoordinatorDelegate { + + func editHistoryViewModelDidClose(_ viewModel: EditHistoryViewModelType) { + self.delegate?.editHistoryCoordinatorDidComplete(self) + } +} diff --git a/Riot/Modules/Room/EditHistory/EditHistoryCoordinatorBridgePresenter.swift b/Riot/Modules/Room/EditHistory/EditHistoryCoordinatorBridgePresenter.swift new file mode 100644 index 000000000..f6b20b5e5 --- /dev/null +++ b/Riot/Modules/Room/EditHistory/EditHistoryCoordinatorBridgePresenter.swift @@ -0,0 +1,118 @@ +// File created from FlowTemplate +// $ createRootCoordinator.sh Room/EditHistory EditHistory +/* + Copyright 2019 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 EditHistoryCoordinatorBridgePresenterDelegate { + func editHistoryCoordinatorBridgePresenterDelegateDidComplete(_ coordinatorBridgePresenter: EditHistoryCoordinatorBridgePresenter) +} + +/// EditHistoryCoordinatorBridgePresenter enables to start EditHistoryCoordinator from a view controller. +/// This bridge is used while waiting for global usage of coordinator pattern. +@objcMembers +final class EditHistoryCoordinatorBridgePresenter: NSObject { + + // MARK: - Properties + + // MARK: Private + + private let session: MXSession + private let event: MXEvent + private var coordinator: EditHistoryCoordinator? + + // MARK: Public + + weak var delegate: EditHistoryCoordinatorBridgePresenterDelegate? + + // MARK: - Setup + + init(session: MXSession, + event: MXEvent) { + self.session = session + self.event = event + 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) { + + guard let formatter = self.createEventFormatter(session: self.session) else { + //s das + return + } + + let editHistoryCoordinator = EditHistoryCoordinator(session: self.session, formatter: formatter, event: self.event) + editHistoryCoordinator.delegate = self + + let navigationController = RiotNavigationController() + navigationController.modalPresentationStyle = .formSheet + navigationController.addChild(editHistoryCoordinator.toPresentable()) + viewController.present(navigationController, animated: animated, completion: nil) + + editHistoryCoordinator.start() + + self.coordinator = editHistoryCoordinator + } + + 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: - Private + + func createEventFormatter(session: MXSession) -> EventFormatter? { + guard let formatter = EventFormatter(matrixSession: session) else { + print("[EditHistoryCoordinatorBridgePresenter] createEventFormatter: Cannot build formatter") + return nil + } + + // Use the same event formatter settings as RoomDataSource + formatter.treatMatrixUserIdAsLink = true + formatter.treatMatrixRoomIdAsLink = true + formatter.treatMatrixRoomAliasAsLink = true + formatter.treatMatrixGroupIdAsLink = true + formatter.eventTypesFilterForMessages = MXKAppSettings.standard()?.eventsFilterForMessages + + // But do not display "...(Edited)" + formatter.showEditionMention = false + + return formatter + } +} + +// MARK: - EditHistoryCoordinatorDelegate +extension EditHistoryCoordinatorBridgePresenter: EditHistoryCoordinatorDelegate { + func editHistoryCoordinatorDidComplete(_ coordinator: EditHistoryCoordinatorType) { + self.delegate?.editHistoryCoordinatorBridgePresenterDelegateDidComplete(self) + } +} diff --git a/Riot/Modules/Room/EditHistory/EditHistoryCoordinatorType.swift b/Riot/Modules/Room/EditHistory/EditHistoryCoordinatorType.swift new file mode 100644 index 000000000..2f02bd705 --- /dev/null +++ b/Riot/Modules/Room/EditHistory/EditHistoryCoordinatorType.swift @@ -0,0 +1,28 @@ +// File created from ScreenTemplate +// $ createScreen.sh Room/EditHistory EditHistory +/* + Copyright 2019 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 EditHistoryCoordinatorDelegate: class { + func editHistoryCoordinatorDidComplete(_ coordinator: EditHistoryCoordinatorType) +} + +/// `EditHistoryCoordinatorType` is a protocol describing a Coordinator that handle keybackup setup navigation flow. +protocol EditHistoryCoordinatorType: Coordinator, Presentable { + var delegate: EditHistoryCoordinatorDelegate? { get } +} diff --git a/Riot/Modules/Room/EditHistory/EditHistoryHeaderView.swift b/Riot/Modules/Room/EditHistory/EditHistoryHeaderView.swift new file mode 100644 index 000000000..a547024d1 --- /dev/null +++ b/Riot/Modules/Room/EditHistory/EditHistoryHeaderView.swift @@ -0,0 +1,36 @@ +/* + Copyright 2019 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 + +final class EditHistoryHeaderView: UITableViewHeaderFooterView, NibLoadable, Reusable, Themable { + + // MARK: - Properties + + @IBOutlet private weak var dateLabel: UILabel! + + // MARK: - Public + + func update(theme: Theme) { + self.contentView.backgroundColor = theme.backgroundColor + self.dateLabel.textColor = theme.headerTextPrimaryColor + } + + func fill(with dateString: String) { + self.dateLabel.text = dateString + } +} diff --git a/Riot/Modules/Room/EditHistory/EditHistoryHeaderView.xib b/Riot/Modules/Room/EditHistory/EditHistoryHeaderView.xib new file mode 100644 index 000000000..e0a28bcf5 --- /dev/null +++ b/Riot/Modules/Room/EditHistory/EditHistoryHeaderView.xib @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Riot/Modules/Room/EditHistory/EditHistoryMessage.swift b/Riot/Modules/Room/EditHistory/EditHistoryMessage.swift new file mode 100644 index 000000000..f33864960 --- /dev/null +++ b/Riot/Modules/Room/EditHistory/EditHistoryMessage.swift @@ -0,0 +1,22 @@ +/* + Copyright 2019 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 EditHistoryMessage { + let date: Date + let message: NSAttributedString +} diff --git a/Riot/Modules/Room/EditHistory/EditHistorySection.swift b/Riot/Modules/Room/EditHistory/EditHistorySection.swift new file mode 100644 index 000000000..550378541 --- /dev/null +++ b/Riot/Modules/Room/EditHistory/EditHistorySection.swift @@ -0,0 +1,22 @@ +/* + Copyright 2019 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 EditHistorySection { + let date: Date + let messages: [EditHistoryMessage] +} diff --git a/Riot/Modules/Room/EditHistory/EditHistoryViewAction.swift b/Riot/Modules/Room/EditHistory/EditHistoryViewAction.swift new file mode 100644 index 000000000..d1fed7fc5 --- /dev/null +++ b/Riot/Modules/Room/EditHistory/EditHistoryViewAction.swift @@ -0,0 +1,25 @@ +// File created from ScreenTemplate +// $ createScreen.sh Room/EditHistory EditHistory +/* + Copyright 2019 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 + +/// EditHistoryViewController view actions exposed to view model +enum EditHistoryViewAction { + case loadMore + case close +} diff --git a/Riot/Modules/Room/EditHistory/EditHistoryViewController.storyboard b/Riot/Modules/Room/EditHistory/EditHistoryViewController.storyboard new file mode 100644 index 000000000..059dae309 --- /dev/null +++ b/Riot/Modules/Room/EditHistory/EditHistoryViewController.storyboard @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Riot/Modules/Room/EditHistory/EditHistoryViewController.swift b/Riot/Modules/Room/EditHistory/EditHistoryViewController.swift new file mode 100644 index 000000000..88c254182 --- /dev/null +++ b/Riot/Modules/Room/EditHistory/EditHistoryViewController.swift @@ -0,0 +1,228 @@ +// File created from ScreenTemplate +// $ createScreen.sh Room/EditHistory EditHistory +/* + Copyright 2019 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 + +final class EditHistoryViewController: UIViewController { + + // MARK: - Constants + + private enum Constants { + static let estimatedRowHeight: CGFloat = 38.0 + static let estimatedSectionHeaderHeight: CGFloat = 28.0 + static let editHistoryMessageTimeFormat = "HH:mm" + } + + // MARK: - Properties + + // MARK: Outlets + + @IBOutlet private weak var tableView: UITableView! + + // MARK: Private + + private var viewModel: EditHistoryViewModelType! + private var theme: Theme! + private var errorPresenter: MXKErrorPresentation! + private var activityIndicatorPresenter: ActivityIndicatorPresenter! + + private var editHistorySections: [EditHistorySection] = [] + + private lazy var sectionDateFormatter: DateFormatter = { + let dateFormatter = DateFormatter() + dateFormatter.dateStyle = .full + dateFormatter.timeStyle = .none + dateFormatter.doesRelativeDateFormatting = true + return dateFormatter + }() + + private lazy var messageDateFormatter: DateFormatter = { + let dateFormatter = DateFormatter() + dateFormatter.dateFormat = Constants.editHistoryMessageTimeFormat + dateFormatter.locale = Locale(identifier: "en_US_POSIX") + return dateFormatter + }() + + // MARK: - Setup + + class func instantiate(with viewModel: EditHistoryViewModelType) -> EditHistoryViewController { + let viewController = StoryboardScene.EditHistoryViewController.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. + + self.title = VectorL10n.roomMessageEditsHistoryTitle + + self.setupViews() + self.activityIndicatorPresenter = ActivityIndicatorPresenter() + self.errorPresenter = MXKErrorAlertPresentation() + + self.registerThemeServiceDidChangeThemeNotification() + self.update(theme: self.theme) + + self.viewModel.viewDelegate = self + + self.viewModel.process(viewAction: .loadMore) + } + + override var preferredStatusBarStyle: UIStatusBarStyle { + return self.theme.statusBarStyle + } + + // MARK: - Private + + private func update(theme: Theme) { + self.theme = theme + + self.view.backgroundColor = theme.backgroundColor + self.tableView.backgroundColor = theme.backgroundColor + + if let navigationBar = self.navigationController?.navigationBar { + theme.applyStyle(onNavigationBar: navigationBar) + } + } + + private func registerThemeServiceDidChangeThemeNotification() { + NotificationCenter.default.addObserver(self, selector: #selector(themeDidChange), name: .themeServiceDidChangeTheme, object: nil) + } + + @objc private func themeDidChange() { + self.update(theme: ThemeService.shared().theme) + } + + private func setupViews() { + let closeBarButtonItem = MXKBarButtonItem(title: VectorL10n.close, style: .plain) { [weak self] in + self?.closeButtonAction() + } + + self.navigationItem.rightBarButtonItem = closeBarButtonItem + + self.setupTableView() + } + + private func setupTableView() { + self.tableView.rowHeight = UITableView.automaticDimension + self.tableView.estimatedRowHeight = Constants.estimatedRowHeight + self.tableView.register(cellType: EditHistoryCell.self) + + self.tableView.sectionHeaderHeight = UITableView.automaticDimension + self.tableView.estimatedSectionHeaderHeight = Constants.estimatedSectionHeaderHeight + self.tableView.register(headerFooterViewType: EditHistoryHeaderView.self) + + self.tableView.tableFooterView = UIView() + } + + private func render(viewState: EditHistoryViewState) { + switch viewState { + case .loading: + self.renderLoading() + case .loaded(let sections, let addedCount, let allDataLoaded): + self.renderLoaded(sections: sections, addedCount: addedCount, allDataLoaded: allDataLoaded) + case .error(let error): + self.render(error: error) + } + } + + private func renderLoading() { + self.activityIndicatorPresenter.presentActivityIndicator(on: self.view, animated: true) + } + + private func renderLoaded(sections: [EditHistorySection], addedCount: Int, allDataLoaded: Bool) { + self.activityIndicatorPresenter.removeCurrentActivityIndicator(animated: true) + self.editHistorySections = sections + self.tableView.reloadData() + } + + private func render(error: Error) { + self.activityIndicatorPresenter.removeCurrentActivityIndicator(animated: true) + self.errorPresenter.presentError(from: self, forError: error, animated: true, handler: nil) + } + + // MARK: - Actions + + private func closeButtonAction() { + self.viewModel.process(viewAction: .close) + } +} + +// MARK: - UITableViewDataSource +extension EditHistoryViewController: UITableViewDataSource { + + func numberOfSections(in tableView: UITableView) -> Int { + return self.editHistorySections.count + } + + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return self.editHistorySections[section].messages.count + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let editHistoryCell = tableView.dequeueReusableCell(for: indexPath, cellType: EditHistoryCell.self) + + let editHistoryMessage = self.editHistorySections[indexPath.section].messages[indexPath.row] + + let timeString = self.messageDateFormatter.string(from: editHistoryMessage.date) + + editHistoryCell.update(theme: self.theme) + editHistoryCell.fill(with: timeString, and: editHistoryMessage.message) + + return editHistoryCell + } + + func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { + guard let editHistoryHeaderView: EditHistoryHeaderView = tableView.dequeueReusableHeaderFooterView() else { + return nil + } + let editHistorySection = self.editHistorySections[section] + let dateString = self.sectionDateFormatter.string(from: editHistorySection.date) + + editHistoryHeaderView.update(theme: self.theme) + editHistoryHeaderView.fill(with: dateString) + return editHistoryHeaderView + } +} + +// MARK: - UITableViewDelegate +extension EditHistoryViewController: UITableViewDelegate { + + func scrollViewDidScroll(_ scrollView: UIScrollView) { + + // Check if a scroll beyond scroll view content occurs + let distanceFromBottom = scrollView.contentSize.height - scrollView.contentOffset.y + if distanceFromBottom < scrollView.frame.size.height { + self.viewModel.process(viewAction: .loadMore) + } + } +} + +// MARK: - EditHistoryViewModelViewDelegate +extension EditHistoryViewController: EditHistoryViewModelViewDelegate { + + func editHistoryViewModel(_ viewModel: EditHistoryViewModelType, didUpdateViewState viewSate: EditHistoryViewState) { + self.render(viewState: viewSate) + } +} diff --git a/Riot/Modules/Room/EditHistory/EditHistoryViewModel.swift b/Riot/Modules/Room/EditHistory/EditHistoryViewModel.swift new file mode 100644 index 000000000..461f32699 --- /dev/null +++ b/Riot/Modules/Room/EditHistory/EditHistoryViewModel.swift @@ -0,0 +1,225 @@ +// File created from ScreenTemplate +// $ createScreen.sh Room/EditHistory EditHistory +/* + Copyright 2019 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 EditHistoryViewModel: EditHistoryViewModelType { + + // MARK: - Constants + + private enum Pagination { + static let count: UInt = 30 + } + + // MARK: - Properties + + // MARK: Private + + private let session: MXSession + private let aggregations: MXAggregations + private let formatter: MXKEventFormatter + private let roomId: String + private let event: MXEvent + private let messageFormattingQueue: DispatchQueue + + private var messages: [EditHistoryMessage] = [] + private var operation: MXHTTPOperation? + private var nextBatch: String? + private var viewState: EditHistoryViewState? + + // MARK: Public + + weak var viewDelegate: EditHistoryViewModelViewDelegate? + weak var coordinatorDelegate: EditHistoryViewModelCoordinatorDelegate? + + // MARK: - Setup + + init(session: MXSession, + formatter: MXKEventFormatter, + event: MXEvent) { + self.session = session + self.aggregations = session.aggregations + self.formatter = formatter + self.event = event + self.roomId = event.roomId + self.messageFormattingQueue = DispatchQueue(label: "\(type(of: self)).messageFormattingQueue") + } + + // MARK: - Public + + func process(viewAction: EditHistoryViewAction) { + switch viewAction { + case .loadMore: + self.loadMoreHistory() + case .close: + self.coordinatorDelegate?.editHistoryViewModelDidClose(self) + } + } + + // MARK: - Private + + private func canLoadMoreHistory() -> Bool { + guard let viewState = self.viewState else { + return true + } + + let canLoadMoreHistory: Bool + + switch viewState { + case .loading: + canLoadMoreHistory = false + case .loaded(sections: _, addedCount: _, allDataLoaded: let allLoaded): + canLoadMoreHistory = !allLoaded + default: + canLoadMoreHistory = true + } + + return canLoadMoreHistory + } + + private func loadMoreHistory() { + guard self.canLoadMoreHistory() else { + print("[EditHistoryViewModel] loadMoreHistory: pending loading or all data loaded") + return + } + + guard self.operation == nil else { + print("[EditHistoryViewModel] loadMoreHistory: operation already pending") + return + } + + self.update(viewState: .loading) + + self.operation = self.aggregations.replaceEvents(forEvent: self.event.eventId, isEncrypted: self.event.isEncrypted, inRoom: self.roomId, from: self.nextBatch, limit: Pagination.count, success: { [weak self] (response) in + guard let sself = self else { + return + } + + sself.nextBatch = response.nextBatch + sself.operation = nil + + sself.process(editEvents: response.chunk) + + if response.nextBatch == nil { + // Append the original event when hitting the end of the edits history + if let originalEvent = response.originalEvent { + sself.process(editEvents: [originalEvent]) + } else { + print("[EditHistoryViewModel] loadMoreHistory: The homeserver did not return the original event") + } + } + + }, failure: { [weak self] error in + guard let sself = self else { + return + } + + sself.operation = nil + sself.update(viewState: .error(error)) + }) + } + + private func process(editEvents: [MXEvent]) { + self.messageFormattingQueue.async { + + let newMessages = editEvents.reversed() + .compactMap { (editEvent) -> EditHistoryMessage? in + return self.process(editEvent: editEvent) + } + + let allDataLoaded = self.nextBatch == nil + let addedCount: Int + + if newMessages.count > 0 { + self.messages.append(contentsOf: newMessages) + addedCount = newMessages.count + } else { + addedCount = 0 + } + + let editHistorySections = self.editHistorySections(from: self.messages) + + DispatchQueue.main.async { + self.update(viewState: .loaded(sections: editHistorySections, addedCount: addedCount, allDataLoaded: allDataLoaded)) + } + } + } + + private func editHistorySections(from editHistoryMessages: [EditHistoryMessage]) -> [EditHistorySection] { + + // Group edit messages by day + + let initial: [Date: [EditHistoryMessage]] = [:] + let dateComponents: Set = [.day, .month, .year] + let calendar = Calendar.current + + let messagesGroupedByDay = editHistoryMessages.reduce(into: initial) { messagesByDay, message in + let components = calendar.dateComponents(dateComponents, from: message.date) + if let date = calendar.date(from: components) { + var messages = messagesByDay[date] ?? [] + messages.append(message) + messagesByDay[date] = messages + } + } + + // Create edit sections + + var sections: [EditHistorySection] = [] + + for (date, messages) in messagesGroupedByDay { + // Sort messages descending (most recent first) + let sortedMessages = messages.sorted { $0.date.compare($1.date) == .orderedDescending } + let section = EditHistorySection(date: date, messages: sortedMessages) + sections.append(section) + } + + // Sort sections descending (most recent first) + let sortedSections = sections.sorted { $0.date.compare($1.date) == .orderedDescending } + + return sortedSections + } + + private func process(editEvent: MXEvent) -> EditHistoryMessage? { + // Create a temporary MXEvent that represents this edition + guard let editedEvent = self.event.editedEvent(fromReplacementEvent: editEvent) else { + print("[EditHistoryViewModel] processEditEvent: Cannot build edited event: \(editEvent.eventId ?? "")") + return nil + } + + if editedEvent.isEncrypted && editedEvent.clear == nil { + if self.session.decryptEvent(editedEvent, inTimeline: nil) == false { + print("[EditHistoryViewModel] processEditEvent: Fail to decrypt event: \(editedEvent.eventId ?? "")") + } + } + + let formatterError = UnsafeMutablePointer.allocate(capacity: 1) + guard let message = self.formatter.attributedString(from: editedEvent, with: nil, error: formatterError) else { + print("[EditHistoryViewModel] processEditEvent: cannot format(error: \(formatterError)) edited event: \(editedEvent.eventId ?? "")") + return nil + } + + let date = Date(timeIntervalSince1970: TimeInterval(editEvent.originServerTs) / 1000) + + return EditHistoryMessage(date: date, message: message) + } + + private func update(viewState: EditHistoryViewState) { + self.viewState = viewState + self.viewDelegate?.editHistoryViewModel(self, didUpdateViewState: viewState) + } +} diff --git a/Riot/Modules/Room/EditHistory/EditHistoryViewModelType.swift b/Riot/Modules/Room/EditHistory/EditHistoryViewModelType.swift new file mode 100644 index 000000000..4ed9ff707 --- /dev/null +++ b/Riot/Modules/Room/EditHistory/EditHistoryViewModelType.swift @@ -0,0 +1,36 @@ +// File created from ScreenTemplate +// $ createScreen.sh Room/EditHistory EditHistory +/* + Copyright 2019 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 EditHistoryViewModelViewDelegate: class { + func editHistoryViewModel(_ viewModel: EditHistoryViewModelType, didUpdateViewState viewSate: EditHistoryViewState) +} + +protocol EditHistoryViewModelCoordinatorDelegate: class { + func editHistoryViewModelDidClose(_ viewModel: EditHistoryViewModelType) +} + +/// Protocol describing the view model used by `EditHistoryViewController` +protocol EditHistoryViewModelType { + + var viewDelegate: EditHistoryViewModelViewDelegate? { get set } + var coordinatorDelegate: EditHistoryViewModelCoordinatorDelegate? { get set } + + func process(viewAction: EditHistoryViewAction) +} diff --git a/Riot/Modules/Room/EditHistory/EditHistoryViewState.swift b/Riot/Modules/Room/EditHistory/EditHistoryViewState.swift new file mode 100644 index 000000000..bb48192ee --- /dev/null +++ b/Riot/Modules/Room/EditHistory/EditHistoryViewState.swift @@ -0,0 +1,26 @@ +// File created from ScreenTemplate +// $ createScreen.sh Room/EditHistory EditHistory +/* + Copyright 2019 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 + +/// EditHistoryViewController view state +enum EditHistoryViewState { + case loading + case loaded(sections: [EditHistorySection], addedCount: Int, allDataLoaded: Bool) + case error(Error) +} diff --git a/Riot/Modules/Room/RoomViewController.m b/Riot/Modules/Room/RoomViewController.m index 49440b8da..5b574c56f 100644 --- a/Riot/Modules/Room/RoomViewController.m +++ b/Riot/Modules/Room/RoomViewController.m @@ -123,7 +123,8 @@ #import "Riot-Swift.h" -@interface RoomViewController () +@interface RoomViewController () { // The expanded header ExpandedRoomTitleView *expandedHeader; @@ -206,9 +207,6 @@ // Observe kThemeServiceDidChangeThemeNotification to handle user interface theme change. id kThemeServiceDidChangeThemeNotificationObserver; - // Tell whether the input text field is in send reply mode. If true typed message will be sent to highlighted event. - BOOL isInReplyMode; - // Listener for `m.room.tombstone` event type id tombstoneEventNotificationsListener; @@ -216,6 +214,16 @@ MXServerNotices *serverNotices; } +@property (nonatomic, weak) IBOutlet UIView *overlayContainerView; + + +@property (nonatomic, strong) RoomContextualMenuViewController *roomContextualMenuViewController; +@property (nonatomic, strong) RoomContextualMenuPresenter *roomContextualMenuPresenter; +@property (nonatomic, strong) MXKErrorAlertPresentation *errorPresenter; +@property (nonatomic, strong) NSString *textMessageBeforeEditing; +@property (nonatomic, strong) EditHistoryCoordinatorBridgePresenter *editHistoryPresenter; +@property (nonatomic, strong) MXKDocumentPickerPresenter *documentPickerPresenter; + @end @implementation RoomViewController @@ -283,6 +291,7 @@ // Listen to the event sent state changes [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(eventDidChangeSentState:) name:kMXEventDidChangeSentStateNotification object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(eventDidChangeIdentifier:) name:kMXEventDidChangeIdentifierNotification object:nil]; } - (void)viewDidLoad @@ -407,6 +416,9 @@ [self refreshRoomInputToolbar]; } + self.roomContextualMenuPresenter = [RoomContextualMenuPresenter new]; + self.errorPresenter = [MXKErrorAlertPresentation new]; + // Observe user interface theme change. kThemeServiceDidChangeThemeNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kThemeServiceDidChangeThemeNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) { @@ -592,6 +604,9 @@ { [super viewDidDisappear:animated]; + // Hide contextual menu if needed + [self hideContextualMenuAnimated:NO]; + // Reset visible room id [AppDelegate theDelegate].visibleRoomId = nil; @@ -939,6 +954,8 @@ - (void)updateRoomInputToolbarViewClassIfNeeded { Class roomInputToolbarViewClass = RoomInputToolbarView.class; + + BOOL shouldDismissContextualMenu = NO; // Check the user has enough power to post message if (self.roomDataSource.roomState) @@ -953,10 +970,12 @@ if (isRoomObsolete || isResourceLimitExceeded) { roomInputToolbarViewClass = nil; + shouldDismissContextualMenu = YES; } else if (!canSend) { roomInputToolbarViewClass = DisabledRoomInputToolbarView.class; + shouldDismissContextualMenu = YES; } } @@ -964,6 +983,12 @@ if (self.isRoomPreview) { roomInputToolbarViewClass = nil; + shouldDismissContextualMenu = YES; + } + + if (shouldDismissContextualMenu) + { + [self hideContextualMenuAnimated:NO]; } // Change inputToolbarView class only if given class is different from current one @@ -981,7 +1006,7 @@ if ([self.inputToolbarView isKindOfClass:RoomInputToolbarView.class]) { - height = ((RoomInputToolbarView*)self.inputToolbarView).mainToolbarMinHeightConstraint.constant; + height = ((RoomInputToolbarView*)self.inputToolbarView).mainToolbarHeightConstraint.constant; } else if ([self.inputToolbarView isKindOfClass:DisabledRoomInputToolbarView.class]) { @@ -1023,7 +1048,8 @@ // Check if (roomAlias.length) { - [self.mainSession joinRoom:roomAlias success:^(MXRoom *room) { + // TODO: /join command does not support via parameters yet + [self.mainSession joinRoom:roomAlias viaServers:nil success:^(MXRoom *room) { // Show the room [[AppDelegate theDelegate] showRoom:room.roomId andEventId:nil withMatrixSession:self.mainSession]; @@ -1098,13 +1124,20 @@ - (void)sendTextMessage:(NSString*)msgTxt { - if (isInReplyMode && customizedRoomDataSource.selectedEventId) + if (self.inputToolBarSendMode == RoomInputToolbarViewSendModeReply && customizedRoomDataSource.selectedEventId) { [self.roomDataSource sendReplyToEventWithId:customizedRoomDataSource.selectedEventId withTextMessage:msgTxt success:nil failure:^(NSError *error) { // Just log the error. The message will be displayed in red in the room history NSLog(@"[MXKRoomViewController] sendTextMessage failed."); }]; } + else if (self.inputToolBarSendMode == RoomInputToolbarViewSendModeEdit && customizedRoomDataSource.selectedEventId) + { + [self.roomDataSource replaceTextMessageForEventWithId:customizedRoomDataSource.selectedEventId withTextMessage:msgTxt success:nil failure:^(NSError *error) { + // Just log the error. The message will be displayed in red + NSLog(@"[MXKRoomViewController] sendTextMessage failed."); + }]; + } else { // Let the datasource send it and manage the local echo @@ -1194,6 +1227,7 @@ missedDiscussionsBadgeLabel = nil; [[NSNotificationCenter defaultCenter] removeObserver:self name:kMXEventDidChangeSentStateNotification object:nil]; + [[NSNotificationCenter defaultCenter] removeObserver:self name:kMXEventDidChangeIdentifierNotification object:nil]; [super destroy]; } @@ -1440,17 +1474,27 @@ } } -- (void)enableReplyMode:(BOOL)enable +- (void)setInputToolBarSendMode:(RoomInputToolbarViewSendMode)sendMode { - isInReplyMode = enable; - if (self.inputToolbarView && [self.inputToolbarView isKindOfClass:[RoomInputToolbarView class]]) { RoomInputToolbarView *roomInputToolbarView = (RoomInputToolbarView*)self.inputToolbarView; - roomInputToolbarView.replyToEnabled = enable; + roomInputToolbarView.sendMode = sendMode; } } +- (RoomInputToolbarViewSendMode)inputToolBarSendMode +{ + RoomInputToolbarViewSendMode sendMode = RoomInputToolbarViewSendModeSend; + if (self.inputToolbarView && [self.inputToolbarView isKindOfClass:[RoomInputToolbarView class]]) + { + RoomInputToolbarView *roomInputToolbarView = (RoomInputToolbarView*)self.inputToolbarView; + sendMode = roomInputToolbarView.sendMode; + } + + return sendMode; +} + - (void)onSwipeGesture:(UISwipeGestureRecognizer*)swipeGestureRecognizer { UIView *view = swipeGestureRecognizer.view; @@ -1478,6 +1522,14 @@ [UIView setAnimationsEnabled:YES]; } +- (void)handleLongPressFromCell:(id)cell withTappedEvent:(MXEvent*)event +{ + if (event && !customizedRoomDataSource.selectedEventId) + { + [self showContextualMenuForEvent:event fromSingleTapGesture:NO cell:cell animated:YES]; + } +} + #pragma mark - Hide/Show expanded header - (void)showExpandedHeader:(BOOL)isVisible @@ -1545,6 +1597,9 @@ mainNavigationController.navigationBar.translucent = isVisible; self.navigationController.navigationBar.translucent = isVisible; + // Hide contextual menu if needed + [self hideContextualMenuAnimated:YES]; + [UIView animateWithDuration:0.3 delay:0 options:UIViewAnimationOptionBeginFromCurrentState | UIViewAnimationOptionCurveEaseIn animations:^{ @@ -1976,6 +2031,15 @@ // Handle here user actions on bubbles for Vector app if (customizedRoomDataSource) { + id bubbleData; + + if ([cell isKindOfClass:[MXKRoomBubbleTableViewCell class]]) + { + MXKRoomBubbleTableViewCell *roomBubbleTableViewCell = (MXKRoomBubbleTableViewCell*)cell; + bubbleData = roomBubbleTableViewCell.bubbleData; + } + + if ([actionIdentifier isEqualToString:kMXKRoomBubbleCellTapOnAvatarView]) { selectedRoomMember = [self.roomDataSource.roomState.members memberWithUserId:userInfo[kMXKRoomBubbleCellUserIdKey]]; @@ -2019,13 +2083,17 @@ } else { - // Highlight this event in displayed message - [self selectEventWithId:tappedEvent.eventId]; + // Show contextual menu on single tap if bubble is not collapsed + if (bubbleData.collapsed) + { + [self selectEventWithId:tappedEvent.eventId]; + } + else + { + [self showContextualMenuForEvent:tappedEvent fromSingleTapGesture:YES cell:cell animated:YES]; + } } } - - // Force table refresh - [self dataSource:self.roomDataSource didCellChange:nil]; } else if ([actionIdentifier isEqualToString:kMXKRoomBubbleCellTapOnOverlayContainer]) { @@ -2040,7 +2108,7 @@ if (selectedEvent) { - [self showEditButtonAlertMenuForEvent:selectedEvent inCell:cell level:0]; + [self showContextualMenuForEvent:selectedEvent fromSingleTapGesture:YES cell:cell animated:YES]; } } else if ([actionIdentifier isEqualToString:kMXKRoomBubbleCellTapOnAttachmentView]) @@ -2066,9 +2134,6 @@ // Highlight this event in displayed message [self selectEventWithId:((MXKRoomBubbleTableViewCell*)cell).bubbleData.attachment.eventId]; } - - // Force table refresh - [self dataSource:self.roomDataSource didCellChange:nil]; } else { @@ -2098,6 +2163,15 @@ [self.roomDataSource collapseRoomBubble:((MXKRoomBubbleTableViewCell*)cell).bubbleData collapsed:YES]; } + else if ([actionIdentifier isEqualToString:kMXKRoomBubbleCellLongPressOnEvent]) + { + MXEvent *tappedEvent = userInfo[kMXKRoomBubbleCellEventKey]; + + if (!bubbleData.collapsed) + { + [self handleLongPressFromCell:cell withTappedEvent:tappedEvent]; + } + } else { // Keep default implementation for other actions @@ -2111,8 +2185,8 @@ } } -// Display the edit menu on 2 pages/levels. -- (void)showEditButtonAlertMenuForEvent:(MXEvent*)selectedEvent inCell:(id)cell level:(NSUInteger)level; +// Display the additiontal event actions menu +- (void)showAdditionalActionsMenuForEvent:(MXEvent*)selectedEvent inCell:(id)cell animated:(BOOL)animated { MXKRoomBubbleTableViewCell *roomBubbleTableViewCell = (MXKRoomBubbleTableViewCell *)cell; MXKAttachment *attachment = roomBubbleTableViewCell.bubbleData.attachment; @@ -2126,42 +2200,39 @@ __weak __typeof(self) weakSelf = self; currentAlert = [UIAlertController alertControllerWithTitle:nil message:nil preferredStyle:UIAlertControllerStyleActionSheet]; - if (level == 0) + // Add actions for a failed event + if (selectedEvent.sentState == MXEventSentStateFailed) { - // Add actions for a failed event - if (selectedEvent.sentState == MXEventSentStateFailed) - { - [currentAlert addAction:[UIAlertAction actionWithTitle:NSLocalizedStringFromTable(@"room_event_action_resend", @"Vector", nil) - style:UIAlertActionStyleDefault - handler:^(UIAlertAction * action) { + [currentAlert addAction:[UIAlertAction actionWithTitle:NSLocalizedStringFromTable(@"room_event_action_resend", @"Vector", nil) + style:UIAlertActionStyleDefault + handler:^(UIAlertAction * action) { + + if (weakSelf) + { + typeof(self) self = weakSelf; - if (weakSelf) - { - typeof(self) self = weakSelf; - - [self cancelEventSelection]; - - // Let the datasource resend. It will manage local echo, etc. - [self.roomDataSource resendEventWithEventId:selectedEvent.eventId success:nil failure:nil]; - } + [self cancelEventSelection]; - }]]; - - [currentAlert addAction:[UIAlertAction actionWithTitle:NSLocalizedStringFromTable(@"room_event_action_delete", @"Vector", nil) - style:UIAlertActionStyleDefault - handler:^(UIAlertAction * action) { + // Let the datasource resend. It will manage local echo, etc. + [self.roomDataSource resendEventWithEventId:selectedEvent.eventId success:nil failure:nil]; + } + + }]]; + + [currentAlert addAction:[UIAlertAction actionWithTitle:NSLocalizedStringFromTable(@"room_event_action_delete", @"Vector", nil) + style:UIAlertActionStyleDefault + handler:^(UIAlertAction * action) { + + if (weakSelf) + { + typeof(self) self = weakSelf; - if (weakSelf) - { - typeof(self) self = weakSelf; - - [self cancelEventSelection]; - - [self.roomDataSource removeEventWithEventId:selectedEvent.eventId]; - } + [self cancelEventSelection]; - }]]; - } + [self.roomDataSource removeEventWithEventId:selectedEvent.eventId]; + } + + }]]; } // Add actions for text message @@ -2179,225 +2250,160 @@ selectedComponent = nil; } - if (level == 0) + + // Check status of the selected event + if (selectedEvent.sentState == MXEventSentStatePreparing || + selectedEvent.sentState == MXEventSentStateEncrypting || + selectedEvent.sentState == MXEventSentStateSending) { - // Check status of the selected event - if (selectedEvent.sentState == MXEventSentStatePreparing || - selectedEvent.sentState == MXEventSentStateEncrypting || - selectedEvent.sentState == MXEventSentStateSending) - { - [currentAlert addAction:[UIAlertAction actionWithTitle:NSLocalizedStringFromTable(@"room_event_action_cancel_send", @"Vector", nil) - style:UIAlertActionStyleDefault - handler:^(UIAlertAction * action) - { - if (weakSelf) - { - typeof(self) self = weakSelf; - - self->currentAlert = nil; - - // Cancel and remove the outgoing message - [self.roomDataSource.room cancelSendingOperation:selectedEvent.eventId]; - [self.roomDataSource removeEventWithEventId:selectedEvent.eventId]; - - [self cancelEventSelection]; - } - - }]]; - } - } - - if (level == 0) - { - [currentAlert addAction:[UIAlertAction actionWithTitle:NSLocalizedStringFromTable(@"room_event_action_copy", @"Vector", nil) + [currentAlert addAction:[UIAlertAction actionWithTitle:NSLocalizedStringFromTable(@"room_event_action_cancel_send", @"Vector", nil) style:UIAlertActionStyleDefault - handler:^(UIAlertAction * action) { - - if (weakSelf) - { - typeof(self) self = weakSelf; - - [self cancelEventSelection]; - - [[UIPasteboard generalPasteboard] setString:selectedComponent.textMessage]; - } - - }]]; + handler:^(UIAlertAction * action) + { + if (weakSelf) + { + typeof(self) self = weakSelf; + + self->currentAlert = nil; + + // Cancel and remove the outgoing message + [self.roomDataSource.room cancelSendingOperation:selectedEvent.eventId]; + [self.roomDataSource removeEventWithEventId:selectedEvent.eventId]; + + [self cancelEventSelection]; + } + + }]]; } - - if (level == 0) - { - [currentAlert addAction:[UIAlertAction actionWithTitle:NSLocalizedStringFromTable(@"room_event_action_quote", @"Vector", nil) - style:UIAlertActionStyleDefault - handler:^(UIAlertAction * action) { + + [currentAlert addAction:[UIAlertAction actionWithTitle:NSLocalizedStringFromTable(@"room_event_action_quote", @"Vector", nil) + style:UIAlertActionStyleDefault + handler:^(UIAlertAction * action) { + + if (weakSelf) + { + typeof(self) self = weakSelf; - if (weakSelf) + [self cancelEventSelection]; + + // Quote the message a la Markdown into the input toolbar composer + self.inputToolbarView.textMessage = [NSString stringWithFormat:@"%@\n>%@\n\n", self.inputToolbarView.textMessage, selectedComponent.textMessage]; + + // And display the keyboard + [self.inputToolbarView becomeFirstResponder]; + } + + }]]; + + [currentAlert addAction:[UIAlertAction actionWithTitle:NSLocalizedStringFromTable(@"room_event_action_share", @"Vector", nil) + style:UIAlertActionStyleDefault + handler:^(UIAlertAction * action) { + + if (weakSelf) + { + typeof(self) self = weakSelf; + + [self cancelEventSelection]; + + NSArray *activityItems = @[selectedComponent.textMessage]; + + UIActivityViewController *activityViewController = [[UIActivityViewController alloc] initWithActivityItems:activityItems applicationActivities:nil]; + + if (activityViewController) { - typeof(self) self = weakSelf; + activityViewController.modalTransitionStyle = UIModalTransitionStyleCoverVertical; + activityViewController.popoverPresentationController.sourceView = roomBubbleTableViewCell; + activityViewController.popoverPresentationController.sourceRect = roomBubbleTableViewCell.bounds; - [self cancelEventSelection]; - - // Quote the message a la Markdown into the input toolbar composer - self.inputToolbarView.textMessage = [NSString stringWithFormat:@"%@\n>%@\n\n", self.inputToolbarView.textMessage, selectedComponent.textMessage]; - - // And display the keyboard - [self.inputToolbarView becomeFirstResponder]; + [self presentViewController:activityViewController animated:YES completion:nil]; } - - }]]; - } - - if (level == 1) - { - [currentAlert addAction:[UIAlertAction actionWithTitle:NSLocalizedStringFromTable(@"room_event_action_share", @"Vector", nil) - style:UIAlertActionStyleDefault - handler:^(UIAlertAction * action) { - - if (weakSelf) - { - typeof(self) self = weakSelf; - - [self cancelEventSelection]; - - NSArray *activityItems = @[selectedComponent.textMessage]; - - UIActivityViewController *activityViewController = [[UIActivityViewController alloc] initWithActivityItems:activityItems applicationActivities:nil]; - - if (activityViewController) - { - activityViewController.modalTransitionStyle = UIModalTransitionStyleCoverVertical; - activityViewController.popoverPresentationController.sourceView = roomBubbleTableViewCell; - activityViewController.popoverPresentationController.sourceRect = roomBubbleTableViewCell.bounds; - - [self presentViewController:activityViewController animated:YES completion:nil]; - } - } - - }]]; - } + } + + }]]; } else // Add action for attachment { - if (level == 0) + if (attachment.type == MXKAttachmentTypeImage || attachment.type == MXKAttachmentTypeVideo) { - if (attachment.type == MXKAttachmentTypeImage || attachment.type == MXKAttachmentTypeVideo) + [currentAlert addAction:[UIAlertAction actionWithTitle:NSLocalizedStringFromTable(@"room_event_action_save", @"Vector", nil) + style:UIAlertActionStyleDefault + handler:^(UIAlertAction * action) { + + if (weakSelf) + { + typeof(self) self = weakSelf; + + [self cancelEventSelection]; + + [self startActivityIndicator]; + + [attachment save:^{ + + __strong __typeof(weakSelf)self = weakSelf; + [self stopActivityIndicator]; + + } failure:^(NSError *error) { + + __strong __typeof(weakSelf)self = weakSelf; + [self stopActivityIndicator]; + + //Alert user + [[AppDelegate theDelegate] showErrorAsAlert:error]; + + }]; + + // Start animation in case of download during attachment preparing + [roomBubbleTableViewCell startProgressUI]; + } + + }]]; + } + + // Check status of the selected event + if (selectedEvent.sentState == MXEventSentStatePreparing || + selectedEvent.sentState == MXEventSentStateEncrypting || + selectedEvent.sentState == MXEventSentStateUploading || + selectedEvent.sentState == MXEventSentStateSending) + { + // Upload id is stored in attachment url (nasty trick) + NSString *uploadId = roomBubbleTableViewCell.bubbleData.attachment.contentURL; + if ([MXMediaManager existingUploaderWithId:uploadId]) { - [currentAlert addAction:[UIAlertAction actionWithTitle:NSLocalizedStringFromTable(@"room_event_action_save", @"Vector", nil) + [currentAlert addAction:[UIAlertAction actionWithTitle:NSLocalizedStringFromTable(@"room_event_action_cancel_send", @"Vector", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { + // Get again the loader + MXMediaLoader *loader = [MXMediaManager existingUploaderWithId:uploadId]; + if (loader) + { + [loader cancel]; + } + // Hide the progress animation + roomBubbleTableViewCell.progressView.hidden = YES; + if (weakSelf) { typeof(self) self = weakSelf; + self->currentAlert = nil; + + // Remove the outgoing message and its related cached file. + [[NSFileManager defaultManager] removeItemAtPath:roomBubbleTableViewCell.bubbleData.attachment.cacheFilePath error:nil]; + [[NSFileManager defaultManager] removeItemAtPath:roomBubbleTableViewCell.bubbleData.attachment.thumbnailCachePath error:nil]; + + // Cancel and remove the outgoing message + [self.roomDataSource.room cancelSendingOperation:selectedEvent.eventId]; + [self.roomDataSource removeEventWithEventId:selectedEvent.eventId]; + [self cancelEventSelection]; - - [self startActivityIndicator]; - - [attachment save:^{ - - __strong __typeof(weakSelf)self = weakSelf; - [self stopActivityIndicator]; - - } failure:^(NSError *error) { - - __strong __typeof(weakSelf)self = weakSelf; - [self stopActivityIndicator]; - - //Alert user - [[AppDelegate theDelegate] showErrorAsAlert:error]; - - }]; - - // Start animation in case of download during attachment preparing - [roomBubbleTableViewCell startProgressUI]; } }]]; } - - if (attachment.type != MXKAttachmentTypeSticker) - { - [currentAlert addAction:[UIAlertAction actionWithTitle:NSLocalizedStringFromTable(@"room_event_action_copy", @"Vector", nil) - style:UIAlertActionStyleDefault - handler:^(UIAlertAction * action) { - - if (weakSelf) - { - typeof(self) self = weakSelf; - - [self cancelEventSelection]; - - [self startActivityIndicator]; - - [attachment copy:^{ - - __strong __typeof(weakSelf)self = weakSelf; - [self stopActivityIndicator]; - - } failure:^(NSError *error) { - - __strong __typeof(weakSelf)self = weakSelf; - [self stopActivityIndicator]; - - //Alert user - [[AppDelegate theDelegate] showErrorAsAlert:error]; - - }]; - - // Start animation in case of download during attachment preparing - [roomBubbleTableViewCell startProgressUI]; - } - - }]]; - } - - // Check status of the selected event - if (selectedEvent.sentState == MXEventSentStatePreparing || - selectedEvent.sentState == MXEventSentStateEncrypting || - selectedEvent.sentState == MXEventSentStateUploading || - selectedEvent.sentState == MXEventSentStateSending) - { - // Upload id is stored in attachment url (nasty trick) - NSString *uploadId = roomBubbleTableViewCell.bubbleData.attachment.contentURL; - if ([MXMediaManager existingUploaderWithId:uploadId]) - { - [currentAlert addAction:[UIAlertAction actionWithTitle:NSLocalizedStringFromTable(@"room_event_action_cancel_send", @"Vector", nil) - style:UIAlertActionStyleDefault - handler:^(UIAlertAction * action) { - - // Get again the loader - MXMediaLoader *loader = [MXMediaManager existingUploaderWithId:uploadId]; - if (loader) - { - [loader cancel]; - } - // Hide the progress animation - roomBubbleTableViewCell.progressView.hidden = YES; - - if (weakSelf) - { - typeof(self) self = weakSelf; - - self->currentAlert = nil; - - // Remove the outgoing message and its related cached file. - [[NSFileManager defaultManager] removeItemAtPath:roomBubbleTableViewCell.bubbleData.attachment.cacheFilePath error:nil]; - [[NSFileManager defaultManager] removeItemAtPath:roomBubbleTableViewCell.bubbleData.attachment.thumbnailCachePath error:nil]; - - // Cancel and remove the outgoing message - [self.roomDataSource.room cancelSendingOperation:selectedEvent.eventId]; - [self.roomDataSource removeEventWithEventId:selectedEvent.eventId]; - - [self cancelEventSelection]; - } - - }]]; - } - } } - - if (level == 1 && (attachment.type != MXKAttachmentTypeSticker)) + + if (attachment.type != MXKAttachmentTypeSticker) { [currentAlert addAction:[UIAlertAction actionWithTitle:NSLocalizedStringFromTable(@"room_event_action_share", @"Vector", nil) style:UIAlertActionStyleDefault @@ -2442,7 +2448,7 @@ if (selectedEvent.sentState == MXEventSentStateSent) { // Check whether download is in progress - if (level == 0 && selectedEvent.isMediaAttachment) + if (selectedEvent.isMediaAttachment) { NSString *downloadId = roomBubbleTableViewCell.bubbleData.attachment.downloadId; if ([MXMediaManager existingDownloaderWithIdentifier:downloadId]) @@ -2471,48 +2477,11 @@ } } - if (level == 0) + // Do not allow to redact the event that enabled encryption (m.room.encryption) + // because it breaks everything + if (selectedEvent.eventType != MXEventTypeRoomEncryption) { - // Do not allow to redact the event that enabled encryption (m.room.encryption) - // because it breaks everything - if (selectedEvent.eventType != MXEventTypeRoomEncryption) - { - [currentAlert addAction:[UIAlertAction actionWithTitle:NSLocalizedStringFromTable(@"room_event_action_redact", @"Vector", nil) - style:UIAlertActionStyleDefault - handler:^(UIAlertAction * action) { - - if (weakSelf) - { - typeof(self) self = weakSelf; - - [self cancelEventSelection]; - - [self startActivityIndicator]; - - [self.roomDataSource.room redactEvent:selectedEvent.eventId reason:nil success:^{ - - __strong __typeof(weakSelf)self = weakSelf; - [self stopActivityIndicator]; - - } failure:^(NSError *error) { - - __strong __typeof(weakSelf)self = weakSelf; - [self stopActivityIndicator]; - - NSLog(@"[RoomVC] Redact event (%@) failed", selectedEvent.eventId); - //Alert user - [[AppDelegate theDelegate] showErrorAsAlert:error]; - - }]; - } - - }]]; - } - } - - if (level == 1) - { - [currentAlert addAction:[UIAlertAction actionWithTitle:NSLocalizedStringFromTable(@"room_event_action_permalink", @"Vector", nil) + [currentAlert addAction:[UIAlertAction actionWithTitle:NSLocalizedStringFromTable(@"room_event_action_redact", @"Vector", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { @@ -2522,37 +2491,65 @@ [self cancelEventSelection]; - // Create a matrix.to permalink that is common to all matrix clients - NSString *permalink = [MXTools permalinkToEvent:selectedEvent.eventId inRoom:selectedEvent.roomId]; + [self startActivityIndicator]; - // Create a room matrix.to permalink - [[UIPasteboard generalPasteboard] setString:permalink]; - } - - }]]; - } - - if (level == 1) - { - [currentAlert addAction:[UIAlertAction actionWithTitle:NSLocalizedStringFromTable(@"room_event_action_view_source", @"Vector", nil) - style:UIAlertActionStyleDefault - handler:^(UIAlertAction * action) { - - if (weakSelf) - { - typeof(self) self = weakSelf; - - [self cancelEventSelection]; - - // Display event details - [self showEventDetails:selectedEvent]; + [self.roomDataSource.room redactEvent:selectedEvent.eventId reason:nil success:^{ + + __strong __typeof(weakSelf)self = weakSelf; + [self stopActivityIndicator]; + + } failure:^(NSError *error) { + + __strong __typeof(weakSelf)self = weakSelf; + [self stopActivityIndicator]; + + NSLog(@"[RoomVC] Redact event (%@) failed", selectedEvent.eventId); + //Alert user + [[AppDelegate theDelegate] showErrorAsAlert:error]; + + }]; } }]]; } + [currentAlert addAction:[UIAlertAction actionWithTitle:NSLocalizedStringFromTable(@"room_event_action_permalink", @"Vector", nil) + style:UIAlertActionStyleDefault + handler:^(UIAlertAction * action) { + + if (weakSelf) + { + typeof(self) self = weakSelf; + + [self cancelEventSelection]; + + // Create a matrix.to permalink that is common to all matrix clients + NSString *permalink = [MXTools permalinkToEvent:selectedEvent.eventId inRoom:selectedEvent.roomId]; + + // Create a room matrix.to permalink + [[UIPasteboard generalPasteboard] setString:permalink]; + } + + }]]; + + [currentAlert addAction:[UIAlertAction actionWithTitle:NSLocalizedStringFromTable(@"room_event_action_view_source", @"Vector", nil) + style:UIAlertActionStyleDefault + handler:^(UIAlertAction * action) { + + if (weakSelf) + { + typeof(self) self = weakSelf; + + [self cancelEventSelection]; + + // Display event details + [self showEventDetails:selectedEvent]; + } + + }]]; + // Add "View Decrypted Source" for e2ee event we can decrypt - if (level == 1 && selectedEvent.isEncrypted && selectedEvent.clearEvent) + if (selectedEvent.isEncrypted && selectedEvent.clearEvent) { [currentAlert addAction:[UIAlertAction actionWithTitle:NSLocalizedStringFromTable(@"room_event_action_view_decrypted_source", @"Vector", nil) style:UIAlertActionStyleDefault @@ -2571,117 +2568,115 @@ }]]; } - if (level == 1) - { - [currentAlert addAction:[UIAlertAction actionWithTitle:NSLocalizedStringFromTable(@"room_event_action_report", @"Vector", nil) - style:UIAlertActionStyleDefault - handler:^(UIAlertAction * action) { + + [currentAlert addAction:[UIAlertAction actionWithTitle:NSLocalizedStringFromTable(@"room_event_action_report", @"Vector", nil) + style:UIAlertActionStyleDefault + handler:^(UIAlertAction * action) { + + if (weakSelf) + { + typeof(self) self = weakSelf; - if (weakSelf) - { - typeof(self) self = weakSelf; - - [self cancelEventSelection]; - - // Prompt user to enter a description of the problem content. - self->currentAlert = [UIAlertController alertControllerWithTitle:NSLocalizedStringFromTable(@"room_event_action_report_prompt_reason", @"Vector", nil) message:nil preferredStyle:UIAlertControllerStyleAlert]; - - [self->currentAlert addTextFieldWithConfigurationHandler:^(UITextField *textField) { - textField.secureTextEntry = NO; - textField.placeholder = nil; - textField.keyboardType = UIKeyboardTypeDefault; - }]; - - [self->currentAlert addAction:[UIAlertAction actionWithTitle:[NSBundle mxk_localizedStringForKey:@"ok"] style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { - - if (weakSelf) - { - typeof(self) self = weakSelf; - NSString *text = [self->currentAlert textFields].firstObject.text; - self->currentAlert = nil; - - [self startActivityIndicator]; - - [self.roomDataSource.room reportEvent:selectedEvent.eventId score:-100 reason:text success:^{ - - __strong __typeof(weakSelf)self = weakSelf; - [self stopActivityIndicator]; - - // Prompt user to ignore content from this user - self->currentAlert = [UIAlertController alertControllerWithTitle:NSLocalizedStringFromTable(@"room_event_action_report_prompt_ignore_user", @"Vector", nil) message:nil preferredStyle:UIAlertControllerStyleAlert]; - - [self->currentAlert addAction:[UIAlertAction actionWithTitle:[NSBundle mxk_localizedStringForKey:@"yes"] style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { - - if (weakSelf) - { - typeof(self) self = weakSelf; - self->currentAlert = nil; - - [self startActivityIndicator]; - - // Add the user to the blacklist: ignored users - [self.mainSession ignoreUsers:@[selectedEvent.sender] success:^{ - - __strong __typeof(weakSelf)self = weakSelf; - [self stopActivityIndicator]; - - } failure:^(NSError *error) { - - __strong __typeof(weakSelf)self = weakSelf; - [self stopActivityIndicator]; - - NSLog(@"[RoomVC] Ignore user (%@) failed", selectedEvent.sender); - //Alert user - [[AppDelegate theDelegate] showErrorAsAlert:error]; - - }]; - } - - }]]; - - [self->currentAlert addAction:[UIAlertAction actionWithTitle:[NSBundle mxk_localizedStringForKey:@"no"] style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { - - if (weakSelf) - { - typeof(self) self = weakSelf; - self->currentAlert = nil; - } - - }]]; - - [self presentViewController:self->currentAlert animated:YES completion:nil]; - - } failure:^(NSError *error) { - - __strong __typeof(weakSelf)self = weakSelf; - [self stopActivityIndicator]; - - NSLog(@"[RoomVC] Report event (%@) failed", selectedEvent.eventId); - //Alert user - [[AppDelegate theDelegate] showErrorAsAlert:error]; - - }]; - } - - }]]; - - [self->currentAlert addAction:[UIAlertAction actionWithTitle:[NSBundle mxk_localizedStringForKey:@"cancel"] style:UIAlertActionStyleCancel handler:^(UIAlertAction * action) { - - if (weakSelf) - { - typeof(self) self = weakSelf; - self->currentAlert = nil; - } - - }]]; - - [self presentViewController:self->currentAlert animated:YES completion:nil]; - } + [self cancelEventSelection]; - }]]; - } - - if (level == 1 && self.roomDataSource.room.summary.isEncrypted) + // Prompt user to enter a description of the problem content. + self->currentAlert = [UIAlertController alertControllerWithTitle:NSLocalizedStringFromTable(@"room_event_action_report_prompt_reason", @"Vector", nil) message:nil preferredStyle:UIAlertControllerStyleAlert]; + + [self->currentAlert addTextFieldWithConfigurationHandler:^(UITextField *textField) { + textField.secureTextEntry = NO; + textField.placeholder = nil; + textField.keyboardType = UIKeyboardTypeDefault; + }]; + + [self->currentAlert addAction:[UIAlertAction actionWithTitle:[NSBundle mxk_localizedStringForKey:@"ok"] style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { + + if (weakSelf) + { + typeof(self) self = weakSelf; + NSString *text = [self->currentAlert textFields].firstObject.text; + self->currentAlert = nil; + + [self startActivityIndicator]; + + [self.roomDataSource.room reportEvent:selectedEvent.eventId score:-100 reason:text success:^{ + + __strong __typeof(weakSelf)self = weakSelf; + [self stopActivityIndicator]; + + // Prompt user to ignore content from this user + self->currentAlert = [UIAlertController alertControllerWithTitle:NSLocalizedStringFromTable(@"room_event_action_report_prompt_ignore_user", @"Vector", nil) message:nil preferredStyle:UIAlertControllerStyleAlert]; + + [self->currentAlert addAction:[UIAlertAction actionWithTitle:[NSBundle mxk_localizedStringForKey:@"yes"] style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { + + if (weakSelf) + { + typeof(self) self = weakSelf; + self->currentAlert = nil; + + [self startActivityIndicator]; + + // Add the user to the blacklist: ignored users + [self.mainSession ignoreUsers:@[selectedEvent.sender] success:^{ + + __strong __typeof(weakSelf)self = weakSelf; + [self stopActivityIndicator]; + + } failure:^(NSError *error) { + + __strong __typeof(weakSelf)self = weakSelf; + [self stopActivityIndicator]; + + NSLog(@"[RoomVC] Ignore user (%@) failed", selectedEvent.sender); + //Alert user + [[AppDelegate theDelegate] showErrorAsAlert:error]; + + }]; + } + + }]]; + + [self->currentAlert addAction:[UIAlertAction actionWithTitle:[NSBundle mxk_localizedStringForKey:@"no"] style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { + + if (weakSelf) + { + typeof(self) self = weakSelf; + self->currentAlert = nil; + } + + }]]; + + [self presentViewController:self->currentAlert animated:YES completion:nil]; + + } failure:^(NSError *error) { + + __strong __typeof(weakSelf)self = weakSelf; + [self stopActivityIndicator]; + + NSLog(@"[RoomVC] Report event (%@) failed", selectedEvent.eventId); + //Alert user + [[AppDelegate theDelegate] showErrorAsAlert:error]; + + }]; + } + + }]]; + + [self->currentAlert addAction:[UIAlertAction actionWithTitle:[NSBundle mxk_localizedStringForKey:@"cancel"] style:UIAlertActionStyleCancel handler:^(UIAlertAction * action) { + + if (weakSelf) + { + typeof(self) self = weakSelf; + self->currentAlert = nil; + } + + }]]; + + [self presentViewController:self->currentAlert animated:YES completion:nil]; + } + + }]]; + + if (self.roomDataSource.room.summary.isEncrypted) { [currentAlert addAction:[UIAlertAction actionWithTitle:NSLocalizedStringFromTable(@"room_event_action_view_encryption", @"Vector", nil) style:UIAlertActionStyleDefault @@ -2698,25 +2693,6 @@ }]]; } - - - if (level == 0) - { - [currentAlert addAction:[UIAlertAction actionWithTitle:NSLocalizedStringFromTable(@"room_event_action_more", @"Vector", nil) - style:UIAlertActionStyleDefault - handler:^(UIAlertAction * action) { - - if (weakSelf) - { - typeof(self) self = weakSelf; - self->currentAlert = nil; - - // Show the next level of options - [self showEditButtonAlertMenuForEvent:selectedEvent inCell:cell level:1]; - } - - }]]; - } } [currentAlert addAction:[UIAlertAction actionWithTitle:NSLocalizedStringFromTable(@"cancel", @"Vector", nil) @@ -2726,7 +2702,7 @@ if (weakSelf) { typeof(self) self = weakSelf; - [self cancelEventSelection]; + [self hideContextualMenuAnimated:YES]; } }]]; @@ -2734,10 +2710,24 @@ // Do not display empty action sheet if (currentAlert.actions.count > 1) { + NSArray *components = roomBubbleTableViewCell.bubbleData.bubbleComponents; + + NSInteger index = 0; + for (MXKRoomBubbleComponent *component in components) + { + if ([component.event.eventId isEqualToString:selectedEvent.eventId]) + { + break; + } + index++; + } + + CGRect sourceRect = [roomBubbleTableViewCell componentFrameInContentViewForIndex:index]; + [currentAlert mxk_setAccessibilityIdentifier:@"RoomVCEventMenuAlert"]; [currentAlert popoverPresentationController].sourceView = roomBubbleTableViewCell; - [currentAlert popoverPresentationController].sourceRect = roomBubbleTableViewCell.bounds; - [self presentViewController:currentAlert animated:YES completion:nil]; + [currentAlert popoverPresentationController].sourceRect = sourceRect; + [self presentViewController:currentAlert animated:animated completion:nil]; } else { @@ -2819,9 +2809,9 @@ NSString *fragment = [NSString stringWithFormat:@"/group/%@", [MXTools encodeURIComponent:absoluteURLString]]; [[AppDelegate theDelegate] handleUniversalLinkFragment:fragment]; } - else if ([absoluteURLString hasPrefix:kEventFormatterOnReRequestKeysLinkAction]) + else if ([absoluteURLString hasPrefix:EventFormatterOnReRequestKeysLinkAction]) { - NSArray *arguments = [absoluteURLString componentsSeparatedByString:kEventFormatterOnReRequestKeysLinkActionSeparator]; + NSArray *arguments = [absoluteURLString componentsSeparatedByString:EventFormatterLinkActionSeparator]; if (arguments.count > 1) { NSString *eventId = arguments[1]; @@ -2833,13 +2823,21 @@ } } } + else if ([absoluteURLString hasPrefix:EventFormatterEditedEventLinkAction]) + { + NSArray *arguments = [absoluteURLString componentsSeparatedByString:EventFormatterLinkActionSeparator]; + if (arguments.count > 1) + { + NSString *eventId = arguments[1]; + [self showEditHistoryForEventId:eventId animated:YES]; + } + shouldDoAction = NO; + } else if (url && urlItemInteractionValue) { // Fallback case for external links - - // TODO: Use UITextItemInteraction enum when minimum deployement target will be iOS 10 switch (urlItemInteractionValue.integerValue) { - case 0: //UITextItemInteractionInvokeDefaultAction + case UITextItemInteractionInvokeDefaultAction: { [[UIApplication sharedApplication] vc_open:url completionHandler:^(BOOL success) { if (!success) @@ -2850,10 +2848,13 @@ shouldDoAction = NO; } break; - case 1: //UITextItemInteractionPresentActions - // Long press on link, let MXKRoomBubbleTableViewCell UITextView present the default contextual menu. + case UITextItemInteractionPresentActions: + { + // Long press on link, present room contextual menu. + shouldDoAction = NO; + } break; - case 2: //UITextItemInteractionPreview + case UITextItemInteractionPreview: // Force touch on link, let MXKRoomBubbleTableViewCell UITextView use default peek and pop behavior. break; default: @@ -2871,16 +2872,23 @@ - (void)selectEventWithId:(NSString*)eventId { - BOOL shouldEnableReplyMode = [self.roomDataSource canReplyToEventWithId:eventId];; - - [self enableReplyMode:shouldEnableReplyMode]; + [self selectEventWithId:eventId inputToolBarSendMode:RoomInputToolbarViewSendModeSend showTimestamp:YES]; +} + +- (void)selectEventWithId:(NSString*)eventId inputToolBarSendMode:(RoomInputToolbarViewSendMode)inputToolBarSendMode showTimestamp:(BOOL)showTimestamp +{ + [self setInputToolBarSendMode:inputToolBarSendMode]; + customizedRoomDataSource.showBubbleDateTimeOnSelection = showTimestamp; customizedRoomDataSource.selectedEventId = eventId; + + // Force table refresh + [self dataSource:self.roomDataSource didCellChange:nil]; } - (void)cancelEventSelection { - [self enableReplyMode:NO]; + [self setInputToolBarSendMode:RoomInputToolbarViewSendModeSend]; if (currentAlert) { @@ -2888,8 +2896,11 @@ currentAlert = nil; } + customizedRoomDataSource.showBubbleDateTimeOnSelection = YES; customizedRoomDataSource.selectedEventId = nil; + [self restoreTextMessageBeforeEditing]; + // Force table refresh [self dataSource:self.roomDataSource didCellChange:nil]; } @@ -2900,6 +2911,45 @@ message:NSLocalizedStringFromTable(@"room_message_unable_open_link_error_message", @"Vector", nil)]; } +- (void)editEventContentWithId:(NSString*)eventId +{ + MXEvent *event = [self.roomDataSource eventWithEventId:eventId]; + + RoomInputToolbarView *roomInputToolbarView = [self inputToolbarViewAsRoomInputToolbarView]; + + if (roomInputToolbarView) + { + self.textMessageBeforeEditing = roomInputToolbarView.textMessage; + roomInputToolbarView.textMessage = [self.roomDataSource editableTextMessageForEvent:event]; + } + + [self selectEventWithId:eventId inputToolBarSendMode:RoomInputToolbarViewSendModeEdit showTimestamp:YES]; +} + +- (void)restoreTextMessageBeforeEditing +{ + RoomInputToolbarView *roomInputToolbarView = [self inputToolbarViewAsRoomInputToolbarView]; + + if (self.textMessageBeforeEditing) + { + roomInputToolbarView.textMessage = self.textMessageBeforeEditing; + } + + self.textMessageBeforeEditing = nil; +} + +- (RoomInputToolbarView*)inputToolbarViewAsRoomInputToolbarView +{ + RoomInputToolbarView *roomInputToolbarView; + + if (self.inputToolbarView && [self.inputToolbarView isKindOfClass:[RoomInputToolbarView class]]) + { + roomInputToolbarView = (RoomInputToolbarView*)self.inputToolbarView; + } + + return roomInputToolbarView; +} + #pragma mark - Segues - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender @@ -3307,6 +3357,17 @@ } } +- (void)roomInputToolbarViewDidTapFileUpload:(MXKRoomInputToolbarView *)toolbarView +{ + MXKDocumentPickerPresenter *documentPickerPresenter = [MXKDocumentPickerPresenter new]; + documentPickerPresenter.delegate = self; + + NSArray *allowedUTIs = @[MXKUTI.data]; + [documentPickerPresenter presentDocumentPickerWith:allowedUTIs from:self animated:YES completion:nil]; + + self.documentPickerPresenter = documentPickerPresenter; +} + #pragma mark - RoomParticipantsViewControllerDelegate - (void)roomParticipantsViewController:(RoomParticipantsViewController *)roomParticipantsViewController mention:(MXRoomMember*)member @@ -3637,7 +3698,7 @@ } // Note in case of simple link to a room the signUrl param is nil - [self joinRoomWithRoomIdOrAlias:roomIdOrAlias andSignUrl:roomPreviewData.emailInvitation.signUrl completion:^(BOOL succeed) { + [self joinRoomWithRoomIdOrAlias:roomIdOrAlias viaServers:roomPreviewData.viaServers andSignUrl:roomPreviewData.emailInvitation.signUrl completion:^(BOOL succeed) { if (succeed) { @@ -3999,11 +4060,43 @@ } else if (customizedRoomDataSource.roomState.isObsolete) { - NSString *replacementRoomId = customizedRoomDataSource.roomState.tombStoneContent.replacementRoomId; - NSString *roomLinkFragment = [NSString stringWithFormat:@"/room/%@", [MXTools encodeURIComponent:replacementRoomId]]; - + MXWeakify(self); [roomActivitiesView displayRoomReplacementWithRoomLinkTappedHandler:^{ - [[AppDelegate theDelegate] handleUniversalLinkFragment:roomLinkFragment]; + MXStrongifyAndReturnIfNil(self); + + MXEvent *stoneTombEvent = [self->customizedRoomDataSource.roomState stateEventsWithType:kMXEventTypeStringRoomTombStone].lastObject; + + NSString *replacementRoomId = self->customizedRoomDataSource.roomState.tombStoneContent.replacementRoomId; + if ([self.roomDataSource.mxSession roomWithRoomId:replacementRoomId]) + { + // Open the room if it is already joined + [[AppDelegate theDelegate] showRoom:replacementRoomId andEventId:nil withMatrixSession:self.roomDataSource.mxSession]; + } + else + { + // Else auto join it via the server that sent the event + NSLog(@"[RoomVC] Auto join an upgraded room: %@ -> %@. Sender: %@", self->customizedRoomDataSource.roomState.roomId, + replacementRoomId, stoneTombEvent.sender); + + NSString *viaSenderServer = [MXTools serverNameInMatrixIdentifier:stoneTombEvent.sender]; + + if (viaSenderServer) + { + [self startActivityIndicator]; + [self.roomDataSource.mxSession joinRoom:replacementRoomId viaServers:@[viaSenderServer] success:^(MXRoom *room) { + [self stopActivityIndicator]; + + [[AppDelegate theDelegate] showRoom:replacementRoomId andEventId:nil withMatrixSession:self.roomDataSource.mxSession]; + + } failure:^(NSError *error) { + [self stopActivityIndicator]; + + NSLog(@"[RoomVC] Failed to join an upgraded room. Error: %@", + error); + [[AppDelegate theDelegate] showErrorAsAlert:error]; + }]; + } + } }]; } else if (customizedRoomDataSource.roomState.isOngoingConferenceCall) @@ -4488,6 +4581,18 @@ } } +- (void)eventDidChangeIdentifier:(NSNotification *)notif +{ + MXEvent *event = notif.object; + NSString *previousId = notif.userInfo[kMXEventIdentifierKey]; + + if ([customizedRoomDataSource.selectedEventId isEqualToString:previousId]) + { + NSLog(@"[RoomVC] eventDidChangeIdentifier: Update selectedEventId"); + customizedRoomDataSource.selectedEventId = event.eventId; + } +} + - (void)resendAllUnsentMessages { @@ -4969,5 +5074,332 @@ } } +#pragma mark - Contextual Menu + +- (NSArray*)contextualMenuItemsForEvent:(MXEvent*)event andCell:(id)cell +{ + NSString *eventId = event.eventId; + MXKRoomBubbleTableViewCell *roomBubbleTableViewCell = (MXKRoomBubbleTableViewCell *)cell; + MXKAttachment *attachment = roomBubbleTableViewCell.bubbleData.attachment; + + MXWeakify(self); + + // Copy action + + RoomContextualMenuItem *copyMenuItem = [[RoomContextualMenuItem alloc] initWithMenuAction:RoomContextualMenuActionCopy]; + copyMenuItem.isEnabled = !attachment || attachment.type != MXKAttachmentTypeSticker; + copyMenuItem.action = ^{ + MXStrongifyAndReturnIfNil(self); + + if (!attachment) + { + NSArray *components = roomBubbleTableViewCell.bubbleData.bubbleComponents; + MXKRoomBubbleComponent *selectedComponent; + for (selectedComponent in components) + { + if ([selectedComponent.event.eventId isEqualToString:event.eventId]) + { + break; + } + selectedComponent = nil; + } + NSString *textMessage = selectedComponent.textMessage; + + [UIPasteboard generalPasteboard].string = textMessage; + + [self hideContextualMenuAnimated:YES]; + } + else if (attachment.type != MXKAttachmentTypeSticker) + { + [self hideContextualMenuAnimated:YES completion:^{ + [self startActivityIndicator]; + + [attachment copy:^{ + + [self stopActivityIndicator]; + + } failure:^(NSError *error) { + + [self stopActivityIndicator]; + + //Alert user + [[AppDelegate theDelegate] showErrorAsAlert:error]; + }]; + + // Start animation in case of download during attachment preparing + [roomBubbleTableViewCell startProgressUI]; + }]; + } + }; + + // Reply action + + RoomContextualMenuItem *replyMenuItem = [[RoomContextualMenuItem alloc] initWithMenuAction:RoomContextualMenuActionReply]; + replyMenuItem.isEnabled = [self.roomDataSource canReplyToEventWithId:eventId]; + replyMenuItem.action = ^{ + MXStrongifyAndReturnIfNil(self); + + [self hideContextualMenuAnimated:YES cancelEventSelection:NO completion:nil]; + [self selectEventWithId:eventId inputToolBarSendMode:RoomInputToolbarViewSendModeReply showTimestamp:NO]; + + // And display the keyboard + [self.inputToolbarView becomeFirstResponder]; + }; + + // Edit action + + RoomContextualMenuItem *editMenuItem = [[RoomContextualMenuItem alloc] initWithMenuAction:RoomContextualMenuActionEdit]; + editMenuItem.action = ^{ + MXStrongifyAndReturnIfNil(self); + [self hideContextualMenuAnimated:YES cancelEventSelection:NO completion:nil]; + [self editEventContentWithId:eventId]; + + // And display the keyboard + [self.inputToolbarView becomeFirstResponder]; + }; + + editMenuItem.isEnabled = [self.roomDataSource canEditEventWithId:eventId]; + + // More action + + RoomContextualMenuItem *moreMenuItem = [[RoomContextualMenuItem alloc] initWithMenuAction:RoomContextualMenuActionMore]; + moreMenuItem.action = ^{ + MXStrongifyAndReturnIfNil(self); + [self hideContextualMenuAnimated:YES completion:nil]; + [self showAdditionalActionsMenuForEvent:event inCell:cell animated:YES]; + }; + + // Actions list + + NSArray *actionItems = @[ + copyMenuItem, + replyMenuItem, + editMenuItem, + moreMenuItem + ]; + + return actionItems; +} + +- (void)showContextualMenuForEvent:(MXEvent*)event fromSingleTapGesture:(BOOL)usedSingleTapGesture cell:(id)cell animated:(BOOL)animated +{ + if (self.roomContextualMenuPresenter.isPresenting) + { + return; + } + + NSString *selectedEventId = event.eventId; + + NSArray* contextualMenuItems = [self contextualMenuItemsForEvent:event andCell:cell]; + ReactionsMenuViewModel *reactionsMenuViewModel; + CGRect bubbleComponentFrameInOverlayView = CGRectNull; + + if ([cell isKindOfClass:MXKRoomBubbleTableViewCell.class] && [self.roomDataSource canReactToEventWithId:event.eventId]) + { + MXKRoomBubbleTableViewCell *roomBubbleTableViewCell = (MXKRoomBubbleTableViewCell*)cell; + MXKRoomBubbleCellData *bubbleCellData = roomBubbleTableViewCell.bubbleData; + NSArray *bubbleComponents = bubbleCellData.bubbleComponents; + + NSInteger foundComponentIndex = [bubbleComponents indexOfObjectPassingTest:^BOOL(MXKRoomBubbleComponent * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { + if (obj.event.eventId == selectedEventId) + { + *stop = YES; + return YES; + } + return NO; + }]; + + CGRect bubbleComponentFrame; + + if (bubbleComponents.count > 0) + { + NSInteger selectedComponentIndex = foundComponentIndex != NSNotFound ? foundComponentIndex : 0; + bubbleComponentFrame = [roomBubbleTableViewCell surroundingFrameInTableViewForComponentIndex:selectedComponentIndex]; + } + else + { + bubbleComponentFrame = roomBubbleTableViewCell.frame; + } + + bubbleComponentFrameInOverlayView = [self.bubblesTableView convertRect:bubbleComponentFrame toView:self.overlayContainerView]; + + NSString *roomId = self.roomDataSource.roomId; + MXAggregations *aggregations = self.mainSession.aggregations; + MXAggregatedReactions *aggregatedReactions = [aggregations aggregatedReactionsOnEvent:selectedEventId inRoom:roomId]; + + reactionsMenuViewModel = [[ReactionsMenuViewModel alloc] initWithAggregatedReactions:aggregatedReactions eventId:selectedEventId]; + reactionsMenuViewModel.coordinatorDelegate = self; + } + + if (!self.roomContextualMenuViewController) + { + self.roomContextualMenuViewController = [RoomContextualMenuViewController instantiate]; + self.roomContextualMenuViewController.delegate = self; + } + + [self.roomContextualMenuViewController updateWithContextualMenuItems:contextualMenuItems reactionsMenuViewModel:reactionsMenuViewModel]; + + [self enableOverlayContainerUserInteractions:YES]; + + [self.roomContextualMenuPresenter presentWithRoomContextualMenuViewController:self.roomContextualMenuViewController + from:self + on:self.overlayContainerView + contentToReactFrame:bubbleComponentFrameInOverlayView + fromSingleTapGesture:usedSingleTapGesture + animated:animated + completion:^{ + }]; + + [self selectEventWithId:selectedEventId]; +} + +- (void)hideContextualMenuAnimated:(BOOL)animated +{ + [self hideContextualMenuAnimated:animated completion:nil]; +} + +- (void)hideContextualMenuAnimated:(BOOL)animated completion:(void(^)(void))completion +{ + [self hideContextualMenuAnimated:animated cancelEventSelection:YES completion:completion]; +} + +- (void)hideContextualMenuAnimated:(BOOL)animated cancelEventSelection:(BOOL)cancelEventSelection completion:(void(^)(void))completion +{ + if (!self.roomContextualMenuPresenter.isPresenting) + { + return; + } + + if (cancelEventSelection) + { + [self cancelEventSelection]; + } + + [self.roomContextualMenuPresenter hideContextualMenuWithAnimated:animated completion:^{ + [self enableOverlayContainerUserInteractions:NO]; + + if (completion) + { + completion(); + } + }]; +} + +- (void)enableOverlayContainerUserInteractions:(BOOL)enableOverlayContainerUserInteractions +{ + self.inputToolbarView.editable = !enableOverlayContainerUserInteractions; + self.bubblesTableView.scrollsToTop = !enableOverlayContainerUserInteractions; + self.overlayContainerView.userInteractionEnabled = enableOverlayContainerUserInteractions; +} + +#pragma mark - RoomContextualMenuViewControllerDelegate + +- (void)roomContextualMenuViewControllerDidTapBackgroundOverlay:(RoomContextualMenuViewController *)viewController +{ + [self hideContextualMenuAnimated:YES]; +} + +#pragma mark - ReactionsMenuViewModelCoordinatorDelegate + +- (void)reactionsMenuViewModel:(ReactionsMenuViewModel *)viewModel didAddReaction:(NSString *)reaction forEventId:(NSString *)eventId +{ + MXWeakify(self); + + [self hideContextualMenuAnimated:YES completion:^{ + + [self.roomDataSource addReaction:reaction forEventId:eventId success:^{ + + } failure:^(NSError *error) { + MXStrongifyAndReturnIfNil(self); + + [self.errorPresenter presentErrorFromViewController:self forError:error animated:YES handler:nil]; + }]; + }]; +} + +- (void)reactionsMenuViewModel:(ReactionsMenuViewModel *)viewModel didRemoveReaction:(NSString *)reaction forEventId:(NSString *)eventId +{ + MXWeakify(self); + + [self hideContextualMenuAnimated:YES completion:^{ + + [self.roomDataSource removeReaction:reaction forEventId:eventId success:^{ + + } failure:^(NSError *error) { + MXStrongifyAndReturnIfNil(self); + + [self.errorPresenter presentErrorFromViewController:self forError:error animated:YES handler:nil]; + }]; + + }]; +} + +#pragma mark - + +- (void)showEditHistoryForEventId:(NSString*)eventId animated:(BOOL)animated +{ + MXEvent *event = [self.roomDataSource eventWithEventId:eventId]; + EditHistoryCoordinatorBridgePresenter *presenter = [[EditHistoryCoordinatorBridgePresenter alloc] initWithSession:self.roomDataSource.mxSession event:event]; + + presenter.delegate = self; + [presenter presentFrom:self animated:animated]; + + self.editHistoryPresenter = presenter; +} + +#pragma mark - EditHistoryCoordinatorBridgePresenterDelegate + +- (void)editHistoryCoordinatorBridgePresenterDelegateDidComplete:(EditHistoryCoordinatorBridgePresenter *)coordinatorBridgePresenter +{ + [coordinatorBridgePresenter dismissWithAnimated:YES completion:nil]; + self.editHistoryPresenter = nil; +} + +#pragma mark - DocumentPickerPresenterDelegate + +- (void)documentPickerPresenterWasCancelled:(MXKDocumentPickerPresenter *)presenter +{ + self.documentPickerPresenter = nil; +} + +- (void)documentPickerPresenter:(MXKDocumentPickerPresenter *)presenter didPickDocumentsAt:(NSURL *)url +{ + self.documentPickerPresenter = nil; + + MXKUTI *fileUTI = [[MXKUTI alloc] initWithLocalFileURL:url]; + NSString *mimeType = fileUTI.mimeType; + + if (fileUTI.isImage) + { + NSData *imageData = [[NSData alloc] initWithContentsOfURL:url]; + + [self.roomDataSource sendImage:imageData mimeType:mimeType success:nil failure:^(NSError *error) { + // Nothing to do. The image is marked as unsent in the room history by the datasource + NSLog(@"[MXKRoomViewController] sendImage failed."); + }]; + } + else if (fileUTI.isVideo) + { + [(RoomDataSource*)self.roomDataSource sendVideo:url success:nil failure:^(NSError *error) { + // Nothing to do. The video is marked as unsent in the room history by the datasource + NSLog(@"[MXKRoomViewController] sendVideo failed."); + }]; + } + else if (fileUTI.isFile) + { + [self.roomDataSource sendFile:url mimeType:mimeType success:nil failure:^(NSError *error) { + // Nothing to do. The file is marked as unsent in the room history by the datasource + NSLog(@"[MXKRoomViewController] sendFile failed."); + }]; + } + else + { + NSLog(@"[MXKRoomViewController] File upload using MIME type %@ is not supported.", mimeType); + + [[AppDelegate theDelegate] showAlertWithTitle:NSLocalizedStringFromTable(@"file_upload_error_title", @"Vector", nil) + message:NSLocalizedStringFromTable(@"file_upload_error_unsupported_file_type_message", @"Vector", nil)]; + } +} + @end diff --git a/Riot/Modules/Room/RoomViewController.xib b/Riot/Modules/Room/RoomViewController.xib index 0a8a14425..6ea9ec67f 100644 --- a/Riot/Modules/Room/RoomViewController.xib +++ b/Riot/Modules/Room/RoomViewController.xib @@ -1,11 +1,11 @@ - + - + @@ -21,6 +21,7 @@ + @@ -130,7 +131,7 @@ - + @@ -146,10 +147,15 @@ + + + + + @@ -160,12 +166,15 @@ + + + @@ -174,7 +183,7 @@ - - + + diff --git a/Riot/Modules/Room/Views/BubbleCells/Encryption/RoomBubbleCellLayout.swift b/Riot/Modules/Room/Views/BubbleCells/Encryption/RoomBubbleCellLayout.swift new file mode 100644 index 000000000..dee83d985 --- /dev/null +++ b/Riot/Modules/Room/Views/BubbleCells/Encryption/RoomBubbleCellLayout.swift @@ -0,0 +1,48 @@ +/* + Copyright 2019 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 + +/// MXKRoomBubbleTableViewCell layout constants +@objcMembers +final class RoomBubbleCellLayout: NSObject { + + // Reactions + + static let reactionsViewTopMargin: CGFloat = 1.0 + static let reactionsViewLeftMargin: CGFloat = 55.0 + static let reactionsViewRightMargin: CGFloat = 15.0 + + // Read receipts + + static let readReceiptsViewTopMargin: CGFloat = 5.0 + static let readReceiptsViewRightMargin: CGFloat = 6.0 + static let readReceiptsViewHeight: CGFloat = 12.0 + static let readReceiptsViewWidth: CGFloat = 150.0 + + // Read marker + + static let readMarkerViewHeight: CGFloat = 2.0 + + // Timestamp + + static let timestampLabelHeight: CGFloat = 18.0 + static let timestampLabelWidth: CGFloat = 39.0 + + // Others + + static let encryptedContentLeftMargin: CGFloat = 15.0 +} diff --git a/Riot/Modules/Room/Views/BubbleCells/Encryption/RoomEncryptedDataBubbleCell.m b/Riot/Modules/Room/Views/BubbleCells/Encryption/RoomEncryptedDataBubbleCell.m index 77df69f6b..ab4c455e5 100644 --- a/Riot/Modules/Room/Views/BubbleCells/Encryption/RoomEncryptedDataBubbleCell.m +++ b/Riot/Modules/Room/Views/BubbleCells/Encryption/RoomEncryptedDataBubbleCell.m @@ -29,7 +29,8 @@ NSString *const kRoomEncryptedDataBubbleCellTapOnEncryptionIcon = @"kRoomEncrypt { encryptionIcon = @"e2e_unencrypted"; - if (event.isLocalEvent) + if (event.isLocalEvent + || event.contentHasBeenEdited) // Local echo for an edit is clear but uses a true event id, the one of the edited event { // Patch: Display the verified icon by default on pending outgoing messages in the encrypted rooms when the encryption is enabled MXRoom *room = [session roomWithRoomId:event.roomId]; diff --git a/Riot/Modules/Room/Views/BubbleCells/RoomIncomingAttachmentBubbleCell.m b/Riot/Modules/Room/Views/BubbleCells/RoomIncomingAttachmentBubbleCell.m index 00018fb4d..2f7e4d80a 100644 --- a/Riot/Modules/Room/Views/BubbleCells/RoomIncomingAttachmentBubbleCell.m +++ b/Riot/Modules/Room/Views/BubbleCells/RoomIncomingAttachmentBubbleCell.m @@ -39,4 +39,16 @@ [self updateUserNameColor]; } ++ (CGFloat)heightForCellData:(MXKCellData*)cellData withMaximumWidth:(CGFloat)maxWidth +{ + CGFloat rowHeight = [self attachmentBubbleCellHeightForCellData:cellData withMaximumWidth:maxWidth]; + + if (rowHeight <= 0) + { + rowHeight = [super heightForCellData:cellData withMaximumWidth:maxWidth]; + } + + return rowHeight; +} + @end diff --git a/Riot/Modules/Room/Views/BubbleCells/RoomIncomingAttachmentWithPaginationTitleBubbleCell.m b/Riot/Modules/Room/Views/BubbleCells/RoomIncomingAttachmentWithPaginationTitleBubbleCell.m index 519bff872..16a614a46 100644 --- a/Riot/Modules/Room/Views/BubbleCells/RoomIncomingAttachmentWithPaginationTitleBubbleCell.m +++ b/Riot/Modules/Room/Views/BubbleCells/RoomIncomingAttachmentWithPaginationTitleBubbleCell.m @@ -45,4 +45,16 @@ } } ++ (CGFloat)heightForCellData:(MXKCellData*)cellData withMaximumWidth:(CGFloat)maxWidth +{ + CGFloat rowHeight = [self attachmentBubbleCellHeightForCellData:cellData withMaximumWidth:maxWidth]; + + if (rowHeight <= 0) + { + rowHeight = [super heightForCellData:cellData withMaximumWidth:maxWidth]; + } + + return rowHeight; +} + @end diff --git a/Riot/Modules/Room/Views/BubbleCells/RoomIncomingAttachmentWithoutSenderInfoBubbleCell.m b/Riot/Modules/Room/Views/BubbleCells/RoomIncomingAttachmentWithoutSenderInfoBubbleCell.m index 712385152..5d63baf00 100644 --- a/Riot/Modules/Room/Views/BubbleCells/RoomIncomingAttachmentWithoutSenderInfoBubbleCell.m +++ b/Riot/Modules/Room/Views/BubbleCells/RoomIncomingAttachmentWithoutSenderInfoBubbleCell.m @@ -16,6 +16,7 @@ */ #import "RoomIncomingAttachmentWithoutSenderInfoBubbleCell.h" +#import "MXKRoomBubbleTableViewCell+Riot.h" #import "ThemeService.h" #import "Riot-Swift.h" @@ -29,4 +30,16 @@ self.messageTextView.tintColor = ThemeService.shared.theme.tintColor; } ++ (CGFloat)heightForCellData:(MXKCellData*)cellData withMaximumWidth:(CGFloat)maxWidth +{ + CGFloat rowHeight = [self attachmentBubbleCellHeightForCellData:cellData withMaximumWidth:maxWidth]; + + if (rowHeight <= 0) + { + rowHeight = [super heightForCellData:cellData withMaximumWidth:maxWidth]; + } + + return rowHeight; +} + @end diff --git a/Riot/Modules/Room/Views/BubbleCells/RoomOutgoingAttachmentBubbleCell.m b/Riot/Modules/Room/Views/BubbleCells/RoomOutgoingAttachmentBubbleCell.m index a50ffe6c9..e6417201b 100644 --- a/Riot/Modules/Room/Views/BubbleCells/RoomOutgoingAttachmentBubbleCell.m +++ b/Riot/Modules/Room/Views/BubbleCells/RoomOutgoingAttachmentBubbleCell.m @@ -62,4 +62,16 @@ } } ++ (CGFloat)heightForCellData:(MXKCellData*)cellData withMaximumWidth:(CGFloat)maxWidth +{ + CGFloat rowHeight = [self attachmentBubbleCellHeightForCellData:cellData withMaximumWidth:maxWidth]; + + if (rowHeight <= 0) + { + rowHeight = [super heightForCellData:cellData withMaximumWidth:maxWidth]; + } + + return rowHeight; +} + @end diff --git a/Riot/Modules/Room/Views/BubbleCells/RoomOutgoingAttachmentWithoutSenderInfoBubbleCell.m b/Riot/Modules/Room/Views/BubbleCells/RoomOutgoingAttachmentWithoutSenderInfoBubbleCell.m index b60dac677..30a50ae2d 100644 --- a/Riot/Modules/Room/Views/BubbleCells/RoomOutgoingAttachmentWithoutSenderInfoBubbleCell.m +++ b/Riot/Modules/Room/Views/BubbleCells/RoomOutgoingAttachmentWithoutSenderInfoBubbleCell.m @@ -19,6 +19,8 @@ #import "ThemeService.h" #import "Riot-Swift.h" +#import "RoomBubbleCellData.h" +#import "MXKRoomBubbleTableViewCell+Riot.h" @implementation RoomOutgoingAttachmentWithoutSenderInfoBubbleCell @@ -36,4 +38,16 @@ [RoomOutgoingAttachmentBubbleCell render:cellData inBubbleCell:self]; } ++ (CGFloat)heightForCellData:(MXKCellData*)cellData withMaximumWidth:(CGFloat)maxWidth +{ + CGFloat rowHeight = [self attachmentBubbleCellHeightForCellData:cellData withMaximumWidth:maxWidth]; + + if (rowHeight <= 0) + { + rowHeight = [super heightForCellData:cellData withMaximumWidth:maxWidth]; + } + + return rowHeight; +} + @end diff --git a/Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarView.h b/Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarView.h index 20c66fcaf..fdff609e1 100644 --- a/Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarView.h +++ b/Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarView.h @@ -18,6 +18,17 @@ #import "MediaPickerViewController.h" +/** + Destination of the message in the composer + */ +typedef enum : NSUInteger +{ + RoomInputToolbarViewSendModeSend, + RoomInputToolbarViewSendModeReply, + RoomInputToolbarViewSendModeEdit +} RoomInputToolbarViewSendMode; + + @protocol RoomInputToolbarViewDelegate /** @@ -27,6 +38,13 @@ */ - (void)roomInputToolbarViewPresentStickerPicker:(MXKRoomInputToolbarView*)toolbarView; +/** + Tells the delegate that the user wants to send external files. + + @param toolbarView the room input toolbar view + */ +- (void)roomInputToolbarViewDidTapFileUpload:(MXKRoomInputToolbarView*)toolbarView; + @end /** @@ -38,7 +56,7 @@ /** The delegate notified when inputs are ready. */ -@property (nonatomic) id delegate; +@property (nonatomic, weak) id delegate; @property (weak, nonatomic) IBOutlet UIView *mainToolbarView; @@ -70,9 +88,9 @@ @property (nonatomic) BOOL isEncryptionEnabled; /** - Tell whether the input text will be a reply to a message. + Destination of the message in the composer. */ -@property (nonatomic, getter=isReplyToEnabled) BOOL replyToEnabled; +@property (nonatomic) RoomInputToolbarViewSendMode sendMode; /** Tell whether a call is active. diff --git a/Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarView.m b/Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarView.m index 0b483bd5c..f09f0ba8f 100644 --- a/Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarView.m +++ b/Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarView.m @@ -69,6 +69,7 @@ [super awakeFromNib]; _supportCallOption = YES; + _sendMode = RoomInputToolbarViewSendModeSend; self.rightInputToolbarButton.hidden = YES; @@ -154,11 +155,33 @@ self.placeholder = placeholder; } -- (void)setReplyToEnabled:(BOOL)isReplyToEnabled +- (void)setSendMode:(RoomInputToolbarViewSendMode)sendMode { - _replyToEnabled = isReplyToEnabled; - + _sendMode = sendMode; + [self updatePlaceholder]; + [self updateToolbarButtonLabel]; +} + +- (void)updateToolbarButtonLabel +{ + NSString *title; + + switch (_sendMode) + { + case RoomInputToolbarViewSendModeReply: + title = NSLocalizedStringFromTable(@"room_action_reply", @"Vector", nil); + break; + case RoomInputToolbarViewSendModeEdit: + title = NSLocalizedStringFromTable(@"save", @"Vector", nil); + break; + default: + title = [NSBundle mxk_localizedStringForKey:@"send"]; + break; + } + + [self.rightInputToolbarButton setTitle:title forState:UIControlStateNormal]; + [self.rightInputToolbarButton setTitle:title forState:UIControlStateHighlighted]; } - (void)updatePlaceholder @@ -172,17 +195,44 @@ if (!shouldDisplayLargePlaceholder) { - placeholder = _replyToEnabled ? NSLocalizedStringFromTable(@"room_message_reply_to_short_placeholder", @"Vector", nil) : NSLocalizedStringFromTable(@"room_message_short_placeholder", @"Vector", nil); + switch (_sendMode) + { + case RoomInputToolbarViewSendModeReply: + placeholder = NSLocalizedStringFromTable(@"room_message_reply_to_short_placeholder", @"Vector", nil); + break; + + default: + placeholder = NSLocalizedStringFromTable(@"room_message_short_placeholder", @"Vector", nil); + break; + } } else { if (_isEncryptionEnabled) { - placeholder = _replyToEnabled ? NSLocalizedStringFromTable(@"encrypted_room_message_reply_to_placeholder", @"Vector", nil) : NSLocalizedStringFromTable(@"encrypted_room_message_placeholder", @"Vector", nil); + switch (_sendMode) + { + case RoomInputToolbarViewSendModeReply: + placeholder = NSLocalizedStringFromTable(@"encrypted_room_message_reply_to_placeholder", @"Vector", nil); + break; + + default: + placeholder = NSLocalizedStringFromTable(@"encrypted_room_message_placeholder", @"Vector", nil); + break; + } } else { - placeholder = _replyToEnabled ? NSLocalizedStringFromTable(@"room_message_reply_to_placeholder", @"Vector", nil) : NSLocalizedStringFromTable(@"room_message_placeholder", @"Vector", nil); + switch (_sendMode) + { + case RoomInputToolbarViewSendModeReply: + placeholder = NSLocalizedStringFromTable(@"room_message_reply_to_placeholder", @"Vector", nil); + break; + + default: + placeholder = NSLocalizedStringFromTable(@"room_message_placeholder", @"Vector", nil); + break; + } } } @@ -299,6 +349,19 @@ } }]]; + + [actionSheet addAction:[UIAlertAction actionWithTitle:NSLocalizedStringFromTable(@"room_action_send_file", @"Vector", nil) + style:UIAlertActionStyleDefault + handler:^(UIAlertAction * action) { + + if (weakSelf) + { + typeof(self) self = weakSelf; + self->actionSheet = nil; + + [self.delegate roomInputToolbarViewDidTapFileUpload:self]; + } + }]]; [actionSheet addAction:[UIAlertAction actionWithTitle:[NSBundle mxk_localizedStringForKey:@"cancel"] style:UIAlertActionStyleCancel diff --git a/Riot/Modules/Settings/SettingsViewController.m b/Riot/Modules/Settings/SettingsViewController.m index cbda06f84..b8e545a09 100644 --- a/Riot/Modules/Settings/SettingsViewController.m +++ b/Riot/Modules/Settings/SettingsViewController.m @@ -119,7 +119,7 @@ enum enum { LABS_USE_ROOM_MEMBERS_LAZY_LOADING_INDEX = 0, - LABS_USE_JITSI_WIDGET_INDEX = 0, + LABS_USE_JITSI_WIDGET_INDEX, LABS_CRYPTO_INDEX, LABS_COUNT }; @@ -2135,7 +2135,7 @@ SignOutAlertPresenterDelegate> [labelAndSwitchCell.mxkSwitch addTarget:self action:@selector(toggleJitsiForConference:) forControlEvents:UIControlEventTouchUpInside]; cell = labelAndSwitchCell; - } + } else if (row == LABS_CRYPTO_INDEX) { MXSession* session = [AppDelegate theDelegate].mxSessions[0]; @@ -2874,7 +2874,11 @@ SignOutAlertPresenterDelegate> if (accountManager.pushDeviceToken) { - [account setEnablePushKitNotifications:!account.isPushKitNotificationActive]; + [account enablePushKitNotifications:!account.isPushKitNotificationActive success:^{ + [self stopActivityIndicator]; + } failure:^(NSError *error) { + [self stopActivityIndicator]; + }]; } else { @@ -2887,7 +2891,11 @@ SignOutAlertPresenterDelegate> } else { - [account setEnablePushKitNotifications:YES]; + [account enablePushKitNotifications:YES success:^{ + [self stopActivityIndicator]; + } failure:^(NSError *error) { + [self stopActivityIndicator]; + }]; } }]; } diff --git a/Riot/SupportingFiles/Info.plist b/Riot/SupportingFiles/Info.plist index f2e6a7621..b69ed44c8 100644 --- a/Riot/SupportingFiles/Info.plist +++ b/Riot/SupportingFiles/Info.plist @@ -17,11 +17,11 @@ CFBundlePackageType APPL CFBundleShortVersionString - 0.8.6 + 0.9.0 CFBundleSignature ???? CFBundleVersion - 0.8.6 + 0.9.0 ITSAppUsesNonExemptEncryption ITSEncryptionExportComplianceCode diff --git a/Riot/SupportingFiles/Riot-Bridging-Header.h b/Riot/SupportingFiles/Riot-Bridging-Header.h index 7467821eb..68348718c 100644 --- a/Riot/SupportingFiles/Riot-Bridging-Header.h +++ b/Riot/SupportingFiles/Riot-Bridging-Header.h @@ -12,3 +12,4 @@ #import "RecentsDataSource.h" #import "AvatarGenerator.h" #import "EncryptionInfoView.h" +#import "EventFormatter.h" diff --git a/Riot/Utils/EventFormatter.h b/Riot/Utils/EventFormatter.h index d55c03257..4952f1441 100644 --- a/Riot/Utils/EventFormatter.h +++ b/Riot/Utils/EventFormatter.h @@ -19,18 +19,41 @@ /** Link string used in attributed strings to mark a keys re-request action. */ -FOUNDATION_EXPORT NSString *const kEventFormatterOnReRequestKeysLinkAction; +FOUNDATION_EXPORT NSString *const EventFormatterOnReRequestKeysLinkAction; /** Parameters separator in the link string. */ -FOUNDATION_EXPORT NSString *const kEventFormatterOnReRequestKeysLinkActionSeparator; +FOUNDATION_EXPORT NSString *const EventFormatterLinkActionSeparator; + +/** + Link string used in attributed strings to mark an edited event action. + */ +FOUNDATION_EXPORT NSString *const EventFormatterEditedEventLinkAction; /** `EventFormatter` class inherits from `MXKEventFormatter` to define Vector formatting */ @interface EventFormatter : MXKEventFormatter +/** + Add a "(edited)" mention to edited message. + Default is YES. + */ +@property (nonatomic) BOOL showEditionMention; + +/** + Text color used to display message edited mention. + Default is `textSecondaryColor`. + */ +@property (nonatomic) UIColor *editionMentionTextColor; + +/** + Text font used to display message edited mention. + Default is system font 12. + */ +@property (nonatomic) UIFont *editionMentionTextFont; + /** String attributes for event timestamp displayed in chat history. */ diff --git a/Riot/Utils/EventFormatter.m b/Riot/Utils/EventFormatter.m index 7926f11cf..3ba09e318 100644 --- a/Riot/Utils/EventFormatter.m +++ b/Riot/Utils/EventFormatter.m @@ -27,8 +27,11 @@ #pragma mark - Constants definitions -NSString *const kEventFormatterOnReRequestKeysLinkAction = @"kEventFormatterOnReRequestKeysLinkAction"; -NSString *const kEventFormatterOnReRequestKeysLinkActionSeparator = @"/"; +NSString *const EventFormatterOnReRequestKeysLinkAction = @"EventFormatterOnReRequestKeysLinkAction"; +NSString *const EventFormatterLinkActionSeparator = @"/"; +NSString *const EventFormatterEditedEventLinkAction = @"EventFormatterEditedEventLinkAction"; + +static NSString *const kEventFormatterTimeFormat = @"HH:mm"; @interface EventFormatter () { @@ -41,6 +44,14 @@ NSString *const kEventFormatterOnReRequestKeysLinkActionSeparator = @"/"; @implementation EventFormatter +- (void)initDateTimeFormatters +{ + [super initDateTimeFormatters]; + + timeFormatter.locale = [NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"]; + [timeFormatter setDateFormat:kEventFormatterTimeFormat]; +} + - (NSAttributedString *)attributedStringFromEvent:(MXEvent *)event withRoomState:(MXRoomState *)roomState error:(MXKEventFormatterError *)error { // Build strings for widget events @@ -149,8 +160,8 @@ NSString *const kEventFormatterOnReRequestKeysLinkActionSeparator = @"/"; NSMutableAttributedString *attributedStringWithRerequestMessage = [attributedString mutableCopy]; [attributedStringWithRerequestMessage appendAttributedString:[[NSAttributedString alloc] initWithString:@"\n"]]; - NSString *linkActionString = [NSString stringWithFormat:@"%@%@%@", kEventFormatterOnReRequestKeysLinkAction, - kEventFormatterOnReRequestKeysLinkActionSeparator, + NSString *linkActionString = [NSString stringWithFormat:@"%@%@%@", EventFormatterOnReRequestKeysLinkAction, + EventFormatterLinkActionSeparator, event.eventId]; [attributedStringWithRerequestMessage appendAttributedString: @@ -171,6 +182,26 @@ NSString *const kEventFormatterOnReRequestKeysLinkActionSeparator = @"/"; attributedString = attributedStringWithRerequestMessage; } } + else if (self.showEditionMention && event.contentHasBeenEdited) + { + NSMutableAttributedString *attributedStringWithEditMention = [attributedString mutableCopy]; + + NSString *linkActionString = [NSString stringWithFormat:@"%@%@%@", EventFormatterEditedEventLinkAction, + EventFormatterLinkActionSeparator, + event.eventId]; + + [attributedStringWithEditMention appendAttributedString: + [[NSAttributedString alloc] initWithString:[NSString stringWithFormat:@" %@", NSLocalizedStringFromTable(@"event_formatter_message_edited_mention", @"Vector", nil)] + attributes:@{ + NSLinkAttributeName: linkActionString, + // NOTE: Color is curretly overidden by UIText.tintColor as we use `NSLinkAttributeName`. + // If we use UITextView.linkTextAttributes to set link color we will also have the issue that color will be the same for all kind of links. + NSForegroundColorAttributeName: self.editionMentionTextColor, + NSFontAttributeName: self.editionMentionTextFont + }]]; + + attributedString = attributedStringWithEditMention; + } return attributedString; } @@ -224,6 +255,8 @@ NSString *const kEventFormatterOnReRequestKeysLinkActionSeparator = @"/"; self.encryptingTextColor = ThemeService.shared.theme.tintColor; self.sendingTextColor = ThemeService.shared.theme.textSecondaryColor; self.errorTextColor = ThemeService.shared.theme.warningColor; + self.showEditionMention = YES; + self.editionMentionTextColor = ThemeService.shared.theme.textSecondaryColor; self.defaultTextFont = [UIFont systemFontOfSize:15]; self.prefixTextFont = [UIFont boldSystemFontOfSize:15]; @@ -232,6 +265,7 @@ NSString *const kEventFormatterOnReRequestKeysLinkActionSeparator = @"/"; self.callNoticesTextFont = [UIFont italicSystemFontOfSize:15]; self.encryptedMessagesTextFont = [UIFont italicSystemFontOfSize:15]; self.emojiOnlyTextFont = [UIFont systemFontOfSize:48]; + self.editionMentionTextFont = [UIFont systemFontOfSize:12]; } return self; } diff --git a/RiotShareExtension/SupportingFiles/Info.plist b/RiotShareExtension/SupportingFiles/Info.plist index e71361f1c..981a68366 100644 --- a/RiotShareExtension/SupportingFiles/Info.plist +++ b/RiotShareExtension/SupportingFiles/Info.plist @@ -17,9 +17,9 @@ CFBundlePackageType XPC! CFBundleShortVersionString - 0.8.6 + 0.9.0 CFBundleVersion - 0.8.6 + 0.9.0 NSExtension NSExtensionAttributes @@ -28,6 +28,8 @@ NSExtensionActivationDictionaryVersion 2 + NSExtensionActivationSupportsFileWithMaxCount + 5 NSExtensionActivationSupportsImageWithMaxCount 5 NSExtensionActivationSupportsMovieWithMaxCount diff --git a/SiriIntents/Info.plist b/SiriIntents/Info.plist index dfd30fd66..b31f06aa7 100644 --- a/SiriIntents/Info.plist +++ b/SiriIntents/Info.plist @@ -17,9 +17,9 @@ CFBundlePackageType XPC! CFBundleShortVersionString - 0.8.6 + 0.9.0 CFBundleVersion - 0.8.6 + 0.9.0 NSExtension NSExtensionAttributes diff --git a/Tools/Logs/filterCryptoLogs.sh b/Tools/Logs/filterCryptoLogs.sh new file mode 100755 index 000000000..72363f783 --- /dev/null +++ b/Tools/Logs/filterCryptoLogs.sh @@ -0,0 +1,59 @@ +#!/bin/sh + +# Filter Riot logs to extract only logs related to end-to-end encryption. +# The output is colorised according to the cryto sub module. +# +# Usage: +# ./filterCryptoLogs.sh console.log + +FILES=$1 + +if [ ! -n "$FILES" ]; then + FILES="*" +fi + +grep -iE 'crypto|MXDevice|olm|error|MXKey|KeyRequest' $FILES \ + | grep -viE 'MXJSONModels|MXOlmSessionResult|MXRealmCryptoStore|NSCocoaErrorDomain|olm_keys_not_sent_error' \ + | awk '{ + # Errors in red (I failed to make a gsub case insensitive) + gsub(".*error.*", "\033[0;31m&\033[0m"); + gsub(".*Error.*", "\033[0;31m&\033[0m"); + gsub(".*ERROR.*", "\033[0;31m&\033[0m"); + + # Isolate each encryption of a message + gsub(".*\\[MXRoom] sendEventOfType\\(MXCrypto\\)\\: Encrypting event.*", + "\n\n\n\n++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n&"); + gsub(".*\\[MXRoom] sendEventOfType\\(MXCrypto\\)\\: Send event.*", + "&\n----------------------------------------------------------------------------------------------------------------\n\n\n\n"); + gsub(".*\\[MXRoom] sendEventOfType\\(MXCrypto\\)\\: Cannot encrypt.*", + "&\n----------------------------------------------------------------------------------------------------------------\n\n\n\n"); + + gsub("\\[MXCrypto\\]", "\033[0;32m&\033[0m"); + gsub("\\[MXOlmDevice\\]", "\033[0;33m&\033[0m"); + + gsub("\\[MXMegolmEncryption\\]", "\033[0;36m&\033[0m"); + + gsub("\\[MXOlmInboundGroupSession\\]", "\033[1;34m&\033[0m"); + gsub("\\[MXMegolmDecryption\\]", "\033[0;34m&\033[0m"); + + gsub("\\[MXOlmDecryption\\]", "\033[0;34m&\033[0m"); + + gsub("\\[MXDeviceList\\]", "\033[0;36m&\033[0m"); + gsub("\\[MXDeviceListOperationsPool\\]", "\033[1;36m&\033[0m"); + + gsub("\\[MXKey\\]", "\033[1;35m&\033[0m"); + gsub("\\[MXKeyBackup\\]", "\033[1;35m&\033[0m"); + gsub("\\[MXKeyBackupPassword\\]", "\033[1;35m&\033[0m"); + gsub("\\[MXMegolmExportEncryption\\]", "\033[1;35m&\033[0m"); + gsub("\\[MXKeyBackupPassword\\]", "\033[1;35m&\033[0m"); + + gsub("\\[MXOutgoingRoomKeyRequestManager\\]", "\033[1;35m&\033[0m"); + gsub("\\[MXIncomingRoomKeyRequestManager\\]", "\033[1;34m&\033[0m"); + + gsub("\\[MXDeviceVerificationTransaction\\]", "\033[0;36m&\033[0m"); + gsub("\\[MXKeyVerification\\]", "\033[0;36m&\033[0m"); + + gsub("\\[MXRealmCryptoStore\\]", "\033[0;37m&\033[0m"); + + print + }' diff --git a/Tools/Templates/buildable/ScreenTemplate/TemplateScreenViewController.swift b/Tools/Templates/buildable/ScreenTemplate/TemplateScreenViewController.swift index 4d1e3b3c6..cc23f78f3 100644 --- a/Tools/Templates/buildable/ScreenTemplate/TemplateScreenViewController.swift +++ b/Tools/Templates/buildable/ScreenTemplate/TemplateScreenViewController.swift @@ -77,10 +77,6 @@ final class TemplateScreenViewController: UIViewController { self.keyboardAvoider?.startAvoiding() } - - override func viewDidAppear(_ animated: Bool) { - super.viewDidAppear(animated) - } override func viewDidDisappear(_ animated: Bool) { super.viewDidDisappear(animated) @@ -88,10 +84,6 @@ final class TemplateScreenViewController: UIViewController { self.keyboardAvoider?.stopAvoiding() } - override func viewDidLayoutSubviews() { - super.viewDidLayoutSubviews() - } - override var preferredStatusBarStyle: UIStatusBarStyle { return self.theme.statusBarStyle }