diff --git a/.github/workflows/ci-build.yml b/.github/workflows/ci-build.yml index 374c82bdef..905c365582 100644 --- a/.github/workflows/ci-build.yml +++ b/.github/workflows/ci-build.yml @@ -15,7 +15,7 @@ env: jobs: build: name: Build - runs-on: macos-12 + runs-on: macos-14 # Concurrency group not needed as this workflow only runs on develop which we always want to test. diff --git a/.github/workflows/ci-tests.yml b/.github/workflows/ci-tests.yml index ba69c463e3..5ffde41ac8 100644 --- a/.github/workflows/ci-tests.yml +++ b/.github/workflows/ci-tests.yml @@ -16,7 +16,7 @@ env: jobs: tests: name: Tests - runs-on: macos-12 + runs-on: macos-14 concurrency: # When running on develop, use the sha to allow all runs of this workflow to run concurrently. diff --git a/.github/workflows/ci-ui-tests.yml b/.github/workflows/ci-ui-tests.yml new file mode 100644 index 0000000000..b13272947f --- /dev/null +++ b/.github/workflows/ci-ui-tests.yml @@ -0,0 +1,63 @@ +name: UI Tests CI + +on: + pull_request: + + workflow_dispatch: + +env: + # Make the git branch for a PR available to our Fastfile + MX_GIT_BRANCH: ${{ github.event.pull_request.head.ref }} + +jobs: + tests: + name: UI Tests + runs-on: macos-14 + + concurrency: + # Only allow a single run of this workflow on each branch, automatically cancelling older runs. + group: ui-tests-${{ github.head_ref }} + cancel-in-progress: true + + steps: + - uses: actions/checkout@v2 + + # Common cache + # Note: GH actions do not support yaml anchor yet. We need to duplicate this for every job + - uses: actions/cache@v2 + with: + path: Pods + key: ${{ runner.os }}-pods-${{ hashFiles('**/Podfile.lock') }} + restore-keys: | + ${{ runner.os }}-pods- + - uses: actions/cache@v2 + with: + path: vendor/bundle + key: ${{ runner.os }}-gems-${{ hashFiles('**/Gemfile.lock') }} + restore-keys: | + ${{ runner.os }}-gems- + + # Make sure we use the latest version of MatrixSDK + - name: Reset MatrixSDK pod + run: rm -rf Pods/MatrixSDK + + # Common setup + # Note: GH actions do not support yaml anchor yet. We need to duplicate this for every job + - name: Brew bundle + run: brew bundle + - name: Bundle install + 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 + run: bundle exec fastlane uitest + + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v3 + with: + flags: uitests + \ No newline at end of file diff --git a/.github/workflows/release-alpha.yml b/.github/workflows/release-alpha.yml new file mode 100644 index 0000000000..e610628b45 --- /dev/null +++ b/.github/workflows/release-alpha.yml @@ -0,0 +1,95 @@ +name: Build alpha release + +on: + + # Triggers the workflow on any pull request + pull_request: + types: [ labeled, synchronize, opened, reopened ] + +env: + # Make the git branch for a PR available to our Fastfile + MX_GIT_BRANCH: ${{ github.event.pull_request.head.ref }} + +jobs: + build: + # Only run for PRs that contain the trigger label. The action will fail for forks due to + # missing secrets, but there's no need to handle this as it won't run automatically. + if: contains(github.event.pull_request.labels.*.name, 'Trigger-PR-Build') + + name: Release + runs-on: macos-14 + + concurrency: + # Only allow a single run of this workflow on each branch, automatically cancelling older runs. + group: alpha-${{ github.head_ref }} + cancel-in-progress: true + + steps: + - uses: actions/checkout@v2 + + # Common cache + # Note: GH actions do not support yaml anchor yet. We need to duplicate this for every job + - name: Cache CocoaPods libraries + uses: actions/cache@v2 + with: + path: Pods + key: ${{ runner.os }}-pods-${{ hashFiles('**/Podfile.lock') }} + restore-keys: | + ${{ runner.os }}-pods- + + - name: Cache Ruby gems + uses: actions/cache@v2 + with: + path: vendor/bundle + key: ${{ runner.os }}-gems-${{ hashFiles('**/Gemfile.lock') }} + restore-keys: | + ${{ runner.os }}-gems- + + # Make sure we use the latest version of MatrixSDK + - name: Reset MatrixSDK pod + run: rm -rf Pods/MatrixSDK + + # Common setup + # Note: GH actions do not support yaml anchor yet. We need to duplicate this for every job + - name: Brew bundle + run: brew bundle + - name: Bundle install + 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 + uses: apple-actions/import-codesign-certs@v1 + with: + p12-file-base64: ${{ secrets.ALPHA_CERTIFICATES_P12 }} + p12-password: ${{ secrets.ALPHA_CERTIFICATES_P12_PASSWORD }} + + # Main step + # The Ad-hoc release link will be referenced as 'DIAWI_FILE_LINK' + # and QR link as 'DIAWI_QR_CODE_LINK' when the Diawi upload succeed + - name: Build Ad-hoc release and send it to Diawi + run: bundle exec fastlane alpha + env: + APPSTORECONNECT_KEY_ID: ${{ secrets.APPSTORECONNECT_KEY_ID }} + APPSTORECONNECT_KEY_ISSUER_ID: ${{ secrets.APPSTORECONNECT_KEY_ISSUER_ID }} + APPSTORECONNECT_KEY_CONTENT: ${{ secrets.APPSTORECONNECT_KEY_CONTENT }} + DIAWI_API_TOKEN: ${{ secrets.DIAWI_API_TOKEN }} + SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} + + - name: Add or update PR comment with Ad-hoc release informations + uses: NejcZdovc/comment-pr@v1 + with: + message: | + :iphone: Scan the QR code below to install the build for this PR. + :lock: This build is for internal testing purpose. Only devices listed in the ad-hoc provisioning profile can install Element Alpha. + + ![QR code](${{ env.DIAWI_QR_CODE_LINK }}) + + If you can't scan the QR code you can install the build via this link: ${{ env.DIAWI_FILE_LINK }} + # Enables to identify and update existing Ad-hoc release message on new commit in the PR + identifier: "GITHUB_COMMENT_ADHOC_RELEASE" + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/sonarcloud.yml b/.github/workflows/sonarcloud.yml new file mode 100644 index 0000000000..d902ca1368 --- /dev/null +++ b/.github/workflows/sonarcloud.yml @@ -0,0 +1,31 @@ +name: SonarCloud analysis + +on: + push: + branches: [ "develop" ] + pull_request: + branches: [ "develop" ] + workflow_dispatch: + +permissions: + pull-requests: read # allows SonarCloud to decorate PRs with analysis results + +jobs: + Analysis: + runs-on: ubuntu-latest + + steps: + - name: Analyze with SonarCloud + + # You can pin the exact commit or the version. + uses: SonarSource/sonarcloud-github-action@master + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} # Generate the token on Sonarcloud.io, add it to the secrets of this repo + with: + # Additional arguments for the sonarcloud scanner + args: + -Dsonar.projectKey=vector-im_element-ios + -Dsonar.organization=new_vector_ltd_organization + -Dsonar.inclusions=RiotSwiftUI/** + # For more info about the parameters, please refer to https://docs.sonarcloud.io/advanced-setup/analysis-parameters/ \ No newline at end of file diff --git a/.github/workflows/triage-incoming.yml b/.github/workflows/triage-incoming.yml deleted file mode 100644 index 11f3280cc9..0000000000 --- a/.github/workflows/triage-incoming.yml +++ /dev/null @@ -1,37 +0,0 @@ -name: Move new issues onto Issue triage board - -on: - issues: - types: [opened] - -jobs: - automate-project-columns: - runs-on: ubuntu-latest - steps: - - uses: alex-page/github-project-automation-plus@bb266ff4dde9242060e2d5418e120a133586d488 - with: - project: Issue triage - column: Incoming - repo-token: ${{ secrets.ELEMENT_BOT_TOKEN }} - - add_to_triage: - runs-on: ubuntu-latest - if: > - github.repository == 'element-hq/element-ios' - steps: - - uses: octokit/graphql-action@v2.x - with: - headers: '{"GraphQL-Features": "projects_next_graphql"}' - query: | - mutation add_to_project($projectid:ID!,$contentid:ID!) { - addProjectV2ItemById(input: {projectId: $projectid contentId: $contentid}) { - item { - id - } - } - } - projectid: ${{ env.PROJECT_ID }} - contentid: ${{ github.event.issue.node_id }} - env: - PROJECT_ID: "PVT_kwDOAM0swc4AMlHr" - GITHUB_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }} diff --git a/.github/workflows/triage-move-labelled.yml b/.github/workflows/triage-move-labelled.yml index 4282571424..b14355db80 100644 --- a/.github/workflows/triage-move-labelled.yml +++ b/.github/workflows/triage-move-labelled.yml @@ -30,17 +30,6 @@ jobs: labels: ['Z-Labs'] }) - move_needs_info_issues: - name: X-Needs-Info issues to Need info column on triage board - runs-on: ubuntu-latest - steps: - - uses: konradpabjan/move-labeled-or-milestoned-issue@219d384e03fa4b6460cd24f9f37d19eb033a4338 - with: - action-token: "${{ secrets.ELEMENT_BOT_TOKEN }}" - project-url: "https://github.com/element-hq/element-ios/projects/12" - column-name: "Need info" - label-name: "X-Needs-Info" - add_priority_design_issues_to_project: name: P1 X-Needs-Design to Design project board runs-on: ubuntu-latest @@ -68,64 +57,3 @@ jobs: with: project-url: https://github.com/orgs/element-hq/projects/28 github-token: ${{ secrets.ELEMENT_BOT_TOKEN }} - - ex_plorers: - name: Add labelled issues to X-Plorer project - runs-on: ubuntu-latest - if: > - contains(github.event.issue.labels.*.name, 'Team: Element X Feature') - steps: - - uses: actions/add-to-project@main - with: - project-url: https://github.com/orgs/element-hq/projects/73 - github-token: ${{ secrets.ELEMENT_BOT_TOKEN }} - - ps_features1: - name: Add labelled issues to PS features team 1 - runs-on: ubuntu-latest - if: > - contains(github.event.issue.labels.*.name, 'A-Polls') || - contains(github.event.issue.labels.*.name, 'A-Location-Sharing') || - (contains(github.event.issue.labels.*.name, 'A-Voice-Messages') && - !contains(github.event.issue.labels.*.name, 'A-Broadcast')) || - (contains(github.event.issue.labels.*.name, 'A-Session-Mgmt') && - contains(github.event.issue.labels.*.name, 'A-User-Settings')) - steps: - - uses: actions/add-to-project@main - with: - project-url: https://github.com/orgs/element-hq/projects/56 - github-token: ${{ secrets.ELEMENT_BOT_TOKEN }} - - ps_features2: - name: Add labelled issues to PS features team 2 - runs-on: ubuntu-latest - if: > - contains(github.event.issue.labels.*.name, 'A-DM-Start') || - contains(github.event.issue.labels.*.name, 'A-Broadcast') - steps: - - uses: actions/add-to-project@main - with: - project-url: https://github.com/orgs/element-hq/projects/58 - github-token: ${{ secrets.ELEMENT_BOT_TOKEN }} - - ps_features3: - name: Add labelled issues to PS features team 3 - runs-on: ubuntu-latest - if: > - contains(github.event.issue.labels.*.name, 'A-Rich-Text-Editor') - steps: - - uses: actions/add-to-project@main - with: - project-url: https://github.com/orgs/element-hq/projects/57 - github-token: ${{ secrets.ELEMENT_BOT_TOKEN }} - - voip: - name: Add labelled issues to VoIP project board - runs-on: ubuntu-latest - if: > - contains(github.event.issue.labels.*.name, 'Team: VoIP') - steps: - - uses: actions/add-to-project@main - with: - project-url: https://github.com/orgs/element-hq/projects/41 - github-token: ${{ secrets.ELEMENT_BOT_TOKEN }} diff --git a/BroadcastUploadExtension/SupportingFiles/PrivacyInfo.xcprivacy b/BroadcastUploadExtension/SupportingFiles/PrivacyInfo.xcprivacy new file mode 100644 index 0000000000..500ae9affe --- /dev/null +++ b/BroadcastUploadExtension/SupportingFiles/PrivacyInfo.xcprivacy @@ -0,0 +1,41 @@ + + + + + NSPrivacyAccessedAPITypes + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryUserDefaults + NSPrivacyAccessedAPITypeReasons + + 1C8F.1 + + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryFileTimestamp + NSPrivacyAccessedAPITypeReasons + + C617.1 + + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryDiskSpace + NSPrivacyAccessedAPITypeReasons + + 7D9E.1 + + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategorySystemBootTime + NSPrivacyAccessedAPITypeReasons + + 3D61.1 + + + + + diff --git a/Btchap/Config/BuildSettings.swift b/Btchap/Config/BuildSettings.swift index f577db02a8..32ddc2f784 100644 --- a/Btchap/Config/BuildSettings.swift +++ b/Btchap/Config/BuildSettings.swift @@ -237,6 +237,7 @@ final class BuildSettings: NSObject { static let tchapFeatureNotificationByEmail = "tchapFeatureNotificationByEmail" static let tchapFeatureVoiceOverIP = "tchapFeatureVoiceOverIP" static let tchapFeatureVideoOverIP = "tchapFeatureVideoOverIP" // Tchap: in pre-prod, allow any feature to any instance. + static let tchapFeatureGeolocationSharing = "tchapFeatureGeolocationSharing" // linked to `locationSharingEnabled` property (see above) static var tchapFeaturesAllowedHomeServersForFeature: [String: [String]] = [ tchapFeatureAnyFeature: [ tchapFeatureAnyHomeServer ] ] @@ -413,9 +414,14 @@ final class BuildSettings: NSObject { // MARK: - Location Sharing /// Overwritten by the home server's .well-known configuration (if any exists) - static let defaultTileServerMapStyleURL = URL(string: "https://api.maptiler.com/maps/streets/style.json?key=")! - - static let locationSharingEnabled = false // Currently disabled in Tchap. + // Tchap: handle different map providers. + private enum TchapMapProvider: String { + case geoDataGouv = "https://openmaptiles.geo.data.gouv.fr/styles/osm-bright/style.json" + case ign = "https://data.geopf.fr/annexes/ressources/vectorTiles/styles/PLAN.IGN/standard.json" + } + static let defaultTileServerMapStyleURL = URL(string: TchapMapProvider.geoDataGouv.rawValue)! + + static let locationSharingEnabled = true // MARK: - Voice Broadcast static let voiceBroadcastChunkLength: Int = 120 diff --git a/Btchap/Generated/InfoPlist.swift b/Btchap/Generated/InfoPlist.swift index d65235e8cb..fd4913b7ce 100644 --- a/Btchap/Generated/InfoPlist.swift +++ b/Btchap/Generated/InfoPlist.swift @@ -30,6 +30,8 @@ internal enum InfoPlist { internal static let nsCameraUsageDescription: String = _document["NSCameraUsageDescription"] internal static let nsContactsUsageDescription: String = _document["NSContactsUsageDescription"] internal static let nsFaceIDUsageDescription: String = _document["NSFaceIDUsageDescription"] + internal static let nsLocationAlwaysAndWhenInUseUsageDescription: String = _document["NSLocationAlwaysAndWhenInUseUsageDescription"] + internal static let nsLocationWhenInUseUsageDescription: String = _document["NSLocationWhenInUseUsageDescription"] internal static let nsMicrophoneUsageDescription: String = _document["NSMicrophoneUsageDescription"] internal static let nsPhotoLibraryUsageDescription: String = _document["NSPhotoLibraryUsageDescription"] internal static let uiBackgroundModes: [String] = _document["UIBackgroundModes"] diff --git a/Btchap/SupportingFiles/Info.plist b/Btchap/SupportingFiles/Info.plist index ec38ef38e4..5e06e305b9 100644 --- a/Btchap/SupportingFiles/Info.plist +++ b/Btchap/SupportingFiles/Info.plist @@ -53,12 +53,17 @@ In order to show who among your contacts already uses Tchap, we can exploit the e-mail addresses of your address book. These data will not be stored. For more information, please visit the privacy policy page in the app settings NSFaceIDUsageDescription Face ID is used to access your app. + NSLocationAlwaysAndWhenInUseUsageDescription + When you share your location to people, Tchap needs access to show them a map. + NSLocationWhenInUseUsageDescription + When you share your location to people, Tchap needs access to show them a map. NSMicrophoneUsageDescription Tchap needs to access your microphone to take videos, and record voice messages. NSPhotoLibraryUsageDescription This allows you to select pictures or videos from the photo library, and send them in your conversations. You can also use one of these pictures to set your profile picture. UIBackgroundModes + location remote-notification voip diff --git a/CHANGES.md b/CHANGES.md index b2a673e097..eb5267b66d 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,35 @@ +## Changes in 1.11.9 (2024-04-02) + +Others + +- Update matrix-analytics-events to version 0.15.0 ([#7768](https://github.com/element-hq/element-ios/pull/7768)) +- Upgrade to build with Xcode 15.2 +- Add a privacy manifest + + +## Changes in 1.11.8 (2024-03-05) + +🙌 Improvements + +- Disable the mark as unread feature to avoid it clashing with the new MSC2876 based one ([#7758](https://github.com/element-hq/element-ios/pull/7758)) + +🐛 Bugfixes + +- Fix a bug where QR codes aren't detected if the camera is too close. ([#7762](https://github.com/element-hq/element-ios/pull/7762)) +- Fix dictation when using the Rich Text Editor ([#7752](https://github.com/element-hq/element-ios/issues/7752)) + + +## Changes in 1.11.7 (2024-02-07) + +🙌 Improvements + +- Upgrade MatrixSDK version ([v0.27.6](https://github.com/matrix-org/matrix-ios-sdk/releases/tag/v0.27.6)). + +🐛 Bugfixes + +- Fix swapped accessibility label between strikethrough and underline format buttons in RTE. ([#7743](https://github.com/element-hq/element-ios/pull/7743)) + + ## Changes in 1.11.6 (2024-01-09) 🙌 Improvements diff --git a/Config/BuildSettings.swift b/Config/BuildSettings.swift index b73ee1f6a7..9abdd1f5f4 100644 --- a/Config/BuildSettings.swift +++ b/Config/BuildSettings.swift @@ -423,9 +423,14 @@ final class BuildSettings: NSObject { // MARK: - Location Sharing /// Overwritten by the home server's .well-known configuration (if any exists) - static let defaultTileServerMapStyleURL = URL(string: "https://api.maptiler.com/maps/streets/style.json?key=")! - - static let locationSharingEnabled = false // Currently disabled in Tchap. + // Tchap: handle different map providers. + private enum TchapMapProvider: String { + case geoDataGouv = "https://openmaptiles.geo.data.gouv.fr/styles/osm-bright/style.json" + case ign = "https://data.geopf.fr/annexes/ressources/vectorTiles/styles/PLAN.IGN/standard.json" + } + static let defaultTileServerMapStyleURL = URL(string: TchapMapProvider.geoDataGouv.rawValue)! + + static let locationSharingEnabled = true // MARK: - Voice Broadcast static let voiceBroadcastChunkLength: Int = 120 diff --git a/DevTchap/Config/BuildSettings.swift b/DevTchap/Config/BuildSettings.swift index 58ee2009f4..08f236910a 100644 --- a/DevTchap/Config/BuildSettings.swift +++ b/DevTchap/Config/BuildSettings.swift @@ -238,6 +238,7 @@ final class BuildSettings: NSObject { static let tchapFeatureNotificationByEmail = "tchapFeatureNotificationByEmail" static let tchapFeatureVoiceOverIP = "tchapFeatureVoiceOverIP" static let tchapFeatureVideoOverIP = "tchapFeatureVideoOverIP" // Tchap: in Dev, allow any feature to any instance. + static let tchapFeatureGeolocationSharing = "tchapFeatureGeolocationSharing" // linked to `locationSharingEnabled` property (see above) static var tchapFeaturesAllowedHomeServersForFeature: [String: [String]] = [ tchapFeatureAnyFeature: [ tchapFeatureAnyHomeServer ] ] @@ -414,9 +415,14 @@ final class BuildSettings: NSObject { // MARK: - Location Sharing /// Overwritten by the home server's .well-known configuration (if any exists) - static let defaultTileServerMapStyleURL = URL(string: "https://api.maptiler.com/maps/streets/style.json?key=")! - - static let locationSharingEnabled = false // Currently disabled in Tchap. + // Tchap: handle different map providers. + private enum TchapMapProvider: String { + case geoDataGouv = "https://openmaptiles.geo.data.gouv.fr/styles/osm-bright/style.json" + case ign = "https://data.geopf.fr/annexes/ressources/vectorTiles/styles/PLAN.IGN/standard.json" + } + static let defaultTileServerMapStyleURL = URL(string: TchapMapProvider.geoDataGouv.rawValue)! + + static let locationSharingEnabled = true // MARK: - Voice Broadcast static let voiceBroadcastChunkLength: Int = 120 diff --git a/DevTchap/Generated/InfoPlist.swift b/DevTchap/Generated/InfoPlist.swift index c2b39bfa29..2306b5b8c2 100644 --- a/DevTchap/Generated/InfoPlist.swift +++ b/DevTchap/Generated/InfoPlist.swift @@ -29,6 +29,8 @@ internal enum InfoPlist { internal static let nsCameraUsageDescription: String = _document["NSCameraUsageDescription"] internal static let nsContactsUsageDescription: String = _document["NSContactsUsageDescription"] internal static let nsFaceIDUsageDescription: String = _document["NSFaceIDUsageDescription"] + internal static let nsLocationAlwaysAndWhenInUseUsageDescription: String = _document["NSLocationAlwaysAndWhenInUseUsageDescription"] + internal static let nsLocationWhenInUseUsageDescription: String = _document["NSLocationWhenInUseUsageDescription"] internal static let nsMicrophoneUsageDescription: String = _document["NSMicrophoneUsageDescription"] internal static let nsPhotoLibraryUsageDescription: String = _document["NSPhotoLibraryUsageDescription"] internal static let uiBackgroundModes: [String] = _document["UIBackgroundModes"] diff --git a/DevTchap/SupportingFiles/Info.plist b/DevTchap/SupportingFiles/Info.plist index cbd3ec180b..4303301c18 100644 --- a/DevTchap/SupportingFiles/Info.plist +++ b/DevTchap/SupportingFiles/Info.plist @@ -51,12 +51,17 @@ In order to show who among your contacts already uses Tchap, we can exploit the e-mail addresses of your address book. These data will not be stored. For more information, please visit the privacy policy page in the app settings NSFaceIDUsageDescription Face ID is used to access your app. + NSLocationAlwaysAndWhenInUseUsageDescription + When you share your location to people, Tchap needs access to show them a map. + NSLocationWhenInUseUsageDescription + When you share your location to people, Tchap needs access to show them a map. NSMicrophoneUsageDescription Tchap needs to access your microphone to take videos, and record voice messages. NSPhotoLibraryUsageDescription This allows you to select pictures or videos from the photo library, and send them in your conversations. You can also use one of these pictures to set your profile picture. UIBackgroundModes + location remote-notification voip diff --git a/Gemfile.lock b/Gemfile.lock index 02bb363639..519e1f087e 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -7,9 +7,11 @@ GIT GEM remote: https://rubygems.org/ specs: - CFPropertyList (3.0.6) + CFPropertyList (3.0.7) + base64 + nkf rexml - activesupport (7.1.2) + activesupport (7.1.3.2) base64 bigdecimal concurrent-ruby (~> 1.0, >= 1.0.2) @@ -19,32 +21,32 @@ GEM minitest (>= 5.1) mutex_m tzinfo (~> 2.0) - addressable (2.8.5) + addressable (2.8.6) public_suffix (>= 2.0.2, < 6.0) algoliasearch (1.27.5) httpclient (~> 2.8, >= 2.8.3) json (>= 1.5.1) - artifactory (3.0.15) + artifactory (3.0.17) atomos (0.1.3) aws-eventstream (1.3.0) - aws-partitions (1.859.0) - aws-sdk-core (3.188.0) - aws-eventstream (~> 1, >= 1.0.2) + aws-partitions (1.899.0) + aws-sdk-core (3.191.4) + aws-eventstream (~> 1, >= 1.3.0) aws-partitions (~> 1, >= 1.651.0) - aws-sigv4 (~> 1.5) + aws-sigv4 (~> 1.8) jmespath (~> 1, >= 1.6.1) - aws-sdk-kms (1.73.0) - aws-sdk-core (~> 3, >= 3.188.0) + aws-sdk-kms (1.78.0) + aws-sdk-core (~> 3, >= 3.191.0) aws-sigv4 (~> 1.1) - aws-sdk-s3 (1.140.0) - aws-sdk-core (~> 3, >= 3.188.0) + aws-sdk-s3 (1.146.0) + aws-sdk-core (~> 3, >= 3.191.0) aws-sdk-kms (~> 1) - aws-sigv4 (~> 1.6) - aws-sigv4 (1.7.0) + 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.4) + bigdecimal (3.1.7) claide (1.1.0) clamp (1.3.2) cocoapods (1.14.3) @@ -88,20 +90,19 @@ GEM colored2 (3.1.2) commander (4.6.0) highline (~> 2.0.0) - concurrent-ruby (1.2.2) + concurrent-ruby (1.2.3) connection_pool (2.4.1) declarative (0.0.20) digest-crc (0.6.5) rake (>= 12.0.0, < 14.0.0) - domain_name (0.6.20231109) + domain_name (0.6.20240107) dotenv (2.8.1) - drb (2.2.0) - ruby2_keywords + drb (2.2.1) emoji_regex (3.2.3) escape (0.0.4) ethon (0.16.0) ffi (>= 1.15.0) - excon (0.104.0) + excon (0.110.0) faraday (1.10.3) faraday-em_http (~> 1.0) faraday-em_synchrony (~> 1.0) @@ -130,8 +131,8 @@ GEM faraday-retry (1.0.3) faraday_middleware (1.2.0) faraday (~> 1.0) - fastimage (2.2.7) - fastlane (2.217.0) + fastimage (2.3.0) + fastlane (2.219.0) CFPropertyList (>= 2.3, < 4.0.0) addressable (>= 2.8, < 3.0.0) artifactory (~> 3.0) @@ -150,6 +151,7 @@ GEM gh_inspector (>= 1.1.2, < 2.0.0) google-apis-androidpublisher_v3 (~> 0.3) google-apis-playcustomapp_v1 (~> 0.1) + google-cloud-env (>= 1.6.0, < 2.0.0) google-cloud-storage (~> 1.31) highline (~> 2.0) http-cookie (~> 1.0.5) @@ -158,7 +160,7 @@ 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) plist (>= 3.1.0, < 4.0.0) rubyzip (>= 2.0.0, < 3.0.0) security (= 0.1.3) @@ -172,7 +174,7 @@ GEM xcpretty (~> 0.3.0) xcpretty-travis-formatter (>= 0.0.3) fastlane-plugin-brew (0.1.1) - fastlane-plugin-sentry (1.16.0) + fastlane-plugin-sentry (1.20.0) os (~> 1.1, >= 1.1.4) fastlane-plugin-versioning (0.5.2) fastlane-plugin-xcodegen (1.1.0) @@ -181,9 +183,9 @@ GEM fourflusher (2.3.1) fuzzy_match (2.0.4) gh_inspector (1.1.3) - google-apis-androidpublisher_v3 (0.53.0) + google-apis-androidpublisher_v3 (0.54.0) google-apis-core (>= 0.11.0, < 2.a) - google-apis-core (0.11.2) + google-apis-core (0.11.3) addressable (~> 2.5, >= 2.5.1) googleauth (>= 0.16.2, < 2.a) httpclient (>= 2.8.1, < 3.a) @@ -191,24 +193,23 @@ GEM representable (~> 3.0) retriable (>= 2.0, < 4.a) rexml - webrick google-apis-iamcredentials_v1 (0.17.0) google-apis-core (>= 0.11.0, < 2.a) google-apis-playcustomapp_v1 (0.13.0) google-apis-core (>= 0.11.0, < 2.a) - google-apis-storage_v1 (0.29.0) + google-apis-storage_v1 (0.31.0) google-apis-core (>= 0.11.0, < 2.a) - google-cloud-core (1.6.0) - google-cloud-env (~> 1.0) + google-cloud-core (1.7.0) + google-cloud-env (>= 1.0, < 3.a) google-cloud-errors (~> 1.0) google-cloud-env (1.6.0) faraday (>= 0.17.3, < 3.0) - google-cloud-errors (1.3.1) - google-cloud-storage (1.45.0) + google-cloud-errors (1.4.0) + google-cloud-storage (1.47.0) addressable (~> 2.8) digest-crc (~> 0.4) google-apis-iamcredentials_v1 (~> 0.1) - google-apis-storage_v1 (~> 0.29.0) + google-apis-storage_v1 (~> 0.31.0) google-cloud-core (~> 1.6) googleauth (>= 0.16.2, < 2.a) mini_mime (~> 1.0) @@ -222,29 +223,31 @@ GEM http-cookie (1.0.5) domain_name (~> 0.5) httpclient (2.8.3) - i18n (1.14.1) + i18n (1.14.4) concurrent-ruby (~> 1.0) jmespath (1.6.2) - json (2.6.3) - jwt (2.7.1) + json (2.7.1) + jwt (2.8.1) + base64 mini_magick (4.12.0) mini_mime (1.1.5) mini_portile2 (2.8.5) - minitest (5.20.0) + minitest (5.22.3) molinillo (0.8.0) multi_json (1.15.0) - multipart-post (2.3.0) + multipart-post (2.4.0) mutex_m (0.2.0) nanaimo (0.3.0) nap (1.1.0) naturally (2.2.1) netrc (0.11.0) - nokogiri (1.15.5) + nkf (0.2.0) + nokogiri (1.15.6) mini_portile2 (~> 2.8.2) racc (~> 1.4) - optparse (0.1.1) + optparse (0.4.0) os (1.1.4) - plist (3.7.0) + plist (3.7.1) public_suffix (4.0.7) racc (1.7.3) rake (13.1.0) @@ -259,7 +262,7 @@ GEM ruby2_keywords (0.0.5) rubyzip (2.3.2) security (0.1.3) - signet (0.18.0) + signet (0.19.0) addressable (~> 2.8) faraday (>= 0.17.5, < 3.a) jwt (>= 1.5, < 3.0) @@ -278,7 +281,7 @@ GEM unicode-display_width (>= 1.1.1, < 3) trailblazer-option (0.1.2) tty-cursor (0.7.1) - tty-screen (0.8.1) + tty-screen (0.8.2) tty-spinner (0.9.3) tty-cursor (~> 0.7) typhoeus (1.4.1) @@ -287,12 +290,11 @@ GEM concurrent-ruby (~> 1.0) uber (0.1.0) unicode-display_width (2.5.0) - webrick (1.8.1) word_wrap (1.0.0) xcode-install (2.8.1) claide (>= 0.9.1) fastlane (>= 2.1.0, < 3.0.0) - xcodeproj (1.23.0) + xcodeproj (1.24.0) CFPropertyList (>= 2.3.3, < 4.0) atomos (~> 0.1.3) claide (>= 1.0.2, < 2.0) diff --git a/Podfile b/Podfile index fc693dd78d..f662089b20 100644 --- a/Podfile +++ b/Podfile @@ -16,7 +16,7 @@ use_frameworks! # - `{ :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.5' +$matrixSDKVersion = '= 0.27.6' # $matrixSDKVersion = :local # $matrixSDKVersion = { :branch => 'develop'} # $matrixSDKVersion = { :specHash => { git: 'https://git.io/fork123', branch: 'fix' } } @@ -59,7 +59,7 @@ end def import_SwiftUI_pods pod 'Introspect', '~> 0.1' pod 'DSBottomSheet', '~> 0.3' - pod 'ZXingObjC', '~> 3.6.5' + pod 'ZXingObjC', '~> 3.6.9' end def import_Common_pods diff --git a/Podfile.lock b/Podfile.lock index 9944489d39..dd6cd62b25 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -39,9 +39,9 @@ PODS: - LoggerAPI (1.9.200): - Logging (~> 1.1) - Logging (1.4.0) - - MatrixSDK (0.27.5): - - MatrixSDK/Core (= 0.27.5) - - MatrixSDK/Core (0.27.5): + - MatrixSDK (0.27.6): + - MatrixSDK/Core (= 0.27.6) + - MatrixSDK/Core (0.27.6): - AFNetworking (~> 4.0.0) - GZIP (~> 1.3.0) - libbase58 (~> 0.1.4) @@ -49,7 +49,7 @@ PODS: - OLMKit (~> 3.2.5) - Realm (= 10.27.0) - SwiftyBeaver (= 1.9.5) - - MatrixSDK/JingleCallStack (0.27.5): + - MatrixSDK/JingleCallStack (0.27.6): - JitsiMeetSDKLite (= 8.1.2-lite) - MatrixSDK/Core - MatrixSDKCrypto (0.3.13) @@ -87,9 +87,9 @@ PODS: - UICollectionViewRightAlignedLayout (0.0.3) - WeakDictionary (2.0.2) - zxcvbn-ios (1.0.4) - - ZXingObjC (3.6.5): - - ZXingObjC/All (= 3.6.5) - - ZXingObjC/All (3.6.5) + - ZXingObjC (3.6.9): + - ZXingObjC/All (= 3.6.9) + - ZXingObjC/All (3.6.9) DEPENDENCIES: - Down (~> 0.11.0) @@ -102,8 +102,8 @@ DEPENDENCIES: - KeychainAccess (~> 4.2.2) - KTCenterFlowLayout (~> 1.3.1) - libPhoneNumber-iOS (~> 0.9.13) - - MatrixSDK (= 0.27.5) - - MatrixSDK/JingleCallStack (= 0.27.5) + - MatrixSDK (= 0.27.6) + - MatrixSDK/JingleCallStack (= 0.27.6) - OLMKit - PostHog (~> 2.0.0) - ReadMoreTextView (~> 3.0.1) @@ -119,7 +119,7 @@ DEPENDENCIES: - UICollectionViewRightAlignedLayout (~> 0.0.3) - WeakDictionary (~> 2.0) - zxcvbn-ios - - ZXingObjC (~> 3.6.5) + - ZXingObjC (~> 3.6.9) SPEC REPOS: trunk: @@ -187,7 +187,7 @@ SPEC CHECKSUMS: libPhoneNumber-iOS: 0a32a9525cf8744fe02c5206eb30d571e38f7d75 LoggerAPI: ad9c4a6f1e32f518fdb43a1347ac14d765ab5e3d Logging: beeb016c9c80cf77042d62e83495816847ef108b - MatrixSDK: f92ffead50eda83c99786afefed9be739987f338 + MatrixSDK: 4129ab9c0acda1d0aad50b1c9765bd795b8d70b9 MatrixSDKCrypto: bf08b72f2cd015d8749420a2b8b92fc0536bedf4 OLMKit: da115f16582e47626616874e20f7bb92222c7a51 PostHog: f9e5c13ceea86bb5314218c85d16125b797eb332 @@ -206,7 +206,7 @@ SPEC CHECKSUMS: UICollectionViewRightAlignedLayout: 823eef8c567eba4a44c21bc2ffcb0d0d5f361e2d WeakDictionary: 8cd038acd77e5d54ca4ebaec3d20853d732b45e0 zxcvbn-ios: fef98b7c80f1512ff0eec47ac1fa399fc00f7e3c - ZXingObjC: fdbb269f25dd2032da343e06f10224d62f537bdb + ZXingObjC: 8898711ab495761b2dbbdec76d90164a6d7e14c5 PODFILE CHECKSUM: a6293c739492b4ee2d4697abbb601e7c92729aea diff --git a/Riot.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Riot.xcworkspace/xcshareddata/swiftpm/Package.resolved index f406af44f5..511ea29a7b 100644 --- a/Riot.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Riot.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -41,8 +41,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/matrix-org/matrix-analytics-events", "state" : { - "revision" : "2f5fa5f1e2f6c6ae1a47c33d953a3ce289167eb0", - "version" : "0.5.0" + "revision" : "44d5a0e898a71f8abbbe12afe9d73e82d370a9a1", + "version" : "0.15.0" } }, { @@ -50,8 +50,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/matrix-org/matrix-wysiwyg-composer-swift", "state" : { - "revision" : "0aa1308c43451fd077e332f72d6a32135f258834", - "version" : "2.19.0" + "revision" : "f788fe2482c0b89019f679a1f43dccf9c25a0782", + "version" : "2.29.0" } }, { @@ -84,7 +84,7 @@ { "identity" : "swift-ogg", "kind" : "remoteSourceControl", - "location" : "https://github.com/vector-im/swift-ogg", + "location" : "https://github.com/element-hq/swift-ogg", "state" : { "branch" : "0.0.1", "revision" : "e9a9e7601da662fd8b97d93781ff5c60b4becf88" diff --git a/Riot/Assets/be.lproj/Vector.strings b/Riot/Assets/be.lproj/Vector.strings index 8b13789179..64295bad49 100644 --- a/Riot/Assets/be.lproj/Vector.strings +++ b/Riot/Assets/be.lproj/Vector.strings @@ -1 +1,9 @@ + + +// Titles +"title_home" = "Đ“Đ°Đ»ĐŸŃžĐœĐ°Ń"; +"people_empty_view_title" = "ĐŁĐŽĐ·Đ”Đ»ŃŒĐœŃ–Đșі"; +"group_details_home" = "Đ“Đ°Đ»ĐŸŃžĐœĐ°Ń"; +"spaces_home_space_title" = "Đ“Đ°Đ»ĐŸŃžĐœĐ°Ń"; +"title_people" = "ĐŁĐŽĐ·Đ”Đ»ŃŒĐœŃ–Đșі"; diff --git a/Riot/Assets/de.lproj/Vector.strings b/Riot/Assets/de.lproj/Vector.strings index 80eb951724..331614eca2 100644 --- a/Riot/Assets/de.lproj/Vector.strings +++ b/Riot/Assets/de.lproj/Vector.strings @@ -2616,8 +2616,8 @@ "manage_session_name_info" = "Sei dir bitte bewusst, dass Sitzungsnamen auch fĂŒr Personen, mit denen du kommunizierst, sichtbar sind. %@"; "manage_session_name_hint" = "Individuelle Sitzungsnamen können dir helfen, deine GerĂ€te einfacher zu erkennen."; "user_other_session_filter" = "Filtern"; -"wysiwyg_composer_format_action_strikethrough" = "Unterstrichen formatieren"; -"wysiwyg_composer_format_action_underline" = "Durchgestrichen formatieren"; +"wysiwyg_composer_format_action_strikethrough" = "Durchgestrichen formatieren"; +"wysiwyg_composer_format_action_underline" = "Unterstrichen formatieren"; "wysiwyg_composer_format_action_italic" = "Kursiv formatieren"; // Formatting Actions diff --git a/Riot/Assets/en.lproj/InfoPlist.strings b/Riot/Assets/en.lproj/InfoPlist.strings index edab7c3751..c26e49651f 100644 --- a/Riot/Assets/en.lproj/InfoPlist.strings +++ b/Riot/Assets/en.lproj/InfoPlist.strings @@ -21,5 +21,5 @@ "NSContactsUsageDescription" = "In order to show who among your contacts already uses Tchap, we can exploit the e-mail addresses of your address book. These data will not be stored. For more information, please visit the privacy policy page in the app settings."; "NSCalendarsUsageDescription" = "See your scheduled meetings in the app."; "NSFaceIDUsageDescription" = "Face ID is used to access your app."; -"NSLocationWhenInUseUsageDescription" = "When you share your location to people, Element needs access to show them a map."; -"NSLocationAlwaysAndWhenInUseUsageDescription" = "When you share your location to people, Element needs access to show them a map."; +"NSLocationWhenInUseUsageDescription" = "When you share your location to people, Tchap needs access to show them a map."; +"NSLocationAlwaysAndWhenInUseUsageDescription" = "When you share your location to people, Tchap needs access to show them a map."; diff --git a/Riot/Assets/en.lproj/Vector.strings b/Riot/Assets/en.lproj/Vector.strings index 0cb9b07a5b..06cc580561 100644 --- a/Riot/Assets/en.lproj/Vector.strings +++ b/Riot/Assets/en.lproj/Vector.strings @@ -378,7 +378,7 @@ "room_recents_conversations_section" = "ROOMS"; "room_recents_no_conversation" = "No rooms"; "room_recents_low_priority_section" = "LOW PRIORITY"; -"room_recents_server_notice_section" = "SYSTEM ALERTS"; +"room_recents_server_notice_section" = "Tchap Announcements"; // Tchap "room_recents_invites_section" = "INVITES"; "room_recents_suggested_rooms_section" = "SUGGESTED ROOMS"; "room_recents_start_chat_with" = "Start chat"; @@ -727,7 +727,7 @@ Tap the + to start adding people."; "settings_devices" = "SESSIONS"; "settings_cryptography" = "CRYPTOGRAPHY"; "settings_key_backup" = "KEY BACKUP"; -"settings_deactivate_account" = "DEACTIVATE ACCOUNT"; +"settings_deactivate_account" = "CLOSE ACCOUNT"; // Tchap "settings_sign_out" = "Sign Out"; "settings_sign_out_confirmation" = "Are you sure?"; @@ -759,7 +759,7 @@ Tap the + to start adding people."; "settings_security" = "SECURITY"; -"settings_enable_push_notif" = "Notifications on this device"; +"settings_enable_push_notif" = "Enable notifications on this device"; // Tchap "settings_device_notifications" = "Device notifications"; "settings_show_decrypted_content" = "Show decrypted content"; "settings_global_settings_info" = "Global notification settings are available on your %@ web client"; @@ -860,7 +860,7 @@ Tap the + to start adding people."; "settings_crypto_export" = "Export keys"; "settings_crypto_blacklist_unverified_devices" = "Encrypt to verified sessions only"; -"settings_deactivate_my_account" = "Deactivate account permanently"; +"settings_deactivate_my_account" = "Close account"; // Tchap "settings_key_backup_info" = "Encrypted messages are secured with end-to-end encryption. Only you and the recipient(s) have the keys to read these messages."; "settings_key_backup_info_checking" = "Checking
"; @@ -1384,21 +1384,21 @@ Tap the + to start adding people."; // Deactivate account -"deactivate_account_title" = "Deactivate Account"; +"deactivate_account_title" = "Close Account"; // Tchap "deactivate_account_informations_part1" = "This will make your account permanently unusable. You will not be able to log in, and no one will be able to re-register the same user ID. This will cause your account to leave all rooms it is participating in, and it will remove your account details from your identity server. "; "deactivate_account_informations_part2_emphasize" = "This action is irreversible."; -"deactivate_account_informations_part3" = "\n\nDeactivating your account "; +"deactivate_account_informations_part3" = "\n\nClosing your account "; // Tchap "deactivate_account_informations_part4_emphasize" = "does not by default cause us to forget messages you have sent. "; "deactivate_account_informations_part5" = "If you would like us to forget your messages, please tick the box below\n\nMessage visibility in Matrix is similar to email. Our forgetting your messages means that messages you have sent will not be shared with any new or unregistered users, but registered users who already have access to these messages will still have access to their copy."; -"deactivate_account_forget_messages_information_part1" = "Please forget all messages I have sent when my account is deactivated ("; +"deactivate_account_forget_messages_information_part1" = "Please forget all messages I have sent when my account is closed ("; // Tchap "deactivate_account_forget_messages_information_part2_emphasize" = "Warning"; "deactivate_account_forget_messages_information_part3" = ": this will cause future users to see an incomplete view of conversations)"; -"deactivate_account_validate_action" = "Deactivate account"; +"deactivate_account_validate_action" = "Close account"; // Tchap -"deactivate_account_password_alert_title" = "Deactivate Account"; +"deactivate_account_password_alert_title" = "Close Account"; // Tchap "deactivate_account_password_alert_message" = "To continue, please enter your Matrix account password"; // Re-request confirmation dialog @@ -2453,8 +2453,8 @@ Tap the + to start adding people."; "location_sharing_settings_toggle_title" = "Enable location sharing"; "location_sharing_allow_background_location_title" = "Allow access"; -"location_sharing_allow_background_location_message" = "If you’d like to share your Live location, Element needs location access when the app is in the background. -To enable access, tap Settings> Location and select Always"; +"location_sharing_allow_background_location_message" = "If you’d like to share your Live location, Tchap needs location access when the app is in the background. +To enable access, tap Settings> Location and select Always"; // Tchap "location_sharing_allow_background_location_validate_action" = "Settings"; "location_sharing_allow_background_location_cancel_action" = "Not now"; "location_sharing_map_credits_title" = "© Copyright"; @@ -2619,8 +2619,8 @@ To enable access, tap Settings> Location and select Always"; // Formatting Actions "wysiwyg_composer_format_action_bold" = "Apply bold format"; "wysiwyg_composer_format_action_italic" = "Apply italic format"; -"wysiwyg_composer_format_action_underline" = "Apply strikethrough format"; -"wysiwyg_composer_format_action_strikethrough" = "Apply underline format"; +"wysiwyg_composer_format_action_underline" = "Apply underline format"; +"wysiwyg_composer_format_action_strikethrough" = "Apply strikethrough format"; "wysiwyg_composer_format_action_link" = "Apply link format"; "wysiwyg_composer_format_action_inline_code" = "Apply inline code format"; "wysiwyg_composer_format_action_unordered_list" = "Toggle bulleted list"; @@ -2773,7 +2773,7 @@ To enable access, tap Settings> Location and select Always"; "notice_room_history_visible_to_members_from_joined_point" = "%@ made future room history visible to all room members, from the point they joined."; "notice_room_history_visible_to_members_from_joined_point_for_dm" = "%@ made future messages visible to everyone, from when they joined."; "notice_crypto_unable_to_decrypt" = "** Unable to decrypt: %@ **"; -"notice_crypto_error_unknown_inbound_session_id" = "The sender's session has not sent us the keys for this message."; +"notice_crypto_error_unknown_inbound_session_id" = "Decrypting
"; // Tchap "notice_sticker" = "sticker"; "notice_in_reply_to" = "In reply to"; "notice_voice_broadcast_live" = "Live broadcast"; diff --git a/Riot/Assets/et.lproj/Vector.strings b/Riot/Assets/et.lproj/Vector.strings index 5080a7ea52..22e9f34192 100644 --- a/Riot/Assets/et.lproj/Vector.strings +++ b/Riot/Assets/et.lproj/Vector.strings @@ -1889,8 +1889,8 @@ "notice_conference_call_started" = "VoIP rĂŒhmakĂ”ne algas"; "notice_conference_call_finished" = "VoIP rĂŒhmakĂ”ne lĂ”ppes"; // Notice Events with "You" -"notice_room_invite_by_you" = "Sina kutsusid kasutajat %@"; -"notice_room_invite_you" = "%@ kutsus sind"; +"notice_room_invite_by_you" = "Sina saatsid kutse kasutajale %@"; +"notice_room_invite_you" = "%@ saatis sulle kutse"; "notice_room_third_party_invite_by_you" = "Sina saatsid kasutajale %@ kutse jututoaga liitumiseks"; "notice_room_third_party_registered_invite_by_you" = "Sina vĂ”tsid vastu kutse %@ nimel"; "notice_room_third_party_revoked_invite_by_you" = "Sina vĂ”tsid tagasi jututoaga liitumise kutse kasutajalt %@"; @@ -2025,7 +2025,7 @@ "notice_room_third_party_invite_for_dm" = "%@ saatis kutse kasutajale %@"; "notice_room_third_party_revoked_invite_for_dm" = "%@ vĂ”ttis tagasi kasutaja %@ kutse"; "notice_room_name_changed_for_dm" = "%@ muutis jututoa uueks nimeks %@."; -"notice_room_third_party_invite_by_you_for_dm" = "Sina kutsusid kasutajat %@"; +"notice_room_third_party_invite_by_you_for_dm" = "Sina saatsid kutse kasutajale %@"; "notice_room_third_party_revoked_invite_by_you_for_dm" = "Sina vĂ”tsid tagasi kasutaja %@ kutse"; "notice_room_name_changed_by_you_for_dm" = "Sa muutsid jututoa uueks nimeks %@."; "notice_room_name_removed_by_you_for_dm" = "Sa eemaldasid jututoa nime"; @@ -2499,8 +2499,8 @@ "authentication_qr_login_start_title" = "Loe QR-koodi"; "authentication_login_with_qr" = "Logi sisse QR-koodi abil"; "wysiwyg_composer_format_action_strikethrough" = "Kasuta allajoonitud kirja"; -"wysiwyg_composer_format_action_underline" = "Kasuta lĂ€bijoonitud kirja"; -"wysiwyg_composer_format_action_italic" = "Kasuta kaldkirja"; +"wysiwyg_composer_format_action_italic" = "Kasuta lĂ€bijoonitud kirja"; +"wysiwyg_composer_format_action_underline" = "Kasuta kaldkirja"; // Formatting Actions "wysiwyg_composer_format_action_bold" = "Kasuta paksu kirja"; diff --git a/Riot/Assets/fr.lproj/InfoPlist.strings b/Riot/Assets/fr.lproj/InfoPlist.strings index 74885bea35..be710f7e8a 100644 --- a/Riot/Assets/fr.lproj/InfoPlist.strings +++ b/Riot/Assets/fr.lproj/InfoPlist.strings @@ -4,3 +4,5 @@ "NSMicrophoneUsageDescription" = "Tchap nĂ©cessite l'accĂšs au microphone pour rĂ©aliser des vidĂ©os avec la camĂ©ra, ou pour enregistrer des messages vocaux."; "NSContactsUsageDescription" = "Afin d’afficher qui parmi vos contacts utilise dĂ©jĂ  Tchap, nous pouvons exploiter les adresses e-mails de votre carnet d'adresse. Ces donnĂ©es ne seront pas mĂ©morisĂ©es. Pour plus d'informations, veuillez consulter les Termes et Conditions disponibles dans les paramĂštres de l'application."; "NSFaceIDUsageDescription" = "Face ID est utilisĂ© pour accĂ©der Ă  votre application."; +"NSLocationWhenInUseUsageDescription" = "Lorsque vous partagez votre position, Tchap a besoin de votre autorisation pour l'afficher sur une carte."; +"NSLocationAlwaysAndWhenInUseUsageDescription" = "Lorsque vous partagez votre position, Tchap a besoin de votre autorisation pour l'afficher sur une carte."; diff --git a/Riot/Assets/fr.lproj/Vector.strings b/Riot/Assets/fr.lproj/Vector.strings index e48f772de3..4f5392ff72 100644 --- a/Riot/Assets/fr.lproj/Vector.strings +++ b/Riot/Assets/fr.lproj/Vector.strings @@ -279,7 +279,7 @@ "settings_add_phone_number" = "Ajouter un numĂ©ro de tĂ©lĂ©phone"; "settings_night_mode" = "Mode nuit"; "settings_fail_to_update_profile" = "Échec de la mise Ă  jour du profil"; -"settings_enable_push_notif" = "Notifications sur cet appareil"; +"settings_enable_push_notif" = "Autoriser les notifications sur cet appareil"; // Tchap "settings_global_settings_info" = "Les paramĂštres de notification globaux sont disponibles sur votre client web %@"; "settings_pin_rooms_with_missed_notif" = "Épingler les salons avec des notifications non lues"; "settings_pin_rooms_with_unread" = "Épingler les salons avec des messages non lus"; @@ -520,21 +520,21 @@ "room_action_send_photo_or_video" = "Envoyer une photo ou une vidĂ©o"; "room_action_send_sticker" = "Envoyer un autocollant"; "room_action_send_file" = "Envoyer un fichier"; -"settings_deactivate_account" = "DÉSACTIVER LE COMPTE"; -"settings_deactivate_my_account" = "DĂ©sactiver le compte pour toujours"; +"settings_deactivate_account" = "FERMER LE COMPTE"; // Tchap +"settings_deactivate_my_account" = "Fermer le compte"; // Tchap "widget_sticker_picker_no_stickerpacks_alert" = "Vous n’avez aucun jeu d’autocollants activĂ©."; "widget_sticker_picker_no_stickerpacks_alert_add_now" = "En ajouter maintenant ?"; -"deactivate_account_title" = "DĂ©sactiver le compte"; +"deactivate_account_title" = "Fermer le compte"; // Tchap "deactivate_account_informations_part1" = "Votre compte sera inutilisable de façon permanente. Vous ne pourrez plus vous connecter et personne ne pourra se rĂ©enregistrer avec le mĂȘme identifiant d’utilisateur. Votre compte quittera tous les salons auxquels il participe, et toutes les informations du compte seront supprimĂ©s du serveur d’identitĂ©. "; "deactivate_account_informations_part2_emphasize" = "Cette action est irrĂ©versible."; -"deactivate_account_informations_part3" = "\n\nLa dĂ©sactivation de votre compte "; +"deactivate_account_informations_part3" = "\n\nLa fermeture de votre compte "; // Tchap "deactivate_account_informations_part4_emphasize" = "ne nous fait pas oublier les messages que vous avez envoyĂ©s par dĂ©faut. "; "deactivate_account_informations_part5" = "Si vous souhaitez que nous oubliions vos messages, cochez la case ci-dessous\n\nLa visibilitĂ© des messages dans Matrix est similaire Ă  celle des e-mails. Notre oubli des messages signifie que les messages que vous avez envoyĂ©s ne seront pas partagĂ©s avec les nouveaux utilisateurs ou les utilisateurs non enregistrĂ©s, mais les utilisateurs qui ont dĂ©jĂ  accĂšs Ă  ces messages auront toujours accĂšs Ă  leur copie."; -"deactivate_account_forget_messages_information_part1" = "Veuillez oublier tous les messages que j’ai envoyĂ©s quand mon compte sera dĂ©sactivĂ© ("; +"deactivate_account_forget_messages_information_part1" = "Veuillez oublier tous les messages que j’ai envoyĂ©s quand mon compte sera fermĂ© ("; // Tchap "deactivate_account_forget_messages_information_part2_emphasize" = "Avertissement"; "deactivate_account_forget_messages_information_part3" = ": les futurs utilisateurs auront alors une vue incomplĂšte des conversations)"; -"deactivate_account_validate_action" = "DĂ©sactiver le compte"; -"deactivate_account_password_alert_title" = "DĂ©sactiver le compte"; +"deactivate_account_validate_action" = "Fermer le compte"; // Tchap +"deactivate_account_password_alert_title" = "Fermer le compte"; // Tchap "deactivate_account_password_alert_message" = "Pour continuer, veuillez renseigner votre mot de passe"; "event_formatter_rerequest_keys_part1" = "Demande envoyĂ©e.\U00A0"; "event_formatter_rerequest_keys_part2_link" = "Renvoyer"; @@ -551,7 +551,7 @@ "settings_labs_room_members_lazy_loading" = "Chargement diffĂ©rĂ© des participants des salons"; "settings_labs_room_members_lazy_loading_error_message" = "Votre serveur d'accueil ne prend pas en charge le chargement diffĂ©rĂ© des participants des salons. RĂ©essayez plus tard."; "room_event_action_view_decrypted_source" = "Voir la source dĂ©chiffrĂ©e"; -"room_recents_server_notice_section" = "ALERTES SYSTÈME"; +"room_recents_server_notice_section" = "Tchap Annonces"; // Tchap "room_resource_limit_exceeded_message_contact_1" = " Veuillez "; "room_resource_limit_exceeded_message_contact_2_link" = "contacter l’administrateur de votre service"; "room_resource_limit_exceeded_message_contact_3" = " pour continuer Ă  l’utiliser."; @@ -1807,7 +1807,7 @@ "notice_room_history_visible_to_members_from_invited_point" = "%@ a rendu l’historique futur du salon visible Ă  tous les membres, Ă  partir du moment oĂč ils ont Ă©tĂ© invitĂ©s."; "notice_room_history_visible_to_members_from_joined_point" = "%@ a rendu l’historique futur du salon visible Ă  tous les membres, Ă  partir de leur arrivĂ©e."; "notice_crypto_unable_to_decrypt" = "** DĂ©chiffrement impossible : %@ **"; -"notice_crypto_error_unknown_inbound_session_id" = "La session de l’expĂ©diteur ne nous a pas envoyĂ© les clĂ©s pour ce message."; +"notice_crypto_error_unknown_inbound_session_id" = "DĂ©chiffrement en cours
"; // Tchap "notice_sticker" = "autocollant"; // room display name "room_displayname_empty_room" = "Salon vide"; @@ -2324,7 +2324,7 @@ "authentication_registration_title" = "CrĂ©ez votre compte"; "room_access_settings_screen_upgrade_alert_title" = "Mettre Ă  niveau le salon"; "room_access_settings_screen_upgrade_required" = "Mise Ă  niveau requise"; -"location_sharing_allow_background_location_message" = "Si vous voulez partager votre localisation en temps rĂ©el, Element doit avoir accĂšs Ă  vos donnĂ©es de localisation lorsque l’application est en arriĂšre-plan. Pour lui donner accĂšs, rendez-vous dans RĂ©glages > Position et sĂ©lectionnez Toujours"; +"location_sharing_allow_background_location_message" = "Si vous voulez partager votre localisation en temps rĂ©el, Tchap doit avoir accĂšs Ă  vos donnĂ©es de localisation lorsque l’application est en arriĂšre-plan. Pour lui donner accĂšs, rendez-vous dans RĂ©glages > Position et sĂ©lectionnez Toujours"; // Tchap "settings_timeline" = "HISTORIQUE"; "authentication_server_selection_login_title" = "Connexion Ă  un serveur d’accueil"; "message_reply_to_sender_sent_their_live_location" = "Localisation en temps rĂ©el."; @@ -2669,8 +2669,8 @@ "wysiwyg_composer_format_action_unordered_list" = "Liste Ă  puces"; "wysiwyg_composer_format_action_inline_code" = "Formater comme code informatique"; "wysiwyg_composer_format_action_link" = "Formater comme lien"; -"wysiwyg_composer_format_action_strikethrough" = "Souligner"; -"wysiwyg_composer_format_action_underline" = "Barrer"; +"wysiwyg_composer_format_action_strikethrough" = "Barrer"; +"wysiwyg_composer_format_action_underline" = "Souligner"; "wysiwyg_composer_format_action_italic" = "Mettre en italique"; // Formatting Actions @@ -2797,4 +2797,3 @@ "local_contacts_access_discovery_warning" = "Afin d’afficher qui parmi vos contacts utilise dĂ©jĂ  Tchap, nous pouvons exploiter les adresses e-mails de votre carnet d'adresse. Ces donnĂ©es ne seront pas mĂ©morisĂ©es. Pour plus d'informations, veuillez consulter les Termes et Conditions disponibles dans les paramĂštres de l'application."; // Events formatter "notice_crypto_unable_to_decrypt" = "Message verrouillĂ©."; // Tchap -"notice_crypto_error_unknown_inbound_session_id" = "Ouvrez Tchap sur un autre appareil pour rĂ©cupĂ©rer vos messages."; // Tchap diff --git a/Riot/Assets/hu.lproj/Vector.strings b/Riot/Assets/hu.lproj/Vector.strings index a77322f083..274042639a 100644 --- a/Riot/Assets/hu.lproj/Vector.strings +++ b/Riot/Assets/hu.lproj/Vector.strings @@ -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_strikethrough" = "AlĂĄhĂșzott"; -"wysiwyg_composer_format_action_underline" = "ÁthĂșzott"; +"wysiwyg_composer_format_action_underline" = "AlĂĄhĂșzott"; +"wysiwyg_composer_format_action_strikethrough" = "ÁthĂșzott"; "wysiwyg_composer_format_action_italic" = "DƑlt"; // Formatting Actions diff --git a/Riot/Assets/id.lproj/Vector.strings b/Riot/Assets/id.lproj/Vector.strings index 0a43411c32..5e8e1c027e 100644 --- a/Riot/Assets/id.lproj/Vector.strings +++ b/Riot/Assets/id.lproj/Vector.strings @@ -2782,8 +2782,8 @@ "authentication_qr_login_start_subtitle" = "Gunakan kamera pada perangkat ini untuk memindai kode QR yang ditampilkan di perangkat Anda yang lain:"; "authentication_qr_login_start_title" = "Pindai kode QR"; "authentication_login_with_qr" = "Masuk dengan kode QR"; -"wysiwyg_composer_format_action_strikethrough" = "Terapkan format garis bawah"; -"wysiwyg_composer_format_action_underline" = "Terapkan format coret"; +"wysiwyg_composer_format_action_underline" = "Terapkan format garis bawah"; +"wysiwyg_composer_format_action_strikethrough" = "Terapkan format coret"; "wysiwyg_composer_format_action_italic" = "Terapkan format miring"; // Formatting Actions diff --git a/Riot/Assets/it.lproj/Vector.strings b/Riot/Assets/it.lproj/Vector.strings index 5658e74789..0e893b97cb 100644 --- a/Riot/Assets/it.lproj/Vector.strings +++ b/Riot/Assets/it.lproj/Vector.strings @@ -2555,8 +2555,8 @@ "authentication_qr_login_start_subtitle" = "Usa la fotocamera di questo dispositivo per scansionare il codice QR mostrato nell'altro dispositivo:"; "authentication_qr_login_start_title" = "Scansiona codice QR"; "authentication_login_with_qr" = "Accedi con codice QR"; -"wysiwyg_composer_format_action_strikethrough" = "Applica formato sottolineato"; -"wysiwyg_composer_format_action_underline" = "Applica formato sbarrato"; +"wysiwyg_composer_format_action_underline" = "Applica formato sottolineato"; +"wysiwyg_composer_format_action_strikethrough" = "Applica formato sbarrato"; "wysiwyg_composer_format_action_italic" = "Applica formato corsivo"; // Formatting Actions diff --git a/Riot/Assets/ja.lproj/Vector.strings b/Riot/Assets/ja.lproj/Vector.strings index c1d7678303..0dfb2562ba 100644 --- a/Riot/Assets/ja.lproj/Vector.strings +++ b/Riot/Assets/ja.lproj/Vector.strings @@ -2752,8 +2752,8 @@ "notice_error_unformattable_event" = "** ăƒĄăƒƒă‚»ăƒŒă‚žă‚’æç”»ă§ăăŸă›ă‚“ă€‚äžć…·ćˆă‚’ć ±ć‘Šă—ăŠăă ă•ă„"; "wysiwyg_composer_format_action_un_indent" = "ă‚€ăƒłăƒ‡ăƒłăƒˆă‚’æž›ă‚‰ă™"; "wysiwyg_composer_format_action_indent" = "ă‚€ăƒłăƒ‡ăƒłăƒˆă‚’ćą—ă‚„ă™"; -"wysiwyg_composer_format_action_strikethrough" = "äž‹ç·šă§èŁ…éŁŸ"; -"wysiwyg_composer_format_action_underline" = "æ‰“ăĄæ¶ˆă—ç·šă§èŁ…éŁŸ"; +"wysiwyg_composer_format_action_underline" = "äž‹ç·šă§èŁ…éŁŸ"; +"wysiwyg_composer_format_action_strikethrough" = "æ‰“ăĄæ¶ˆă—ç·šă§èŁ…éŁŸ"; // MARK: - WYSIWYG Composer diff --git a/Riot/Assets/nl.lproj/Vector.strings b/Riot/Assets/nl.lproj/Vector.strings index a814c281b5..f0ddd9d106 100644 --- a/Riot/Assets/nl.lproj/Vector.strings +++ b/Riot/Assets/nl.lproj/Vector.strings @@ -2646,8 +2646,8 @@ "invite_to" = "Uitnodigen %@"; "room_event_encryption_info_key_authenticity_not_guaranteed" = "De authenticiteit van dit versleutelde bericht kan niet worden gegarandeerd op dit apparaat."; "deselect_all" = "Deselecteer alles"; -"wysiwyg_composer_format_action_strikethrough" = "Onderstrepen formaat toepassen"; -"wysiwyg_composer_format_action_underline" = "Doorstrepen formaat toepassen"; +"wysiwyg_composer_format_action_underline" = "Onderstrepen formaat toepassen"; +"wysiwyg_composer_format_action_strikethrough" = "Doorstrepen formaat toepassen"; "wysiwyg_composer_format_action_italic" = "Cursief formaat toepassen"; // Formatting Actions diff --git a/Riot/Assets/peo.lproj/InfoPlist.strings b/Riot/Assets/peo.lproj/InfoPlist.strings new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/Riot/Assets/peo.lproj/InfoPlist.strings @@ -0,0 +1 @@ + diff --git a/Riot/Assets/pl.lproj/Vector.strings b/Riot/Assets/pl.lproj/Vector.strings index 21be89f5de..ba5a2bc091 100644 --- a/Riot/Assets/pl.lproj/Vector.strings +++ b/Riot/Assets/pl.lproj/Vector.strings @@ -2694,8 +2694,8 @@ "wysiwyg_composer_format_action_unordered_list" = "PrzeƂącz listę punktorĂłw"; "wysiwyg_composer_format_action_inline_code" = "Zastosuj kod w tekƛcie"; "wysiwyg_composer_format_action_link" = "Zastosuj link"; -"wysiwyg_composer_format_action_strikethrough" = "Zastosuj podkreƛlenie"; -"wysiwyg_composer_format_action_underline" = "Zastosuj przekreƛlenie"; +"wysiwyg_composer_format_action_underline" = "Zastosuj podkreƛlenie"; +"wysiwyg_composer_format_action_strikethrough" = "Zastosuj przekreƛlenie"; "wysiwyg_composer_format_action_italic" = "Zastosuj kursywę"; // Formatting Actions diff --git a/Riot/Assets/pt_BR.lproj/Vector.strings b/Riot/Assets/pt_BR.lproj/Vector.strings index d60ed99b62..991716cd9f 100644 --- a/Riot/Assets/pt_BR.lproj/Vector.strings +++ b/Riot/Assets/pt_BR.lproj/Vector.strings @@ -2556,8 +2556,8 @@ "authentication_qr_login_start_subtitle" = "Use a cĂąmera neste dispositivo para scannar o QR code mostrado em seu outro dispositivo:"; "authentication_qr_login_start_title" = "Scannar QR code"; "authentication_login_with_qr" = "Fazer signin com QR code"; -"wysiwyg_composer_format_action_strikethrough" = "Aplicar formato sublinhar"; -"wysiwyg_composer_format_action_underline" = "Aplicar formato tachar"; +"wysiwyg_composer_format_action_underline" = "Aplicar formato sublinhar"; +"wysiwyg_composer_format_action_strikethrough" = "Aplicar formato tachar"; "wysiwyg_composer_format_action_italic" = "Aplicar formato itĂĄlico"; // Formatting Actions diff --git a/Riot/Assets/sk.lproj/Vector.strings b/Riot/Assets/sk.lproj/Vector.strings index 6f4eda6a4a..a18873e4d8 100644 --- a/Riot/Assets/sk.lproj/Vector.strings +++ b/Riot/Assets/sk.lproj/Vector.strings @@ -2778,8 +2778,8 @@ "authentication_qr_login_start_subtitle" = "Pomocou fotoaparĂĄtu na tomto zariadenĂ­ naskenujte QR kĂłd zobrazenĂœ na vaĆĄom druhom zariadenĂ­:"; "authentication_qr_login_start_title" = "SkenovaĆ„ QR kĂłd"; "authentication_login_with_qr" = "PrihlĂĄsiĆ„ sa pomocou QR kĂłdu"; -"wysiwyg_composer_format_action_strikethrough" = "PouĆŸiĆ„ formĂĄt podčiarknutia"; -"wysiwyg_composer_format_action_underline" = "PouĆŸiĆ„ formĂĄt prečiarknutia"; +"wysiwyg_composer_format_action_underline" = "PouĆŸiĆ„ formĂĄt podčiarknutia"; +"wysiwyg_composer_format_action_strikethrough" = "PouĆŸiĆ„ formĂĄt prečiarknutia"; "wysiwyg_composer_format_action_italic" = "PouĆŸiĆ„ formĂĄt kurzĂ­vou"; // Formatting Actions diff --git a/Riot/Assets/sq.lproj/InfoPlist.strings b/Riot/Assets/sq.lproj/InfoPlist.strings index a4d354d560..e2f9af626f 100644 --- a/Riot/Assets/sq.lproj/InfoPlist.strings +++ b/Riot/Assets/sq.lproj/InfoPlist.strings @@ -5,5 +5,5 @@ "NSContactsUsageDescription" = "Do t’i jepen shĂ«rbyesit tuaj tĂ« identiteteve, pĂ«r ta ndihmuar tĂ« gjejĂ« kontakte tuajt nĂ« Matrix."; "NSCalendarsUsageDescription" = "Shihini te aplikacioni takimet tuaja tĂ« planifikuara."; "NSFaceIDUsageDescription" = "Face ID pĂ«rdoret qĂ« tĂ« hyni nĂ« aplikacionin tuaj."; -"NSLocationWhenInUseUsageDescription" = "Kur ndani vendndodhjen tuaj me persona, Element-i ka nevojĂ« pĂ«r hyrje nĂ« tĂ«, qĂ« t’u trgojĂ« atyre njĂ« hartĂ«."; +"NSLocationWhenInUseUsageDescription" = "Kur u tregoni vendndodhjen tuaj tĂ« tjerĂ«ve, Element-i ka nevojĂ« pĂ«r hyrje nĂ« tĂ«, qĂ« t’u tregojĂ« atyre njĂ« hartĂ«."; "NSLocationAlwaysAndWhenInUseUsageDescription" = "Kur u tregoni vendndodhjen tuaj tĂ« tjerĂ«ve, Element-it i duhet hyrje pĂ«r t’u shfaqur njĂ« hartĂ«."; diff --git a/Riot/Assets/sq.lproj/Vector.strings b/Riot/Assets/sq.lproj/Vector.strings index 1dc4968b50..cd338d626b 100644 --- a/Riot/Assets/sq.lproj/Vector.strings +++ b/Riot/Assets/sq.lproj/Vector.strings @@ -127,8 +127,8 @@ "contacts_user_directory_offline_section" = "DREJTORI PËRDORUESISH (jashtĂ« linje)"; // Chat participants "room_participants_title" = "PjesĂ«marrĂ«s"; -"room_participants_add_participant" = "Shtoni pjesmarrĂ«s"; -"room_participants_one_participant" = "1 pjesmarrĂ«s"; +"room_participants_add_participant" = "Shtoni pjesĂ«marrĂ«s"; +"room_participants_one_participant" = "1 pjesĂ«marrĂ«s"; "room_participants_multi_participants" = "%d pjesĂ«marrĂ«s"; "room_participants_leave_prompt_title" = "Dilni nga dhomĂ«"; "room_participants_leave_prompt_msg" = "Jeni i sigurt se doni tĂ« ikni nga dhoma?"; @@ -317,7 +317,7 @@ "group_home_one_room_format" = "1 dhomĂ«"; "group_invitation_format" = "%@ ju ftoi tĂ« bĂ«heni pjesĂ« e kĂ«saj bashkĂ«sie"; // Group participants -"group_participants_add_participant" = "Shtoni pjesmarrĂ«s"; +"group_participants_add_participant" = "Shtoni pjesĂ«marrĂ«s"; "group_participants_leave_prompt_title" = "Braktiseni grupin"; "group_participants_leave_prompt_msg" = "Jeni i sigurt se doni ta braktisni grupin?"; "group_participants_remove_prompt_title" = "Ripohim"; @@ -398,7 +398,7 @@ "auth_add_email_and_phone_warning" = "Regjistrimi me email dhe me numĂ«r telefoni njĂ«herazi nuk mbulohet ende, deri sa tĂ« ketĂ« API. Do tĂ« merret parasysh vetĂ«m numri i telefonit. Email-in tuaj mund ta shtoni te profili juaj, te rregullimet."; "room_creation_appearance_picture" = "Foto fjalosjeje (nĂ« daçi)"; "room_creation_invite_another_user" = "ID PĂ«rdoruesi, emĂ«r ose email"; -"room_recents_favourites_section" = "TË PARAPALQYERA"; +"room_recents_favourites_section" = "TË PARAPËLQYERA"; "room_recents_server_notice_section" = "SINJALIZIME SISTEMI"; "room_recents_join_room_title" = "Hyni nĂ« njĂ« dhomĂ«"; "room_participants_invite_another_user" = "KĂ«rkoni / ftoni sipas ID-je PĂ«rdoruesi, Emri ose email-i"; @@ -507,7 +507,7 @@ "room_details_history_section_members_only" = "VetĂ«m anĂ«tarĂ«t (qĂ« nga çasti i pĂ«rzgjedhjes sĂ« kĂ«saj mundĂ«sie)"; "room_details_history_section_prompt_msg" = "Ndryshime se cilĂ«t mund tĂ« lexojnĂ« historikun do tĂ« vlejnĂ« vetĂ«m pĂ«r mesazhe tĂ« ardhshĂ«m nĂ« kĂ«tĂ« dhomĂ«. DukshmĂ«ria e historikut ekzistues nuk do tĂ« ndryshohet."; "room_details_addresses_disable_main_address_prompt_msg" = "S’do tĂ« keni adresĂ« kryesore tĂ« specifikuar. Adresa kryesore parazgjedhje pĂ«r kĂ«tĂ« dhomĂ« do tĂ« zgjidhet nĂ« tym"; -"room_details_advanced_enable_e2e_encryption" = "Aktivizo fshehtĂ«zim (kujdes: s’mund tĂ« çaktizohet mĂ«!)"; +"room_details_advanced_enable_e2e_encryption" = "Aktivizo fshehtĂ«zim (kujdes: s’mund tĂ« çaktivizohet mĂ«!)"; "room_details_advanced_e2e_encryption_prompt_message" = "FshehtĂ«zimi skaj-mĂ«-skaj Ă«shtĂ« eksperimental dhe mund tĂ« mos jetĂ« i qĂ«ndrueshĂ«m.\n\nS’duhet t’i zini ende besĂ« pĂ«r sigurim tĂ« dhĂ«nash.\n\nPajisjet s’do tĂ« jenĂ« ende nĂ« gjendje tĂ« shfshehtĂ«zojnĂ« historik nga periudha pĂ«rpara se tĂ« merrnin pjesĂ« te dhomĂ«.\n\nPasi tĂ« jetĂ« aktivizuar fshehtĂ«zimi pĂ«r njĂ« dhomĂ«, s’mund tĂ« çaktivizohet mĂ« (hĂ«pĂ«rhĂ«).\n\nMesazhet e fshehtĂ«zuar s’do tĂ« jenĂ« tĂ« dukshĂ«m nĂ« klientĂ« qĂ« nuk sendĂ«rtojnĂ« ende fshehtĂ«zimin."; "room_details_fail_to_update_room_guest_access" = "S’arrihet tĂ« pĂ«rditĂ«sohet mundĂ«sia e hyrjes nĂ« dhomĂ« tĂ« vizitorĂ«ve"; "group_participants_invite_malformed_id" = "ID e keqformuar. Duhet tĂ« jetĂ« njĂ« ID Matrix, si '@localpart:domain'"; @@ -619,7 +619,7 @@ "key_backup_setup_intro_setup_action_without_existing_backup" = "Fillo tĂ« pĂ«rdorĂ«sh Kopjeruajtje Kyçesh"; "key_backup_setup_intro_setup_action_with_existing_backup" = "PĂ«rdor Kopjeruajtje Kyçesh"; "key_backup_setup_passphrase_title" = "Sigurojeni kopjeruajtjen tuaj me njĂ« FrazĂ« Sigurie"; -"key_backup_setup_passphrase_setup_recovery_key_info" = "Ose, sigurojeni kopjeruajtjen tuaj me njĂ« Kyç Sigurie, duke e ruajtur kĂ«tĂ« diku tĂ« parrezikuar."; +"key_backup_setup_passphrase_setup_recovery_key_info" = "Ose, sigurojeni kopjeruajtjen tuaj me njĂ« Kyç Sigurie, duke e ruajtur kĂ«tĂ« diku tĂ« parrezik."; "key_backup_setup_passphrase_setup_recovery_key_action" = "(TĂ« mĂ«tejshme) Rregullojeni me njĂ« Kyç Sigurie"; "key_backup_setup_success_title" = "Sukses!"; // Success from passphrase @@ -627,7 +627,7 @@ "key_backup_setup_success_from_passphrase_save_recovery_key_action" = "Ruani Kyç Sigurie"; "key_backup_setup_success_from_passphrase_done_action" = "U krye"; // Success from recovery key -"key_backup_setup_success_from_recovery_key_info" = "Po bĂ«het kopjeruajtja pĂ«r kyçet tuaj.\n\nBĂ«ni njĂ« kopje tĂ« kĂ«tij Kyçi Sigurie dhe mbajeni tĂ« parrezikuar."; +"key_backup_setup_success_from_recovery_key_info" = "Po bĂ«het kopjeruajtja pĂ«r kyçet tuaj.\n\nBĂ«ni njĂ« kopje tĂ« kĂ«tij Kyçi Sigurie dhe mbajeni tĂ« parrezik."; "key_backup_setup_success_from_recovery_key_recovery_key_title" = "Kyç Sigurie"; "key_backup_setup_success_from_recovery_key_make_copy_action" = "BĂ«ni njĂ« Kopje"; "key_backup_setup_success_from_recovery_key_made_copy_action" = "Kam bĂ«rĂ« njĂ« kopje"; @@ -1129,7 +1129,7 @@ "secure_key_backup_setup_existing_backup_error_info" = "Shkyçeni, qĂ« ta ripĂ«rdorni te kopjeruajtja e sigurt ose pĂ«r ta fshirĂ« qĂ« tĂ« krijoni njĂ« kopjeruajtje tĂ« re mesazhesh te kopjeruajtja e sigurt."; "secure_key_backup_setup_existing_backup_error_unlock_it" = "Shkyçe"; "secure_key_backup_setup_existing_backup_error_delete_it" = "Fshije"; -"sign_out_non_existing_key_backup_alert_setup_secure_backup_action" = "Fillo tĂ« pĂ«rdorĂ«sh Kojperuajtje tĂ« Sigurt"; +"sign_out_non_existing_key_backup_alert_setup_secure_backup_action" = "Fillo tĂ« pĂ«rdorĂ«sh Kopjeruajtje tĂ« Sigurt"; "security_settings_crypto_sessions_description_2" = "NĂ«se nuk njihni njĂ« palĂ« kredenciale, ndryshoni fjalĂ«kalimin tuaj Matrix dhe riujdisni Kopjeruajtjen e Sigurt."; "cross_signing_setup_banner_title" = "Ujdisni fshehtĂ«zim"; "cross_signing_setup_banner_subtitle" = "Verifikoni mĂ« me lehtĂ«si pajisje tĂ« tjera"; @@ -1517,7 +1517,7 @@ "poll_timeline_vote_not_registered_action" = "OK"; "poll_timeline_vote_not_registered_subtitle" = "Na ndjeni, vota juaj s’u regjistrua, ju lutemi, riprovoni"; "poll_timeline_vote_not_registered_title" = "VotĂ« e paregjistruar"; -"poll_timeline_total_final_results" = "Rezultati pĂ«rfundimtar, bazua nĂ« %lu votĂ«"; +"poll_timeline_total_final_results" = "Rezultati pĂ«rfundimtar, bazua nĂ« %lu vota"; "poll_timeline_total_final_results_one_vote" = "Rezultati pĂ«rfundimtar, bazua nĂ« 1 votĂ«"; "poll_timeline_total_votes_not_voted" = "%lu vota tĂ« hedhura. QĂ« tĂ« shihni pĂ«rfundimet, votoni"; "poll_timeline_total_one_vote_not_voted" = "1 votĂ« e hedhur. QĂ« tĂ« shihni pĂ«rfundimet, votoni"; @@ -1975,9 +1975,9 @@ // Room members "room_member_ignore_prompt" = "Doni tĂ« fshihen krejt mesazhet nga ky pĂ«rdorues?"; "room_member_power_level_prompt" = "S’do tĂ« jeni nĂ« gjendje ta zhbĂ«ni kĂ«tĂ« ndryshim, ngaqĂ« po e promovoni pĂ«rdoruesin tĂ« ketĂ« tĂ« njĂ«jtĂ«n shkallĂ« pushteti si ju vetĂ«.\nJeni i sigurt?"; -"attachment_e2e_keys_file_prompt" = "Kjo kartelĂ« pĂ«rmban kyçe fshehtĂ«zimi tĂ« eksportur nga njĂ« klient Matrix.\nDoni tĂ« shihni lĂ«ndĂ«n e kartelĂ«s apo tĂ« importoni kyçet qĂ« ajo pĂ«rmban?"; +"attachment_e2e_keys_file_prompt" = "Kjo kartelĂ« pĂ«rmban kyçe fshehtĂ«zimi tĂ« eksportuar nga njĂ« klient Matrix.\nDoni tĂ« shihni lĂ«ndĂ«n e kartelĂ«s apo tĂ« importoni kyçet qĂ« ajo pĂ«rmban?"; "e2e_import_prompt" = "Ky proces ju lejon tĂ« importoni kyçe fshehtĂ«zimi qĂ« keni eksportuar mĂ« parĂ« nga njĂ« tjetĂ«r klient Matrix. Mandej do tĂ« jeni nĂ« gjendje tĂ« shfshehtĂ«zoni çfarĂ«do mesazhesh qĂ« mund tĂ« shfshehtĂ«zojĂ« ai klient tjetĂ«r.\nKartela e eksportit Ă«shtĂ« e mbrojtur me njĂ« frazĂ«kalim. QĂ« tĂ« shfshehtĂ«zoni kartelĂ«n, duhet ta jepni frazĂ«kalimin kĂ«tu."; -"e2e_export_prompt" = "Ky proces ju lejon tĂ« eksportoni te njĂ« kartelĂ« vendore kyçet pĂ«r mesazhe qĂ« keni marrĂ« nĂ« dhoma tĂ« fshehtĂ«zuara. Mandej do tĂ« jeni nĂ« gjendje ta importoni kartelĂ«n te njĂ« tjetĂ«r klient Matrix nĂ« tĂ« ardhmen, qĂ« kĂ«shtu ai klient tĂ« jetĂ« nĂ« gjendje t’i fshehtĂ«zojĂ« kĂ«to mesazhe.\nKartela e eksportuar do t’i lejojĂ«, cilitdo qĂ« mund ta lexojĂ«, tĂ« shfshehtĂ«zojĂ« çfarĂ«do mesazhesh tĂ« fshehtĂ«zuar qĂ« mund tĂ« shihni ju, ndaj duhet tĂ« bĂ«ni kujdes ta mbani tĂ« parrezikuar."; +"e2e_export_prompt" = "Ky proces ju lejon tĂ« eksportoni te njĂ« kartelĂ« vendore kyçet pĂ«r mesazhe qĂ« keni marrĂ« nĂ« dhoma tĂ« fshehtĂ«zuara. Mandej do tĂ« jeni nĂ« gjendje ta importoni kartelĂ«n te njĂ« tjetĂ«r klient Matrix nĂ« tĂ« ardhmen, qĂ« kĂ«shtu ai klient tĂ« jetĂ« nĂ« gjendje t’i fshehtĂ«zojĂ« kĂ«to mesazhe.\nKartela e eksportuar do t’i lejojĂ«, cilitdo qĂ« mund ta lexojĂ«, tĂ« shfshehtĂ«zojĂ« çfarĂ«do mesazhesh tĂ« fshehtĂ«zuar qĂ« mund tĂ« shihni ju, ndaj duhet tĂ« bĂ«ni kujdes ta mbani tĂ« parrezik."; "error_common_message" = "Ndodhi njĂ« gabim. Ju lutemi, riprovoni mĂ« vonĂ«."; // Permissions "camera_access_not_granted_for_call" = "Thirrjet video lypin pĂ«rdorim tĂ« KamerĂ«s, por %@ s’ka leje pĂ«r ta pĂ«rdorur"; @@ -1988,7 +1988,7 @@ "redact" = "Hiqe"; // contacts list screen "invitation_message" = "Do tĂ« doja tĂ« bisedoja me ju me Matrix. PĂ«r tĂ« pasur mĂ« tepĂ«r itĂ« dhĂ«na, ju lutem, vizitoni sajtin http://matrix.org."; -"notification_settings_global_info" = "Rregullimet mbi njoftimet ruhen te llogaria juaj e pĂ«rdoruesit dhe ndahen me krejt klientĂ«t qĂ« i mbulojnĂ« ato (pĂ«rfshi njoftimet nĂ« desktop).\n\nRregullat zbatohen sipas njĂ« radhe; rregulli i parĂ« qĂ« ka pĂ«rputhje pĂ«rcakton lĂ«ndĂ«n pĂ«r mesazhin.\nKĂ«shtu: njoftimet sipas fjalĂ«sh janĂ« mĂ« tĂ« rĂ«ndĂ«sishme se njoftimet sipas dhomash tĂ« cilat janĂ« mĂ« tĂ« rĂ«ndĂ«sishme se njoftimet sipas dĂ«rguesish.\nFor multiple rules of the same kind, the first one in the list that matches takes priority."; +"notification_settings_global_info" = "Rregullimet mbi njoftimet ruhen te llogaria juaj e pĂ«rdoruesit dhe ndahen me krejt klientĂ«t qĂ« i mbulojnĂ« ato (pĂ«rfshi njoftimet nĂ« desktop).\n\nRregullat zbatohen sipas njĂ« radhe; rregulli i parĂ« qĂ« ka pĂ«rputhje pĂ«rcakton lĂ«ndĂ«n pĂ«r mesazhin.\nKĂ«shtu: njoftimet sipas fjalĂ«sh janĂ« mĂ« tĂ« rĂ«ndĂ«sishme se njoftimet sipas dhomash tĂ« cilat janĂ« mĂ« tĂ« rĂ«ndĂ«sishme se njoftimet sipas dĂ«rguesish.\nPĂ«r rregulla tĂ« shumta tĂ« tĂ« njĂ«jtit lloj, i pari nĂ« listĂ« qĂ« ka pĂ«rputhje ka pĂ«rparĂ«sinĂ«."; "notification_settings_per_word_notifications" = "Njoftime sipas fjale"; "notification_settings_per_word_info" = "PĂ«r fjalĂ«t pĂ«rputhjet gjenden pa marrĂ« parasysh shkrimin me tĂ« madhe apo tĂ« vogĂ«l, dhe mund tĂ« pĂ«rfshijnĂ« njĂ« shenjĂ« tĂ« gjithĂ«pushtetshme *. KĂ«shtu:\nkot pĂ«rputhet me vargun kot tĂ« rrethuar nga pĂ«rkufizues fjalĂ«sh (p.sh. shenja pikĂ«simi apo hapĂ«sira, ose fillim/fund rreshti).\nkot* pĂ«rputhet me çfarĂ«do fjale qĂ« fillon me kot.\n*kot* pĂ«rputhet me çfarĂ«do fjale qĂ« pĂ«rfshin 3 shkronjat kot."; "notification_settings_per_room_notifications" = "Njoftime sipas dhome"; @@ -2420,9 +2420,9 @@ "all_chats_edit_layout_add_section_message" = "Fiksoni ndarje te kreu, pĂ«r hyrje tĂ« lehtĂ« nĂ« ta"; "room_event_encryption_info_key_authenticity_not_guaranteed" = "S’mund tĂ« garantohet mirĂ«filltĂ«sia e kĂ«tij mesazhi tĂ« fshehtĂ«zuar nĂ« kĂ«tĂ« pajisje."; "deselect_all" = "ShpĂ«rzgjidhi Krejt"; -"wysiwyg_composer_format_action_strikethrough" = "Apliko format me tĂ« nĂ«nvizuara"; -"wysiwyg_composer_format_action_underline" = "Apliko format me tĂ« hequravije"; -"wysiwyg_composer_format_action_italic" = "Apliko format me tĂ« pjerrta"; +"wysiwyg_composer_format_action_underline" = "Apliko format me tĂ« nĂ«nvizuara"; +"wysiwyg_composer_format_action_strikethrough" = "Apliko format me tĂ« hequravije"; +"wysiwyg_composer_format_action_italic" = "Apliko format me tĂ« pjerrĂ«ta"; // Formatting Actions "wysiwyg_composer_format_action_bold" = "Apliko format me tĂ« trasha"; @@ -2448,7 +2448,7 @@ "user_session_details_device_os" = "Sistem Operativ"; "user_session_details_device_browser" = "Shfletues"; "user_session_details_device_model" = "Model"; -"user_session_details_device_ip_location" = "Venndodhje IP-je"; +"user_session_details_device_ip_location" = "Vendndodhje IP-je"; "user_session_details_device_ip_address" = "AdresĂ« IP"; "user_session_details_last_activity" = "Veprimtaria e fundit"; "user_session_details_session_section_footer" = "Kopjoni çfarĂ«do tĂ« dhĂ«ne duke prekur mbi tĂ« dhe duke e mbajtur tĂ« shtypur."; @@ -2660,7 +2660,7 @@ "wysiwyg_composer_format_action_link" = "Apliko formatim lidhjeje"; "notice_voice_broadcast_ended_by_you" = "PĂ«rfunduar njĂ« transmetim zanor."; "notice_voice_broadcast_ended" = "%@ pĂ«rfundoi njĂ« transmetim zanor."; -"notice_voice_broadcast_live" = "Transmetim i drejtĂ«pĂ«rdrejtĂ«"; +"notice_voice_broadcast_live" = "Transmetim i drejtpĂ«rdrejtĂ«"; "user_other_session_security_recommendation_title" = "Sesione tĂ« tjerĂ«"; "poll_timeline_ended_text" = "PĂ«rfundoi pyetĂ«sori"; "poll_timeline_decryption_error" = "PĂ«r shkak gabimesh shfshehtĂ«zimi, mund tĂ« mos jenĂ« numĂ«ruar disa vota"; diff --git a/Riot/Assets/sv.lproj/Vector.strings b/Riot/Assets/sv.lproj/Vector.strings index cb9918a3a4..85662f0292 100644 --- a/Riot/Assets/sv.lproj/Vector.strings +++ b/Riot/Assets/sv.lproj/Vector.strings @@ -2485,8 +2485,8 @@ "wysiwyg_composer_format_action_unordered_list" = "VĂ€xla punktlista"; "wysiwyg_composer_format_action_inline_code" = "TillĂ€mpa inline-kodstil"; "wysiwyg_composer_format_action_link" = "TillĂ€mpa lĂ€nkformat"; -"wysiwyg_composer_format_action_strikethrough" = "TillĂ€mpa understruken stil"; -"wysiwyg_composer_format_action_underline" = "TillĂ€mpa genomstruken stil"; +"wysiwyg_composer_format_action_underline" = "TillĂ€mpa understruken stil"; +"wysiwyg_composer_format_action_strikethrough" = "TillĂ€mpa genomstruken stil"; "wysiwyg_composer_format_action_italic" = "TillĂ€mpa kursiv stil"; // Formatting Actions diff --git a/Riot/Assets/uk.lproj/Vector.strings b/Riot/Assets/uk.lproj/Vector.strings index 04e9b75d54..7c18d9f8b7 100644 --- a/Riot/Assets/uk.lproj/Vector.strings +++ b/Riot/Assets/uk.lproj/Vector.strings @@ -2780,8 +2780,8 @@ "authentication_qr_login_start_subtitle" = "ВоĐșĐŸŃ€ĐžŃŃ‚ĐŸĐČуĐčŃ‚Đ” ĐșĐ°ĐŒĐ”Ń€Ńƒ ĐœĐ° Ń†ŃŒĐŸĐŒŃƒ ĐżŃ€ĐžŃŃ‚Ń€ĐŸŃ—, Ń‰ĐŸĐ± зісĐșĐ°ĐœŃƒĐČато QR-ĐșĐŸĐŽ, ĐżĐŸĐșĐ°Đ·Đ°ĐœĐžĐč ĐœĐ° Ń–ĐœŃˆĐŸĐŒŃƒ ĐżŃ€ĐžŃŃ‚Ń€ĐŸŃ—:"; "authentication_qr_login_start_title" = "ĐĄĐșĐ°ĐœŃƒĐČато QR-ĐșĐŸĐŽ"; "authentication_login_with_qr" = "ĐŁĐČіĐčто ĐČĐžĐșĐŸŃ€ĐžŃŃ‚Đ°ĐČшО QR-ĐșĐŸĐŽ"; -"wysiwyg_composer_format_action_strikethrough" = "Đ—Đ°ŃŃ‚ĐŸŃŃƒĐČато Ń„ĐŸŃ€ĐŒĐ°Ń‚ŃƒĐČĐ°ĐœĐœŃ піЮĐșŃ€Đ”ŃĐ»Đ”ĐœĐžĐŒ"; -"wysiwyg_composer_format_action_underline" = "Đ—Đ°ŃŃ‚ĐŸŃŃƒĐČато Ń„ĐŸŃ€ĐŒĐ°Ń‚ŃƒĐČĐ°ĐœĐœŃ пДрДĐșŃ€Đ”ŃĐ»Đ”ĐœĐžĐŒ"; +"wysiwyg_composer_format_action_underline" = "Đ—Đ°ŃŃ‚ĐŸŃŃƒĐČато Ń„ĐŸŃ€ĐŒĐ°Ń‚ŃƒĐČĐ°ĐœĐœŃ піЮĐșŃ€Đ”ŃĐ»Đ”ĐœĐžĐŒ"; +"wysiwyg_composer_format_action_strikethrough" = "Đ—Đ°ŃŃ‚ĐŸŃŃƒĐČато Ń„ĐŸŃ€ĐŒĐ°Ń‚ŃƒĐČĐ°ĐœĐœŃ пДрДĐșŃ€Đ”ŃĐ»Đ”ĐœĐžĐŒ"; // Formatting Actions "wysiwyg_composer_format_action_bold" = "Đ—Đ°ŃŃ‚ĐŸŃŃƒĐČато Ń„ĐŸŃ€ĐŒĐ°Ń‚ŃƒĐČĐ°ĐœĐœŃ Đ¶ĐžŃ€ĐœĐžĐŒ"; diff --git a/Riot/Assets/zh_Hans.lproj/Vector.strings b/Riot/Assets/zh_Hans.lproj/Vector.strings index 79177b974e..50d43aed9a 100644 --- a/Riot/Assets/zh_Hans.lproj/Vector.strings +++ b/Riot/Assets/zh_Hans.lproj/Vector.strings @@ -928,7 +928,7 @@ // MARK: Emoji picker "emoji_picker_title" = "揍ćș”"; "emoji_picker_people_category" = "èĄšæƒ…ć’Œäșș物"; -"emoji_picker_nature_category" = "ćŠšç‰©ć’Œè‡Ș热"; +"emoji_picker_nature_category" = "ćŠšç‰©ć’Œè‡Ș然"; "emoji_picker_foods_category" = "éŁŸç‰©ć’Œé„źæ–™"; "emoji_picker_activity_category" = "æŽ»ćŠš"; "emoji_picker_places_category" = "æ—…æžžć’Œæ™Żç‚č"; @@ -952,7 +952,7 @@ "key_verification_tile_request_incoming_approval_decline" = "拒绝"; "key_verification_tile_conclusion_done_title" = "ć·ČéȘŒèŻ"; "key_verification_tile_conclusion_warning_title" = "äžèą«äżĄä»»çš„ç™»ćœ•"; -"key_verification_incoming_request_incoming_alert_message" = "%@æƒłèŠéȘŒèŻ"; +"key_verification_incoming_request_incoming_alert_message" = "%@ æƒłèŠèż›èĄŒéȘŒèŻ"; "user_verification_start_verify_action" = "ćŒ€ć§‹éȘŒèŻ"; "user_verification_start_information_part1" = "äžșäș†éąć€–çš„ćź‰ć…šæ€§ïŒŒèŻ·éȘŒèŻïŒš "; "user_verification_start_information_part2" = " æŁ€æŸ„ćœšäœ çš„äž€äžȘèźŸć€‡äžŠçš„äž€æŹĄæ€§ä»Łç ă€‚"; @@ -2405,3 +2405,24 @@ "room_access_space_chooser_known_spaces_section" = "æ‚šçŸ„é“çš„ćŒ…ć« %@ 的ç©ș问"; "room_access_space_chooser_other_spaces_section_info" = "èż™äș›ćŸˆćŻèƒœæ˜Ż %@ 的缡理摘揂侎。"; "room_access_space_chooser_other_spaces_section" = "慶他ç©șé—Žæˆ–æˆżé—Ž"; +"event_formatter_message_deleted" = "æ¶ˆæŻć·Čćˆ é™€"; +"network_offline_title" = "æ‚šć·Č犻çșż"; + +// MARK: Sign out warning + +"sign_out" = "登ć‡ș"; + +// Unverified sessions +"key_verification_alert_title" = "悚有æœȘéȘŒèŻçš„äŒšèŻ"; +"pill_message_in" = "朹 %@ é‡Œçš„æ¶ˆæŻ"; + +// Legacy to Rust security upgrade + +"key_verification_self_verify_security_upgrade_alert_title" = "ćș”甚ć·Č曎新"; +"sign_out_confirmation_message" = "æ‚šçĄźćźšèŠç™»ć‡șć—ïŒŸ"; +"device_verification_self_verify_open_on_other_device_title" = "ćœšæ‚šçš„ćŠäž€ć°èźŸć€‡äžŠæ‰“ćŒ€ %@"; +"device_verification_self_verify_open_on_other_device_information" = "æ‚šéœ€èŠć…ˆéȘŒèŻæ­€äŒšèŻæ‰èƒœèŻ»ć–ćŠ ćŻ†äżĄæŻă€‚\n\nćœšæ‚šçš„ć…¶ä»–èźŸć€‡äžŠæ‰“ćŒ€ Element ćč¶æŒ‰ç…§èŻŽæ˜Žèż›èĄŒæ“äœœă€‚"; +"device_verification_self_verify_wait_recover_secrets_additional_help" = "ć·Čæ— æł•èźżé—ź %@ äŒšèŻïŒŸ"; +"network_offline_message" = "æ‚šć·Č犻çșżïŒŒèŻ·æŁ€æŸ„æ‚šçš„çœ‘ç»œé“ŸæŽ„ă€‚"; +"key_verification_alert_body" = "é‡æ–°æŁ€æŸ„ä»„çĄźäżæ‚šçš„èŽŠæˆ·ćź‰ć…šă€‚"; +"key_verification_scan_qr_code_title" = "扫描 QR Code"; diff --git a/Riot/Assets/zh_Hant.lproj/Vector.strings b/Riot/Assets/zh_Hant.lproj/Vector.strings index f92adb766d..f7818a7907 100644 --- a/Riot/Assets/zh_Hant.lproj/Vector.strings +++ b/Riot/Assets/zh_Hant.lproj/Vector.strings @@ -2026,8 +2026,8 @@ // Links "wysiwyg_composer_link_action_text" = "æ–‡ć­—"; "wysiwyg_composer_format_action_link" = "ć„—ç”šé€Łç”æ ŒćŒ"; -"wysiwyg_composer_format_action_strikethrough" = "愗甚ćș•ç·šæ ŒćŒ"; -"wysiwyg_composer_format_action_underline" = "愗甚ćˆȘé™€ç·šæ ŒćŒ"; +"wysiwyg_composer_format_action_underline" = "愗甚ćș•ç·šæ ŒćŒ"; +"wysiwyg_composer_format_action_strikethrough" = "愗甚ćˆȘé™€ç·šæ ŒćŒ"; "wysiwyg_composer_format_action_italic" = "ć„—ç”šçŸ©ćŒæ–œé«”æ ŒćŒ"; // Formatting Actions diff --git a/Riot/Generated/Vector_Strings.swift b/Riot/Generated/Vector_Strings.swift index 3c72cbcaa4..ba4a20c2f5 100644 --- a/Riot/Generated/Vector_Strings.swift +++ b/Riot/Generated/Vector_Strings.swift @@ -9607,7 +9607,7 @@ public class VectorL10n: NSObject { public static var wysiwygComposerFormatActionQuote: String { return VectorL10n.tr("Vector", "wysiwyg_composer_format_action_quote") } - /// Apply underline format + /// Apply strikethrough format public static var wysiwygComposerFormatActionStrikethrough: String { return VectorL10n.tr("Vector", "wysiwyg_composer_format_action_strikethrough") } @@ -9615,7 +9615,7 @@ public class VectorL10n: NSObject { public static var wysiwygComposerFormatActionUnIndent: String { return VectorL10n.tr("Vector", "wysiwyg_composer_format_action_un_indent") } - /// Apply strikethrough format + /// Apply underline format public static var wysiwygComposerFormatActionUnderline: String { return VectorL10n.tr("Vector", "wysiwyg_composer_format_action_underline") } diff --git a/Riot/Model/HomeserverConfiguration/HomeserverConfiguration.swift b/Riot/Model/HomeserverConfiguration/HomeserverConfiguration.swift index a3bee4c8f3..994e4fb2c6 100644 --- a/Riot/Model/HomeserverConfiguration/HomeserverConfiguration.swift +++ b/Riot/Model/HomeserverConfiguration/HomeserverConfiguration.swift @@ -23,13 +23,13 @@ final class HomeserverConfiguration: NSObject { // Note: Use an object per configuration subject when there is multiple properties related let jitsi: HomeserverJitsiConfiguration let encryption: HomeserverEncryptionConfiguration -// let tileServer: HomeserverTileServerConfiguration + let tileServer: HomeserverTileServerConfiguration init(jitsi: HomeserverJitsiConfiguration, - encryption: HomeserverEncryptionConfiguration/*, - tileServer: HomeserverTileServerConfiguration*/) { + encryption: HomeserverEncryptionConfiguration, + tileServer: HomeserverTileServerConfiguration) { self.jitsi = jitsi self.encryption = encryption -// self.tileServer = tileServer + self.tileServer = tileServer } } diff --git a/Riot/Model/HomeserverConfiguration/HomeserverConfigurationBuilder.swift b/Riot/Model/HomeserverConfiguration/HomeserverConfigurationBuilder.swift index 494b50d0a0..bc6182faac 100644 --- a/Riot/Model/HomeserverConfiguration/HomeserverConfigurationBuilder.swift +++ b/Riot/Model/HomeserverConfiguration/HomeserverConfigurationBuilder.swift @@ -78,15 +78,15 @@ final class HomeserverConfigurationBuilder: NSObject { // Tile server configuration -// let tileServerMapStyleURL: URL -// if let mapStyleURLString = wellKnown?.tileServer?.mapStyleURLString, -// let mapStyleURL = URL(string: mapStyleURLString) { -// tileServerMapStyleURL = mapStyleURL -// } else { -// tileServerMapStyleURL = BuildSettings.defaultTileServerMapStyleURL -// } -// -// let tileServerConfiguration = HomeserverTileServerConfiguration(mapStyleURL: tileServerMapStyleURL) + let tileServerMapStyleURL: URL + if let mapStyleURLString = wellKnown?.tileServer?.mapStyleURLString, + let mapStyleURL = URL(string: mapStyleURLString) { + tileServerMapStyleURL = mapStyleURL + } else { + tileServerMapStyleURL = BuildSettings.defaultTileServerMapStyleURL + } + + let tileServerConfiguration = HomeserverTileServerConfiguration(mapStyleURL: tileServerMapStyleURL) // Create HomeserverConfiguration @@ -95,8 +95,8 @@ final class HomeserverConfigurationBuilder: NSObject { useFor1To1Calls: useJitsiFor1To1Calls) return HomeserverConfiguration(jitsi: jitsiConfiguration, - encryption: encryptionConfiguration/*, - tileServer: tileServerConfiguration*/) + encryption: encryptionConfiguration, + tileServer: tileServerConfiguration) } // MARK: - Private diff --git a/Riot/Modules/Analytics/Analytics.swift b/Riot/Modules/Analytics/Analytics.swift index 1a30841b98..d9d68e2f85 100644 --- a/Riot/Modules/Analytics/Analytics.swift +++ b/Riot/Modules/Analytics/Analytics.swift @@ -213,6 +213,25 @@ import AnalyticsEvents } } +@objc +protocol E2EAnalytics { + func trackE2EEError(_ failure: DecryptionFailure) +} + + +@objc extension Analytics: E2EAnalytics { + + /// Track an E2EE error that occurred + /// - Parameters: + /// - reason: The error that occurred. + /// - context: Additional context of the error that occured + func trackE2EEError(_ failure: DecryptionFailure) { + let event = failure.toAnalyticsEvent() + capture(event: event) + } + +} + // MARK: - Public tracking methods // The following methods are exposed for compatibility with Objective-C as // the `capture` method and the generated events cannot be bridged from Swift. @@ -266,20 +285,7 @@ extension Analytics { func trackInteraction(_ uiElement: AnalyticsUIElement) { trackInteraction(uiElement, interactionType: .Touch, index: nil) } - - /// Track an E2EE error that occurred - /// - Parameters: - /// - reason: The error that occurred. - /// - context: Additional context of the error that occured - func trackE2EEError(_ reason: DecryptionFailureReason, context: String) { - let event = AnalyticsEvent.Error( - context: context, - cryptoModule: .Rust, - domain: .E2EE, - name: reason.errorName - ) - capture(event: event) - } + /// Track when a user becomes unauthenticated without pressing the `sign out` button. /// - Parameters: @@ -355,7 +361,8 @@ extension Analytics: MXAnalyticsDelegate { func trackCallError(with reason: __MXCallHangupReason, video isVideo: Bool, numberOfParticipants: Int, incoming isIncoming: Bool) { let callEvent = AnalyticsEvent.CallError(isVideo: isVideo, numParticipants: numberOfParticipants, placed: !isIncoming) - let event = AnalyticsEvent.Error(context: nil, cryptoModule: nil, domain: .VOIP, name: reason.errorName) + let event = AnalyticsEvent.Error(context: nil, cryptoModule: nil, cryptoSDK: nil, domain: .VOIP, eventLocalAgeMillis: nil, + isFederated: nil, isMatrixDotOrg: nil, name: reason.errorName, timeToDecryptMillis: nil, userTrustsOwnIdentity: nil, wasVisibleToUser: nil) capture(event: callEvent) capture(event: event) } @@ -386,6 +393,7 @@ extension Analytics: MXAnalyticsDelegate { let event = AnalyticsEvent.Composer(inThread: inThread, isEditing: isEditing, isReply: isReply, + messageType: .Text, startsThread: startsThread) capture(event: event) } diff --git a/Riot/Modules/Analytics/DecryptionFailure+Analytics.swift b/Riot/Modules/Analytics/DecryptionFailure+Analytics.swift new file mode 100644 index 0000000000..cd129aee35 --- /dev/null +++ b/Riot/Modules/Analytics/DecryptionFailure+Analytics.swift @@ -0,0 +1,44 @@ +// +// 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 AnalyticsEvents + +extension DecryptionFailure { + + public func toAnalyticsEvent() -> AnalyticsEvent.Error { + + let timeToDecryptMillis: Int = if self.timeToDecrypt != nil { + Int(self.timeToDecrypt! * 1000) + } else { + -1 + } + return AnalyticsEvent.Error( + context: self.context, + cryptoModule: .Rust, + cryptoSDK: .Rust, + domain: .E2EE, + + eventLocalAgeMillis: nil, + isFederated: nil, + isMatrixDotOrg: nil, + name: self.reason.errorName, + timeToDecryptMillis: timeToDecryptMillis, + userTrustsOwnIdentity: nil, + wasVisibleToUser: nil + ) + } +} diff --git a/Riot/Modules/Analytics/DecryptionFailure.swift b/Riot/Modules/Analytics/DecryptionFailure.swift index 1c991db88f..9e0ca57858 100644 --- a/Riot/Modules/Analytics/DecryptionFailure.swift +++ b/Riot/Modules/Analytics/DecryptionFailure.swift @@ -38,15 +38,19 @@ import AnalyticsEvents /// The id of the event that was unabled to decrypt. let failedEventId: String /// The time the failure has been reported. - let ts: TimeInterval = Date().timeIntervalSince1970 + let ts: TimeInterval /// Decryption failure reason. let reason: DecryptionFailureReason /// Additional context of failure let context: String - init(failedEventId: String, reason: DecryptionFailureReason, context: String) { + /// UTDs can be permanent or temporary. If temporary, this field will contain the time it took to decrypt the message in milliseconds. If permanent should be nil + var timeToDecrypt: TimeInterval? + + init(failedEventId: String, reason: DecryptionFailureReason, context: String, ts: TimeInterval) { self.failedEventId = failedEventId self.reason = reason self.context = context + self.ts = ts } } diff --git a/Riot/Modules/Analytics/DecryptionFailureTracker.h b/Riot/Modules/Analytics/DecryptionFailureTracker.h deleted file mode 100644 index b8f9ca467e..0000000000 --- a/Riot/Modules/Analytics/DecryptionFailureTracker.h +++ /dev/null @@ -1,55 +0,0 @@ -/* - Copyright 2018 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 - -@class DecryptionFailureTracker; - -@class Analytics; -@import MatrixSDK; - -@interface DecryptionFailureTracker : NSObject - -/** - Returns the shared tracker. - - @return the shared tracker. - */ -+ (instancetype)sharedInstance; - -/** - The delegate object to receive analytics events. - */ -@property (nonatomic, weak) Analytics *delegate; - -/** - Report an event unable to decrypt. - - This error can be momentary. The DecryptionFailureTracker will check if it gets - fixed. Else, it will generate a failure (@see `trackFailures`). - - @param event the event. - @param roomState the room state when the event was received. - @param userId my user id. - */ -- (void)reportUnableToDecryptErrorForEvent:(MXEvent*)event withRoomState:(MXRoomState*)roomState myUser:(NSString*)userId; - -/** - Flush current data. - */ -- (void)dispatch; - -@end diff --git a/Riot/Modules/Analytics/DecryptionFailureTracker.m b/Riot/Modules/Analytics/DecryptionFailureTracker.m deleted file mode 100644 index 4a749b71aa..0000000000 --- a/Riot/Modules/Analytics/DecryptionFailureTracker.m +++ /dev/null @@ -1,174 +0,0 @@ -/* - Copyright 2018 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 "DecryptionFailureTracker.h" -#import "GeneratedInterface-Swift.h" - - -// Call `checkFailures` every `CHECK_INTERVAL` -#define CHECK_INTERVAL 2 - -// Give events a chance to be decrypted by waiting `GRACE_PERIOD` before counting -// and reporting them as failures -#define GRACE_PERIOD 4 - -// E2E failures analytics category. -NSString *const kDecryptionFailureTrackerAnalyticsCategory = @"e2e.failure"; - -@interface DecryptionFailureTracker() -{ - // Reported failures - // Every `CHECK_INTERVAL`, this list is checked for failures that happened - // more than`GRACE_PERIOD` ago. Those that did are reported to the delegate. - NSMutableDictionary *reportedFailures; - - // Event ids of failures that were tracked previously - NSMutableSet *trackedEvents; - - // Timer for periodic check - NSTimer *checkFailuresTimer; -} -@end - -@implementation DecryptionFailureTracker - -+ (instancetype)sharedInstance -{ - static DecryptionFailureTracker *sharedInstance = nil; - static dispatch_once_t onceToken; - - dispatch_once(&onceToken, ^{ - sharedInstance = [[DecryptionFailureTracker alloc] init]; - }); - - return sharedInstance; -} - -- (instancetype)init -{ - self = [super init]; - if (self) - { - reportedFailures = [NSMutableDictionary dictionary]; - trackedEvents = [NSMutableSet set]; - - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(eventDidDecrypt:) name:kMXEventDidDecryptNotification object:nil]; - - checkFailuresTimer = [NSTimer scheduledTimerWithTimeInterval:CHECK_INTERVAL - target:self - selector:@selector(checkFailures) - userInfo:nil - repeats:YES]; - } - return self; -} - -- (void)reportUnableToDecryptErrorForEvent:(MXEvent *)event withRoomState:(MXRoomState *)roomState myUser:(NSString *)userId -{ - if (reportedFailures[event.eventId] || [trackedEvents containsObject:event.eventId]) - { - return; - } - - // Filter out "expected" UTDs - // We cannot decrypt messages sent before the user joined the room - MXRoomMember *myUser = [roomState.members memberWithUserId:userId]; - if (!myUser || myUser.membership != MXMembershipJoin) - { - return; - } - - NSString *failedEventId = event.eventId; - DecryptionFailureReason reason; - - // Categorise the error - switch (event.decryptionError.code) - { - case MXDecryptingErrorUnknownInboundSessionIdCode: - reason = DecryptionFailureReasonOlmKeysNotSent; - break; - - case MXDecryptingErrorOlmCode: - reason = DecryptionFailureReasonOlmIndexError; - break; - - default: - // All other error codes will be tracked as `OlmUnspecifiedError` and will include `context` containing - // the actual error code and localized description - reason = DecryptionFailureReasonUnspecified; - break; - } - - NSString *context = [NSString stringWithFormat:@"code: %ld, description: %@", event.decryptionError.code, event.decryptionError.localizedDescription]; - reportedFailures[event.eventId] = [[DecryptionFailure alloc] initWithFailedEventId:failedEventId - reason:reason - context:context]; -} - -- (void)dispatch -{ - [self checkFailures]; -} - -#pragma mark - Private methods - -/** - Mark reported failures that occured before tsNow - GRACE_PERIOD as failures that should be - tracked. - */ -- (void)checkFailures -{ - if (!_delegate) - { - return; - } - - NSTimeInterval tsNow = [NSDate date].timeIntervalSince1970; - - NSMutableArray *failuresToTrack = [NSMutableArray array]; - - for (DecryptionFailure *reportedFailure in reportedFailures.allValues) - { - if (reportedFailure.ts < tsNow - GRACE_PERIOD) - { - [failuresToTrack addObject:reportedFailure]; - [reportedFailures removeObjectForKey:reportedFailure.failedEventId]; - [trackedEvents addObject:reportedFailure.failedEventId]; - } - } - - if (failuresToTrack.count) - { - // Sort failures by error reason - NSMutableDictionary *failuresCounts = [NSMutableDictionary dictionary]; - for (DecryptionFailure *failure in failuresToTrack) - { - failuresCounts[@(failure.reason)] = @(failuresCounts[@(failure.reason)].unsignedIntegerValue + 1); - [self.delegate trackE2EEError:failure.reason context:failure.context]; - } - - MXLogDebug(@"[DecryptionFailureTracker] trackFailures: %@", failuresCounts); - } -} - -- (void)eventDidDecrypt:(NSNotification *)notif -{ - // Could be an event in the reportedFailures, remove it - MXEvent *event = notif.object; - [reportedFailures removeObjectForKey:event.eventId]; -} - -@end diff --git a/Riot/Modules/Analytics/DecryptionFailureTracker.swift b/Riot/Modules/Analytics/DecryptionFailureTracker.swift new file mode 100644 index 0000000000..19b8afb19c --- /dev/null +++ b/Riot/Modules/Analytics/DecryptionFailureTracker.swift @@ -0,0 +1,174 @@ +// +// 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 + + +// Protocol to get the current time. Used for easy testing +protocol TimeProvider { + func nowTs() -> TimeInterval +} + +class DefaultTimeProvider: TimeProvider { + + func nowTs() -> TimeInterval { + return Date.now.timeIntervalSince1970 + } + +} + + +@objc +class DecryptionFailureTracker: NSObject { + + let GRACE_PERIOD: TimeInterval = 4 + // Call `checkFailures` every `CHECK_INTERVAL` + let CHECK_INTERVAL: TimeInterval = 15 + + // The maximum time to wait for a late decryption before reporting as permanent UTD + let MAX_WAIT_FOR_LATE_DECRYPTION: TimeInterval = 60 + + @objc weak var delegate: E2EAnalytics? + + // Reported failures + var reportedFailures = [String /* eventId */: DecryptionFailure]() + + // Event ids of failures that were tracked previously + var trackedEvents = Set() + + var checkFailuresTimer: Timer? + + @objc static let sharedInstance = DecryptionFailureTracker() + + var timeProvider: TimeProvider = DefaultTimeProvider() + + override init() { + super.init() + + NotificationCenter.default.addObserver(self, + selector: #selector(eventDidDecrypt(_:)), + name: .mxEventDidDecrypt, + object: nil) + + } + + @objc + func reportUnableToDecryptError(forEvent event: MXEvent, withRoomState roomState: MXRoomState, myUser userId: String) { + if reportedFailures[event.eventId] != nil || trackedEvents.contains(event.eventId) { + return + } + + // Filter out "expected" UTDs + // We cannot decrypt messages sent before the user joined the room + guard let myUser = roomState.members.member(withUserId: userId) else { return } + if myUser.membership != MXMembership.join { + return + } + + guard let failedEventId = event.eventId else { return } + + guard let error = event.decryptionError as? NSError else { return } + + var reason = DecryptionFailureReason.unspecified + + if error.code == MXDecryptingErrorUnknownInboundSessionIdCode.rawValue { + reason = DecryptionFailureReason.olmKeysNotSent + } else if error.code == MXDecryptingErrorOlmCode.rawValue { + reason = DecryptionFailureReason.olmIndexError + } + + let context = String(format: "code: %ld, description: %@", error.code, event.decryptionError.localizedDescription) + + reportedFailures[failedEventId] = DecryptionFailure(failedEventId: failedEventId, reason: reason, context: context, ts: self.timeProvider.nowTs()) + + // Start the ticker if needed. There is no need to have a ticker if no failures are tracked + if checkFailuresTimer == nil { + self.checkFailuresTimer = Timer.scheduledTimer(withTimeInterval: CHECK_INTERVAL, repeats: true) { [weak self] _ in + self?.checkFailures() + } + } + + } + + @objc + func dispatch() { + self.checkFailures() + } + + @objc + func eventDidDecrypt(_ notification: Notification) { + guard let event = notification.object as? MXEvent else { return } + + guard let reportedFailure = self.reportedFailures[event.eventId] else { return } + + let now = self.timeProvider.nowTs() + let ellapsedTime = now - reportedFailure.ts + + if ellapsedTime < 4 { + // event is graced + reportedFailures.removeValue(forKey: event.eventId) + } else { + // It's a late decrypt must be reported as a late decrypt + reportedFailure.timeToDecrypt = ellapsedTime + self.delegate?.trackE2EEError(reportedFailure) + } + // Remove from reported failures + self.trackedEvents.insert(event.eventId) + reportedFailures.removeValue(forKey: event.eventId) + + // Check if we still need the ticker timer + if reportedFailures.isEmpty { + // Invalidate the current timer, nothing to check for + self.checkFailuresTimer?.invalidate() + self.checkFailuresTimer = nil + } + + } + + /** + Mark reported failures that occured before tsNow - GRACE_PERIOD as failures that should be + tracked. + */ + @objc + func checkFailures() { + guard let delegate = self.delegate else {return} + + let tsNow = self.timeProvider.nowTs() + var failuresToCheck = [DecryptionFailure]() + + for reportedFailure in self.reportedFailures.values { + let ellapsed = tsNow - reportedFailure.ts + if ellapsed > MAX_WAIT_FOR_LATE_DECRYPTION { + failuresToCheck.append(reportedFailure) + reportedFailure.timeToDecrypt = nil + reportedFailures.removeValue(forKey: reportedFailure.failedEventId) + trackedEvents.insert(reportedFailure.failedEventId) + } + } + + for failure in failuresToCheck { + delegate.trackE2EEError(failure) + } + + // Check if we still need the ticker timer + if reportedFailures.isEmpty { + // Invalidate the current timer, nothing to check for + self.checkFailuresTimer?.invalidate() + self.checkFailuresTimer = nil + } + } + +} diff --git a/Riot/Modules/Application/AppCoordinator.swift b/Riot/Modules/Application/AppCoordinator.swift index 2993942d42..e55874016c 100755 --- a/Riot/Modules/Application/AppCoordinator.swift +++ b/Riot/Modules/Application/AppCoordinator.swift @@ -442,14 +442,17 @@ final class AppCoordinator: NSObject, AppCoordinatorType { self.expiredAccountAlertController?.dismiss(animated: false) - let alert = UIAlertController(title: TchapL10n.warningTitle, message: TchapL10n.expiredAccountAlertMessage, preferredStyle: .alert) + // Tchap: customize wording + let alert = UIAlertController(title: TchapL10n.expiredAccountAlertTitle, message: TchapL10n.expiredAccountAlertMessage, preferredStyle: .alert) + // Tchap: customize wording let resumeTitle = TchapL10n.expiredAccountResumeButton let resumeAction = UIAlertAction(title: resumeTitle, style: .default, handler: { action in // Relaunch the session self.reloadSession(clearCache: false) }) alert.addAction(resumeAction) + // Tchap: customize wording let sendEmailTitle = TchapL10n.expiredAccountRequestRenewalEmailButton let sendEmailAction = UIAlertAction(title: sendEmailTitle, style: .default, handler: { action in // Request a new email for the main account @@ -484,9 +487,10 @@ final class AppCoordinator: NSObject, AppCoordinatorType { self.expiredAccountAlertController?.dismiss(animated: false) - let alert = UIAlertController(title: TchapL10n.infoTitle, message: TchapL10n.expiredAccountOnNewSentEmailMsg, preferredStyle: .alert) + // Tchap: customize wording + let alert = UIAlertController(title: TchapL10n.expiredAccountOnNewSentEmailTitle, message: TchapL10n.expiredAccountOnNewSentEmailMessage, preferredStyle: .alert) - let resumeTitle = TchapL10n.expiredAccountResumeButton + let resumeTitle = TchapL10n.expiredAccountOnNewSentEmailButton let resumeAction = UIAlertAction(title: resumeTitle, style: .default, handler: { action in // Relaunch the session self.reloadSession(clearCache: false) diff --git a/Riot/Modules/Application/LegacyAppDelegate.m b/Riot/Modules/Application/LegacyAppDelegate.m index db266e53f0..1011a4f832 100644 --- a/Riot/Modules/Application/LegacyAppDelegate.m +++ b/Riot/Modules/Application/LegacyAppDelegate.m @@ -33,7 +33,6 @@ //#import "ContactDetailsViewController.h" #import "BugReportViewController.h" -#import "DecryptionFailureTracker.h" #import "Tools.h" #import "WidgetManager.h" diff --git a/Riot/Modules/Common/Recents/DataSources/RecentsDataSource.m b/Riot/Modules/Common/Recents/DataSources/RecentsDataSource.m index 96d0025cec..48add73d5f 100644 --- a/Riot/Modules/Common/Recents/DataSources/RecentsDataSource.m +++ b/Riot/Modules/Common/Recents/DataSources/RecentsDataSource.m @@ -255,10 +255,13 @@ - (RecentsDataSourceSections *)makeDataSourceSections [types addObject:@(RecentsDataSourceSectionTypeLowPriority)]; } - if (self.serverNoticeCellDataArray.count > 0) - { - [types addObject:@(RecentsDataSourceSectionTypeServerNotice)]; - } + // Tchap: don't display server notices as separate section. + // Server notices are displayed in the main section "all chats" + // (see modification in `updateConversationFetcher` in `RecentListService` +// if (self.serverNoticeCellDataArray.count > 0) +// { +// [types addObject:@(RecentsDataSourceSectionTypeServerNotice)]; +// } return [[RecentsDataSourceSections alloc] initWithSectionTypes:types.copy]; } diff --git a/Riot/Modules/Common/Recents/Service/MatrixSDK/RecentsListService.swift b/Riot/Modules/Common/Recents/Service/MatrixSDK/RecentsListService.swift index bd8f52ae77..1401abe6c4 100644 --- a/Riot/Modules/Common/Recents/Service/MatrixSDK/RecentsListService.swift +++ b/Riot/Modules/Common/Recents/Service/MatrixSDK/RecentsListService.swift @@ -717,7 +717,12 @@ public class RecentsListService: NSObject, RecentsListServiceProtocol { } private func updateConversationFetcher(_ fetcher: MXRoomListDataFetcher, for mode: RecentsDataSourceMode) { - var notDataTypes: MXRoomSummaryDataTypes = mode == .allChats ? [.hidden, .conferenceUser, .invited, .lowPriority, .serverNotice, .space] : [.hidden, .conferenceUser, .direct, .invited, .lowPriority, .serverNotice, .space] + // Tchap: + // don't exclude serverNotice channels from "all chats" group fetcher + // to get server Notices (aka "Tchap Annonces") in the main room list + // to be displayed as classic rooms +// var notDataTypes: MXRoomSummaryDataTypes = mode == .allChats ? [.hidden, .conferenceUser, .invited, .lowPriority, .serverNotice, .space] : [.hidden, .conferenceUser, .direct, .invited, .lowPriority, .serverNotice, .space] + var notDataTypes: MXRoomSummaryDataTypes = mode == .allChats ? [.hidden, .conferenceUser, .invited, .lowPriority, /*.serverNotice,*/ .space] : [.hidden, .conferenceUser, .direct, .invited, .lowPriority, /*.serverNotice,*/ .space] switch mode { case .home: diff --git a/Riot/Modules/Common/Recents/Views/RecentTableViewCell.m b/Riot/Modules/Common/Recents/Views/RecentTableViewCell.m index 4e9dedb143..32dd25ab37 100644 --- a/Riot/Modules/Common/Recents/Views/RecentTableViewCell.m +++ b/Riot/Modules/Common/Recents/Views/RecentTableViewCell.m @@ -93,13 +93,7 @@ - (void)render:(MXKCellData *)cellData self.lastEventDecriptionLabelTrailingConstraint.constant = self.unsentImageView.hidden ? 10 : 30; // Notify unreads and bing - if (roomCellData.isRoomMarkedAsUnread) - { - self.missedNotifAndUnreadBadgeBgView.hidden = NO; - self.missedNotifAndUnreadBadgeBgView.backgroundColor = ThemeService.shared.theme.tintColor; - self.missedNotifAndUnreadBadgeBgViewWidthConstraint.constant = 20; - } - else if (roomCellData.hasUnread) + if (roomCellData.hasUnread) { self.missedNotifAndUnreadIndicator.hidden = NO; if (0 < roomCellData.notificationCount) diff --git a/Riot/Modules/ContextMenu/ActionProviders/RoomActionProvider.swift b/Riot/Modules/ContextMenu/ActionProviders/RoomActionProvider.swift index c019aae9c0..d9cde9dbe6 100644 --- a/Riot/Modules/ContextMenu/ActionProviders/RoomActionProvider.swift +++ b/Riot/Modules/ContextMenu/ActionProviders/RoomActionProvider.swift @@ -34,7 +34,7 @@ class RoomActionProvider: RoomActionProviderProtocol { var menu: UIMenu { if service.isRoomJoined { - var children = service.hasUnread ? [self.markAsReadAction] : [self.markAsUnreadAction] + var children = service.hasUnread ? [self.markAsReadAction] : [] children.append(contentsOf: [ self.directChatAction, self.notificationsAction, diff --git a/Riot/Modules/ContextMenu/Services/RoomContextActionService.swift b/Riot/Modules/ContextMenu/Services/RoomContextActionService.swift index 12c02c9381..eb49328250 100644 --- a/Riot/Modules/ContextMenu/Services/RoomContextActionService.swift +++ b/Riot/Modules/ContextMenu/Services/RoomContextActionService.swift @@ -38,7 +38,7 @@ class RoomContextActionService: NSObject, RoomContextActionServiceProtocol { self.room = room self.delegate = delegate self.isRoomJoined = room.summary?.isJoined ?? false - self.hasUnread = (room.summary?.hasAnyUnread ?? false) || room.isMarkedAsUnread + self.hasUnread = room.summary?.hasAnyUnread ?? false self.roomMembership = room.summary?.membership ?? .unknown self.session = room.mxSession self.unownedRoomService = UnownedRoomContextActionService(roomId: room.roomId, canonicalAlias: room.summary?.aliases?.first, session: self.session, delegate: delegate) diff --git a/Riot/Modules/MatrixKit/Models/Account/MXKAccount.m b/Riot/Modules/MatrixKit/Models/Account/MXKAccount.m index b67c5b78b6..8b297e221e 100644 --- a/Riot/Modules/MatrixKit/Models/Account/MXKAccount.m +++ b/Riot/Modules/MatrixKit/Models/Account/MXKAccount.m @@ -998,7 +998,11 @@ -(void)openSessionWithStore:(id)store // Enable the antivirus scanner in the current session. [mxSession setAntivirusServerURL:_antivirusServerURL]; } - + // Tchap: else hard code antivirus server URL with homeServer URL like in Tchap Android. + else { + [mxSession setAntivirusServerURL:self.mxCredentials.homeServer]; + } + // Set default MXEvent -> NSString formatter MXKEventFormatter *eventFormatter = [[MXKEventFormatter alloc] initWithMatrixSession:self.mxSession]; eventFormatter.isForSubtitle = YES; diff --git a/Riot/Modules/MatrixKit/Utils/EventFormatter/MXKEventFormatter.m b/Riot/Modules/MatrixKit/Utils/EventFormatter/MXKEventFormatter.m index d6a3b5cfd3..1fd543a036 100644 --- a/Riot/Modules/MatrixKit/Utils/EventFormatter/MXKEventFormatter.m +++ b/Riot/Modules/MatrixKit/Utils/EventFormatter/MXKEventFormatter.m @@ -2029,6 +2029,9 @@ - (NSString*)renderReplyTo:(NSString*)htmlString withRoomState:(MXRoomState*)roo } } + // Tchap: truncate quoted reply if necessary. + html = [self tchapTruncatedQuotedReplyFrom:html]; + // Replace
In reply to // By
['In reply to' from resources] // To localize the "In reply to" string @@ -2042,6 +2045,42 @@ - (NSString*)renderReplyTo:(NSString*)htmlString withRoomState:(MXRoomState*)roo return html; } +// Tchap: truncate long quoted reply +- (NSString *)tchapTruncatedQuotedReplyFrom:(NSString *)fullQuotedReply { + static NSRegularExpression *htmlQuotedTextRegex; + + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + htmlQuotedTextRegex = [NSRegularExpression regularExpressionWithPattern:kRepliedTextPattern + options:NSRegularExpressionCaseInsensitive + error:nil]; + }); + + NSTextCheckingResult * regexResults = [htmlQuotedTextRegex firstMatchInString:fullQuotedReply + options:0 + range:NSMakeRange(0, fullQuotedReply.length)]; + + // Check if a quoted text is present. + if( regexResults.numberOfRanges < 1 ) + { + // No reply found. + return fullQuotedReply; + } + + NSRange quotedTextRange = [regexResults rangeAtIndex:1]; + + NSUInteger quotedTextMaxLength = 60; // Max length of quoted text + + if( quotedTextRange.location != NSNotFound && quotedTextRange.length > quotedTextMaxLength ) + { + NSRange truncatedRange = NSMakeRange(quotedTextRange.location + quotedTextMaxLength, quotedTextRange.length - quotedTextMaxLength); + return [fullQuotedReply stringByReplacingCharactersInRange:truncatedRange withString:@"
"]; + } + + // The quoted reply is not found or already short. Return it as is. + return fullQuotedReply; +} + - (NSString*)renderPollEndedReplyTo:(NSString*)htmlString repliedEvent:(MXEvent*)repliedEvent { static NSRegularExpression *endedPollRegex; static dispatch_once_t onceToken; diff --git a/Riot/Modules/QRCode/Reader/QRCodeReaderView.swift b/Riot/Modules/QRCode/Reader/QRCodeReaderView.swift index 21f1b8497c..302cd63104 100644 --- a/Riot/Modules/QRCode/Reader/QRCodeReaderView.swift +++ b/Riot/Modules/QRCode/Reader/QRCodeReaderView.swift @@ -113,7 +113,6 @@ final class QRCodeReaderView: UIView { } private func applyOrientation() { - let orientation = UIApplication.shared.statusBarOrientation let captureRotation: Double let scanRectRotation: Double @@ -136,59 +135,13 @@ final class QRCodeReaderView: UIView { scanRectRotation = 90 } - applyRectOfInterest(orientation: orientation) - let angleRadius = captureRotation / 180.0 * Double.pi - let captureTranform = CGAffineTransform(rotationAngle: CGFloat(angleRadius)) + let captureTransform = CGAffineTransform(rotationAngle: CGFloat(angleRadius)) - zxCapture.transform = captureTranform + zxCapture.transform = captureTransform zxCapture.rotation = CGFloat(scanRectRotation) zxCapture.layer.frame = self.bounds } - - private func applyRectOfInterest(orientation: UIInterfaceOrientation) { - var transformedVideoRect = self.frame - let cameraSessionPreset = zxCapture.sessionPreset - - var scaleVideoX, scaleVideoY: CGFloat - var videoHeight, videoWidth: CGFloat - - // Currently support only for 1920x1080 || 1280x720 - if cameraSessionPreset == AVCaptureSession.Preset.hd1920x1080.rawValue { - videoHeight = 1080.0 - videoWidth = 1920.0 - } else { - videoHeight = 720.0 - videoWidth = 1280.0 - } - - if orientation == UIInterfaceOrientation.portrait { - scaleVideoX = self.frame.width / videoHeight - scaleVideoY = self.frame.height / videoWidth - - // Convert CGPoint under portrait mode to map with orientation of image - // because the image will be cropped before rotate - // reference: https://github.com/TheLevelUp/ZXingObjC/issues/222 - let realX = transformedVideoRect.origin.y - let realY = self.frame.size.width - transformedVideoRect.size.width - transformedVideoRect.origin.x - let realWidth = transformedVideoRect.size.height - let realHeight = transformedVideoRect.size.width - transformedVideoRect = CGRect(x: realX, y: realY, width: realWidth, height: realHeight) - - } else { - scaleVideoX = self.frame.width / videoWidth - scaleVideoY = self.frame.height / videoHeight - } - - captureSizeTransform = CGAffineTransform(scaleX: 1.0/scaleVideoX, y: 1.0/scaleVideoY) - - guard let _captureSizeTransform = captureSizeTransform else { - return - } - - let transformRect = transformedVideoRect.applying(_captureSizeTransform) - zxCapture.scanRect = transformRect - } } diff --git a/Riot/Modules/Room/RoomCoordinator.swift b/Riot/Modules/Room/RoomCoordinator.swift index c94cdfb178..40dede1629 100644 --- a/Riot/Modules/Room/RoomCoordinator.swift +++ b/Riot/Modules/Room/RoomCoordinator.swift @@ -315,166 +315,166 @@ final class RoomCoordinator: NSObject, RoomCoordinatorProtocol { } // Tchap: Disable Live location sharing -// private func showLiveLocationViewer() { -// guard let roomId = self.roomId else { -// return -// } -// -// self.showLiveLocationViewer(for: roomId) -// } -// -// private func showLiveLocationViewer(for roomId: String) { -// -// guard let mxSession = self.mxSession, let navigationRouter = self.navigationRouter else { -// return -// } -// -// guard mxSession.locationService.isSomeoneSharingDisplayableLocation(inRoomWithId: roomId) else { -// return -// } -// -// let parameters = LiveLocationSharingViewerCoordinatorParameters(session: mxSession, roomId: roomId, navigationRouter: nil) -// -// let coordinator = LiveLocationSharingViewerCoordinator(parameters: parameters) -// -// coordinator.completion = { [weak self, weak coordinator] in -// guard let self = self, let coordinator = coordinator else { -// return -// } -// -// self.navigationRouter?.dismissModule(animated: true, completion: nil) -// self.remove(childCoordinator: coordinator) -// } -// -// add(childCoordinator: coordinator) -// -// navigationRouter.present(coordinator, animated: true) -// coordinator.start() -// } -// -// private func stopLiveLocationSharing(forBeaconInfoEventId beaconInfoEventId: String? = nil, inRoomWithId roomId: String) { -// guard let session = self.mxSession else { -// return -// } -// -// let errorHandler: (Error) -> Void = { error in -// -// let viewController = self.roomViewController -// -// viewController.errorPresenter.presentError(from: viewController, title: VectorL10n.error, message: VectorL10n.locationSharingLiveStopSharingError, animated: true) { -// } -// } -// -// // TODO: Handle loading state on the banner by replacing stop button with a spinner -// self.showLocationSharingIndicator(withMessage: VectorL10n.locationSharingLiveStopSharingProgress) -// -// if let beaconInfoEventId = beaconInfoEventId { -// session.locationService.stopUserLocationSharing(withBeaconInfoEventId: beaconInfoEventId, roomId: roomId) { -// [weak self] response in -// -// self?.hideLocationSharingIndicator() -// -// switch response { -// case .success: -// break -// case .failure(let error): -// errorHandler(error) -// } -// } -// } else { -// session.locationService.stopUserLocationSharing(inRoomWithId: roomId) { [weak self] response in -// -// self?.hideLocationSharingIndicator() -// -// switch response { -// case .success: -// break -// case .failure(let error): -// errorHandler(error) -// } -// } -// } -// } -// -// private func showLocationCoordinatorWithEvent(_ event: MXEvent, bubbleData: MXKRoomBubbleCellDataStoring) { -// guard let mxSession = self.mxSession, -// let navigationRouter = self.navigationRouter, -// let mediaManager = mxSession.mediaManager, -// let locationContent = event.location else { -// MXLog.error("[RoomCoordinator] Invalid location showing coordinator parameters. Returning.") -// return -// } -// -// let avatarData = AvatarInput(mxContentUri: bubbleData.senderAvatarUrl, -// matrixItemId: bubbleData.senderId, -// displayName: bubbleData.senderDisplayName) -// -// -// let location = CLLocationCoordinate2D(latitude: locationContent.latitude, longitude: locationContent.longitude) -// let coordinateType = locationContent.assetType -// -// guard let locationSharingCoordinatetype = coordinateType.locationSharingCoordinateType() else { -// fatalError("[LocationSharingCoordinator] event asset type is not supported: \(coordinateType)") -// } -// -// let parameters = StaticLocationViewingCoordinatorParameters( -// session: mxSession, -// mediaManager: mediaManager, -// avatarData: avatarData, -// location: location, -// coordinateType: locationSharingCoordinatetype) -// -// let coordinator = StaticLocationViewingCoordinator(parameters: parameters) -// -// coordinator.completion = { [weak self, weak coordinator] in -// guard let self = self, let coordinator = coordinator else { -// return -// } -// -// self.navigationRouter?.dismissModule(animated: true, completion: nil) -// self.remove(childCoordinator: coordinator) -// } -// -// add(childCoordinator: coordinator) -// -// navigationRouter.present(coordinator, animated: true) -// coordinator.start() -// } -// -// private func startLocationCoordinator() { -// guard let mxSession = mxSession, -// let navigationRouter = self.navigationRouter, -// let mediaManager = mxSession.mediaManager, -// let user = mxSession.myUser else { -// MXLog.error("[RoomCoordinator] Invalid location sharing coordinator parameters. Returning.") -// return -// } -// -// let avatarData = AvatarInput(mxContentUri: user.avatarUrl, -// matrixItemId: user.userId, -// displayName: user.displayname) -// -// let parameters = LocationSharingCoordinatorParameters(session: mxSession, -// roomDataSource: roomViewController.roomDataSource, -// mediaManager: mediaManager, -// avatarData: avatarData) -// -// let coordinator = LocationSharingCoordinator(parameters: parameters) -// -// coordinator.completion = { [weak self, weak coordinator] in -// guard let self = self, let coordinator = coordinator else { -// return -// } -// -// self.navigationRouter?.dismissModule(animated: true, completion: nil) -// self.remove(childCoordinator: coordinator) -// } -// -// add(childCoordinator: coordinator) -// -// navigationRouter.present(coordinator, animated: true) -// coordinator.start() -// } + private func showLiveLocationViewer() { + guard let roomId = self.roomId else { + return + } + + self.showLiveLocationViewer(for: roomId) + } + + private func showLiveLocationViewer(for roomId: String) { + + guard let mxSession = self.mxSession, let navigationRouter = self.navigationRouter else { + return + } + + guard mxSession.locationService.isSomeoneSharingDisplayableLocation(inRoomWithId: roomId) else { + return + } + + let parameters = LiveLocationSharingViewerCoordinatorParameters(session: mxSession, roomId: roomId, navigationRouter: nil) + + let coordinator = LiveLocationSharingViewerCoordinator(parameters: parameters) + + coordinator.completion = { [weak self, weak coordinator] in + guard let self = self, let coordinator = coordinator else { + return + } + + self.navigationRouter?.dismissModule(animated: true, completion: nil) + self.remove(childCoordinator: coordinator) + } + + add(childCoordinator: coordinator) + + navigationRouter.present(coordinator, animated: true) + coordinator.start() + } + + private func stopLiveLocationSharing(forBeaconInfoEventId beaconInfoEventId: String? = nil, inRoomWithId roomId: String) { + guard let session = self.mxSession else { + return + } + + let errorHandler: (Error) -> Void = { error in + + let viewController = self.roomViewController + + viewController.errorPresenter.presentError(from: viewController, title: VectorL10n.error, message: VectorL10n.locationSharingLiveStopSharingError, animated: true) { + } + } + + // TODO: Handle loading state on the banner by replacing stop button with a spinner + self.showLocationSharingIndicator(withMessage: VectorL10n.locationSharingLiveStopSharingProgress) + + if let beaconInfoEventId = beaconInfoEventId { + session.locationService.stopUserLocationSharing(withBeaconInfoEventId: beaconInfoEventId, roomId: roomId) { + [weak self] response in + + self?.hideLocationSharingIndicator() + + switch response { + case .success: + break + case .failure(let error): + errorHandler(error) + } + } + } else { + session.locationService.stopUserLocationSharing(inRoomWithId: roomId) { [weak self] response in + + self?.hideLocationSharingIndicator() + + switch response { + case .success: + break + case .failure(let error): + errorHandler(error) + } + } + } + } + + private func showLocationCoordinatorWithEvent(_ event: MXEvent, bubbleData: MXKRoomBubbleCellDataStoring) { + guard let mxSession = self.mxSession, + let navigationRouter = self.navigationRouter, + let mediaManager = mxSession.mediaManager, + let locationContent = event.location else { + MXLog.error("[RoomCoordinator] Invalid location showing coordinator parameters. Returning.") + return + } + + let avatarData = AvatarInput(mxContentUri: bubbleData.senderAvatarUrl, + matrixItemId: bubbleData.senderId, + displayName: bubbleData.senderDisplayName) + + + let location = CLLocationCoordinate2D(latitude: locationContent.latitude, longitude: locationContent.longitude) + let coordinateType = locationContent.assetType + + guard let locationSharingCoordinatetype = coordinateType.locationSharingCoordinateType() else { + fatalError("[LocationSharingCoordinator] event asset type is not supported: \(coordinateType)") + } + + let parameters = StaticLocationViewingCoordinatorParameters( + session: mxSession, + mediaManager: mediaManager, + avatarData: avatarData, + location: location, + coordinateType: locationSharingCoordinatetype) + + let coordinator = StaticLocationViewingCoordinator(parameters: parameters) + + coordinator.completion = { [weak self, weak coordinator] in + guard let self = self, let coordinator = coordinator else { + return + } + + self.navigationRouter?.dismissModule(animated: true, completion: nil) + self.remove(childCoordinator: coordinator) + } + + add(childCoordinator: coordinator) + + navigationRouter.present(coordinator, animated: true) + coordinator.start() + } + + private func startLocationCoordinator() { + guard let mxSession = mxSession, + let navigationRouter = self.navigationRouter, + let mediaManager = mxSession.mediaManager, + let user = mxSession.myUser else { + MXLog.error("[RoomCoordinator] Invalid location sharing coordinator parameters. Returning.") + return + } + + let avatarData = AvatarInput(mxContentUri: user.avatarUrl, + matrixItemId: user.userId, + displayName: user.displayname) + + let parameters = LocationSharingCoordinatorParameters(session: mxSession, + roomDataSource: roomViewController.roomDataSource, + mediaManager: mediaManager, + avatarData: avatarData) + + let coordinator = LocationSharingCoordinator(parameters: parameters) + + coordinator.completion = { [weak self, weak coordinator] in + guard let self = self, let coordinator = coordinator else { + return + } + + self.navigationRouter?.dismissModule(animated: true, completion: nil) + self.remove(childCoordinator: coordinator) + } + + add(childCoordinator: coordinator) + + navigationRouter.present(coordinator, animated: true) + coordinator.start() + } private func startEditPollCoordinator(startEvent: MXEvent? = nil) { let parameters = PollEditFormCoordinatorParameters(room: roomViewController.roomDataSource.room, pollStartEvent: startEvent) @@ -579,7 +579,7 @@ extension RoomCoordinator: UIAdaptivePresentationControllerDelegate { // MARK: - RoomViewControllerDelegate extension RoomCoordinator: RoomViewControllerDelegate { - + func roomViewController(_ roomViewController: RoomViewController, showRoomWithId roomID: String, eventId eventID: String?) { self.delegate?.roomCoordinator(self, didSelectRoomWithId: roomID, eventId: eventID) } @@ -621,11 +621,11 @@ extension RoomCoordinator: RoomViewControllerDelegate { } func roomViewControllerDidRequestLocationSharingFormPresentation(_ roomViewController: RoomViewController) { -// startLocationCoordinator() + startLocationCoordinator() } func roomViewController(_ roomViewController: RoomViewController, didRequestLocationPresentationFor event: MXEvent, bubbleData: MXKRoomBubbleCellDataStoring) { -// showLocationCoordinatorWithEvent(event, bubbleData: bubbleData) + showLocationCoordinatorWithEvent(event, bubbleData: bubbleData) } func roomViewController(_ roomViewController: RoomViewController, didRequestLiveLocationPresentationForBubbleData bubbleData: MXKRoomBubbleCellDataStoring) { @@ -635,7 +635,7 @@ extension RoomCoordinator: RoomViewControllerDelegate { } // Tchap: Disable Live location sharing -// showLiveLocationViewer(for: roomId) + showLiveLocationViewer(for: roomId) } func roomViewController(_ roomViewController: RoomViewController, locationShareActivityViewControllerFor event: MXEvent) -> UIActivityViewController? { @@ -643,8 +643,7 @@ extension RoomCoordinator: RoomViewControllerDelegate { return nil } - // Tchap: Location Sharing is disabled in Tchap - return nil// LocationSharingCoordinator.shareLocationActivityController(CLLocationCoordinate2D(latitude: location.latitude, longitude: location.longitude)) + return LocationSharingCoordinator.shareLocationActivityController(CLLocationCoordinate2D(latitude: location.latitude, longitude: location.longitude)) } func roomViewController(_ roomViewController: RoomViewController, canEndPollWithEventIdentifier eventIdentifier: String) -> Bool { @@ -672,8 +671,7 @@ extension RoomCoordinator: RoomViewControllerDelegate { } func roomViewControllerDidTapLiveLocationSharingBanner(_ roomViewController: RoomViewController) { - -// showLiveLocationViewer() + showLiveLocationViewer() } func roomViewControllerDidStopLiveLocationSharing(_ roomViewController: RoomViewController, beaconInfoEventId: String?) { @@ -682,7 +680,7 @@ extension RoomCoordinator: RoomViewControllerDelegate { return } -// self.stopLiveLocationSharing(forBeaconInfoEventId: beaconInfoEventId, inRoomWithId: roomId) + self.stopLiveLocationSharing(forBeaconInfoEventId: beaconInfoEventId, inRoomWithId: roomId) } func threadsCoordinator(for roomViewController: RoomViewController, threadId: String?) -> ThreadsCoordinatorBridgePresenter? { diff --git a/Riot/Modules/Room/RoomViewController.h b/Riot/Modules/Room/RoomViewController.h index 9029e33e72..a05b3a0156 100644 --- a/Riot/Modules/Room/RoomViewController.h +++ b/Riot/Modules/Room/RoomViewController.h @@ -32,9 +32,9 @@ @protocol RoomViewControllerDelegate; @class RoomDisplayConfiguration; // Tchap: Disable Threads -// Tchap: Disable Live location sharing //@class ThreadsCoordinatorBridgePresenter; -//@class LiveLocationSharingBannerView; +// Tchap: Disable Live location sharing +@class LiveLocationSharingBannerView; @class VoiceBroadcastService; @class ComposerLinkActionBridgePresenter; @@ -108,11 +108,11 @@ extern NSTimeInterval const kResizeComposerAnimationDuration; @property (weak, nonatomic, nullable) IBOutlet UIStackView *topBannersStackView; // Tchap: Disable Live location sharing -// /// Indicate YES to show live location sharing banner -//@property (nonatomic, readonly) BOOL shouldShowLiveLocationSharingBannerView; -// -// /// Displayed live location sharing banner if any -//@property (nonatomic, weak) LiveLocationSharingBannerView *liveLocationSharingBannerView; + /// Indicate YES to show live location sharing banner +@property (nonatomic, readonly) BOOL shouldShowLiveLocationSharingBannerView; + + /// Displayed live location sharing banner if any +@property (nonatomic, weak) LiveLocationSharingBannerView *liveLocationSharingBannerView; // The customized room data source for Vector @property (nonatomic, nullable) RoomDataSource *customizedRoomDataSource; @@ -312,9 +312,9 @@ didRequestLocationPresentationForEvent:(MXEvent *)event bubbleData:(id)bubbleData; // Tchap: Disable Live location sharing -/// Ask the coordinator to present the live location sharing viewer. -//- (void)roomViewController:(RoomViewController *)roomViewController -//didRequestLiveLocationPresentationForBubbleData:(id)bubbleData; +// Ask the coordinator to present the live location sharing viewer. +- (void)roomViewController:(RoomViewController *)roomViewController +didRequestLiveLocationPresentationForBubbleData:(id)bubbleData; - (nullable UIActivityViewController *)roomViewController:(RoomViewController *)roomViewController locationShareActivityViewControllerForEvent:(MXEvent *)event; @@ -349,7 +349,7 @@ didRequestEditForPollWithStartEvent:(MXEvent *)startEvent; // Tchap: Disable Live location sharing /// User tap live location sharing stop action -//- (void)roomViewControllerDidStopLiveLocationSharing:(RoomViewController *)roomViewController beaconInfoEventId:(nullable NSString*)beaconInfoEventId; +- (void)roomViewControllerDidStopLiveLocationSharing:(RoomViewController *)roomViewController beaconInfoEventId:(nullable NSString*)beaconInfoEventId; /// User tap live location sharing banner - (void)roomViewControllerDidTapLiveLocationSharingBanner:(RoomViewController *)roomViewController; diff --git a/Riot/Modules/Room/RoomViewController.m b/Riot/Modules/Room/RoomViewController.m index b0024715e8..b93c1e4ac9 100644 --- a/Riot/Modules/Room/RoomViewController.m +++ b/Riot/Modules/Room/RoomViewController.m @@ -1657,10 +1657,10 @@ - (void)setMissedDiscussionsBadgeHidden:(BOOL)missedDiscussionsBadgeHidden{ } // Tchap: Disable Live location sharing -//- (BOOL)shouldShowLiveLocationSharingBannerView -//{ -// return self.customizedRoomDataSource.isCurrentUserSharingActiveLocation; -//} +- (BOOL)shouldShowLiveLocationSharingBannerView +{ + return self.customizedRoomDataSource.isCurrentUserSharingActiveLocation; +} - (void)setForceHideInputToolBar:(BOOL)forceHideInputToolBar { @@ -2425,7 +2425,10 @@ - (void)setupActions { [self.delegate roomViewControllerDidRequestPollCreationFormPresentation:self]; }]]; } - if (BuildSettings.locationSharingEnabled && !self.isNewDirectChat) + // Tchap: allow location sharing by feature flag +// if (BuildSettings.locationSharingEnabled && !self.isNewDirectChat) + MXKAccount *account = MXKAccountManager.sharedManager.activeAccounts.firstObject; + if ([account isFeatureActivated:BuildSettings.tchapFeatureGeolocationSharing] && BuildSettings.locationSharingEnabled && !self.isNewDirectChat) { [actionItems addObject:[[RoomActionItem alloc] initWithImage:AssetImages.actionLocation.image andAction:^{ MXStrongifyAndReturnIfNil(self); @@ -3330,39 +3333,39 @@ - (RoomTimelineCellIdentifier)cellIdentifierForCellData:(MXKCellData*)cellData a } } } -// else if (bubbleData.tag == RoomBubbleCellDataTagLocation || bubbleData.tag == RoomBubbleCellDataTagLiveLocation) -// { -// if (bubbleData.isIncoming) -// { -// if (bubbleData.isPaginationFirstBubble) -// { -// cellIdentifier = RoomTimelineCellIdentifierIncomingLocationWithPaginationTitle; -// } -// else if (bubbleData.shouldHideSenderInformation) -// { -// cellIdentifier = RoomTimelineCellIdentifierIncomingLocationWithoutSenderInfo; -// } -// else -// { -// cellIdentifier = RoomTimelineCellIdentifierIncomingLocation; -// } -// } -// else -// { -// if (bubbleData.isPaginationFirstBubble) -// { -// cellIdentifier = RoomTimelineCellIdentifierOutgoingLocationWithPaginationTitle; -// } -// else if (bubbleData.shouldHideSenderInformation) -// { -// cellIdentifier = RoomTimelineCellIdentifierOutgoingLocationWithoutSenderInfo; -// } -// else -// { -// cellIdentifier = RoomTimelineCellIdentifierOutgoingLocation; -// } -// } -// } + else if (bubbleData.tag == RoomBubbleCellDataTagLocation || bubbleData.tag == RoomBubbleCellDataTagLiveLocation) + { + if (bubbleData.isIncoming) + { + if (bubbleData.isPaginationFirstBubble) + { + cellIdentifier = RoomTimelineCellIdentifierIncomingLocationWithPaginationTitle; + } + else if (bubbleData.shouldHideSenderInformation) + { + cellIdentifier = RoomTimelineCellIdentifierIncomingLocationWithoutSenderInfo; + } + else + { + cellIdentifier = RoomTimelineCellIdentifierIncomingLocation; + } + } + else + { + if (bubbleData.isPaginationFirstBubble) + { + cellIdentifier = RoomTimelineCellIdentifierOutgoingLocationWithPaginationTitle; + } + else if (bubbleData.shouldHideSenderInformation) + { + cellIdentifier = RoomTimelineCellIdentifierOutgoingLocationWithoutSenderInfo; + } + else + { + cellIdentifier = RoomTimelineCellIdentifierOutgoingLocation; + } + } + } // else if (bubbleData.tag == RoomBubbleCellDataTagVoiceBroadcastPlayback) // { // if (bubbleData.isIncoming) @@ -3630,26 +3633,26 @@ - (void)dataSource:(MXKDataSource *)dataSource didRecognizeAction:(NSString *)ac [self mention:roomMember]; } } -// else if ([actionIdentifier isEqualToString:kMXKRoomBubbleCellStopShareButtonPressed]) -// { -// NSString *beaconInfoEventId; -// -// if ([bubbleData isKindOfClass:[RoomBubbleCellData class]]) -// { -// RoomBubbleCellData *roomBubbleCellData = (RoomBubbleCellData*)bubbleData; -// beaconInfoEventId = roomBubbleCellData.beaconInfoSummary.id; -// } -// -// [self.delegate roomViewControllerDidStopLiveLocationSharing:self beaconInfoEventId:beaconInfoEventId]; -// } -// else if ([actionIdentifier isEqualToString:kMXKRoomBubbleCellRetryShareButtonPressed]) -// { -// MXEvent *selectedEvent = userInfo[kMXKRoomBubbleCellEventKey]; -// if (selectedEvent) -// { -// // TODO: - Implement retry live location action -// } -// } + else if ([actionIdentifier isEqualToString:kMXKRoomBubbleCellStopShareButtonPressed]) + { + NSString *beaconInfoEventId; + + if ([bubbleData isKindOfClass:[RoomBubbleCellData class]]) + { + RoomBubbleCellData *roomBubbleCellData = (RoomBubbleCellData*)bubbleData; + beaconInfoEventId = roomBubbleCellData.beaconInfoSummary.id; + } + + [self.delegate roomViewControllerDidStopLiveLocationSharing:self beaconInfoEventId:beaconInfoEventId]; + } + else if ([actionIdentifier isEqualToString:kMXKRoomBubbleCellRetryShareButtonPressed]) + { + MXEvent *selectedEvent = userInfo[kMXKRoomBubbleCellEventKey]; + if (selectedEvent) + { + // TODO: - Implement retry live location action + } + } else if ([actionIdentifier isEqualToString:kMXKRoomBubbleCellTapOnMessageTextView] || [actionIdentifier isEqualToString:kMXKRoomBubbleCellTapOnContentView]) { // Retrieve the tapped event @@ -3660,10 +3663,10 @@ - (void)dataSource:(MXKDataSource *)dataSource didRecognizeAction:(NSString *)ac { [self cancelEventSelection]; } -// else if (bubbleData.tag == RoomBubbleCellDataTagLiveLocation) -// { -// [self.delegate roomViewController:self didRequestLiveLocationPresentationForBubbleData:bubbleData]; -// } + else if (bubbleData.tag == RoomBubbleCellDataTagLiveLocation) + { + [self.delegate roomViewController:self didRequestLiveLocationPresentationForBubbleData:bubbleData]; + } else if (tappedEvent) { if (tappedEvent.eventType == MXEventTypeRoomCreate) @@ -7767,6 +7770,15 @@ - (void)documentPickerPresenter:(MXKDocumentPickerPresenter *)presenter didPickD { self.documentPickerPresenter = nil; + // Tchap: check maxUploadSize accepted by the home server before trying to upload. + NSUInteger maxUploadFileSize = self.roomDataSource.mxSession.maxUploadSize; + NSDictionary *fileAttributes = [NSFileManager.defaultManager attributesOfItemAtPath:url.path error:nil]; + if (fileAttributes && fileAttributes.fileSize > maxUploadFileSize) { + [self showAlertWithTitle:TchapL10n.roomSendFileTooBigTitle + message:[TchapL10n roomSendFileTooBigMessage:maxUploadFileSize/(1024*1024) :fileAttributes.fileSize/(1024*1024)]]; + return; + } + MXKUTI *fileUTI = [[MXKUTI alloc] initWithLocalFileURL:url]; NSString *mimeType = fileUTI.mimeType; diff --git a/Riot/Modules/Room/TimelineCells/Call/Direct/RoomDirectCallStatusCell.swift b/Riot/Modules/Room/TimelineCells/Call/Direct/RoomDirectCallStatusCell.swift index 84e3973e46..5dc216d027 100644 --- a/Riot/Modules/Room/TimelineCells/Call/Direct/RoomDirectCallStatusCell.swift +++ b/Riot/Modules/Room/TimelineCells/Call/Direct/RoomDirectCallStatusCell.swift @@ -111,6 +111,11 @@ class RoomDirectCallStatusCell: RoomCallBaseCell { } } + // Tchap: report VoIP problem button icon ô€Œ­ + private var reportVoIPProblemButtonIcon: UIImage { + return UIImage(systemName: "exclamationmark.circle.fill")! + } + private var actionUserInfo: [AnyHashable: Any]? { if let event = callInviteEvent { return [kMXKRoomBubbleCellEventKey: event] @@ -179,7 +184,7 @@ class RoomDirectCallStatusCell: RoomCallBaseCell { view.firstButton.style = .positive view.firstButton.setTitle(TchapL10n.eventFormatterReportIncident, for: .normal) - view.firstButton.setImage(callButtonIcon, for: .normal) + view.firstButton.setImage(reportVoIPProblemButtonIcon, for: .normal) view.firstButton.removeTarget(nil, action: nil, for: .touchUpInside) view.firstButton.addTarget(self, action: #selector(reportIncidentAction(_:)), for: .touchUpInside) diff --git a/Riot/Modules/Room/TimelineCells/Styles/Bubble/BubbleRoomTimelineCellProvider.m b/Riot/Modules/Room/TimelineCells/Styles/Bubble/BubbleRoomTimelineCellProvider.m index 2dc24aa911..1e2acac960 100644 --- a/Riot/Modules/Room/TimelineCells/Styles/Bubble/BubbleRoomTimelineCellProvider.m +++ b/Riot/Modules/Room/TimelineCells/Styles/Bubble/BubbleRoomTimelineCellProvider.m @@ -110,14 +110,14 @@ - (void)registerPollCellsForTableView:(UITableView *)tableView - (void)registerLocationCellsForTableView:(UITableView*)tableView { -// // Incoming -// [tableView registerClass:LocationIncomingBubbleCell.class forCellReuseIdentifier:LocationIncomingBubbleCell.defaultReuseIdentifier]; -// [tableView registerClass:LocationIncomingWithoutSenderInfoBubbleCell.class forCellReuseIdentifier:LocationIncomingWithoutSenderInfoBubbleCell.defaultReuseIdentifier]; -// [tableView registerClass:LocationIncomingWithPaginationTitleBubbleCell.class forCellReuseIdentifier:LocationIncomingWithPaginationTitleBubbleCell.defaultReuseIdentifier]; -// -// // Outgoing -// [tableView registerClass:LocationOutgoingWithoutSenderInfoBubbleCell.class forCellReuseIdentifier:LocationOutgoingWithoutSenderInfoBubbleCell.defaultReuseIdentifier]; -// [tableView registerClass:LocationOutgoingWithPaginationTitleBubbleCell.class forCellReuseIdentifier:LocationOutgoingWithPaginationTitleBubbleCell.defaultReuseIdentifier]; + // Incoming + [tableView registerClass:LocationIncomingBubbleCell.class forCellReuseIdentifier:LocationIncomingBubbleCell.defaultReuseIdentifier]; + [tableView registerClass:LocationIncomingWithoutSenderInfoBubbleCell.class forCellReuseIdentifier:LocationIncomingWithoutSenderInfoBubbleCell.defaultReuseIdentifier]; + [tableView registerClass:LocationIncomingWithPaginationTitleBubbleCell.class forCellReuseIdentifier:LocationIncomingWithPaginationTitleBubbleCell.defaultReuseIdentifier]; + + // Outgoing + [tableView registerClass:LocationOutgoingWithoutSenderInfoBubbleCell.class forCellReuseIdentifier:LocationOutgoingWithoutSenderInfoBubbleCell.defaultReuseIdentifier]; + [tableView registerClass:LocationOutgoingWithPaginationTitleBubbleCell.class forCellReuseIdentifier:LocationOutgoingWithPaginationTitleBubbleCell.defaultReuseIdentifier]; } - (void)registerFileWithoutThumbnailCellsForTableView:(UITableView*)tableView @@ -299,7 +299,7 @@ - (void)registerVoiceBroadcastRecorderCellsForTableView:(UITableView*)tableView - (NSDictionary*)locationCellsMapping { - return @{/* + return @{ // Incoming @(RoomTimelineCellIdentifierIncomingLocation) : LocationIncomingBubbleCell.class, @(RoomTimelineCellIdentifierIncomingLocationWithoutSenderInfo) : LocationIncomingWithoutSenderInfoBubbleCell.class, @@ -307,7 +307,7 @@ - (void)registerVoiceBroadcastRecorderCellsForTableView:(UITableView*)tableView // Outgoing @(RoomTimelineCellIdentifierOutgoingLocation) : LocationOutgoingWithoutSenderInfoBubbleCell.class, @(RoomTimelineCellIdentifierOutgoingLocationWithoutSenderInfo) : LocationOutgoingWithoutSenderInfoBubbleCell.class, - @(RoomTimelineCellIdentifierOutgoingLocationWithPaginationTitle) : LocationOutgoingWithPaginationTitleBubbleCell.class*/ + @(RoomTimelineCellIdentifierOutgoingLocationWithPaginationTitle) : LocationOutgoingWithPaginationTitleBubbleCell.class }; } diff --git a/Riot/Modules/Room/TimelineCells/Styles/Plain/PlainRoomTimelineCellProvider.m b/Riot/Modules/Room/TimelineCells/Styles/Plain/PlainRoomTimelineCellProvider.m index e178d77235..f5b493a32a 100644 --- a/Riot/Modules/Room/TimelineCells/Styles/Plain/PlainRoomTimelineCellProvider.m +++ b/Riot/Modules/Room/TimelineCells/Styles/Plain/PlainRoomTimelineCellProvider.m @@ -268,9 +268,9 @@ - (void)registerPollCellsForTableView:(UITableView*)tableView - (void)registerLocationCellsForTableView:(UITableView*)tableView { -// [tableView registerClass:LocationCell.class forCellReuseIdentifier:LocationCell.defaultReuseIdentifier]; -// [tableView registerClass:LocationWithoutSenderInfoCell.class forCellReuseIdentifier:LocationWithoutSenderInfoCell.defaultReuseIdentifier]; -// [tableView registerClass:LocationWithPaginationTitleCell.class forCellReuseIdentifier:LocationWithPaginationTitleCell.defaultReuseIdentifier]; + [tableView registerClass:LocationPlainCell.class forCellReuseIdentifier:LocationPlainCell.defaultReuseIdentifier]; + [tableView registerClass:LocationWithoutSenderInfoPlainCell.class forCellReuseIdentifier:LocationWithoutSenderInfoPlainCell.defaultReuseIdentifier]; + [tableView registerClass:LocationWithPaginationTitlePlainCell.class forCellReuseIdentifier:LocationWithPaginationTitlePlainCell.defaultReuseIdentifier]; } - (void)registerAntivirusCellsForTableView:(UITableView*)tableView @@ -580,13 +580,13 @@ - (void)registerVoiceBroadcastRecorderCellsForTableView:(UITableView*)tableView { return @{ // Incoming -// @(RoomTimelineCellIdentifierIncomingLocation) : LocationPlainCell.class, -// @(RoomTimelineCellIdentifierIncomingLocationWithoutSenderInfo) : LocationWithoutSenderInfoPlainCell.class, -// @(RoomTimelineCellIdentifierIncomingLocationWithPaginationTitle) : LocationWithPaginationTitlePlainCell.class, + @(RoomTimelineCellIdentifierIncomingLocation) : LocationPlainCell.class, + @(RoomTimelineCellIdentifierIncomingLocationWithoutSenderInfo) : LocationWithoutSenderInfoPlainCell.class, + @(RoomTimelineCellIdentifierIncomingLocationWithPaginationTitle) : LocationWithPaginationTitlePlainCell.class, // Outgoing -// @(RoomTimelineCellIdentifierOutgoingLocation) : LocationPlainCell.class, -// @(RoomTimelineCellIdentifierOutgoingLocationWithoutSenderInfo) : LocationWithoutSenderInfoPlainCell.class, -// @(RoomTimelineCellIdentifierOutgoingLocationWithPaginationTitle) : LocationWithPaginationTitlePlainCell.class + @(RoomTimelineCellIdentifierOutgoingLocation) : LocationPlainCell.class, + @(RoomTimelineCellIdentifierOutgoingLocationWithoutSenderInfo) : LocationWithoutSenderInfoPlainCell.class, + @(RoomTimelineCellIdentifierOutgoingLocationWithPaginationTitle) : LocationWithPaginationTitlePlainCell.class }; } diff --git a/Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarView.m b/Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarView.m index 06a4e1ee1a..1bdbdf03ae 100644 --- a/Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarView.m +++ b/Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarView.m @@ -234,7 +234,9 @@ - (void)updateToolbarButtonLabelWithPreviousMode:(RoomInputToolbarViewSendMode)p self.textView.maxHeight -= kContextBarHeight; break; case RoomInputToolbarViewSendModeEdit: - buttonImage = AssetImages.saveIcon.image; + // Tchap: use Tchap icon to save edited message. + // buttonImage = AssetImages.saveIcon.image; + buttonImage = AssetImages_tchap.sendIconTchap.image ; self.inputContextImageView.image = AssetImages.inputEditIcon.image; self.inputContextLabel.text = [VectorL10n roomMessageEditing]; diff --git a/Riot/Modules/Settings/SettingsViewController.m b/Riot/Modules/Settings/SettingsViewController.m index e76ab67acb..23151dbe15 100644 --- a/Riot/Modules/Settings/SettingsViewController.m +++ b/Riot/Modules/Settings/SettingsViewController.m @@ -190,8 +190,7 @@ typedef NS_ENUM(NSUInteger, LABS_ENABLE) LABS_ENABLE_RINGING_FOR_GROUP_CALLS_INDEX = 0, LABS_ENABLE_THREADS_INDEX, LABS_ENABLE_AUTO_REPORT_DECRYPTION_ERRORS, - // Tchap: Location sharing is disabled in Tchap -// LABS_ENABLE_LIVE_LOCATION_SHARING, + LABS_ENABLE_LIVE_LOCATION_SHARING, LABS_ENABLE_NEW_SESSION_MANAGER, LABS_ENABLE_NEW_CLIENT_INFO_FEATURE, LABS_ENABLE_WYSIWYG_COMPOSER, @@ -681,8 +680,7 @@ - (void)updateSections [sectionLabs addRowWithTag:LABS_ENABLE_AUTO_REPORT_DECRYPTION_ERRORS]; if (BuildSettings.locationSharingEnabled) { - // Tchap: Location sharing is disabled in Tchap -// [sectionLabs addRowWithTag:LABS_ENABLE_LIVE_LOCATION_SHARING]; + [sectionLabs addRowWithTag:LABS_ENABLE_LIVE_LOCATION_SHARING]; } [sectionLabs addRowWithTag:LABS_ENABLE_NEW_SESSION_MANAGER]; [sectionLabs addRowWithTag:LABS_ENABLE_NEW_CLIENT_INFO_FEATURE]; @@ -2107,8 +2105,8 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N // Tchap: Add Hide User from directory MXKTableViewCellWithLabelAndSwitch* labelAndSwitchCell = [self getLabelAndSwitchCell:tableView forIndexPath:indexPath]; - NSString *title = NSLocalizedStringFromTable(@"settings_hide_from_users_directory_title", @"Tchap", nil); - NSString *summary = NSLocalizedStringFromTable(@"settings_hide_from_users_directory_summary", @"Tchap", nil); + NSString *title = TchapL10n.settingsHideFromUsersDirectoryTitle; + NSString *summary = TchapL10n.settingsHideFromUsersDirectorySummary; NSMutableAttributedString *attributedText = [[NSMutableAttributedString alloc] initWithString: title attributes:@{NSForegroundColorAttributeName : ThemeService.shared.theme.textPrimaryColor, NSFontAttributeName: [UIFont systemFontOfSize:17.0]}]; @@ -2171,7 +2169,18 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N { MXKTableViewCellWithLabelAndSwitch* labelAndSwitchCell = [self getLabelAndSwitchCell:tableView forIndexPath:indexPath]; - labelAndSwitchCell.mxkLabel.text = [VectorL10n settingsEnablePushNotif]; + // Tchap: adapt "Enable notifications" label and text. +// labelAndSwitchCell.mxkLabel.text = [VectorL10n settingsEnablePushNotif]; + + NSMutableAttributedString *attributedText = [[NSMutableAttributedString alloc] initWithString: VectorL10n.settingsEnablePushNotif + attributes:@{NSForegroundColorAttributeName : ThemeService.shared.theme.textPrimaryColor, + NSFontAttributeName: [UIFont systemFontOfSize:17.0]}]; + [attributedText appendAttributedString:[[NSAttributedString alloc] initWithString:@"\n\n" attributes:@{NSFontAttributeName: [UIFont systemFontOfSize:4]}]]; + [attributedText appendAttributedString:[[NSMutableAttributedString alloc] initWithString: TchapL10n.settingsEnablePushNotifText + attributes:@{NSForegroundColorAttributeName : ThemeService.shared.theme.textSecondaryColor, + NSFontAttributeName: [UIFont systemFontOfSize:14.0]}]]; + + labelAndSwitchCell.mxkLabel.attributedText = attributedText; labelAndSwitchCell.mxkSwitch.onTintColor = ThemeService.shared.theme.tintColor; labelAndSwitchCell.mxkSwitch.enabled = YES; [labelAndSwitchCell.mxkSwitch addTarget:self action:@selector(togglePushNotifications:) forControlEvents:UIControlEventTouchUpInside]; @@ -2232,7 +2241,34 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N { MXKTableViewCellWithLabelAndSwitch* labelAndSwitchCell = [self getLabelAndSwitchCell:tableView forIndexPath:indexPath]; - labelAndSwitchCell.mxkLabel.text = TchapL10n.settingsNotificationEmail; + + + // Tchap: add explanation to "enable notiofications by email" +// labelAndSwitchCell.mxkLabel.text = TchapL10n.settingsNotificationEmail; + + NSMutableAttributedString *attributedText = [[NSMutableAttributedString alloc] initWithString: TchapL10n.settingsNotificationEmail + attributes:@{NSForegroundColorAttributeName : ThemeService.shared.theme.textPrimaryColor, + NSFontAttributeName: [UIFont systemFontOfSize:17.0]}]; + [attributedText appendAttributedString:[[NSAttributedString alloc] initWithString:@"\n\n" attributes:@{NSFontAttributeName: [UIFont systemFontOfSize:4]}]]; + [attributedText appendAttributedString:[[NSMutableAttributedString alloc] initWithString: TchapL10n.settingsEnableEmailNotifText + attributes:@{NSForegroundColorAttributeName : ThemeService.shared.theme.textSecondaryColor, + NSFontAttributeName: [UIFont systemFontOfSize:14.0]}]]; + + [attributedText appendString:@" "]; + + [attributedText appendAttributedString: + [[NSAttributedString alloc] initWithString:TchapL10n.settingsEnableEmailNotifLink + attributes:@{ + NSForegroundColorAttributeName: ThemeService.shared.theme.textSecondaryColor, + NSFontAttributeName: [UIFont systemFontOfSize:14.0], + NSUnderlineStyleAttributeName: [NSNumber numberWithInt:NSUnderlineStyleSingle] + }]]; + + labelAndSwitchCell.mxkLabel.attributedText = attributedText; + + [labelAndSwitchCell.mxkLabel addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(displayEmailNotificationFaq)]]; + labelAndSwitchCell.mxkLabel.userInteractionEnabled = YES; + labelAndSwitchCell.mxkSwitch.onTintColor = ThemeService.shared.theme.tintColor; labelAndSwitchCell.mxkSwitch.enabled = YES; [labelAndSwitchCell.mxkSwitch addTarget:self action:@selector(toggleNotificationsByEmail:) forControlEvents:UIControlEventTouchUpInside]; @@ -2691,11 +2727,10 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N { cell = [self buildAutoReportDecryptionErrorsCellForTableView:tableView atIndexPath:indexPath]; } - // Tchap: Location sharing is disabled in Tchap -// else if (row == LABS_ENABLE_LIVE_LOCATION_SHARING) -// { -// cell = [self buildLiveLocationSharingCellForTableView:tableView atIndexPath:indexPath]; -// } + else if (row == LABS_ENABLE_LIVE_LOCATION_SHARING) + { + cell = [self buildLiveLocationSharingCellForTableView:tableView atIndexPath:indexPath]; + } else if (row == LABS_ENABLE_NEW_SESSION_MANAGER) { MXKTableViewCellWithLabelAndSwitch *labelAndSwitchCell = [self getLabelAndSwitchCell:tableView forIndexPath:indexPath]; @@ -5119,4 +5154,12 @@ - (void)showUserSessionsFlow // MXLogWarning(@"Unexpected callback after OIDC account management.") //} +// Tchap: display email notification faq +- (void)displayEmailNotificationFaq { + NSString *targetUrlString = @"https://aide.tchap.beta.gouv.fr/fr/article/notification-par-email-draft-6k7k89/"; + + WebSheetViewController *webCtrl = [[WebSheetViewController alloc] initWithTargetUrl:[NSURL URLWithString:targetUrlString]]; + [self presentViewController:webCtrl animated:YES completion:nil]; +} + @end diff --git a/Riot/Modules/Spaces/SpaceRoomList/ExploreRoomCoordinator.swift b/Riot/Modules/Spaces/SpaceRoomList/ExploreRoomCoordinator.swift index a5cde02ff3..24125ae49c 100644 --- a/Riot/Modules/Spaces/SpaceRoomList/ExploreRoomCoordinator.swift +++ b/Riot/Modules/Spaces/SpaceRoomList/ExploreRoomCoordinator.swift @@ -428,8 +428,7 @@ extension ExploreRoomCoordinator: RoomViewControllerDelegate { guard let location = event.location else { return nil } - // Tchap: No location sharing available in Tchap - return nil// LocationSharingCoordinator.shareLocationActivityController(CLLocationCoordinate2D(latitude: location.latitude, longitude: location.longitude)) + return LocationSharingCoordinator.shareLocationActivityController(CLLocationCoordinate2D(latitude: location.latitude, longitude: location.longitude)) } func roomViewController(_ roomViewController: RoomViewController, canEditPollWithEventIdentifier eventIdentifier: String) -> Bool { diff --git a/Riot/SupportingFiles/PrivacyInfo.xcprivacy b/Riot/SupportingFiles/PrivacyInfo.xcprivacy new file mode 100644 index 0000000000..500ae9affe --- /dev/null +++ b/Riot/SupportingFiles/PrivacyInfo.xcprivacy @@ -0,0 +1,41 @@ + + + + + NSPrivacyAccessedAPITypes + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryUserDefaults + NSPrivacyAccessedAPITypeReasons + + 1C8F.1 + + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryFileTimestamp + NSPrivacyAccessedAPITypeReasons + + C617.1 + + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryDiskSpace + NSPrivacyAccessedAPITypeReasons + + 7D9E.1 + + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategorySystemBootTime + NSPrivacyAccessedAPITypeReasons + + 3D61.1 + + + + + diff --git a/Riot/Utils/EventFormatter.m b/Riot/Utils/EventFormatter.m index 27b6a99065..d456343ea1 100644 --- a/Riot/Utils/EventFormatter.m +++ b/Riot/Utils/EventFormatter.m @@ -23,7 +23,6 @@ #import "WidgetManager.h" #import "MXDecryptionResult.h" -#import "DecryptionFailureTracker.h" #import @@ -370,8 +369,10 @@ - (NSAttributedString *)unsafeAttributedStringFromEvent:(MXEvent *)event // }]]; NSMutableAttributedString *attributedStringWithRerequestMessage = [attributedString mutableCopy]; + // Tchap: Reset initial String to optimize error message for user + [attributedStringWithRerequestMessage.mutableString setString: [NSString stringWithFormat:@"%@\n", VectorL10n.noticeCryptoErrorUnknownInboundSessionId]]; - [attributedStringWithRerequestMessage appendString:[NSString stringWithFormat:@" %@\n", VectorL10n.noticeCryptoErrorUnknownInboundSessionId]]; +// [attributedStringWithRerequestMessage appendString:[NSString stringWithFormat:@" %@\n", VectorL10n.noticeCryptoErrorUnknownInboundSessionId]]; NSString *linkActionString = [NSString stringWithFormat:@"%@%@%@", EventFormatterFaqLinkAction, EventFormatterLinkActionSeparator, @"https://aide.tchap.beta.gouv.fr/fr/article/dechiffrement-impossible-de-mes-messages-comment-y-remedier-iphone-xotgv1"]; @@ -385,6 +386,11 @@ - (NSAttributedString *)unsafeAttributedStringFromEvent:(MXEvent *)event NSUnderlineStyleAttributeName: [NSNumber numberWithInt:NSUnderlineStyleSingle] }]]; + [attributedStringWithRerequestMessage addAttributes:@{ + NSForegroundColorAttributeName: [UIColor colorWithRed:106.0/255.0 green:106.0/255.0 blue:106.0/255.0 alpha:1.0], + NSFontAttributeName: self.encryptedMessagesTextFont, + } range:NSMakeRange(0, attributedStringWithRerequestMessage.length)]; + attributedString = attributedStringWithRerequestMessage; } } diff --git a/RiotNSE/BuildSettings.swift b/RiotNSE/BuildSettings.swift index 15bd01100e..ce637a7d40 100644 --- a/RiotNSE/BuildSettings.swift +++ b/RiotNSE/BuildSettings.swift @@ -267,17 +267,21 @@ final class BuildSettings: NSObject { static let tchapFeatureNotificationByEmail = "tchapFeatureNotificationByEmail" static let tchapFeatureVoiceOverIP = "tchapFeatureVoiceOverIP" static let tchapFeatureVideoOverIP = "tchapFeatureVideoOverIP" + static let tchapFeatureGeolocationSharing = "tchapFeatureGeolocationSharing" // linked to `locationSharingEnabled` property (see above) static var tchapFeaturesAllowedHomeServersForFeature: [String: [String]] = [ tchapFeatureNotificationByEmail: [ - "agent.dinum.tchap.gouv.fr" + tchapFeatureAnyHomeServer ], tchapFeatureVoiceOverIP: [ "agent.dinum.tchap.gouv.fr" - ] + ], // No activation of video calls actually in Tchap Production. // tchapFeatureVideoOverIP: [ // "agent.dinum.tchap.gouv.fr" // ], + tchapFeatureGeolocationSharing: [ + tchapFeatureAnyHomeServer + ] ] // MARK: - Side Menu @@ -452,9 +456,14 @@ final class BuildSettings: NSObject { // MARK: - Location Sharing /// Overwritten by the home server's .well-known configuration (if any exists) - static let defaultTileServerMapStyleURL = URL(string: "https://api.maptiler.com/maps/streets/style.json?key=")! - - static let locationSharingEnabled = false // Currently disabled in Tchap. + // Tchap: handle different map providers. + private enum TchapMapProvider: String { + case geoDataGouv = "https://openmaptiles.geo.data.gouv.fr/styles/osm-bright/style.json" + case ign = "https://data.geopf.fr/annexes/ressources/vectorTiles/styles/PLAN.IGN/standard.json" + } + static let defaultTileServerMapStyleURL = URL(string: TchapMapProvider.geoDataGouv.rawValue)! + + static let locationSharingEnabled = true // MARK: - Voice Broadcast static let voiceBroadcastChunkLength: Int = 120 diff --git a/RiotNSE/Common.xcconfig b/RiotNSE/Common.xcconfig index 5fb618d128..19b42ed0c8 100644 --- a/RiotNSE/Common.xcconfig +++ b/RiotNSE/Common.xcconfig @@ -19,7 +19,10 @@ #include "Tchap/SupportingFiles/App-Common.xcconfig" -PRODUCT_NAME = RiotNSE + +// Tchap: Customize TchapNSE user-agent for back-end logs. +// PRODUCT_NAME = RiotNSE +PRODUCT_NAME = TchapNSE PRODUCT_BUNDLE_IDENTIFIER = $(BASE_BUNDLE_IDENTIFIER).nse INFOPLIST_FILE = RiotNSE/Info.plist @@ -27,5 +30,8 @@ INFOPLIST_FILE = RiotNSE/Info.plist CODE_SIGN_ENTITLEMENTS = RiotNSE/RiotNSE.entitlements SKIP_INSTALL = YES -SWIFT_OBJC_BRIDGING_HEADER = $(SRCROOT)/$(PRODUCT_NAME)/SupportingFiles/RiotNSE-Bridging-Header.h + +// Tchap: Customize TchapNSE user-agent for back-end logs. +// SWIFT_OBJC_BRIDGING_HEADER = $(SRCROOT)/$(PRODUCT_NAME)/SupportingFiles/RiotNSE-Bridging-Header.h +SWIFT_OBJC_BRIDGING_HEADER = $(SRCROOT)/RiotNSE/SupportingFiles/RiotNSE-Bridging-Header.h diff --git a/RiotNSE/SupportingFiles/PrivacyInfo.xcprivacy b/RiotNSE/SupportingFiles/PrivacyInfo.xcprivacy new file mode 100644 index 0000000000..500ae9affe --- /dev/null +++ b/RiotNSE/SupportingFiles/PrivacyInfo.xcprivacy @@ -0,0 +1,41 @@ + + + + + NSPrivacyAccessedAPITypes + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryUserDefaults + NSPrivacyAccessedAPITypeReasons + + 1C8F.1 + + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryFileTimestamp + NSPrivacyAccessedAPITypeReasons + + C617.1 + + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryDiskSpace + NSPrivacyAccessedAPITypeReasons + + 7D9E.1 + + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategorySystemBootTime + NSPrivacyAccessedAPITypeReasons + + 3D61.1 + + + + + diff --git a/RiotShareExtension/BuildSettings.swift b/RiotShareExtension/BuildSettings.swift index 15bd01100e..ce637a7d40 100644 --- a/RiotShareExtension/BuildSettings.swift +++ b/RiotShareExtension/BuildSettings.swift @@ -267,17 +267,21 @@ final class BuildSettings: NSObject { static let tchapFeatureNotificationByEmail = "tchapFeatureNotificationByEmail" static let tchapFeatureVoiceOverIP = "tchapFeatureVoiceOverIP" static let tchapFeatureVideoOverIP = "tchapFeatureVideoOverIP" + static let tchapFeatureGeolocationSharing = "tchapFeatureGeolocationSharing" // linked to `locationSharingEnabled` property (see above) static var tchapFeaturesAllowedHomeServersForFeature: [String: [String]] = [ tchapFeatureNotificationByEmail: [ - "agent.dinum.tchap.gouv.fr" + tchapFeatureAnyHomeServer ], tchapFeatureVoiceOverIP: [ "agent.dinum.tchap.gouv.fr" - ] + ], // No activation of video calls actually in Tchap Production. // tchapFeatureVideoOverIP: [ // "agent.dinum.tchap.gouv.fr" // ], + tchapFeatureGeolocationSharing: [ + tchapFeatureAnyHomeServer + ] ] // MARK: - Side Menu @@ -452,9 +456,14 @@ final class BuildSettings: NSObject { // MARK: - Location Sharing /// Overwritten by the home server's .well-known configuration (if any exists) - static let defaultTileServerMapStyleURL = URL(string: "https://api.maptiler.com/maps/streets/style.json?key=")! - - static let locationSharingEnabled = false // Currently disabled in Tchap. + // Tchap: handle different map providers. + private enum TchapMapProvider: String { + case geoDataGouv = "https://openmaptiles.geo.data.gouv.fr/styles/osm-bright/style.json" + case ign = "https://data.geopf.fr/annexes/ressources/vectorTiles/styles/PLAN.IGN/standard.json" + } + static let defaultTileServerMapStyleURL = URL(string: TchapMapProvider.geoDataGouv.rawValue)! + + static let locationSharingEnabled = true // MARK: - Voice Broadcast static let voiceBroadcastChunkLength: Int = 120 diff --git a/RiotShareExtension/SupportingFiles/PrivacyInfo.xcprivacy b/RiotShareExtension/SupportingFiles/PrivacyInfo.xcprivacy new file mode 100644 index 0000000000..c3b45f6413 --- /dev/null +++ b/RiotShareExtension/SupportingFiles/PrivacyInfo.xcprivacy @@ -0,0 +1,41 @@ + + + + + NSPrivacyAccessedAPITypes + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryUserDefaults + NSPrivacyAccessedAPITypeReasons + + 1C8F.1 + + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryFileTimestamp + NSPrivacyAccessedAPITypeReasons + + C617.1 + + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryDiskSpace + NSPrivacyAccessedAPITypeReasons + + 7D9E.1 + + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategorySystemBootTime + NSPrivacyAccessedAPITypeReasons + + 3D61.1 + + + + + \ No newline at end of file diff --git a/RiotSwiftUI/Info.plist b/RiotSwiftUI/Info.plist index a74d218642..df3ec702a8 100644 --- a/RiotSwiftUI/Info.plist +++ b/RiotSwiftUI/Info.plist @@ -23,8 +23,8 @@ CFBundleDisplayName RiotSwiftUI NSLocationWhenInUseUsageDescription - When you share your location to people, Element needs access to show them a map. + When you share your location to people, Tchap needs access to show them a map. NSLocationAlwaysAndWhenInUseUsageDescription - When you share your location to people, Element needs access to show them a map. + When you share your location to people, Tchap needs access to show them a map. diff --git a/RiotSwiftUI/Modules/Common/Test/UI/XCUIElement.swift b/RiotSwiftUI/Modules/Common/Test/UI/XCUIElement.swift new file mode 100644 index 0000000000..db41a603ef --- /dev/null +++ b/RiotSwiftUI/Modules/Common/Test/UI/XCUIElement.swift @@ -0,0 +1,28 @@ +// +// 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 + +extension XCUIElement { + func forceTap() { + if isHittable { + tap() + } else { + let coordinate: XCUICoordinate = coordinate(withNormalizedOffset: .init(dx: 0.5, dy: 0.5)) + coordinate.tap() + } + } +} diff --git a/RiotSwiftUI/Modules/Room/Composer/View/Composer.swift b/RiotSwiftUI/Modules/Room/Composer/View/Composer.swift index f2a60ce066..5850980920 100644 --- a/RiotSwiftUI/Modules/Room/Composer/View/Composer.swift +++ b/RiotSwiftUI/Modules/Room/Composer/View/Composer.swift @@ -137,7 +137,7 @@ struct Composer: View { placeholder: viewModel.viewState.placeholder ?? "", viewModel: wysiwygViewModel, itemProviderHelper: nil, - keyCommandHandler: handleKeyCommand, + keyCommands: keyCommands, pasteHandler: nil ) .clipped() @@ -228,15 +228,13 @@ struct Composer: View { } } - func handleKeyCommand(_ keyCommand: WysiwygKeyCommand) -> Bool { - switch keyCommand { - case .enter: - sendMessageAction(wysiwygViewModel.content) - wysiwygViewModel.clearContent() - return true - case .shiftEnter: - return false - } + var keyCommands: [WysiwygKeyCommand] { + [ + .enter { + sendMessageAction(wysiwygViewModel.content) + wysiwygViewModel.clearContent() + } + ] } /// Computes the total height of the composer (excluding the RTE formatting bar). diff --git a/RiotSwiftUI/Modules/Settings/Notifications/Test/Unit/NotificationSettingsViewModelTests.swift b/RiotSwiftUI/Modules/Settings/Notifications/Test/Unit/NotificationSettingsViewModelTests.swift index 95b5e08fad..b241dcfcee 100644 --- a/RiotSwiftUI/Modules/Settings/Notifications/Test/Unit/NotificationSettingsViewModelTests.swift +++ b/RiotSwiftUI/Modules/Settings/Notifications/Test/Unit/NotificationSettingsViewModelTests.swift @@ -41,37 +41,6 @@ final class NotificationSettingsViewModelTests: XCTestCase { XCTAssertEqual(viewModel.viewState.selectionState[.encrypted], false) } - func testUpdateOneToOneRuleAlsoUpdatesPollRules() async { - setupWithPollRules() - - await viewModel.update(ruleID: .oneToOneRoom, isChecked: false) - - XCTAssertEqual(viewModel.viewState.selectionState.count, 8) - XCTAssertEqual(viewModel.viewState.selectionState[.oneToOneRoom], false) - XCTAssertEqual(viewModel.viewState.selectionState[.oneToOnePollStart], false) - XCTAssertEqual(viewModel.viewState.selectionState[.oneToOnePollEnd], false) - - // unrelated poll rules stay the same - XCTAssertEqual(viewModel.viewState.selectionState[.allOtherMessages], true) - XCTAssertEqual(viewModel.viewState.selectionState[.pollStart], true) - XCTAssertEqual(viewModel.viewState.selectionState[.pollEnd], true) - } - - func testUpdateMessageRuleAlsoUpdatesPollRules() async { - setupWithPollRules() - - await viewModel.update(ruleID: .allOtherMessages, isChecked: false) - XCTAssertEqual(viewModel.viewState.selectionState.count, 8) - XCTAssertEqual(viewModel.viewState.selectionState[.allOtherMessages], false) - XCTAssertEqual(viewModel.viewState.selectionState[.pollStart], false) - XCTAssertEqual(viewModel.viewState.selectionState[.pollEnd], false) - - // unrelated poll rules stay the same - XCTAssertEqual(viewModel.viewState.selectionState[.oneToOneRoom], true) - XCTAssertEqual(viewModel.viewState.selectionState[.oneToOnePollStart], true) - XCTAssertEqual(viewModel.viewState.selectionState[.oneToOnePollEnd], true) - } - func testMismatchingRulesAreHandled() async { setupWithPollRules() diff --git a/RiotSwiftUI/Modules/UserSessions/UserOtherSessions/Test/UI/UserOtherSessionsUITests.swift b/RiotSwiftUI/Modules/UserSessions/UserOtherSessions/Test/UI/UserOtherSessionsUITests.swift index 71a6516592..3fcf2a8ccd 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserOtherSessions/Test/UI/UserOtherSessionsUITests.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserOtherSessions/Test/UI/UserOtherSessionsUITests.swift @@ -56,7 +56,7 @@ class UserOtherSessionsUITests: MockScreenTestCase { func test_whenOtherSessionsMoreMenuButtonSelected_moreMenuIsCorrect() { app.goToScreenWithIdentifier(MockUserOtherSessionsScreenState.all.title) - app.buttons["More"].tap() + app.buttons["More"].forceTap() XCTAssertTrue(app.buttons["Select sessions"].exists) XCTAssertTrue(app.buttons["Sign out of 6 sessions"].exists) } @@ -64,7 +64,7 @@ class UserOtherSessionsUITests: MockScreenTestCase { func test_whenOtherSessionsSelectSessionsSelected_navBarContainsCorrectButtons() { app.goToScreenWithIdentifier(MockUserOtherSessionsScreenState.all.title) - app.buttons["More"].tap() + app.buttons["More"].forceTap() app.buttons["Select sessions"].tap() let signOutButton = app.buttons["Sign out"] XCTAssertTrue(signOutButton.exists) @@ -76,7 +76,7 @@ class UserOtherSessionsUITests: MockScreenTestCase { func test_whenOtherSessionsSelectAllSelected_navBarContainsCorrectButtons() { app.goToScreenWithIdentifier(MockUserOtherSessionsScreenState.all.title) - app.buttons["More"].tap() + app.buttons["More"].forceTap() app.buttons["Select sessions"].tap() app.buttons["Select All"].tap() XCTAssertTrue(app.buttons["Deselect All"].exists) @@ -85,7 +85,7 @@ class UserOtherSessionsUITests: MockScreenTestCase { func test_whenAllOtherSessionsAreSelected_navBarContainsCorrectButtons() { app.goToScreenWithIdentifier(MockUserOtherSessionsScreenState.all.title) - app.buttons["More"].tap() + app.buttons["More"].forceTap() app.buttons["Select sessions"].tap() for i in 0...MockUserOtherSessionsScreenState.all.allSessions().count - 1 { app.buttons["UserSessionListItem_\(i)"].tap() @@ -95,7 +95,7 @@ class UserOtherSessionsUITests: MockScreenTestCase { func test_whenChangingSessionSelection_signOutButtonChangesItState() { app.goToScreenWithIdentifier(MockUserOtherSessionsScreenState.all.title) - app.buttons["More"].tap() + app.buttons["More"].forceTap() app.buttons["Select sessions"].tap() let signOutButton = app.buttons["Sign out"] XCTAssertTrue(signOutButton.exists) diff --git a/RiotSwiftUI/Modules/UserSessions/UserOtherSessions/View/UserOtherSessionsToolbar.swift b/RiotSwiftUI/Modules/UserSessions/UserOtherSessions/View/UserOtherSessionsToolbar.swift index ba844904ec..39970240d9 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserOtherSessions/View/UserOtherSessionsToolbar.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserOtherSessions/View/UserOtherSessionsToolbar.swift @@ -80,35 +80,33 @@ struct UserOtherSessionsToolbar: ToolbarContent { } private func optionsMenu() -> some View { - Button { } label: { - Menu { - if showDeviceLogout { // As you can only sign out the selected sessions, we don't allow selection when you're unable to sign out devices. - Button { - isEditModeEnabled = true - } label: { - Label(VectorL10n.userOtherSessionMenuSelectSessions, systemImage: "checkmark.circle") - } - .disabled(sessionCount == 0) - } - + Menu { + if showDeviceLogout { // As you can only sign out the selected sessions, we don't allow selection when you're unable to sign out devices. Button { - isShowLocationEnabled.toggle() + isEditModeEnabled = true } label: { - Label(showLocationInfo: isShowLocationEnabled) - } - - if sessionCount > 0, showDeviceLogout { - DestructiveButton { - onSignOut() - } label: { - Label(VectorL10n.userOtherSessionMenuSignOutSessions(String(sessionCount)), systemImage: "rectangle.portrait.and.arrow.forward.fill") - } + Label(VectorL10n.userOtherSessionMenuSelectSessions, systemImage: "checkmark.circle") } + .disabled(sessionCount == 0) + } + + Button { + isShowLocationEnabled.toggle() } label: { - Image(systemName: "ellipsis") - .padding(.horizontal, 4) - .padding(.vertical, 12) + Label(showLocationInfo: isShowLocationEnabled) + } + + if sessionCount > 0, showDeviceLogout { + DestructiveButton { + onSignOut() + } label: { + Label(VectorL10n.userOtherSessionMenuSignOutSessions(String(sessionCount)), systemImage: "rectangle.portrait.and.arrow.forward.fill") + } } + } label: { + Image(systemName: "ellipsis") + .padding(.horizontal, 4) + .padding(.vertical, 12) } .accessibilityIdentifier("More") } diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/Test/UI/UserSessionOverviewUITests.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/Test/UI/UserSessionOverviewUITests.swift index 643c28cbe9..93938057ca 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/Test/UI/UserSessionOverviewUITests.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/Test/UI/UserSessionOverviewUITests.swift @@ -67,7 +67,7 @@ class UserSessionOverviewUITests: MockScreenTestCase { let navTitle = VectorL10n.userSessionOverviewSessionTitle let barButton = app.navigationBars[navTitle].buttons["Menu"] XCTAssertTrue(barButton.exists) - barButton.tap() + barButton.forceTap() XCTAssertTrue(app.buttons[VectorL10n.signOut].exists) XCTAssertTrue(app.buttons[VectorL10n.manageSessionRename].exists) } diff --git a/RiotSwiftUI/target.yml b/RiotSwiftUI/target.yml index d7f17f313f..ca8d623c7d 100644 --- a/RiotSwiftUI/target.yml +++ b/RiotSwiftUI/target.yml @@ -65,7 +65,7 @@ targets: - path: ../Riot/Categories/Codable.swift - path: ../Riot/Assets/en.lproj/Vector.strings - path: ../Riot/Modules/Analytics/AnalyticsScreen.swift - - path: ../Riot/Modules/LocationSharing/LocationAuthorizationStatus.swift + - path: ../Riot/Modules/LocationSharing/ - path: ../Riot/Modules/QRCode/QRCodeGenerator.swift - path: ../Riot/Modules/VoiceBroadcast/VoiceBroadcastSDK/MatrixSDK/VoiceBroadcastInfoState.swift - path: ../Riot/Assets/en.lproj/Untranslated.strings diff --git a/RiotTests/DecryptionFailureTrackerTests.swift b/RiotTests/DecryptionFailureTrackerTests.swift new file mode 100644 index 0000000000..7cd9bf480f --- /dev/null +++ b/RiotTests/DecryptionFailureTrackerTests.swift @@ -0,0 +1,341 @@ +// +// 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 XCTest +@testable import Element + + +class DecryptionFailureTrackerTests: XCTestCase { + + class TimeShifter: TimeProvider { + + var timestamp = TimeInterval(0) + + func nowTs() -> TimeInterval { + return timestamp + } + } + + class AnalyticsDelegate : E2EAnalytics { + var reportedFailure: Element.DecryptionFailure?; + + func trackE2EEError(_ reason: Element.DecryptionFailure) { + reportedFailure = reason + } + + } + + let timeShifter = TimeShifter() + + func test_grace_period() { + + let myUser = "test@example.com"; + + let decryptionFailureTracker = DecryptionFailureTracker(); + decryptionFailureTracker.timeProvider = timeShifter; + + let testDelegate = AnalyticsDelegate(); + + decryptionFailureTracker.delegate = testDelegate; + + timeShifter.timestamp = TimeInterval(0) + + let fakeEvent = FakeEvent(id: "$0000"); + fakeEvent.decryptionError = NSError(domain: MXDecryptingErrorDomain, code: Int(MXDecryptingErrorUnknownInboundSessionIdCode.rawValue)) + + + let fakeRoomState = FakeRoomState(); + fakeRoomState.mockMembers = FakeRoomMembers(joined: [myUser]) + decryptionFailureTracker.reportUnableToDecryptError(forEvent: fakeEvent, withRoomState: fakeRoomState, myUser: myUser); + + timeShifter.timestamp = TimeInterval(2) + + // simulate decrypted in the grace period + NotificationCenter.default.post(name: .mxEventDidDecrypt, object: fakeEvent) + + decryptionFailureTracker.checkFailures(); + + XCTAssertNil(testDelegate.reportedFailure); + + // Pass the grace period + timeShifter.timestamp = TimeInterval(5) + + decryptionFailureTracker.checkFailures(); + XCTAssertNil(testDelegate.reportedFailure); + + } + + func test_report_ratcheted_key_utd() { + + let myUser = "test@example.com"; + + let decryptionFailureTracker = DecryptionFailureTracker(); + decryptionFailureTracker.timeProvider = timeShifter; + + let testDelegate = AnalyticsDelegate(); + + decryptionFailureTracker.delegate = testDelegate; + + timeShifter.timestamp = TimeInterval(0) + + let fakeEvent = FakeEvent(id: "$0000"); + fakeEvent.decryptionError = NSError(domain: MXDecryptingErrorDomain, code: Int(MXDecryptingErrorOlmCode.rawValue)) + + + let fakeRoomState = FakeRoomState(); + fakeRoomState.mockMembers = FakeRoomMembers(joined: [myUser]) + decryptionFailureTracker.reportUnableToDecryptError(forEvent: fakeEvent, withRoomState: fakeRoomState, myUser: myUser); + + // Pass the max period + timeShifter.timestamp = TimeInterval(70) + + decryptionFailureTracker.checkFailures(); + + XCTAssertEqual(testDelegate.reportedFailure?.reason, DecryptionFailureReason.olmIndexError); + } + + func test_report_unspecified_error() { + + let myUser = "test@example.com"; + + let decryptionFailureTracker = DecryptionFailureTracker(); + decryptionFailureTracker.timeProvider = timeShifter; + + let testDelegate = AnalyticsDelegate(); + + decryptionFailureTracker.delegate = testDelegate; + + timeShifter.timestamp = TimeInterval(0) + + let fakeEvent = FakeEvent(id: "$0000"); + fakeEvent.decryptionError = NSError(domain: MXDecryptingErrorDomain, code: Int(MXDecryptingErrorBadRoomCode.rawValue)) + + + let fakeRoomState = FakeRoomState(); + fakeRoomState.mockMembers = FakeRoomMembers(joined: [myUser]) + decryptionFailureTracker.reportUnableToDecryptError(forEvent: fakeEvent, withRoomState: fakeRoomState, myUser: myUser); + + // Pass the max period + timeShifter.timestamp = TimeInterval(70) + + decryptionFailureTracker.checkFailures(); + + XCTAssertEqual(testDelegate.reportedFailure?.reason, DecryptionFailureReason.unspecified); + } + + + + func test_do_not_double_report() { + + let myUser = "test@example.com"; + + let decryptionFailureTracker = DecryptionFailureTracker(); + decryptionFailureTracker.timeProvider = timeShifter; + + let testDelegate = AnalyticsDelegate(); + + decryptionFailureTracker.delegate = testDelegate; + + timeShifter.timestamp = TimeInterval(0) + + let fakeEvent = FakeEvent(id: "$0000"); + fakeEvent.decryptionError = NSError(domain: MXDecryptingErrorDomain, code: Int(MXDecryptingErrorUnknownInboundSessionIdCode.rawValue)) + + + let fakeRoomState = FakeRoomState(); + fakeRoomState.mockMembers = FakeRoomMembers(joined: [myUser]) + + decryptionFailureTracker.reportUnableToDecryptError(forEvent: fakeEvent, withRoomState: fakeRoomState, myUser: myUser); + + // Pass the max period + timeShifter.timestamp = TimeInterval(70) + + decryptionFailureTracker.checkFailures(); + + XCTAssertEqual(testDelegate.reportedFailure?.reason, DecryptionFailureReason.olmKeysNotSent); + + // Try to report again the same event + testDelegate.reportedFailure = nil + decryptionFailureTracker.reportUnableToDecryptError(forEvent: fakeEvent, withRoomState: fakeRoomState, myUser: myUser); + // Pass the grace period + timeShifter.timestamp = TimeInterval(10) + + decryptionFailureTracker.checkFailures(); + + XCTAssertNil(testDelegate.reportedFailure); + } + + + func test_ignore_not_member() { + + let myUser = "test@example.com"; + + let decryptionFailureTracker = DecryptionFailureTracker(); + decryptionFailureTracker.timeProvider = timeShifter; + + let testDelegate = AnalyticsDelegate(); + + decryptionFailureTracker.delegate = testDelegate; + + timeShifter.timestamp = TimeInterval(0) + + let fakeEvent = FakeEvent(id: "$0000"); + fakeEvent.decryptionError = NSError(domain: MXDecryptingErrorDomain, code: Int(MXDecryptingErrorUnknownInboundSessionIdCode.rawValue)) + + + let fakeRoomState = FakeRoomState(); + let fakeMembers = FakeRoomMembers() + fakeMembers.mockMembers[myUser] = MXMembership.ban + fakeRoomState.mockMembers = fakeMembers + + decryptionFailureTracker.reportUnableToDecryptError(forEvent: fakeEvent, withRoomState: fakeRoomState, myUser: myUser); + + // Pass the grace period + timeShifter.timestamp = TimeInterval(5) + + decryptionFailureTracker.checkFailures(); + + XCTAssertNil(testDelegate.reportedFailure); + } + + + + func test_notification_center() { + + let myUser = "test@example.com"; + + let decryptionFailureTracker = DecryptionFailureTracker(); + decryptionFailureTracker.timeProvider = timeShifter; + + let testDelegate = AnalyticsDelegate(); + + decryptionFailureTracker.delegate = testDelegate; + + timeShifter.timestamp = TimeInterval(0) + + let fakeEvent = FakeEvent(id: "$0000"); + fakeEvent.decryptionError = NSError(domain: MXDecryptingErrorDomain, code: Int(MXDecryptingErrorUnknownInboundSessionIdCode.rawValue)) + + + let fakeRoomState = FakeRoomState(); + fakeRoomState.mockMembers = FakeRoomMembers(joined: [myUser]) + + decryptionFailureTracker.reportUnableToDecryptError(forEvent: fakeEvent, withRoomState: fakeRoomState, myUser: myUser); + + // Shift time below GRACE_PERIOD + timeShifter.timestamp = TimeInterval(2) + + // Simulate event gets decrypted + NotificationCenter.default.post(name: .mxEventDidDecrypt, object: fakeEvent) + + + // Shift time after GRACE_PERIOD + timeShifter.timestamp = TimeInterval(6) + + + decryptionFailureTracker.checkFailures(); + + // Event should have been graced + XCTAssertNil(testDelegate.reportedFailure); + } + + + func test_should_report_late_decrypt() { + + let myUser = "test@example.com"; + + let decryptionFailureTracker = DecryptionFailureTracker(); + decryptionFailureTracker.timeProvider = timeShifter; + + let testDelegate = AnalyticsDelegate(); + + decryptionFailureTracker.delegate = testDelegate; + + timeShifter.timestamp = TimeInterval(0) + + let fakeEvent = FakeEvent(id: "$0000"); + fakeEvent.decryptionError = NSError(domain: MXDecryptingErrorDomain, code: Int(MXDecryptingErrorUnknownInboundSessionIdCode.rawValue)) + + + let fakeRoomState = FakeRoomState(); + fakeRoomState.mockMembers = FakeRoomMembers(joined: [myUser]) + + decryptionFailureTracker.reportUnableToDecryptError(forEvent: fakeEvent, withRoomState: fakeRoomState, myUser: myUser); + + // Simulate succesful decryption after grace period but before max wait + timeShifter.timestamp = TimeInterval(20) + + // Simulate event gets decrypted + NotificationCenter.default.post(name: .mxEventDidDecrypt, object: fakeEvent) + + + decryptionFailureTracker.checkFailures(); + + // Event should have been reported as a late decrypt + XCTAssertEqual(testDelegate.reportedFailure?.reason, DecryptionFailureReason.olmKeysNotSent); + XCTAssertEqual(testDelegate.reportedFailure?.timeToDecrypt, TimeInterval(20)); + + // Assert that it's converted to millis for reporting + let analyticsError = testDelegate.reportedFailure!.toAnalyticsEvent() + + XCTAssertEqual(analyticsError.timeToDecryptMillis, 20000) + + } + + + + func test_should_report_permanent_decryption_error() { + + let myUser = "test@example.com"; + + let decryptionFailureTracker = DecryptionFailureTracker(); + decryptionFailureTracker.timeProvider = timeShifter; + + let testDelegate = AnalyticsDelegate(); + + decryptionFailureTracker.delegate = testDelegate; + + timeShifter.timestamp = TimeInterval(0) + + let fakeEvent = FakeEvent(id: "$0000"); + fakeEvent.decryptionError = NSError(domain: MXDecryptingErrorDomain, code: Int(MXDecryptingErrorUnknownInboundSessionIdCode.rawValue)) + + + let fakeRoomState = FakeRoomState(); + fakeRoomState.mockMembers = FakeRoomMembers(joined: [myUser]) + + decryptionFailureTracker.reportUnableToDecryptError(forEvent: fakeEvent, withRoomState: fakeRoomState, myUser: myUser); + + // Simulate succesful decryption after max wait + timeShifter.timestamp = TimeInterval(70) + + decryptionFailureTracker.checkFailures(); + + // Event should have been reported as a late decrypt + XCTAssertEqual(testDelegate.reportedFailure?.reason, DecryptionFailureReason.olmKeysNotSent); + XCTAssertNil(testDelegate.reportedFailure?.timeToDecrypt); + + + // Assert that it's converted to -1 for reporting + let analyticsError = testDelegate.reportedFailure!.toAnalyticsEvent() + + XCTAssertEqual(analyticsError.timeToDecryptMillis, -1) + + } +} + diff --git a/RiotTests/FakeUtils.swift b/RiotTests/FakeUtils.swift new file mode 100644 index 0000000000..7bd350e4bc --- /dev/null +++ b/RiotTests/FakeUtils.swift @@ -0,0 +1,109 @@ +// +// 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 + + +class FakeEvent: MXEvent { + + var mockEventId: String; + var mockSender: String!; + var mockDecryptionError: Error? + + init(id: String) { + mockEventId = id + super.init() + } + + required init?(coder: NSCoder) { + fatalError() + } + + override var sender: String! { + get { return mockSender } + set { mockSender = newValue } + } + + override var eventId: String! { + get { return mockEventId } + set { mockEventId = newValue } + } + + override var decryptionError: Error? { + get { return mockDecryptionError } + set { mockDecryptionError = newValue } + } + +} + + +class FakeRoomState: MXRoomState { + + var mockMembers: MXRoomMembers? + + override var members: MXRoomMembers? { + get { return mockMembers } + set { mockMembers = newValue } + } + +} + +class FakeRoomMember: MXRoomMember { + var mockMembership: MXMembership = MXMembership.join + var mockUserId: String! + var mockMembers: MXRoomMembers? = FakeRoomMembers() + + init(mockUserId: String!) { + self.mockUserId = mockUserId + super.init() + } + + override var membership: MXMembership { + get { return mockMembership } + set { mockMembership = newValue } + } + + override var userId: String!{ + get { return mockUserId } + set { mockUserId = newValue } + } + +} + + +class FakeRoomMembers: MXRoomMembers { + + var mockMembers = [String : MXMembership]() + + init(joined: [String] = [String]()) { + for userId in joined { + self.mockMembers[userId] = MXMembership.join + } + super.init() + } + + override func member(withUserId userId: String!) -> MXRoomMember? { + let membership = mockMembers[userId] + if membership != nil { + let mockMember = FakeRoomMember(mockUserId: userId) + mockMember.mockMembership = membership! + return mockMember + } else { + return nil + } + } + +} diff --git a/TCHAP_CHANGES.md b/TCHAP_CHANGES.md index 1fb7fedd31..34a49f547b 100644 --- a/TCHAP_CHANGES.md +++ b/TCHAP_CHANGES.md @@ -1,3 +1,19 @@ +## Changes in 2.7.3 (2024-04-02) + +🙌 Improvements + +- Rename RiotNSE extension product name to TchapNSE. It will change user-agent of requests in backend logs. ([#664](https://github.com/tchapgouv/tchap-ios/issues/664)) +- RĂ©activation de l'antivirus ([#887](https://github.com/tchapgouv/tchap-ios/issues/887)) +- Activation du partage de gĂ©olocalisation ([#970](https://github.com/tchapgouv/tchap-ios/issues/970)) +- Mettre une icĂŽne plus adaptĂ©e sur le bouton "Signaler un problĂšme" lors d'un appel VoIP ([#974](https://github.com/tchapgouv/tchap-ios/issues/974)) +- Changement de la formulation du bouton des rĂ©glages pour autoriser les notifications sur l'appareil. Ajout d'un texte explicatif. ([#975](https://github.com/tchapgouv/tchap-ios/issues/975)) +- Changer le message d'erreur affichĂ© en cas de problĂšme de dĂ©chiffrement ([#976](https://github.com/tchapgouv/tchap-ios/issues/976)) +- Modifier l'intitulĂ© de dĂ©sactivation de compte ([#982](https://github.com/tchapgouv/tchap-ios/issues/982)) +- Utiliser le bouton de validation Tchap en Ă©dition de message. ([#986](https://github.com/tchapgouv/tchap-ios/issues/986)) +- La "notification par email" est rendue disponible Ă  tout le monde. ([#995](https://github.com/tchapgouv/tchap-ios/issues/995)) +- Ajout des textes d'alerte d'activation de la gĂ©olocalisation dans les targets Btchap et DevTchap ([#998](https://github.com/tchapgouv/tchap-ios/issues/998)) + + ## Changes in 2.7.2 (2024-02-26) 🙌 Improvements diff --git a/Tchap/Assets/Localizations/fr.lproj/Tchap.strings b/Tchap/Assets/Localizations/fr.lproj/Tchap.strings index d4b803f5fb..faf42d7c05 100644 --- a/Tchap/Assets/Localizations/fr.lproj/Tchap.strings +++ b/Tchap/Assets/Localizations/fr.lproj/Tchap.strings @@ -247,10 +247,13 @@ //////////////////////////////////////////////////////////////////////////////// // MARK: Expired Account -"expired_account_alert_message" = "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."; -"expired_account_resume_button" = "J’ai renouvelĂ© mon compte"; -"expired_account_request_renewal_email_button" = "Demander l’envoi d’un nouvel email"; -"expired_account_on_new_sent_email_msg" = "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."; +"expired_account_alert_title" = "Votre compte a expirĂ©"; +"expired_account_alert_message" = "Un email vous a Ă©tĂ© envoyĂ© pour renouveler votre compte. Une fois que vous aurez suivi le lien qu’il contient, cliquez ci-dessous."; +"expired_account_resume_button" = "Continuer"; +"expired_account_request_renewal_email_button" = "Emvoyer un nouvel email"; +"expired_account_on_new_sent_email_title" = "Email envoyĂ©"; +"expired_account_on_new_sent_email_message" = "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."; +"expired_account_on_new_sent_email_button" = "Continuer"; //////////////////////////////////////////////////////////////////////////////// // MARK: Change password @@ -316,22 +319,33 @@ //////////////////////////////////////////////////////////////////////////////// // MARK: Room Decryption error -"room_decryption_error_faq_link_message" = "Sinon, consulter cet article de FAQ."; +"room_decryption_error_faq_link_message" = "En savoir plus."; //////////////////////////////////////////////////////////////////////////////// // MARK: Room Invite -"room_invite_error_action_forbidden" = "Cet utilisateur n'est pas autorisĂ© Ă  rejoindre ce salon."; +"room_invite_error_action_forbidden" = "Cet utilisateur est dĂ©jĂ  membre du salon ou n'est pas autorisĂ© Ă  le rejoindre."; "room_invite_search_consign" = "Veuillez saisir le nom d'un correspondant pour le rechercher dans l'annuaire"; //////////////////////////////////////////////////////////////////////////////// // MARK: Room Invite "security_cross_signing_setup_title" = "Activer la signature croisĂ©e"; -"security_cross_signing_reset_title" = "Êtes-vous sur ?"; +"security_cross_signing_reset_title" = "Êtes-vous sĂ»r ?"; "security_cross_signing_reset_message" = "Faites cette opĂ©ration seulement si vous avez perdu tous vos autres appareils vĂ©rifiĂ©s."; "security_cross_signing_reset_action_title" = "RĂ©initialiser"; +//////////////////////////////////////////////////////////////////////////////// +// MARK: Room send file +"room_send_file_too_big_title" = "Erreur d'envoi"; +"room_send_file_too_big_message" = "Le fichier est trop lourd pour ĂȘtre envoyĂ©. La taille limite est de %ldMo, mais la taille de votre fichier est de %ldMo."; + //////////////////////////////////////////////////////////////////////////////// // MARK: VoIP "event_formatter_report_incident" = "Signaler un problĂšme"; "void_report_incident_title" = "Signaler un problĂšme VoIP"; "void_report_incident_description" = "Vous avez rencontrĂ© un souci durant votre appel VoIP. Dites-nous ce qui s'est passĂ© :"; + +//////////////////////////////////////////////////////////////////////////////// +// MARK: Settings +"settings_enable_push_notif_text" = "Sans cette autorisation, les appels entrants ne seront pas notifiĂ©s."; +"settings_enable_email_notif_text" = "Recevez un e-mail si au moins un message rĂ©cent non lu pendant 72h."; +"settings_enable_email_notif_link" = "En savoir plus."; diff --git a/Tchap/Config/BuildSettings.swift b/Tchap/Config/BuildSettings.swift index b5b7bd85ca..51a0c2008c 100644 --- a/Tchap/Config/BuildSettings.swift +++ b/Tchap/Config/BuildSettings.swift @@ -267,19 +267,23 @@ final class BuildSettings: NSObject { static let tchapFeatureNotificationByEmail = "tchapFeatureNotificationByEmail" static let tchapFeatureVoiceOverIP = "tchapFeatureVoiceOverIP" static let tchapFeatureVideoOverIP = "tchapFeatureVideoOverIP" + static let tchapFeatureGeolocationSharing = "tchapFeatureGeolocationSharing" // linked to `locationSharingEnabled` property (see above) static var tchapFeaturesAllowedHomeServersForFeature: [String: [String]] = [ tchapFeatureNotificationByEmail: [ - "agent.dinum.tchap.gouv.fr" + tchapFeatureAnyHomeServer ], tchapFeatureVoiceOverIP: [ "agent.dinum.tchap.gouv.fr", "agent.diplomatie.tchap.gouv.fr", "agent.finances.tchap.gouv.fr" - ] + ], // No activation of video calls actually in Tchap Production. // tchapFeatureVideoOverIP: [ // "agent.dinum.tchap.gouv.fr" // ], + tchapFeatureGeolocationSharing: [ + tchapFeatureAnyHomeServer + ] ] // MARK: - Side Menu @@ -454,9 +458,14 @@ final class BuildSettings: NSObject { // MARK: - Location Sharing /// Overwritten by the home server's .well-known configuration (if any exists) - static let defaultTileServerMapStyleURL = URL(string: "https://api.maptiler.com/maps/streets/style.json?key=")! - - static let locationSharingEnabled = false // Currently disabled in Tchap. + // Tchap: handle different map providers. + private enum TchapMapProvider: String { + case geoDataGouv = "https://openmaptiles.geo.data.gouv.fr/styles/osm-bright/style.json" + case ign = "https://data.geopf.fr/annexes/ressources/vectorTiles/styles/PLAN.IGN/standard.json" + } + static let defaultTileServerMapStyleURL = URL(string: TchapMapProvider.geoDataGouv.rawValue)! + + static let locationSharingEnabled = true // MARK: - Voice Broadcast static let voiceBroadcastChunkLength: Int = 120 diff --git a/Tchap/Generated/InfoPlist.swift b/Tchap/Generated/InfoPlist.swift index d65235e8cb..fd4913b7ce 100644 --- a/Tchap/Generated/InfoPlist.swift +++ b/Tchap/Generated/InfoPlist.swift @@ -30,6 +30,8 @@ internal enum InfoPlist { internal static let nsCameraUsageDescription: String = _document["NSCameraUsageDescription"] internal static let nsContactsUsageDescription: String = _document["NSContactsUsageDescription"] internal static let nsFaceIDUsageDescription: String = _document["NSFaceIDUsageDescription"] + internal static let nsLocationAlwaysAndWhenInUseUsageDescription: String = _document["NSLocationAlwaysAndWhenInUseUsageDescription"] + internal static let nsLocationWhenInUseUsageDescription: String = _document["NSLocationWhenInUseUsageDescription"] internal static let nsMicrophoneUsageDescription: String = _document["NSMicrophoneUsageDescription"] internal static let nsPhotoLibraryUsageDescription: String = _document["NSPhotoLibraryUsageDescription"] internal static let uiBackgroundModes: [String] = _document["UIBackgroundModes"] diff --git a/Tchap/Generated/Strings.swift b/Tchap/Generated/Strings.swift index dd83433c6e..9f27d360de 100644 --- a/Tchap/Generated/Strings.swift +++ b/Tchap/Generated/Strings.swift @@ -271,6 +271,10 @@ public class TchapL10n: NSObject { public static var errorTitleDefault: String { return TchapL10n.tr("Tchap", "error_title_default") } + /// Signaler un problĂšme + 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. public static var expiredAccountAlertMessage: String { return TchapL10n.tr("Tchap", "expired_account_alert_message") @@ -563,7 +567,7 @@ public class TchapL10n: NSObject { public static var roomCreationTitle: String { return TchapL10n.tr("Tchap", "room_creation_title") } - /// Sinon, consulter cet article de FAQ. + /// En savoir plus. public static var roomDecryptionErrorFaqLinkMessage: String { return TchapL10n.tr("Tchap", "room_decryption_error_faq_link_message") } @@ -743,7 +747,7 @@ public class TchapL10n: NSObject { public static var securityCrossSigningResetMessage: String { return TchapL10n.tr("Tchap", "security_cross_signing_reset_message") } - /// Êtes-vous sur ? + /// Êtes-vous sĂ»r ? public static var securityCrossSigningResetTitle: String { return TchapL10n.tr("Tchap", "security_cross_signing_reset_title") } @@ -791,6 +795,10 @@ public class TchapL10n: NSObject { public static var settingsCryptoImportInvalidFile: String { return TchapL10n.tr("Tchap", "settings_crypto_import_invalid_file") } + /// 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") + } /// Les autres utilisateurs ne pourront pas dĂ©couvrir mon compte lors de leurs recherches public static var settingsHideFromUsersDirectorySummary: String { return TchapL10n.tr("Tchap", "settings_hide_from_users_directory_summary") @@ -855,6 +863,14 @@ public class TchapL10n: NSObject { public static var tchapRoomInvalidLink: String { return TchapL10n.tr("Tchap", "tchap_room_invalid_link") } + /// Vous avez rencontrĂ© un souci durant votre appel VoIP. Dites-nous ce qui s'est passĂ© : + public static var voidReportIncidentDescription: String { + return TchapL10n.tr("Tchap", "void_report_incident_description") + } + /// Signaler un problĂšme VoIP + public static var voidReportIncidentTitle: String { + return TchapL10n.tr("Tchap", "void_report_incident_title") + } /// Attention public static var warningTitle: String { return TchapL10n.tr("Tchap", "warning_title") diff --git a/Tchap/Modules/RoomPreview/RoomPreviewCoordinator.swift b/Tchap/Modules/RoomPreview/RoomPreviewCoordinator.swift index c0103af40e..5c345ebed2 100644 --- a/Tchap/Modules/RoomPreview/RoomPreviewCoordinator.swift +++ b/Tchap/Modules/RoomPreview/RoomPreviewCoordinator.swift @@ -220,6 +220,15 @@ final class RoomPreviewCoordinator: NSObject, RoomPreviewCoordinatorType { // MARK: - RoomViewControllerDelegate extension RoomPreviewCoordinator: RoomViewControllerDelegate { + + func roomViewController(_ roomViewController: RoomViewController, didRequestLiveLocationPresentationForBubbleData bubbleData: MXKRoomBubbleCellDataStoring) { + // + } + + func roomViewControllerDidStopLiveLocationSharing(_ roomViewController: RoomViewController, beaconInfoEventId: String?) { + // + } + func roomViewController(_ roomViewController: RoomViewController, showRoomWithId roomID: String, eventId eventID: String?) { // } diff --git a/Tchap/SupportingFiles/Info.plist b/Tchap/SupportingFiles/Info.plist index 87e719493e..666cd98359 100644 --- a/Tchap/SupportingFiles/Info.plist +++ b/Tchap/SupportingFiles/Info.plist @@ -53,12 +53,17 @@ In order to show who among your contacts already uses Tchap, we can exploit the e-mail addresses of your address book. These data will not be stored. For more information, please visit the privacy policy page in the app settings NSFaceIDUsageDescription Face ID is used to access your app. + NSLocationAlwaysAndWhenInUseUsageDescription + When you share your location to people, Tchap needs access to show them a map. + NSLocationWhenInUseUsageDescription + When you share your location to people, Tchap needs access to show them a map. NSMicrophoneUsageDescription Tchap needs to access your microphone to take videos, and record voice messages. NSPhotoLibraryUsageDescription This allows you to select pictures or videos from the photo library, and send them in your conversations. You can also use one of these pictures to set your profile picture. UIBackgroundModes + location remote-notification voip diff --git a/Tchap/SupportingFiles/PrivacyInfo.xcprivacy b/Tchap/SupportingFiles/PrivacyInfo.xcprivacy new file mode 100644 index 0000000000..500ae9affe --- /dev/null +++ b/Tchap/SupportingFiles/PrivacyInfo.xcprivacy @@ -0,0 +1,41 @@ + + + + + NSPrivacyAccessedAPITypes + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryUserDefaults + NSPrivacyAccessedAPITypeReasons + + 1C8F.1 + + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryFileTimestamp + NSPrivacyAccessedAPITypeReasons + + C617.1 + + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryDiskSpace + NSPrivacyAccessedAPITypeReasons + + 7D9E.1 + + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategorySystemBootTime + NSPrivacyAccessedAPITypeReasons + + 3D61.1 + + + + + diff --git a/Tchap/target.yml b/Tchap/target.yml index b94a4cfd3c..9f497793c1 100644 --- a/Tchap/target.yml +++ b/Tchap/target.yml @@ -53,6 +53,7 @@ targetTemplates: - package: SwiftOGG - package: WysiwygComposer - package: AnalyticsEvents + - package: Mapbox preBuildScripts: - name: 🛠 Environment @@ -245,6 +246,7 @@ targetTemplates: - path: ../Riot/Modules/KeyBackup - path: ../Riot/Modules/KeyVerification - path: ../Riot/Modules/LaunchLoading + - path: ../Riot/Modules/LocationSharing - path: ../Riot/Modules/MatrixKit - path: ../Riot/Modules/MediaPicker - path: ../Riot/Modules/MediaPickerV2 @@ -257,13 +259,13 @@ targetTemplates: - path: ../Riot/Modules/Rendezvous - path: ../Riot/Modules/Room excludes: - - "Location" - - "RoomViewController+LocationSharing.swift" - - "TimelineCells/LocationView" - - "TimelineCells/Styles/Plain/Cells/Location" - - "TimelineCells/Styles/Bubble/Cells/Location" - - "Views/BubbleCells/KeyVerification/SizingViewHeight.swift" - - "Views/BubbleCells/Location" +# - "Location" +# - "RoomViewController+LocationSharing.swift" +# - "TimelineCells/LocationView" +# - "TimelineCells/Styles/Plain/Cells/Location" +# - "TimelineCells/Styles/Bubble/Cells/Location" +# - "Views/BubbleCells/KeyVerification/SizingViewHeight.swift" +# - "Views/BubbleCells/Location" - path: ../Riot/Modules/Rooms - path: ../Riot/Modules/Secrets - path: ../Riot/Modules/SecureBackup @@ -294,7 +296,7 @@ targetTemplates: excludes: - "**/Test/**" - "Common/Locale/LocaleProvider.swift" - - "LocationSharing" +# - "LocationSharing" - "Room/LiveLocationSharingViewer" - "Room/LocationSharing" - "Room/StaticLocationSharingViewer" diff --git a/TchapNSE/Common.xcconfig b/TchapNSE/Common.xcconfig index 5fb618d128..19b42ed0c8 100644 --- a/TchapNSE/Common.xcconfig +++ b/TchapNSE/Common.xcconfig @@ -19,7 +19,10 @@ #include "Tchap/SupportingFiles/App-Common.xcconfig" -PRODUCT_NAME = RiotNSE + +// Tchap: Customize TchapNSE user-agent for back-end logs. +// PRODUCT_NAME = RiotNSE +PRODUCT_NAME = TchapNSE PRODUCT_BUNDLE_IDENTIFIER = $(BASE_BUNDLE_IDENTIFIER).nse INFOPLIST_FILE = RiotNSE/Info.plist @@ -27,5 +30,8 @@ INFOPLIST_FILE = RiotNSE/Info.plist CODE_SIGN_ENTITLEMENTS = RiotNSE/RiotNSE.entitlements SKIP_INSTALL = YES -SWIFT_OBJC_BRIDGING_HEADER = $(SRCROOT)/$(PRODUCT_NAME)/SupportingFiles/RiotNSE-Bridging-Header.h + +// Tchap: Customize TchapNSE user-agent for back-end logs. +// SWIFT_OBJC_BRIDGING_HEADER = $(SRCROOT)/$(PRODUCT_NAME)/SupportingFiles/RiotNSE-Bridging-Header.h +SWIFT_OBJC_BRIDGING_HEADER = $(SRCROOT)/RiotNSE/SupportingFiles/RiotNSE-Bridging-Header.h diff --git a/changelog.d/1000.change b/changelog.d/1000.change new file mode 100644 index 0000000000..83cb7789ff --- /dev/null +++ b/changelog.d/1000.change @@ -0,0 +1 @@ +Afficher le salon "Tchap Annonces" dans le flux normal des salons \ No newline at end of file diff --git a/changelog.d/1002.change b/changelog.d/1002.change new file mode 100644 index 0000000000..99144a92cc --- /dev/null +++ b/changelog.d/1002.change @@ -0,0 +1 @@ +Ajout d'un "Privacy Manifest" dans le projet Xcode (obligatoire Ă  partir du 1er mai 2024) \ No newline at end of file diff --git a/changelog.d/1009.change b/changelog.d/1009.change new file mode 100644 index 0000000000..d09358cb85 --- /dev/null +++ b/changelog.d/1009.change @@ -0,0 +1 @@ +L'affichage de gĂ©olocalisation plante quand l'affichage en bulle est dĂ©sactivĂ© \ No newline at end of file diff --git a/changelog.d/1015.change b/changelog.d/1015.change new file mode 100644 index 0000000000..9dd68d1fa8 --- /dev/null +++ b/changelog.d/1015.change @@ -0,0 +1 @@ +Afficher un message d'alerte avant envoi d'une piĂšce jointe trop lourde. \ No newline at end of file diff --git a/changelog.d/1017.change b/changelog.d/1017.change new file mode 100644 index 0000000000..bb319aecfb --- /dev/null +++ b/changelog.d/1017.change @@ -0,0 +1 @@ +Ajout de la gĂ©olocalisation en background sur les targets Dev et Pre-prod \ No newline at end of file diff --git a/changelog.d/1019.change b/changelog.d/1019.change new file mode 100644 index 0000000000..d26acdd760 --- /dev/null +++ b/changelog.d/1019.change @@ -0,0 +1 @@ +Mauvais libellĂ© de fermeture de compte en anglais. \ No newline at end of file diff --git a/changelog.d/1021.change b/changelog.d/1021.change new file mode 100644 index 0000000000..f56b6a6b2f --- /dev/null +++ b/changelog.d/1021.change @@ -0,0 +1 @@ +AmĂ©liorer les textes du parcours de renouvellement de compte. \ No newline at end of file diff --git a/changelog.d/1022.change b/changelog.d/1022.change new file mode 100644 index 0000000000..56a47497b4 --- /dev/null +++ b/changelog.d/1022.change @@ -0,0 +1 @@ +Mauvais message d'erreur quand on invite un utilisateur dĂ©jĂ  prĂ©sent dans un salon \ No newline at end of file diff --git a/changelog.d/1027.change b/changelog.d/1027.change new file mode 100644 index 0000000000..f56b6a6b2f --- /dev/null +++ b/changelog.d/1027.change @@ -0,0 +1 @@ +AmĂ©liorer les textes du parcours de renouvellement de compte. \ No newline at end of file diff --git a/changelog.d/1029.change b/changelog.d/1029.change new file mode 100644 index 0000000000..59871594a9 --- /dev/null +++ b/changelog.d/1029.change @@ -0,0 +1 @@ +AmĂ©liorer la comprĂ©hension du fonctionnement des notifications par email. \ No newline at end of file diff --git a/changelog.d/832.change b/changelog.d/832.change new file mode 100644 index 0000000000..72cdd7a085 --- /dev/null +++ b/changelog.d/832.change @@ -0,0 +1 @@ +Limiter la longueur du message d'origine Ă  l'affichage d'une rĂ©ponse avec citation. \ No newline at end of file diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 973e2ea63f..79cea6c24b 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -21,7 +21,7 @@ platform :ios do before_all do # Ensure used Xcode version - xcversion(version: "14.2") + xcversion(version: "15.2") end #### Public #### @@ -142,7 +142,7 @@ platform :ios do run_tests( workspace: "Tchap.xcworkspace", scheme: "RiotSwiftUITests", - device: "iPhone 14", + device: "iPhone 15", code_coverage: true, # Test result configuration result_bundle: true, diff --git a/project.yml b/project.yml index c34413a687..60a1bfb830 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.5.0 + exactVersion: 0.15.0 Mapbox: url: https://github.com/maplibre/maplibre-gl-native-distribution minVersion: 5.12.2 @@ -66,7 +66,7 @@ packages: branch: 0.0.1 WysiwygComposer: url: https://github.com/matrix-org/matrix-wysiwyg-composer-swift - version: 2.19.0 + version: 2.29.0 DeviceKit: url: https://github.com/devicekit/DeviceKit majorVersion: 4.7.0