diff --git a/.github/workflows/ci-ui-tests.yml b/.github/workflows/ci-ui-tests.yml index b13272947f..322323739e 100644 --- a/.github/workflows/ci-ui-tests.yml +++ b/.github/workflows/ci-ui-tests.yml @@ -20,7 +20,9 @@ jobs: cancel-in-progress: true steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 + with: + submodules: 'true' # Common cache # Note: GH actions do not support yaml anchor yet. We need to duplicate this for every job @@ -49,8 +51,6 @@ jobs: run: | bundle config path vendor/bundle bundle install --jobs 4 --retry 3 - - name: Use right MatrixSDK versions - run: bundle exec fastlane point_dependencies_to_related_branches # Main step - name: UI tests diff --git a/.github/workflows/release-alpha.yml b/.github/workflows/release-alpha.yml index e610628b45..aa90ef5bab 100644 --- a/.github/workflows/release-alpha.yml +++ b/.github/workflows/release-alpha.yml @@ -25,7 +25,9 @@ jobs: cancel-in-progress: true steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 + with: + submodules: 'true' # Common cache # Note: GH actions do not support yaml anchor yet. We need to duplicate this for every job @@ -57,8 +59,6 @@ jobs: run: | bundle config path vendor/bundle bundle install --jobs 4 --retry 3 - - name: Use right MatrixSDK versions - run: bundle exec fastlane point_dependencies_to_related_branches # Import alpha release private signing certificate - name: Import signing certificate diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000000..90f7f83cb4 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "matrix-ios-sdk"] + path = matrix-ios-sdk + url = git@github.com:matrix-org/matrix-ios-sdk.git diff --git a/CHANGES.md b/CHANGES.md index bf6fc003bd..907d69b54b 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,22 @@ +## Changes in 1.11.15 (2024-06-18) + +No significant changes. + + +## Changes in 1.11.14 (2024-06-17) + +🙌 Improvements + +- Room retention event implementation ([#7809](https://github.com/element-hq/element-ios/pull/7809)) + + +## Changes in 1.11.13 (2024-06-12) + +Others + +- Analytics | Add support for super properties and appPlatform ([#7801](https://github.com/element-hq/element-ios/issues/7801)) + + ## Changes in 1.11.12 (2024-05-30) 🐛 Bugfixes diff --git a/Gemfile.lock b/Gemfile.lock index 519e1f087e..bacf716b3e 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -11,7 +11,7 @@ GEM base64 nkf rexml - activesupport (7.1.3.2) + activesupport (7.1.3.4) base64 bigdecimal concurrent-ruby (~> 1.0, >= 1.0.2) @@ -29,24 +29,24 @@ GEM artifactory (3.0.17) atomos (0.1.3) aws-eventstream (1.3.0) - aws-partitions (1.899.0) - aws-sdk-core (3.191.4) + aws-partitions (1.941.0) + aws-sdk-core (3.197.0) aws-eventstream (~> 1, >= 1.3.0) aws-partitions (~> 1, >= 1.651.0) aws-sigv4 (~> 1.8) jmespath (~> 1, >= 1.6.1) - aws-sdk-kms (1.78.0) - aws-sdk-core (~> 3, >= 3.191.0) + aws-sdk-kms (1.83.0) + aws-sdk-core (~> 3, >= 3.197.0) aws-sigv4 (~> 1.1) - aws-sdk-s3 (1.146.0) - aws-sdk-core (~> 3, >= 3.191.0) + aws-sdk-s3 (1.152.0) + aws-sdk-core (~> 3, >= 3.197.0) aws-sdk-kms (~> 1) aws-sigv4 (~> 1.8) aws-sigv4 (1.8.0) aws-eventstream (~> 1, >= 1.0.2) babosa (1.0.4) base64 (0.2.0) - bigdecimal (3.1.7) + bigdecimal (3.1.8) claide (1.1.0) clamp (1.3.2) cocoapods (1.14.3) @@ -90,7 +90,7 @@ GEM colored2 (3.1.2) commander (4.6.0) highline (~> 2.0.0) - concurrent-ruby (1.2.3) + concurrent-ruby (1.3.3) connection_pool (2.4.1) declarative (0.0.20) digest-crc (0.6.5) @@ -131,15 +131,15 @@ GEM faraday-retry (1.0.3) faraday_middleware (1.2.0) faraday (~> 1.0) - fastimage (2.3.0) - fastlane (2.219.0) + fastimage (2.3.1) + fastlane (2.220.0) CFPropertyList (>= 2.3, < 4.0.0) addressable (>= 2.8, < 3.0.0) artifactory (~> 3.0) aws-sdk-s3 (~> 1.0) babosa (>= 1.0.3, < 2.0.0) bundler (>= 1.12.0, < 3.0.0) - colored + colored (~> 1.2) commander (~> 4.6) dotenv (>= 2.1.1, < 3.0.0) emoji_regex (>= 0.1, < 4.0) @@ -160,10 +160,10 @@ GEM mini_magick (>= 4.9.4, < 5.0.0) multipart-post (>= 2.0.0, < 3.0.0) naturally (~> 2.2) - optparse (>= 0.1.1) + optparse (>= 0.1.1, < 1.0.0) plist (>= 3.1.0, < 4.0.0) rubyzip (>= 2.0.0, < 3.0.0) - security (= 0.1.3) + security (= 0.1.5) simctl (~> 1.6.3) terminal-notifier (>= 2.0.0, < 3.0.0) terminal-table (~> 3) @@ -172,14 +172,14 @@ GEM word_wrap (~> 1.0.0) xcodeproj (>= 1.13.0, < 2.0.0) xcpretty (~> 0.3.0) - xcpretty-travis-formatter (>= 0.0.3) + xcpretty-travis-formatter (>= 0.0.3, < 2.0.0) fastlane-plugin-brew (0.1.1) - fastlane-plugin-sentry (1.20.0) + fastlane-plugin-sentry (1.23.0) os (~> 1.1, >= 1.1.4) fastlane-plugin-versioning (0.5.2) fastlane-plugin-xcodegen (1.1.0) fastlane-plugin-brew (~> 0.1.1) - ffi (1.16.3) + ffi (1.17.0) fourflusher (2.3.1) fuzzy_match (2.0.4) gh_inspector (1.1.3) @@ -220,22 +220,22 @@ GEM os (>= 0.9, < 2.0) signet (>= 0.16, < 2.a) highline (2.0.3) - http-cookie (1.0.5) + http-cookie (1.0.6) domain_name (~> 0.5) httpclient (2.8.3) - i18n (1.14.4) + i18n (1.14.5) concurrent-ruby (~> 1.0) jmespath (1.6.2) - json (2.7.1) + json (2.7.2) jwt (2.8.1) base64 mini_magick (4.12.0) mini_mime (1.1.5) - mini_portile2 (2.8.5) - minitest (5.22.3) + mini_portile2 (2.8.7) + minitest (5.23.1) molinillo (0.8.0) multi_json (1.15.0) - multipart-post (2.4.0) + multipart-post (2.4.1) mutex_m (0.2.0) nanaimo (0.3.0) nap (1.1.0) @@ -245,23 +245,24 @@ GEM nokogiri (1.15.6) mini_portile2 (~> 2.8.2) racc (~> 1.4) - optparse (0.4.0) + optparse (0.5.0) os (1.1.4) plist (3.7.1) public_suffix (4.0.7) - racc (1.7.3) - rake (13.1.0) + racc (1.8.0) + rake (13.2.1) representable (3.2.0) declarative (< 0.1.0) trailblazer-option (>= 0.1.1, < 0.2.0) uber (< 0.2.0) retriable (3.1.2) - rexml (3.2.6) + rexml (3.2.9) + strscan rouge (2.0.7) ruby-macho (2.5.1) ruby2_keywords (0.0.5) rubyzip (2.3.2) - security (0.1.3) + security (0.1.5) signet (0.19.0) addressable (~> 2.8) faraday (>= 0.17.5, < 3.a) @@ -276,6 +277,7 @@ GEM clamp (~> 1.3) nokogiri (>= 1.14.3) xcodeproj (~> 1.21) + strscan (3.1.0) terminal-notifier (2.0.0) terminal-table (3.0.2) unicode-display_width (>= 1.1.1, < 3) diff --git a/Podfile b/Podfile index c39f78fb11..d3c1db477e 100644 --- a/Podfile +++ b/Podfile @@ -9,44 +9,10 @@ inhibit_all_warnings! # Use frameworks to allow usage of pods written in Swift use_frameworks! -# Different flavours of pods to MatrixSDK. Can be one of: -# - a String indicating an official MatrixSDK released version number -# - `:local` (to use Development Pods) -# - `{ :branch => 'sdk branch name'}` to depend on specific branch of MatrixSDK repo -# - `{ :specHash => {sdk spec hash}` to depend on specific pod options (:git => …, :podspec => …) for MatrixSDK repo. Used by Fastfile during CI -# -# Warning: our internal tooling depends on the name of this variable name, so be sure not to change it -$matrixSDKVersion = '= 0.27.8' -# $matrixSDKVersion = :local -# $matrixSDKVersion = { :branch => 'develop'} -# $matrixSDKVersion = { :specHash => { git: 'https://git.io/fork123', branch: 'fix' } } - -######################################## - -case $matrixSDKVersion -when :local -$matrixSDKVersionSpec = { :path => '../matrix-ios-sdk/MatrixSDK.podspec' } -when Hash -spec_mode, sdk_spec = $matrixSDKVersion.first # extract first and only key/value pair; key is spec_mode, value is sdk_spec - - case spec_mode - when :branch - # :branch => sdk branch name - sdk_spec = { :git => 'https://github.com/matrix-org/matrix-ios-sdk.git', :branch => sdk_spec.to_s } unless sdk_spec.is_a?(Hash) - when :specHash - # :specHash => {sdk spec Hash} - sdk_spec = sdk_spec - end - -$matrixSDKVersionSpec = sdk_spec -when String # specific MatrixSDK released version -$matrixSDKVersionSpec = $matrixSDKVersion -end - # Method to import the MatrixSDK def import_MatrixSDK - pod 'MatrixSDK', $matrixSDKVersionSpec, :inhibit_warnings => false - pod 'MatrixSDK/JingleCallStack', $matrixSDKVersionSpec, :inhibit_warnings => false + pod 'MatrixSDK', :path => 'matrix-ios-sdk/MatrixSDK.podspec', :inhibit_warnings => false + pod 'MatrixSDK/JingleCallStack', :path => 'matrix-ios-sdk/MatrixSDK.podspec', :inhibit_warnings => false end ######################################## @@ -95,6 +61,7 @@ abstract_target 'TchapPods' do # Tools pod 'SwiftGen', '~> 6.3' pod 'SwiftLint', '~> 0.49.1' + pod 'SwiftFormat/CLI' target "Tchap" do import_MatrixSDK diff --git a/Podfile.lock b/Podfile.lock index 8566bd194e..3ea7cb1e53 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -39,20 +39,20 @@ PODS: - LoggerAPI (1.9.200): - Logging (~> 1.1) - Logging (1.4.0) - - MatrixSDK (0.27.7): - - MatrixSDK/Core (= 0.27.7) - - MatrixSDK/Core (0.27.7): + - MatrixSDK (0.27.10): + - MatrixSDK/Core (= 0.27.10) + - MatrixSDK/Core (0.27.10): - AFNetworking (~> 4.0.0) - GZIP (~> 1.3.0) - libbase58 (~> 0.1.4) - - MatrixSDKCrypto (= 0.3.13) + - MatrixSDKCrypto (= 0.4.2) - OLMKit (~> 3.2.5) - Realm (= 10.27.0) - SwiftyBeaver (= 1.9.5) - - MatrixSDK/JingleCallStack (0.27.7): + - MatrixSDK/JingleCallStack (0.27.10): - JitsiMeetSDKLite (= 8.1.2-lite) - MatrixSDK/Core - - MatrixSDKCrypto (0.3.13) + - MatrixSDKCrypto (0.4.2) - OLMKit (3.2.12): - OLMKit/olmc (= 3.2.12) - OLMKit/olmcpp (= 3.2.12) @@ -74,6 +74,7 @@ PODS: - Sentry/Core (7.15.0) - SideMenu (6.5.0) - SwiftBase32 (0.9.0) + - SwiftFormat/CLI (0.54.0) - SwiftGen (6.6.2) - SwiftJWT (3.6.200): - BlueCryptor (~> 1.0) @@ -102,8 +103,8 @@ DEPENDENCIES: - KeychainAccess (~> 4.2.2) - KTCenterFlowLayout (~> 1.3.1) - libPhoneNumber-iOS (~> 0.9.13) - - MatrixSDK (= 0.27.7) - - MatrixSDK/JingleCallStack (= 0.27.7) + - MatrixSDK (from `matrix-ios-sdk/MatrixSDK.podspec`) + - MatrixSDK/JingleCallStack (from `matrix-ios-sdk/MatrixSDK.podspec`) - OLMKit - PostHog (~> 2.0.0) - ReadMoreTextView (~> 3.0.1) @@ -112,6 +113,7 @@ DEPENDENCIES: - Sentry (~> 7.15.0) - SideMenu (~> 6.5) - SwiftBase32 (~> 0.9.0) + - SwiftFormat/CLI - SwiftGen (~> 6.3) - SwiftJWT (~> 3.6.200) - SwiftLint (~> 0.49.1) @@ -144,7 +146,6 @@ SPEC REPOS: - libPhoneNumber-iOS - LoggerAPI - Logging - - MatrixSDK - MatrixSDKCrypto - OLMKit - PostHog @@ -155,6 +156,7 @@ SPEC REPOS: - Sentry - SideMenu - SwiftBase32 + - SwiftFormat - SwiftGen - SwiftJWT - SwiftLint @@ -165,6 +167,10 @@ SPEC REPOS: - zxcvbn-ios - ZXingObjC +EXTERNAL SOURCES: + MatrixSDK: + :path: matrix-ios-sdk/MatrixSDK.podspec + SPEC CHECKSUMS: AFNetworking: 3bd23d814e976cd148d7d44c3ab78017b744cd58 BlueCryptor: b0aee3d9b8f367b49b30de11cda90e1735571c24 @@ -187,8 +193,8 @@ SPEC CHECKSUMS: libPhoneNumber-iOS: 0a32a9525cf8744fe02c5206eb30d571e38f7d75 LoggerAPI: ad9c4a6f1e32f518fdb43a1347ac14d765ab5e3d Logging: beeb016c9c80cf77042d62e83495816847ef108b - MatrixSDK: e07b2309f3c6498c1df987441da7006d099c47a4 - MatrixSDKCrypto: bf08b72f2cd015d8749420a2b8b92fc0536bedf4 + MatrixSDK: c805f9306d60955215f4b15043ed0f96fd4867b3 + MatrixSDKCrypto: 736069ee0a5ec12852ab3498bf2242acecc443fc OLMKit: da115f16582e47626616874e20f7bb92222c7a51 PostHog: 660ec6c9d80cec17b685e148f17f6785a88b597d ReadMoreTextView: 19147adf93abce6d7271e14031a00303fe28720d @@ -198,6 +204,7 @@ SPEC CHECKSUMS: Sentry: 63ca44f5e0c8cea0ee5a07686b02e56104f41ef7 SideMenu: f583187d21c5b1dd04c72002be544b555a2627a2 SwiftBase32: 9399c25a80666dc66b51e10076bf591e3bbb8f17 + SwiftFormat: 0e0b577434e6aa63bc82a8905b40d9597b8452d4 SwiftGen: 1366a7f71aeef49954ca5a63ba4bef6b0f24138c SwiftJWT: 88c412708f58c169d431d344c87bc79a87c830ae SwiftLint: 32ee33ded0636d0905ef6911b2b67bbaeeedafa5 @@ -208,6 +215,6 @@ SPEC CHECKSUMS: zxcvbn-ios: fef98b7c80f1512ff0eec47ac1fa399fc00f7e3c ZXingObjC: 8898711ab495761b2dbbdec76d90164a6d7e14c5 -PODFILE CHECKSUM: 3db384be27ed06c11b15ee9abc4616afd45428c1 +PODFILE CHECKSUM: 971cd7529e2d127b237469afa2c18e6dced151ec COCOAPODS: 1.14.3 diff --git a/Riot.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Riot.xcworkspace/xcshareddata/swiftpm/Package.resolved index 2d8489845b..c90d47d46f 100644 --- a/Riot.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Riot.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -5,8 +5,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/devicekit/DeviceKit", "state" : { - "revision" : "20e0991f3975916ab0f6d58db84d8bc64f883537", - "version" : "4.7.0" + "revision" : "d37e70cb2646666dcf276d7d3d4a9760a41ff8a6", + "version" : "4.9.0" } }, { @@ -41,8 +41,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/matrix-org/matrix-analytics-events", "state" : { - "revision" : "44d5a0e898a71f8abbbe12afe9d73e82d370a9a1", - "version" : "0.15.0" + "revision" : "de0cac487e5e7f607ee17045882204c91585461f", + "version" : "0.23.1" } }, { @@ -72,13 +72,22 @@ "version" : "0.8.4" } }, + { + "identity" : "posthog-ios", + "kind" : "remoteSourceControl", + "location" : "https://github.com/PostHog/posthog-ios", + "state" : { + "revision" : "8b2508444962d67aa5f8770074f32d493383dafd", + "version" : "3.2.5" + } + }, { "identity" : "swift-collections", "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-collections", "state" : { - "revision" : "48254824bb4248676bf7ce56014ff57b142b77eb", - "version" : "1.0.2" + "revision" : "94cf62b3ba8d4bed62680a282d4c25f9c63c2efb", + "version" : "1.1.0" } }, { diff --git a/Riot/Assets/en.lproj/Vector.strings b/Riot/Assets/en.lproj/Vector.strings index c45dc58348..363d3bb6d1 100644 --- a/Riot/Assets/en.lproj/Vector.strings +++ b/Riot/Assets/en.lproj/Vector.strings @@ -594,6 +594,8 @@ Tap the + to start adding people."; "room_action_send_sticker" = "Send sticker"; "room_action_send_file" = "Send file"; "room_action_reply" = "Reply"; +"room_action_report" = "Report room"; +"room_action_report_prompt_reason" = "Reason for reporting this room"; "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."; diff --git a/Riot/Assets/hu.lproj/Vector.strings b/Riot/Assets/hu.lproj/Vector.strings index c965d650ce..216ab88fdb 100644 --- a/Riot/Assets/hu.lproj/Vector.strings +++ b/Riot/Assets/hu.lproj/Vector.strings @@ -1352,7 +1352,7 @@ "event_formatter_call_ringing" = "Hívás…"; "event_formatter_call_connecting" = "Kapcsolás…"; "settings_labs_enable_ringing_for_group_calls" = "Csengetés csoportos hívásokhoz"; -"room_no_privileges_to_create_group_call" = "Adminisztrátornak vagy moderátornak kell lenned a hívás indításához."; +"room_no_privileges_to_create_group_call" = "Adminisztrátornak vagy moderátornak kell lennie a hívás indításához."; "room_join_group_call" = "Csatlakozás"; // Chat @@ -2501,8 +2501,8 @@ "room_first_message_placeholder" = "Küld el az első üzenetedet…"; "authentication_qr_login_confirm_title" = "Biztonságos kapcsolat beállítva"; "room_event_encryption_info_key_authenticity_not_guaranteed" = "A titkosított üzenetek valódiságát ezen az eszközön nem lehet garantálni."; -"wysiwyg_composer_format_action_underline" = "Aláhúzott"; -"wysiwyg_composer_format_action_strikethrough" = "Áthúzott"; +"wysiwyg_composer_format_action_underline" = "Aláhúzott formázás alkalmazása"; +"wysiwyg_composer_format_action_strikethrough" = "Áthúzott formázás alkalmazása"; "wysiwyg_composer_format_action_italic" = "Dőlt"; // Formatting Actions @@ -2755,3 +2755,11 @@ "room_command_kick_user_description" = "Eltávolítja az adott azonosítójú felhasználót ebből a szobából"; "room_command_ban_user_description" = "Kitiltja az adott azonosítójú felhasználót"; "room_command_unban_user_description" = "Feloldja az adott azonosítójú felhasználó kitiltását"; +"notice_display_name_changed_to" = "%@ erre módosította a megjelenítendő nevét: %@"; +"poll_timeline_loading" = "Betöltés..."; +"manage_session_redirect" = "Át lesz irányítva a kiszolgálója hitelesítési szolgáltatójához, hogy befejezze a kijelentkezést."; +"manage_session_redirect_error" = "A funkcionalitás jelenleg nem érhető el. Lépjen kapcsolatba a Matrix-kiszolgáló rendszergazdájával."; +"settings_manage_account_title" = "Fiók"; +"settings_manage_account_action" = "Fiók kezelése"; +"settings_manage_account_description" = "A fiókja kezelése itt: %@"; +"room_command_change_room_topic_description" = "Beállítja a szoba témáját"; diff --git a/Riot/Assets/nn.lproj/InfoPlist.strings b/Riot/Assets/nn.lproj/InfoPlist.strings new file mode 100644 index 0000000000..c1f10e5b0a --- /dev/null +++ b/Riot/Assets/nn.lproj/InfoPlist.strings @@ -0,0 +1,7 @@ + + +"NSCalendarsUsageDescription" = "Sjå dei planlagde møta dine i appen."; +"NSFaceIDUsageDescription" = "Face ID vert brukt til å få tilgang til appen din."; +// Permissions usage explanations +"NSCameraUsageDescription" = "Kameraet vert brukt til videosamtalar, og til å ta og laste opp bilde og videoar."; +"NSMicrophoneUsageDescription" = "Appen treng tilgang til mikrofonen for samtalar, og for å spele inn video og lydmeldingar."; diff --git a/Riot/Assets/nn.lproj/Localizable.strings b/Riot/Assets/nn.lproj/Localizable.strings new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/Riot/Assets/nn.lproj/Localizable.strings @@ -0,0 +1 @@ + diff --git a/Riot/Assets/nn.lproj/Vector.strings b/Riot/Assets/nn.lproj/Vector.strings index 1eec1c5583..533975c2fa 100644 --- a/Riot/Assets/nn.lproj/Vector.strings +++ b/Riot/Assets/nn.lproj/Vector.strings @@ -9,3 +9,74 @@ "warning" = "Åtvaring"; // String for App Store "store_short_description" = "Sikker desentralisert chat/IP-telefoni"; +"ok" = "OK"; +"callbar_only_single_paused" = "Samtale sett på pause"; +"cancel" = "Avbryt"; +"save" = "Lagra"; +"join" = "Ver med"; +"decline" = "Avslå"; +"accept" = "Godta"; +"preview" = "Førehandsvising"; +"camera" = "Kamera"; +"voice" = "Røyst"; +"video" = "Video"; +"active_call" = "Aktiv samtale"; +"active_call_details" = "Aktiv samtale (%@)"; +"joined" = "Vart med"; +"later" = "Seinare"; +"rename" = "Endre namn"; +"collapse" = "Skjul"; +"send_to" = "Send til %@"; +"sending" = "Sender"; +"close" = "Lat att"; +"skip" = "Hopp over"; +"switch" = "Byt"; +"more" = "Meir"; +"less" = "Mindre"; +"open" = "Opne"; +"done" = "Ferdig"; + +// Call Bar +"callbar_only_single_active" = "Trykk for å gå tilbake til samtalen (%@)"; +"callbar_only_multiple_paused" = "%@ samtalar sett på pause"; +"callbar_return" = "Gå tilbake"; +"callbar_only_single_active_group" = "Trykk for å bli med i konferansesamtalen (%@)"; + +// Accessibility +"accessibility_checkbox_label" = "avkryssingsboks"; +"accessibility_button_label" = "knapp"; +"error" = "Feil"; +"invite_to" = "Inviter til %@"; + +// MARK: Onboarding +"onboarding_splash_register_button_title" = "Opprett konto"; +"accessibility_selected" = "vald"; +"private" = "Privat"; +"public" = "Offentleg"; +"stop" = "Stopp"; +"new_word" = "Ny"; +"existing" = "Eksisterande"; +"add" = "Legg til"; +"suggest" = "Føreslå"; +"edit" = "Rediger"; + +// Activities +"loading" = "Lastar"; +"saving" = "Lagrar"; +"callbar_active_and_single_paused" = "1 aktiv samtale (%@) · 1 samtale sett i pause"; +"callbar_active_and_multiple_paused" = "1 aktiv samtale (%@) · %@ samtalar sett på pause"; +"confirm" = "Stadfest"; +"onboarding_splash_login_button_title" = "Eg har allereie ein konto"; +"authentication_forgot_password_text_field_placeholder" = "E-postadresse"; +"next" = "Neste"; +"back" = "Tilbake"; +"continue" = "Fortset"; +"create" = "Lag"; +"remove" = "Fjern"; +"invite" = "Inviter"; +"retry" = "Prøv på nytt"; +"on" = "På"; +"off" = "Av"; +"authentication_login_username" = "Brukarnamn / e-postadresse / telefonnummer"; +"onboarding_display_name_placeholder" = "Visingsnamn"; +"authentication_login_with_qr" = "Logg inn med QR-kode"; diff --git a/Riot/Assets/pt_BR.lproj/Vector.strings b/Riot/Assets/pt_BR.lproj/Vector.strings index 991716cd9f..edd2cf70e6 100644 --- a/Riot/Assets/pt_BR.lproj/Vector.strings +++ b/Riot/Assets/pt_BR.lproj/Vector.strings @@ -1226,7 +1226,7 @@ "secrets_recovery_reset_action_part_2" = "Resettar tudo"; "secrets_setup_recovery_passphrase_summary_information" = "Lembre-se de sua Frase de Segurança. Ela pode ser usada para destrancar suas mensagens & dados encriptados."; "secrets_setup_recovery_passphrase_summary_title" = "Salvar sua Frase de Segurança"; -"home_empty_view_information" = "O app de chat seguro tudo-em-um para equipes, amigas(os) e organizações. Toque no botão + abaixo para adicionar pessoas e salas."; +"home_empty_view_information" = "O app de chat seguro tudo-em-um para equipes, amigos e organizações. Toque no botão + abaixo para adicionar pessoas e salas."; // MARK: - Home @@ -1244,7 +1244,7 @@ // MARK: - Invite friends -"invite_friends_action" = "Convidar amigas(os) para %@"; +"invite_friends_action" = "Convidar amigos para %@"; "pin_protection_settings_change_pin" = "Mudar PIN"; "pin_protection_confirm_pin_to_change" = "Confirme PIN para mudar PIN"; "bug_report_background_mode" = "Continuar em background"; @@ -1348,7 +1348,7 @@ "side_menu_action_feedback" = "Feedback"; "side_menu_action_help" = "Ajuda"; "side_menu_action_settings" = "Ajustes"; -"side_menu_action_invite_friends" = "Convidar amigas(os)"; +"side_menu_action_invite_friends" = "Convidar amigos"; // Mark: - Side menu @@ -1632,7 +1632,7 @@ "onboarding_use_case_not_sure_yet" = "Não tem certeza ainda? %@"; "onboarding_use_case_community_messaging" = "Comunidades"; "onboarding_use_case_work_messaging" = "Times"; -"onboarding_use_case_personal_messaging" = "Amigas(os) e família"; +"onboarding_use_case_personal_messaging" = "Amigos e família"; "onboarding_use_case_message" = "Nós vamos ajudá-la(o) a ficar conectada(o)"; "onboarding_use_case_title" = "Com quem você vai fazer chat mais?"; @@ -2654,3 +2654,20 @@ "user_other_session_security_recommendation_title" = "Outras sessões"; "room_creation_only_one_email_invite" = "Você só pode convidar um e-mail de cada vez"; "accessibility_selected" = "selecionado"; +"room_creation_user_not_found_prompt_title" = "Confirmação"; +"room_creation_user_not_found_prompt_message" = "Não foi possível encontrar perfis para este ID do Matrix. Quer começar uma conversa mesmo assim?"; +"room_creation_user_not_found_prompt_invite_action" = "Começar conversa mesmo assim"; +"room_participants_invite_anyway" = "Convidar mesmo assim"; +"room_command_part_room_description" = "Sair da sala"; +"room_command_kick_user_description" = "Remove o usuário com o ID fornecido desta sala"; +"room_command_unban_user_description" = "Desbane o usuário com o ID fornecido"; +"room_command_set_user_power_level_description" = "Define o nível de poder de um usuário"; +"room_participants_invite_unknown_participant_prompt_to_msg" = "Não foi possível encontrar perfis para este ID do Matrix. Tem certeza que deseja convidar %@ para %@?"; +"room_command_ban_user_description" = "Bane o usuário com o ID fornecido"; + +// Room commands descriptions +"room_command_change_display_name_description" = "Altera o seu nome de exibição"; +"room_command_emote_description" = "Exibe ação"; +"room_command_join_room_description" = "Entra na sala com o endereço fornecido"; +"room_command_invite_user_description" = "Convida o usuário com o ID fornecido para a sala atual"; +"authentication_qr_login_failure_device_not_supported" = "Vincular com este dispositivo não é suportado."; diff --git a/Riot/Categories/MXRoomSummary.swift b/Riot/Categories/MXRoomSummary.swift new file mode 100644 index 0000000000..4cf55ff6a6 --- /dev/null +++ b/Riot/Categories/MXRoomSummary.swift @@ -0,0 +1,57 @@ +// +// Copyright 2024 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 Notification.Name { + static let roomSummaryDidRemoveExpiredDataFromStore = Notification.Name(MXRoomSummary.roomSummaryDidRemoveExpiredDataFromStore) +} + +extension MXRoomSummary { + @objc static let roomSummaryDidRemoveExpiredDataFromStore = "roomSummaryDidRemoveExpiredDataFromStore" + @objc static let roomRetentionStateEventType = "m.room.retention" + @objc static let roomRetentionEventMaxLifetimeKey = "max_lifetime" + @objc static let roomRetentionMaxLifetime = "roomRetentionMaxLifetime" + + /// Get the room messages retention period in days + private func roomRetentionPeriodInMillis() -> UInt64 { + if let period = self.others[MXRoomSummary.roomRetentionMaxLifetime] as? UInt64 { + return period + } else { + return Tools.durationInMs(fromDays: 365) + } + } + + /// Get the timestamp below which the received messages must be removed from the store, and the display + @objc func minimumTimestamp() -> UInt64 { + let periodInMs = self.roomRetentionPeriodInMillis() + let currentTs = (UInt64)(Date().timeIntervalSince1970 * 1000) + return (currentTs - periodInMs) + } + + /// Remove the expired messages from the store. + /// If some data are removed, this operation posts the notification: roomSummaryDidRemoveExpiredDataFromStore. + /// This operation does not commit the potential change. We let the caller trigger the commit when this is the more suitable. + /// + /// Provide a boolean telling whether some data have been removed. + @objc func removeExpiredRoomContentsFromStore() -> Bool { + let ret = self.mxSession.store.removeAllMessagesSent(before: self.minimumTimestamp(), inRoom: roomId) + if ret { + NotificationCenter.default.post(name: .roomSummaryDidRemoveExpiredDataFromStore, object: self) + } + return ret + } +} diff --git a/Riot/Categories/MXSession.swift b/Riot/Categories/MXSession.swift index 10df98141f..4d7256b62f 100644 --- a/Riot/Categories/MXSession.swift +++ b/Riot/Categories/MXSession.swift @@ -25,4 +25,16 @@ extension MXSession { matrixItemId: userId, displayName: user?.displayname) } + + /// Clean the storage of a session by removing the expired contents. + @objc func removeExpiredMessages() { + var hasStoreChanged = false + for room in self.rooms { + hasStoreChanged = hasStoreChanged || room.summary.removeExpiredRoomContentsFromStore() + } + + if hasStoreChanged { + self.store.commit?() + } + } } diff --git a/Riot/Generated/Vector_Strings.swift b/Riot/Generated/Vector_Strings.swift index cec6f80b3d..28e3ed8e1d 100644 --- a/Riot/Generated/Vector_Strings.swift +++ b/Riot/Generated/Vector_Strings.swift @@ -5207,6 +5207,14 @@ public class VectorL10n: NSObject { public static var roomActionReply: String { return VectorL10n.tr("Vector", "room_action_reply") } + /// Report room + public static var roomActionReport: String { + return VectorL10n.tr("Vector", "room_action_report") + } + /// Reason for reporting this room + public static var roomActionReportPromptReason: String { + return VectorL10n.tr("Vector", "room_action_report_prompt_reason") + } /// Send file public static var roomActionSendFile: String { return VectorL10n.tr("Vector", "room_action_send_file") diff --git a/Riot/Managers/Settings/RiotSettings.swift b/Riot/Managers/Settings/RiotSettings.swift index 5e0e90d6db..c2755d9561 100644 --- a/Riot/Managers/Settings/RiotSettings.swift +++ b/Riot/Managers/Settings/RiotSettings.swift @@ -97,10 +97,6 @@ final class RiotSettings: NSObject { @UserDefault(key: UserDefaultsKeys.pinRoomsWithUnreadMessagesOnHome, defaultValue: false, storage: defaults) var pinRoomsWithUnreadMessagesOnHome - /// Indicate to show Not Safe For Work public rooms. - @UserDefault(key: "showNSFWPublicRooms", defaultValue: false, storage: defaults) - var showNSFWPublicRooms - /// Indicate if the user wants to display the join and leave events in the room history. /// (No by default) @UserDefault(key: "showJoinLeaveEvents", defaultValue: false, storage: defaults) @@ -346,10 +342,7 @@ final class RiotSettings: NSObject { @UserDefault(key: "settingsScreenShowNotificationDecodedContentOption", defaultValue: BuildSettings.settingsScreenShowNotificationDecodedContentOption, storage: defaults) var settingsScreenShowNotificationDecodedContentOption - - @UserDefault(key: "settingsScreenShowNsfwRoomsOption", defaultValue: BuildSettings.settingsScreenShowNsfwRoomsOption, storage: defaults) - var settingsScreenShowNsfwRoomsOption - + @UserDefault(key: "settingsSecurityScreenShowSessions", defaultValue: BuildSettings.settingsSecurityScreenShowSessions, storage: defaults) var settingsSecurityScreenShowSessions diff --git a/Riot/Modules/Analytics/Analytics.swift b/Riot/Modules/Analytics/Analytics.swift index d9d68e2f85..3b19c98090 100644 --- a/Riot/Modules/Analytics/Analytics.swift +++ b/Riot/Modules/Analytics/Analytics.swift @@ -14,7 +14,6 @@ // limitations under the License. // -import PostHog import AnalyticsEvents /// A class responsible for managing a variety of analytics clients @@ -95,6 +94,9 @@ import AnalyticsEvents guard let session = session else { return } useAnalyticsSettings(from: session) + client.updateSuperProperties(.init(appPlatform: .EI, + cryptoSDK: .Rust, + cryptoSDKVersion: session.crypto.version)) } /// Stops analytics tracking and calls `reset` to clear any IDs and event queues. @@ -149,6 +151,13 @@ import AnalyticsEvents switch result { case .success(let settings): self.identify(with: settings) + self.client.updateSuperProperties( + AnalyticsEvent.SuperProperties( + appPlatform: .EI, + cryptoSDK: .Rust, + cryptoSDKVersion: session.crypto.version + ) + ) self.service = nil case .failure: MXLog.error("[Analytics] Failed to use analytics settings. Will continue to run without analytics ID.") @@ -243,7 +252,9 @@ extension Analytics { let userProperties = AnalyticsEvent.UserProperties(allChatsActiveFilter: allChatsActiveFilter?.analyticsName, ftueUseCaseSelection: ftueUseCase?.analyticsName, numFavouriteRooms: numFavouriteRooms, - numSpaces: numSpaces) + numSpaces: numSpaces, + recoveryState: nil, + verificationState: nil) client.updateUserProperties(userProperties) } diff --git a/Riot/Modules/Analytics/AnalyticsClientProtocol.swift b/Riot/Modules/Analytics/AnalyticsClientProtocol.swift index e16b962e5e..cfc90fadbc 100644 --- a/Riot/Modules/Analytics/AnalyticsClientProtocol.swift +++ b/Riot/Modules/Analytics/AnalyticsClientProtocol.swift @@ -53,4 +53,11 @@ protocol AnalyticsClientProtocol { /// be a delay when updating user properties as these are cached to be included /// as part of the next event that gets captured. func updateUserProperties(_ userProperties: AnalyticsEvent.UserProperties) + + + /// Updates the super properties. + /// Super properties added to all captured events and screen. + /// - Parameter superProperties: The properties event to capture. + func updateSuperProperties(_ event: AnalyticsEvent.SuperProperties) + } diff --git a/Riot/Modules/Analytics/PHGPostHogConfiguration.swift b/Riot/Modules/Analytics/PHGPostHogConfiguration.swift index 8bed049201..875de74e48 100644 --- a/Riot/Modules/Analytics/PHGPostHogConfiguration.swift +++ b/Riot/Modules/Analytics/PHGPostHogConfiguration.swift @@ -16,14 +16,16 @@ import PostHog -extension PHGPostHogConfiguration { - static var standard: PHGPostHogConfiguration? { +extension PostHogConfig { + static var standard: PostHogConfig? { let analyticsConfiguration = BuildSettings.analyticsConfiguration guard analyticsConfiguration.isEnabled else { return nil } - let postHogConfiguration = PHGPostHogConfiguration(apiKey: analyticsConfiguration.apiKey, host: analyticsConfiguration.host) - postHogConfiguration.shouldSendDeviceID = false - + let postHogConfiguration = PostHogConfig(apiKey: analyticsConfiguration.apiKey, host: analyticsConfiguration.host) + // We capture screens manually + postHogConfiguration.captureScreenViews = false + + return postHogConfiguration } } diff --git a/Riot/Modules/Analytics/PostHogAnalyticsClient.swift b/Riot/Modules/Analytics/PostHogAnalyticsClient.swift index ec49716bed..5a70c58373 100644 --- a/Riot/Modules/Analytics/PostHogAnalyticsClient.swift +++ b/Riot/Modules/Analytics/PostHogAnalyticsClient.swift @@ -19,31 +19,48 @@ import AnalyticsEvents /// An analytics client that reports events to a PostHog server. class PostHogAnalyticsClient: AnalyticsClientProtocol { + + private var posthogFactory: PostHogFactory = DefaultPostHogFactory() + + init(posthogFactory: PostHogFactory? = nil) { + if let factory = posthogFactory { + self.posthogFactory = factory + } + } + /// The PHGPostHog object used to report events. - private var postHog: PHGPostHog? + private var postHog: PostHogProtocol? /// Any user properties to be included with the next captured event. private(set) var pendingUserProperties: AnalyticsEvent.UserProperties? + /// Super Properties are properties associated with events that are set once and then sent with every capture call, be it a $screen, an autocaptured button click, or anything else. + /// It is different from user properties that will be attached to the user and not events. + /// Not persisted for now, should be set on start. + private var superProperties: AnalyticsEvent.SuperProperties? + static let shared = PostHogAnalyticsClient() - var isRunning: Bool { postHog?.enabled ?? false } + var isRunning: Bool { + guard let postHog else { return false } + return !postHog.isOptOut() + } func start() { // Only start if analytics have been configured in BuildSettings - guard let configuration = PHGPostHogConfiguration.standard else { return } + guard let configuration = PostHogConfig.standard else { return } if postHog == nil { - postHog = PHGPostHog(configuration: configuration) + postHog = posthogFactory.createPostHog(config: configuration) } - postHog?.enable() + postHog?.optIn() } func identify(id: String) { if let userProperties = pendingUserProperties { // As user properties overwrite old ones, compactMap the dictionary to avoid resetting any missing properties - postHog?.identify(id, properties: userProperties.properties.compactMapValues { $0 }) + postHog?.identify(id, userProperties: userProperties.properties.compactMapValues { $0 }) pendingUserProperties = nil } else { postHog?.identify(id) @@ -56,10 +73,9 @@ class PostHogAnalyticsClient: AnalyticsClientProtocol { } func stop() { - postHog?.disable() + postHog?.optOut() - // As of PostHog 1.4.4, setting the client to nil here doesn't release - // it. Keep it around to avoid having multiple instances if the user re-enables + self.postHog = nil } func flush() { @@ -67,11 +83,13 @@ class PostHogAnalyticsClient: AnalyticsClientProtocol { } func capture(_ event: AnalyticsEventProtocol) { - postHog?.capture(event.eventName, properties: attachUserProperties(to: event.properties)) + postHog?.capture(event.eventName, properties: attachSuperProperties(to: event.properties), userProperties: pendingUserProperties?.properties.compactMapValues { $0 }) + // Pending user properties have been added + self.pendingUserProperties = nil } func screen(_ event: AnalyticsScreenProtocol) { - postHog?.screen(event.screenName.rawValue, properties: attachUserProperties(to: event.properties)) + postHog?.screen(event.screenName.rawValue, properties: attachSuperProperties(to: event.properties)) } func updateUserProperties(_ userProperties: AnalyticsEvent.UserProperties) { @@ -84,25 +102,35 @@ class PostHogAnalyticsClient: AnalyticsClientProtocol { self.pendingUserProperties = AnalyticsEvent.UserProperties(allChatsActiveFilter: userProperties.allChatsActiveFilter ?? pendingUserProperties.allChatsActiveFilter, ftueUseCaseSelection: userProperties.ftueUseCaseSelection ?? pendingUserProperties.ftueUseCaseSelection, numFavouriteRooms: userProperties.numFavouriteRooms ?? pendingUserProperties.numFavouriteRooms, - numSpaces: userProperties.numSpaces ?? pendingUserProperties.numSpaces) + numSpaces: userProperties.numSpaces ?? pendingUserProperties.numSpaces, + // Not yet supported + recoveryState: nil, verificationState: nil) } - // MARK: - Private + func updateSuperProperties(_ updatedProperties: AnalyticsEvent.SuperProperties) { + self.superProperties = AnalyticsEvent.SuperProperties( + appPlatform: updatedProperties.appPlatform ?? superProperties?.appPlatform, + cryptoSDK: updatedProperties.cryptoSDK ?? superProperties?.cryptoSDK, + cryptoSDKVersion: updatedProperties.cryptoSDKVersion ?? superProperties?.cryptoSDKVersion + ) + } - /// Given a dictionary containing properties from an event, this method will return those properties - /// with any pending user properties included under the `$set` key. - /// - Parameter properties: A dictionary of properties from an event. - /// - Returns: The `properties` dictionary with any user properties included. - private func attachUserProperties(to properties: [String: Any]) -> [String: Any] { - guard isRunning, let userProperties = pendingUserProperties else { return properties } + /// Attach super properties to events. + /// If the property is already set on the event, the already set value will be kept. + private func attachSuperProperties(to properties: [String: Any]) -> [String: Any] { + guard isRunning, let superProperties else { return properties } var properties = properties - // As user properties overwrite old ones via $set, compactMap the dictionary to avoid resetting any missing properties - properties["$set"] = userProperties.properties.compactMapValues { $0 } - pendingUserProperties = nil + superProperties.properties.forEach { (key: String, value: Any) in + if properties[key] == nil { + properties[key] = value + } + } return properties } + + } extension PostHogAnalyticsClient: RemoteFeaturesClientProtocol { diff --git a/Riot/Modules/Analytics/PosthogProtocol.swift b/Riot/Modules/Analytics/PosthogProtocol.swift new file mode 100644 index 0000000000..d24d19e170 --- /dev/null +++ b/Riot/Modules/Analytics/PosthogProtocol.swift @@ -0,0 +1,53 @@ +// +// Copyright 2024 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 PostHog + +protocol PostHogProtocol { + func optIn() + + func optOut() + + func reset() + + func flush() + + func capture(_ event: String, properties: [String: Any]?, userProperties: [String: Any]?) + + func screen(_ screenTitle: String, properties: [String: Any]?) + + func isFeatureEnabled(_ feature: String) -> Bool + + func identify(_ distinctId: String) + + func identify(_ distinctId: String, userProperties: [String: Any]?) + + func isOptOut() -> Bool +} + +protocol PostHogFactory { + func createPostHog(config: PostHogConfig) -> PostHogProtocol +} + +class DefaultPostHogFactory: PostHogFactory { + func createPostHog(config: PostHogConfig) -> PostHogProtocol { + PostHogSDK.shared.setup(config) + return PostHogSDK.shared + } +} + +extension PostHogSDK: PostHogProtocol { } diff --git a/Riot/Modules/Application/LegacyAppDelegate.m b/Riot/Modules/Application/LegacyAppDelegate.m index 1011a4f832..99fe7fcc58 100644 --- a/Riot/Modules/Application/LegacyAppDelegate.m +++ b/Riot/Modules/Application/LegacyAppDelegate.m @@ -1991,6 +1991,9 @@ - (void)initMatrixSessions [self registerNewRequestNotificationForSession:mxSession]; [self.pushNotificationService checkPushKitPushersInSession:mxSession]; + + // Clean the storage by removing expired data + [mxSession removeExpiredMessages]; } else if (mxSession.state == MXSessionStateRunning) { diff --git a/Riot/Modules/Common/Recents/DataSources/RecentsDataSource.m b/Riot/Modules/Common/Recents/DataSources/RecentsDataSource.m index 48add73d5f..38808bd136 100644 --- a/Riot/Modules/Common/Recents/DataSources/RecentsDataSource.m +++ b/Riot/Modules/Common/Recents/DataSources/RecentsDataSource.m @@ -484,7 +484,6 @@ - (MXKSessionRecentsDataSource *)addMatrixSession:(MXSession *)mxSession if (!_publicRoomsDirectoryDataSource) { _publicRoomsDirectoryDataSource = [[PublicRoomsDirectoryDataSource alloc] initWithMatrixSession:mxSession]; - _publicRoomsDirectoryDataSource.showNSFWRooms = RiotSettings.shared.showNSFWPublicRooms; _publicRoomsDirectoryDataSource.delegate = self; } diff --git a/Riot/Modules/GlobalSearch/UnifiedSearchViewController.m b/Riot/Modules/GlobalSearch/UnifiedSearchViewController.m index 8703623bba..94ae50f5a2 100644 --- a/Riot/Modules/GlobalSearch/UnifiedSearchViewController.m +++ b/Riot/Modules/GlobalSearch/UnifiedSearchViewController.m @@ -141,8 +141,6 @@ - (void)viewWillAppear:(BOOL)animated // Reset searches [recentsDataSource searchWithPatterns:nil]; - // TODO: Notify RiotSettings.shared.showNSFWPublicRooms change for iPad as viewWillAppear may not be called - recentsDataSource.publicRoomsDirectoryDataSource.showNSFWRooms = RiotSettings.shared.showNSFWPublicRooms; [self updateSearch]; diff --git a/Riot/Modules/PublicRoomList/DataSources/PublicRoomsDirectoryDataSource.h b/Riot/Modules/PublicRoomList/DataSources/PublicRoomsDirectoryDataSource.h index 6fce1d92b0..121722ed46 100644 --- a/Riot/Modules/PublicRoomList/DataSources/PublicRoomsDirectoryDataSource.h +++ b/Riot/Modules/PublicRoomList/DataSources/PublicRoomsDirectoryDataSource.h @@ -45,11 +45,6 @@ */ @property (nonatomic) BOOL includeAllNetworks; -/** - Flag to indicate to show Not Safe For Work rooms in the public room list. - */ -@property (nonatomic) BOOL showNSFWRooms; - /** List public rooms from a third party protocol. Default is nil. diff --git a/Riot/Modules/PublicRoomList/DataSources/PublicRoomsDirectoryDataSource.m b/Riot/Modules/PublicRoomList/DataSources/PublicRoomsDirectoryDataSource.m index cad8560125..5ae737549c 100644 --- a/Riot/Modules/PublicRoomList/DataSources/PublicRoomsDirectoryDataSource.m +++ b/Riot/Modules/PublicRoomList/DataSources/PublicRoomsDirectoryDataSource.m @@ -46,6 +46,8 @@ @interface PublicRoomsDirectoryDataSource () NSString *nextBatch; } +@property (nonatomic, strong) NSRegularExpression *forbiddenTermsRegex; + @end @implementation PublicRoomsDirectoryDataSource @@ -57,6 +59,15 @@ - (instancetype)init { rooms = [NSMutableArray array]; _paginationLimit = 20; + + NSString *path = [[NSBundle mainBundle] pathForResource:@"forbidden_terms" ofType:@"txt"]; + NSString *fileContents = [NSString stringWithContentsOfFile:path encoding: NSUTF8StringEncoding error:nil]; + NSArray *forbiddenTerms = [fileContents componentsSeparatedByCharactersInSet: NSCharacterSet.whitespaceAndNewlineCharacterSet]; + + NSString *pattern = [NSString stringWithFormat:@"\\b(%@)\\b", [forbiddenTerms componentsJoinedByString:@"|"]]; + pattern = [pattern stringByAppendingString:@"|(\\b18\\+)"]; // Special case "18+" + + _forbiddenTermsRegex = [[NSRegularExpression alloc] initWithPattern:pattern options:NSRegularExpressionCaseInsensitive error:nil]; } return self; } @@ -155,16 +166,6 @@ - (void)setSearchPattern:(NSString *)searchPattern } } -- (void)setShowNSFWRooms:(BOOL)showNSFWRooms -{ - if (showNSFWRooms != _showNSFWRooms) - { - _showNSFWRooms = showNSFWRooms; - - [self resetPagination]; - } -} - - (NSUInteger)roomsCount { return rooms.count; @@ -254,14 +255,7 @@ - (MXHTTPOperation *)paginate:(void (^)(NSUInteger))complete failure:(void (^)(N NSArray *publicRooms; - if (self.showNSFWRooms) - { - publicRooms = publicRoomsResponse.chunk; - } - else - { - publicRooms = [self filterPublicRooms:publicRoomsResponse.chunk containingKeyword:kNSFWKeyword]; - } + publicRooms = [self filterPublicRooms:publicRoomsResponse.chunk]; [self->rooms addObjectsFromArray:publicRooms]; self->nextBatch = publicRoomsResponse.nextBatch; @@ -338,15 +332,23 @@ - (void)setState:(MXKDataSourceState)newState } } -- (NSArray*)filterPublicRooms:(NSArray*)publicRooms containingKeyword:(NSString*)keyword +- (NSArray*)filterPublicRooms:(NSArray*)publicRooms { NSMutableArray *filteredRooms = [NSMutableArray new]; for (MXPublicRoom *publicRoom in publicRooms) { - if (NO == [[publicRoom.name lowercaseString] containsString:keyword] - && NO == [[publicRoom.topic lowercaseString] containsString:keyword]) - { + BOOL shouldAllow = YES; + + if (publicRoom.name != nil) { + shouldAllow &= [self.forbiddenTermsRegex numberOfMatchesInString:publicRoom.name options:0 range:NSMakeRange(0, publicRoom.name.length)] == 0; + } + + if (publicRoom.topic != nil) { + shouldAllow &= [self.forbiddenTermsRegex numberOfMatchesInString:publicRoom.topic options:0 range:NSMakeRange(0, publicRoom.topic.length)] == 0; + } + + if (shouldAllow) { [filteredRooms addObject:publicRoom]; } } diff --git a/Riot/Modules/Room/DataSources/RoomDataSource.m b/Riot/Modules/Room/DataSources/RoomDataSource.m index 4fac685701..906aabba71 100644 --- a/Riot/Modules/Room/DataSources/RoomDataSource.m +++ b/Riot/Modules/Room/DataSources/RoomDataSource.m @@ -33,6 +33,9 @@ @interface RoomDataSource() )delegate +{ + [self unregisterRoomSummaryDidRemoveExpiredDataFromStoreNotifications]; + [self removeRoomRetentionEventListener]; + + if (delegate && self.isLive) + { + if (self.room) + { + // Remove the potential expired messages from the store + if ([self.room.summary removeExpiredRoomContentsFromStore]) + { + [self.mxSession.store commit]; + } + [self addRoomRetentionEventListener]; + } + + // Observe room history flush (expired content data) + [self registerRoomSummaryDidRemoveExpiredDataFromStoreNotifications]; + [self roomSummaryDidRemoveExpiredDataFromStore]; + } + + [super setDelegate:delegate]; +} + - (void)destroy { if (kThemeServiceDidChangeThemeNotificationObserver) @@ -197,6 +225,9 @@ - (void)destroy [self.mxSession.aggregations.beaconAggregations removeListener:self.beaconInfoSummaryDeletionListener]; } + [self unregisterRoomSummaryDidRemoveExpiredDataFromStoreNotifications]; + [self removeRoomRetentionEventListener]; + [super destroy]; } @@ -1245,4 +1276,79 @@ - (void)updateCurrentUserLocationSharingStatus } } +#pragma mark - roomSummaryDidRemoveExpiredDataFromStore notifications + +- (void)registerRoomSummaryDidRemoveExpiredDataFromStoreNotifications +{ + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(roomSummaryDidRemoveExpiredDataFromStore:) name:MXRoomSummary.roomSummaryDidRemoveExpiredDataFromStore object:nil]; +} + +- (void)unregisterRoomSummaryDidRemoveExpiredDataFromStoreNotifications +{ + [[NSNotificationCenter defaultCenter] removeObserver:self name:MXRoomSummary.roomSummaryDidRemoveExpiredDataFromStore object:nil]; +} + +- (void)roomSummaryDidRemoveExpiredDataFromStore:(NSNotification*)notification +{ + MXRoomSummary *roomSummary = notification.object; + if (self.mxSession == roomSummary.mxSession && [self.roomId isEqualToString:roomSummary.roomId]) + { + [self roomSummaryDidRemoveExpiredDataFromStore]; + } +} + +- (void)roomSummaryDidRemoveExpiredDataFromStore +{ + // Check whether the first cell data refers to an expired event (this may be a state event + MXEvent *firstMessageEvent; + for (id cellData in bubbles) + { + for (MXEvent *event in cellData.events) + { + if (!event.isState) { + firstMessageEvent = event; + break; + } + } + + if (firstMessageEvent) + { + break; + } + } + + if (firstMessageEvent && firstMessageEvent.originServerTs < self.room.summary.minimumTimestamp) + { + [self reload]; + } +} + +#pragma mark - room retention event listener + +- (void)addRoomRetentionEventListener +{ + // Register a listener to handle the room retention in live timelines + retentionListener = [self.timeline listenToEventsOfTypes:@[MXRoomSummary.roomRetentionStateEventType] onEvent:^(MXEvent *redactionEvent, MXTimelineDirection direction, MXRoomState *roomState) { + + // Consider only live events + if (direction == MXTimelineDirectionForwards) + { + // Remove the potential expired messages from the store + if ([self.room.summary removeExpiredRoomContentsFromStore]) + { + [self.mxSession.store commit]; + } + } + }]; +} + +- (void)removeRoomRetentionEventListener +{ + if (retentionListener) + { + [self.timeline removeListener:retentionListener]; + retentionListener = nil; + } +} + @end diff --git a/Riot/Modules/Room/RoomInfo/RoomInfoCoordinator.swift b/Riot/Modules/Room/RoomInfo/RoomInfoCoordinator.swift index 372d246003..53b7ecbab9 100644 --- a/Riot/Modules/Room/RoomInfo/RoomInfoCoordinator.swift +++ b/Riot/Modules/Room/RoomInfo/RoomInfoCoordinator.swift @@ -237,6 +237,9 @@ extension RoomInfoCoordinator: RoomInfoListCoordinatorDelegate { self.delegate?.roomInfoCoordinatorDidLeaveRoom(self) } + func roomInfoListCoordinatorDidRequestReportRoom(_ coordinator: RoomInfoListCoordinatorType) { + self.delegate?.roomInfoCoordinatorDidRequestReportRoom(self) + } } extension RoomInfoCoordinator: RoomParticipantsViewControllerDelegate { diff --git a/Riot/Modules/Room/RoomInfo/RoomInfoCoordinatorBridgePresenter.swift b/Riot/Modules/Room/RoomInfo/RoomInfoCoordinatorBridgePresenter.swift index 39e740bfcc..4484bd0d68 100644 --- a/Riot/Modules/Room/RoomInfo/RoomInfoCoordinatorBridgePresenter.swift +++ b/Riot/Modules/Room/RoomInfo/RoomInfoCoordinatorBridgePresenter.swift @@ -25,6 +25,7 @@ import MatrixSDK func roomInfoCoordinatorBridgePresenterDelegateDidLeaveRoom(_ coordinatorBridgePresenter: RoomInfoCoordinatorBridgePresenter) func roomInfoCoordinatorBridgePresenter(_ coordinatorBridgePresenter: RoomInfoCoordinatorBridgePresenter, didReplaceRoomWithReplacementId roomId: String) func roomInfoCoordinatorBridgePresenter(_ coordinator: RoomInfoCoordinatorBridgePresenter, viewEventInTimeline event: MXEvent) + func roomInfoCoordinatorBridgePresenterDidRequestReportRoom(_ coordinatorBridgePresenter: RoomInfoCoordinatorBridgePresenter) } /// RoomInfoCoordinatorBridgePresenter enables to start RoomInfoCoordinator from a view controller. @@ -131,9 +132,14 @@ extension RoomInfoCoordinatorBridgePresenter: RoomInfoCoordinatorDelegate { func roomInfoCoordinator(_ coordinator: RoomInfoCoordinatorType, didReplaceRoomWithReplacementId roomId: String) { self.delegate?.roomInfoCoordinatorBridgePresenter(self, didReplaceRoomWithReplacementId: roomId) } + func roomInfoCoordinator(_ coordinator: RoomInfoCoordinatorType, viewEventInTimeline event: MXEvent) { self.delegate?.roomInfoCoordinatorBridgePresenter(self, viewEventInTimeline: event) } + + func roomInfoCoordinatorDidRequestReportRoom(_ coordinator: RoomInfoCoordinatorType) { + self.delegate?.roomInfoCoordinatorBridgePresenterDidRequestReportRoom(self) + } } // MARK: - UIAdaptivePresentationControllerDelegate diff --git a/Riot/Modules/Room/RoomInfo/RoomInfoCoordinatorType.swift b/Riot/Modules/Room/RoomInfo/RoomInfoCoordinatorType.swift index 2122ddf1df..077ef7b524 100644 --- a/Riot/Modules/Room/RoomInfo/RoomInfoCoordinatorType.swift +++ b/Riot/Modules/Room/RoomInfo/RoomInfoCoordinatorType.swift @@ -25,6 +25,7 @@ protocol RoomInfoCoordinatorDelegate: AnyObject { func roomInfoCoordinatorDidLeaveRoom(_ coordinator: RoomInfoCoordinatorType) func roomInfoCoordinator(_ coordinator: RoomInfoCoordinatorType, didReplaceRoomWithReplacementId roomId: String) func roomInfoCoordinator(_ coordinator: RoomInfoCoordinatorType, viewEventInTimeline event: MXEvent) + func roomInfoCoordinatorDidRequestReportRoom(_ coordinator: RoomInfoCoordinatorType) } /// `RoomInfoCoordinatorType` is a protocol describing a Coordinator that handle keybackup setup navigation flow. diff --git a/Riot/Modules/Room/RoomInfo/RoomInfoList/RoomInfoListCoordinator.swift b/Riot/Modules/Room/RoomInfo/RoomInfoList/RoomInfoListCoordinator.swift index 6da79ca32c..25131222ba 100644 --- a/Riot/Modules/Room/RoomInfo/RoomInfoList/RoomInfoListCoordinator.swift +++ b/Riot/Modules/Room/RoomInfo/RoomInfoList/RoomInfoListCoordinator.swift @@ -75,4 +75,7 @@ extension RoomInfoListCoordinator: RoomInfoListViewModelCoordinatorDelegate { self.delegate?.roomInfoListCoordinatorDidLeaveRoom(self) } + func roomInfoListViewModelDidRequestReportRoom(_ viewModel: RoomInfoListViewModelType) { + self.delegate?.roomInfoListCoordinatorDidRequestReportRoom(self) + } } diff --git a/Riot/Modules/Room/RoomInfo/RoomInfoList/RoomInfoListCoordinatorType.swift b/Riot/Modules/Room/RoomInfo/RoomInfoList/RoomInfoListCoordinatorType.swift index 592acd67b2..f17102c1ed 100644 --- a/Riot/Modules/Room/RoomInfo/RoomInfoList/RoomInfoListCoordinatorType.swift +++ b/Riot/Modules/Room/RoomInfo/RoomInfoList/RoomInfoListCoordinatorType.swift @@ -22,6 +22,7 @@ protocol RoomInfoListCoordinatorDelegate: AnyObject { func roomInfoListCoordinator(_ coordinator: RoomInfoListCoordinatorType, wantsToNavigateTo target: RoomInfoListTarget) func roomInfoListCoordinatorDidCancel(_ coordinator: RoomInfoListCoordinatorType) func roomInfoListCoordinatorDidLeaveRoom(_ coordinator: RoomInfoListCoordinatorType) + func roomInfoListCoordinatorDidRequestReportRoom(_ coordinator: RoomInfoListCoordinatorType) } /// `RoomInfoListCoordinatorType` is a protocol describing a Coordinator that handle key backup setup passphrase navigation flow. diff --git a/Riot/Modules/Room/RoomInfo/RoomInfoList/RoomInfoListViewAction.swift b/Riot/Modules/Room/RoomInfo/RoomInfoList/RoomInfoListViewAction.swift index 6383d4fb5d..e01bfeefc1 100644 --- a/Riot/Modules/Room/RoomInfo/RoomInfoList/RoomInfoListViewAction.swift +++ b/Riot/Modules/Room/RoomInfo/RoomInfoList/RoomInfoListViewAction.swift @@ -48,4 +48,5 @@ enum RoomInfoListViewAction { case navigate(target: RoomInfoListTarget) case leave case cancel + case report } diff --git a/Riot/Modules/Room/RoomInfo/RoomInfoList/RoomInfoListViewController.swift b/Riot/Modules/Room/RoomInfo/RoomInfoList/RoomInfoListViewController.swift index 93df4d701d..cc11456df0 100644 --- a/Riot/Modules/Room/RoomInfo/RoomInfoList/RoomInfoListViewController.swift +++ b/Riot/Modules/Room/RoomInfo/RoomInfoList/RoomInfoListViewController.swift @@ -228,8 +228,16 @@ final class RoomInfoListViewController: UIViewController { rows: [rowLeave], footer: nil) + let rowReport = Row(type: .destructive, icon: Asset.Images.error.image, text: VectorL10n.roomEventActionReport, accessoryType: .disclosureIndicator) { + self.viewModel.process(viewAction: .report) + } + let sectionReport = Section(header: nil, + rows: [rowReport], + footer: nil) + tmpSections.append(sectionSettings) tmpSections.append(sectionLeave) + tmpSections.append(sectionReport) sections = tmpSections } diff --git a/Riot/Modules/Room/RoomInfo/RoomInfoList/RoomInfoListViewModel.swift b/Riot/Modules/Room/RoomInfo/RoomInfoList/RoomInfoListViewModel.swift index 572754251a..bf2c82e9b4 100644 --- a/Riot/Modules/Room/RoomInfo/RoomInfoList/RoomInfoListViewModel.swift +++ b/Riot/Modules/Room/RoomInfo/RoomInfoList/RoomInfoListViewModel.swift @@ -79,6 +79,8 @@ final class RoomInfoListViewModel: NSObject, RoomInfoListViewModelType { self.leave() case .cancel: self.coordinatorDelegate?.roomInfoListViewModelDidCancel(self) + case .report: + self.coordinatorDelegate?.roomInfoListViewModelDidRequestReportRoom(self) } } diff --git a/Riot/Modules/Room/RoomInfo/RoomInfoList/RoomInfoListViewModelType.swift b/Riot/Modules/Room/RoomInfo/RoomInfoList/RoomInfoListViewModelType.swift index 8a5a1b411a..460c33d511 100644 --- a/Riot/Modules/Room/RoomInfo/RoomInfoList/RoomInfoListViewModelType.swift +++ b/Riot/Modules/Room/RoomInfo/RoomInfoList/RoomInfoListViewModelType.swift @@ -26,6 +26,7 @@ protocol RoomInfoListViewModelCoordinatorDelegate: AnyObject { func roomInfoListViewModelDidCancel(_ viewModel: RoomInfoListViewModelType) func roomInfoListViewModelDidLeaveRoom(_ viewModel: RoomInfoListViewModelType) func roomInfoListViewModel(_ viewModel: RoomInfoListViewModelType, wantsToNavigateTo target: RoomInfoListTarget) + func roomInfoListViewModelDidRequestReportRoom(_ viewModel: RoomInfoListViewModelType) } /// Protocol describing the view model used by `RoomInfoListViewController` diff --git a/Riot/Modules/Room/RoomViewController.m b/Riot/Modules/Room/RoomViewController.m index b93c1e4ac9..aa225a00e7 100644 --- a/Riot/Modules/Room/RoomViewController.m +++ b/Riot/Modules/Room/RoomViewController.m @@ -5412,6 +5412,50 @@ - (IBAction)onButtonPressed:(id)sender } } +- (void)handleReportRoom +{ + // Prompt user to enter a description of the problem content. + UIAlertController *reportReasonAlert = [UIAlertController alertControllerWithTitle:[VectorL10n roomActionReportPromptReason] + message:nil + preferredStyle:UIAlertControllerStyleAlert]; + + [reportReasonAlert addTextFieldWithConfigurationHandler:^(UITextField *textField) { + textField.secureTextEntry = NO; + textField.placeholder = nil; + textField.keyboardType = UIKeyboardTypeDefault; + }]; + + MXWeakify(self); + [reportReasonAlert addAction:[UIAlertAction actionWithTitle:[VectorL10n ok] style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { + MXStrongifyAndReturnIfNil(self); + + NSString *text = [self->currentAlert textFields].firstObject.text; + self->currentAlert = nil; + + [self startActivityIndicator]; + + [self.roomDataSource.mxSession.matrixRestClient reportRoom:self.roomDataSource.roomId reason:text success:^{ + MXStrongifyAndReturnIfNil(self); + [self stopActivityIndicator]; + } failure:^(NSError *error) { + MXStrongifyAndReturnIfNil(self); + [self stopActivityIndicator]; + + MXLogDebug(@"[RoomVC] Report room (%@) failed", self.roomDataSource.roomId); + //Alert user + [self showError:error]; + }]; + }]]; + + [reportReasonAlert addAction:[UIAlertAction actionWithTitle:[VectorL10n cancel] style:UIAlertActionStyleCancel handler:^(UIAlertAction * action) { + MXStrongifyAndReturnIfNil(self); + self->currentAlert = nil; + }]]; + + [self presentViewController:reportReasonAlert animated:YES completion:nil]; + self->currentAlert = reportReasonAlert; +} + #pragma mark - UITableViewDelegate - (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath @@ -5657,6 +5701,10 @@ - (void)roomTitleView:(RoomTitleView*)titleView recognizeTapGesture:(UITapGestur { [self presentDeclineOptionsFromView:tappedView]; } + else if (tappedView == previewHeader.reportButton) + { + [self handleReportRoom]; + } } - (void)presentDeclineOptionsFromView:(UIView *)view @@ -8042,6 +8090,11 @@ - (void)roomInfoCoordinatorBridgePresenter:(RoomInfoCoordinatorBridgePresenter * [self reloadRoomWihtEventId:event.eventId threadId:event.threadId forceUpdateRoomMarker:NO]; } +- (void)roomInfoCoordinatorBridgePresenterDidRequestReportRoom:(RoomInfoCoordinatorBridgePresenter *)coordinatorBridgePresenter +{ + [self handleReportRoom]; +} + -(void)reloadRoomWihtEventId:(NSString *)eventId threadId:(NSString *)threadId forceUpdateRoomMarker:(BOOL)forceUpdateRoomMarker diff --git a/Riot/Modules/Room/Views/Title/Preview/PreviewRoomTitleView.h b/Riot/Modules/Room/Views/Title/Preview/PreviewRoomTitleView.h index 79fdde1bb1..d8bcfb086c 100644 --- a/Riot/Modules/Room/Views/Title/Preview/PreviewRoomTitleView.h +++ b/Riot/Modules/Room/Views/Title/Preview/PreviewRoomTitleView.h @@ -32,6 +32,7 @@ @property (weak, nonatomic) IBOutlet UIView *buttonsContainer; @property (weak, nonatomic) IBOutlet UIButton *leftButton; @property (weak, nonatomic) IBOutlet UIButton *rightButton; +@property (weak, nonatomic) IBOutlet UIButton *reportButton; @property (weak, nonatomic) IBOutlet UILabel *subNoticeLabel; @property (weak, nonatomic) IBOutlet UIView *bottomBorderView; diff --git a/Riot/Modules/Room/Views/Title/Preview/PreviewRoomTitleView.m b/Riot/Modules/Room/Views/Title/Preview/PreviewRoomTitleView.m index 7e8456a5b9..35fdbab129 100644 --- a/Riot/Modules/Room/Views/Title/Preview/PreviewRoomTitleView.m +++ b/Riot/Modules/Room/Views/Title/Preview/PreviewRoomTitleView.m @@ -51,12 +51,22 @@ - (void)awakeFromNib [self.rightButton setTitle:[VectorL10n join] forState:UIControlStateNormal]; [self.rightButton setTitle:[VectorL10n join] forState:UIControlStateHighlighted]; + [self.reportButton setTitle:[VectorL10n roomActionReport] forState:UIControlStateNormal]; + [self.reportButton setTitle:[VectorL10n roomActionReport] forState:UIControlStateHighlighted]; + tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(reportTapGesture:)]; [tap setNumberOfTouchesRequired:1]; [tap setNumberOfTapsRequired:1]; [tap setDelegate:self]; [self.rightButton addGestureRecognizer:tap]; self.rightButton.userInteractionEnabled = YES; + + tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(reportTapGesture:)]; + [tap setNumberOfTouchesRequired:1]; + [tap setNumberOfTapsRequired:1]; + [tap setDelegate:self]; + [self.reportButton addGestureRecognizer:tap]; + self.reportButton.userInteractionEnabled = YES; } -(void)customizeViewRendering @@ -86,6 +96,8 @@ -(void)customizeViewRendering [self.rightButton.layer setCornerRadius:5]; self.rightButton.clipsToBounds = YES; self.rightButton.backgroundColor = ThemeService.shared.theme.tintColor; + + [self.reportButton setTitleColor:ThemeService.shared.theme.warningColor forState:UIControlStateNormal]; } - (void)refreshDisplay diff --git a/Riot/Modules/Room/Views/Title/Preview/PreviewRoomTitleView.xib b/Riot/Modules/Room/Views/Title/Preview/PreviewRoomTitleView.xib index 31fcca2736..873ff88e21 100644 --- a/Riot/Modules/Room/Views/Title/Preview/PreviewRoomTitleView.xib +++ b/Riot/Modules/Room/Views/Title/Preview/PreviewRoomTitleView.xib @@ -1,25 +1,21 @@ - - - - + + - - - + - + - + @@ -38,7 +34,7 @@ - + @@ -48,9 +44,9 @@ - + - + @@ -58,11 +54,11 @@ - + - - - + @@ -154,15 +158,17 @@ - + + + @@ -187,6 +193,7 @@ + @@ -194,9 +201,16 @@ + - + + + + + + + diff --git a/Riot/Modules/Settings/SettingsViewController.m b/Riot/Modules/Settings/SettingsViewController.m index 23151dbe15..05198da50f 100644 --- a/Riot/Modules/Settings/SettingsViewController.m +++ b/Riot/Modules/Settings/SettingsViewController.m @@ -169,8 +169,7 @@ typedef NS_ENUM(NSUInteger, PREFERENCES) typedef NS_ENUM(NSUInteger, ADVANCED) { - ADVANCED_SHOW_NSFW_ROOMS_INDEX = 0, - ADVANCED_CRASH_REPORT_INDEX, + ADVANCED_CRASH_REPORT_INDEX = 0, ADVANCED_ENABLE_RAGESHAKE_INDEX, ADVANCED_MARK_ALL_AS_READ_INDEX, ADVANCED_CLEAR_CACHE_INDEX, @@ -625,11 +624,6 @@ - (void)updateSections Section *sectionAdvanced = [Section sectionWithTag:SECTION_TAG_ADVANCED]; sectionAdvanced.headerTitle = [VectorL10n settingsAdvanced]; - if (RiotSettings.shared.settingsScreenShowNsfwRoomsOption) - { - [sectionAdvanced addRowWithTag:ADVANCED_SHOW_NSFW_ROOMS_INDEX]; - } - if (BuildSettings.settingsScreenAllowChangingCrashUsageDataSettings) { [sectionAdvanced addRowWithTag:ADVANCED_CRASH_REPORT_INDEX]; @@ -2541,20 +2535,7 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N } else if (section == SECTION_TAG_ADVANCED) { - if (row == ADVANCED_SHOW_NSFW_ROOMS_INDEX) - { - MXKTableViewCellWithLabelAndSwitch* labelAndSwitchCell = [self getLabelAndSwitchCell:tableView forIndexPath:indexPath]; - - labelAndSwitchCell.mxkLabel.text = [VectorL10n settingsShowNSFWPublicRooms]; - - labelAndSwitchCell.mxkSwitch.on = RiotSettings.shared.showNSFWPublicRooms; - labelAndSwitchCell.mxkSwitch.onTintColor = ThemeService.shared.theme.tintColor; - labelAndSwitchCell.mxkSwitch.enabled = YES; - [labelAndSwitchCell.mxkSwitch addTarget:self action:@selector(toggleNSFWPublicRoomsFiltering:) forControlEvents:UIControlEventTouchUpInside]; - - cell = labelAndSwitchCell; - } - else if (row == ADVANCED_CRASH_REPORT_INDEX) + if (row == ADVANCED_CRASH_REPORT_INDEX) { MXKTableViewCellWithLabelAndSwitch* sendCrashReportCell = [self getLabelAndSwitchCell:tableView forIndexPath:indexPath]; @@ -4413,11 +4394,6 @@ - (void)togglePresenceOfflineMode:(UISwitch *)sender } } -- (void)toggleNSFWPublicRoomsFiltering:(UISwitch *)sender -{ - RiotSettings.shared.showNSFWPublicRooms = sender.isOn; -} - - (void)toggleEnableRoomMessageBubbles:(UISwitch *)sender { RiotSettings.shared.roomScreenEnableMessageBubbles = sender.isOn; diff --git a/Riot/Modules/Spaces/SpaceRoomList/ExploreRoomCoordinator.swift b/Riot/Modules/Spaces/SpaceRoomList/ExploreRoomCoordinator.swift index 24125ae49c..a728d573c3 100644 --- a/Riot/Modules/Spaces/SpaceRoomList/ExploreRoomCoordinator.swift +++ b/Riot/Modules/Spaces/SpaceRoomList/ExploreRoomCoordinator.swift @@ -519,8 +519,12 @@ extension ExploreRoomCoordinator: RoomInfoCoordinatorDelegate { self.remove(childCoordinator: coordinator) } } + func roomInfoCoordinator(_ coordinator: RoomInfoCoordinatorType, viewEventInTimeline event: MXEvent) { } + func roomInfoCoordinatorDidRequestReportRoom(_ coordinator: RoomInfoCoordinatorType) { + + } } diff --git a/Riot/SupportingFiles/forbidden_terms.txt b/Riot/SupportingFiles/forbidden_terms.txt new file mode 100644 index 0000000000..ff22aa40fc --- /dev/null +++ b/Riot/SupportingFiles/forbidden_terms.txt @@ -0,0 +1,68 @@ +anal +bbw +bdsm +beast +bestiality +blowjob +bondage +boobs +clit +cock +cuck +cum +cunt +daddy +dick +dildo +erotic +exhibitionism +faggot +femboy +fisting +flogging +fmf +foursome +futa +gangbang +gore +h3ntai +handjob +hentai +incest +jizz +kink +loli +m4f +masturbate +masturbation +mfm +milf +moresome +naked +neet +nsfw +nude +nudity +orgy +pedo +pegging +penis +petplay +porn +pussy +rape +rimming +sadism +sadomasochism +sexy +shota +spank +squirt +strap-on +threesome +vagina +vibrator +voyeur +watersports +xxx +zoo \ No newline at end of file diff --git a/Riot/Utils/EventFormatter.m b/Riot/Utils/EventFormatter.m index 55c05905ad..17ab314fd1 100644 --- a/Riot/Utils/EventFormatter.m +++ b/Riot/Utils/EventFormatter.m @@ -659,10 +659,17 @@ - (MXEvent *)voiceBroadcastInfoStartedEventWithEvent:(MXEvent *)voiceBroadcastIn - (BOOL)session:(MXSession *)session updateRoomSummary:(MXRoomSummary *)summary withStateEvents:(NSArray *)stateEvents roomState:(MXRoomState *)roomState { BOOL updated = [super session:session updateRoomSummary:summary withStateEvents:stateEvents roomState:roomState]; - + + MXEvent* lastRoomRetentionEvent = [self roomRetentionEventFromStateEvents:stateEvents]; + if (lastRoomRetentionEvent) + { + summary.others[MXRoomSummary.roomRetentionMaxLifetime] = lastRoomRetentionEvent.content[MXRoomSummary.roomRetentionEventMaxLifetimeKey]; + updated = YES; + } + // Store in the room summary some additional information updated |= [summary tc_updateWithStateEvents:stateEvents]; - + // Customisation for EMS Functional Members in direct rooms if (BuildSettings.supportFunctionalMembers && summary.room.isDirect) { @@ -675,7 +682,7 @@ - (BOOL)session:(MXSession *)session updateRoomSummary:(MXRoomSummary *)summary // room name which we'll do twice more in updateRoomSummary:withServerRoomSummary:roomState: anyway. // // So return YES and let that happen there. - return YES; + updated = YES; } } @@ -831,6 +838,12 @@ - (MXEvent *)functionalMembersEventFromStateEvents:(NSArray *)stateEv return [stateEvents filteredArrayUsingPredicate:functionalMembersPredicate].lastObject; } +- (MXEvent *)roomRetentionEventFromStateEvents:(NSArray *)stateEvents +{ + NSPredicate *functionalMembersPredicate = [NSPredicate predicateWithFormat:@"type == %@", kMXEventTypeStringRoomRetention]; + return [stateEvents filteredArrayUsingPredicate:functionalMembersPredicate].lastObject; +} + #pragma mark - Timestamp formatting - (NSString*)dateStringFromDate:(NSDate *)date withTime:(BOOL)time diff --git a/Riot/target.yml b/Riot/target.yml index 7384ed9305..8cc7e87a6e 100644 --- a/Riot/target.yml +++ b/Riot/target.yml @@ -49,6 +49,7 @@ targets: - package: WysiwygComposer - package: DeviceKit - package: DTCoreText + - package: PostHog configFiles: Debug: Debug.xcconfig diff --git a/RiotNSE/BuildSettings.swift b/RiotNSE/BuildSettings.swift index ce637a7d40..2911cd2259 100644 --- a/RiotNSE/BuildSettings.swift +++ b/RiotNSE/BuildSettings.swift @@ -272,8 +272,25 @@ final class BuildSettings: NSObject { tchapFeatureNotificationByEmail: [ tchapFeatureAnyHomeServer ], + // Audio calls for all except Finances in Tchap Production. tchapFeatureVoiceOverIP: [ - "agent.dinum.tchap.gouv.fr" + "agent.externe.tchap.gouv.fr", + "agent.collectivites.tchap.gouv.fr", + "agent.tchap.gouv.fr", + "agent.elysee.tchap.gouv.fr", + "agent.pm.tchap.gouv.fr", + "agent.ssi.tchap.gouv.fr", +// "agent.finances.tchap.gouv.fr", + "agent.social.tchap.gouv.fr", + "agent.interieur.tchap.gouv.fr", + "agent.agriculture.tchap.gouv.fr", + "agent.justice.tchap.gouv.fr", + "agent.diplomatie.tchap.gouv.fr", + "agent.intradef.tchap.gouv.fr", + "agent.dinum.tchap.gouv.fr", + "agent.culture.tchap.gouv.fr", + "agent.dev-durable.tchap.gouv.fr", + "agent.education.tchap.gouv.fr" ], // No activation of video calls actually in Tchap Production. // tchapFeatureVideoOverIP: [ diff --git a/RiotNSE/target.yml b/RiotNSE/target.yml index 70394a3ff2..341955186d 100644 --- a/RiotNSE/target.yml +++ b/RiotNSE/target.yml @@ -35,6 +35,7 @@ targets: - package: AnalyticsEvents - package: DeviceKit - package: DTCoreText + - package: PostHog configFiles: Debug: Debug.xcconfig diff --git a/RiotShareExtension/BuildSettings.swift b/RiotShareExtension/BuildSettings.swift index ce637a7d40..2911cd2259 100644 --- a/RiotShareExtension/BuildSettings.swift +++ b/RiotShareExtension/BuildSettings.swift @@ -272,8 +272,25 @@ final class BuildSettings: NSObject { tchapFeatureNotificationByEmail: [ tchapFeatureAnyHomeServer ], + // Audio calls for all except Finances in Tchap Production. tchapFeatureVoiceOverIP: [ - "agent.dinum.tchap.gouv.fr" + "agent.externe.tchap.gouv.fr", + "agent.collectivites.tchap.gouv.fr", + "agent.tchap.gouv.fr", + "agent.elysee.tchap.gouv.fr", + "agent.pm.tchap.gouv.fr", + "agent.ssi.tchap.gouv.fr", +// "agent.finances.tchap.gouv.fr", + "agent.social.tchap.gouv.fr", + "agent.interieur.tchap.gouv.fr", + "agent.agriculture.tchap.gouv.fr", + "agent.justice.tchap.gouv.fr", + "agent.diplomatie.tchap.gouv.fr", + "agent.intradef.tchap.gouv.fr", + "agent.dinum.tchap.gouv.fr", + "agent.culture.tchap.gouv.fr", + "agent.dev-durable.tchap.gouv.fr", + "agent.education.tchap.gouv.fr" ], // No activation of video calls actually in Tchap Production. // tchapFeatureVideoOverIP: [ diff --git a/RiotShareExtension/target.yml b/RiotShareExtension/target.yml index b9399f787d..3d8224d84f 100644 --- a/RiotShareExtension/target.yml +++ b/RiotShareExtension/target.yml @@ -35,6 +35,7 @@ targets: - package: AnalyticsEvents - package: DeviceKit - package: DTCoreText + - package: PostHog configFiles: Debug: Debug.xcconfig diff --git a/RiotTests/AnalyticsTests.swift b/RiotTests/AnalyticsTests.swift index 33ed283895..a8759c0176 100644 --- a/RiotTests/AnalyticsTests.swift +++ b/RiotTests/AnalyticsTests.swift @@ -78,7 +78,7 @@ class AnalyticsTests: XCTestCase { XCTAssertNil(client.pendingUserProperties, "No user properties should have been set yet.") // When updating the user properties - client.updateUserProperties(AnalyticsEvent.UserProperties(allChatsActiveFilter: nil, ftueUseCaseSelection: .PersonalMessaging, numFavouriteRooms: 4, numSpaces: 5)) + client.updateUserProperties(AnalyticsEvent.UserProperties(allChatsActiveFilter: nil, ftueUseCaseSelection: .PersonalMessaging, numFavouriteRooms: 4, numSpaces: 5, recoveryState: nil, verificationState: nil)) // Then the properties should be cached XCTAssertNotNil(client.pendingUserProperties, "The user properties should be cached.") @@ -90,7 +90,7 @@ class AnalyticsTests: XCTestCase { func testMergingUserProperties() { // Given a client with a cached use case user properties let client = PostHogAnalyticsClient() - client.updateUserProperties(AnalyticsEvent.UserProperties(allChatsActiveFilter: nil, ftueUseCaseSelection: .PersonalMessaging, numFavouriteRooms: nil, numSpaces: nil)) + client.updateUserProperties(AnalyticsEvent.UserProperties(allChatsActiveFilter: nil, ftueUseCaseSelection: .PersonalMessaging, numFavouriteRooms: nil, numSpaces: nil, recoveryState: nil, verificationState: nil)) XCTAssertNotNil(client.pendingUserProperties, "The user properties should be cached.") XCTAssertEqual(client.pendingUserProperties?.ftueUseCaseSelection, .PersonalMessaging, "The use case selection should match.") @@ -98,7 +98,7 @@ class AnalyticsTests: XCTestCase { XCTAssertNil(client.pendingUserProperties?.numSpaces, "The number of spaces should not be set.") // When updating the number of spaces - client.updateUserProperties(AnalyticsEvent.UserProperties(allChatsActiveFilter: nil, ftueUseCaseSelection: nil, numFavouriteRooms: 4, numSpaces: 5)) + client.updateUserProperties(AnalyticsEvent.UserProperties(allChatsActiveFilter: nil, ftueUseCaseSelection: nil, numFavouriteRooms: 4, numSpaces: 5, recoveryState: nil, verificationState: nil)) // Then the new properties should be updated and the existing properties should remain unchanged XCTAssertNotNil(client.pendingUserProperties, "The user properties should be cached.") @@ -107,7 +107,7 @@ class AnalyticsTests: XCTestCase { XCTAssertEqual(client.pendingUserProperties?.numSpaces, 5, "The number of spaces should have been updated.") // When updating the number of spaces - client.updateUserProperties(AnalyticsEvent.UserProperties(allChatsActiveFilter: .Favourites, ftueUseCaseSelection: nil, numFavouriteRooms: nil, numSpaces: nil)) + client.updateUserProperties(AnalyticsEvent.UserProperties(allChatsActiveFilter: .Favourites, ftueUseCaseSelection: nil, numFavouriteRooms: nil, numSpaces: nil, recoveryState: nil, verificationState: nil)) // Then the new properties should be updated and the existing properties should remain unchanged XCTAssertNotNil(client.pendingUserProperties, "The user properties should be cached.") @@ -120,14 +120,15 @@ class AnalyticsTests: XCTestCase { func testSendingUserProperties() { // Given a client with user properties set let client = PostHogAnalyticsClient() - client.updateUserProperties(AnalyticsEvent.UserProperties(allChatsActiveFilter: nil, ftueUseCaseSelection: .PersonalMessaging, numFavouriteRooms: nil, numSpaces: nil)) + client.updateUserProperties(AnalyticsEvent.UserProperties(allChatsActiveFilter: nil, ftueUseCaseSelection: .PersonalMessaging, numFavouriteRooms: nil, numSpaces: nil, recoveryState: nil, verificationState: nil)) client.start() XCTAssertNotNil(client.pendingUserProperties, "The user properties should be cached.") XCTAssertEqual(client.pendingUserProperties?.ftueUseCaseSelection, .PersonalMessaging, "The use case selection should match.") // When sending an event (tests run under Debug configuration so this is sent to the development instance) - client.screen(AnalyticsEvent.MobileScreen(durationMs: nil, screenName: .Home)) + let event = AnalyticsEvent.Signup(authenticationType: .Other) + client.capture(event) // Then the properties should be cleared XCTAssertNil(client.pendingUserProperties, "The user properties should be cleared.") @@ -136,7 +137,7 @@ class AnalyticsTests: XCTestCase { func testSendingUserPropertiesWithIdentify() { // Given a client with user properties set let client = PostHogAnalyticsClient() - client.updateUserProperties(AnalyticsEvent.UserProperties(allChatsActiveFilter: nil, ftueUseCaseSelection: .PersonalMessaging, numFavouriteRooms: nil, numSpaces: nil)) + client.updateUserProperties(AnalyticsEvent.UserProperties(allChatsActiveFilter: nil, ftueUseCaseSelection: .PersonalMessaging, numFavouriteRooms: nil, numSpaces: nil, recoveryState: nil, verificationState: nil)) client.start() XCTAssertNotNil(client.pendingUserProperties, "The user properties should be cached.") diff --git a/RiotTests/FakeUtils.swift b/RiotTests/FakeUtils.swift index 6077a449ef..f4649e9d8b 100644 --- a/RiotTests/FakeUtils.swift +++ b/RiotTests/FakeUtils.swift @@ -15,6 +15,8 @@ // import Foundation +import PostHog +@testable import Element class FakeEvent: MXEvent { @@ -346,3 +348,132 @@ class FakeKeyVerificationManager: NSObject, MXKeyVerificationManager { } } + +class MockPostHog: PostHogProtocol { + + private var enabled = false + + func optIn() { + enabled = true + } + + func optOut() { + enabled = false + } + + func reset() { } + + func flush() { + + } + + var capturePropertiesUserPropertiesUnderlyingCallsCount = 0 + var capturePropertiesUserPropertiesCallsCount: Int { + get { + if Thread.isMainThread { + return capturePropertiesUserPropertiesUnderlyingCallsCount + } else { + var returnValue: Int? = nil + DispatchQueue.main.sync { + returnValue = capturePropertiesUserPropertiesUnderlyingCallsCount + } + + return returnValue! + } + } + set { + if Thread.isMainThread { + capturePropertiesUserPropertiesUnderlyingCallsCount = newValue + } else { + DispatchQueue.main.sync { + capturePropertiesUserPropertiesUnderlyingCallsCount = newValue + } + } + } + } + var capturePropertiesUserPropertiesCalled: Bool { + return capturePropertiesUserPropertiesCallsCount > 0 + } + var capturePropertiesUserPropertiesReceivedArguments: (event: String, properties: [String: Any]?, userProperties: [String: Any]?)? + var capturePropertiesUserPropertiesReceivedInvocations: [(event: String, properties: [String: Any]?, userProperties: [String: Any]?)] = [] + var capturePropertiesUserPropertiesClosure: ((String, [String: Any]?, [String: Any]?) -> Void)? + + func capture(_ event: String, properties: [String: Any]?, userProperties: [String: Any]?) { + if !enabled { return } + capturePropertiesUserPropertiesCallsCount += 1 + capturePropertiesUserPropertiesReceivedArguments = (event: event, properties: properties, userProperties: userProperties) + capturePropertiesUserPropertiesReceivedInvocations.append((event: event, properties: properties, userProperties: userProperties)) + capturePropertiesUserPropertiesClosure?(event, properties, userProperties) + } + + var screenPropertiesUnderlyingCallsCount = 0 + var screenPropertiesCallsCount: Int { + get { + if Thread.isMainThread { + return screenPropertiesUnderlyingCallsCount + } else { + var returnValue: Int? = nil + DispatchQueue.main.sync { + returnValue = screenPropertiesUnderlyingCallsCount + } + + return returnValue! + } + } + set { + if Thread.isMainThread { + screenPropertiesUnderlyingCallsCount = newValue + } else { + DispatchQueue.main.sync { + screenPropertiesUnderlyingCallsCount = newValue + } + } + } + } + + var screenPropertiesCalled: Bool { + return screenPropertiesCallsCount > 0 + } + var screenPropertiesReceivedArguments: (screenTitle: String, properties: [String: Any]?)? + var screenPropertiesReceivedInvocations: [(screenTitle: String, properties: [String: Any]?)] = [] + var screenPropertiesClosure: ((String, [String: Any]?) -> Void)? + + func screen(_ screenTitle: String, properties: [String: Any]?) { + if !enabled { return } + screenPropertiesCallsCount += 1 + screenPropertiesReceivedArguments = (screenTitle: screenTitle, properties: properties) + screenPropertiesReceivedInvocations.append((screenTitle: screenTitle, properties: properties)) + screenPropertiesClosure?(screenTitle, properties) + } + + func isFeatureEnabled(_ feature: String) -> Bool { + return true + } + + func identify(_ distinctId: String) { + + } + + func identify(_ distinctId: String, userProperties: [String : Any]?) { + + } + + func isOptOut() -> Bool { + !enabled + } + + +} + +class MockPostHogFactory: PostHogFactory { + var mock: PostHogProtocol! + + init(mock: PostHogProtocol) { + self.mock = mock + } + + func createPostHog(config: PostHogConfig) -> PostHogProtocol { + mock + } +} + diff --git a/RiotTests/PostHogAnalyticsClientTests.swift b/RiotTests/PostHogAnalyticsClientTests.swift new file mode 100644 index 0000000000..081c54688b --- /dev/null +++ b/RiotTests/PostHogAnalyticsClientTests.swift @@ -0,0 +1,113 @@ +// +// Copyright 2024 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 XCTest +@testable import Element +import AnalyticsEvents + +class PostHogAnalyticsClientTests: XCTestCase { + + private var posthogMock: MockPostHog! + + override func setUp() { + posthogMock = MockPostHog() + } + + func testSuperPropertiesAddedToAllCaptured() { + let analyticsClient = PostHogAnalyticsClient(posthogFactory: MockPostHogFactory(mock: posthogMock)) + analyticsClient.start() + + let superProperties = AnalyticsEvent.SuperProperties(appPlatform: .EI, cryptoSDK: .Rust, cryptoSDKVersion: "0.0") + + analyticsClient.updateSuperProperties(superProperties) + // It should be the same for any event + let someEvent = AnalyticsEvent.CallEnded(durationMs: 0, isVideo: false, numParticipants: 1, placed: true) + analyticsClient.capture(someEvent) + + let capturedEvent = posthogMock.capturePropertiesUserPropertiesReceivedArguments + + // All the super properties should have been added + XCTAssertEqual(capturedEvent?.properties?["cryptoSDK"] as? String, AnalyticsEvent.SuperProperties.CryptoSDK.Rust.rawValue) + XCTAssertEqual(capturedEvent?.properties?["appPlatform"] as? String, AnalyticsEvent.SuperProperties.AppPlatform.EI.rawValue) + XCTAssertEqual(capturedEvent?.properties?["cryptoSDKVersion"] as? String, "0.0") + + // Other properties should be there + XCTAssertEqual(capturedEvent?.properties?["isVideo"] as? Bool, false) + + // Should also work for screens + + analyticsClient.screen(AnalyticsEvent.MobileScreen.init(durationMs: 0, screenName: .Home)) + + + let capturedScreen = posthogMock.screenPropertiesReceivedArguments + + + XCTAssertEqual(capturedScreen?.properties?["cryptoSDK"] as? String, AnalyticsEvent.SuperProperties.CryptoSDK.Rust.rawValue) + XCTAssertEqual(capturedScreen?.properties?["appPlatform"] as? String, AnalyticsEvent.SuperProperties.AppPlatform.EI.rawValue) + XCTAssertEqual(capturedScreen?.properties?["cryptoSDKVersion"] as? String, "0.0") + + + XCTAssertEqual(capturedScreen?.screenTitle, AnalyticsEvent.MobileScreen.ScreenName.Home.rawValue) + + + } + + func testSuperPropertiesCanBeUdpated() { + let analyticsClient = PostHogAnalyticsClient(posthogFactory: MockPostHogFactory(mock: posthogMock)) + analyticsClient.start() + + let superProperties = AnalyticsEvent.SuperProperties(appPlatform: .EI, cryptoSDK: .Rust, cryptoSDKVersion: "0.0") + + analyticsClient.updateSuperProperties(superProperties) + // It should be the same for any event + let someEvent = AnalyticsEvent.CallEnded(durationMs: 0, isVideo: false, numParticipants: 1, placed: true) + analyticsClient.capture(someEvent) + + let capturedEvent = posthogMock.capturePropertiesUserPropertiesReceivedArguments + + // + XCTAssertEqual(capturedEvent?.properties?["cryptoSDKVersion"] as? String, "0.0") + + analyticsClient.updateSuperProperties(AnalyticsEvent.SuperProperties(appPlatform: .EI, cryptoSDK: .Rust, cryptoSDKVersion: "1.0")) + + + analyticsClient.capture(someEvent) + + let secondCapturedEvent = posthogMock.capturePropertiesUserPropertiesReceivedArguments + + XCTAssertEqual(secondCapturedEvent?.properties?["cryptoSDKVersion"] as? String, "1.0") + } + + func testSuperPropertiesDontOverrideEventProperties() { + let analyticsClient = PostHogAnalyticsClient(posthogFactory: MockPostHogFactory(mock: posthogMock)) + analyticsClient.start() + + // Super property for cryptoSDK is rust + let superProperties = AnalyticsEvent.SuperProperties(appPlatform: nil, cryptoSDK: .Rust, cryptoSDKVersion: nil) + + analyticsClient.updateSuperProperties(superProperties) + + // This event as a similar named property `cryptoSDK` with Legacy value + let someEvent = AnalyticsEvent.Error(context: nil, cryptoModule: nil, cryptoSDK: .Legacy, domain: .E2EE, eventLocalAgeMillis: nil, isFederated: nil, isMatrixDotOrg: nil, name: .OlmKeysNotSentError, timeToDecryptMillis: nil, userTrustsOwnIdentity: nil, wasVisibleToUser: nil) + + analyticsClient.capture(someEvent) + + let capturedEvent = posthogMock.capturePropertiesUserPropertiesReceivedArguments + + XCTAssertEqual(capturedEvent?.properties?["cryptoSDK"] as? String, AnalyticsEvent.Error.CryptoSDK.Legacy.rawValue) + } + +} diff --git a/Tchap/Extensions/MXRoomSummary.swift b/Tchap/Extensions/MXRoomSummary+Tchap.swift similarity index 100% rename from Tchap/Extensions/MXRoomSummary.swift rename to Tchap/Extensions/MXRoomSummary+Tchap.swift diff --git a/Tchap/Generated/Strings.swift b/Tchap/Generated/Strings.swift index 9f27d360de..0377110b34 100644 --- a/Tchap/Generated/Strings.swift +++ b/Tchap/Generated/Strings.swift @@ -275,19 +275,31 @@ public class TchapL10n: NSObject { public static var eventFormatterReportIncident: String { return TchapL10n.tr("Tchap", "event_formatter_report_incident") } - /// La durée de validité de votre compte a expiré. Un email vous a été envoyé pour la renouveler. Une fois que vous aurez suivi le lien qu’il contient, cliquez ci-dessous. + /// Un email vous a été envoyé pour renouveler votre compte. Une fois que vous aurez suivi le lien qu’il contient, cliquez ci-dessous. public static var expiredAccountAlertMessage: String { return TchapL10n.tr("Tchap", "expired_account_alert_message") } - /// Un nouvel email vous a été envoyé pour renouveler la validité de votre compte. Une fois que vous aurez suivi le lien qu’il contient, cliquez ci-dessous. - public static var expiredAccountOnNewSentEmailMsg: String { - return TchapL10n.tr("Tchap", "expired_account_on_new_sent_email_msg") + /// Votre compte a expiré + public static var expiredAccountAlertTitle: String { + return TchapL10n.tr("Tchap", "expired_account_alert_title") } - /// Demander l’envoi d’un nouvel email + /// Continuer + public static var expiredAccountOnNewSentEmailButton: String { + return TchapL10n.tr("Tchap", "expired_account_on_new_sent_email_button") + } + /// Un nouvel email vous a été envoyé pour renouveler votre compte. Une fois que vous aurez suivi le lien qu’il contient, cliquez ci-dessous. + public static var expiredAccountOnNewSentEmailMessage: String { + return TchapL10n.tr("Tchap", "expired_account_on_new_sent_email_message") + } + /// Email envoyé + public static var expiredAccountOnNewSentEmailTitle: String { + return TchapL10n.tr("Tchap", "expired_account_on_new_sent_email_title") + } + /// Emvoyer un nouvel email public static var expiredAccountRequestRenewalEmailButton: String { return TchapL10n.tr("Tchap", "expired_account_request_renewal_email_button") } - /// J’ai renouvelé mon compte + /// Continuer public static var expiredAccountResumeButton: String { return TchapL10n.tr("Tchap", "expired_account_resume_button") } @@ -591,7 +603,7 @@ public class TchapL10n: NSObject { public static var roomFilesTabTitle: String { return TchapL10n.tr("Tchap", "room_files_tab_title") } - /// Cet utilisateur n'est pas autorisé à rejoindre ce salon. + /// Cet utilisateur est déjà membre du salon ou n'est pas autorisé à le rejoindre. public static var roomInviteErrorActionForbidden: String { return TchapL10n.tr("Tchap", "room_invite_error_action_forbidden") } @@ -615,6 +627,14 @@ public class TchapL10n: NSObject { public static var roomMembersTabTitle: String { return TchapL10n.tr("Tchap", "room_members_tab_title") } + /// Le fichier est trop lourd pour être envoyé. La taille limite est de %ldMo, mais la taille de votre fichier est de %ldMo. + public static func roomSendFileTooBigMessage(_ p1: Int, _ p2: Int) -> String { + return TchapL10n.tr("Tchap", "room_send_file_too_big_message", p1, p2) + } + /// Erreur d'envoi + public static var roomSendFileTooBigTitle: String { + return TchapL10n.tr("Tchap", "room_send_file_too_big_title") + } /// Ce changement n’est pas supporté actuellement car le salon est accessible par lien. Il sera supporté prochainement public static var roomSettingsAllowExternalUsersForbidden: String { return TchapL10n.tr("Tchap", "room_settings_allow_external_users_forbidden") @@ -795,6 +815,14 @@ public class TchapL10n: NSObject { public static var settingsCryptoImportInvalidFile: String { return TchapL10n.tr("Tchap", "settings_crypto_import_invalid_file") } + /// En savoir plus. + public static var settingsEnableEmailNotifLink: String { + return TchapL10n.tr("Tchap", "settings_enable_email_notif_link") + } + /// Recevez un e-mail si au moins un message récent non lu pendant 72h. + public static var settingsEnableEmailNotifText: String { + return TchapL10n.tr("Tchap", "settings_enable_email_notif_text") + } /// Sans cette autorisation, les appels entrants ne seront pas notifiés. public static var settingsEnablePushNotifText: String { return TchapL10n.tr("Tchap", "settings_enable_push_notif_text") @@ -807,7 +835,7 @@ public class TchapL10n: NSObject { public static var settingsHideFromUsersDirectoryTitle: String { return TchapL10n.tr("Tchap", "settings_hide_from_users_directory_title") } - /// Notification par courriel + /// Notification par e-mail public static var settingsNotificationEmail: String { return TchapL10n.tr("Tchap", "settings_notification_email") } diff --git a/Tchap/Utils/Tools.h b/Tchap/Utils/Tools.h index cd092ca6f2..645fc421a8 100644 --- a/Tchap/Utils/Tools.h +++ b/Tchap/Utils/Tools.h @@ -54,6 +54,13 @@ */ + (NSURL*)fixURLWithSeveralHashKeys:(NSURL*)url; +#pragma mark - Time utilities + +/** + * Convert a number of days to a duration in ms. + */ ++ (uint64_t)durationInMsFromDays:(uint)days; + #pragma mark - Tchap permalink /* @@ -84,4 +91,5 @@ + (NSString*)permalinkToEvent:(NSString*)eventId inRoom:(NSString*)roomIdOrAlias; + @end diff --git a/Tchap/Utils/Tools.m b/Tchap/Utils/Tools.m index 598ec7b435..8db610bbbd 100644 --- a/Tchap/Utils/Tools.m +++ b/Tchap/Utils/Tools.m @@ -142,6 +142,13 @@ + (NSURL *)fixURLWithSeveralHashKeys:(NSURL *)url return fixedURL; } +#pragma mark - Time utilities + ++ (uint64_t)durationInMsFromDays:(uint)days +{ + return days * (uint64_t)(86400000); +} + #pragma mark - Tchap permalink + (NSString *)permalinkToRoom:(NSString *)roomIdOrAlias diff --git a/Tchap/target.yml b/Tchap/target.yml index 9f497793c1..63080dfe35 100644 --- a/Tchap/target.yml +++ b/Tchap/target.yml @@ -54,6 +54,7 @@ targetTemplates: - package: WysiwygComposer - package: AnalyticsEvents - package: Mapbox + - package: PostHog preBuildScripts: - name: 🛠 Environment diff --git a/Tools/Release/buildRelease.sh b/Tools/Release/buildRelease.sh index 583b00b62c..0f925efc1c 100755 --- a/Tools/Release/buildRelease.sh +++ b/Tools/Release/buildRelease.sh @@ -42,7 +42,7 @@ cp -R ../../../.. /tmp/$REPO_NAME mv /tmp/$REPO_NAME . else echo "Git clone $REPO_URL with branch/tag $TAG..." -git clone $REPO_URL --depth=1 --branch $TAG +git clone --recursive $REPO_URL --depth=1 --branch $TAG fi cd $REPO_NAME @@ -55,11 +55,6 @@ bundle update # Update fastlane plugins bundle exec fastlane update_plugins -# Use appropriated dependencies according to the current branch -if [ "$LOCAL_SOURCE" != true ]; then -bundle exec fastlane point_dependencies_to_same_feature -fi - # Build bundle exec fastlane app_store build_number:$BUILD_NUMBER git_tag:$TAG diff --git a/changelog.d/1066.change b/changelog.d/1066.change new file mode 100644 index 0000000000..8a583097a1 --- /dev/null +++ b/changelog.d/1066.change @@ -0,0 +1 @@ +Rebase sur Element 1.11.15 \ No newline at end of file diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 79cea6c24b..4fabf60640 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -49,27 +49,6 @@ platform :ios do end - desc "Point MatrixSDK to the related branches if such ones exist" - lane :point_dependencies_to_related_branches do - current_branch = mx_git_branch - UI.message("Current branch: #{current_branch}") - if current_branch.start_with?("release/") - point_dependencies_to_pending_releases - else - point_dependencies_to_same_feature - end - end - - desc "Point MatrixSDK to release/*/release branch if it exists, master otherwise" - lane :point_dependencies_to_pending_releases do - edit_podfile(branch_pattern: "release/*/release", default_branch: "master") - end - - desc "Point MatrixSDK to the branch with the same name as the current branch if such one exist, develop otherwise" - lane :point_dependencies_to_same_feature do - edit_podfile(branch_pattern: mx_git_branch) unless mx_git_branch.to_s.empty? - end - desc "Use an app variant. An app variant overwrite default project configuration or ressource files with custom values" lane :setup_app_variant do |options| appVariantScript = "../Variants/setup_app_variant.sh" @@ -418,25 +397,6 @@ platform :ios do preprocessor_definitions end - desc "Edit the Podfile in order to point MatrixSDK to the appropriate branches." - private_lane :edit_podfile do |options| - require 'net/http' - - branch_pattern = options[:branch_pattern] - sdk_slug = "matrix-org/matrix-ios-sdk" - - default_branch = options[:default_branch] || 'develop' - sdk_branch = find_branch(sdk_slug, branch_pattern) || default_branch - - sdk_spec = { git: 'https://github.com/matrix-org/matrix-ios-sdk.git', branch: sdk_branch } - - UI.message "✏️ Modify Podfile to point `MatrixSDK/*` to \`#{sdk_branch}\` branch..." - podfile_content = File.read('../Podfile') # current dir is 'fastlane/' hence the '../' - podfile_content.gsub!(%r{^\$matrixSDKVersion\s*=\s*.*$}, "$matrixSDKVersion = { :specHash => #{sdk_spec} }") - File.write('../Podfile', podfile_content) - UI.command_output("Content of modified Podfile:\n" + podfile_content) - end - desc "Upload dsym files to Sentry to symbolicate crashes" # private_lane :upload_dsyms_to_sentry do # UI.user_error!("'SENTRY_AUTH_TOKEN' environment variable should be set to use this lane") unless !ENV["SENTRY_AUTH_TOKEN"].to_s.empty? diff --git a/matrix-ios-sdk b/matrix-ios-sdk new file mode 160000 index 0000000000..c64c052425 --- /dev/null +++ b/matrix-ios-sdk @@ -0,0 +1 @@ +Subproject commit c64c052425eadf8d13b5ed76e0bc0e015be05411 diff --git a/project.yml b/project.yml index 4efd80a94b..87be3e8f70 100644 --- a/project.yml +++ b/project.yml @@ -52,7 +52,7 @@ include: packages: AnalyticsEvents: url: https://github.com/matrix-org/matrix-analytics-events - exactVersion: 0.15.0 + exactVersion: 0.23.1 Mapbox: url: https://github.com/maplibre/maplibre-gl-native-distribution minVersion: 5.12.2 @@ -73,3 +73,6 @@ packages: DTCoreText: url: https://github.com/Cocoanetics/DTCoreText version: 1.6.26 + PostHog: + url: https://github.com/PostHog/posthog-ios + minorVersion: 3.2.5